JS: rewrite more native generators with templates, fix issues in template engine

This commit is contained in:
Alexey Andreev 2023-10-29 20:17:18 +01:00
parent 7c4aa522d3
commit a1cc817504
12 changed files with 554 additions and 407 deletions

View File

@ -26,25 +26,24 @@ public class LongNativeGenerator implements Generator {
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) { switch (methodRef.getName()) {
case "compare": case "compare":
writer.append("return Long_compare(").append(context.getParameterName(1)).append(", ") generateRuntimeCall(context, writer, "Long_compare");
.append(context.getParameterName(2)).append(");").softNewLine();
context.useLongLibrary();
break; break;
case "compareUnsigned": case "compareUnsigned":
writer.append("return Long_ucompare(").append(context.getParameterName(1)).append(", ") generateRuntimeCall(context, writer, "Long_ucompare");
.append(context.getParameterName(2)).append(");").softNewLine();
context.useLongLibrary();
break; break;
case "divideUnsigned": case "divideUnsigned":
writer.append("return Long_udiv(").append(context.getParameterName(1)).append(", ") generateRuntimeCall(context, writer, "Long_udiv");
.append(context.getParameterName(2)).append(");").softNewLine();
context.useLongLibrary();
break; break;
case "remainderUnsigned": case "remainderUnsigned":
writer.append("return Long_urem(").append(context.getParameterName(1)).append(", ") generateRuntimeCall(context, writer, "Long_urem");
.append(context.getParameterName(2)).append(");").softNewLine();
context.useLongLibrary();
break; 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();
}
} }

View File

