JS: add conditional intrinsic to JS template engine, use it to get rid of remaining generated methods in runtime

This commit is contained in:
Alexey Andreev 2023-10-26 21:17:08 +02:00
parent c5768e07bc
commit 5d5fb47ca8
6 changed files with 734 additions and 614 deletions

View File

@ -0,0 +1,470 @@
/*
* 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.backend.javascript.ast;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.ArrayComprehension;
import org.mozilla.javascript.ast.ArrayComprehensionLoop;
import org.mozilla.javascript.ast.ArrayLiteral;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.Block;
import org.mozilla.javascript.ast.BreakStatement;
import org.mozilla.javascript.ast.CatchClause;
import org.mozilla.javascript.ast.ConditionalExpression;
import org.mozilla.javascript.ast.ContinueStatement;
import org.mozilla.javascript.ast.DoLoop;
import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.EmptyExpression;
import org.mozilla.javascript.ast.EmptyStatement;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.ForInLoop;
import org.mozilla.javascript.ast.ForLoop;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.GeneratorExpression;
import org.mozilla.javascript.ast.GeneratorExpressionLoop;
import org.mozilla.javascript.ast.IfStatement;
import org.mozilla.javascript.ast.InfixExpression;
import org.mozilla.javascript.ast.KeywordLiteral;
import org.mozilla.javascript.ast.LabeledStatement;
import org.mozilla.javascript.ast.LetNode;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NewExpression;
import org.mozilla.javascript.ast.NumberLiteral;
import org.mozilla.javascript.ast.ObjectLiteral;
import org.mozilla.javascript.ast.ObjectProperty;
import org.mozilla.javascript.ast.ParenthesizedExpression;
import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.RegExpLiteral;
import org.mozilla.javascript.ast.ReturnStatement;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.ast.StringLiteral;
import org.mozilla.javascript.ast.SwitchCase;
import org.mozilla.javascript.ast.SwitchStatement;
import org.mozilla.javascript.ast.ThrowStatement;
import org.mozilla.javascript.ast.TryStatement;
import org.mozilla.javascript.ast.UnaryExpression;
import org.mozilla.javascript.ast.UpdateExpression;
import org.mozilla.javascript.ast.VariableDeclaration;
import org.mozilla.javascript.ast.VariableInitializer;
import org.mozilla.javascript.ast.WhileLoop;
public class AstVisitor {
protected AstNode replacement;
protected boolean hasReplacement;
public final void visit(AstNode node) {
switch (node.getType()) {
case Token.SCRIPT:
visit((AstRoot) node);
break;
case Token.CALL:
case Token.NEW:
visit((FunctionCall) node);
break;
case Token.FUNCTION:
visit((FunctionNode) node);
break;
case Token.ARRAYCOMP:
visit((ArrayComprehension) node);
break;
case Token.GETPROP:
visit((PropertyGet) node);
break;
case Token.GENEXPR:
visit((GeneratorExpression) node);
break;
case Token.NUMBER:
visit((NumberLiteral) node);
break;
case Token.STRING:
visit((StringLiteral) node);
break;
case Token.TRUE:
case Token.FALSE:
case Token.THIS:
case Token.NULL:
visit((KeywordLiteral) node);
break;
case Token.NAME:
visit((Name) node);
break;
case Token.REGEXP:
visit((RegExpLiteral) node);
break;
case Token.OBJECTLIT:
visit((ObjectLiteral) node);
break;
case Token.ARRAYLIT:
visit((ArrayLiteral) node);
break;
case Token.BLOCK:
if (node instanceof Block) {
visit((Block) node);
} else if (node instanceof Scope) {
visit((Scope) node);
}
break;
case Token.HOOK:
visit((ConditionalExpression) node);
break;
case Token.GETELEM:
visit((ElementGet) node);
break;
case Token.LETEXPR:
visit((LetNode) node);
break;
case Token.LP:
visit((ParenthesizedExpression) node);
break;
case Token.EMPTY:
if (node instanceof EmptyExpression) {
visit((EmptyExpression) node);
} else if (node instanceof EmptyStatement) {
visit((EmptyStatement) node);
}
break;
case Token.EXPR_VOID:
case Token.EXPR_RESULT:
if (node instanceof ExpressionStatement) {
visit((ExpressionStatement) node);
} else if (node instanceof LabeledStatement) {
visit((LabeledStatement) node);
}
break;
case Token.BREAK:
visit((BreakStatement) node);
break;
case Token.CONTINUE:
visit((ContinueStatement) node);
break;
case Token.RETURN:
visit((ReturnStatement) node);
break;
case Token.DO:
visit((DoLoop) node);
break;
case Token.FOR:
if (node instanceof ForInLoop) {
visit((ForInLoop) node);
} else if (node instanceof ForLoop) {
visit((ForLoop) node);
}
break;
case Token.IF:
visit((IfStatement) node);
break;
case Token.SWITCH:
visit((SwitchStatement) node);
break;
case Token.THROW:
visit((ThrowStatement) node);
break;
case Token.TRY:
visit((TryStatement) node);
break;
case Token.CONST:
case Token.VAR:
case Token.LET:
visit((VariableDeclaration) node);
break;
case Token.WHILE:
visit((WhileLoop) node);
break;
default:
if (node instanceof InfixExpression) {
visit((InfixExpression) node);
} else if (node instanceof UnaryExpression) {
visit((UnaryExpression) node);
} else if (node instanceof UpdateExpression) {
visit((UpdateExpression) node);
}
break;
}
}
protected final void visitMany(List<AstNode> nodes) {
for (var i = 0; i < nodes.size(); ++i) {
var node = nodes.get(i);
visit(node);
if (hasReplacement) {
nodes.set(i, replacement);
}
hasReplacement = false;
replacement = null;
}
}
protected final void visitChildren(AstNode node) {
for (var child = node.getFirstChild(); child != null;) {
var next = child.getNext();
visit((AstNode) child);
if (hasReplacement) {
if (replacement != null) {
node.replaceChild(child, replacement);
} else {
node.removeChild(child);
}
replacement = null;
hasReplacement = false;
}
child = next;
}
}
protected final <T extends AstNode, S extends AstNode> void visitProperty(T owner, Function<T, S> getter,
BiConsumer<T, S> setter) {
visitProperty(owner, getter, setter, null);
}
protected final <T extends AstNode, S extends AstNode> void visitProperty(T owner, Function<T, S> getter,
BiConsumer<T, S> setter, Supplier<S> defaultValue) {
var node = getter.apply(owner);
if (node != null) {
visit(node);
if (hasReplacement) {
if (replacement == null && defaultValue != null) {
replacement = defaultValue.get();
}
//noinspection unchecked
setter.accept(owner, (S) replacement);
}
replacement = null;
hasReplacement = false;
}
}
public void visit(AstRoot node) {
visitChildren(node);
}
public void visit(Block node) {
visitChildren(node);
}
public void visit(Scope node) {
visitChildren(node);
}
public void visit(LabeledStatement node) {
visitProperty(node, LabeledStatement::getStatement, LabeledStatement::setStatement, EMPTY_DEFAULT);
}
public void visit(BreakStatement node) {
}
public void visit(ContinueStatement node) {
}
public void visit(ReturnStatement node) {
visitProperty(node, ReturnStatement::getReturnValue, ReturnStatement::setReturnValue);
}
public void visit(ThrowStatement node) {
visitProperty(node, ThrowStatement::getExpression, ThrowStatement::setExpression,
() -> new KeywordLiteral(0, 0, Token.NULL));
}
public void visit(DoLoop node) {
visitProperty(node, DoLoop::getBody, DoLoop::setBody, EMPTY_DEFAULT);
visitProperty(node, DoLoop::getCondition, DoLoop::setCondition, NULL_DEFAULT);
}
public void visit(ForInLoop 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);
}
public void visit(ForLoop 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);
}
public void visit(WhileLoop node) {
visitProperty(node, WhileLoop::getCondition, WhileLoop::setCondition, NULL_DEFAULT);
visitProperty(node, WhileLoop::getBody, WhileLoop::setBody, EMPTY_DEFAULT);
}
public void visit(IfStatement node) {
visitProperty(node, IfStatement::getCondition, IfStatement::setCondition, NULL_DEFAULT);
visitProperty(node, IfStatement::getThenPart, IfStatement::setThenPart, EMPTY_DEFAULT);
visitProperty(node, IfStatement::getElsePart, IfStatement::setElsePart);
}
public void visit(SwitchStatement node) {
visitProperty(node, SwitchStatement::getExpression, SwitchStatement::setExpression, NULL_DEFAULT);
for (var sc : node.getCases()) {
visitProperty(sc, SwitchCase::getExpression, SwitchCase::setExpression);
if (sc.getStatements() != null) {
visitMany(sc.getStatements());
}
}
}
public void visit(TryStatement node) {
visitProperty(node, TryStatement::getTryBlock, TryStatement::setTryBlock, NULL_DEFAULT);
for (var cc : node.getCatchClauses()) {
visitProperty(cc, CatchClause::getVarName, CatchClause::setVarName);
visitProperty(cc, CatchClause::getCatchCondition, CatchClause::setCatchCondition);
if (cc.getBody() != null) {
visitChildren(cc.getBody());
}
}
visitProperty(node, TryStatement::getFinallyBlock, TryStatement::setFinallyBlock);
}
public void visit(VariableDeclaration node) {
for (var variable : node.getVariables()) {
visit(variable);
}
}
public void visit(VariableInitializer node) {
visitProperty(node, VariableInitializer::getTarget, VariableInitializer::setTarget);
visitProperty(node, VariableInitializer::getInitializer, VariableInitializer::setInitializer);
}
public void visit(ExpressionStatement node) {
visitProperty(node, ExpressionStatement::getExpression, ExpressionStatement::setExpression, NULL_DEFAULT);
}
public void visit(ElementGet node) {
visitProperty(node, ElementGet::getTarget, ElementGet::setTarget, NULL_DEFAULT);
visitProperty(node, ElementGet::getElement, ElementGet::setElement, NULL_DEFAULT);
}
public void visit(PropertyGet node) {
visitProperty(node, PropertyGet::getTarget, PropertyGet::setTarget, NULL_DEFAULT);
visitProperty(node, PropertyGet::getProperty, PropertyGet::setProperty);
}
public void visit(FunctionCall node) {
visitProperty(node, FunctionCall::getTarget, FunctionCall::setTarget);
visitMany(node.getArguments());
if (node instanceof NewExpression) {
var newExpr = (NewExpression) node;
visitProperty(newExpr, NewExpression::getInitializer, NewExpression::setInitializer);
}
}
public void visit(ConditionalExpression node) {
visitProperty(node, ConditionalExpression::getTestExpression, ConditionalExpression::setTestExpression);
visitProperty(node, ConditionalExpression::getTrueExpression, ConditionalExpression::setTrueExpression);
visitProperty(node, ConditionalExpression::getFalseExpression, ConditionalExpression::setFalseExpression);
}
public void visit(ArrayComprehension 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);
}
public void visit(GeneratorExpression node) {
for (var loop : node.getLoops()) {
visitProperty(loop, GeneratorExpressionLoop::getIterator, GeneratorExpressionLoop::setIterator);
visitProperty(loop, GeneratorExpressionLoop::getIteratedObject,
GeneratorExpressionLoop::setIteratedObject);
}
visitProperty(node, GeneratorExpression::getFilter, GeneratorExpression::setFilter);
visitProperty(node, GeneratorExpression::getResult, GeneratorExpression::setResult);
}
public void visit(NumberLiteral node) {
}
public void visit(StringLiteral node) {
}
public void visit(KeywordLiteral node) {
}
public void visit(Name node) {
}
public void visit(RegExpLiteral node) {
}
public void visit(ArrayLiteral node) {
visitMany(node.getElements());
}
public void visit(ObjectLiteral node) {
if (node.getElements() != null) {
for (var element : node.getElements()) {
visit(element);
}
}
}
public void visit(ObjectProperty node) {
visitProperty(node, ObjectProperty::getLeft, ObjectProperty::setLeft);
visitProperty(node, ObjectProperty::getRight, ObjectProperty::setRight);
}
public void visit(FunctionNode node) {
visitProperty(node, FunctionNode::getFunctionName, FunctionNode::setFunctionName);
visitMany(node.getParams());
visitChildren(node.getBody());
}
public void visit(LetNode node) {
visitProperty(node, LetNode::getVariables, LetNode::setVariables);
visitProperty(node, LetNode::getBody, LetNode::setBody);
}
public void visit(ParenthesizedExpression node) {
visitProperty(node, ParenthesizedExpression::getExpression, ParenthesizedExpression::setExpression);
}
public void visit(EmptyExpression node) {
}
public void visit(EmptyStatement node) {
}
public void visit(InfixExpression node) {
visitProperty(node, InfixExpression::getLeft, InfixExpression::setLeft);
visitProperty(node, InfixExpression::getRight, InfixExpression::setRight);
}
public void visit(UnaryExpression node) {
visitProperty(node, UnaryExpression::getOperand, UnaryExpression::setOperand);
}
public void visit(UpdateExpression node) {
visitProperty(node, UpdateExpression::getOperand, UpdateExpression::setOperand);
}
protected final void replaceWith(AstNode node) {
hasReplacement = true;
replacement = node;
}
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

@ -1,441 +0,0 @@
/*
* Copyright 2019 konsoletyper.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.rendering;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.ArrayComprehension;
import org.mozilla.javascript.ast.ArrayComprehensionLoop;
import org.mozilla.javascript.ast.ArrayLiteral;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.Block;
import org.mozilla.javascript.ast.BreakStatement;
import org.mozilla.javascript.ast.CatchClause;
import org.mozilla.javascript.ast.ConditionalExpression;
import org.mozilla.javascript.ast.ContinueStatement;
import org.mozilla.javascript.ast.DoLoop;
import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.EmptyExpression;
import org.mozilla.javascript.ast.EmptyStatement;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.ForInLoop;
import org.mozilla.javascript.ast.ForLoop;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.GeneratorExpression;
import org.mozilla.javascript.ast.GeneratorExpressionLoop;
import org.mozilla.javascript.ast.IfStatement;
import org.mozilla.javascript.ast.InfixExpression;
import org.mozilla.javascript.ast.LabeledStatement;
import org.mozilla.javascript.ast.LetNode;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NewExpression;
import org.mozilla.javascript.ast.NumberLiteral;
import org.mozilla.javascript.ast.ObjectLiteral;
import org.mozilla.javascript.ast.ObjectProperty;
import org.mozilla.javascript.ast.ParenthesizedExpression;
import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.RegExpLiteral;
import org.mozilla.javascript.ast.ReturnStatement;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.ast.StringLiteral;
import org.mozilla.javascript.ast.SwitchCase;
import org.mozilla.javascript.ast.SwitchStatement;
import org.mozilla.javascript.ast.ThrowStatement;
import org.mozilla.javascript.ast.TryStatement;
import org.mozilla.javascript.ast.UnaryExpression;
import org.mozilla.javascript.ast.VariableDeclaration;
import org.mozilla.javascript.ast.VariableInitializer;
import org.mozilla.javascript.ast.WhileLoop;
public class AstVisitor {
public void accept(AstNode node) {
switch (node.getType()) {
case Token.SCRIPT:
visitRoot((AstRoot) node);
break;
case Token.CALL:
case Token.NEW:
visitFunctionCall((FunctionCall) node);
break;
case Token.FUNCTION:
visitFunction((FunctionNode) node);
break;
case Token.ARRAYCOMP:
visitArrayComprehension((ArrayComprehension) node);
break;
case Token.GETPROP:
visitPropertyGet((PropertyGet) node);
break;
case Token.GENEXPR:
visitGenerator((GeneratorExpression) node);
break;
case Token.NUMBER:
visitNumber((NumberLiteral) node);
break;
case Token.STRING:
visitString((StringLiteral) node);
break;
case Token.TRUE:
visitTrue(node);
break;
case Token.FALSE:
visitFalse(node);
break;
case Token.THIS:
visitThis(node);
break;
case Token.NULL:
visitNull(node);
break;
case Token.NAME:
visitName((Name) node);
break;
case Token.REGEXP:
visitRegexp((RegExpLiteral) node);
break;
case Token.OBJECTLIT:
visitObjectLiteral((ObjectLiteral) node);
break;
case Token.ARRAYLIT:
visitArrayLiteral((ArrayLiteral) node);
break;
case Token.BLOCK:
if (node instanceof Block) {
visitBlock((Block) node);
} else if (node instanceof Scope) {
visitScope((Scope) node);
}
break;
case Token.HOOK:
visitConditionalExpr((ConditionalExpression) node);
break;
case Token.GETELEM:
visitElementGet((ElementGet) node);
break;
case Token.LETEXPR:
visitLet((LetNode) node);
break;
case Token.LP:
visitParenthesized((ParenthesizedExpression) node);
break;
case Token.EMPTY:
if (node instanceof EmptyStatement) {
visitEmpty((EmptyStatement) node);
} else {
visitEmpty((EmptyExpression) node);
}
break;
case Token.EXPR_VOID:
case Token.EXPR_RESULT:
if (node instanceof ExpressionStatement) {
visitExpressionStatement((ExpressionStatement) node);
} else if (node instanceof LabeledStatement) {
visitLabeledStatement((LabeledStatement) node);
}
break;
case Token.BREAK:
visitBreak((BreakStatement) node);
break;
case Token.CONTINUE:
visitContinue((ContinueStatement) node);
break;
case Token.RETURN:
visitReturn((ReturnStatement) node);
break;
case Token.DO:
visitDo((DoLoop) node);
break;
case Token.FOR:
if (node instanceof ForInLoop) {
visitForIn((ForInLoop) node);
} else if (node instanceof ForLoop) {
visitFor((ForLoop) node);
}
break;
case Token.IF:
visitIf((IfStatement) node);
break;
case Token.SWITCH:
visitSwitch((SwitchStatement) node);
break;
case Token.THROW:
visitThrow((ThrowStatement) node);
break;
case Token.TRY:
visitTry((TryStatement) node);
break;
case Token.CONST:
case Token.VAR:
case Token.LET:
visitVariableDeclaration((VariableDeclaration) node);
break;
case Token.WHILE:
visitWhile((WhileLoop) node);
break;
default:
if (node instanceof InfixExpression) {
visitInfix((InfixExpression) node);
} else if (node instanceof UnaryExpression) {
visitUnary((UnaryExpression) node);
}
break;
}
}
protected void visitRoot(AstRoot node) {
for (Node child : node) {
accept((AstNode) child);
}
}
protected void visitBlock(Block node) {
for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
accept((AstNode) child);
}
}
protected void visitScope(Scope node) {
for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
accept((AstNode) child);
}
}
protected void visitLabeledStatement(LabeledStatement node) {
accept(node.getStatement());
}
protected void visitBreak(BreakStatement node) {
}
protected void visitContinue(ContinueStatement node) {
}
protected void visitReturn(ReturnStatement node) {
if (node.getReturnValue() != null) {
accept(node.getReturnValue());
}
}
protected void visitThrow(ThrowStatement node) {
accept(node.getExpression());
}
protected void visitDo(DoLoop node) {
accept(node.getBody());
accept(node.getCondition());
}
protected void visitForIn(ForInLoop node) {
accept(node.getIterator());
accept(node.getIteratedObject());
accept(node.getBody());
}
protected void visitFor(ForLoop node) {
accept(node.getInitializer());
accept(node.getCondition());
accept(node.getIncrement());
accept(node.getBody());
}
protected void visitWhile(WhileLoop node) {
accept(node.getCondition());
accept(node.getBody());
}
protected void visitIf(IfStatement node) {
accept(node.getCondition());
accept(node.getThenPart());
if (node.getElsePart() != null) {
accept(node.getElsePart());
}
}
protected void visitSwitch(SwitchStatement node) {
accept(node.getExpression());
for (SwitchCase sc : node.getCases()) {
if (sc.getExpression() != null) {
accept(sc.getExpression());
}
if (sc.getStatements() != null) {
for (AstNode stmt : sc.getStatements()) {
accept(stmt);
}
}
}
}
protected void visitTry(TryStatement node) {
accept(node.getTryBlock());
for (CatchClause cc : node.getCatchClauses()) {
if (cc.getCatchCondition() != null) {
accept(cc.getCatchCondition());
}
accept(cc.getBody());
}
if (node.getFinallyBlock() != null) {
accept(node.getFinallyBlock());
}
}
protected void visitVariableDeclaration(VariableDeclaration node) {
for (int i = 1; i < node.getVariables().size(); ++i) {
visitVariableInitializer(node.getVariables().get(i));
}
}
protected void visitVariableInitializer(VariableInitializer node) {
accept(node.getTarget());
if (node.getInitializer() != null) {
accept(node.getInitializer());
}
}
protected void visitExpressionStatement(ExpressionStatement node) {
accept(node.getExpression());
}
protected void visitElementGet(ElementGet node) {
accept(node.getTarget());
accept(node.getElement());
}
protected void visitPropertyGet(PropertyGet node) {
accept(node.getLeft());
accept(node.getRight());
}
protected void visitFunctionCall(FunctionCall node) {
accept(node.getTarget());
for (AstNode arg : node.getArguments()) {
accept(arg);
}
if (node instanceof NewExpression) {
NewExpression newExpr = (NewExpression) node;
if (newExpr.getInitializer() != null) {
accept(newExpr.getInitializer());
}
}
}
protected void visitConditionalExpr(ConditionalExpression node) {
accept(node.getTestExpression());
accept(node.getTrueExpression());
accept(node.getFalseExpression());
}
protected void visitArrayComprehension(ArrayComprehension node) {
for (ArrayComprehensionLoop loop : node.getLoops()) {
accept(loop.getIterator());
accept(loop.getIteratedObject());
}
if (node.getFilter() != null) {
accept(node.getFilter());
}
accept(node.getResult());
}
protected void visitGenerator(GeneratorExpression node) {
for (GeneratorExpressionLoop loop : node.getLoops()) {
accept(loop.getIterator());
accept(loop.getIteratedObject());
}
if (node.getFilter() != null) {
accept(node.getFilter());
}
accept(node.getResult());
}
protected void visitNumber(NumberLiteral node) {
}
protected void visitString(StringLiteral node) {
}
protected void visitThis(AstNode node) {
}
protected void visitTrue(AstNode node) {
}
protected void visitFalse(AstNode node) {
}
protected void visitNull(AstNode node) {
}
protected void visitEmpty(EmptyStatement node) {
}
protected void visitEmpty(EmptyExpression node) {
}
protected void visitName(Name node) {
}
protected void visitRegexp(RegExpLiteral node) {
}
protected void visitArrayLiteral(ArrayLiteral node) {
for (AstNode element : node.getElements()) {
accept(element);
}
}
protected void visitObjectLiteral(ObjectLiteral node) {
if (node.getElements() != null) {
for (ObjectProperty property : node.getElements()) {
visitObjectProperty(property);
}
}
}
protected void visitObjectProperty(ObjectProperty node) {
accept(node.getLeft());
accept(node.getRight());
}
protected void visitFunction(FunctionNode node) {
if (node.getFunctionName() != null) {
accept(node.getFunctionName());
}
for (AstNode param : node.getParams()) {
accept(param);
}
accept(node.getBody());
}
protected void visitLet(LetNode node) {
accept(node.getVariables());
accept(node.getBody());
}
protected void visitParenthesized(ParenthesizedExpression node) {
accept(node.getExpression());
}
protected void visitUnary(UnaryExpression node) {
accept(node.getOperand());
}
protected void visitInfix(InfixExpression node) {
accept(node.getLeft());
accept(node.getRight());
}
}

View File

@ -24,32 +24,12 @@ import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstRoot;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.templating.TemplatingAstTransformer;
import org.teavm.backend.javascript.templating.TemplatingAstWriter;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.vm.RenderingException;
public class RuntimeRenderer {
private static final String STRING_CLASS = String.class.getName();
private static final String THREAD_CLASS = Thread.class.getName();
private static final String STE_CLASS = StackTraceElement.class.getName();
private static final MethodDescriptor STRING_INTERN_METHOD = new MethodDescriptor("intern", String.class);
private static final MethodDescriptor CURRENT_THREAD_METHOD = new MethodDescriptor("currentThread",
Thread.class);
private static final MethodReference STACK_TRACE_ELEM_INIT = new MethodReference(StackTraceElement.class,
"<init>", String.class, String.class, String.class, int.class, void.class);
private static final MethodReference SET_STACK_TRACE_METHOD = new MethodReference(Throwable.class,
"setStackTrace", StackTraceElement[].class, void.class);
private static final MethodReference AIOOBE_INIT_METHOD = new MethodReference(ArrayIndexOutOfBoundsException.class,
"<init>", void.class);
private static final MethodReference CCE_INIT_METHOD = new MethodReference(ClassCastException.class,
"<init>", void.class);
private final ClassReaderSource classSource;
private final SourceWriter writer;
@ -61,13 +41,7 @@ public class RuntimeRenderer {
public void renderRuntime() throws RenderingException {
try {
renderHandWrittenRuntime("runtime.js");
renderRuntimeThrowablecls();
renderRuntimeIntern();
renderRuntimeThreads();
renderCreateStackTraceElement();
renderSetStackTrace();
renderThrowAIOOBE();
renderThrowCCE();
renderHandWrittenRuntime("intern.js");
} catch (IOException e) {
throw new RenderingException("IO error", e);
}
@ -76,6 +50,7 @@ public class RuntimeRenderer {
public void renderHandWrittenRuntime(String name) throws IOException {
AstRoot ast = parseRuntime(name);
ast.visit(new StringConstantElimination());
new TemplatingAstTransformer(classSource).visit(ast);
var astWriter = new TemplatingAstWriter(writer);
astWriter.hoist(ast);
astWriter.print(ast);
@ -93,121 +68,4 @@ public class RuntimeRenderer {
return factory.parse(reader, null, 0);
}
}
private void renderRuntimeIntern() throws IOException {
if (!needInternMethod()) {
writer.append("function $rt_intern(str) {").indent().softNewLine();
writer.append("return str;").softNewLine();
writer.outdent().append("}").softNewLine();
} else {
renderHandWrittenRuntime("intern.js");
}
}
private boolean needInternMethod() {
ClassReader cls = classSource.get(STRING_CLASS);
if (cls == null) {
return false;
}
MethodReader method = cls.getMethod(STRING_INTERN_METHOD);
return method != null && method.hasModifier(ElementModifier.NATIVE);
}
private void renderRuntimeThrowablecls() throws IOException {
writer.append("function $rt_stecls()").ws().append("{").indent().softNewLine();
writer.append("return ");
if (classSource.get(STE_CLASS) != null) {
writer.appendClass(STE_CLASS);
} else {
writer.appendClass("java.lang.Object");
}
writer.append(";").softNewLine().outdent().append("}").newLine();
}
private void renderRuntimeThreads() throws IOException {
ClassReader threadCls = classSource.get(THREAD_CLASS);
MethodReader currentThreadMethod = threadCls != null ? threadCls.getMethod(CURRENT_THREAD_METHOD) : null;
boolean threadUsed = currentThreadMethod != null && currentThreadMethod.getProgram() != null;
writer.append("function $rt_getThread()").ws().append("{").indent().softNewLine();
if (threadUsed) {
writer.append("return ").appendMethodBody(Thread.class, "currentThread", Thread.class).append("();")
.softNewLine();
} else {
writer.append("return null;").softNewLine();
}
writer.outdent().append("}").newLine();
writer.append("function $rt_setThread(t)").ws().append("{").indent().softNewLine();
if (threadUsed) {
writer.append("return ").appendMethodBody(Thread.class, "setCurrentThread", Thread.class, void.class)
.append("(t);").softNewLine();
}
writer.outdent().append("}").newLine();
}
private void renderCreateStackTraceElement() throws IOException {
ClassReader cls = classSource.get(STACK_TRACE_ELEM_INIT.getClassName());
MethodReader stackTraceElemInit = cls != null ? cls.getMethod(STACK_TRACE_ELEM_INIT.getDescriptor()) : null;
boolean supported = stackTraceElemInit != null && stackTraceElemInit.getProgram() != null;
writer.append("function $rt_createStackElement(")
.append("className,").ws()
.append("methodName,").ws()
.append("fileName,").ws()
.append("lineNumber)").ws().append("{").indent().softNewLine();
writer.append("return ");
if (supported) {
writer.appendInit(STACK_TRACE_ELEM_INIT);
writer.append("(className,").ws()
.append("methodName,").ws()
.append("fileName,").ws()
.append("lineNumber)");
} else {
writer.append("null");
}
writer.append(";").softNewLine();
writer.outdent().append("}").newLine();
}
private void renderSetStackTrace() throws IOException {
ClassReader cls = classSource.get(SET_STACK_TRACE_METHOD.getClassName());
MethodReader setStackTrace = cls != null ? cls.getMethod(SET_STACK_TRACE_METHOD.getDescriptor()) : null;
boolean supported = setStackTrace != null && setStackTrace.getProgram() != null;
writer.append("function $rt_setStack(e,").ws().append("stack)").ws().append("{").indent().softNewLine();
if (supported) {
writer.appendMethodBody(SET_STACK_TRACE_METHOD);
writer.append("(e,").ws().append("stack);").softNewLine();
}
writer.outdent().append("}").newLine();
}
private void renderThrowAIOOBE() throws IOException {
writer.append("function $rt_throwAIOOBE()").ws().append("{").indent().softNewLine();
ClassReader cls = classSource.get(AIOOBE_INIT_METHOD.getClassName());
if (cls != null) {
MethodReader method = cls.getMethod(AIOOBE_INIT_METHOD.getDescriptor());
if (method != null && !method.hasModifier(ElementModifier.ABSTRACT)) {
writer.append("$rt_throw(").appendInit(AIOOBE_INIT_METHOD).append("());").softNewLine();
}
}
writer.outdent().append("}").newLine();
}
private void renderThrowCCE() throws IOException {
writer.append("function $rt_throwCCE()").ws().append("{").indent().softNewLine();
ClassReader cls = classSource.get(CCE_INIT_METHOD.getClassName());
if (cls != null) {
MethodReader method = cls.getMethod(CCE_INIT_METHOD.getDescriptor());
if (method != null && !method.hasModifier(ElementModifier.ABSTRACT)) {
writer.append("$rt_throw(").appendInit(CCE_INIT_METHOD).append("());").softNewLine();
}
}
writer.outdent().append("}").newLine();
}
}

View File

@ -0,0 +1,180 @@
/*
* 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.backend.javascript.templating;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.Block;
import org.mozilla.javascript.ast.ConditionalExpression;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.IfStatement;
import org.mozilla.javascript.ast.InfixExpression;
import org.mozilla.javascript.ast.KeywordLiteral;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.StringLiteral;
import org.mozilla.javascript.ast.UnaryExpression;
import org.teavm.backend.javascript.ast.AstVisitor;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
public class TemplatingAstTransformer extends AstVisitor {
private ClassReaderSource classes;
public TemplatingAstTransformer(ClassReaderSource classes) {
this.classes = classes;
}
@Override
public void visit(Block node) {
super.visit(node);
if (node.getFirstChild() == null) {
replaceWith(null);
} else if (node.getFirstChild() == node.getLastChild()) {
replaceWith((AstNode) node.getFirstChild());
}
}
@Override
public void visit(IfStatement node) {
super.visit(node);
if (node.getCondition().getType() == Token.TRUE) {
replaceWith(node.getThenPart());
} else if (node.getCondition().getType() == Token.FALSE) {
replaceWith(node.getElsePart());
}
}
@Override
public void visit(ConditionalExpression node) {
super.visit(node);
if (node.getTestExpression().getType() == Token.TRUE) {
replaceWith(node.getTrueExpression());
} else if (node.getTestExpression().getType() == Token.FALSE) {
replaceWith(node.getFalseExpression());
}
}
@Override
public void visit(InfixExpression node) {
super.visit(node);
if (node.getType() == Token.AND) {
if (node.getLeft().getType() == Token.FALSE) {
replaceWith(node.getLeft());
} else if (node.getRight().getType() == Token.FALSE) {
replaceWith(node.getRight());
} else if (node.getLeft().getType() == Token.TRUE) {
replaceWith(node.getRight());
} else if (node.getRight().getType() == Token.TRUE) {
replaceWith(node.getLeft());
}
} else if (node.getType() == Token.OR) {
if (node.getLeft().getType() == Token.TRUE) {
replaceWith(node.getLeft());
} else if (node.getRight().getType() == Token.TRUE) {
replaceWith(node.getRight());
} else if (node.getLeft().getType() == Token.FALSE) {
replaceWith(node.getRight());
} else if (node.getRight().getType() == Token.FALSE) {
replaceWith(node.getLeft());
}
}
}
@Override
public void visit(UnaryExpression node) {
super.visit(node);
if (node.getType() == Token.NOT) {
if (node.getOperand().getType() == Token.TRUE) {
node.getOperand().setType(Token.FALSE);
} else if (node.getOperand().getType() == Token.FALSE) {
node.getOperand().setType(Token.TRUE);
}
}
}
@Override
public void visit(FunctionCall node) {
super.visit(node);
if (node.getTarget() instanceof Name) {
var name = (Name) node.getTarget();
if (name.getDefiningScope() == null) {
tryIntrinsicName(node, name.getIdentifier());
}
}
}
private void tryIntrinsicName(FunctionCall node, String identifier) {
switch (identifier) {
case "teavm_javaClassExists":
javaClassExists(node);
break;
case "teavm_javaMethodExists":
javaMethodExists(node);
break;
case "teavm_javaConstructorExists":
javaConstructorExists(node);
break;
}
}
private void javaClassExists(FunctionCall node) {
if (node.getArguments().size() != 1) {
return;
}
var classArg = node.getArguments().get(0);
if (!(classArg instanceof StringLiteral)) {
return;
}
var className = ((StringLiteral) classArg).getValue();
var exists = classes.get(className) != null;
replaceWith(new KeywordLiteral(0, 0, exists ? Token.TRUE : Token.FALSE));
}
private void javaMethodExists(FunctionCall node) {
if (node.getArguments().size() != 2) {
return;
}
var classArg = node.getArguments().get(0);
var methodArg = node.getArguments().get(1);
if (!(classArg instanceof StringLiteral) || !(methodArg instanceof StringLiteral)) {
return;
}
var className = ((StringLiteral) classArg).getValue();
var methodName = ((StringLiteral) methodArg).getValue();
var method = classes.resolveImplementation(new MethodReference(className, MethodDescriptor.parse(methodName)));
var exists = method != null && (method.getProgram() != null || method.hasModifier(ElementModifier.NATIVE));
replaceWith(new KeywordLiteral(0, 0, exists ? Token.TRUE : Token.FALSE));
}
private void javaConstructorExists(FunctionCall node) {
if (node.getArguments().size() != 2) {
return;
}
var classArg = node.getArguments().get(0);
var methodArg = node.getArguments().get(1);
if (!(classArg instanceof StringLiteral) || !(methodArg instanceof StringLiteral)) {
return;
}
var className = ((StringLiteral) classArg).getValue();
var methodName = ((StringLiteral) methodArg).getValue();
var method = classes.resolveImplementation(new MethodReference(className, "<init>",
MethodDescriptor.parseSignature(methodName)));
var exists = method != null && (method.getProgram() != null || method.hasModifier(ElementModifier.NATIVE));
replaceWith(new KeywordLiteral(0, 0, exists ? Token.TRUE : Token.FALSE));
}
}

View File

@ -15,37 +15,44 @@
*/
"use strict";
var $rt_intern = function() {
var map = Object.create(null);
var $rt_intern
if (teavm_javaMethodExists("java.lang.String", "intern()Ljava/lang/String;")) {
$rt_intern = function() {
var map = Object.create(null);
var get;
if (typeof WeakRef !== 'undefined') {
var registry = new FinalizationRegistry(value => {
delete map[value];
});
var get;
if (typeof WeakRef !== 'undefined') {
var registry = new FinalizationRegistry(value => {
delete map[value];
});
get = function(str) {
var key = $rt_ustr(str);
var ref = map[key];
var result = typeof ref !== 'undefined' ? ref.deref() : void 0;
if (typeof result !== 'object') {
result = str;
map[key] = new WeakRef(result);
registry.register(result, key);
get = function (str) {
var key = $rt_ustr(str);
var ref = map[key];
var result = typeof ref !== 'undefined' ? ref.deref() : void 0;
if (typeof result !== 'object') {
result = str;
map[key] = new WeakRef(result);
registry.register(result, key);
}
return result;
}
return result;
}
} else {
get = function(str) {
var key = $rt_ustr(str);
var result = map[key];
if (typeof result !== 'object') {
result = str;
map[key] = result;
} else {
get = function (str) {
var key = $rt_ustr(str);
var result = map[key];
if (typeof result !== 'object') {
result = str;
map[key] = result;
}
return result;
}
return result;
}
return get;
}();
} else {
$rt_intern = function(str) {
return str;
}
return get;
}();
}

View File

@ -967,4 +967,50 @@ function $rt_throwableMessage(t) {
}
function $rt_throwableCause(t) {
return teavm_javaMethod("java.lang.Throwable", "getCause()Ljava/lang/Throwable;")(t);
}
function $rt_stecls() {
if (teavm_javaClassExists("java.lang.StackTraceElement")) {
return teavm_javaClass("java.lang.StackTraceElement");
} else {
return $rt_objcls();
}
}
function $rt_throwAIOOBE() {
if (teavm_javaConstructorExists("java.lang.ArrayIndexOutOfBoundsException", "()V")) {
$rt_throw(teavm_javaConstructor("java.lang.ArrayIndexOutOfBoundsException", "()V")());
} else {
$rt_throw($rt_createException($rt_str("")));
}
}
function $rt_throwCCE() {
if (teavm_javaConstructorExists("java.lang.ClassCastException", "()V")) {
$rt_throw(teavm_javaConstructor("java.lang.ClassCastException", "()V")());
} else {
$rt_throw($rt_createException($rt_str("")));
}
}
function $rt_getThread() {
if (teavm_javaMethodExists("java.lang.Thread", "currentThread()Ljava/lang/Thread;")) {
return teavm_javaMethod("java.lang.Thread", "currentThread()Ljava/lang/Thread;")();
}
}
function $rt_setThread(t) {
if (teavm_javaMethodExists("java.lang.Thread", "setCurrentThread(Ljava/lang/Thread;)V")) {
return teavm_javaMethod("java.lang.Thread", "setCurrentThread(Ljava/lang/Thread;)V")(t);
}
}
function $rt_createStackElement(className, methodName, fileName, lineNumber) {
if (teavm_javaConstructorExists("java.lang.StackTraceElement",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V")) {
return teavm_javaConstructor("java.lang.StackTraceElement",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V")(className, methodName, fileName, lineNumber);
} else {
return null;
}
}
function $rt_setStack(e, stack) {
if (teavm_javaMethodExists("java.lang.Throwable", "setStackTrace([Ljava/lang/StackTraceElement;)V")) {
teavm_javaMethod("java.lang.Throwable", "setStackTrace([Ljava/lang/StackTraceElement;)V")(e, stack);
}
}