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 {
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();
}
}

View File

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

View File

@ -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<String, Scope> 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<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> EMPTY_DEFAULT = () -> new EmptyStatement(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;
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<String, NameEmitter> nameMap = new HashMap<>();
private boolean rootScope = true;
protected boolean rootScope = true;
private Set<String> aliases = new HashSet<>();
private Function<String, NameEmitter> globalNameWriter;
public final Map<String, Scope> currentScopes = new HashMap<>();
public AstWriter(SourceWriter writer, Function<String, NameEmitter> 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());
}
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<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_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");
}

View File

@ -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<String, Integer>();
var nameParameters = new HashMap<String, SourceFragment>();
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<String, SourceFragment> 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));
}

View File

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

View File

@ -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<String, SourceFragment> names;
private Map<String, SourceFragment> names;
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));
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;
}

View File

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

View File

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

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