@ -30,6 +30,7 @@ public final class AstUtil {
var env = new CompilerEnvirons(); var env = new CompilerEnvirons();
env.setRecoverFromErrors(true); env.setRecoverFromErrors(true);
env.setLanguageVersion(Context.VERSION_1_8); env.setLanguageVersion(Context.VERSION_1_8);
env.setIdeMode(true);
var factory = new JSParser(env); var factory = new JSParser(env);
return factory.parse(string, null, 0); return factory.parse(string, null, 0);

View File

@ -15,7 +15,11 @@
*/ */
package org.teavm.backend.javascript.ast; 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.List;
import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -70,6 +74,7 @@ import org.mozilla.javascript.ast.WhileLoop;
public class AstVisitor { public class AstVisitor {
protected AstNode replacement; protected AstNode replacement;
protected boolean hasReplacement; protected boolean hasReplacement;
protected final Map<String, Scope> currentScopes = new HashMap<>();
public final void visit(AstNode node) { public final void visit(AstNode node) {
switch (node.getType()) { switch (node.getType()) {
@ -261,7 +266,9 @@ public class AstVisitor {
} }
public void visit(Scope node) { public void visit(Scope node) {
var scope = enterScope(node);
visitChildren(node); visitChildren(node);
leaveScope(scope);
} }
public void visit(LabeledStatement node) { public void visit(LabeledStatement node) {
@ -284,26 +291,34 @@ public class AstVisitor {
} }
public void visit(DoLoop node) { public void visit(DoLoop node) {
var scope = enterScope(node);
visitProperty(node, DoLoop::getBody, DoLoop::setBody, EMPTY_DEFAULT); visitProperty(node, DoLoop::getBody, DoLoop::setBody, EMPTY_DEFAULT);
visitProperty(node, DoLoop::getCondition, DoLoop::setCondition, NULL_DEFAULT); visitProperty(node, DoLoop::getCondition, DoLoop::setCondition, NULL_DEFAULT);
leaveScope(scope);
} }
public void visit(ForInLoop node) { public void visit(ForInLoop node) {
var scope = enterScope(node);
visitProperty(node, ForInLoop::getIterator, ForInLoop::setIterator, NULL_DEFAULT); visitProperty(node, ForInLoop::getIterator, ForInLoop::setIterator, NULL_DEFAULT);
visitProperty(node, ForInLoop::getIteratedObject, ForInLoop::setIteratedObject, NULL_DEFAULT); visitProperty(node, ForInLoop::getIteratedObject, ForInLoop::setIteratedObject, NULL_DEFAULT);
visitProperty(node, ForInLoop::getBody, ForInLoop::setBody, EMPTY_DEFAULT); visitProperty(node, ForInLoop::getBody, ForInLoop::setBody, EMPTY_DEFAULT);
leaveScope(scope);
} }
public void visit(ForLoop node) { public void visit(ForLoop node) {
var scope = enterScope(node);
visitProperty(node, ForLoop::getInitializer, ForLoop::setInitializer, EMPTY_EXPR_DEFAULT); visitProperty(node, ForLoop::getInitializer, ForLoop::setInitializer, EMPTY_EXPR_DEFAULT);
visitProperty(node, ForLoop::getCondition, ForLoop::setCondition, EMPTY_EXPR_DEFAULT); visitProperty(node, ForLoop::getCondition, ForLoop::setCondition, EMPTY_EXPR_DEFAULT);
visitProperty(node, ForLoop::getIncrement, ForLoop::setIncrement, EMPTY_EXPR_DEFAULT); visitProperty(node, ForLoop::getIncrement, ForLoop::setIncrement, EMPTY_EXPR_DEFAULT);
visitProperty(node, ForLoop::getBody, ForLoop::setBody, EMPTY_DEFAULT); visitProperty(node, ForLoop::getBody, ForLoop::setBody, EMPTY_DEFAULT);
leaveScope(scope);
} }
public void visit(WhileLoop node) { public void visit(WhileLoop node) {
var scope = enterScope(node);
visitProperty(node, WhileLoop::getCondition, WhileLoop::setCondition, NULL_DEFAULT); visitProperty(node, WhileLoop::getCondition, WhileLoop::setCondition, NULL_DEFAULT);
visitProperty(node, WhileLoop::getBody, WhileLoop::setBody, EMPTY_DEFAULT); visitProperty(node, WhileLoop::getBody, WhileLoop::setBody, EMPTY_DEFAULT);
leaveScope(scope);
} }
public void visit(IfStatement node) { public void visit(IfStatement node) {
@ -375,15 +390,18 @@ public class AstVisitor {
} }
public void visit(ArrayComprehension node) { public void visit(ArrayComprehension node) {
var scope = enterScope(node);
for (var loop : node.getLoops()) { for (var loop : node.getLoops()) {
visitProperty(loop, ArrayComprehensionLoop::getIterator, ArrayComprehensionLoop::setIterator); visitProperty(loop, ArrayComprehensionLoop::getIterator, ArrayComprehensionLoop::setIterator);
visitProperty(loop, ArrayComprehensionLoop::getIteratedObject, ArrayComprehensionLoop::setIteratedObject); visitProperty(loop, ArrayComprehensionLoop::getIteratedObject, ArrayComprehensionLoop::setIteratedObject);
} }
visitProperty(node, ArrayComprehension::getFilter, ArrayComprehension::setFilter); visitProperty(node, ArrayComprehension::getFilter, ArrayComprehension::setFilter);
visitProperty(node, ArrayComprehension::getResult, ArrayComprehension::setResult); visitProperty(node, ArrayComprehension::getResult, ArrayComprehension::setResult);
leaveScope(scope);
} }
public void visit(GeneratorExpression node) { public void visit(GeneratorExpression node) {
var scope = enterScope(node);
for (var loop : node.getLoops()) { for (var loop : node.getLoops()) {
visitProperty(loop, GeneratorExpressionLoop::getIterator, GeneratorExpressionLoop::setIterator); visitProperty(loop, GeneratorExpressionLoop::getIterator, GeneratorExpressionLoop::setIterator);
visitProperty(loop, GeneratorExpressionLoop::getIteratedObject, visitProperty(loop, GeneratorExpressionLoop::getIteratedObject,
@ -391,6 +409,7 @@ public class AstVisitor {
} }
visitProperty(node, GeneratorExpression::getFilter, GeneratorExpression::setFilter); visitProperty(node, GeneratorExpression::getFilter, GeneratorExpression::setFilter);
visitProperty(node, GeneratorExpression::getResult, GeneratorExpression::setResult); visitProperty(node, GeneratorExpression::getResult, GeneratorExpression::setResult);
leaveScope(scope);
} }
public void visit(NumberLiteral node) { public void visit(NumberLiteral node) {
@ -426,14 +445,21 @@ public class AstVisitor {
} }
public void visit(FunctionNode node) { 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); visitProperty(node, FunctionNode::getFunctionName, FunctionNode::setFunctionName);
visitMany(node.getParams()); visitMany(node.getParams());
visitChildren(node.getBody()); visitChildren(node.getBody());
leaveScope(scope);
} }
public void visit(LetNode node) { public void visit(LetNode node) {
var scope = enterScope(node);
visitProperty(node, LetNode::getVariables, LetNode::setVariables); visitProperty(node, LetNode::getVariables, LetNode::setVariables);
visitProperty(node, LetNode::getBody, LetNode::setBody); visitProperty(node, LetNode::getBody, LetNode::setBody);
leaveScope(scope);
} }
public void visit(ParenthesizedExpression node) { public void visit(ParenthesizedExpression node) {
@ -464,6 +490,33 @@ public class AstVisitor {
replacement = node; replacement = node;
} }
private Map<String, Scope> enterScope(Scope scope) {
if (scope.getSymbolTable() == null) {
return Collections.emptyMap();
}
var map = new LinkedHashMap<String, Scope>();
for (var name : scope.getSymbolTable().keySet()) {
map.put(name, currentScopes.get(name));
currentScopes.put(name, scope);
}
return map;
}
private void leaveScope(Map<String, Scope> 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<AstNode> NULL_DEFAULT = () -> new KeywordLiteral(0, 0, Token.NULL); private static final Supplier<AstNode> NULL_DEFAULT = () -> new KeywordLiteral(0, 0, Token.NULL);
private static final Supplier<AstNode> EMPTY_DEFAULT = () -> new EmptyStatement(0, 0); private static final Supplier<AstNode> EMPTY_DEFAULT = () -> new EmptyStatement(0, 0);
private static final Supplier<AstNode> EMPTY_EXPR_DEFAULT = () -> new EmptyExpression(0, 0); private static final Supplier<AstNode> EMPTY_EXPR_DEFAULT = () -> new EmptyExpression(0, 0);

View File

@ -16,8 +16,10 @@
package org.teavm.backend.javascript.rendering; package org.teavm.backend.javascript.rendering;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -93,9 +95,10 @@ public class AstWriter {
public static final int PRECEDENCE_COMMA = 18; public static final int PRECEDENCE_COMMA = 18;
protected final SourceWriter writer; protected final SourceWriter writer;
private Map<String, NameEmitter> nameMap = new HashMap<>(); private Map<String, NameEmitter> nameMap = new HashMap<>();
private boolean rootScope = true; protected boolean rootScope = true;
private Set<String> aliases = new HashSet<>(); private Set<String> aliases = new HashSet<>();
private Function<String, NameEmitter> globalNameWriter; private Function<String, NameEmitter> globalNameWriter;
public final Map<String, Scope> currentScopes = new HashMap<>();
public AstWriter(SourceWriter writer, Function<String, NameEmitter> globalNameWriter) { public AstWriter(SourceWriter writer, Function<String, NameEmitter> globalNameWriter) {
this.writer = writer; this.writer = writer;
@ -319,12 +322,14 @@ public class AstWriter {
} }
private void print(Scope node) throws IOException { private void print(Scope node) throws IOException {
var scope = enterScope(node);
writer.append('{').softNewLine().indent(); writer.append('{').softNewLine().indent();
for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
print((AstNode) child); print((AstNode) child);
writer.softNewLine(); writer.softNewLine();
} }
writer.outdent().append('}'); writer.outdent().append('}');
leaveScope(scope);
} }
private void print(LabeledStatement node) throws IOException { private void print(LabeledStatement node) throws IOException {
@ -366,14 +371,17 @@ public class AstWriter {
} }
private void print(DoLoop node) throws IOException { private void print(DoLoop node) throws IOException {
var scope = enterScope(node);
writer.append("do ").ws(); writer.append("do ").ws();
print(node.getBody()); print(node.getBody());
writer.append("while").ws().append('('); writer.append("while").ws().append('(');
print(node.getCondition()); print(node.getCondition());
writer.append(");"); writer.append(");");
leaveScope(scope);
} }
private void print(ForInLoop node) throws IOException { private void print(ForInLoop node) throws IOException {
var scope = enterScope(node);
writer.append("for"); writer.append("for");
if (node.isForEach()) { if (node.isForEach()) {
writer.append(" each"); writer.append(" each");
@ -384,9 +392,11 @@ public class AstWriter {
print(node.getIteratedObject()); print(node.getIteratedObject());
writer.append(')').ws(); writer.append(')').ws();
print(node.getBody()); print(node.getBody());
leaveScope(scope);
} }
private void print(ForLoop node) throws IOException { private void print(ForLoop node) throws IOException {
var scope = enterScope(node);
writer.append("for").ws().append('('); writer.append("for").ws().append('(');
print(node.getInitializer()); print(node.getInitializer());
writer.append(';'); writer.append(';');
@ -395,13 +405,16 @@ public class AstWriter {
print(node.getIncrement()); print(node.getIncrement());
writer.append(')').ws(); writer.append(')').ws();
print(node.getBody()); print(node.getBody());
leaveScope(scope);
} }
private void print(WhileLoop node) throws IOException { private void print(WhileLoop node) throws IOException {
var scope = enterScope(node);
writer.append("while").ws().append('('); writer.append("while").ws().append('(');
print(node.getCondition()); print(node.getCondition());
writer.append(')').ws(); writer.append(')').ws();
print(node.getBody()); print(node.getBody());
leaveScope(scope);
} }
private void print(IfStatement node) throws IOException { private void print(IfStatement node) throws IOException {
@ -606,6 +619,7 @@ public class AstWriter {
} }
private void print(ArrayComprehension node) throws IOException { private void print(ArrayComprehension node) throws IOException {
var scope = enterScope(node);
writer.append("["); writer.append("[");
for (ArrayComprehensionLoop loop : node.getLoops()) { for (ArrayComprehensionLoop loop : node.getLoops()) {
writer.append("for").ws().append("("); writer.append("for").ws().append("(");
@ -621,9 +635,11 @@ public class AstWriter {
} }
print(node.getResult()); print(node.getResult());
writer.append(']'); writer.append(']');
leaveScope(scope);
} }
private void print(GeneratorExpression node) throws IOException { private void print(GeneratorExpression node) throws IOException {
var scope = enterScope(node);
writer.append("("); writer.append("(");
for (GeneratorExpressionLoop loop : node.getLoops()) { for (GeneratorExpressionLoop loop : node.getLoops()) {
writer.append("for").ws().append("("); writer.append("for").ws().append("(");
@ -639,6 +655,7 @@ public class AstWriter {
} }
print(node.getResult()); print(node.getResult());
writer.append(')'); writer.append(')');
leaveScope(scope);
} }
private void print(NumberLiteral node) throws IOException { private void print(NumberLiteral node) throws IOException {
@ -652,7 +669,8 @@ public class AstWriter {
} }
public void print(Name node, int precedence) throws IOException { 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()); var alias = nameMap.get(node.getIdentifier());
if (alias == null) { if (alias == null) {
if (globalNameWriter != null) { if (globalNameWriter != null) {
@ -667,10 +685,6 @@ public class AstWriter {
} }
} }
protected final boolean isRootScope() {
return rootScope;
}
private void print(RegExpLiteral node) throws IOException { private void print(RegExpLiteral node) throws IOException {
writer.append('/').append(node.getValue()).append('/').append(node.getFlags()); writer.append('/').append(node.getValue()).append('/').append(node.getFlags());
} }
@ -710,16 +724,28 @@ public class AstWriter {
} }
private void print(FunctionNode node) throws IOException { 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"); writer.append("function");
} }
if (node.getFunctionName() != null) { if (node.getFunctionName() != null) {
writer.append(' '); writer.append(' ');
print(node.getFunctionName()); print(node.getFunctionName());
} }
if (!isArrow || node.getParams().size() != 1) {
writer.append('('); writer.append('(');
printList(node.getParams()); printList(node.getParams());
writer.append(')').ws(); writer.append(')').ws();
} else {
print(node.getParams().get(0));
}
if (isArrow) {
writer.append("=>").ws();
}
if (node.isExpressionClosure()) { if (node.isExpressionClosure()) {
if (node.getBody().getLastChild() instanceof ReturnStatement) { if (node.getBody().getLastChild() instanceof ReturnStatement) {
@ -731,13 +757,17 @@ public class AstWriter {
} else { } else {
print(node.getBody()); print(node.getBody());
} }
leaveScope(scope);
} }
private void print(LetNode node) throws IOException { private void print(LetNode node) throws IOException {
var scope = enterScope(node);
writer.append("let").ws().append('('); writer.append("let").ws().append('(');
printList(node.getVariables().getVariables()); printList(node.getVariables().getVariables());
writer.append(')'); writer.append(')');
print(node.getBody()); print(node.getBody());
leaveScope(scope);
} }
private void print(ParenthesizedExpression node, int precedence) throws IOException { private void print(ParenthesizedExpression node, int precedence) throws IOException {
@ -934,4 +964,30 @@ public class AstWriter {
return false; return false;
} }
} }
private Map<String, Scope> enterScope(Scope scope) {
if (scope.getSymbolTable() == null) {
return Collections.emptyMap();
}
var map = new LinkedHashMap<String, Scope>();
for (var name : scope.getSymbolTable().keySet()) {
map.put(name, currentScopes.get(name));
currentScopes.put(name, scope);
}
return map;
}
private void leaveScope(Map<String, Scope> 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);
}
} }

View File

@ -264,7 +264,7 @@ public class Renderer implements RenderingManager {
"$rt_createFloatArray", "$rt_createDoubleArray", "$rt_compare", "$rt_createFloatArray", "$rt_createDoubleArray", "$rt_compare",
"$rt_castToClass", "$rt_castToInterface", "$rt_equalDoubles", "$rt_castToClass", "$rt_castToInterface", "$rt_equalDoubles",
"$rt_str", "Long_toNumber", "Long_fromInt", "Long_fromNumber", "Long_create", "Long_ZERO", "$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"); "Long_hi", "Long_lo");
} }

View File

@ -16,7 +16,6 @@
package org.teavm.backend.javascript.templating; package org.teavm.backend.javascript.templating;
import java.util.HashMap; import java.util.HashMap;
import java.util.function.Function;
import java.util.function.IntFunction; import java.util.function.IntFunction;
import org.mozilla.javascript.ast.AstNode; import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.FunctionNode; import org.mozilla.javascript.ast.FunctionNode;
@ -59,21 +58,22 @@ public class JavaScriptTemplate {
public SourceFragment build() { public SourceFragment build() {
var intParameters = parameters; var intParameters = parameters;
var paramNameToIndex = new HashMap<String, Integer>(); var nameParameters = new HashMap<String, SourceFragment>();
for (var i = 0; i < node.getParams().size(); ++i) { for (var i = 0; i < node.getParams().size(); ++i) {
var param = node.getParams().get(i); var param = node.getParams().get(i);
if (param instanceof Name) { if (param instanceof Name) {
paramNameToIndex.put(((Name) param).getIdentifier(), i); nameParameters.put(((Name) param).getIdentifier(), intParameters.apply(i));
} }
} }
Function<String, SourceFragment> nameParameters = name -> {
var index = paramNameToIndex.get(name);
return index != null ? intParameters.apply(index) : null;
};
var thisFragment = parameters.apply(0); var thisFragment = parameters.apply(0);
var body = node.getBody(); var body = node.getBody();
return (writer, precedence) -> { return (writer, precedence) -> {
var astWriter = new TemplatingAstWriter(writer, nameParameters, node); 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) { if (thisFragment != null) {
astWriter.declareNameEmitter("this", thisPrecedence -> thisFragment.write(writer, thisPrecedence)); astWriter.declareNameEmitter("this", thisPrecedence -> thisFragment.write(writer, thisPrecedence));
} }

View File

@ -112,7 +112,8 @@ public class TemplatingAstTransformer extends AstVisitor {
super.visit(node); super.visit(node);
if (node.getTarget() instanceof Name) { if (node.getTarget() instanceof Name) {
var name = (Name) node.getTarget(); var name = (Name) node.getTarget();
if (name.getDefiningScope() == null) { var scope = scopeOfId(name.getIdentifier());
if (scope == null) {
tryIntrinsicName(node, name.getIdentifier()); tryIntrinsicName(node, name.getIdentifier());
} }
} }

View File

@ -16,9 +16,10 @@
package org.teavm.backend.javascript.templating; package org.teavm.backend.javascript.templating;
import java.io.IOException; import java.io.IOException;
import java.util.function.Function; import java.util.Map;
import org.mozilla.javascript.ast.ElementGet; import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.FunctionCall; import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Name; import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.PropertyGet; import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.Scope; import org.mozilla.javascript.ast.Scope;
@ -31,20 +32,28 @@ import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
public class TemplatingAstWriter extends AstWriter { public class TemplatingAstWriter extends AstWriter {
private Function<String, SourceFragment> names; private Map<String, SourceFragment> names;
private Scope scope; private Scope scope;
public TemplatingAstWriter(SourceWriter writer, Function<String, SourceFragment> names, Scope scope) { public TemplatingAstWriter(SourceWriter writer, Map<String, SourceFragment> names, Scope scope) {
super(writer, new DefaultGlobalNameWriter(writer)); super(writer, new DefaultGlobalNameWriter(writer));
this.names = names; this.names = names;
this.scope = scope; this.scope = scope;
if (names != null) {
for (var name : names.keySet()) {
currentScopes.put(name, scope);
}
}
if (scope instanceof FunctionNode) {
currentScopes.put("arguments", scope);
}
} }
@Override @Override
protected boolean intrinsic(FunctionCall node, int precedence) throws IOException { protected boolean intrinsic(FunctionCall node, int precedence) throws IOException {
if (node.getTarget() instanceof Name) { if (node.getTarget() instanceof Name) {
var name = (Name) node.getTarget(); var name = (Name) node.getTarget();
if (name.getDefiningScope() == null) { if (scopeOfId(name.getIdentifier()) == null) {
return tryIntrinsicName(node, name.getIdentifier()); return tryIntrinsicName(node, name.getIdentifier());
} }
} }
@ -126,7 +135,7 @@ public class TemplatingAstWriter extends AstWriter {
var call = (FunctionCall) node.getElement(); var call = (FunctionCall) node.getElement();
if (call.getTarget() instanceof Name) { if (call.getTarget() instanceof Name) {
var name = (Name) call.getTarget(); var name = (Name) call.getTarget();
if (name.getDefiningScope() == null) { if (scopeOfId(name.getIdentifier()) == null) {
switch (name.getIdentifier()) { switch (name.getIdentifier()) {
case "teavm_javaVirtualMethod": case "teavm_javaVirtualMethod":
if (writeJavaVirtualMethod(node, call)) { if (writeJavaVirtualMethod(node, call)) {
@ -149,9 +158,13 @@ public class TemplatingAstWriter extends AstWriter {
public void print(PropertyGet node) throws IOException { public void print(PropertyGet node) throws IOException {
if (node.getTarget() instanceof Name) { if (node.getTarget() instanceof Name) {
var name = (Name) node.getTarget(); 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("."); writer.append("$rt_globals").append(".");
print(node.getProperty()); print(node.getProperty());
rootScope = oldRootScope;
return; return;
} }
} }
@ -187,15 +200,16 @@ public class TemplatingAstWriter extends AstWriter {
@Override @Override
public void print(Name node, int precedence) throws IOException { public void print(Name node, int precedence) throws IOException {
if (isRootScope()) { var definingScope = scopeOfId(node.getIdentifier());
if (names != null && node.getDefiningScope() == scope) { if (rootScope) {
var fragment = names.apply(node.getIdentifier()); if (names != null && definingScope == scope) {
var fragment = names.get(node.getIdentifier());
if (fragment != null) { if (fragment != null) {
fragment.write(writer, precedence); fragment.write(writer, precedence);
return; return;
} }
} }
if (node.getDefiningScope() == null && scope != null) { if (definingScope == null && scope != null) {
writer.appendFunction(node.getIdentifier()); writer.appendFunction(node.getIdentifier());
return; return;
} }

View File

@ -32,107 +32,107 @@ final class JS {
private JS() { private JS() {
} }
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject arrayData(Object array); public static native JSObject arrayData(Object array);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native byte[] dataToByteArray(JSObject obj); public static native byte[] dataToByteArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native char[] dataToCharArray(JSObject obj); public static native char[] dataToCharArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native short[] dataToShortArray(JSObject obj); public static native short[] dataToShortArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native int[] dataToIntArray(JSObject obj); public static native int[] dataToIntArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native float[] dataToFloatArray(JSObject obj); public static native float[] dataToFloatArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native double[] dataToDoubleArray(JSObject obj); public static native double[] dataToDoubleArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject[] dataToArray(JSObject obj); public static native JSObject[] dataToArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject wrap(byte value); public static native JSObject wrap(byte value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject wrap(short value); public static native JSObject wrap(short value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject wrap(int value); public static native JSObject wrap(int value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject wrap(char value); public static native JSObject wrap(char value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject wrap(float value); public static native JSObject wrap(float value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject wrap(double value); public static native JSObject wrap(double value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject wrap(boolean value); public static native JSObject wrap(boolean value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject wrap(String value); public static native JSObject wrap(String value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native byte unwrapByte(JSObject value); public static native byte unwrapByte(JSObject value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native char unwrapCharacter(JSObject value); public static native char unwrapCharacter(JSObject value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native short unwrapShort(JSObject value); public static native short unwrapShort(JSObject value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native int unwrapInt(JSObject value); public static native int unwrapInt(JSObject value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native float unwrapFloat(JSObject value); public static native float unwrapFloat(JSObject value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native double unwrapDouble(JSObject value); public static native double unwrapDouble(JSObject value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native boolean unwrapBoolean(JSObject value); public static native boolean unwrapBoolean(JSObject value);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native String unwrapString(JSObject value); public static native String unwrapString(JSObject value);
@ -448,97 +448,97 @@ final class JS {
return JS::unwrapStringArray; return JS::unwrapStringArray;
} }
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method); public static native JSObject invoke(JSObject instance, JSObject method);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a); public static native JSObject invoke(JSObject instance, JSObject method, JSObject a);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b); public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c); public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d); JSObject d);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e); JSObject d, JSObject e);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e, JSObject f); JSObject d, JSObject e, JSObject f);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e, JSObject f, JSObject g); JSObject d, JSObject e, JSObject f, JSObject g);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, 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 d, JSObject e, JSObject f, JSObject g, JSObject h);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, 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 d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, 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 d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, 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 d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, 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 d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k,
JSObject l); JSObject l);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, 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 d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k,
JSObject l, JSObject m); JSObject l, JSObject m);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@JSBody(params = { "instance", "index" }, script = "return instance[index];") @JSBody(params = { "instance", "index" }, script = "return instance[index];")
public static native JSObject get(JSObject instance, JSObject index); public static native JSObject get(JSObject instance, JSObject index);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@JSBody(params = { "instance", "index" }, script = "return instance[index];") @JSBody(params = { "instance", "index" }, script = "return instance[index];")
@NoSideEffects @NoSideEffects
public static native JSObject getPure(JSObject instance, JSObject index); public static native JSObject getPure(JSObject instance, JSObject index);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeInjector.class)
@JSBody(params = { "instance", "index", "obj" }, script = "instance[index] = obj;") @JSBody(params = { "instance", "index", "obj" }, script = "instance[index] = obj;")
public static native void set(JSObject instance, JSObject index, JSObject 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;") @JSBody(params = { "instance", "index", "obj" }, script = "instance[index] = obj;")
@NoSideEffects @NoSideEffects
public static native void setPure(JSObject instance, JSObject index, JSObject obj); public static native void setPure(JSObject instance, JSObject index, JSObject obj);
@GeneratedBy(JSNativeGenerator.class) @GeneratedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject function(JSObject instance, JSObject property); public static native JSObject function(JSObject instance, JSObject property);
@GeneratedBy(JSNativeGenerator.class) @GeneratedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class)
public static native JSObject functionAsObject(JSObject instance, JSObject property); public static native JSObject functionAsObject(JSObject instance, JSObject property);
} }

View File

@ -15,321 +15,31 @@
*/ */
package org.teavm.jso.impl; package org.teavm.jso.impl;
import static org.teavm.backend.javascript.rendering.RenderingUtil.escapeString;
import java.io.IOException; 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.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.Precedence;
import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.backend.javascript.spi.Injector; import org.teavm.backend.javascript.templating.JavaScriptTemplate;
import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
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.MethodReference;
import org.teavm.model.ValueType;
public class JSNativeGenerator implements Injector, DependencyPlugin, Generator { public class JSNativeGenerator implements Generator {
private Set<MethodReference> reachedFunctorMethods = new HashSet<>(); private JavaScriptTemplate template;
private Set<DependencyNode> functorParamNodes = new HashSet<>();
public JSNativeGenerator(JavaScriptTemplateFactory templateFactory) throws IOException {
template = templateFactory.createFromResource("org/teavm/jso/impl/JS.js");
}
@Override @Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef)
throws IOException { throws IOException {
switch (methodRef.getName()) { switch (methodRef.getName()) {
case "function": case "function":
writeFunction(context, writer); template.builder("jsFunction").withContext(context).build().write(writer, 0);
break; break;
case "functionAsObject": case "functionAsObject":
writeFunctionAsObject(context, writer); template.builder("jsFunctionAsObject").withContext(context).build().write(writer, 0);
break; 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;
}
} }

View File

@ -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<MethodReference> reachedFunctorMethods = new HashSet<>();
private Set<DependencyNode> 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;
}
}

View File

@ -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;
}