From ed7e8ff7f42627f1a0e18848a64bce6dd80b5997 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 16 Sep 2016 22:46:19 +0300 Subject: [PATCH] javascript: refactor renderer --- .../java/lang/ClassLoaderNativeGenerator.java | 3 +- .../backend/javascript/JavaScriptTarget.java | 14 +- .../codegen/DefaultAliasProvider.java | 27 +- .../javascript/rendering/Renderer.java | 1821 +---------------- .../rendering/RenderingContext.java | 263 ++- .../rendering/RenderingManager.java | 36 + .../javascript/rendering/RenderingUtil.java | 84 + .../rendering/StatementRenderer.java | 1521 ++++++++++++++ .../javascript/spi/GeneratorContext.java | 7 +- .../vm/spi/AbstractRendererListener.java | 8 +- .../org/teavm/vm/spi/RendererListener.java | 4 +- .../teavm/html4j/JavaScriptBodyGenerator.java | 27 +- .../html4j/JavaScriptResourceInterceptor.java | 16 +- .../teavm/html4j/ResourcesInterceptor.java | 12 +- .../org/teavm/jso/impl/JSAliasRenderer.java | 4 +- .../org/teavm/jso/impl/JSNativeGenerator.java | 4 +- ...ScopedMetadataProviderNativeGenerator.java | 12 +- .../java/org/teavm/tooling/TeaVMTool.java | 6 +- 18 files changed, 2054 insertions(+), 1815 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java create mode 100644 core/src/main/java/org/teavm/backend/javascript/rendering/RenderingUtil.java create mode 100644 core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java index c7362bf6a..1d854f1e8 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java @@ -8,6 +8,7 @@ import java.util.HashSet; import java.util.ServiceLoader; import java.util.Set; import org.apache.commons.io.IOUtils; +import org.teavm.backend.javascript.rendering.RenderingUtil; import org.teavm.classlib.ResourceSupplier; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.rendering.Renderer; @@ -54,7 +55,7 @@ public class ClassLoaderNativeGenerator implements Injector { first = false; writer.newLine(); String data = Base64.getEncoder().encodeToString(IOUtils.toByteArray(input)); - writer.append("\"").append(Renderer.escapeString(resource)).append("\""); + writer.append("\"").append(RenderingUtil.escapeString(resource)).append("\""); writer.ws().append(':').ws(); writer.append("\"").append(data).append("\""); } diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index 6e14df993..4bb697c2e 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -37,11 +37,13 @@ import org.teavm.backend.javascript.codegen.MinifyingAliasProvider; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriterBuilder; import org.teavm.backend.javascript.rendering.Renderer; +import org.teavm.backend.javascript.rendering.RenderingContext; import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.InjectedBy; import org.teavm.backend.javascript.spi.Injector; import org.teavm.debugging.information.DebugInformationEmitter; +import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.debugging.information.SourceLocation; import org.teavm.dependency.DependencyChecker; import org.teavm.dependency.DependencyListener; @@ -225,8 +227,14 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { builder.setMinified(minifying); SourceWriter sourceWriter = builder.build(writer); - Renderer renderer = new Renderer(sourceWriter, classes, controller.getClassLoader(), controller.getServices(), - asyncMethods, asyncFamilyMethods, controller.getDiagnostics()); + DebugInformationEmitter debugEmitterToUse = debugEmitter; + if (debugEmitterToUse == null) { + debugEmitterToUse = new DummyDebugInformationEmitter(); + } + RenderingContext renderingContext = new RenderingContext(debugEmitterToUse, classes, + controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming); + Renderer renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods, + controller.getDiagnostics(), renderingContext); renderer.setProperties(controller.getProperties()); renderer.setMinifying(minifying); if (debugEmitter != null) { @@ -245,7 +253,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { } renderer.getDebugEmitter().setLocationProvider(sourceWriter); for (Map.Entry entry : methodInjectors.entrySet()) { - renderer.addInjector(entry.getKey(), entry.getValue()); + renderingContext.addInjector(entry.getKey(), entry.getValue()); } try { for (RendererListener listener : rendererListeners) { diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java index 2ba1fd934..98d280e77 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/DefaultAliasProvider.java @@ -56,10 +56,16 @@ public class DefaultAliasProvider implements AliasProvider { @Override public String getMethodAlias(MethodDescriptor method) { String alias = method.getName(); - if (alias.equals("")) { - alias = "$init"; - } else if (alias.equals("")) { - alias = "$clinit"; + switch (alias) { + case "": + alias = "$_init_"; + break; + case "": + alias = "$_clinit_"; + break; + default: + alias = "$" + alias; + break; } return makeUnique(knownVirtualAliases, alias); } @@ -67,10 +73,13 @@ public class DefaultAliasProvider implements AliasProvider { @Override public String getStaticMethodAlias(MethodReference method) { String alias = method.getDescriptor().getName(); - if (alias.equals("")) { - alias = "$init"; - } else if (alias.equals("")) { - alias = "$clinit"; + switch (alias) { + case "": + alias = "_init_"; + break; + case "": + alias = "_clinit_"; + break; } return makeUnique(knownAliases, getClassAlias(method.getClassName()) + "_" + alias); @@ -78,7 +87,7 @@ public class DefaultAliasProvider implements AliasProvider { @Override public String getFieldAlias(FieldReference field) { - return makeUnique(knownVirtualAliases, field.getFieldName()); + return makeUnique(knownVirtualAliases, "$" + field.getFieldName()); } @Override diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index 5e3a6bcc9..fac7b76c8 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -16,159 +16,64 @@ package org.teavm.backend.javascript.rendering; import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.Set; -import org.teavm.ast.AssignmentStatement; import org.teavm.ast.AsyncMethodNode; import org.teavm.ast.AsyncMethodPart; -import org.teavm.ast.BinaryExpr; -import org.teavm.ast.BinaryOperation; -import org.teavm.ast.BlockStatement; -import org.teavm.ast.BreakStatement; -import org.teavm.ast.CastExpr; import org.teavm.ast.ClassNode; -import org.teavm.ast.ConditionalExpr; -import org.teavm.ast.ConditionalStatement; -import org.teavm.ast.ConstantExpr; -import org.teavm.ast.ContinueStatement; -import org.teavm.ast.Expr; -import org.teavm.ast.ExprVisitor; import org.teavm.ast.FieldNode; -import org.teavm.ast.GotoPartStatement; -import org.teavm.ast.InitClassStatement; -import org.teavm.ast.InstanceOfExpr; -import org.teavm.ast.InvocationExpr; -import org.teavm.ast.InvocationType; import org.teavm.ast.MethodNode; import org.teavm.ast.MethodNodeVisitor; -import org.teavm.ast.MonitorEnterStatement; -import org.teavm.ast.MonitorExitStatement; import org.teavm.ast.NativeMethodNode; -import org.teavm.ast.NewArrayExpr; -import org.teavm.ast.NewExpr; -import org.teavm.ast.NewMultiArrayExpr; -import org.teavm.ast.OperationType; -import org.teavm.ast.PrimitiveCastExpr; -import org.teavm.ast.QualificationExpr; import org.teavm.ast.RegularMethodNode; -import org.teavm.ast.ReturnStatement; -import org.teavm.ast.SequentialStatement; -import org.teavm.ast.Statement; -import org.teavm.ast.StatementVisitor; -import org.teavm.ast.SubscriptExpr; -import org.teavm.ast.SwitchClause; -import org.teavm.ast.SwitchStatement; -import org.teavm.ast.ThrowStatement; -import org.teavm.ast.TryCatchStatement; -import org.teavm.ast.UnaryExpr; -import org.teavm.ast.UnwrapArrayExpr; -import org.teavm.ast.VariableExpr; import org.teavm.ast.VariableNode; -import org.teavm.ast.WhileStatement; import org.teavm.backend.javascript.codegen.NamingException; import org.teavm.backend.javascript.codegen.NamingOrderer; import org.teavm.backend.javascript.codegen.NamingStrategy; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.spi.GeneratorContext; -import org.teavm.backend.javascript.spi.InjectedBy; -import org.teavm.backend.javascript.spi.Injector; -import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.common.ServiceRepository; import org.teavm.debugging.information.DebugInformationEmitter; -import org.teavm.debugging.information.DeferredCallSite; import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.diagnostics.Diagnostics; -import org.teavm.model.AnnotationHolder; -import org.teavm.model.ClassHolder; -import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; import org.teavm.model.FieldReference; -import org.teavm.model.ListableClassHolderSource; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodDescriptor; -import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; -import org.teavm.model.TextLocation; import org.teavm.model.ValueType; import org.teavm.vm.RenderingException; -public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext { - private static final String variableNames = "abcdefghijkmnopqrstuvwxyz"; - private static final String variablePartNames = "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - private static final Set keywords = new HashSet<>(Arrays.asList("break", "case", "catch", - "class", "const", "continue", "debugger", "default", "delete", "do", "else", "export", - "extends", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "return", - "super", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "yield")); +public class Renderer implements RenderingManager { private final NamingStrategy naming; private final SourceWriter writer; - private final ListableClassHolderSource classSource; + private final ListableClassReaderSource classSource; private final ClassLoader classLoader; private boolean minifying; - private final Map injectorMap = new HashMap<>(); - private final Map stringPoolMap = new HashMap<>(); - private final List stringPool = new ArrayList<>(); private final Properties properties = new Properties(); private final ServiceRepository services; private DebugInformationEmitter debugEmitter = new DummyDebugInformationEmitter(); - private final Deque locationStack = new ArrayDeque<>(); - private DeferredCallSite lastCallSite; - private DeferredCallSite prevCallSite; private final Set asyncMethods; private final Set asyncFamilyMethods; private final Diagnostics diagnostics; - private boolean async; - private Precedence precedence; - private final Map blockIdMap = new HashMap<>(); - private final List cachedVariableNames = new ArrayList<>(); - private final Set usedVariableNames = new HashSet<>(); - private MethodNode currentMethod; - private boolean end; - private int currentPart; + private RenderingContext context; private List postponedFieldInitializers = new ArrayList<>(); - private static class InjectorHolder { - public final Injector injector; - - private InjectorHolder(Injector injector) { - this.injector = injector; - } - } - - private static class LocationStackEntry { - final TextLocation location; - - LocationStackEntry(TextLocation location) { - this.location = location; - } - } - - public void addInjector(MethodReference method, Injector injector) { - injectorMap.put(method, new InjectorHolder(injector)); - } - - public Renderer(SourceWriter writer, ListableClassHolderSource classSource, ClassLoader classLoader, - ServiceRepository services, Set asyncMethods, Set asyncFamilyMethods, - Diagnostics diagnostics) { - this.naming = writer.getNaming(); + public Renderer(SourceWriter writer, Set asyncMethods, Set asyncFamilyMethods, + Diagnostics diagnostics, RenderingContext context) { + this.naming = context.getNaming(); this.writer = writer; - this.classSource = classSource; - this.classLoader = classLoader; - this.services = services; + this.classSource = context.getClassSource(); + this.classLoader = context.getClassLoader(); + this.services = context.getServices(); this.asyncMethods = new HashSet<>(asyncMethods); this.asyncFamilyMethods = new HashSet<>(asyncFamilyMethods); this.diagnostics = diagnostics; + this.context = context; } @Override @@ -191,7 +96,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } @Override - public ListableClassHolderSource getClassSource() { + public ListableClassReaderSource getClassSource() { return classSource; } @@ -221,16 +126,16 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } public void renderStringPool() throws RenderingException { - if (stringPool.isEmpty()) { + if (context.getStringPool().isEmpty()) { return; } try { writer.append("$rt_stringPool(["); - for (int i = 0; i < stringPool.size(); ++i) { + for (int i = 0; i < context.getStringPool().size(); ++i) { if (i > 0) { writer.append(',').ws(); } - writer.append('"').append(escapeString(stringPool.get(i))).append('"'); + writer.append('"').append(RenderingUtil.escapeString(context.getStringPool().get(i))).append('"'); } writer.append("]);").newLine(); } catch (IOException e) { @@ -409,7 +314,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } FieldReference fieldRef = new FieldReference(cls.getName(), field.getName()); writer.append(thisAliased ? "a" : "this").append(".").appendField(fieldRef).ws() - .append("=").ws().append(constantToString(value)).append(";").softNewLine(); + .append("=").ws().append(context.constantToString(value)).append(";").softNewLine(); debugEmitter.addField(field.getName(), naming.getNameFor(fieldRef)); } @@ -431,7 +336,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext value = null; } writer.append("var ").appendStaticField(fieldRef).ws().append("=").ws() - .append(constantToString(value)).append(";").softNewLine(); + .append(context.constantToString(value)).append(";").softNewLine(); } } catch (NamingException e) { throw new RenderingException("Error rendering class " + cls.getName() + ". See cause for details", e); @@ -444,7 +349,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext debugEmitter.emitClass(cls.getName()); try { List nonInitMethods = new ArrayList<>(); - MethodHolder clinit = classSource.get(cls.getName()).getMethod( + MethodReader clinit = classSource.get(cls.getName()).getMethod( new MethodDescriptor("", ValueType.VOID)); boolean needsClinit = clinit != null; List clinitMethods = new ArrayList<>(); @@ -501,7 +406,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } first = false; writer.appendClass(cls.getName()).append(",").ws(); - writer.append("\"").append(escapeString(cls.getName())).append("\",").ws(); + writer.append("\"").append(RenderingUtil.escapeString(cls.getName())).append("\",").ws(); if (cls.getParentName() != null) { writer.appendClass(cls.getParentName()); } else { @@ -522,7 +427,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext flags |= 1; } writer.append(flags).append(',').ws(); - MethodHolder clinit = classSource.get(cls.getName()).getMethod( + MethodReader clinit = classSource.get(cls.getName()).getMethod( new MethodDescriptor("", ValueType.VOID)); if (clinit != null) { writer.appendClass(cls.getName()).append("_$callClinit"); @@ -599,7 +504,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (i > 1) { writer.append(",").ws(); } - writer.append(variableName(i)); + writer.append(variableNameForInitializer(i)); } writer.append(")").ws().append("{").softNewLine().indent(); writer.append("var $r").ws().append("=").ws().append("new ").appendClass( @@ -607,7 +512,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(naming.getFullNameFor(ref)).append("($r"); for (int i = 1; i <= ref.parameterCount(); ++i) { writer.append(",").ws(); - writer.append(variableName(i)); + writer.append(variableNameForInitializer(i)); } writer.append(");").softNewLine(); writer.append("return $r;").softNewLine(); @@ -615,6 +520,10 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext debugEmitter.emitMethod(null); } + private String variableNameForInitializer(int index) { + return minifying ? RenderingUtil.indexToId(index) : "var_" + index; + } + private void renderVirtualDeclarations(List methods) throws NamingException, IOException { writer.append("["); boolean first = true; @@ -637,7 +546,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(",").ws().append("function("); List args = new ArrayList<>(); for (int i = 1; i <= ref.parameterCount(); ++i) { - args.add(variableName(i)); + args.add(variableNameForInitializer(i)); } for (int i = 0; i < args.size(); ++i) { if (i > 0) { @@ -658,10 +567,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } private void renderBody(MethodNode method, boolean inner) throws IOException { - currentMethod = method; - cachedVariableNames.clear(); - usedVariableNames.clear(); - blockIdMap.clear(); + StatementRenderer statementRenderer = new StatementRenderer(context, writer); + statementRenderer.setCurrentMethod(method); + MethodReference ref = method.getReference(); debugEmitter.emitMethod(ref.getDescriptor()); String name = naming.getFullNameFor(ref); @@ -678,10 +586,10 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (i > startParam) { writer.append(",").ws(); } - writer.append(variableName(i)); + writer.append(statementRenderer.variableName(i)); } writer.append(")").ws().append("{").softNewLine().indent(); - method.acceptVisitor(new MethodBodyRenderer()); + method.acceptVisitor(new MethodBodyRenderer(statementRenderer)); writer.outdent().append("}"); if (inner) { writer.append(';'); @@ -692,12 +600,17 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { private boolean async; + private StatementRenderer statementRenderer; + + public MethodBodyRenderer(StatementRenderer statementRenderer) { + this.statementRenderer = statementRenderer; + } @Override public void visit(NativeMethodNode methodNode) { try { this.async = methodNode.isAsync(); - Renderer.this.async = methodNode.isAsync(); + statementRenderer.setAsync(methodNode.isAsync()); methodNode.getGenerator().generate(this, writer, methodNode.getReference()); } catch (IOException e) { throw new RenderingException("IO error occurred", e); @@ -707,12 +620,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(RegularMethodNode method) { try { - Renderer.this.async = false; + statementRenderer.setAsync(false); this.async = false; MethodReference ref = method.getReference(); for (int i = 0; i < method.getVariables().size(); ++i) { debugEmitter.emitVariable(new String[] { method.getVariables().get(i).getName() }, - variableName(i)); + statementRenderer.variableName(i)); } int variableCount = 0; @@ -724,7 +637,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext boolean hasTryCatch = tryCatchFinder.tryCatchFound; List variableNames = new ArrayList<>(); for (int i = ref.parameterCount() + 1; i < variableCount; ++i) { - variableNames.add(variableName(i)); + variableNames.add(statementRenderer.variableName(i)); } if (hasTryCatch) { variableNames.add("$je"); @@ -740,9 +653,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(";").softNewLine(); } - end = true; - currentPart = 0; - method.getBody().acceptVisitor(Renderer.this); + statementRenderer.setEnd(true); + statementRenderer.setCurrentPart(0); + method.getBody().acceptVisitor(statementRenderer); } catch (IOException e) { throw new RenderingException("IO error occurred", e); } @@ -751,12 +664,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(AsyncMethodNode methodNode) { try { - Renderer.this.async = true; + statementRenderer.setAsync(true); this.async = true; MethodReference ref = methodNode.getReference(); for (int i = 0; i < methodNode.getVariables().size(); ++i) { debugEmitter.emitVariable(new String[] { methodNode.getVariables().get(i).getName() }, - variableName(i)); + statementRenderer.variableName(i)); } int variableCount = 0; for (VariableNode var : methodNode.getVariables()) { @@ -764,7 +677,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } List variableNames = new ArrayList<>(); for (int i = ref.parameterCount() + 1; i < variableCount; ++i) { - variableNames.add(variableName(i)); + variableNames.add(statementRenderer.variableName(i)); } TryCatchFinder tryCatchFinder = new TryCatchFinder(); for (AsyncMethodPart part : methodNode.getBody()) { @@ -776,8 +689,8 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (hasTryCatch) { variableNames.add("$je"); } - variableNames.add(pointerName()); - variableNames.add(tempVarName()); + variableNames.add(context.pointerName()); + variableNames.add(context.tempVarName()); if (!variableNames.isEmpty()) { writer.append("var "); for (int i = 0; i < variableNames.size(); ++i) { @@ -796,16 +709,16 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext String popName = minifying ? "l" : "pop"; String pushName = minifying ? "s" : "push"; - writer.append(pointerName()).ws().append('=').ws().append("0;").softNewLine(); + writer.append(context.pointerName()).ws().append('=').ws().append("0;").softNewLine(); writer.append("if").ws().append("(").appendFunction("$rt_resuming").append("())").ws() .append("{").indent().softNewLine(); - writer.append("var ").append(threadName()).ws().append('=').ws() + writer.append("var ").append(context.threadName()).ws().append('=').ws() .appendFunction("$rt_nativeThread").append("();").softNewLine(); - writer.append(pointerName()).ws().append('=').ws().append(threadName()).append(".") + writer.append(context.pointerName()).ws().append('=').ws().append(context.threadName()).append(".") .append(popName).append("();"); for (int i = variableCount - 1; i >= firstToSave; --i) { - writer.append(variableName(i)).ws().append('=').ws().append(threadName()).append(".") - .append(popName).append("();"); + writer.append(statementRenderer.variableName(i)).ws().append('=').ws().append(context.threadName()) + .append(".").append(popName).append("();"); } writer.softNewLine(); writer.outdent().append("}").softNewLine(); @@ -813,9 +726,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { writer.append("try").ws().append('{').indent().softNewLine(); } - writer.append(mainLoopName()).append(":").ws().append("while").ws().append("(true)") + writer.append(context.mainLoopName()).append(":").ws().append("while").ws().append("(true)") .ws().append("{").ws(); - writer.append("switch").ws().append("(").append(pointerName()).append(")").ws() + writer.append("switch").ws().append("(").append(context.pointerName()).append(")").ws() .append('{').softNewLine(); for (int i = 0; i < methodNode.getBody().size(); ++i) { writer.append("case ").append(i).append(":").indent().softNewLine(); @@ -823,14 +736,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.appendMethodBody(new MethodReference(Object.class, "monitorEnter", Object.class, void.class)); writer.append("("); - appendMonitor(methodNode); + appendMonitor(statementRenderer, methodNode); writer.append(");").softNewLine(); - emitSuspendChecker(); + statementRenderer.emitSuspendChecker(); } AsyncMethodPart part = methodNode.getBody().get(i); - end = true; - currentPart = i; - part.getStatement().acceptVisitor(Renderer.this); + statementRenderer.setEnd(true); + statementRenderer.setCurrentPart(i); + part.getStatement().acceptVisitor(statementRenderer); writer.outdent(); } writer.append("default:").ws().appendFunction("$rt_invalidPointer").append("();").softNewLine(); @@ -843,7 +756,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.appendMethodBody(new MethodReference(Object.class, "monitorExit", Object.class, void.class)); writer.append("("); - appendMonitor(methodNode); + appendMonitor(statementRenderer, methodNode); writer.append(");").softNewLine(); writer.outdent().append('}').softNewLine(); writer.outdent().append('}').softNewLine(); @@ -851,9 +764,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.appendFunction("$rt_nativeThread").append("().").append(pushName).append("("); for (int i = firstToSave; i < variableCount; ++i) { - writer.append(variableName(i)).append(',').ws(); + writer.append(statementRenderer.variableName(i)).append(',').ws(); } - writer.append(pointerName()).append(");"); + writer.append(context.pointerName()).append(");"); writer.softNewLine(); } catch (IOException e) { throw new RenderingException("IO error occurred", e); @@ -862,7 +775,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public String getParameterName(int index) { - return variableName(index); + return statementRenderer.variableName(index); } @Override @@ -904,1611 +817,19 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public Diagnostics getDiagnostics() { return diagnostics; } + + @Override + public String typeToClassString(ValueType type) { + return context.typeToClsString(type); + } } - private void appendMonitor(MethodNode methodNode) throws IOException { + private void appendMonitor(StatementRenderer statementRenderer, MethodNode methodNode) throws IOException { if (methodNode.getModifiers().contains(ElementModifier.STATIC)) { writer.appendFunction("$rt_cls").append("(") .appendClass(methodNode.getReference().getClassName()).append(")"); } else { - writer.append(variableName(0)); - } - } - - private void pushLocation(TextLocation location) { - LocationStackEntry prevEntry = locationStack.peek(); - if (location != null) { - if (prevEntry == null || !location.equals(prevEntry.location)) { - debugEmitter.emitLocation(location.getFileName(), location.getLine()); - } - } else { - if (prevEntry != null) { - debugEmitter.emitLocation(null, -1); - } - } - locationStack.push(new LocationStackEntry(location)); - } - - private void popLocation() { - LocationStackEntry prevEntry = locationStack.pop(); - LocationStackEntry entry = locationStack.peek(); - if (entry != null) { - if (!entry.location.equals(prevEntry.location)) { - debugEmitter.emitLocation(entry.location.getFileName(), entry.location.getLine()); - } - } else { - debugEmitter.emitLocation(null, -1); - } - } - - @Override - public void visit(AssignmentStatement statement) throws RenderingException { - try { - debugEmitter.emitStatementStart(); - if (statement.getLocation() != null) { - pushLocation(statement.getLocation()); - } - prevCallSite = debugEmitter.emitCallSite(); - if (statement.getLeftValue() != null) { - if (statement.isAsync()) { - writer.append(tempVarName()); - } else { - precedence = Precedence.COMMA; - statement.getLeftValue().acceptVisitor(this); - } - writer.ws().append("=").ws(); - } - precedence = Precedence.COMMA; - statement.getRightValue().acceptVisitor(this); - debugEmitter.emitCallSite(); - writer.append(";").softNewLine(); - if (statement.isAsync()) { - emitSuspendChecker(); - if (statement.getLeftValue() != null) { - precedence = Precedence.COMMA; - statement.getLeftValue().acceptVisitor(this); - writer.ws().append("=").ws().append(tempVarName()).append(";").softNewLine(); - } - } - if (statement.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(SequentialStatement statement) { - visitStatements(statement.getSequence()); - } - - @Override - public void visit(ConditionalStatement statement) { - try { - while (true) { - debugEmitter.emitStatementStart(); - if (statement.getCondition().getLocation() != null) { - pushLocation(statement.getCondition().getLocation()); - } - prevCallSite = debugEmitter.emitCallSite(); - writer.append("if").ws().append("("); - precedence = Precedence.COMMA; - statement.getCondition().acceptVisitor(this); - if (statement.getCondition().getLocation() != null) { - popLocation(); - } - debugEmitter.emitCallSite(); - writer.append(")").ws().append("{").softNewLine().indent(); - visitStatements(statement.getConsequent()); - if (!statement.getAlternative().isEmpty()) { - writer.outdent().append("}").ws(); - if (statement.getAlternative().size() == 1 - && statement.getAlternative().get(0) instanceof ConditionalStatement) { - statement = (ConditionalStatement) statement.getAlternative().get(0); - writer.append("else "); - continue; - } - writer.append("else").ws().append("{").indent().softNewLine(); - visitStatements(statement.getAlternative()); - } - break; - } - writer.outdent().append("}").softNewLine(); - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(SwitchStatement statement) { - try { - debugEmitter.emitStatementStart(); - if (statement.getValue().getLocation() != null) { - pushLocation(statement.getValue().getLocation()); - } - if (statement.getId() != null) { - writer.append(mapBlockId(statement.getId())).append(":").ws(); - } - prevCallSite = debugEmitter.emitCallSite(); - writer.append("switch").ws().append("("); - precedence = Precedence.min(); - statement.getValue().acceptVisitor(this); - if (statement.getValue().getLocation() != null) { - popLocation(); - } - debugEmitter.emitCallSite(); - writer.append(")").ws().append("{").softNewLine().indent(); - for (SwitchClause clause : statement.getClauses()) { - for (int condition : clause.getConditions()) { - writer.append("case ").append(condition).append(":").softNewLine(); - } - writer.indent(); - boolean oldEnd = end; - for (Statement part : clause.getBody()) { - end = false; - part.acceptVisitor(this); - } - end = oldEnd; - writer.outdent(); - } - if (statement.getDefaultClause() != null) { - writer.append("default:").softNewLine().indent(); - boolean oldEnd = end; - for (Statement part : statement.getDefaultClause()) { - end = false; - part.acceptVisitor(this); - } - end = oldEnd; - writer.outdent(); - } - writer.outdent().append("}").softNewLine(); - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(WhileStatement statement) { - try { - debugEmitter.emitStatementStart(); - if (statement.getCondition() != null && statement.getCondition().getLocation() != null) { - pushLocation(statement.getCondition().getLocation()); - } - if (statement.getId() != null) { - writer.append(mapBlockId(statement.getId())).append(":").ws(); - } - writer.append("while").ws().append("("); - if (statement.getCondition() != null) { - prevCallSite = debugEmitter.emitCallSite(); - precedence = Precedence.min(); - statement.getCondition().acceptVisitor(this); - debugEmitter.emitCallSite(); - if (statement.getCondition().getLocation() != null) { - popLocation(); - } - } else { - writer.append("true"); - } - writer.append(")").ws().append("{").softNewLine().indent(); - boolean oldEnd = end; - for (Statement part : statement.getBody()) { - end = false; - part.acceptVisitor(this); - } - end = oldEnd; - writer.outdent().append("}").softNewLine(); - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - private String mapBlockId(String id) { - String name = blockIdMap.get(id); - if (name == null) { - int index = blockIdMap.size(); - name = "$b" + indexToId(index); - blockIdMap.put(id, name); - } - return name; - } - - private String indexToId(int index) { - StringBuilder sb = new StringBuilder(); - do { - sb.append(variablePartNames.charAt(index % variablePartNames.length())); - index /= variablePartNames.length(); - } while (index > 0); - return sb.toString(); - } - - @Override - public void visit(BlockStatement statement) { - try { - writer.append(mapBlockId(statement.getId())).append(":").ws().append("{").softNewLine().indent(); - visitStatements(statement.getBody()); - writer.outdent().append("}").softNewLine(); - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(BreakStatement statement) { - try { - debugEmitter.emitStatementStart(); - if (statement.getLocation() != null) { - pushLocation(statement.getLocation()); - } - writer.append("break"); - if (statement.getTarget() != null) { - writer.append(' ').append(mapBlockId(statement.getTarget().getId())); - } - writer.append(";").softNewLine(); - if (statement.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(ContinueStatement statement) { - try { - debugEmitter.emitStatementStart(); - if (statement.getLocation() != null) { - pushLocation(statement.getLocation()); - } - writer.append("continue"); - if (statement.getTarget() != null) { - writer.append(' ').append(mapBlockId(statement.getTarget().getId())); - } - writer.append(";").softNewLine(); - if (statement.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(ReturnStatement statement) { - try { - debugEmitter.emitStatementStart(); - if (statement.getLocation() != null) { - pushLocation(statement.getLocation()); - } - writer.append("return"); - if (statement.getResult() != null) { - writer.append(' '); - prevCallSite = debugEmitter.emitCallSite(); - precedence = Precedence.min(); - statement.getResult().acceptVisitor(this); - debugEmitter.emitCallSite(); - } - writer.append(";").softNewLine(); - if (statement.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(ThrowStatement statement) { - try { - debugEmitter.emitStatementStart(); - if (statement.getLocation() != null) { - pushLocation(statement.getLocation()); - } - writer.appendFunction("$rt_throw").append("("); - prevCallSite = debugEmitter.emitCallSite(); - precedence = Precedence.min(); - statement.getException().acceptVisitor(this); - writer.append(");").softNewLine(); - debugEmitter.emitCallSite(); - if (statement.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(InitClassStatement statement) { - ClassReader cls = classSource.get(statement.getClassName()); - if (cls == null) { - return; - } - MethodReader method = cls.getMethod(new MethodDescriptor("", void.class)); - if (method == null) { - return; - } - try { - debugEmitter.emitStatementStart(); - if (statement.getLocation() != null) { - pushLocation(statement.getLocation()); - } - writer.appendClass(statement.getClassName()).append("_$callClinit();").softNewLine(); - if (statement.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - private String variableName(int index) { - while (index >= cachedVariableNames.size()) { - cachedVariableNames.add(null); - } - String name = cachedVariableNames.get(index); - if (name == null) { - name = generateVariableName(index); - cachedVariableNames.set(index, name); - } - return name; - } - - private String generateVariableName(int index) { - if (index == 0) { - return minifying ? "$t" : "$this"; - } - - if (!minifying) { - VariableNode variable = index < currentMethod.getVariables().size() - ? currentMethod.getVariables().get(index) - : null; - if (variable != null && variable.getName() != null) { - String result = escapeName(variable.getName()); - if (keywords.contains(result) || !usedVariableNames.add(result)) { - String base = result; - int suffix = 0; - do { - result = base + "_" + suffix++; - } while (!usedVariableNames.add(result)); - } - return result; - } else { - return "var$" + index; - } - } else { - --index; - if (index < variableNames.length()) { - return Character.toString(variableNames.charAt(index)); - } else { - return Character.toString(variableNames.charAt(index % variableNames.length())) - + index / variableNames.length(); - } - } - } - - private static String escapeName(String name) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < name.length(); ++i) { - char c = name.charAt(i); - sb.append(Character.isJavaIdentifierPart(c) ? c : '_'); - } - return sb.toString(); - } - - private String pointerName() { - return minifying ? "$p" : "$ptr"; - } - - private String mainLoopName() { - return minifying ? "$m" : "$main"; - } - - private String tempVarName() { - return minifying ? "$z" : "$tmp"; - } - - private String threadName() { - return minifying ? "$T" : "$thread"; - } - - private void visitBinary(BinaryExpr expr, String op, boolean guarded) { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - if (guarded) { - visitBinary(BinaryOperation.OR, "|", () -> visitBinary(expr, op, false), - () -> { - try { - writer.append("0"); - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - }); - } else { - visitBinary(expr.getOperation(), op, () -> expr.getFirstOperand().acceptVisitor(this), - () -> expr.getSecondOperand().acceptVisitor(this)); - } - if (expr.getLocation() != null) { - popLocation(); - } - } - - private void visitBinary(BinaryOperation operation, String infixText, Runnable a, Runnable b) { - try { - Precedence outerPrecedence = precedence; - Precedence innerPrecedence = getPrecedence(operation); - if (innerPrecedence.ordinal() < outerPrecedence.ordinal()) { - writer.append('('); - } - - switch (operation) { - case ADD: - case SUBTRACT: - case MULTIPLY: - case DIVIDE: - case MODULO: - case AND: - case OR: - case BITWISE_AND: - case BITWISE_OR: - case BITWISE_XOR: - case LEFT_SHIFT: - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - precedence = innerPrecedence; - break; - default: - precedence = innerPrecedence.next(); - } - a.run(); - - writer.ws().append(infixText).ws(); - - switch (operation) { - case ADD: - case MULTIPLY: - case AND: - case OR: - case BITWISE_AND: - case BITWISE_OR: - case BITWISE_XOR: - precedence = innerPrecedence; - break; - default: - precedence = innerPrecedence.next(); - break; - } - b.run(); - - if (innerPrecedence.ordinal() < outerPrecedence.ordinal()) { - writer.append(')'); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - private static Precedence getPrecedence(BinaryOperation op) { - switch (op) { - case ADD: - case SUBTRACT: - return Precedence.ADDITION; - case MULTIPLY: - case DIVIDE: - case MODULO: - return Precedence.MULTIPLICATION; - case AND: - return Precedence.LOGICAL_AND; - case OR: - return Precedence.LOGICAL_OR; - case EQUALS: - case NOT_EQUALS: - return Precedence.EQUALITY; - case GREATER: - case GREATER_OR_EQUALS: - case LESS: - case LESS_OR_EQUALS: - return Precedence.COMPARISON; - case BITWISE_AND: - return Precedence.BITWISE_AND; - case BITWISE_OR: - return Precedence.BITWISE_OR; - case BITWISE_XOR: - return Precedence.BITWISE_XOR; - case LEFT_SHIFT: - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - return Precedence.BITWISE_SHIFT; - default: - return Precedence.GROUPING; - } - } - - private void visitBinaryFunction(BinaryExpr expr, String function) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - writer.append(function); - writer.append('('); - precedence = Precedence.min(); - expr.getFirstOperand().acceptVisitor(this); - writer.append(",").ws(); - precedence = Precedence.min(); - expr.getSecondOperand().acceptVisitor(this); - writer.append(')'); - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occured", e); - } - } - - @Override - public void visit(BinaryExpr expr) { - if (expr.getType() == OperationType.LONG) { - switch (expr.getOperation()) { - case ADD: - visitBinaryFunction(expr, "Long_add"); - break; - case SUBTRACT: - visitBinaryFunction(expr, "Long_sub"); - break; - case MULTIPLY: - visitBinaryFunction(expr, "Long_mul"); - break; - case DIVIDE: - visitBinaryFunction(expr, "Long_div"); - break; - case MODULO: - visitBinaryFunction(expr, "Long_rem"); - break; - case BITWISE_OR: - visitBinaryFunction(expr, "Long_or"); - break; - case BITWISE_AND: - visitBinaryFunction(expr, "Long_and"); - break; - case BITWISE_XOR: - visitBinaryFunction(expr, "Long_xor"); - break; - case LEFT_SHIFT: - visitBinaryFunction(expr, "Long_shl"); - break; - case RIGHT_SHIFT: - visitBinaryFunction(expr, "Long_shr"); - break; - case UNSIGNED_RIGHT_SHIFT: - visitBinaryFunction(expr, "Long_shru"); - break; - case COMPARE: - visitBinaryFunction(expr, "Long_compare"); - break; - case EQUALS: - visitBinaryFunction(expr, "Long_eq"); - break; - case NOT_EQUALS: - visitBinaryFunction(expr, "Long_ne"); - break; - case LESS: - visitBinaryFunction(expr, "Long_lt"); - break; - case LESS_OR_EQUALS: - visitBinaryFunction(expr, "Long_le"); - break; - case GREATER: - visitBinaryFunction(expr, "Long_gt"); - break; - case GREATER_OR_EQUALS: - visitBinaryFunction(expr, "Long_ge"); - break; - default: - break; - } - } else { - switch (expr.getOperation()) { - case ADD: - visitBinary(expr, "+", expr.getType() == OperationType.INT); - break; - case SUBTRACT: - visitBinary(expr, "-", expr.getType() == OperationType.INT); - break; - case MULTIPLY: - visitBinary(expr, "*", expr.getType() == OperationType.INT); - break; - case DIVIDE: - visitBinary(expr, "/", expr.getType() == OperationType.INT); - break; - case MODULO: - visitBinary(expr, "%", expr.getType() == OperationType.INT); - break; - case EQUALS: - if (expr.getType() == OperationType.INT) { - visitBinary(expr, "==", false); - } else { - visitBinary(expr, "===", false); - } - break; - case NOT_EQUALS: - if (expr.getType() == OperationType.INT) { - visitBinary(expr, "!=", false); - } else { - visitBinary(expr, "!==", false); - } - break; - case GREATER: - visitBinary(expr, ">", false); - break; - case GREATER_OR_EQUALS: - visitBinary(expr, ">=", false); - break; - case LESS: - visitBinary(expr, "<", false); - break; - case LESS_OR_EQUALS: - visitBinary(expr, "<=", false); - break; - case COMPARE: - visitBinaryFunction(expr, naming.getNameForFunction("$rt_compare")); - break; - case OR: - visitBinary(expr, "||", false); - break; - case AND: - visitBinary(expr, "&&", false); - break; - case BITWISE_OR: - visitBinary(expr, "|", false); - break; - case BITWISE_AND: - visitBinary(expr, "&", false); - break; - case BITWISE_XOR: - visitBinary(expr, "^", false); - break; - case LEFT_SHIFT: - visitBinary(expr, "<<", false); - break; - case RIGHT_SHIFT: - visitBinary(expr, ">>", false); - break; - case UNSIGNED_RIGHT_SHIFT: - visitBinary(expr, ">>>", false); - break; - } - } - } - - @Override - public void visit(UnaryExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - Precedence outerPrecedence = precedence; - switch (expr.getOperation()) { - case NOT: { - if (expr.getType() == OperationType.LONG) { - writer.append("Long_not("); - precedence = Precedence.min(); - expr.getOperand().acceptVisitor(this); - writer.append(')'); - } else { - if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { - writer.append('('); - } - writer.append(expr.getType() == null ? "!" : "~"); - precedence = Precedence.UNARY; - expr.getOperand().acceptVisitor(this); - if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { - writer.append(')'); - } - } - break; - } - case NEGATE: - if (expr.getType() == OperationType.LONG) { - writer.append("Long_neg("); - precedence = Precedence.min(); - expr.getOperand().acceptVisitor(this); - writer.append(')'); - } else { - if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { - writer.append('('); - } - writer.append(" -"); - precedence = Precedence.UNARY; - expr.getOperand().acceptVisitor(this); - if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { - writer.append(')'); - } - } - break; - case LENGTH: - precedence = Precedence.MEMBER_ACCESS; - expr.getOperand().acceptVisitor(this); - writer.append(".length"); - break; - case INT_TO_BYTE: - if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { - writer.append('('); - } - precedence = Precedence.BITWISE_SHIFT; - expr.getOperand().acceptVisitor(this); - writer.ws().append("<<").ws().append("24").ws().append(">>").ws().append("24"); - if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { - writer.append(')'); - } - break; - case INT_TO_SHORT: - if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { - writer.append('('); - } - precedence = Precedence.BITWISE_SHIFT; - expr.getOperand().acceptVisitor(this); - writer.ws().append("<<").ws().append("16").ws().append(">>").ws().append("16"); - if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { - writer.append(')'); - } - break; - case INT_TO_CHAR: - if (outerPrecedence.ordinal() > Precedence.BITWISE_AND.ordinal()) { - writer.append('('); - } - precedence = Precedence.BITWISE_AND; - expr.getOperand().acceptVisitor(this); - writer.ws().append("&").ws().append("65535"); - if (outerPrecedence.ordinal() > Precedence.BITWISE_AND.ordinal()) { - writer.append(')'); - } - break; - case NULL_CHECK: - writer.appendFunction("$rt_nullCheck").append("("); - precedence = Precedence.min(); - expr.getOperand().acceptVisitor(this); - writer.append(')'); - break; - } - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occured", e); - } - } - - @Override - public void visit(CastExpr expr) { - expr.getValue().acceptVisitor(this); - } - - @Override - public void visit(PrimitiveCastExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - switch (expr.getSource()) { - case INT: - if (expr.getTarget() == OperationType.LONG) { - writer.append("Long_fromInt("); - precedence = Precedence.min(); - expr.getValue().acceptVisitor(this); - writer.append(')'); - } else { - expr.getValue().acceptVisitor(this); - } - break; - case LONG: - switch (expr.getTarget()) { - case INT: - precedence = Precedence.MEMBER_ACCESS; - expr.getValue().acceptVisitor(this); - writer.append(".lo"); - break; - case FLOAT: - case DOUBLE: - writer.append("Long_toNumber("); - precedence = Precedence.min(); - expr.getValue().acceptVisitor(this); - writer.append(')'); - break; - default: - expr.getValue().acceptVisitor(this); - } - break; - case FLOAT: - case DOUBLE: - switch (expr.getTarget()) { - case LONG: - writer.append("Long_fromNumber("); - precedence = Precedence.min(); - expr.getValue().acceptVisitor(this); - writer.append(')'); - break; - case INT: - visitBinary(BinaryOperation.BITWISE_OR, "|", () -> expr.getValue().acceptVisitor(this), - () -> { - try { - writer.append("0"); - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - }); - break; - default: - expr.getValue().acceptVisitor(this); - } - break; - } - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - } catch (IOException e) { - throw new RenderingException("IO error occured", e); - } - } - - @Override - public void visit(ConditionalExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - - Precedence outerPrecedence = precedence; - if (outerPrecedence.ordinal() > Precedence.CONDITIONAL.ordinal()) { - writer.append('('); - } - - precedence = Precedence.CONDITIONAL.next(); - expr.getCondition().acceptVisitor(this); - writer.ws().append("?").ws(); - precedence = Precedence.CONDITIONAL.next(); - expr.getConsequent().acceptVisitor(this); - writer.ws().append(":").ws(); - precedence = Precedence.CONDITIONAL; - expr.getAlternative().acceptVisitor(this); - - if (outerPrecedence.ordinal() > Precedence.CONDITIONAL.ordinal()) { - writer.append('('); - } - - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occured", e); - } - } - - @Override - public void visit(ConstantExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - String str = constantToString(expr.getValue()); - if (str.startsWith("-")) { - writer.append(' '); - } - writer.append(str); - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occured", e); - } - } - - private String constantToString(Object cst) { - if (cst == null) { - return "null"; - } - if (cst instanceof ValueType) { - ValueType type = (ValueType) cst; - return naming.getNameForFunction("$rt_cls") + "(" + typeToClsString(naming, type) + ")"; - } else if (cst instanceof String) { - String string = (String) cst; - Integer index = stringPoolMap.get(string); - if (index == null) { - index = stringPool.size(); - stringPool.add(string); - stringPoolMap.put(string, index); - } - return "$rt_s(" + index + ")"; - } else if (cst instanceof Long) { - long value = (Long) cst; - if (value == 0) { - return "Long_ZERO"; - } else if ((int) value == value) { - return "Long_fromInt(" + value + ")"; - } else { - return "new Long(" + (value & 0xFFFFFFFFL) + ", " + (value >>> 32) + ")"; - } - } else if (cst instanceof Character) { - return Integer.toString((Character) cst); - } else { - return cst.toString(); - } - } - - public static String typeToClsString(NamingStrategy naming, ValueType type) { - int arrayCount = 0; - while (type instanceof ValueType.Array) { - arrayCount++; - type = ((ValueType.Array) type).getItemType(); - } - String value; - if (type instanceof ValueType.Object) { - ValueType.Object objType = (ValueType.Object) type; - value = naming.getNameFor(objType.getClassName()); - } else if (type instanceof ValueType.Void) { - value = "$rt_voidcls()"; - } else if (type instanceof ValueType.Primitive) { - ValueType.Primitive primitiveType = (ValueType.Primitive) type; - switch (primitiveType.getKind()) { - case BOOLEAN: - value = "$rt_booleancls()"; - break; - case CHARACTER: - value = "$rt_charcls()"; - break; - case BYTE: - value = "$rt_bytecls()"; - break; - case SHORT: - value = "$rt_shortcls()"; - break; - case INTEGER: - value = "$rt_intcls()"; - break; - case LONG: - value = "$rt_longcls()"; - break; - case FLOAT: - value = "$rt_floatcls()"; - break; - case DOUBLE: - value = "$rt_doublecls()"; - break; - default: - throw new IllegalArgumentException("The type is not renderable"); - } - } else { - throw new IllegalArgumentException("The type is not renderable"); - } - - for (int i = 0; i < arrayCount; ++i) { - value = "$rt_arraycls(" + value + ")"; - } - return value; - } - - public static String escapeString(String str) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < str.length(); ++i) { - char c = str.charAt(i); - switch (c) { - case '\r': - sb.append("\\r"); - break; - case '\n': - sb.append("\\n"); - break; - case '\t': - sb.append("\\t"); - break; - case '\'': - sb.append("\\'"); - break; - case '\"': - sb.append("\\\""); - break; - case '\\': - sb.append("\\\\"); - break; - default: - if (c < ' ') { - sb.append("\\u00").append(Character.forDigit(c / 16, 16)) - .append(Character.forDigit(c % 16, 16)); - } else if (Character.isLowSurrogate(c) || Character.isHighSurrogate(c)) { - sb.append("\\u") - .append(Character.forDigit(c / 0x1000, 0x10)) - .append(Character.forDigit((c / 0x100) % 0x10, 0x10)) - .append(Character.forDigit((c / 0x10) % 0x10, 0x10)) - .append(Character.forDigit(c % 0x10, 0x10)); - } else { - sb.append(c); - } - break; - } - } - return sb.toString(); - } - - @Override - public void visit(VariableExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - writer.append(variableName(expr.getIndex())); - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occured", e); - } - } - - @Override - public void visit(SubscriptExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - precedence = Precedence.MEMBER_ACCESS; - expr.getArray().acceptVisitor(this); - writer.append('['); - precedence = Precedence.min(); - expr.getIndex().acceptVisitor(this); - writer.append(']'); - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(UnwrapArrayExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - precedence = Precedence.MEMBER_ACCESS; - expr.getArray().acceptVisitor(this); - writer.append(".data"); - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(InvocationExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - Injector injector = getInjector(expr.getMethod()); - if (injector != null) { - injector.generate(new InjectorContextImpl(expr.getArguments()), expr.getMethod()); - } else { - if (expr.getType() == InvocationType.DYNAMIC) { - precedence = Precedence.MEMBER_ACCESS; - expr.getArguments().get(0).acceptVisitor(this); - } - MethodReference method = expr.getMethod(); - String name = naming.getNameFor(method.getDescriptor()); - DeferredCallSite callSite = prevCallSite; - boolean shouldEraseCallSite = lastCallSite == null; - if (lastCallSite == null) { - lastCallSite = callSite; - } - boolean virtual = false; - switch (expr.getType()) { - case STATIC: - writer.append(naming.getFullNameFor(method)).append("("); - prevCallSite = debugEmitter.emitCallSite(); - for (int i = 0; i < expr.getArguments().size(); ++i) { - if (i > 0) { - writer.append(",").ws(); - } - precedence = Precedence.min(); - expr.getArguments().get(i).acceptVisitor(this); - } - break; - case SPECIAL: - writer.append(naming.getFullNameFor(method)).append("("); - prevCallSite = debugEmitter.emitCallSite(); - precedence = Precedence.min(); - expr.getArguments().get(0).acceptVisitor(this); - for (int i = 1; i < expr.getArguments().size(); ++i) { - writer.append(",").ws(); - precedence = Precedence.min(); - expr.getArguments().get(i).acceptVisitor(this); - } - break; - case DYNAMIC: - writer.append(".").append(name).append("("); - prevCallSite = debugEmitter.emitCallSite(); - for (int i = 1; i < expr.getArguments().size(); ++i) { - if (i > 1) { - writer.append(",").ws(); - } - precedence = Precedence.min(); - expr.getArguments().get(i).acceptVisitor(this); - } - virtual = true; - break; - case CONSTRUCTOR: - writer.append(naming.getNameForInit(expr.getMethod())).append("("); - prevCallSite = debugEmitter.emitCallSite(); - for (int i = 0; i < expr.getArguments().size(); ++i) { - if (i > 0) { - writer.append(",").ws(); - } - precedence = Precedence.min(); - expr.getArguments().get(i).acceptVisitor(this); - } - break; - } - writer.append(')'); - if (lastCallSite != null) { - if (virtual) { - lastCallSite.setVirtualMethod(expr.getMethod()); - } else { - lastCallSite.setStaticMethod(expr.getMethod()); - } - lastCallSite = callSite; - } - if (shouldEraseCallSite) { - lastCallSite = null; - } - } - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(QualificationExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - precedence = Precedence.MEMBER_ACCESS; - - if (expr.getQualified() != null) { - expr.getQualified().acceptVisitor(this); - writer.append('.').appendField(expr.getField()); - } else { - writer.appendStaticField(expr.getField()); - } - - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(NewExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - precedence = Precedence.FUNCTION_CALL; - writer.append("new ").append(naming.getNameFor(expr.getConstructedClass())); - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(NewArrayExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - ValueType type = expr.getType(); - if (type instanceof ValueType.Primitive) { - switch (((ValueType.Primitive) type).getKind()) { - case BOOLEAN: - writer.append("$rt_createBooleanArray("); - precedence = Precedence.min(); - expr.getLength().acceptVisitor(this); - writer.append(")"); - break; - case BYTE: - writer.append("$rt_createByteArray("); - precedence = Precedence.min(); - expr.getLength().acceptVisitor(this); - writer.append(")"); - break; - case SHORT: - writer.append("$rt_createShortArray("); - precedence = Precedence.min(); - expr.getLength().acceptVisitor(this); - writer.append(")"); - break; - case INTEGER: - writer.append("$rt_createIntArray("); - precedence = Precedence.min(); - expr.getLength().acceptVisitor(this); - writer.append(")"); - break; - case LONG: - writer.append("$rt_createLongArray("); - precedence = Precedence.min(); - expr.getLength().acceptVisitor(this); - writer.append(")"); - break; - case FLOAT: - writer.append("$rt_createFloatArray("); - precedence = Precedence.min(); - expr.getLength().acceptVisitor(this); - writer.append(")"); - break; - case DOUBLE: - writer.append("$rt_createDoubleArray("); - precedence = Precedence.min(); - expr.getLength().acceptVisitor(this); - writer.append(")"); - break; - case CHARACTER: - writer.append("$rt_createCharArray("); - precedence = Precedence.min(); - expr.getLength().acceptVisitor(this); - writer.append(")"); - break; - } - } else { - writer.appendFunction("$rt_createArray").append("(").append(typeToClsString(naming, expr.getType())) - .append(",").ws(); - precedence = Precedence.min(); - expr.getLength().acceptVisitor(this); - writer.append(")"); - } - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(NewMultiArrayExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - ValueType type = expr.getType(); - for (int i = 0; i < expr.getDimensions().size(); ++i) { - type = ((ValueType.Array) type).getItemType(); - } - if (type instanceof ValueType.Primitive) { - switch (((ValueType.Primitive) type).getKind()) { - case BOOLEAN: - writer.append("$rt_createBooleanMultiArray("); - break; - case BYTE: - writer.append("$rt_createByteMultiArray("); - break; - case SHORT: - writer.append("$rt_createShortMultiArray("); - break; - case INTEGER: - writer.append("$rt_createIntMultiArray("); - break; - case LONG: - writer.append("$rt_createLongMultiArray("); - break; - case FLOAT: - writer.append("$rt_createFloatMultiArray("); - break; - case DOUBLE: - writer.append("$rt_createDoubleMultiArray("); - break; - case CHARACTER: - writer.append("$rt_createCharMultiArray("); - break; - } - } else { - writer.append("$rt_createMultiArray(").append(typeToClsString(naming, expr.getType())) - .append(",").ws(); - } - writer.append("["); - boolean first = true; - List dimensions = new ArrayList<>(expr.getDimensions()); - Collections.reverse(dimensions); - for (Expr dimension : dimensions) { - if (!first) { - writer.append(",").ws(); - } - first = false; - precedence = Precedence.min(); - dimension.acceptVisitor(this); - } - writer.append("])"); - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(InstanceOfExpr expr) { - try { - if (expr.getLocation() != null) { - pushLocation(expr.getLocation()); - } - if (expr.getType() instanceof ValueType.Object) { - String clsName = ((ValueType.Object) expr.getType()).getClassName(); - ClassHolder cls = classSource.get(clsName); - if (cls != null && !cls.getModifiers().contains(ElementModifier.INTERFACE)) { - boolean needsParentheses = Precedence.COMPARISON.ordinal() < precedence.ordinal(); - if (needsParentheses) { - writer.append('('); - } - precedence = Precedence.CONDITIONAL.next(); - expr.getExpr().acceptVisitor(this); - writer.append(" instanceof ").appendClass(clsName); - if (expr.getLocation() != null) { - popLocation(); - } - return; - } - } - writer.appendFunction("$rt_isInstance").append("("); - precedence = Precedence.min(); - expr.getExpr().acceptVisitor(this); - writer.append(",").ws().append(typeToClsString(naming, expr.getType())).append(")"); - if (expr.getLocation() != null) { - popLocation(); - } - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - private void visitStatements(List statements) { - if (statements.isEmpty()) { - return; - } - boolean oldEnd = end; - for (int i = 0; i < statements.size() - 1; ++i) { - end = false; - statements.get(i).acceptVisitor(this); - } - end = oldEnd; - statements.get(statements.size() - 1).acceptVisitor(this); - end = oldEnd; - } - - @Override - public void visit(TryCatchStatement statement) { - try { - writer.append("try").ws().append("{").softNewLine().indent(); - List sequence = new ArrayList<>(); - sequence.add(statement); - List protectedBody = statement.getProtectedBody(); - while (protectedBody.size() == 1 && protectedBody.get(0) instanceof TryCatchStatement) { - TryCatchStatement nextStatement = (TryCatchStatement) protectedBody.get(0); - sequence.add(nextStatement); - protectedBody = nextStatement.getProtectedBody(); - } - visitStatements(protectedBody); - writer.outdent().append("}").ws().append("catch").ws().append("($e)") - .ws().append("{").indent().softNewLine(); - writer.append("$je").ws().append("=").ws().append("$e.$javaException;").softNewLine(); - for (TryCatchStatement catchClause : sequence) { - writer.append("if").ws().append("($je"); - if (catchClause.getExceptionType() != null) { - writer.ws().append("&&").ws().append("$je instanceof ") - .appendClass(catchClause.getExceptionType()); - } - writer.append(")").ws().append("{").indent().softNewLine(); - if (catchClause.getExceptionVariable() != null) { - writer.append(variableName(catchClause.getExceptionVariable())).ws().append("=").ws() - .append("$je;").softNewLine(); - } - visitStatements(catchClause.getHandler()); - writer.outdent().append("}").ws().append("else "); - } - writer.append("{").indent().softNewLine(); - writer.append("throw $e;").softNewLine(); - writer.outdent().append("}").softNewLine(); - writer.outdent().append("}").softNewLine(); - } catch (IOException e) { - throw new RenderingException("IO error occurred", e); - } - } - - @Override - public void visit(GotoPartStatement statement) { - try { - if (statement.getPart() != currentPart) { - writer.append(pointerName()).ws().append("=").ws().append(statement.getPart()).append(";") - .softNewLine(); - } - if (!end || statement.getPart() != currentPart + 1) { - writer.append("continue ").append(mainLoopName()).append(";").softNewLine(); - } - } catch (IOException ex) { - throw new RenderingException("IO error occurred", ex); - } - } - - @Override - public void visit(MonitorEnterStatement statement) { - try { - if (async) { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorEnter", Object.class, void.class); - writer.appendMethodBody(monitorEnterRef).append("("); - precedence = Precedence.min(); - statement.getObjectRef().acceptVisitor(this); - writer.append(");").softNewLine(); - emitSuspendChecker(); - } else { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorEnterSync", Object.class, void.class); - writer.appendMethodBody(monitorEnterRef).append('('); - precedence = Precedence.min(); - statement.getObjectRef().acceptVisitor(this); - writer.append(");").softNewLine(); - } - } catch (IOException ex) { - throw new RenderingException("IO error occurred", ex); - } - } - - private void emitSuspendChecker() throws IOException { - writer.append("if").ws().append("(").appendFunction("$rt_suspending").append("())").ws() - .append("{").indent().softNewLine(); - writer.append("break ").append(mainLoopName()).append(";").softNewLine(); - writer.outdent().append("}").softNewLine(); - } - - @Override - public void visit(MonitorExitStatement statement) { - try { - if (async) { - MethodReference monitorExitRef = new MethodReference( - Object.class, "monitorExit", Object.class, void.class); - writer.appendMethodBody(monitorExitRef).append("("); - precedence = Precedence.min(); - statement.getObjectRef().acceptVisitor(this); - writer.append(");").softNewLine(); - } else { - MethodReference monitorEnterRef = new MethodReference( - Object.class, "monitorExitSync", Object.class, void.class); - writer.appendMethodBody(monitorEnterRef).append('('); - precedence = Precedence.min(); - statement.getObjectRef().acceptVisitor(this); - writer.append(");").softNewLine(); - } - } catch (IOException ex) { - throw new RenderingException("IO error occurred", ex); - } - } - - - private Injector getInjector(MethodReference ref) { - InjectorHolder holder = injectorMap.get(ref); - if (holder == null) { - holder = new InjectorHolder(null); - ClassHolder cls = classSource.get(ref.getClassName()); - if (cls != null) { - MethodHolder method = cls.getMethod(ref.getDescriptor()); - if (method != null) { - AnnotationHolder injectedByAnnot = method.getAnnotations().get(InjectedBy.class.getName()); - if (injectedByAnnot != null) { - ValueType type = injectedByAnnot.getValues().get("value").getJavaClass(); - holder = new InjectorHolder(instantiateInjector(((ValueType.Object) type).getClassName())); - } - } - } - injectorMap.put(ref, holder); - } - return holder.injector; - } - - private Injector instantiateInjector(String type) { - try { - Class cls = Class.forName(type, true, classLoader).asSubclass(Injector.class); - Constructor cons = cls.getConstructor(); - return cons.newInstance(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Illegal injector: " + type, e); - } catch (NoSuchMethodException e) { - throw new RuntimeException("Default constructor was not found in the " + type + " injector", e); - } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { - throw new RuntimeException("Error instantiating injector " + type, e); - } - } - - private class InjectorContextImpl implements InjectorContext { - private final List arguments; - private final Precedence precedence = Renderer.this.precedence; - - InjectorContextImpl(List arguments) { - this.arguments = arguments; - } - - @Override - public Expr getArgument(int index) { - return arguments.get(index); - } - - @Override - public boolean isMinifying() { - return minifying; - } - - @Override - public SourceWriter getWriter() { - return writer; - } - - @Override - public void writeEscaped(String str) throws IOException { - writer.append(escapeString(str)); - } - - @Override - public void writeType(ValueType type) throws IOException { - writer.append(typeToClsString(naming, type)); - } - - @Override - public void writeExpr(Expr expr) throws IOException { - writeExpr(expr, Precedence.GROUPING); - } - - @Override - public void writeExpr(Expr expr, Precedence precedence) throws IOException { - Renderer.this.precedence = precedence; - expr.acceptVisitor(Renderer.this); - } - - @Override - public int argumentCount() { - return arguments.size(); - } - - @Override - public T getService(Class type) { - return services.getService(type); - } - - @Override - public Properties getProperties() { - return new Properties(properties); - } - - @Override - public Precedence getPrecedence() { - return precedence; - } - - @Override - public ClassLoader getClassLoader() { - return classLoader; - } - - @Override - public ListableClassReaderSource getClassSource() { - return classSource; + writer.append(statementRenderer.variableName(0)); } } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java index d2eea7af1..0e401c21c 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingContext.java @@ -15,22 +15,269 @@ */ package org.teavm.backend.javascript.rendering; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; import org.teavm.backend.javascript.codegen.NamingStrategy; -import org.teavm.backend.javascript.codegen.SourceWriter; +import org.teavm.backend.javascript.spi.InjectedBy; +import org.teavm.backend.javascript.spi.Injector; import org.teavm.common.ServiceRepository; +import org.teavm.debugging.information.DebugInformationEmitter; +import org.teavm.model.AnnotationReader; +import org.teavm.model.ClassReader; import org.teavm.model.ListableClassReaderSource; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.TextLocation; +import org.teavm.model.ValueType; -public interface RenderingContext extends ServiceRepository { - NamingStrategy getNaming(); +public class RenderingContext { + private final DebugInformationEmitter debugEmitter; + private ListableClassReaderSource classSource; + private ClassLoader classLoader; + private ServiceRepository services; + private Properties properties; + private NamingStrategy naming; + private final Deque locationStack = new ArrayDeque<>(); + private final Map stringPoolMap = new HashMap<>(); + private final List stringPool = new ArrayList<>(); + private final List readonlyStringPool = Collections.unmodifiableList(stringPool); + private final Map injectorMap = new HashMap<>(); + private boolean minifying; - SourceWriter getWriter(); + public RenderingContext(DebugInformationEmitter debugEmitter, ListableClassReaderSource classSource, + ClassLoader classLoader, ServiceRepository services, Properties properties, + NamingStrategy naming) { + this.debugEmitter = debugEmitter; + this.classSource = classSource; + this.classLoader = classLoader; + this.services = services; + this.properties = properties; + this.naming = naming; + } - boolean isMinifying(); + public ListableClassReaderSource getClassSource() { + return classSource; + } - ListableClassReaderSource getClassSource(); + public ClassLoader getClassLoader() { + return classLoader; + } - ClassLoader getClassLoader(); + public ServiceRepository getServices() { + return services; + } - Properties getProperties(); + public Properties getProperties() { + return properties; + } + + public NamingStrategy getNaming() { + return naming; + } + + public void setMinifying(boolean minifying) { + this.minifying = minifying; + } + + public DebugInformationEmitter getDebugEmitter() { + return debugEmitter; + } + + public void pushLocation(TextLocation location) { + LocationStackEntry prevEntry = locationStack.peek(); + if (location != null) { + if (prevEntry == null || !location.equals(prevEntry.location)) { + debugEmitter.emitLocation(location.getFileName(), location.getLine()); + } + } else { + if (prevEntry != null) { + debugEmitter.emitLocation(null, -1); + } + } + locationStack.push(new LocationStackEntry(location)); + } + + public void popLocation() { + LocationStackEntry prevEntry = locationStack.pop(); + LocationStackEntry entry = locationStack.peek(); + if (entry != null) { + if (!entry.location.equals(prevEntry.location)) { + debugEmitter.emitLocation(entry.location.getFileName(), entry.location.getLine()); + } + } else { + debugEmitter.emitLocation(null, -1); + } + } + + public boolean isMinifying() { + return minifying; + } + + public int lookupString(String string) { + return stringPoolMap.computeIfAbsent(string, key -> { + stringPool.add(key); + return stringPool.size() - 1; + }); + } + + public List getStringPool() { + return readonlyStringPool; + } + + public String constantToString(Object cst) { + if (cst == null) { + return "null"; + } + if (cst instanceof ValueType) { + ValueType type = (ValueType) cst; + return naming.getNameForFunction("$rt_cls") + "(" + typeToClsString(type) + ")"; + } else if (cst instanceof String) { + String string = (String) cst; + int index = lookupString(string); + return "$rt_s(" + index + ")"; + } else if (cst instanceof Long) { + long value = (Long) cst; + if (value == 0) { + return "Long_ZERO"; + } else if ((int) value == value) { + return "Long_fromInt(" + value + ")"; + } else { + return "new Long(" + (value & 0xFFFFFFFFL) + ", " + (value >>> 32) + ")"; + } + } else if (cst instanceof Character) { + return Integer.toString((Character) cst); + } else { + return cst.toString(); + } + } + + public String typeToClsString(ValueType type) { + int arrayCount = 0; + while (type instanceof ValueType.Array) { + arrayCount++; + type = ((ValueType.Array) type).getItemType(); + } + String value; + if (type instanceof ValueType.Object) { + ValueType.Object objType = (ValueType.Object) type; + value = naming.getNameFor(objType.getClassName()); + } else if (type instanceof ValueType.Void) { + value = "$rt_voidcls()"; + } else if (type instanceof ValueType.Primitive) { + ValueType.Primitive primitiveType = (ValueType.Primitive) type; + switch (primitiveType.getKind()) { + case BOOLEAN: + value = "$rt_booleancls()"; + break; + case CHARACTER: + value = "$rt_charcls()"; + break; + case BYTE: + value = "$rt_bytecls()"; + break; + case SHORT: + value = "$rt_shortcls()"; + break; + case INTEGER: + value = "$rt_intcls()"; + break; + case LONG: + value = "$rt_longcls()"; + break; + case FLOAT: + value = "$rt_floatcls()"; + break; + case DOUBLE: + value = "$rt_doublecls()"; + break; + default: + throw new IllegalArgumentException("The type is not renderable"); + } + } else { + throw new IllegalArgumentException("The type is not renderable"); + } + + for (int i = 0; i < arrayCount; ++i) { + value = "$rt_arraycls(" + value + ")"; + } + return value; + } + + public String pointerName() { + return minifying ? "$p" : "$ptr"; + } + + public String mainLoopName() { + return minifying ? "$m" : "$main"; + } + + public String tempVarName() { + return minifying ? "$z" : "$tmp"; + } + + public String threadName() { + return minifying ? "$T" : "$thread"; + } + + private static class LocationStackEntry { + final TextLocation location; + + LocationStackEntry(TextLocation location) { + this.location = location; + } + } + + public void addInjector(MethodReference method, Injector injector) { + injectorMap.put(method, new InjectorHolder(injector)); + } + + public Injector getInjector(MethodReference ref) { + InjectorHolder holder = injectorMap.get(ref); + if (holder == null) { + holder = new InjectorHolder(null); + ClassReader cls = classSource.get(ref.getClassName()); + if (cls != null) { + MethodReader method = cls.getMethod(ref.getDescriptor()); + if (method != null) { + AnnotationReader injectedByAnnot = method.getAnnotations().get(InjectedBy.class.getName()); + if (injectedByAnnot != null) { + ValueType type = injectedByAnnot.getValue("value").getJavaClass(); + holder = new InjectorHolder(instantiateInjector(((ValueType.Object) type).getClassName())); + } + } + } + injectorMap.put(ref, holder); + } + return holder.injector; + } + + private Injector instantiateInjector(String type) { + try { + Class cls = Class.forName(type, true, classLoader).asSubclass(Injector.class); + Constructor cons = cls.getConstructor(); + return cons.newInstance(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Illegal injector: " + type, e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Default constructor was not found in the " + type + " injector", e); + } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new RuntimeException("Error instantiating injector " + type, e); + } + } + + private static class InjectorHolder { + public final Injector injector; + + private InjectorHolder(Injector injector) { + this.injector = injector; + } + } } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java new file mode 100644 index 000000000..53e0ef84e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.javascript.rendering; + +import java.util.Properties; +import org.teavm.backend.javascript.codegen.NamingStrategy; +import org.teavm.backend.javascript.codegen.SourceWriter; +import org.teavm.common.ServiceRepository; +import org.teavm.model.ListableClassReaderSource; + +public interface RenderingManager extends ServiceRepository { + NamingStrategy getNaming(); + + SourceWriter getWriter(); + + boolean isMinifying(); + + ListableClassReaderSource getClassSource(); + + ClassLoader getClassLoader(); + + Properties getProperties(); +} diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingUtil.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingUtil.java new file mode 100644 index 000000000..f14be7d5c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingUtil.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.javascript.rendering; + +public final class RenderingUtil { + public static final String variableNames = "abcdefghijkmnopqrstuvwxyz"; + public static final String variablePartNames = "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + private RenderingUtil() { + } + + public static String escapeName(String name) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < name.length(); ++i) { + char c = name.charAt(i); + sb.append(Character.isJavaIdentifierPart(c) ? c : '_'); + } + return sb.toString(); + } + + public static String escapeString(String str) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < str.length(); ++i) { + char c = str.charAt(i); + switch (c) { + case '\r': + sb.append("\\r"); + break; + case '\n': + sb.append("\\n"); + break; + case '\t': + sb.append("\\t"); + break; + case '\'': + sb.append("\\'"); + break; + case '\"': + sb.append("\\\""); + break; + case '\\': + sb.append("\\\\"); + break; + default: + if (c < ' ') { + sb.append("\\u00").append(Character.forDigit(c / 16, 16)) + .append(Character.forDigit(c % 16, 16)); + } else if (Character.isLowSurrogate(c) || Character.isHighSurrogate(c)) { + sb.append("\\u") + .append(Character.forDigit(c / 0x1000, 0x10)) + .append(Character.forDigit((c / 0x100) % 0x10, 0x10)) + .append(Character.forDigit((c / 0x10) % 0x10, 0x10)) + .append(Character.forDigit(c % 0x10, 0x10)); + } else { + sb.append(c); + } + break; + } + } + return sb.toString(); + } + + public static String indexToId(int index) { + StringBuilder sb = new StringBuilder(); + do { + sb.append(variablePartNames.charAt(index % variablePartNames.length())); + index /= variablePartNames.length(); + } while (index > 0); + return sb.toString(); + } +} diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java new file mode 100644 index 000000000..e36ce574a --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java @@ -0,0 +1,1521 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.javascript.rendering; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import org.teavm.ast.AssignmentStatement; +import org.teavm.ast.BinaryExpr; +import org.teavm.ast.BinaryOperation; +import org.teavm.ast.BlockStatement; +import org.teavm.ast.BreakStatement; +import org.teavm.ast.CastExpr; +import org.teavm.ast.ConditionalExpr; +import org.teavm.ast.ConditionalStatement; +import org.teavm.ast.ConstantExpr; +import org.teavm.ast.ContinueStatement; +import org.teavm.ast.Expr; +import org.teavm.ast.ExprVisitor; +import org.teavm.ast.GotoPartStatement; +import org.teavm.ast.InitClassStatement; +import org.teavm.ast.InstanceOfExpr; +import org.teavm.ast.InvocationExpr; +import org.teavm.ast.InvocationType; +import org.teavm.ast.MethodNode; +import org.teavm.ast.MonitorEnterStatement; +import org.teavm.ast.MonitorExitStatement; +import org.teavm.ast.NewArrayExpr; +import org.teavm.ast.NewExpr; +import org.teavm.ast.NewMultiArrayExpr; +import org.teavm.ast.OperationType; +import org.teavm.ast.PrimitiveCastExpr; +import org.teavm.ast.QualificationExpr; +import org.teavm.ast.ReturnStatement; +import org.teavm.ast.SequentialStatement; +import org.teavm.ast.Statement; +import org.teavm.ast.StatementVisitor; +import org.teavm.ast.SubscriptExpr; +import org.teavm.ast.SwitchClause; +import org.teavm.ast.SwitchStatement; +import org.teavm.ast.ThrowStatement; +import org.teavm.ast.TryCatchStatement; +import org.teavm.ast.UnaryExpr; +import org.teavm.ast.UnwrapArrayExpr; +import org.teavm.ast.VariableExpr; +import org.teavm.ast.VariableNode; +import org.teavm.ast.WhileStatement; +import org.teavm.backend.javascript.codegen.NamingStrategy; +import org.teavm.backend.javascript.codegen.SourceWriter; +import org.teavm.backend.javascript.spi.Injector; +import org.teavm.backend.javascript.spi.InjectorContext; +import org.teavm.debugging.information.DebugInformationEmitter; +import org.teavm.debugging.information.DeferredCallSite; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.TextLocation; +import org.teavm.model.ValueType; +import org.teavm.vm.RenderingException; + +public class StatementRenderer implements ExprVisitor, StatementVisitor { + private static final Set keywords = new HashSet<>(Arrays.asList("break", "case", "catch", + "class", "const", "continue", "debugger", "default", "delete", "do", "else", "export", + "extends", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "return", + "super", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "yield")); + private RenderingContext context; + private SourceWriter writer; + private ClassReaderSource classSource; + private boolean async; + private boolean minifying; + private Precedence precedence; + private DebugInformationEmitter debugEmitter; + private NamingStrategy naming; + private DeferredCallSite lastCallSite; + private DeferredCallSite prevCallSite; + private boolean end; + private final Map blockIdMap = new HashMap<>(); + private final List cachedVariableNames = new ArrayList<>(); + private final Set usedVariableNames = new HashSet<>(); + private MethodNode currentMethod; + private int currentPart; + + public StatementRenderer(RenderingContext context, SourceWriter writer) { + this.context = context; + this.writer = writer; + this.classSource = context.getClassSource(); + this.minifying = context.isMinifying(); + this.naming = context.getNaming(); + this.debugEmitter = context.getDebugEmitter(); + } + + public boolean isAsync() { + return async; + } + + public void setAsync(boolean async) { + this.async = async; + } + + public MethodNode getCurrentMethod() { + return currentMethod; + } + + public void setCurrentMethod(MethodNode currentMethod) { + this.currentMethod = currentMethod; + } + + public int getCurrentPart() { + return currentPart; + } + + public void setCurrentPart(int currentPart) { + this.currentPart = currentPart; + } + + public void setEnd(boolean end) { + this.end = end; + } + + private void pushLocation(TextLocation location) { + context.pushLocation(location); + } + + private void popLocation() { + context.popLocation(); + } + + @Override + public void visit(AssignmentStatement statement) throws RenderingException { + try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } + prevCallSite = debugEmitter.emitCallSite(); + if (statement.getLeftValue() != null) { + if (statement.isAsync()) { + writer.append(context.tempVarName()); + } else { + precedence = Precedence.COMMA; + statement.getLeftValue().acceptVisitor(this); + } + writer.ws().append("=").ws(); + } + precedence = Precedence.COMMA; + statement.getRightValue().acceptVisitor(this); + debugEmitter.emitCallSite(); + writer.append(";").softNewLine(); + if (statement.isAsync()) { + emitSuspendChecker(); + if (statement.getLeftValue() != null) { + precedence = Precedence.COMMA; + statement.getLeftValue().acceptVisitor(this); + writer.ws().append("=").ws().append(context.tempVarName()).append(";").softNewLine(); + } + } + if (statement.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(SequentialStatement statement) { + visitStatements(statement.getSequence()); + } + + @Override + public void visit(ConditionalStatement statement) { + try { + while (true) { + debugEmitter.emitStatementStart(); + if (statement.getCondition().getLocation() != null) { + pushLocation(statement.getCondition().getLocation()); + } + prevCallSite = debugEmitter.emitCallSite(); + writer.append("if").ws().append("("); + precedence = Precedence.COMMA; + statement.getCondition().acceptVisitor(this); + if (statement.getCondition().getLocation() != null) { + popLocation(); + } + debugEmitter.emitCallSite(); + writer.append(")").ws().append("{").softNewLine().indent(); + visitStatements(statement.getConsequent()); + if (!statement.getAlternative().isEmpty()) { + writer.outdent().append("}").ws(); + if (statement.getAlternative().size() == 1 + && statement.getAlternative().get(0) instanceof ConditionalStatement) { + statement = (ConditionalStatement) statement.getAlternative().get(0); + writer.append("else "); + continue; + } + writer.append("else").ws().append("{").indent().softNewLine(); + visitStatements(statement.getAlternative()); + } + break; + } + writer.outdent().append("}").softNewLine(); + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(SwitchStatement statement) { + try { + debugEmitter.emitStatementStart(); + if (statement.getValue().getLocation() != null) { + pushLocation(statement.getValue().getLocation()); + } + if (statement.getId() != null) { + writer.append(mapBlockId(statement.getId())).append(":").ws(); + } + prevCallSite = debugEmitter.emitCallSite(); + writer.append("switch").ws().append("("); + precedence = Precedence.min(); + statement.getValue().acceptVisitor(this); + if (statement.getValue().getLocation() != null) { + popLocation(); + } + debugEmitter.emitCallSite(); + writer.append(")").ws().append("{").softNewLine().indent(); + for (SwitchClause clause : statement.getClauses()) { + for (int condition : clause.getConditions()) { + writer.append("case ").append(condition).append(":").softNewLine(); + } + writer.indent(); + boolean oldEnd = end; + for (Statement part : clause.getBody()) { + end = false; + part.acceptVisitor(this); + } + end = oldEnd; + writer.outdent(); + } + if (statement.getDefaultClause() != null) { + writer.append("default:").softNewLine().indent(); + boolean oldEnd = end; + for (Statement part : statement.getDefaultClause()) { + end = false; + part.acceptVisitor(this); + } + end = oldEnd; + writer.outdent(); + } + writer.outdent().append("}").softNewLine(); + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(WhileStatement statement) { + try { + debugEmitter.emitStatementStart(); + if (statement.getCondition() != null && statement.getCondition().getLocation() != null) { + pushLocation(statement.getCondition().getLocation()); + } + if (statement.getId() != null) { + writer.append(mapBlockId(statement.getId())).append(":").ws(); + } + writer.append("while").ws().append("("); + if (statement.getCondition() != null) { + prevCallSite = debugEmitter.emitCallSite(); + precedence = Precedence.min(); + statement.getCondition().acceptVisitor(this); + debugEmitter.emitCallSite(); + if (statement.getCondition().getLocation() != null) { + popLocation(); + } + } else { + writer.append("true"); + } + writer.append(")").ws().append("{").softNewLine().indent(); + boolean oldEnd = end; + for (Statement part : statement.getBody()) { + end = false; + part.acceptVisitor(this); + } + end = oldEnd; + writer.outdent().append("}").softNewLine(); + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + private String mapBlockId(String id) { + String name = blockIdMap.get(id); + if (name == null) { + int index = blockIdMap.size(); + name = "$b" + RenderingUtil.indexToId(index); + blockIdMap.put(id, name); + } + return name; + } + + @Override + public void visit(BlockStatement statement) { + try { + writer.append(mapBlockId(statement.getId())).append(":").ws().append("{").softNewLine().indent(); + visitStatements(statement.getBody()); + writer.outdent().append("}").softNewLine(); + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(BreakStatement statement) { + try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } + writer.append("break"); + if (statement.getTarget() != null) { + writer.append(' ').append(mapBlockId(statement.getTarget().getId())); + } + writer.append(";").softNewLine(); + if (statement.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(ContinueStatement statement) { + try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } + writer.append("continue"); + if (statement.getTarget() != null) { + writer.append(' ').append(mapBlockId(statement.getTarget().getId())); + } + writer.append(";").softNewLine(); + if (statement.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(ReturnStatement statement) { + try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } + writer.append("return"); + if (statement.getResult() != null) { + writer.append(' '); + prevCallSite = debugEmitter.emitCallSite(); + precedence = Precedence.min(); + statement.getResult().acceptVisitor(this); + debugEmitter.emitCallSite(); + } + writer.append(";").softNewLine(); + if (statement.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(ThrowStatement statement) { + try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } + writer.appendFunction("$rt_throw").append("("); + prevCallSite = debugEmitter.emitCallSite(); + precedence = Precedence.min(); + statement.getException().acceptVisitor(this); + writer.append(");").softNewLine(); + debugEmitter.emitCallSite(); + if (statement.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(InitClassStatement statement) { + ClassReader cls = classSource.get(statement.getClassName()); + if (cls == null) { + return; + } + MethodReader method = cls.getMethod(new MethodDescriptor("", void.class)); + if (method == null) { + return; + } + try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } + writer.appendClass(statement.getClassName()).append("_$callClinit();").softNewLine(); + if (statement.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + public String variableName(int index) { + while (index >= cachedVariableNames.size()) { + cachedVariableNames.add(null); + } + String name = cachedVariableNames.get(index); + if (name == null) { + name = generateVariableName(index); + cachedVariableNames.set(index, name); + } + return name; + } + + private String generateVariableName(int index) { + if (index == 0) { + return minifying ? "$t" : "$this"; + } + + if (!minifying) { + VariableNode variable = index < currentMethod.getVariables().size() + ? currentMethod.getVariables().get(index) + : null; + if (variable != null && variable.getName() != null) { + String result = RenderingUtil.escapeName(variable.getName()); + if (keywords.contains(result) || !usedVariableNames.add(result)) { + String base = result; + int suffix = 0; + do { + result = base + "_" + suffix++; + } while (!usedVariableNames.add(result)); + } + return result; + } else { + return "var$" + index; + } + } else { + return RenderingUtil.indexToId(--index); + } + } + + private void visitBinary(BinaryExpr expr, String op, boolean guarded) { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + if (guarded) { + visitBinary(BinaryOperation.OR, "|", () -> visitBinary(expr, op, false), + () -> { + try { + writer.append("0"); + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + }); + } else { + visitBinary(expr.getOperation(), op, () -> expr.getFirstOperand().acceptVisitor(this), + () -> expr.getSecondOperand().acceptVisitor(this)); + } + if (expr.getLocation() != null) { + popLocation(); + } + } + + private void visitBinary(BinaryOperation operation, String infixText, Runnable a, Runnable b) { + try { + Precedence outerPrecedence = precedence; + Precedence innerPrecedence = getPrecedence(operation); + if (innerPrecedence.ordinal() < outerPrecedence.ordinal()) { + writer.append('('); + } + + switch (operation) { + case ADD: + case SUBTRACT: + case MULTIPLY: + case DIVIDE: + case MODULO: + case AND: + case OR: + case BITWISE_AND: + case BITWISE_OR: + case BITWISE_XOR: + case LEFT_SHIFT: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + precedence = innerPrecedence; + break; + default: + precedence = innerPrecedence.next(); + } + a.run(); + + writer.ws().append(infixText).ws(); + + switch (operation) { + case ADD: + case MULTIPLY: + case AND: + case OR: + case BITWISE_AND: + case BITWISE_OR: + case BITWISE_XOR: + precedence = innerPrecedence; + break; + default: + precedence = innerPrecedence.next(); + break; + } + b.run(); + + if (innerPrecedence.ordinal() < outerPrecedence.ordinal()) { + writer.append(')'); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + private static Precedence getPrecedence(BinaryOperation op) { + switch (op) { + case ADD: + case SUBTRACT: + return Precedence.ADDITION; + case MULTIPLY: + case DIVIDE: + case MODULO: + return Precedence.MULTIPLICATION; + case AND: + return Precedence.LOGICAL_AND; + case OR: + return Precedence.LOGICAL_OR; + case EQUALS: + case NOT_EQUALS: + return Precedence.EQUALITY; + case GREATER: + case GREATER_OR_EQUALS: + case LESS: + case LESS_OR_EQUALS: + return Precedence.COMPARISON; + case BITWISE_AND: + return Precedence.BITWISE_AND; + case BITWISE_OR: + return Precedence.BITWISE_OR; + case BITWISE_XOR: + return Precedence.BITWISE_XOR; + case LEFT_SHIFT: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + return Precedence.BITWISE_SHIFT; + default: + return Precedence.GROUPING; + } + } + + private void visitBinaryFunction(BinaryExpr expr, String function) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + writer.append(function); + writer.append('('); + precedence = Precedence.min(); + expr.getFirstOperand().acceptVisitor(this); + writer.append(",").ws(); + precedence = Precedence.min(); + expr.getSecondOperand().acceptVisitor(this); + writer.append(')'); + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occured", e); + } + } + + @Override + public void visit(BinaryExpr expr) { + if (expr.getType() == OperationType.LONG) { + switch (expr.getOperation()) { + case ADD: + visitBinaryFunction(expr, "Long_add"); + break; + case SUBTRACT: + visitBinaryFunction(expr, "Long_sub"); + break; + case MULTIPLY: + visitBinaryFunction(expr, "Long_mul"); + break; + case DIVIDE: + visitBinaryFunction(expr, "Long_div"); + break; + case MODULO: + visitBinaryFunction(expr, "Long_rem"); + break; + case BITWISE_OR: + visitBinaryFunction(expr, "Long_or"); + break; + case BITWISE_AND: + visitBinaryFunction(expr, "Long_and"); + break; + case BITWISE_XOR: + visitBinaryFunction(expr, "Long_xor"); + break; + case LEFT_SHIFT: + visitBinaryFunction(expr, "Long_shl"); + break; + case RIGHT_SHIFT: + visitBinaryFunction(expr, "Long_shr"); + break; + case UNSIGNED_RIGHT_SHIFT: + visitBinaryFunction(expr, "Long_shru"); + break; + case COMPARE: + visitBinaryFunction(expr, "Long_compare"); + break; + case EQUALS: + visitBinaryFunction(expr, "Long_eq"); + break; + case NOT_EQUALS: + visitBinaryFunction(expr, "Long_ne"); + break; + case LESS: + visitBinaryFunction(expr, "Long_lt"); + break; + case LESS_OR_EQUALS: + visitBinaryFunction(expr, "Long_le"); + break; + case GREATER: + visitBinaryFunction(expr, "Long_gt"); + break; + case GREATER_OR_EQUALS: + visitBinaryFunction(expr, "Long_ge"); + break; + default: + break; + } + } else { + switch (expr.getOperation()) { + case ADD: + visitBinary(expr, "+", expr.getType() == OperationType.INT); + break; + case SUBTRACT: + visitBinary(expr, "-", expr.getType() == OperationType.INT); + break; + case MULTIPLY: + visitBinary(expr, "*", expr.getType() == OperationType.INT); + break; + case DIVIDE: + visitBinary(expr, "/", expr.getType() == OperationType.INT); + break; + case MODULO: + visitBinary(expr, "%", expr.getType() == OperationType.INT); + break; + case EQUALS: + if (expr.getType() == OperationType.INT) { + visitBinary(expr, "==", false); + } else { + visitBinary(expr, "===", false); + } + break; + case NOT_EQUALS: + if (expr.getType() == OperationType.INT) { + visitBinary(expr, "!=", false); + } else { + visitBinary(expr, "!==", false); + } + break; + case GREATER: + visitBinary(expr, ">", false); + break; + case GREATER_OR_EQUALS: + visitBinary(expr, ">=", false); + break; + case LESS: + visitBinary(expr, "<", false); + break; + case LESS_OR_EQUALS: + visitBinary(expr, "<=", false); + break; + case COMPARE: + visitBinaryFunction(expr, naming.getNameForFunction("$rt_compare")); + break; + case OR: + visitBinary(expr, "||", false); + break; + case AND: + visitBinary(expr, "&&", false); + break; + case BITWISE_OR: + visitBinary(expr, "|", false); + break; + case BITWISE_AND: + visitBinary(expr, "&", false); + break; + case BITWISE_XOR: + visitBinary(expr, "^", false); + break; + case LEFT_SHIFT: + visitBinary(expr, "<<", false); + break; + case RIGHT_SHIFT: + visitBinary(expr, ">>", false); + break; + case UNSIGNED_RIGHT_SHIFT: + visitBinary(expr, ">>>", false); + break; + } + } + } + + @Override + public void visit(UnaryExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + Precedence outerPrecedence = precedence; + switch (expr.getOperation()) { + case NOT: { + if (expr.getType() == OperationType.LONG) { + writer.append("Long_not("); + precedence = Precedence.min(); + expr.getOperand().acceptVisitor(this); + writer.append(')'); + } else { + if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { + writer.append('('); + } + writer.append(expr.getType() == null ? "!" : "~"); + precedence = Precedence.UNARY; + expr.getOperand().acceptVisitor(this); + if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { + writer.append(')'); + } + } + break; + } + case NEGATE: + if (expr.getType() == OperationType.LONG) { + writer.append("Long_neg("); + precedence = Precedence.min(); + expr.getOperand().acceptVisitor(this); + writer.append(')'); + } else { + if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { + writer.append('('); + } + writer.append(" -"); + precedence = Precedence.UNARY; + expr.getOperand().acceptVisitor(this); + if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { + writer.append(')'); + } + } + break; + case LENGTH: + precedence = Precedence.MEMBER_ACCESS; + expr.getOperand().acceptVisitor(this); + writer.append(".length"); + break; + case INT_TO_BYTE: + if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { + writer.append('('); + } + precedence = Precedence.BITWISE_SHIFT; + expr.getOperand().acceptVisitor(this); + writer.ws().append("<<").ws().append("24").ws().append(">>").ws().append("24"); + if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { + writer.append(')'); + } + break; + case INT_TO_SHORT: + if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { + writer.append('('); + } + precedence = Precedence.BITWISE_SHIFT; + expr.getOperand().acceptVisitor(this); + writer.ws().append("<<").ws().append("16").ws().append(">>").ws().append("16"); + if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { + writer.append(')'); + } + break; + case INT_TO_CHAR: + if (outerPrecedence.ordinal() > Precedence.BITWISE_AND.ordinal()) { + writer.append('('); + } + precedence = Precedence.BITWISE_AND; + expr.getOperand().acceptVisitor(this); + writer.ws().append("&").ws().append("65535"); + if (outerPrecedence.ordinal() > Precedence.BITWISE_AND.ordinal()) { + writer.append(')'); + } + break; + case NULL_CHECK: + writer.appendFunction("$rt_nullCheck").append("("); + precedence = Precedence.min(); + expr.getOperand().acceptVisitor(this); + writer.append(')'); + break; + } + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occured", e); + } + } + + @Override + public void visit(CastExpr expr) { + expr.getValue().acceptVisitor(this); + } + + @Override + public void visit(PrimitiveCastExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + switch (expr.getSource()) { + case INT: + if (expr.getTarget() == OperationType.LONG) { + writer.append("Long_fromInt("); + precedence = Precedence.min(); + expr.getValue().acceptVisitor(this); + writer.append(')'); + } else { + expr.getValue().acceptVisitor(this); + } + break; + case LONG: + switch (expr.getTarget()) { + case INT: + precedence = Precedence.MEMBER_ACCESS; + expr.getValue().acceptVisitor(this); + writer.append(".lo"); + break; + case FLOAT: + case DOUBLE: + writer.append("Long_toNumber("); + precedence = Precedence.min(); + expr.getValue().acceptVisitor(this); + writer.append(')'); + break; + default: + expr.getValue().acceptVisitor(this); + } + break; + case FLOAT: + case DOUBLE: + switch (expr.getTarget()) { + case LONG: + writer.append("Long_fromNumber("); + precedence = Precedence.min(); + expr.getValue().acceptVisitor(this); + writer.append(')'); + break; + case INT: + visitBinary(BinaryOperation.BITWISE_OR, "|", () -> expr.getValue().acceptVisitor(this), + () -> { + try { + writer.append("0"); + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + }); + break; + default: + expr.getValue().acceptVisitor(this); + } + break; + } + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + } catch (IOException e) { + throw new RenderingException("IO error occured", e); + } + } + + @Override + public void visit(ConditionalExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + + Precedence outerPrecedence = precedence; + if (outerPrecedence.ordinal() > Precedence.CONDITIONAL.ordinal()) { + writer.append('('); + } + + precedence = Precedence.CONDITIONAL.next(); + expr.getCondition().acceptVisitor(this); + writer.ws().append("?").ws(); + precedence = Precedence.CONDITIONAL.next(); + expr.getConsequent().acceptVisitor(this); + writer.ws().append(":").ws(); + precedence = Precedence.CONDITIONAL; + expr.getAlternative().acceptVisitor(this); + + if (outerPrecedence.ordinal() > Precedence.CONDITIONAL.ordinal()) { + writer.append('('); + } + + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occured", e); + } + } + + @Override + public void visit(ConstantExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + String str = context.constantToString(expr.getValue()); + if (str.startsWith("-")) { + writer.append(' '); + } + writer.append(str); + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occured", e); + } + } + + @Override + public void visit(VariableExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + writer.append(variableName(expr.getIndex())); + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occured", e); + } + } + + @Override + public void visit(SubscriptExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + precedence = Precedence.MEMBER_ACCESS; + expr.getArray().acceptVisitor(this); + writer.append('['); + precedence = Precedence.min(); + expr.getIndex().acceptVisitor(this); + writer.append(']'); + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(UnwrapArrayExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + precedence = Precedence.MEMBER_ACCESS; + expr.getArray().acceptVisitor(this); + writer.append(".data"); + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(InvocationExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + Injector injector = context.getInjector(expr.getMethod()); + if (injector != null) { + injector.generate(new InjectorContextImpl(expr.getArguments()), expr.getMethod()); + } else { + if (expr.getType() == InvocationType.DYNAMIC) { + precedence = Precedence.MEMBER_ACCESS; + expr.getArguments().get(0).acceptVisitor(this); + } + MethodReference method = expr.getMethod(); + String name = naming.getNameFor(method.getDescriptor()); + DeferredCallSite callSite = prevCallSite; + boolean shouldEraseCallSite = lastCallSite == null; + if (lastCallSite == null) { + lastCallSite = callSite; + } + boolean virtual = false; + switch (expr.getType()) { + case STATIC: + writer.append(naming.getFullNameFor(method)).append("("); + prevCallSite = debugEmitter.emitCallSite(); + for (int i = 0; i < expr.getArguments().size(); ++i) { + if (i > 0) { + writer.append(",").ws(); + } + precedence = Precedence.min(); + expr.getArguments().get(i).acceptVisitor(this); + } + break; + case SPECIAL: + writer.append(naming.getFullNameFor(method)).append("("); + prevCallSite = debugEmitter.emitCallSite(); + precedence = Precedence.min(); + expr.getArguments().get(0).acceptVisitor(this); + for (int i = 1; i < expr.getArguments().size(); ++i) { + writer.append(",").ws(); + precedence = Precedence.min(); + expr.getArguments().get(i).acceptVisitor(this); + } + break; + case DYNAMIC: + writer.append(".").append(name).append("("); + prevCallSite = debugEmitter.emitCallSite(); + for (int i = 1; i < expr.getArguments().size(); ++i) { + if (i > 1) { + writer.append(",").ws(); + } + precedence = Precedence.min(); + expr.getArguments().get(i).acceptVisitor(this); + } + virtual = true; + break; + case CONSTRUCTOR: + writer.append(naming.getNameForInit(expr.getMethod())).append("("); + prevCallSite = debugEmitter.emitCallSite(); + for (int i = 0; i < expr.getArguments().size(); ++i) { + if (i > 0) { + writer.append(",").ws(); + } + precedence = Precedence.min(); + expr.getArguments().get(i).acceptVisitor(this); + } + break; + } + writer.append(')'); + if (lastCallSite != null) { + if (virtual) { + lastCallSite.setVirtualMethod(expr.getMethod()); + } else { + lastCallSite.setStaticMethod(expr.getMethod()); + } + lastCallSite = callSite; + } + if (shouldEraseCallSite) { + lastCallSite = null; + } + } + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(QualificationExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + precedence = Precedence.MEMBER_ACCESS; + + if (expr.getQualified() != null) { + expr.getQualified().acceptVisitor(this); + writer.append('.').appendField(expr.getField()); + } else { + writer.appendStaticField(expr.getField()); + } + + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(NewExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + precedence = Precedence.FUNCTION_CALL; + writer.append("new ").append(naming.getNameFor(expr.getConstructedClass())); + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(NewArrayExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + ValueType type = expr.getType(); + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + writer.append("$rt_createBooleanArray("); + precedence = Precedence.min(); + expr.getLength().acceptVisitor(this); + writer.append(")"); + break; + case BYTE: + writer.append("$rt_createByteArray("); + precedence = Precedence.min(); + expr.getLength().acceptVisitor(this); + writer.append(")"); + break; + case SHORT: + writer.append("$rt_createShortArray("); + precedence = Precedence.min(); + expr.getLength().acceptVisitor(this); + writer.append(")"); + break; + case INTEGER: + writer.append("$rt_createIntArray("); + precedence = Precedence.min(); + expr.getLength().acceptVisitor(this); + writer.append(")"); + break; + case LONG: + writer.append("$rt_createLongArray("); + precedence = Precedence.min(); + expr.getLength().acceptVisitor(this); + writer.append(")"); + break; + case FLOAT: + writer.append("$rt_createFloatArray("); + precedence = Precedence.min(); + expr.getLength().acceptVisitor(this); + writer.append(")"); + break; + case DOUBLE: + writer.append("$rt_createDoubleArray("); + precedence = Precedence.min(); + expr.getLength().acceptVisitor(this); + writer.append(")"); + break; + case CHARACTER: + writer.append("$rt_createCharArray("); + precedence = Precedence.min(); + expr.getLength().acceptVisitor(this); + writer.append(")"); + break; + } + } else { + writer.appendFunction("$rt_createArray").append("(").append(context.typeToClsString(expr.getType())) + .append(",").ws(); + precedence = Precedence.min(); + expr.getLength().acceptVisitor(this); + writer.append(")"); + } + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(NewMultiArrayExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + ValueType type = expr.getType(); + for (int i = 0; i < expr.getDimensions().size(); ++i) { + type = ((ValueType.Array) type).getItemType(); + } + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + writer.append("$rt_createBooleanMultiArray("); + break; + case BYTE: + writer.append("$rt_createByteMultiArray("); + break; + case SHORT: + writer.append("$rt_createShortMultiArray("); + break; + case INTEGER: + writer.append("$rt_createIntMultiArray("); + break; + case LONG: + writer.append("$rt_createLongMultiArray("); + break; + case FLOAT: + writer.append("$rt_createFloatMultiArray("); + break; + case DOUBLE: + writer.append("$rt_createDoubleMultiArray("); + break; + case CHARACTER: + writer.append("$rt_createCharMultiArray("); + break; + } + } else { + writer.append("$rt_createMultiArray(").append(context.typeToClsString(expr.getType())) + .append(",").ws(); + } + writer.append("["); + boolean first = true; + List dimensions = new ArrayList<>(expr.getDimensions()); + Collections.reverse(dimensions); + for (Expr dimension : dimensions) { + if (!first) { + writer.append(",").ws(); + } + first = false; + precedence = Precedence.min(); + dimension.acceptVisitor(this); + } + writer.append("])"); + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(InstanceOfExpr expr) { + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + if (expr.getType() instanceof ValueType.Object) { + String clsName = ((ValueType.Object) expr.getType()).getClassName(); + ClassReader cls = classSource.get(clsName); + if (cls != null && !cls.hasModifier(ElementModifier.INTERFACE)) { + boolean needsParentheses = Precedence.COMPARISON.ordinal() < precedence.ordinal(); + if (needsParentheses) { + writer.append('('); + } + precedence = Precedence.CONDITIONAL.next(); + expr.getExpr().acceptVisitor(this); + writer.append(" instanceof ").appendClass(clsName); + if (expr.getLocation() != null) { + popLocation(); + } + return; + } + } + writer.appendFunction("$rt_isInstance").append("("); + precedence = Precedence.min(); + expr.getExpr().acceptVisitor(this); + writer.append(",").ws().append(context.typeToClsString(expr.getType())).append(")"); + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + private void visitStatements(List statements) { + if (statements.isEmpty()) { + return; + } + boolean oldEnd = end; + for (int i = 0; i < statements.size() - 1; ++i) { + end = false; + statements.get(i).acceptVisitor(this); + } + end = oldEnd; + statements.get(statements.size() - 1).acceptVisitor(this); + end = oldEnd; + } + + @Override + public void visit(TryCatchStatement statement) { + try { + writer.append("try").ws().append("{").softNewLine().indent(); + List sequence = new ArrayList<>(); + sequence.add(statement); + List protectedBody = statement.getProtectedBody(); + while (protectedBody.size() == 1 && protectedBody.get(0) instanceof TryCatchStatement) { + TryCatchStatement nextStatement = (TryCatchStatement) protectedBody.get(0); + sequence.add(nextStatement); + protectedBody = nextStatement.getProtectedBody(); + } + visitStatements(protectedBody); + writer.outdent().append("}").ws().append("catch").ws().append("($e)") + .ws().append("{").indent().softNewLine(); + writer.append("$je").ws().append("=").ws().append("$e.$javaException;").softNewLine(); + for (TryCatchStatement catchClause : sequence) { + writer.append("if").ws().append("($je"); + if (catchClause.getExceptionType() != null) { + writer.ws().append("&&").ws().append("$je instanceof ") + .appendClass(catchClause.getExceptionType()); + } + writer.append(")").ws().append("{").indent().softNewLine(); + if (catchClause.getExceptionVariable() != null) { + writer.append(variableName(catchClause.getExceptionVariable())).ws().append("=").ws() + .append("$je;").softNewLine(); + } + visitStatements(catchClause.getHandler()); + writer.outdent().append("}").ws().append("else "); + } + writer.append("{").indent().softNewLine(); + writer.append("throw $e;").softNewLine(); + writer.outdent().append("}").softNewLine(); + writer.outdent().append("}").softNewLine(); + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } + } + + @Override + public void visit(GotoPartStatement statement) { + try { + if (statement.getPart() != currentPart) { + writer.append(context.pointerName()).ws().append("=").ws().append(statement.getPart()).append(";") + .softNewLine(); + } + if (!end || statement.getPart() != currentPart + 1) { + writer.append("continue ").append(context.mainLoopName()).append(";").softNewLine(); + } + } catch (IOException ex) { + throw new RenderingException("IO error occurred", ex); + } + } + + @Override + public void visit(MonitorEnterStatement statement) { + try { + if (async) { + MethodReference monitorEnterRef = new MethodReference( + Object.class, "monitorEnter", Object.class, void.class); + writer.appendMethodBody(monitorEnterRef).append("("); + precedence = Precedence.min(); + statement.getObjectRef().acceptVisitor(this); + writer.append(");").softNewLine(); + emitSuspendChecker(); + } else { + MethodReference monitorEnterRef = new MethodReference( + Object.class, "monitorEnterSync", Object.class, void.class); + writer.appendMethodBody(monitorEnterRef).append('('); + precedence = Precedence.min(); + statement.getObjectRef().acceptVisitor(this); + writer.append(");").softNewLine(); + } + } catch (IOException ex) { + throw new RenderingException("IO error occurred", ex); + } + } + + public void emitSuspendChecker() throws IOException { + writer.append("if").ws().append("(").appendFunction("$rt_suspending").append("())").ws() + .append("{").indent().softNewLine(); + writer.append("break ").append(context.mainLoopName()).append(";").softNewLine(); + writer.outdent().append("}").softNewLine(); + } + + @Override + public void visit(MonitorExitStatement statement) { + try { + if (async) { + MethodReference monitorExitRef = new MethodReference( + Object.class, "monitorExit", Object.class, void.class); + writer.appendMethodBody(monitorExitRef).append("("); + precedence = Precedence.min(); + statement.getObjectRef().acceptVisitor(this); + writer.append(");").softNewLine(); + } else { + MethodReference monitorEnterRef = new MethodReference( + Object.class, "monitorExitSync", Object.class, void.class); + writer.appendMethodBody(monitorEnterRef).append('('); + precedence = Precedence.min(); + statement.getObjectRef().acceptVisitor(this); + writer.append(");").softNewLine(); + } + } catch (IOException ex) { + throw new RenderingException("IO error occurred", ex); + } + } + + private class InjectorContextImpl implements InjectorContext { + private final List arguments; + private final Precedence precedence = StatementRenderer.this.precedence; + + InjectorContextImpl(List arguments) { + this.arguments = arguments; + } + + @Override + public Expr getArgument(int index) { + return arguments.get(index); + } + + @Override + public boolean isMinifying() { + return minifying; + } + + @Override + public SourceWriter getWriter() { + return writer; + } + + @Override + public void writeEscaped(String str) throws IOException { + writer.append(RenderingUtil.escapeString(str)); + } + + @Override + public void writeType(ValueType type) throws IOException { + writer.append(context.typeToClsString(type)); + } + + @Override + public void writeExpr(Expr expr) throws IOException { + writeExpr(expr, Precedence.GROUPING); + } + + @Override + public void writeExpr(Expr expr, Precedence precedence) throws IOException { + StatementRenderer.this.precedence = precedence; + expr.acceptVisitor(StatementRenderer.this); + } + + @Override + public int argumentCount() { + return arguments.size(); + } + + @Override + public T getService(Class type) { + return context.getServices().getService(type); + } + + @Override + public Properties getProperties() { + return new Properties(context.getProperties()); + } + + @Override + public Precedence getPrecedence() { + return precedence; + } + + @Override + public ClassLoader getClassLoader() { + return context.getClassLoader(); + } + + @Override + public ListableClassReaderSource getClassSource() { + return context.getClassSource(); + } + } +} diff --git a/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java b/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java index a8c39149a..015a57d11 100644 --- a/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java +++ b/core/src/main/java/org/teavm/backend/javascript/spi/GeneratorContext.java @@ -20,11 +20,8 @@ import org.teavm.common.ServiceRepository; import org.teavm.diagnostics.Diagnostics; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; -/** - * - * @author Alexey Andreev - */ public interface GeneratorContext extends ServiceRepository { String getParameterName(int index); @@ -41,4 +38,6 @@ public interface GeneratorContext extends ServiceRepository { boolean isAsyncFamily(MethodReference method); Diagnostics getDiagnostics(); + + String typeToClassString(ValueType type); } diff --git a/core/src/main/java/org/teavm/vm/spi/AbstractRendererListener.java b/core/src/main/java/org/teavm/vm/spi/AbstractRendererListener.java index 16ca66a16..3eef3ffdd 100644 --- a/core/src/main/java/org/teavm/vm/spi/AbstractRendererListener.java +++ b/core/src/main/java/org/teavm/vm/spi/AbstractRendererListener.java @@ -16,16 +16,12 @@ package org.teavm.vm.spi; import java.io.IOException; -import org.teavm.backend.javascript.rendering.RenderingContext; +import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.vm.BuildTarget; -/** - * - * @author Alexey Andreev - */ public abstract class AbstractRendererListener implements RendererListener { @Override - public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { + public void begin(RenderingManager manager, BuildTarget buildTarget) throws IOException { } @Override diff --git a/core/src/main/java/org/teavm/vm/spi/RendererListener.java b/core/src/main/java/org/teavm/vm/spi/RendererListener.java index d45ca564f..4e0d49568 100644 --- a/core/src/main/java/org/teavm/vm/spi/RendererListener.java +++ b/core/src/main/java/org/teavm/vm/spi/RendererListener.java @@ -16,7 +16,7 @@ package org.teavm.vm.spi; import java.io.IOException; -import org.teavm.backend.javascript.rendering.RenderingContext; +import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.vm.BuildTarget; /** @@ -24,7 +24,7 @@ import org.teavm.vm.BuildTarget; * @author Alexey Andreev */ public interface RendererListener { - void begin(RenderingContext context, BuildTarget buildTarget) throws IOException; + void begin(RenderingManager context, BuildTarget buildTarget) throws IOException; void complete() throws IOException; } diff --git a/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java b/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java index f38cbad0e..cc5db7a61 100644 --- a/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java +++ b/html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java @@ -20,10 +20,17 @@ import java.util.List; import net.java.html.js.JavaScriptBody; import org.teavm.backend.javascript.codegen.NamingStrategy; import org.teavm.backend.javascript.codegen.SourceWriter; -import org.teavm.backend.javascript.rendering.Renderer; import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.GeneratorContext; -import org.teavm.model.*; +import org.teavm.model.AnnotationReader; +import org.teavm.model.AnnotationValue; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; public class JavaScriptBodyGenerator implements Generator { @Override @@ -35,7 +42,8 @@ public class JavaScriptBodyGenerator implements Generator { List args = annot.getValue("args").getList(); AnnotationValue javacall = annot.getValue("javacall"); if (javacall != null && javacall.getBoolean()) { - GeneratorJsCallback callbackGen = new GeneratorJsCallback(context.getClassSource(), writer.getNaming()); + GeneratorJsCallback callbackGen = new GeneratorJsCallback(context, context.getClassSource(), + writer.getNaming()); body = callbackGen.parse(body); } writer.append("var result = (function("); @@ -55,7 +63,7 @@ public class JavaScriptBodyGenerator implements Generator { } writer.append(");").softNewLine(); writer.append("return "); - unwrapValue(writer, method.getResultType(), "result"); + unwrapValue(context, writer, method.getResultType(), "result"); writer.append(";").softNewLine(); } @@ -64,16 +72,19 @@ public class JavaScriptBodyGenerator implements Generator { writer.append("(").append(param).append(")"); } - private void unwrapValue(SourceWriter writer, ValueType type, String param) throws IOException { + private void unwrapValue(GeneratorContext context, SourceWriter writer, ValueType type, String param) + throws IOException { writer.appendMethodBody(JavaScriptConvGenerator.fromJsMethod); - writer.append("(").append(param).append(",").ws().append(Renderer.typeToClsString(writer.getNaming(), type)) + writer.append("(").append(param).append(",").ws().append(context.typeToClassString(type)) .append(")"); } private static class GeneratorJsCallback extends JsCallback { + private GeneratorContext context; private ClassReaderSource classSource; private NamingStrategy naming; - public GeneratorJsCallback(ClassReaderSource classSource, NamingStrategy naming) { + public GeneratorJsCallback(GeneratorContext context, ClassReaderSource classSource, NamingStrategy naming) { + this.context = context; this.classSource = classSource; this.naming = naming; } @@ -105,7 +116,7 @@ public class JavaScriptBodyGenerator implements Generator { ValueType paramType = simplifyParamType(reader.parameterType(i)); sb.append(naming.getFullNameFor(JavaScriptConvGenerator.fromJsMethod)).append("(p").append(i) .append(", ") - .append(Renderer.typeToClsString(naming, paramType)).append(")"); + .append(context.typeToClassString(paramType)).append(")"); } sb.append(")); })("); if (ident != null) { diff --git a/html4j/src/main/java/org/teavm/html4j/JavaScriptResourceInterceptor.java b/html4j/src/main/java/org/teavm/html4j/JavaScriptResourceInterceptor.java index a4dd920d1..65b4661c2 100644 --- a/html4j/src/main/java/org/teavm/html4j/JavaScriptResourceInterceptor.java +++ b/html4j/src/main/java/org/teavm/html4j/JavaScriptResourceInterceptor.java @@ -20,7 +20,7 @@ import java.io.InputStream; import java.io.StringWriter; import net.java.html.js.JavaScriptResource; import org.apache.commons.io.IOUtils; -import org.teavm.backend.javascript.rendering.RenderingContext; +import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.model.AnnotationReader; import org.teavm.model.ClassReader; import org.teavm.vm.BuildTarget; @@ -29,10 +29,10 @@ import org.teavm.vm.spi.AbstractRendererListener; public class JavaScriptResourceInterceptor extends AbstractRendererListener { @Override - public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { + public void begin(RenderingManager manager, BuildTarget buildTarget) throws IOException { boolean hasOneResource = false; - for (String className : context.getClassSource().getClassNames()) { - ClassReader cls = context.getClassSource().get(className); + for (String className : manager.getClassSource().getClassNames()) { + ClassReader cls = manager.getClassSource().get(className); AnnotationReader annot = cls.getAnnotations().get(JavaScriptResource.class.getName()); if (annot == null) { continue; @@ -40,7 +40,7 @@ public class JavaScriptResourceInterceptor extends AbstractRendererListener { String path = annot.getValue("value").getString(); String packageName = className.substring(0, className.lastIndexOf('.')); String resourceName = packageName.replace('.', '/') + "/" + path; - try (InputStream input = context.getClassLoader().getResourceAsStream(resourceName)) { + try (InputStream input = manager.getClassLoader().getResourceAsStream(resourceName)) { if (input == null) { throw new RenderingException("Error processing JavaScriptResource annotation on class " + className + ". Resource not found: " + resourceName); @@ -48,13 +48,13 @@ public class JavaScriptResourceInterceptor extends AbstractRendererListener { StringWriter writer = new StringWriter(); IOUtils.copy(input, writer); writer.close(); - context.getWriter().append("// Resource " + path + " included by " + className).newLine(); - context.getWriter().append(writer.toString()).newLine().newLine(); + manager.getWriter().append("// Resource " + path + " included by " + className).newLine(); + manager.getWriter().append(writer.toString()).newLine().newLine(); } hasOneResource = true; } if (hasOneResource) { - context.getWriter().append("// TeaVM generated classes").newLine(); + manager.getWriter().append("// TeaVM generated classes").newLine(); } } } diff --git a/html4j/src/main/java/org/teavm/html4j/ResourcesInterceptor.java b/html4j/src/main/java/org/teavm/html4j/ResourcesInterceptor.java index 5e504b273..2e2f63b8b 100644 --- a/html4j/src/main/java/org/teavm/html4j/ResourcesInterceptor.java +++ b/html4j/src/main/java/org/teavm/html4j/ResourcesInterceptor.java @@ -23,7 +23,7 @@ import java.util.HashSet; import java.util.Set; import org.apache.commons.io.IOUtils; import org.teavm.backend.javascript.codegen.SourceWriter; -import org.teavm.backend.javascript.rendering.RenderingContext; +import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.vm.BuildTarget; import org.teavm.vm.spi.AbstractRendererListener; @@ -34,16 +34,16 @@ import org.teavm.vm.spi.AbstractRendererListener; public class ResourcesInterceptor extends AbstractRendererListener { private final Set processed = new HashSet<>(); @Override - public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { + public void begin(RenderingManager manager, BuildTarget buildTarget) throws IOException { boolean hasOneResource = false; - for (String className : context.getClassSource().getClassNames()) { + for (String className : manager.getClassSource().getClassNames()) { final int lastDot = className.lastIndexOf('.'); if (lastDot == -1) { continue; } String packageName = className.substring(0, lastDot); String resourceName = packageName.replace('.', '/') + "/" + "jvm.txt"; - try (InputStream input = context.getClassLoader().getResourceAsStream(resourceName)) { + try (InputStream input = manager.getClassLoader().getResourceAsStream(resourceName)) { if (input == null || !processed.add(resourceName)) { continue; } @@ -51,7 +51,7 @@ public class ResourcesInterceptor extends AbstractRendererListener { IOUtils.copy(input, arr); String base64 = Base64.getEncoder().encodeToString(arr.toByteArray()); input.close(); - final SourceWriter w = context.getWriter(); + final SourceWriter w = manager.getWriter(); w.append("// Resource " + resourceName + " included by " + className).newLine(); w.append("if (!window.teaVMResources) window.teaVMResources = {};").newLine(); w.append("window.teaVMResources['" + resourceName + "'] = '"); @@ -60,7 +60,7 @@ public class ResourcesInterceptor extends AbstractRendererListener { hasOneResource = true; } if (hasOneResource) { - context.getWriter().append("// TeaVM generated classes").newLine(); + manager.getWriter().append("// TeaVM generated classes").newLine(); } } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java index 1156000e5..f416869c6 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java @@ -18,7 +18,7 @@ package org.teavm.jso.impl; import java.io.IOException; import java.util.Map; import org.teavm.backend.javascript.codegen.SourceWriter; -import org.teavm.backend.javascript.rendering.RenderingContext; +import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.jso.impl.JSDependencyListener.ExposedClass; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; @@ -41,7 +41,7 @@ class JSAliasRenderer implements RendererListener { } @Override - public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { + public void begin(RenderingManager context, BuildTarget buildTarget) throws IOException { writer = context.getWriter(); classSource = context.getClassSource(); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java index ee371be52..02b9dbdea 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java @@ -15,13 +15,13 @@ */ package org.teavm.jso.impl; +import static org.teavm.backend.javascript.rendering.RenderingUtil.escapeString; import java.io.IOException; import org.teavm.ast.ConstantExpr; import org.teavm.ast.Expr; import org.teavm.ast.InvocationExpr; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.rendering.Precedence; -import org.teavm.backend.javascript.rendering.Renderer; import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.Injector; @@ -122,7 +122,7 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator if (context.getArgument(0) instanceof ConstantExpr) { ConstantExpr constant = (ConstantExpr) context.getArgument(0); if (constant.getValue() instanceof String) { - writer.append('"').append(Renderer.escapeString((String) constant.getValue())).append('"'); + writer.append('"').append(escapeString((String) constant.getValue())).append('"'); break; } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/ClassScopedMetadataProviderNativeGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/ClassScopedMetadataProviderNativeGenerator.java index c558169de..03b4ead85 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/ClassScopedMetadataProviderNativeGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/ClassScopedMetadataProviderNativeGenerator.java @@ -20,10 +20,16 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Map; import org.teavm.backend.javascript.codegen.SourceWriter; -import org.teavm.backend.javascript.rendering.Renderer; +import org.teavm.backend.javascript.rendering.RenderingUtil; import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.GeneratorContext; -import org.teavm.model.*; +import org.teavm.model.AnnotationReader; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassReader; +import org.teavm.model.ElementModifier; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; import org.teavm.platform.metadata.ClassScopedMetadataGenerator; import org.teavm.platform.metadata.ClassScopedMetadataProvider; import org.teavm.platform.metadata.Resource; @@ -80,7 +86,7 @@ public class ClassScopedMetadataProviderNativeGenerator implements Generator { context.getClassLoader(), context.getProperties(), context); Map resourceMap = generator.generateMetadata(metadataContext, methodRef); - writer.append("var p").ws().append("=").ws().append("\"" + Renderer.escapeString("$$res_" + writer.append("var p").ws().append("=").ws().append("\"" + RenderingUtil.escapeString("$$res_" + writer.getNaming().getFullNameFor(methodRef)) + "\"").append(";").softNewLine(); for (Map.Entry entry : resourceMap.entrySet()) { writer.appendClass(entry.getKey()).append("[p]").ws().append("=").ws(); diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 26bd7f47a..37fbdeb91 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -34,7 +34,7 @@ import java.util.Properties; import java.util.Set; import org.apache.commons.io.IOUtils; import org.teavm.backend.javascript.JavaScriptTarget; -import org.teavm.backend.javascript.rendering.RenderingContext; +import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.backend.wasm.WasmTarget; import org.teavm.cache.DiskCachedClassHolderSource; import org.teavm.cache.DiskProgramCache; @@ -525,11 +525,11 @@ public class TeaVMTool implements BaseTeaVMTool { private AbstractRendererListener runtimeInjector = new AbstractRendererListener() { @Override - public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { + public void begin(RenderingManager manager, BuildTarget buildTarget) throws IOException { StringWriter writer = new StringWriter(); resourceToWriter("org/teavm/backend/javascript/runtime.js", writer); writer.close(); - context.getWriter().append(writer.toString()).newLine(); + manager.getWriter().append(writer.toString()).newLine(); } };