From a1cc81750476f45938fc8abc1b43a6d1f940472f Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 29 Oct 2023 20:17:18 +0100 Subject: [PATCH] JS: rewrite more native generators with templates, fix issues in template engine --- .../java/lang/LongNativeGenerator.java | 23 +- .../teavm/backend/javascript/ast/AstUtil.java | 1 + .../backend/javascript/ast/AstVisitor.java | 53 +++ .../javascript/rendering/AstWriter.java | 76 ++++- .../javascript/rendering/Renderer.java | 2 +- .../templating/JavaScriptTemplate.java | 14 +- .../templating/TemplatingAstTransformer.java | 3 +- .../templating/TemplatingAstWriter.java | 34 +- .../src/main/java/org/teavm/jso/impl/JS.java | 132 ++++---- .../org/teavm/jso/impl/JSNativeGenerator.java | 310 +----------------- .../org/teavm/jso/impl/JSNativeInjector.java | 279 ++++++++++++++++ .../main/resources/org/teavm/jso/impl/JS.js | 34 ++ 12 files changed, 554 insertions(+), 407 deletions(-) create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java create mode 100644 jso/impl/src/main/resources/org/teavm/jso/impl/JS.js diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/LongNativeGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/LongNativeGenerator.java index 5f5b7202c..3655eb87f 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/LongNativeGenerator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/LongNativeGenerator.java @@ -26,25 +26,24 @@ public class LongNativeGenerator implements Generator { public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { switch (methodRef.getName()) { case "compare": - writer.append("return Long_compare(").append(context.getParameterName(1)).append(", ") - .append(context.getParameterName(2)).append(");").softNewLine(); - context.useLongLibrary(); + generateRuntimeCall(context, writer, "Long_compare"); break; case "compareUnsigned": - writer.append("return Long_ucompare(").append(context.getParameterName(1)).append(", ") - .append(context.getParameterName(2)).append(");").softNewLine(); - context.useLongLibrary(); + generateRuntimeCall(context, writer, "Long_ucompare"); break; case "divideUnsigned": - writer.append("return Long_udiv(").append(context.getParameterName(1)).append(", ") - .append(context.getParameterName(2)).append(");").softNewLine(); - context.useLongLibrary(); + generateRuntimeCall(context, writer, "Long_udiv"); break; case "remainderUnsigned": - writer.append("return Long_urem(").append(context.getParameterName(1)).append(", ") - .append(context.getParameterName(2)).append(");").softNewLine(); - context.useLongLibrary(); + generateRuntimeCall(context, writer, "Long_urem"); break; } } + + private void generateRuntimeCall(GeneratorContext context, SourceWriter writer, String name) throws IOException { + writer.append("return ").appendFunction(name).append("(").append(context.getParameterName(1)) + .append(",").ws() + .append(context.getParameterName(2)).append(");").softNewLine(); + context.useLongLibrary(); + } } diff --git a/core/src/main/java/org/teavm/backend/javascript/ast/AstUtil.java b/core/src/main/java/org/teavm/backend/javascript/ast/AstUtil.java index 39b37d077..93f072bb5 100644 --- a/core/src/main/java/org/teavm/backend/javascript/ast/AstUtil.java +++ b/core/src/main/java/org/teavm/backend/javascript/ast/AstUtil.java @@ -30,6 +30,7 @@ public final class AstUtil { var env = new CompilerEnvirons(); env.setRecoverFromErrors(true); env.setLanguageVersion(Context.VERSION_1_8); + env.setIdeMode(true); var factory = new JSParser(env); return factory.parse(string, null, 0); diff --git a/core/src/main/java/org/teavm/backend/javascript/ast/AstVisitor.java b/core/src/main/java/org/teavm/backend/javascript/ast/AstVisitor.java index f2a4fd093..b85958a05 100644 --- a/core/src/main/java/org/teavm/backend/javascript/ast/AstVisitor.java +++ b/core/src/main/java/org/teavm/backend/javascript/ast/AstVisitor.java @@ -15,7 +15,11 @@ */ package org.teavm.backend.javascript.ast; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; @@ -70,6 +74,7 @@ import org.mozilla.javascript.ast.WhileLoop; public class AstVisitor { protected AstNode replacement; protected boolean hasReplacement; + protected final Map currentScopes = new HashMap<>(); public final void visit(AstNode node) { switch (node.getType()) { @@ -261,7 +266,9 @@ public class AstVisitor { } public void visit(Scope node) { + var scope = enterScope(node); visitChildren(node); + leaveScope(scope); } public void visit(LabeledStatement node) { @@ -284,26 +291,34 @@ public class AstVisitor { } public void visit(DoLoop node) { + var scope = enterScope(node); visitProperty(node, DoLoop::getBody, DoLoop::setBody, EMPTY_DEFAULT); visitProperty(node, DoLoop::getCondition, DoLoop::setCondition, NULL_DEFAULT); + leaveScope(scope); } public void visit(ForInLoop node) { + var scope = enterScope(node); visitProperty(node, ForInLoop::getIterator, ForInLoop::setIterator, NULL_DEFAULT); visitProperty(node, ForInLoop::getIteratedObject, ForInLoop::setIteratedObject, NULL_DEFAULT); visitProperty(node, ForInLoop::getBody, ForInLoop::setBody, EMPTY_DEFAULT); + leaveScope(scope); } public void visit(ForLoop node) { + var scope = enterScope(node); visitProperty(node, ForLoop::getInitializer, ForLoop::setInitializer, EMPTY_EXPR_DEFAULT); visitProperty(node, ForLoop::getCondition, ForLoop::setCondition, EMPTY_EXPR_DEFAULT); visitProperty(node, ForLoop::getIncrement, ForLoop::setIncrement, EMPTY_EXPR_DEFAULT); visitProperty(node, ForLoop::getBody, ForLoop::setBody, EMPTY_DEFAULT); + leaveScope(scope); } public void visit(WhileLoop node) { + var scope = enterScope(node); visitProperty(node, WhileLoop::getCondition, WhileLoop::setCondition, NULL_DEFAULT); visitProperty(node, WhileLoop::getBody, WhileLoop::setBody, EMPTY_DEFAULT); + leaveScope(scope); } public void visit(IfStatement node) { @@ -375,15 +390,18 @@ public class AstVisitor { } public void visit(ArrayComprehension node) { + var scope = enterScope(node); for (var loop : node.getLoops()) { visitProperty(loop, ArrayComprehensionLoop::getIterator, ArrayComprehensionLoop::setIterator); visitProperty(loop, ArrayComprehensionLoop::getIteratedObject, ArrayComprehensionLoop::setIteratedObject); } visitProperty(node, ArrayComprehension::getFilter, ArrayComprehension::setFilter); visitProperty(node, ArrayComprehension::getResult, ArrayComprehension::setResult); + leaveScope(scope); } public void visit(GeneratorExpression node) { + var scope = enterScope(node); for (var loop : node.getLoops()) { visitProperty(loop, GeneratorExpressionLoop::getIterator, GeneratorExpressionLoop::setIterator); visitProperty(loop, GeneratorExpressionLoop::getIteratedObject, @@ -391,6 +409,7 @@ public class AstVisitor { } visitProperty(node, GeneratorExpression::getFilter, GeneratorExpression::setFilter); visitProperty(node, GeneratorExpression::getResult, GeneratorExpression::setResult); + leaveScope(scope); } public void visit(NumberLiteral node) { @@ -426,14 +445,21 @@ public class AstVisitor { } public void visit(FunctionNode node) { + var scope = enterScope(node); + if (node.getFunctionType() != FunctionNode.ARROW_FUNCTION) { + currentScopes.put("arguments", node); + } visitProperty(node, FunctionNode::getFunctionName, FunctionNode::setFunctionName); visitMany(node.getParams()); visitChildren(node.getBody()); + leaveScope(scope); } public void visit(LetNode node) { + var scope = enterScope(node); visitProperty(node, LetNode::getVariables, LetNode::setVariables); visitProperty(node, LetNode::getBody, LetNode::setBody); + leaveScope(scope); } public void visit(ParenthesizedExpression node) { @@ -464,6 +490,33 @@ public class AstVisitor { replacement = node; } + + private Map enterScope(Scope scope) { + if (scope.getSymbolTable() == null) { + return Collections.emptyMap(); + } + var map = new LinkedHashMap(); + for (var name : scope.getSymbolTable().keySet()) { + map.put(name, currentScopes.get(name)); + currentScopes.put(name, scope); + } + return map; + } + + private void leaveScope(Map backup) { + for (var entry : backup.entrySet()) { + if (entry.getValue() == null) { + currentScopes.remove(entry.getKey()); + } else { + currentScopes.put(entry.getKey(), entry.getValue()); + } + } + } + + protected Scope scopeOfId(String id) { + return currentScopes.get(id); + } + private static final Supplier NULL_DEFAULT = () -> new KeywordLiteral(0, 0, Token.NULL); private static final Supplier EMPTY_DEFAULT = () -> new EmptyStatement(0, 0); private static final Supplier EMPTY_EXPR_DEFAULT = () -> new EmptyExpression(0, 0); diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java b/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java index 516dcb273..90427ff2c 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/AstWriter.java @@ -16,8 +16,10 @@ package org.teavm.backend.javascript.rendering; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -93,9 +95,10 @@ public class AstWriter { public static final int PRECEDENCE_COMMA = 18; protected final SourceWriter writer; private Map nameMap = new HashMap<>(); - private boolean rootScope = true; + protected boolean rootScope = true; private Set aliases = new HashSet<>(); private Function globalNameWriter; + public final Map currentScopes = new HashMap<>(); public AstWriter(SourceWriter writer, Function globalNameWriter) { this.writer = writer; @@ -319,12 +322,14 @@ public class AstWriter { } private void print(Scope node) throws IOException { + var scope = enterScope(node); writer.append('{').softNewLine().indent(); for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { print((AstNode) child); writer.softNewLine(); } writer.outdent().append('}'); + leaveScope(scope); } private void print(LabeledStatement node) throws IOException { @@ -366,14 +371,17 @@ public class AstWriter { } private void print(DoLoop node) throws IOException { + var scope = enterScope(node); writer.append("do ").ws(); print(node.getBody()); writer.append("while").ws().append('('); print(node.getCondition()); writer.append(");"); + leaveScope(scope); } private void print(ForInLoop node) throws IOException { + var scope = enterScope(node); writer.append("for"); if (node.isForEach()) { writer.append(" each"); @@ -384,9 +392,11 @@ public class AstWriter { print(node.getIteratedObject()); writer.append(')').ws(); print(node.getBody()); + leaveScope(scope); } private void print(ForLoop node) throws IOException { + var scope = enterScope(node); writer.append("for").ws().append('('); print(node.getInitializer()); writer.append(';'); @@ -395,13 +405,16 @@ public class AstWriter { print(node.getIncrement()); writer.append(')').ws(); print(node.getBody()); + leaveScope(scope); } private void print(WhileLoop node) throws IOException { + var scope = enterScope(node); writer.append("while").ws().append('('); print(node.getCondition()); writer.append(')').ws(); print(node.getBody()); + leaveScope(scope); } private void print(IfStatement node) throws IOException { @@ -606,6 +619,7 @@ public class AstWriter { } private void print(ArrayComprehension node) throws IOException { + var scope = enterScope(node); writer.append("["); for (ArrayComprehensionLoop loop : node.getLoops()) { writer.append("for").ws().append("("); @@ -621,9 +635,11 @@ public class AstWriter { } print(node.getResult()); writer.append(']'); + leaveScope(scope); } private void print(GeneratorExpression node) throws IOException { + var scope = enterScope(node); writer.append("("); for (GeneratorExpressionLoop loop : node.getLoops()) { writer.append("for").ws().append("("); @@ -639,6 +655,7 @@ public class AstWriter { } print(node.getResult()); writer.append(')'); + leaveScope(scope); } private void print(NumberLiteral node) throws IOException { @@ -652,7 +669,8 @@ public class AstWriter { } public void print(Name node, int precedence) throws IOException { - if (rootScope && node.getDefiningScope() == null) { + var definingScope = scopeOfId(node.getIdentifier()); + if (rootScope && definingScope == null) { var alias = nameMap.get(node.getIdentifier()); if (alias == null) { if (globalNameWriter != null) { @@ -667,10 +685,6 @@ public class AstWriter { } } - protected final boolean isRootScope() { - return rootScope; - } - private void print(RegExpLiteral node) throws IOException { writer.append('/').append(node.getValue()).append('/').append(node.getFlags()); } @@ -710,16 +724,28 @@ public class AstWriter { } private void print(FunctionNode node) throws IOException { - if (!node.isMethod()) { + var scope = enterScope(node); + var isArrow = node.getFunctionType() == FunctionNode.ARROW_FUNCTION; + if (!isArrow) { + currentScopes.put("arguments", node); + } + if (!node.isMethod() && !isArrow) { writer.append("function"); } if (node.getFunctionName() != null) { writer.append(' '); print(node.getFunctionName()); } - writer.append('('); - printList(node.getParams()); - writer.append(')').ws(); + if (!isArrow || node.getParams().size() != 1) { + writer.append('('); + printList(node.getParams()); + writer.append(')').ws(); + } else { + print(node.getParams().get(0)); + } + if (isArrow) { + writer.append("=>").ws(); + } if (node.isExpressionClosure()) { if (node.getBody().getLastChild() instanceof ReturnStatement) { @@ -731,13 +757,17 @@ public class AstWriter { } else { print(node.getBody()); } + + leaveScope(scope); } private void print(LetNode node) throws IOException { + var scope = enterScope(node); writer.append("let").ws().append('('); printList(node.getVariables().getVariables()); writer.append(')'); print(node.getBody()); + leaveScope(scope); } private void print(ParenthesizedExpression node, int precedence) throws IOException { @@ -934,4 +964,30 @@ public class AstWriter { return false; } } + + private Map enterScope(Scope scope) { + if (scope.getSymbolTable() == null) { + return Collections.emptyMap(); + } + var map = new LinkedHashMap(); + for (var name : scope.getSymbolTable().keySet()) { + map.put(name, currentScopes.get(name)); + currentScopes.put(name, scope); + } + return map; + } + + private void leaveScope(Map backup) { + for (var entry : backup.entrySet()) { + if (entry.getValue() == null) { + currentScopes.remove(entry.getKey()); + } else { + currentScopes.put(entry.getKey(), entry.getValue()); + } + } + } + + protected Scope scopeOfId(String id) { + return currentScopes.get(id); + } } 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 dd4e8c3df..c7da72beb 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 @@ -264,7 +264,7 @@ public class Renderer implements RenderingManager { "$rt_createFloatArray", "$rt_createDoubleArray", "$rt_compare", "$rt_castToClass", "$rt_castToInterface", "$rt_equalDoubles", "$rt_str", "Long_toNumber", "Long_fromInt", "Long_fromNumber", "Long_create", "Long_ZERO", - "$rt_intern", "$rt_substring", + "$rt_intern", "$rt_substring", "$rt_ustr", "Long_hi", "Long_lo"); } diff --git a/core/src/main/java/org/teavm/backend/javascript/templating/JavaScriptTemplate.java b/core/src/main/java/org/teavm/backend/javascript/templating/JavaScriptTemplate.java index e639dc1ff..d106be0e0 100644 --- a/core/src/main/java/org/teavm/backend/javascript/templating/JavaScriptTemplate.java +++ b/core/src/main/java/org/teavm/backend/javascript/templating/JavaScriptTemplate.java @@ -16,7 +16,6 @@ package org.teavm.backend.javascript.templating; import java.util.HashMap; -import java.util.function.Function; import java.util.function.IntFunction; import org.mozilla.javascript.ast.AstNode; import org.mozilla.javascript.ast.FunctionNode; @@ -59,21 +58,22 @@ public class JavaScriptTemplate { public SourceFragment build() { var intParameters = parameters; - var paramNameToIndex = new HashMap(); + var nameParameters = new HashMap(); for (var i = 0; i < node.getParams().size(); ++i) { var param = node.getParams().get(i); if (param instanceof Name) { - paramNameToIndex.put(((Name) param).getIdentifier(), i); + nameParameters.put(((Name) param).getIdentifier(), intParameters.apply(i)); } } - Function nameParameters = name -> { - var index = paramNameToIndex.get(name); - return index != null ? intParameters.apply(index) : null; - }; var thisFragment = parameters.apply(0); var body = node.getBody(); return (writer, precedence) -> { var astWriter = new TemplatingAstWriter(writer, nameParameters, node); + if (node.getSymbolTable() != null) { + for (var name : node.getSymbolTable().keySet()) { + astWriter.currentScopes.put(name, node); + } + } if (thisFragment != null) { astWriter.declareNameEmitter("this", thisPrecedence -> thisFragment.write(writer, thisPrecedence)); } diff --git a/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstTransformer.java b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstTransformer.java index a160bc3db..52c56ca4c 100644 --- a/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstTransformer.java +++ b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstTransformer.java @@ -112,7 +112,8 @@ public class TemplatingAstTransformer extends AstVisitor { super.visit(node); if (node.getTarget() instanceof Name) { var name = (Name) node.getTarget(); - if (name.getDefiningScope() == null) { + var scope = scopeOfId(name.getIdentifier()); + if (scope == null) { tryIntrinsicName(node, name.getIdentifier()); } } diff --git a/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java index 9ba8aa12c..f5fe26ab3 100644 --- a/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java +++ b/core/src/main/java/org/teavm/backend/javascript/templating/TemplatingAstWriter.java @@ -16,9 +16,10 @@ package org.teavm.backend.javascript.templating; import java.io.IOException; -import java.util.function.Function; +import java.util.Map; import org.mozilla.javascript.ast.ElementGet; import org.mozilla.javascript.ast.FunctionCall; +import org.mozilla.javascript.ast.FunctionNode; import org.mozilla.javascript.ast.Name; import org.mozilla.javascript.ast.PropertyGet; import org.mozilla.javascript.ast.Scope; @@ -31,20 +32,28 @@ import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; public class TemplatingAstWriter extends AstWriter { - private Function names; + private Map names; private Scope scope; - public TemplatingAstWriter(SourceWriter writer, Function names, Scope scope) { + public TemplatingAstWriter(SourceWriter writer, Map names, Scope scope) { super(writer, new DefaultGlobalNameWriter(writer)); this.names = names; this.scope = scope; + if (names != null) { + for (var name : names.keySet()) { + currentScopes.put(name, scope); + } + } + if (scope instanceof FunctionNode) { + currentScopes.put("arguments", scope); + } } @Override protected boolean intrinsic(FunctionCall node, int precedence) throws IOException { if (node.getTarget() instanceof Name) { var name = (Name) node.getTarget(); - if (name.getDefiningScope() == null) { + if (scopeOfId(name.getIdentifier()) == null) { return tryIntrinsicName(node, name.getIdentifier()); } } @@ -126,7 +135,7 @@ public class TemplatingAstWriter extends AstWriter { var call = (FunctionCall) node.getElement(); if (call.getTarget() instanceof Name) { var name = (Name) call.getTarget(); - if (name.getDefiningScope() == null) { + if (scopeOfId(name.getIdentifier()) == null) { switch (name.getIdentifier()) { case "teavm_javaVirtualMethod": if (writeJavaVirtualMethod(node, call)) { @@ -149,9 +158,13 @@ public class TemplatingAstWriter extends AstWriter { public void print(PropertyGet node) throws IOException { if (node.getTarget() instanceof Name) { var name = (Name) node.getTarget(); - if (name.getDefiningScope() == null && name.getIdentifier().equals("teavm_globals")) { + var scope = scopeOfId(name.getIdentifier()); + if (scope == null && name.getIdentifier().equals("teavm_globals")) { + var oldRootScope = rootScope; + rootScope = false; writer.append("$rt_globals").append("."); print(node.getProperty()); + rootScope = oldRootScope; return; } } @@ -187,15 +200,16 @@ public class TemplatingAstWriter extends AstWriter { @Override public void print(Name node, int precedence) throws IOException { - if (isRootScope()) { - if (names != null && node.getDefiningScope() == scope) { - var fragment = names.apply(node.getIdentifier()); + var definingScope = scopeOfId(node.getIdentifier()); + if (rootScope) { + if (names != null && definingScope == scope) { + var fragment = names.get(node.getIdentifier()); if (fragment != null) { fragment.write(writer, precedence); return; } } - if (node.getDefiningScope() == null && scope != null) { + if (definingScope == null && scope != null) { writer.appendFunction(node.getIdentifier()); return; } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java index 41bd84e37..76b762acf 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java @@ -32,107 +32,107 @@ final class JS { private JS() { } - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject arrayData(Object array); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) @NoSideEffects public static native byte[] dataToByteArray(JSObject obj); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) @NoSideEffects public static native char[] dataToCharArray(JSObject obj); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) @NoSideEffects public static native short[] dataToShortArray(JSObject obj); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) @NoSideEffects public static native int[] dataToIntArray(JSObject obj); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) @NoSideEffects public static native float[] dataToFloatArray(JSObject obj); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) @NoSideEffects public static native double[] dataToDoubleArray(JSObject obj); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) @NoSideEffects public static native JSObject[] dataToArray(JSObject obj); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject wrap(byte value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject wrap(short value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject wrap(int value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject wrap(char value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject wrap(float value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject wrap(double value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject wrap(boolean value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject wrap(String value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native byte unwrapByte(JSObject value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native char unwrapCharacter(JSObject value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native short unwrapShort(JSObject value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native int unwrapInt(JSObject value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native float unwrapFloat(JSObject value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native double unwrapDouble(JSObject value); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native boolean unwrapBoolean(JSObject value); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) @NoSideEffects public static native String unwrapString(JSObject value); @@ -448,97 +448,97 @@ final class JS { return JS::unwrapStringArray; } - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k, JSObject l); - @InjectedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k, JSObject l, JSObject m); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @JSBody(params = { "instance", "index" }, script = "return instance[index];") public static native JSObject get(JSObject instance, JSObject index); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @JSBody(params = { "instance", "index" }, script = "return instance[index];") @NoSideEffects public static native JSObject getPure(JSObject instance, JSObject index); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @JSBody(params = { "instance", "index", "obj" }, script = "instance[index] = obj;") public static native void set(JSObject instance, JSObject index, JSObject obj); - @InjectedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeInjector.class) @JSBody(params = { "instance", "index", "obj" }, script = "instance[index] = obj;") @NoSideEffects public static native void setPure(JSObject instance, JSObject index, JSObject obj); @GeneratedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject function(JSObject instance, JSObject property); @GeneratedBy(JSNativeGenerator.class) - @PluggableDependency(JSNativeGenerator.class) + @PluggableDependency(JSNativeInjector.class) public static native JSObject functionAsObject(JSObject instance, JSObject property); } 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 25b123fc2..2e79462eb 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,321 +15,31 @@ */ package org.teavm.jso.impl; -import static org.teavm.backend.javascript.rendering.RenderingUtil.escapeString; import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -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.spi.Generator; import org.teavm.backend.javascript.spi.GeneratorContext; -import org.teavm.backend.javascript.spi.Injector; -import org.teavm.backend.javascript.spi.InjectorContext; -import org.teavm.dependency.DependencyAgent; -import org.teavm.dependency.DependencyNode; -import org.teavm.dependency.DependencyPlugin; -import org.teavm.dependency.MethodDependency; -import org.teavm.model.ClassReader; -import org.teavm.model.ElementModifier; -import org.teavm.model.MethodReader; +import org.teavm.backend.javascript.templating.JavaScriptTemplate; +import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory; import org.teavm.model.MethodReference; -import org.teavm.model.ValueType; -public class JSNativeGenerator implements Injector, DependencyPlugin, Generator { - private Set reachedFunctorMethods = new HashSet<>(); - private Set functorParamNodes = new HashSet<>(); +public class JSNativeGenerator implements Generator { + private JavaScriptTemplate template; + + public JSNativeGenerator(JavaScriptTemplateFactory templateFactory) throws IOException { + template = templateFactory.createFromResource("org/teavm/jso/impl/JS.js"); + } @Override public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { switch (methodRef.getName()) { case "function": - writeFunction(context, writer); + template.builder("jsFunction").withContext(context).build().write(writer, 0); break; case "functionAsObject": - writeFunctionAsObject(context, writer); + template.builder("jsFunctionAsObject").withContext(context).build().write(writer, 0); break; } } - - private void writeFunction(GeneratorContext context, SourceWriter writer) throws IOException { - String thisName = context.getParameterName(1); - String methodName = context.getParameterName(2); - writer.append("var name").ws().append('=').ws().append("'jso$functor$'").ws().append('+').ws() - .append(methodName).append(';').softNewLine(); - writer.append("if").ws().append("(!").append(thisName).append("[name])").ws().append('{') - .indent().softNewLine(); - - writer.append("var fn").ws().append('=').ws().append("function()").ws().append('{') - .indent().softNewLine(); - writer.append("return ").append(thisName).append('[').append(methodName).append(']') - .append(".apply(").append(thisName).append(',').ws().append("arguments);").softNewLine(); - writer.outdent().append("};").softNewLine(); - writer.append(thisName).append("[name]").ws().append('=').ws().append("function()").ws().append('{') - .indent().softNewLine(); - writer.append("return fn;").softNewLine(); - writer.outdent().append("};").softNewLine(); - - writer.outdent().append('}').softNewLine(); - writer.append("return ").append(thisName).append("[name]();").softNewLine(); - } - - private void writeFunctionAsObject(GeneratorContext context, SourceWriter writer) throws IOException { - String thisName = context.getParameterName(1); - String methodName = context.getParameterName(2); - - writer.append("if").ws().append("(typeof ").append(thisName).ws().append("!==").ws().append("\"function\")") - .ws().append("return ").append(thisName).append(";").softNewLine(); - writer.append("var result").ws().append("=").ws().append("{};").softNewLine(); - writer.append("result[").append(methodName).append("]").ws().append("=").ws().append(thisName) - .append(";").softNewLine(); - writer.append("return result;").softNewLine(); - } - - @Override - public void generate(InjectorContext context, MethodReference methodRef) throws IOException { - SourceWriter writer = context.getWriter(); - switch (methodRef.getName()) { - case "arrayData": - context.writeExpr(context.getArgument(0)); - writer.append(".data"); - break; - case "get": - case "getPure": - context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS); - renderProperty(context.getArgument(1), context); - break; - case "set": - case "setPure": - context.writeExpr(context.getArgument(0), Precedence.ASSIGNMENT.next()); - renderProperty(context.getArgument(1), context); - writer.ws().append('=').ws(); - context.writeExpr(context.getArgument(2), Precedence.ASSIGNMENT.next()); - break; - case "invoke": - context.writeExpr(context.getArgument(0), Precedence.GROUPING); - renderProperty(context.getArgument(1), context); - writer.append('('); - for (int i = 2; i < context.argumentCount(); ++i) { - if (i > 2) { - writer.append(',').ws(); - } - context.writeExpr(context.getArgument(i), Precedence.min()); - } - writer.append(')'); - break; - case "instantiate": - if (context.getPrecedence().ordinal() >= Precedence.FUNCTION_CALL.ordinal()) { - writer.append("("); - } - writer.append("new "); - context.writeExpr(context.getArgument(0), Precedence.GROUPING); - renderProperty(context.getArgument(1), context); - writer.append("("); - for (int i = 2; i < context.argumentCount(); ++i) { - if (i > 2) { - writer.append(',').ws(); - } - context.writeExpr(context.getArgument(i), Precedence.min()); - } - writer.append(")"); - if (context.getPrecedence().ordinal() >= Precedence.FUNCTION_CALL.ordinal()) { - writer.append(")"); - } - break; - case "wrap": - if (methodRef.getDescriptor().parameterType(0).isObject("java.lang.String")) { - if (context.getArgument(0) instanceof ConstantExpr) { - ConstantExpr constant = (ConstantExpr) context.getArgument(0); - if (constant.getValue() instanceof String) { - writer.append('"').append(escapeString((String) constant.getValue())).append('"'); - break; - } - } - writer.append("$rt_ustr("); - context.writeExpr(context.getArgument(0), Precedence.min()); - writer.append(")"); - } else if (methodRef.getDescriptor().parameterType(0) == ValueType.BOOLEAN) { - if (context.getPrecedence().ordinal() >= Precedence.UNARY.ordinal()) { - writer.append("("); - } - writer.append("!!"); - context.writeExpr(context.getArgument(0), Precedence.UNARY); - if (context.getPrecedence().ordinal() >= Precedence.UNARY.ordinal()) { - writer.append(")"); - } - } else { - context.writeExpr(context.getArgument(0), context.getPrecedence()); - } - break; - case "unwrapString": - writer.appendFunction("$rt_str").append("("); - context.writeExpr(context.getArgument(0), Precedence.min()); - writer.append(")"); - break; - case "unwrapBoolean": - if (context.getPrecedence().ordinal() >= Precedence.CONDITIONAL.ordinal()) { - writer.append("("); - } - context.writeExpr(context.getArgument(0), Precedence.CONDITIONAL.next()); - writer.ws().append("?").ws().append("1").ws().append(":").ws().append("0"); - if (context.getPrecedence().ordinal() >= Precedence.CONDITIONAL.ordinal()) { - writer.append(")"); - } - break; - - case "dataToByteArray": - writer.append("$rt_wrapArray($rt_bytecls,").ws(); - context.writeExpr(context.getArgument(0), Precedence.min()); - writer.append(")"); - break; - case "dataToShortArray": - writer.append("$rt_wrapArray($rt_shortcls,").ws(); - context.writeExpr(context.getArgument(0), Precedence.min()); - writer.append(")"); - break; - case "dataToCharArray": - writer.append("$rt_wrapArray($rt_charcls,").ws(); - context.writeExpr(context.getArgument(0), Precedence.min()); - writer.append(")"); - break; - case "dataToIntArray": - writer.append("$rt_wrapArray($rt_intcls,").ws(); - context.writeExpr(context.getArgument(0), Precedence.min()); - writer.append(")"); - break; - case "dataToFloatArray": - writer.append("$rt_wrapArray($rt_floatcls,").ws(); - context.writeExpr(context.getArgument(0), Precedence.min()); - writer.append(")"); - break; - case "dataToDoubleArray": - writer.append("$rt_wrapArray($rt_doublecls,").ws(); - context.writeExpr(context.getArgument(0), Precedence.min()); - writer.append(")"); - break; - case "dataToArray": - writer.append("$rt_wrapArray($rt_objcls,").ws(); - context.writeExpr(context.getArgument(0), Precedence.min()); - writer.append(")"); - break; - - default: - if (methodRef.getName().startsWith("unwrap")) { - context.writeExpr(context.getArgument(0), context.getPrecedence()); - } - break; - } - } - - @Override - public void methodReached(DependencyAgent agent, MethodDependency method) { - switch (method.getReference().getName()) { - case "invoke": - case "instantiate": - case "function": - if (reachedFunctorMethods.add(method.getReference()) && !method.isMissing()) { - for (int i = 0; i < method.getReference().parameterCount(); ++i) { - DependencyNode node = method.getVariable(i); - if (functorParamNodes.add(node)) { - node.addConsumer(type -> { - if (agent.getClassHierarchy().isSuperType(method.getMethod().getOwnerName(), - type.getName(), false)) { - reachFunctorMethods(agent, type.getName()); - } - }); - } - } - } - break; - case "unwrapString": - method.getResult().propagate(agent.getType("java.lang.String")); - break; - - case "dataToByteArray": - method.getResult().propagate(agent.getType("[B")); - break; - case "dataToShortArray": - method.getResult().propagate(agent.getType("[S")); - break; - case "dataToCharArray": - method.getResult().propagate(agent.getType("[C")); - break; - case "dataToIntArray": - method.getResult().propagate(agent.getType("[I")); - break; - case "dataToFloatArray": - method.getResult().propagate(agent.getType("[F")); - break; - case "dataToDoubleArray": - method.getResult().propagate(agent.getType("[D")); - break; - case "dataToArray": - method.getResult().propagate(agent.getType("[Ljava/lang/Object;")); - break; - } - } - - private void reachFunctorMethods(DependencyAgent agent, String type) { - ClassReader cls = agent.getClassSource().get(type); - if (cls != null) { - for (MethodReader method : cls.getMethods()) { - if (!method.hasModifier(ElementModifier.STATIC)) { - agent.linkMethod(method.getReference()).use(); - } - } - } - } - - - private void renderProperty(Expr property, InjectorContext context) throws IOException { - SourceWriter writer = context.getWriter(); - String name = extractPropertyName(property); - if (name == null) { - writer.append('['); - context.writeExpr(property, Precedence.min()); - writer.append(']'); - } else if (!isIdentifier(name)) { - writer.append("[\""); - context.writeEscaped(name); - writer.append("\"]"); - } else { - writer.append(".").append(name); - } - } - - private String extractPropertyName(Expr propertyName) { - if (!(propertyName instanceof InvocationExpr)) { - return null; - } - InvocationExpr invoke = (InvocationExpr) propertyName; - if (!invoke.getMethod().getClassName().equals(JS.class.getName())) { - return null; - } - if (!invoke.getMethod().getName().equals("wrap") - || !invoke.getMethod().getDescriptor().parameterType(0).isObject("java.lang.String")) { - return null; - } - Expr arg = invoke.getArguments().get(0); - if (!(arg instanceof ConstantExpr)) { - return null; - } - ConstantExpr constant = (ConstantExpr) arg; - return constant.getValue() instanceof String ? (String) constant.getValue() : null; - } - - private boolean isIdentifier(String name) { - if (name.isEmpty() || !Character.isJavaIdentifierStart(name.charAt(0))) { - return false; - } - for (int i = 1; i < name.length(); ++i) { - if (!Character.isJavaIdentifierPart(name.charAt(i))) { - return false; - } - } - return true; - } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java new file mode 100644 index 000000000..23d8e5645 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeInjector.java @@ -0,0 +1,279 @@ +/* + * Copyright 2023 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.jso.impl; + +import static org.teavm.backend.javascript.rendering.RenderingUtil.escapeString; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +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.spi.Injector; +import org.teavm.backend.javascript.spi.InjectorContext; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyNode; +import org.teavm.dependency.DependencyPlugin; +import org.teavm.dependency.MethodDependency; +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; + +public class JSNativeInjector implements Injector, DependencyPlugin { + private Set reachedFunctorMethods = new HashSet<>(); + private Set functorParamNodes = new HashSet<>(); + + @Override + public void generate(InjectorContext context, MethodReference methodRef) throws IOException { + SourceWriter writer = context.getWriter(); + switch (methodRef.getName()) { + case "arrayData": + context.writeExpr(context.getArgument(0)); + writer.append(".data"); + break; + case "get": + case "getPure": + context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS); + renderProperty(context.getArgument(1), context); + break; + case "set": + case "setPure": + context.writeExpr(context.getArgument(0), Precedence.ASSIGNMENT.next()); + renderProperty(context.getArgument(1), context); + writer.ws().append('=').ws(); + context.writeExpr(context.getArgument(2), Precedence.ASSIGNMENT.next()); + break; + case "invoke": + context.writeExpr(context.getArgument(0), Precedence.GROUPING); + renderProperty(context.getArgument(1), context); + writer.append('('); + for (int i = 2; i < context.argumentCount(); ++i) { + if (i > 2) { + writer.append(',').ws(); + } + context.writeExpr(context.getArgument(i), Precedence.min()); + } + writer.append(')'); + break; + case "instantiate": + if (context.getPrecedence().ordinal() >= Precedence.FUNCTION_CALL.ordinal()) { + writer.append("("); + } + writer.append("new "); + context.writeExpr(context.getArgument(0), Precedence.GROUPING); + renderProperty(context.getArgument(1), context); + writer.append("("); + for (int i = 2; i < context.argumentCount(); ++i) { + if (i > 2) { + writer.append(',').ws(); + } + context.writeExpr(context.getArgument(i), Precedence.min()); + } + writer.append(")"); + if (context.getPrecedence().ordinal() >= Precedence.FUNCTION_CALL.ordinal()) { + writer.append(")"); + } + break; + case "wrap": + if (methodRef.getDescriptor().parameterType(0).isObject("java.lang.String")) { + if (context.getArgument(0) instanceof ConstantExpr) { + ConstantExpr constant = (ConstantExpr) context.getArgument(0); + if (constant.getValue() instanceof String) { + writer.append('"').append(escapeString((String) constant.getValue())).append('"'); + break; + } + } + writer.appendFunction("$rt_ustr").append("("); + context.writeExpr(context.getArgument(0), Precedence.min()); + writer.append(")"); + } else if (methodRef.getDescriptor().parameterType(0) == ValueType.BOOLEAN) { + if (context.getPrecedence().ordinal() >= Precedence.UNARY.ordinal()) { + writer.append("("); + } + writer.append("!!"); + context.writeExpr(context.getArgument(0), Precedence.UNARY); + if (context.getPrecedence().ordinal() >= Precedence.UNARY.ordinal()) { + writer.append(")"); + } + } else { + context.writeExpr(context.getArgument(0), context.getPrecedence()); + } + break; + case "unwrapString": + writer.appendFunction("$rt_str").append("("); + context.writeExpr(context.getArgument(0), Precedence.min()); + writer.append(")"); + break; + case "unwrapBoolean": + if (context.getPrecedence().ordinal() >= Precedence.CONDITIONAL.ordinal()) { + writer.append("("); + } + context.writeExpr(context.getArgument(0), Precedence.CONDITIONAL.next()); + writer.ws().append("?").ws().append("1").ws().append(":").ws().append("0"); + if (context.getPrecedence().ordinal() >= Precedence.CONDITIONAL.ordinal()) { + writer.append(")"); + } + break; + + case "dataToByteArray": + dataToArray(context, "$rt_bytecls"); + break; + case "dataToShortArray": + dataToArray(context, "$rt_shortcls"); + break; + case "dataToCharArray": + dataToArray(context, "$rt_charcls"); + break; + case "dataToIntArray": + dataToArray(context, "$rt_intcls"); + break; + case "dataToFloatArray": + dataToArray(context, "$rt_floatcls"); + break; + case "dataToDoubleArray": + dataToArray(context, "$rt_doublecls"); + break; + case "dataToArray": + dataToArray(context, "$rt_objcls"); + break; + + default: + if (methodRef.getName().startsWith("unwrap")) { + context.writeExpr(context.getArgument(0), context.getPrecedence()); + } + break; + } + } + + private void dataToArray(InjectorContext context, String className) throws IOException { + var writer = context.getWriter(); + writer.appendFunction("$rt_wrapArray").append("(").appendFunction(className).append(",").ws(); + context.writeExpr(context.getArgument(0), Precedence.min()); + writer.append(")"); + } + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + switch (method.getReference().getName()) { + case "invoke": + case "instantiate": + case "function": + if (reachedFunctorMethods.add(method.getReference()) && !method.isMissing()) { + for (int i = 0; i < method.getReference().parameterCount(); ++i) { + DependencyNode node = method.getVariable(i); + if (functorParamNodes.add(node)) { + node.addConsumer(type -> { + if (agent.getClassHierarchy().isSuperType(method.getMethod().getOwnerName(), + type.getName(), false)) { + reachFunctorMethods(agent, type.getName()); + } + }); + } + } + } + break; + case "unwrapString": + method.getResult().propagate(agent.getType("java.lang.String")); + break; + + case "dataToByteArray": + method.getResult().propagate(agent.getType("[B")); + break; + case "dataToShortArray": + method.getResult().propagate(agent.getType("[S")); + break; + case "dataToCharArray": + method.getResult().propagate(agent.getType("[C")); + break; + case "dataToIntArray": + method.getResult().propagate(agent.getType("[I")); + break; + case "dataToFloatArray": + method.getResult().propagate(agent.getType("[F")); + break; + case "dataToDoubleArray": + method.getResult().propagate(agent.getType("[D")); + break; + case "dataToArray": + method.getResult().propagate(agent.getType("[Ljava/lang/Object;")); + break; + } + } + + private void reachFunctorMethods(DependencyAgent agent, String type) { + ClassReader cls = agent.getClassSource().get(type); + if (cls != null) { + for (MethodReader method : cls.getMethods()) { + if (!method.hasModifier(ElementModifier.STATIC)) { + agent.linkMethod(method.getReference()).use(); + } + } + } + } + + + private void renderProperty(Expr property, InjectorContext context) throws IOException { + SourceWriter writer = context.getWriter(); + String name = extractPropertyName(property); + if (name == null) { + writer.append('['); + context.writeExpr(property, Precedence.min()); + writer.append(']'); + } else if (!isIdentifier(name)) { + writer.append("[\""); + context.writeEscaped(name); + writer.append("\"]"); + } else { + writer.append(".").append(name); + } + } + + private String extractPropertyName(Expr propertyName) { + if (!(propertyName instanceof InvocationExpr)) { + return null; + } + InvocationExpr invoke = (InvocationExpr) propertyName; + if (!invoke.getMethod().getClassName().equals(JS.class.getName())) { + return null; + } + if (!invoke.getMethod().getName().equals("wrap") + || !invoke.getMethod().getDescriptor().parameterType(0).isObject("java.lang.String")) { + return null; + } + Expr arg = invoke.getArguments().get(0); + if (!(arg instanceof ConstantExpr)) { + return null; + } + ConstantExpr constant = (ConstantExpr) arg; + return constant.getValue() instanceof String ? (String) constant.getValue() : null; + } + + private boolean isIdentifier(String name) { + if (name.isEmpty() || !Character.isJavaIdentifierStart(name.charAt(0))) { + return false; + } + for (int i = 1; i < name.length(); ++i) { + if (!Character.isJavaIdentifierPart(name.charAt(i))) { + return false; + } + } + return true; + } +} diff --git a/jso/impl/src/main/resources/org/teavm/jso/impl/JS.js b/jso/impl/src/main/resources/org/teavm/jso/impl/JS.js new file mode 100644 index 000000000..7a84bb4cf --- /dev/null +++ b/jso/impl/src/main/resources/org/teavm/jso/impl/JS.js @@ -0,0 +1,34 @@ +/* + * Copyright 2023 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. + */ + +function jsFunction(self, method) { + let name = 'jso$functor$' + method; + let result = self[name]; + if (typeof result !== 'function') { + let fn = function() { + return self[method].apply(self, arguments); + } + result = () => fn; + self[name] = result; + } + return result(); +} +function jsFunctionAsObject(self, method) { + if (typeof self !== 'function') return self; + let result = {}; + result[method] = self; + return result; +} \ No newline at end of file