| /* | |
| * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package jdk.jshell; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.List; | |
| import static java.util.stream.Collectors.joining; | |
| import static jdk.jshell.Util.DOIT_METHOD_NAME; | |
| /** | |
| * Wrapping of source into Java methods, fields, etc. All but outer layer | |
| * wrapping with imports and class. | |
| */ | |
| abstract class Wrap implements GeneralWrap { | |
| private static Wrap methodWrap(String prefix, String source, String suffix) { | |
| Wrap wunit = new NoWrap(source); | |
| return new DoitMethodWrap(new CompoundWrap(prefix, wunit, suffix)); | |
| } | |
| public static Wrap methodWrap(String source) { | |
| return methodWrap("", source, semi(source) + " return null;\n"); | |
| } | |
| public static Wrap methodReturnWrap(String source) { | |
| return methodWrap("return ", source, semi(source)); | |
| } | |
| public static Wrap methodUnreachableSemiWrap(String source) { | |
| return methodWrap("", source, semi(source)); | |
| } | |
| public static Wrap methodUnreachableWrap(String source) { | |
| return methodWrap("", source, ""); | |
| } | |
| private static String indent(int n) { | |
| return " ".substring(0, n * 4); | |
| } | |
| private static String nlindent(int n) { | |
| return "\n" + indent(n); | |
| } | |
| /**Create a stub of a compilable representation of a variable snippet. | |
| * The variable is always represented by a field. If the variable | |
| * in the snippet has an initializer, the field is initialized by | |
| * calling the DOIT_METHOD_NAME method. | |
| * | |
| * In some cases, the real inferred type of the variable may be non-denotable | |
| * (e.g. intersection types). The declared type of the field must always | |
| * be denotable (i.e. such that it can be written into the classfile), but | |
| * if the real type is potentially non-denotable, the {@code enhanced} parameter | |
| * must be true. | |
| * | |
| * @param source the snippet's masked source code | |
| * @param wtype variable's denotable type suitable for field declaration | |
| * @param brackets any [] that should be appended to the type | |
| * @param wname a wrap of the source that denotes the name of the variable | |
| * @param winit Initializer or null | |
| * @param enhanced if the real inferred type of the variable is potentially | |
| * non-denotable, this must be true | |
| * @return a Wrap that declares the given variable, potentially with | |
| * an initialization method | |
| */ | |
| public static Wrap varWrap(String source, Wrap wtype, String brackets, | |
| Wrap wname, Wrap winit, boolean enhanced, | |
| Wrap anonDeclareWrap) { | |
| List<Object> components = new ArrayList<>(); | |
| components.add(new VarDeclareWrap(wtype, brackets, wname)); | |
| Wrap wmeth; | |
| if (winit == null) { | |
| wmeth = new CompoundWrap(new NoWrap(" "), " return null;\n"); | |
| } else { | |
| // int x = y | |
| if (enhanced) { | |
| // private static <Z> Z do_itAux() { | |
| // wtype x_ = y; | |
| // @SuppressWarnings("unchecked") | |
| // Z x__ = (Z) x_; | |
| // return x__; | |
| // } | |
| // in do_it method: | |
| //return do_itAux(); | |
| //find an unused name: | |
| String scratchName = "$"; | |
| while (winit.wrapped().contains(scratchName)) { | |
| scratchName += "$"; | |
| } | |
| Wrap waux = new CompoundWrap( | |
| " private static <" + scratchName + "> " + scratchName +" ", DOIT_METHOD_NAME + "Aux", "() throws java.lang.Throwable {\n", | |
| wtype, brackets + " ", scratchName, "_ =\n ", winit, semi(winit), | |
| " @java.lang.SuppressWarnings(\"unchecked\") ", scratchName, " ", scratchName, "__ = (", scratchName, ")", scratchName, "_;\n", | |
| " return ", scratchName, "__;\n", | |
| "}" | |
| ); | |
| components.add(waux); | |
| wmeth = new CompoundWrap( | |
| " return ", wname, " = ", DOIT_METHOD_NAME + "Aux", "();\n" | |
| ); | |
| } else { | |
| // int x_ = y; return x = x_; | |
| // decl + "_ = " + init ; + "return " + name + "= " + name + "_ ;" | |
| wmeth = new CompoundWrap( | |
| wtype, brackets + " ", wname, "_ =\n ", winit, semi(winit), | |
| " return ", wname, " = ", wname, "_;\n" | |
| ); | |
| } | |
| } | |
| components.add(new DoitMethodWrap(wmeth)); | |
| if (anonDeclareWrap != null) { | |
| components.add(anonDeclareWrap); | |
| } | |
| return new CompoundWrap(components.toArray()); | |
| } | |
| public static Wrap tempVarWrap(String source, String typename, String name, Wrap anonDeclareWrap) { | |
| RangeWrap winit = new NoWrap(source); | |
| // y | |
| // return $1 = y; | |
| // "return " + $1 + "=" + init ; | |
| Wrap wmeth = new CompoundWrap("return " + name + " =\n ", winit, semi(winit)); | |
| Wrap wInitMeth = new DoitMethodWrap(wmeth); | |
| String varDecl = " public static\n " + typename + " " + name + ";\n"; | |
| return anonDeclareWrap != null ? new CompoundWrap(varDecl, wInitMeth, anonDeclareWrap) | |
| : new CompoundWrap(varDecl, wInitMeth); | |
| } | |
| public static Wrap simpleWrap(String source) { | |
| return new NoWrap(source); | |
| } | |
| public static Wrap identityWrap(String source) { | |
| return new NoWrap(source); | |
| } | |
| public static Wrap rangeWrap(String source, Range range) { | |
| return new RangeWrap(source, range); | |
| } | |
| public static Wrap classMemberWrap(String source) { | |
| Wrap w = new NoWrap(source); | |
| return new CompoundWrap(" public static\n ", w); | |
| } | |
| private static int countLines(String s) { | |
| return countLines(s, 0, s.length()); | |
| } | |
| private static int countLines(String s, int from, int toEx) { | |
| int cnt = 0; | |
| int idx = from; | |
| while ((idx = s.indexOf('\n', idx)) > 0) { | |
| if (idx >= toEx) break; | |
| ++cnt; | |
| ++idx; | |
| } | |
| return cnt; | |
| } | |
| public static final class Range { | |
| final int begin; | |
| final int end; // exclusive | |
| Range(int begin, int end) { | |
| this.begin = begin; | |
| this.end = end; | |
| } | |
| Range(String s) { | |
| this.begin = 0; | |
| this.end = s.length(); | |
| } | |
| String part(String s) { | |
| return s.substring(begin, end); | |
| } | |
| int length() { | |
| return end - begin; | |
| } | |
| boolean isEmpty() { | |
| return end == begin; | |
| } | |
| void verify(String s) { | |
| if (begin < 0 || end <= begin || end > s.length()) { | |
| throw new InternalError("Bad Range: " + s + "[" + begin + "," + end + "]"); | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| return "Range[" + begin + "," + end + ")"; | |
| } | |
| } | |
| public static class CompoundWrap extends Wrap { | |
| final Object[] os; | |
| final String wrapped; | |
| final int snidxFirst; | |
| final int snidxLast; | |
| final int snlineFirst; | |
| final int snlineLast; | |
| CompoundWrap(Object... os) { | |
| this.os = os; | |
| int sniFirst = Integer.MAX_VALUE; | |
| int sniLast = Integer.MIN_VALUE; | |
| int snlnFirst = Integer.MAX_VALUE; | |
| int snlnLast = Integer.MIN_VALUE; | |
| StringBuilder sb = new StringBuilder(); | |
| for (Object o : os) { | |
| if (o instanceof String) { | |
| String s = (String) o; | |
| sb.append(s); | |
| } else if (o instanceof Wrap) { | |
| Wrap w = (Wrap) o; | |
| if (w.firstSnippetIndex() < sniFirst) { | |
| sniFirst = w.firstSnippetIndex(); | |
| } | |
| if (w.lastSnippetIndex() > sniLast) { | |
| sniLast = w.lastSnippetIndex(); | |
| } | |
| if (w.firstSnippetLine() < snlnFirst) { | |
| snlnFirst = w.firstSnippetLine(); | |
| } | |
| if (w.lastSnippetLine() > snlnLast) { | |
| snlnLast = w.lastSnippetLine(); | |
| } | |
| sb.append(w.wrapped()); | |
| } else { | |
| throw new InternalError("Bad object in CommoundWrap: " + o); | |
| } | |
| } | |
| this.wrapped = sb.toString(); | |
| this.snidxFirst = sniFirst; | |
| this.snidxLast = sniLast; | |
| this.snlineFirst = snlnFirst; | |
| this.snlineLast = snlnLast; | |
| } | |
| @Override | |
| public String wrapped() { | |
| return wrapped; | |
| } | |
| @Override | |
| public int snippetIndexToWrapIndex(int sni) { | |
| int before = 0; | |
| for (Object o : os) { | |
| if (o instanceof String) { | |
| String s = (String) o; | |
| before += s.length(); | |
| } else if (o instanceof Wrap) { | |
| Wrap w = (Wrap) o; | |
| if (sni >= w.firstSnippetIndex() && sni < w.lastSnippetIndex()) { | |
| int wwi = w.snippetIndexToWrapIndex(sni); | |
| debugWrap("\nCommoundWrap.snippetIndexToWrapIndex: SnippetIndex(%d) -> WrapIndex(%d + %d = %d)" | |
| + "\n === %s", | |
| sni, wwi, before, wwi + before, wrapped()); | |
| return wwi + before; | |
| } | |
| before += w.wrapped().length(); | |
| } | |
| } | |
| return 0; | |
| } | |
| Wrap wrapIndexToWrap(long wi) { | |
| int before = 0; | |
| Wrap w = null; | |
| for (Object o : os) { | |
| if (o instanceof String) { | |
| String s = (String) o; | |
| before += s.length(); | |
| } else if (o instanceof Wrap) { | |
| w = (Wrap) o; | |
| int len = w.wrapped().length(); | |
| if ((wi - before) <= len) { | |
| debugWrap("CommoundWrap.wrapIndexToWrap: Defer to wrap %s - wi: %d. before; %d >>> %s\n", | |
| w, wi, before, w.wrapped()); | |
| return w; | |
| } | |
| before += len; | |
| } | |
| } | |
| return w; | |
| } | |
| @Override | |
| public int wrapIndexToSnippetIndex(int wi) { | |
| int before = 0; | |
| for (Object o : os) { | |
| if (o instanceof String) { | |
| String s = (String) o; | |
| before += s.length(); | |
| } else if (o instanceof Wrap) { | |
| Wrap w = (Wrap) o; | |
| int len = w.wrapped().length(); | |
| if ((wi - before) <= len) { | |
| int si = w.wrapIndexToSnippetIndex(wi - before); | |
| debugWrap("\nCommoundWrap.wrapIndexToSnippetIndex: WrapIndex(%d) -> SnippetIndex(%d)\n", | |
| wi, si); | |
| return si; | |
| } | |
| before += len; | |
| } | |
| } | |
| return lastSnippetIndex(); | |
| } | |
| @Override | |
| public int firstSnippetIndex() { | |
| return snidxFirst; | |
| } | |
| @Override | |
| public int lastSnippetIndex() { | |
| return snidxLast; | |
| } | |
| @Override | |
| public int snippetLineToWrapLine(int snline) { | |
| int before = 0; | |
| for (Object o : os) { | |
| if (o instanceof String) { | |
| String s = (String) o; | |
| before += countLines(s); | |
| } else if (o instanceof Wrap) { | |
| Wrap w = (Wrap) o; | |
| if (snline >= w.firstSnippetLine() && snline <= w.lastSnippetLine()) { | |
| return w.snippetLineToWrapLine(snline) + before; | |
| } | |
| before += countLines(w.wrapped()); | |
| } | |
| } | |
| return 0; | |
| } | |
| Wrap wrapLineToWrap(int wline) { | |
| int before = 0; | |
| Wrap w = null; | |
| for (Object o : os) { | |
| if (o instanceof String) { | |
| String s = (String) o; | |
| before += countLines(s); | |
| } else if (o instanceof Wrap) { | |
| w = (Wrap) o; | |
| int lns = countLines(w.wrapped()); | |
| if ((wline - before) <= lns) { | |
| return w; | |
| } | |
| before += lns; | |
| } | |
| } | |
| return w; | |
| } | |
| @Override | |
| public int wrapLineToSnippetLine(int wline) { | |
| int before = 0; | |
| for (Object o : os) { | |
| if (o instanceof String) { | |
| String s = (String) o; | |
| before += countLines(s); | |
| } else if (o instanceof Wrap) { | |
| Wrap w = (Wrap) o; | |
| int lns = countLines(w.wrapped()); | |
| if ((wline - before) <= lns) { | |
| return w.wrapLineToSnippetLine(wline - before); | |
| } | |
| before += lns; | |
| } | |
| } | |
| return 0; | |
| } | |
| @Override | |
| public int firstSnippetLine() { | |
| return snlineFirst; | |
| } | |
| @Override | |
| public int lastSnippetLine() { | |
| return snlineLast; | |
| } | |
| @Override | |
| public String toString() { | |
| return "CompoundWrap(" + Arrays.stream(os) | |
| .map(o -> (o instanceof String) | |
| ? "\"" + o + "\"" | |
| : o.toString()) | |
| .collect(joining(",")) | |
| + ")"; | |
| } | |
| } | |
| static class RangeWrap extends Wrap { | |
| final Range range; | |
| final String wrapped; // The snippet portion of the source | |
| final int firstSnline; // Line count to start of snippet portion | |
| final int lastSnline; // Line count to end of snippet portion | |
| RangeWrap(String snippetSource, Range usedWithinSnippet) { | |
| this.range = usedWithinSnippet; | |
| this.wrapped = usedWithinSnippet.part(snippetSource); | |
| usedWithinSnippet.verify(snippetSource); | |
| this.firstSnline = countLines(snippetSource, 0, range.begin); | |
| this.lastSnline = firstSnline + countLines(snippetSource, range.begin, range.end); | |
| } | |
| @Override | |
| public String wrapped() { | |
| return wrapped; | |
| } | |
| @Override | |
| public int snippetIndexToWrapIndex(int sni) { | |
| if (sni < range.begin) { | |
| debugWrap("\nRangeWrap.snippetIndexToWrapIndex: ERR before SnippetIndex(%d) -> WrapIndex(%d + %d = %d)\n", | |
| sni, 0); | |
| return 0; | |
| } | |
| if (sni > range.end) { | |
| debugWrap("\nRangeWrap.snippetIndexToWrapIndex: ERR after SnippetIndex(%d) -> WrapIndex(%d + %d = %d)\n", | |
| sni, range.length()); | |
| return range.length(); | |
| } | |
| int wi = sni - range.begin; | |
| debugWrap("\nRangeWrap.snippetIndexToWrapIndex: SnippetIndex(%d) -> WrapIndex(%d + %d = %d)" | |
| + "\n === %s", | |
| sni, sni, range.begin, sni - range.begin, wrapped()); | |
| return wi; | |
| } | |
| @Override | |
| public int wrapIndexToSnippetIndex(int wi) { | |
| if (wi < 0) { | |
| debugWrap("\nRangeWrap.wrapIndexToSnippetIndex: ERR before WrapIndex(%d) -> SnippetIndex(%d)\n", | |
| wi, 0); | |
| return 0; // bad index | |
| } | |
| int max = range.length(); | |
| if (wi > max) { | |
| debugWrap("\nRangeWrap.wrapIndexToSnippetIndex: ERR after WrapIndex(%d) -> SnippetIndex(%d)\n", | |
| wi, max + range.begin); | |
| return max + range.begin; | |
| } | |
| int sni = wi + range.begin; | |
| debugWrap("\nRangeWrap.wrapIndexToSnippetIndex: WrapIndex(%d) -> SnippetIndex(%d)\n", | |
| wi, sni); | |
| return sni; | |
| } | |
| @Override | |
| public int firstSnippetIndex() { | |
| return range.begin; | |
| } | |
| @Override | |
| public int lastSnippetIndex() { | |
| return range.end; | |
| } | |
| @Override | |
| public int snippetLineToWrapLine(int snline) { | |
| if (snline < firstSnline) { | |
| return 0; | |
| } | |
| if (snline >= lastSnline) { | |
| return lastSnline - firstSnline; | |
| } | |
| return snline - firstSnline; | |
| } | |
| @Override | |
| public int wrapLineToSnippetLine(int wline) { | |
| if (wline < 0) { | |
| return 0; // bad index | |
| } | |
| int max = lastSnline - firstSnline; | |
| if (wline > max) { | |
| wline = max; | |
| } | |
| return wline + firstSnline; | |
| } | |
| @Override | |
| public int firstSnippetLine() { | |
| return firstSnline; | |
| } | |
| @Override | |
| public int lastSnippetLine() { | |
| return lastSnline; | |
| } | |
| @Override | |
| public String toString() { | |
| return "RangeWrap(" + range + ")"; | |
| } | |
| } | |
| private static class NoWrap extends RangeWrap { | |
| NoWrap(String unit) { | |
| super(unit, new Range(unit)); | |
| } | |
| } | |
| private static String semi(Wrap w) { | |
| return semi(w.wrapped()); | |
| } | |
| private static String semi(String s) { | |
| return ((s.endsWith(";")) ? "\n" : ((s.endsWith(";\n")) ? "" : ";\n")); | |
| } | |
| private static class DoitMethodWrap extends CompoundWrap { | |
| DoitMethodWrap(Wrap w) { | |
| super(" public static java.lang.Object " + DOIT_METHOD_NAME + "() throws java.lang.Throwable {\n" | |
| + " ", w, | |
| " }\n"); | |
| } | |
| } | |
| private static class VarDeclareWrap extends CompoundWrap { | |
| VarDeclareWrap(Wrap wtype, String brackets, Wrap wname) { | |
| super(" public static ", wtype, brackets + " ", wname, semi(wname)); | |
| } | |
| } | |
| void debugWrap(String format, Object... args) { | |
| //System.err.printf(format, args); | |
| //state.debug(this, InternalDebugControl.DBG_WRAP, format, args); | |
| } | |
| } |