js: refactor and simplify AstWriter, properly fix case with variable in catch block

This commit is contained in:
Alexey Andreev 2024-03-06 20:24:35 +01:00
parent e4452152b7
commit ccfe19994b
8 changed files with 111 additions and 147 deletions

View File

@ -15,10 +15,9 @@
*/
package org.teavm.backend.javascript.rendering;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -97,9 +96,9 @@ public class AstWriter {
protected boolean rootScope = true;
private Set<String> aliases = new HashSet<>();
private Function<String, NameEmitter> globalNameWriter;
public final Map<String, Scope> currentScopes = new HashMap<>();
protected final Set<Scope> topLevelScopes = new HashSet<>();
private Set<String> nonTopLevels = new HashSet<>();
private boolean inFunction;
private int scopeLevel;
public AstWriter(SourceWriter writer, Function<String, NameEmitter> globalNameWriter) {
this.writer = writer;
@ -115,13 +114,13 @@ public class AstWriter {
return;
}
if (aliases.add(name)) {
nameMap.put(name, p -> writer.append(name));
nameMap.put(name, (w, p) -> w.append(name));
return;
}
for (int i = 0;; ++i) {
String alias = name + "_" + i;
if (aliases.add(alias)) {
nameMap.put(name, p -> writer.append(alias));
nameMap.put(name, (w, p) -> w.append(alias));
return;
}
}
@ -202,7 +201,7 @@ public class AstWriter {
break;
case Token.THIS:
if (nameMap.containsKey("this")) {
nameMap.get("this").emit(precedence);
nameMap.get("this").emit(writer, precedence);
} else {
writer.append("this");
}
@ -324,7 +323,7 @@ public class AstWriter {
}
private void print(Scope node) {
var scope = enterScope(node);
var scope = enterScope(node, false);
writer.append('{').softNewLine().indent();
for (Node child = node.getFirstChild(); child != null; child = child.getNext()) {
if (!print((AstNode) child)) {
@ -332,7 +331,7 @@ public class AstWriter {
}
}
writer.outdent().append('}');
leaveScope(scope, node);
leaveScope(scope);
}
private void print(LabeledStatement node) {
@ -374,17 +373,17 @@ public class AstWriter {
}
private void print(DoLoop node) {
var scope = enterScope(node);
var scope = enterScope(node, false);
writer.append("do ").ws();
print(node.getBody());
writer.append("while").ws().append('(');
print(node.getCondition());
writer.append(");");
leaveScope(scope, node);
leaveScope(scope);
}
private void print(ForInLoop node) {
var scope = enterScope(node);
var scope = enterScope(node, false);
writer.append("for");
if (node.isForEach()) {
writer.append(" each");
@ -395,11 +394,11 @@ public class AstWriter {
print(node.getIteratedObject());
writer.append(')').ws();
print(node.getBody());
leaveScope(scope, node);
leaveScope(scope);
}
private void print(ForLoop node) {
var scope = enterScope(node);
var scope = enterScope(node, false);
writer.append("for").ws().append('(');
print(node.getInitializer());
writer.append(';');
@ -408,16 +407,16 @@ public class AstWriter {
print(node.getIncrement());
writer.append(')').ws();
print(node.getBody());
leaveScope(scope, node);
leaveScope(scope);
}
private void print(WhileLoop node) {
var scope = enterScope(node);
var scope = enterScope(node, false);
writer.append("while").ws().append('(');
print(node.getCondition());
writer.append(')').ws();
print(node.getBody());
leaveScope(scope, node);
leaveScope(scope);
}
private void print(IfStatement node) {
@ -458,8 +457,10 @@ public class AstWriter {
private void print(TryStatement node) {
writer.append("try ");
print(node.getTryBlock());
for (CatchClause cc : node.getCatchClauses()) {
for (var cc : node.getCatchClauses()) {
writer.ws().append("catch").ws().append('(');
var scope = enterScope(false);
includeInScope(scope, cc.getVarName().getIdentifier());
print(cc.getVarName());
if (cc.getCatchCondition() != null) {
writer.append(" if ");
@ -467,6 +468,7 @@ public class AstWriter {
}
writer.append(')');
print(cc.getBody());
leaveScope(scope);
}
if (node.getFinallyBlock() != null) {
writer.ws().append("finally ");
@ -475,10 +477,9 @@ public class AstWriter {
}
private boolean print(VariableDeclaration node) {
if (isTopLevel() && node.getVariables().get(0).getTarget() instanceof Name) {
if (isTopLevelOutput() && node.getVariables().get(0).getTarget() instanceof Name) {
var name = (Name) node.getVariables().get(0).getTarget();
var definingScope = scopeOfId(name.getIdentifier());
if (definingScope == null || topLevelScopes.contains(definingScope)) {
if (isTopLevelIdentifier(name.getIdentifier())) {
printTopLevel(node);
return true;
}
@ -647,7 +648,7 @@ public class AstWriter {
}
private void print(ArrayComprehension node) {
var scope = enterScope(node);
var scope = enterScope(node, false);
writer.append("[");
for (ArrayComprehensionLoop loop : node.getLoops()) {
writer.append("for").ws().append("(");
@ -663,11 +664,11 @@ public class AstWriter {
}
print(node.getResult());
writer.append(']');
leaveScope(scope, node);
leaveScope(scope);
}
private void print(GeneratorExpression node) {
var scope = enterScope(node);
var scope = enterScope(node, false);
writer.append("(");
for (GeneratorExpressionLoop loop : node.getLoops()) {
writer.append("for").ws().append("(");
@ -683,7 +684,7 @@ public class AstWriter {
}
print(node.getResult());
writer.append(')');
leaveScope(scope, node);
leaveScope(scope);
}
private void print(NumberLiteral node) {
@ -697,17 +698,16 @@ public class AstWriter {
}
public void print(Name node, int precedence) {
var definingScope = scopeOfId(node.getIdentifier());
if (rootScope && definingScope == null) {
if (rootScope && isTopLevelIdentifier(node.getIdentifier())) {
var alias = nameMap.get(node.getIdentifier());
if (alias == null) {
if (globalNameWriter != null) {
alias = globalNameWriter.apply(node.getIdentifier());
} else {
alias = prec -> writer.append(node.getIdentifier());
alias = (w, prec) -> w.append(node.getIdentifier());
}
}
alias.emit(precedence);
alias.emit(writer, precedence);
} else {
writer.append(node.getIdentifier());
}
@ -753,18 +753,16 @@ public class AstWriter {
protected boolean print(FunctionNode node) {
var isArrow = node.getFunctionType() == FunctionNode.ARROW_FUNCTION;
if (isTopLevel() && !isArrow && node.getFunctionName() != null) {
var definingScope = scopeOfId(node.getFunctionName().getIdentifier());
if (definingScope == null || topLevelScopes.contains(definingScope)) {
printTopLevel(node);
return true;
}
if (isTopLevelOutput() && !isArrow && node.getFunctionName() != null
&& isTopLevelIdentifier(node.getFunctionName().getIdentifier())) {
printTopLevel(node);
return true;
}
var wasInFunction = inFunction;
inFunction = true;
var scope = enterScope(node);
var scope = enterScope(node, true);
if (!isArrow) {
currentScopes.put("arguments", node);
includeInScope(scope, "arguments");
}
if (!node.isMethod() && !isArrow) {
writer.append("function");
@ -797,7 +795,7 @@ public class AstWriter {
print(node.getBody());
}
leaveScope(scope, node);
leaveScope(scope);
inFunction = wasInFunction;
return false;
}
@ -805,25 +803,25 @@ public class AstWriter {
private void printTopLevel(FunctionNode node) {
var wasInFunction = inFunction;
inFunction = true;
var scope = enterScope(node);
currentScopes.put("arguments", node);
var scope = enterScope(node, true);
includeInScope(scope, "arguments");
writer.startFunctionDeclaration().appendFunction(node.getFunctionName().getIdentifier());
writer.append('(');
printList(node.getParams());
writer.append(')').ws();
print(node.getBody());
writer.endDeclaration();
leaveScope(scope, node);
leaveScope(scope);
inFunction = wasInFunction;
}
private void print(LetNode node) {
var scope = enterScope(node);
var scope = enterScope(node, false);
writer.append("let").ws().append('(');
printList(node.getVariables().getVariables());
writer.append(')');
print(node.getBody());
leaveScope(scope, node);
leaveScope(scope);
}
private void print(ParenthesizedExpression node, int precedence) {
@ -1021,47 +1019,45 @@ public class AstWriter {
}
}
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);
}
onEnterScope(scope);
return map;
}
protected void onEnterScope(Scope scope) {
if (isTopLevel() && !inFunction()) {
topLevelScopes.add(scope);
}
}
private void leaveScope(Map<String, Scope> backup, Scope scope) {
for (var entry : backup.entrySet()) {
if (entry.getValue() == null) {
currentScopes.remove(entry.getKey());
} else {
currentScopes.put(entry.getKey(), entry.getValue());
private Set<String> enterScope(Scope scope, boolean nesting) {
var set = enterScope(nesting);
if (scope.getSymbolTable() != null) {
for (var name : scope.getSymbolTable().keySet()) {
includeInScope(set, name);
}
}
onLeaveScope(scope);
return set;
}
protected void onLeaveScope(Scope scope) {
if (isTopLevel() && !inFunction()) {
topLevelScopes.remove(scope);
public Set<String> enterScope(boolean nesting) {
if (scopeLevel > 0 || nesting) {
++scopeLevel;
}
return new LinkedHashSet<>();
}
public final void includeInScope(Set<String> scope, String name) {
if (nonTopLevels.add(name)) {
scope.add(name);
}
}
protected Scope scopeOfId(String id) {
return currentScopes.get(id);
public void leaveScope(Set<String> backup) {
nonTopLevels.removeAll(backup);
if (scopeLevel > 0) {
--scopeLevel;
}
}
protected boolean isTopLevel() {
protected boolean isTopLevelIdentifier(String id) {
return !nonTopLevels.contains(id);
}
protected boolean isInTopLevelScope() {
return scopeLevel == 0;
}
protected boolean isTopLevelOutput() {
return false;
}
}

View File

@ -16,20 +16,13 @@
package org.teavm.backend.javascript.rendering;
import java.util.function.Function;
import org.teavm.backend.javascript.codegen.SourceWriter;
public class DefaultGlobalNameWriter implements Function<String, NameEmitter> {
private SourceWriter writer;
public DefaultGlobalNameWriter(SourceWriter writer) {
this.writer = writer;
}
@Override
public NameEmitter apply(String s) {
if (s.startsWith("$rt_") || s.startsWith("Long_") || s.equals("Long")) {
return prec -> writer.appendFunction(s);
return (w, intprec) -> w.appendFunction(s);
}
return prec -> writer.appendGlobal(s);
return (w, prec) -> w.appendGlobal(s);
}
}

View File

@ -15,6 +15,8 @@
*/
package org.teavm.backend.javascript.rendering;
import org.teavm.backend.javascript.codegen.SourceWriter;
public interface NameEmitter {
void emit(int precedence);
void emit(SourceWriter writer, int precedence);
}

View File

@ -21,7 +21,9 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstRoot;
@ -42,6 +44,7 @@ public class RuntimeRenderer {
private final ClassReaderSource classSource;
private final SourceWriter writer;
private final ClassInitializerInfo classInitializerInfo;
private final Set<String> topLevelNames = new HashSet<>();
public RuntimeRenderer(ClassReaderSource classSource, SourceWriter writer,
ClassInitializerInfo classInitializerInfo) {
@ -83,12 +86,15 @@ public class RuntimeRenderer {
ast.visit(new StringConstantElimination());
new TemplatingAstTransformer(classSource).visit(ast);
removablePartsFinder.visit(ast);
topLevelNames.addAll(ast.getSymbolTable().keySet());
return ast;
}
private void renderRuntimePart(AstRoot ast) {
var astWriter = new TemplatingAstWriter(writer, null, null, classInitializerInfo);
astWriter.hoist(ast);
var astWriter = new TemplatingAstWriter(writer, classInitializerInfo, true);
for (var name : topLevelNames) {
astWriter.declareNameEmitter(name, (w, prec) -> w.appendFunction(name));
}
astWriter.print(ast);
}

View File

@ -21,6 +21,7 @@ import java.util.function.IntFunction;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Name;
import org.teavm.backend.javascript.rendering.NameEmitter;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.model.ClassReaderSource;
@ -65,27 +66,26 @@ public class JavaScriptTemplate {
public SourceFragment build() {
var intParameters = parameters;
var nameParameters = new HashMap<String, SourceFragment>();
var nameParameters = new HashMap<String, NameEmitter>();
for (var i = 0; i < node.getParams().size(); ++i) {
var param = node.getParams().get(i);
if (param instanceof Name) {
nameParameters.put(((Name) param).getIdentifier(), intParameters.apply(i + 1));
var sourceFragment = intParameters.apply(i + 1);
nameParameters.put(((Name) param).getIdentifier(), sourceFragment::write);
}
}
var thisFragment = parameters.apply(0);
var body = node.getBody();
return (writer, precedence) -> {
var astWriter = new TemplatingAstWriter(writer, nameParameters, node, null);
var astWriter = new TemplatingAstWriter(writer, null, false);
for (var entry : nameParameters.entrySet()) {
astWriter.declareNameEmitter(entry.getKey(), entry.getValue());
}
for (var entry : fragments.entrySet()) {
astWriter.setFragment(entry.getKey(), entry.getValue());
}
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));
astWriter.declareNameEmitter("this", thisFragment::write);
}
for (var child = body.getFirstChild(); child != null; child = child.getNext()) {
astWriter.print((AstNode) child);

View File

@ -19,10 +19,8 @@ import java.util.HashMap;
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;
import org.mozilla.javascript.ast.StringLiteral;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.AstWriter;
@ -33,25 +31,14 @@ import org.teavm.model.MethodReference;
import org.teavm.model.analysis.ClassInitializerInfo;
public class TemplatingAstWriter extends AstWriter {
private Map<String, SourceFragment> names;
private Scope scope;
private Map<String, SourceFragment> fragments = new HashMap<>();
private ClassInitializerInfo classInitializerInfo;
private boolean topLevelOutput;
public TemplatingAstWriter(SourceWriter writer, Map<String, SourceFragment> names, Scope scope,
ClassInitializerInfo classInitializerInfo) {
super(writer, new DefaultGlobalNameWriter(writer));
public TemplatingAstWriter(SourceWriter writer, ClassInitializerInfo classInitializerInfo, boolean topLevelOutput) {
super(writer, new DefaultGlobalNameWriter());
this.classInitializerInfo = classInitializerInfo;
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);
}
this.topLevelOutput = topLevelOutput;
}
public void setFragment(String name, SourceFragment fragment) {
@ -62,7 +49,7 @@ public class TemplatingAstWriter extends AstWriter {
protected boolean intrinsic(FunctionCall node, int precedence) {
if (node.getTarget() instanceof Name) {
var name = (Name) node.getTarget();
if (scopeOfId(name.getIdentifier()) == null) {
if (isTopLevelIdentifier(name.getIdentifier())) {
return tryIntrinsicName(node, name.getIdentifier());
}
}
@ -164,7 +151,7 @@ public class TemplatingAstWriter extends AstWriter {
var call = (FunctionCall) node.getElement();
if (call.getTarget() instanceof Name) {
var name = (Name) call.getTarget();
if (scopeOfId(name.getIdentifier()) == null) {
if (isTopLevelIdentifier(name.getIdentifier())) {
switch (name.getIdentifier()) {
case "teavm_javaVirtualMethod":
if (writeJavaVirtualMethod(node, call)) {
@ -187,8 +174,7 @@ public class TemplatingAstWriter extends AstWriter {
public void print(PropertyGet node) {
if (node.getTarget() instanceof Name) {
var name = (Name) node.getTarget();
var scope = scopeOfId(name.getIdentifier());
if (scope == null && name.getIdentifier().equals("teavm_globals")) {
if (isTopLevelIdentifier(name.getIdentifier()) && name.getIdentifier().equals("teavm_globals")) {
var oldRootScope = rootScope;
rootScope = false;
writer.appendGlobal(node.getProperty().getIdentifier());
@ -227,26 +213,7 @@ public class TemplatingAstWriter extends AstWriter {
}
@Override
public void print(Name node, int precedence) {
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 (definingScope == null || topLevelScopes.contains(definingScope)) {
writer.appendFunction(node.getIdentifier());
return;
}
}
super.print(node, precedence);
}
@Override
protected boolean isTopLevel() {
return names == null;
public boolean isTopLevelOutput() {
return topLevelOutput;
}
}

View File

@ -35,7 +35,7 @@ public class AstWriterTest {
builder.setMinified(true);
sourceWriter = builder.build(sb);
writer = new AstWriter(sourceWriter, null);
writerWithGlobals = new AstWriter(sourceWriter, name -> prec -> sourceWriter.append("globals.").append(name));
writerWithGlobals = new AstWriter(sourceWriter, name -> (w, prec) -> w.append("globals.").append(name));
}
@Test

View File

@ -44,21 +44,21 @@ class JSBodyAstEmitter implements JSBodyEmitter {
@Override
public void emit(InjectorContext context) {
var astWriter = new AstWriter(context.getWriter(), new DefaultGlobalNameWriter(context.getWriter()));
var astWriter = new AstWriter(context.getWriter(), new DefaultGlobalNameWriter());
int paramIndex = 0;
if (!isStatic) {
int index = paramIndex++;
astWriter.declareNameEmitter("this", prec -> context.writeExpr(context.getArgument(index),
astWriter.declareNameEmitter("this", (w, prec) -> context.writeExpr(context.getArgument(index),
convert(prec)));
}
for (int i = 0; i < parameterNames.length; ++i) {
int index = paramIndex++;
astWriter.declareNameEmitter(parameterNames[i],
prec -> context.writeExpr(context.getArgument(index), convert(prec)));
(w, prec) -> context.writeExpr(context.getArgument(index), convert(prec)));
}
for (var importInfo : imports) {
astWriter.declareNameEmitter(importInfo.alias,
prec -> context.getWriter().appendFunction(context.importModule(importInfo.fromModule)));
(w, prec) -> context.getWriter().appendFunction(context.importModule(importInfo.fromModule)));
}
astWriter.hoist(rootAst);
astWriter.print(ast, convert(context.getPrecedence()));
@ -148,19 +148,19 @@ class JSBodyAstEmitter implements JSBodyEmitter {
@Override
public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) {
var astWriter = new AstWriter(writer, new DefaultGlobalNameWriter(writer));
var astWriter = new AstWriter(writer, new DefaultGlobalNameWriter());
int paramIndex = 1;
if (!isStatic) {
int index = paramIndex++;
astWriter.declareNameEmitter("this", prec -> writer.append(context.getParameterName(index)));
astWriter.declareNameEmitter("this", (w, prec) -> w.append(context.getParameterName(index)));
}
for (var parameterName : parameterNames) {
int index = paramIndex++;
astWriter.declareNameEmitter(parameterName, prec -> writer.append(context.getParameterName(index)));
astWriter.declareNameEmitter(parameterName, (w, prec) -> w.append(context.getParameterName(index)));
}
for (var importInfo : imports) {
astWriter.declareNameEmitter(importInfo.alias,
prec -> writer.appendFunction(context.importModule(importInfo.fromModule)));
(w, prec) -> w.appendFunction(context.importModule(importInfo.fromModule)));
}
astWriter.hoist(rootAst);
if (ast instanceof Block) {