JS: store global object in a variable to avoid name clashes between generated declarations (in minified mode) and global declarations

This commit is contained in:
Alexey Andreev 2022-11-16 20:53:23 +01:00
parent 68efdddddf
commit 64ae44ee01
8 changed files with 213 additions and 114 deletions

View File

@ -464,11 +464,11 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
for (String key : controller.getEntryPoints().keySet()) { for (String key : controller.getEntryPoints().keySet()) {
writer.append("var ").append(key).append(";").softNewLine(); writer.append("var ").append(key).append(";").softNewLine();
} }
writer.append("(function()").ws().append("{").newLine(); writer.append("(function($rt_globals)").ws().append("{").newLine();
} }
private void printWrapperEnd(SourceWriter writer) throws IOException { private void printWrapperEnd(SourceWriter writer) throws IOException {
writer.append("})();").newLine(); writer.append("})(this);").newLine();
} }
private void printStats(Renderer renderer, int totalSize) { private void printStats(Renderer renderer, int totalSize) {

View File

@ -16,12 +16,12 @@
package org.teavm.backend.javascript.rendering; package org.teavm.backend.javascript.rendering;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import org.mozilla.javascript.Node; import org.mozilla.javascript.Node;
import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Token; import org.mozilla.javascript.Token;
@ -92,13 +92,16 @@ public class AstWriter {
public static final int PRECEDENCE_COMMA = 18; public static final int PRECEDENCE_COMMA = 18;
private SourceWriter writer; private SourceWriter writer;
private Map<String, NameEmitter> nameMap = new HashMap<>(); private Map<String, NameEmitter> nameMap = new HashMap<>();
private boolean rootScope = true;
private Set<String> aliases = new HashSet<>(); private Set<String> aliases = new HashSet<>();
private Function<String, NameEmitter> globalNameWriter;
public AstWriter(SourceWriter writer) { public AstWriter(SourceWriter writer, Function<String, NameEmitter> globalNameWriter) {
this.writer = writer; this.writer = writer;
this.globalNameWriter = globalNameWriter;
} }
private void declareName(String name) { public void declareName(String name) {
if (nameMap.containsKey(name)) { if (nameMap.containsKey(name)) {
return; return;
} }
@ -124,14 +127,25 @@ public class AstWriter {
} }
public void hoist(AstNode node) { public void hoist(AstNode node) {
declareName("arguments");
node.visit(n -> { node.visit(n -> {
if (n instanceof Scope) { if (n instanceof Scope) {
Scope scope = (Scope) n; var scope = (Scope) n;
if (scope.getSymbolTable() != null) { if (scope.getSymbolTable() != null) {
for (String name : scope.getSymbolTable().keySet()) { for (var name : scope.getSymbolTable().keySet()) {
declareName(name); declareName(name);
} }
} }
} else if (n instanceof CatchClause) {
var clause = (CatchClause) n;
var name = clause.getVarName().getIdentifier();
declareName(name);
} else if (n instanceof VariableInitializer) {
var initializer = (VariableInitializer) n;
if (initializer.getTarget() instanceof Name) {
var id = ((Name) initializer.getTarget()).getIdentifier();
declareName(id);
}
} }
return true; return true;
}); });
@ -488,10 +502,10 @@ public class AstWriter {
private void print(PropertyGet node) throws IOException { private void print(PropertyGet node) throws IOException {
print(node.getLeft(), PRECEDENCE_MEMBER); print(node.getLeft(), PRECEDENCE_MEMBER);
writer.append('.'); writer.append('.');
Map<String, NameEmitter> oldNameMap = nameMap; var oldRootScope = rootScope;
nameMap = Collections.emptyMap(); rootScope = false;
print(node.getRight()); print(node.getRight());
nameMap = oldNameMap; rootScope = oldRootScope;
} }
private void print(FunctionCall node, int precedence) throws IOException { private void print(FunctionCall node, int precedence) throws IOException {
@ -627,11 +641,19 @@ public class AstWriter {
} }
private void print(Name node, int precedence) throws IOException { private void print(Name node, int precedence) throws IOException {
NameEmitter alias = nameMap.get(node.getIdentifier()); if (rootScope) {
if (alias == null) { var alias = nameMap.get(node.getIdentifier());
alias = prec -> writer.append(node.getIdentifier()); if (alias == null) {
if (globalNameWriter != null) {
alias = globalNameWriter.apply(node.getIdentifier());
} else {
alias = prec -> writer.append(node.getIdentifier());
}
}
alias.emit(precedence);
} else {
writer.append(node.getIdentifier());
} }
alias.emit(precedence);
} }
private void print(RegExpLiteral node) throws IOException { private void print(RegExpLiteral node) throws IOException {
@ -662,10 +684,10 @@ public class AstWriter {
} else if (node.isSetterMethod()) { } else if (node.isSetterMethod()) {
writer.append("set "); writer.append("set ");
} }
Map<String, NameEmitter> oldNameMap = nameMap; var oldRootScope = rootScope;
nameMap = Collections.emptyMap(); rootScope = false;
print(node.getLeft()); print(node.getLeft());
nameMap = oldNameMap; rootScope = oldRootScope;
if (!node.isMethod()) { if (!node.isMethod()) {
writer.ws().append(':').ws(); writer.ws().append(':').ws();
} }

View File

@ -0,0 +1,35 @@
/*
* Copyright 2022 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.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.append(s);
}
return prec -> writer.append("$rt_globals").append('.').append(s);
}
}

View File

@ -87,7 +87,7 @@ public class RuntimeRenderer {
AstRoot ast = parseRuntime(name); AstRoot ast = parseRuntime(name);
ast.visit(new StringConstantElimination()); ast.visit(new StringConstantElimination());
new RuntimeAstTransformer(writer.getNaming()).accept(ast); new RuntimeAstTransformer(writer.getNaming()).accept(ast);
AstWriter astWriter = new AstWriter(writer); var astWriter = new AstWriter(writer, new DefaultGlobalNameWriter(writer));
astWriter.hoist(ast); astWriter.hoist(ast);
astWriter.print(ast); astWriter.print(ast);
} }

View File

@ -15,14 +15,12 @@
*/ */
package org.teavm.backend.javascript.rendering; package org.teavm.backend.javascript.rendering;
import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import org.junit.Test; import org.junit.Test;
import org.mozilla.javascript.CompilerEnvirons; import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context; import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstRoot;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.codegen.SourceWriterBuilder; import org.teavm.backend.javascript.codegen.SourceWriterBuilder;
@ -30,227 +28,269 @@ public class AstWriterTest {
private StringBuilder sb = new StringBuilder(); private StringBuilder sb = new StringBuilder();
private SourceWriter sourceWriter; private SourceWriter sourceWriter;
private AstWriter writer; private AstWriter writer;
private AstWriter writerWithGlobals;
public AstWriterTest() { public AstWriterTest() {
SourceWriterBuilder builder = new SourceWriterBuilder(null); var builder = new SourceWriterBuilder(null);
builder.setMinified(true); builder.setMinified(true);
sourceWriter = builder.build(sb); sourceWriter = builder.build(sb);
writer = new AstWriter(sourceWriter); writer = new AstWriter(sourceWriter, null);
writerWithGlobals = new AstWriter(sourceWriter, name -> prec -> sourceWriter.append("globals.").append(name));
} }
@Test @Test
public void writesReturn() throws IOException { public void writesReturn() throws IOException {
assertThat(transform("return x;"), is("return x;")); assertEquals("return x;", transform("return x;"));
} }
@Test @Test
public void writesEmptyReturn() throws IOException { public void writesEmptyReturn() throws IOException {
assertThat(transform("return;"), is("return;")); assertEquals("return;", transform("return;"));
} }
@Test @Test
public void writesThrow() throws IOException { public void writesThrow() throws IOException {
assertThat(transform("throw x;"), is("throw x;")); assertEquals("throw x;", transform("throw x;"));
} }
@Test @Test
public void writesBreak() throws IOException { public void writesBreak() throws IOException {
assertThat(transform("a: while (true) { break a; }"), is("a:while(true){break a;}")); assertEquals("a:while(true){break a;}", transform("a: while (true) { break a; }"));
} }
@Test @Test
public void writesEmptyBreak() throws IOException { public void writesEmptyBreak() throws IOException {
assertThat(transform("while(true) { break; }"), is("while(true){break;}")); assertEquals("while(true){break;}", transform("while(true) { break; }"));
} }
@Test @Test
public void writesContinue() throws IOException { public void writesContinue() throws IOException {
assertThat(transform("a: while (true) { continue a; }"), is("a:while(true){continue a;}")); assertEquals("a:while(true){continue a;}", transform("a: while (true) { continue a; }"));
} }
@Test @Test
public void writesEmptyContinue() throws IOException { public void writesEmptyContinue() throws IOException {
assertThat(transform("while(true) { continue; }"), is("while(true){continue;}")); assertEquals("while(true){continue;}", transform("while(true) { continue; }"));
} }
@Test @Test
public void writesBlock() throws IOException { public void writesBlock() throws IOException {
assertThat(transform("{ foo(); bar(); }"), is("{foo();bar();}")); assertEquals("{foo();bar();}", transform("{ foo(); bar(); }"));
} }
@Test @Test
public void writesTryCatch() throws IOException { public void writesTryCatch() throws IOException {
assertThat(transform("try { foo(); } catch (e) { alert(e); }"), is("try {foo();}catch(e){alert(e);}")); assertEquals("try {foo();}catch(e){alert(e);}", transform("try { foo(); } catch (e) { alert(e); }"));
assertThat(transform("try { foo(); } finally { close(); }"), is("try {foo();}finally {close();}")); assertEquals("try {foo();}finally {close();}", transform("try { foo(); } finally { close(); }"));
} }
@Test @Test
public void writesFor() throws IOException { public void writesFor() throws IOException {
assertThat(transform("for (var i = 0; i < array.length; ++i,++j) foo(array[i]);"), assertEquals(
is("for(var i=0;i<array.length;++i,++j)foo(array[i]);")); "for(var i=0;i<array.length;++i,++j)foo(array[i]);",
transform("for (var i = 0; i < array.length; ++i,++j) foo(array[i]);")
);
} }
@Test @Test
public void writesEmptyFor() throws IOException { public void writesEmptyFor() throws IOException {
assertThat(transform("for (;;) foo();"), is("for(;;)foo();")); assertEquals("for(;;)foo();", transform("for (;;) foo();"));
} }
@Test @Test
public void writesForIn() throws IOException { public void writesForIn() throws IOException {
assertThat(transform("for (var property in window) alert(property);"), assertEquals(
is("for(var property in window)alert(property);")); "for(var property in window)alert(property);",
transform("for (var property in window) alert(property);")
);
} }
@Test @Test
public void writesWhile() throws IOException { public void writesWhile() throws IOException {
assertThat(transform("while (shouldProceed()) proceed();"), is("while(shouldProceed())proceed();")); assertEquals("while(shouldProceed())proceed();", transform("while (shouldProceed()) proceed();"));
} }
@Test @Test
public void writesDoWhile() throws IOException { public void writesDoWhile() throws IOException {
assertThat(transform("do proceed(); while(shouldRepeat());"), is("do proceed();while(shouldRepeat());")); assertEquals("do proceed();while(shouldRepeat());", transform("do proceed(); while(shouldRepeat());"));
} }
@Test @Test
public void writesIfElse() throws IOException { public void writesIfElse() throws IOException {
assertThat(transform("if (test()) performTrue(); else performFalse();"), assertEquals(
is("if(test())performTrue();else performFalse();")); "if(test())performTrue();else performFalse();",
transform("if (test()) performTrue(); else performFalse();")
);
} }
@Test @Test
public void writesIf() throws IOException { public void writesIf() throws IOException {
assertThat(transform("if (shouldPerform()) perform();"), is("if(shouldPerform())perform();")); assertEquals("if(shouldPerform())perform();", transform("if (shouldPerform()) perform();"));
} }
@Test @Test
public void writesSwitch() throws IOException { public void writesSwitch() throws IOException {
assertThat(transform("switch (c) { " assertEquals(
"switch(c){case '.':case '?':matchAny();break;case '*':matchSequence();break;"
+ "default:matchChar(c);break;}",
transform("switch (c) { "
+ "case '.': case '?': matchAny(); break; " + "case '.': case '?': matchAny(); break; "
+ "case '*': matchSequence(); break;" + "case '*': matchSequence(); break;"
+ "default: matchChar(c); break; } "), + "default: matchChar(c); break; } ")
is("switch(c){case '.':case '?':matchAny();break;case '*':matchSequence();break;" );
+ "default:matchChar(c);break;}"));
} }
@Test @Test
public void writesLet() throws IOException { public void writesLet() throws IOException {
assertThat(transform("let x = 1; alert(x);"), is("let x=1;alert(x);")); assertEquals("let x=1;alert(x);", transform("let x = 1; alert(x);"));
assertThat(transform("let x = 1, y; alert(x,y);"), is("let x=1,y;alert(x,y);")); assertEquals("let x=1,y;alert(x,y);", transform("let x = 1, y; alert(x,y);"));
} }
@Test @Test
public void writesConst() throws IOException { public void writesConst() throws IOException {
assertThat(transform("const x = 1,y = 2; alert(x,y);"), is("const x=1,y=2;alert(x,y);")); assertEquals("const xx=1,yy=2;alert(xx,yy);", transform("const xx = 1,yy = 2; alert(xx,yy);"));
} }
@Test @Test
public void writesElementGet() throws IOException { public void writesElementGet() throws IOException {
assertThat(transform("return array[i];"), is("return array[i];")); assertEquals("return array[i];", transform("return array[i];"));
assertThat(transform("return array[i][j];"), is("return array[i][j];")); assertEquals("return array[i][j];", transform("return array[i][j];"));
assertThat(transform("return (array[i])[j];"), is("return array[i][j];")); assertEquals("return array[i][j];", transform("return (array[i])[j];"));
assertThat(transform("return (a + b)[i];"), is("return (a+b)[i];")); assertEquals("return (a+b)[i];", transform("return (a + b)[i];"));
assertThat(transform("return a + b[i];"), is("return a+b[i];")); assertEquals("return a+b[i];", transform("return a + b[i];"));
} }
@Test @Test
public void writesPropertyGet() throws IOException { public void writesPropertyGet() throws IOException {
assertThat(transform("return array.length;"), is("return array.length;")); assertEquals("return array.length;", transform("return array.length;"));
assertThat(transform("return (array).length;"), is("return array.length;")); assertEquals("return array.length;", transform("return (array).length;"));
assertThat(transform("return (x + y).toString();"), is("return (x+y).toString();")); assertEquals("return (x+y).toString();", transform("return (x + y).toString();"));
} }
@Test @Test
public void writesFunctionCall() throws IOException { public void writesFunctionCall() throws IOException {
assertThat(transform("return f(x);"), is("return f(x);")); assertEquals("return f(x);", transform("return f(x);"));
assertThat(transform("return (f)(x);"), is("return f(x);")); assertEquals("return f(x);", transform("return (f)(x);"));
assertThat(transform("return (f + g)(x);"), is("return (f+g)(x);")); assertEquals("return (f+g)(x);", transform("return (f + g)(x);"));
} }
@Test @Test
public void writesConstructorCall() throws IOException { public void writesConstructorCall() throws IOException {
assertThat(transform("return new (f + g)(x);"), is("return new (f+g)(x);")); assertEquals("return new (f+g)(x);", transform("return new (f + g)(x);"));
assertThat(transform("return new f + g(x);"), is("return new f()+g(x);")); assertEquals("return new f()+g(x);", transform("return new f + g(x);"));
assertThat(transform("return new f()(x);"), is("return new f()(x);")); assertEquals("return new f()(x);", transform("return new f()(x);"));
assertThat(transform("return (new f())(x);"), is("return new f()(x);")); assertEquals("return new f()(x);", transform("return (new f())(x);"));
assertThat(transform("return new (f())(x);"), is("return new (f())(x);")); assertEquals("return new (f())(x);", transform("return new (f())(x);"));
assertThat(transform("return new f[0](x);"), is("return new f[0](x);")); assertEquals("return new f[0](x);", transform("return new f[0](x);"));
assertThat(transform("return (new f[0](x));"), is("return new f[0](x);")); assertEquals("return new f[0](x);", transform("return (new f[0](x));"));
} }
@Test @Test
public void writesConditionalExpr() throws IOException { public void writesConditionalExpr() throws IOException {
assertThat(transform("return cond ? 1 : 0;"), is("return cond?1:0;")); assertEquals("return cond?1:0;", transform("return cond ? 1 : 0;"));
assertThat(transform("return a < b ? -1 : a > b ? 1 : 0;"), is("return a<b? -1:a>b?1:0;")); assertEquals("return a<b? -1:a>b?1:0;", transform("return a < b ? -1 : a > b ? 1 : 0;"));
assertThat(transform("return a < b ? -1 : (a > b ? 1 : 0);"), is("return a<b? -1:a>b?1:0;")); assertEquals("return a<b? -1:a>b?1:0;", transform("return a < b ? -1 : (a > b ? 1 : 0);"));
assertThat(transform("return (a < b ? x == y : x != y) ? 1 : 0;"), is("return (a<b?x==y:x!=y)?1:0;")); assertEquals("return (a<b?x==y:x!=y)?1:0;", transform("return (a < b ? x == y : x != y) ? 1 : 0;"));
assertThat(transform("return a < b ? (x > y ? x : y) : z"), is("return a<b?(x>y?x:y):z;")); assertEquals("return a<b?(x>y?x:y):z;", transform("return a < b ? (x > y ? x : y) : z"));
} }
@Test @Test
public void writesRegExp() throws IOException { public void writesRegExp() throws IOException {
assertThat(transform("return /[a-z]+/.match(text);"), is("return /[a-z]+/.match(text);")); assertEquals("return /[a-z]+/.match(text);", transform("return /[a-z]+/.match(text);"));
assertThat(transform("return /[a-z]+/ig.match(text);"), is("return /[a-z]+/ig.match(text);")); assertEquals("return /[a-z]+/ig.match(text);", transform("return /[a-z]+/ig.match(text);"));
} }
@Test @Test
public void writesArrayLiteral() throws IOException { public void writesArrayLiteral() throws IOException {
assertThat(transform("return [];"), is("return [];")); assertEquals("return [];", transform("return [];"));
assertThat(transform("return [a, b + c];"), is("return [a,b+c];")); assertEquals("return [a,b+c];", transform("return [a, b + c];"));
} }
@Test @Test
public void writesObjectLiteral() throws IOException { public void writesObjectLiteral() throws IOException {
assertThat(transform("return {};"), is("return {};")); assertEquals("return {};", transform("return {};"));
assertThat(transform("return { foo : bar };"), is("return {foo:bar};")); assertEquals("return {foo:bar};", transform("return { foo : bar };"));
assertThat(transform("return { foo : bar };"), is("return {foo:bar};")); assertEquals("return {foo:bar};", transform("return { foo : bar };"));
assertThat(transform("return { _foo : bar, get foo() { return this._foo; } };"), assertEquals(
is("return {_foo:bar,get foo(){return this._foo;}};")); "return {_foo:bar,get foo(){return this._foo;}};",
transform("return { _foo : bar, get foo() { return this._foo; } };")
);
} }
@Test @Test
public void writesFunction() throws IOException { public void writesFunction() throws IOException {
assertThat(transform("return function f(x, y) { return x + y; };"), assertEquals(
is("return function f(x,y){return x+y;};")); "return function f(x,y){return x+y;};",
transform("return function f(x, y) { return x + y; };")
);
} }
@Test @Test
public void writesUnary() throws IOException { public void writesUnary() throws IOException {
assertThat(transform("return -a;"), is("return -a;")); assertEquals("return -a;", transform("return -a;"));
assertThat(transform("return -(a + b);"), is("return -(a+b);")); assertEquals("return -(a+b);", transform("return -(a + b);"));
assertThat(transform("return -a + b;"), is("return -a+b;")); assertEquals("return -a+b;", transform("return -a + b;"));
assertThat(transform("return (-a) + b;"), is("return -a+b;")); assertEquals("return -a+b;", transform("return (-a) + b;"));
assertThat(transform("return (-f)(x);"), is("return ( -f)(x);")); assertEquals("return ( -f)(x);", transform("return (-f)(x);"));
assertThat(transform("return typeof a;"), is("return typeof a;")); assertEquals("return typeof a;", transform("return typeof a;"));
} }
@Test @Test
public void writesPostfix() throws IOException { public void writesPostfix() throws IOException {
assertThat(transform("return a++;"), is("return a++;")); assertEquals("return a++;", transform("return a++;"));
} }
@Test @Test
public void respectsPrecedence() throws IOException { public void respectsPrecedence() throws IOException {
assertThat(transform("return a + b + c;"), is("return a+b+c;")); assertEquals("return a+b+c;", transform("return a + b + c;"));
assertThat(transform("return (a + b) + c;"), is("return a+b+c;")); assertEquals("return a+b+c;", transform("return (a + b) + c;"));
assertThat(transform("return a + (b + c);"), is("return a+b+c;")); assertEquals("return a+b+c;", transform("return a + (b + c);"));
assertThat(transform("return a - b + c;"), is("return a -b+c;")); assertEquals("return a -b+c;", transform("return a - b + c;"));
assertThat(transform("return (a - b) + c;"), is("return a -b+c;")); assertEquals("return a -b+c;", transform("return (a - b) + c;"));
assertThat(transform("return a - (b + c);"), is("return a -(b+c);")); assertEquals("return a -(b+c);", transform("return a - (b + c);"));
} }
@Test @Test
public void writesDelete() throws IOException { public void writesDelete() throws IOException {
assertThat(transform("delete a.b;"), is("delete a.b;")); assertEquals("delete a.b;", transform("delete a.b;"));
}
@Test
public void writesGlobalRef() throws IOException {
assertEquals(
"function(x){return x+globals.y;}",
transformGlobals("function(x) { return x + y; }")
);
assertEquals(
"try {globals.foo();}catch(x){globals.foo(x);}",
transformGlobals("try { foo(); } catch (x) { foo(x); }")
);
assertEquals(
"for(var i=0;i<10;++i){globals.foo(i,globals.j);}",
transformGlobals("for (var i = 0; i < 10; ++i) { foo(i, j); }")
);
assertEquals(
"function(){var x=0;globals.foo(x,globals.y);}",
transformGlobals("function() { var x = 0; foo(x, y); }")
);
} }
private String transform(String text) throws IOException { private String transform(String text) throws IOException {
return transform(text, writer);
}
private String transformGlobals(String text) throws IOException {
return transform(text, writerWithGlobals);
}
private String transform(String text, AstWriter writer) throws IOException {
sb.setLength(0); sb.setLength(0);
CompilerEnvirons env = new CompilerEnvirons(); var env = new CompilerEnvirons();
env.setRecoverFromErrors(true); env.setRecoverFromErrors(true);
env.setLanguageVersion(Context.VERSION_1_8); env.setLanguageVersion(Context.VERSION_1_8);
JSParser factory = new JSParser(env); var factory = new JSParser(env);
factory.enterFunction(); factory.enterFunction();
AstRoot rootNode = factory.parse(new StringReader(text), null, 0); var rootNode = factory.parse(new StringReader(text), null, 0);
factory.exitFunction(); factory.exitFunction();
writer.hoist(rootNode); writer.hoist(rootNode);
writer.print(rootNode); writer.print(rootNode);

View File

@ -21,6 +21,7 @@ import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.Block; import org.mozilla.javascript.ast.Block;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.AstWriter; import org.teavm.backend.javascript.rendering.AstWriter;
import org.teavm.backend.javascript.rendering.DefaultGlobalNameWriter;
import org.teavm.backend.javascript.rendering.Precedence; import org.teavm.backend.javascript.rendering.Precedence;
import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.backend.javascript.spi.InjectorContext;
@ -29,17 +30,19 @@ import org.teavm.model.MethodReference;
class JSBodyAstEmitter implements JSBodyEmitter { class JSBodyAstEmitter implements JSBodyEmitter {
private boolean isStatic; private boolean isStatic;
private AstNode ast; private AstNode ast;
private AstNode rootAst;
private String[] parameterNames; private String[] parameterNames;
JSBodyAstEmitter(boolean isStatic, AstNode ast, String[] parameterNames) { JSBodyAstEmitter(boolean isStatic, AstNode ast, AstNode rootAst, String[] parameterNames) {
this.isStatic = isStatic; this.isStatic = isStatic;
this.ast = ast; this.ast = ast;
this.rootAst = rootAst;
this.parameterNames = parameterNames; this.parameterNames = parameterNames;
} }
@Override @Override
public void emit(InjectorContext context) throws IOException { public void emit(InjectorContext context) throws IOException {
AstWriter astWriter = new AstWriter(context.getWriter()); var astWriter = new AstWriter(context.getWriter(), new DefaultGlobalNameWriter(context.getWriter()));
int paramIndex = 0; int paramIndex = 0;
if (!isStatic) { if (!isStatic) {
int index = paramIndex++; int index = paramIndex++;
@ -51,7 +54,7 @@ class JSBodyAstEmitter implements JSBodyEmitter {
astWriter.declareNameEmitter(parameterNames[i], astWriter.declareNameEmitter(parameterNames[i],
prec -> context.writeExpr(context.getArgument(index), convert(prec))); prec -> context.writeExpr(context.getArgument(index), convert(prec)));
} }
astWriter.hoist(ast); astWriter.hoist(rootAst);
astWriter.print(ast, convert(context.getPrecedence())); astWriter.print(ast, convert(context.getPrecedence()));
} }
@ -139,7 +142,7 @@ class JSBodyAstEmitter implements JSBodyEmitter {
@Override @Override
public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
AstWriter astWriter = new AstWriter(writer); var astWriter = new AstWriter(writer, new DefaultGlobalNameWriter(writer));
int paramIndex = 1; int paramIndex = 1;
if (!isStatic) { if (!isStatic) {
int index = paramIndex++; int index = paramIndex++;
@ -149,7 +152,7 @@ class JSBodyAstEmitter implements JSBodyEmitter {
int index = paramIndex++; int index = paramIndex++;
astWriter.declareNameEmitter(parameterNames[i], prec -> writer.append(context.getParameterName(index))); astWriter.declareNameEmitter(parameterNames[i], prec -> writer.append(context.getParameterName(index)));
} }
astWriter.hoist(ast); astWriter.hoist(rootAst);
if (ast instanceof Block) { if (ast instanceof Block) {
for (Node child = ast.getFirstChild(); child != null; child = child.getNext()) { for (Node child = ast.getFirstChild(); child != null; child = child.getNext()) {
astWriter.print((AstNode) child); astWriter.print((AstNode) child);

View File

@ -26,7 +26,6 @@ import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.mozilla.javascript.CompilerEnvirons; import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context; import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot; import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.FunctionNode; import org.mozilla.javascript.ast.FunctionNode;
import org.teavm.backend.javascript.rendering.JSParser; import org.teavm.backend.javascript.rendering.JSParser;
@ -556,27 +555,26 @@ class JSClassProcessor {
.toArray(String[]::new) : new String[0]; .toArray(String[]::new) : new String[0];
// Parse JS script // Parse JS script
TeaVMErrorReporter errorReporter = new TeaVMErrorReporter(diagnostics, var errorReporter = new TeaVMErrorReporter(diagnostics, new CallLocation(methodToProcess.getReference()));
new CallLocation(methodToProcess.getReference())); var env = new CompilerEnvirons();
CompilerEnvirons env = new CompilerEnvirons();
env.setRecoverFromErrors(true); env.setRecoverFromErrors(true);
env.setLanguageVersion(Context.VERSION_1_8); env.setLanguageVersion(Context.VERSION_1_8);
env.setIdeMode(true); env.setIdeMode(true);
JSParser parser = new JSParser(env, errorReporter); var parser = new JSParser(env, errorReporter);
AstRoot rootNode; AstRoot rootNode;
try { try {
rootNode = (AstRoot) parser.parseAsObject(new StringReader("function(){" + script + "}"), null, 0); rootNode = (AstRoot) parser.parseAsObject(new StringReader("function(){" + script + "}"), null, 0);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("IO Error occurred", e); throw new RuntimeException("IO Error occurred", e);
} }
AstNode body = ((FunctionNode) rootNode.getFirstChild()).getBody(); var body = ((FunctionNode) rootNode.getFirstChild()).getBody();
repository.methodMap.put(methodToProcess.getReference(), proxyMethod); repository.methodMap.put(methodToProcess.getReference(), proxyMethod);
if (errorReporter.hasErrors()) { if (errorReporter.hasErrors()) {
repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod, repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod,
script, parameterNames)); script, parameterNames));
} else { } else {
AstNode expr = JSBodyInlineUtil.isSuitableForInlining(methodToProcess.getReference(), var expr = JSBodyInlineUtil.isSuitableForInlining(methodToProcess.getReference(),
parameterNames, body); parameterNames, body);
if (expr != null) { if (expr != null) {
repository.inlineMethods.add(methodToProcess.getReference()); repository.inlineMethods.add(methodToProcess.getReference());
@ -584,7 +582,7 @@ class JSClassProcessor {
expr = body; expr = body;
} }
javaInvocationProcessor.process(location, expr); javaInvocationProcessor.process(location, expr);
repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, expr, parameterNames)); repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, expr, rootNode, parameterNames));
} }
} }

View File

@ -164,6 +164,7 @@ public class IncrementalTest {
private String runScript(String script, String fileName) { private String runScript(String script, String fileName) {
Scriptable scope = new NativeObject(); Scriptable scope = new NativeObject();
scope.setParentScope(rhinoRootScope); scope.setParentScope(rhinoRootScope);
scope.setPrototype(rhinoRootScope);
rhinoContext.evaluateString(scope, script, fileName, 1, null); rhinoContext.evaluateString(scope, script, fileName, 1, null);
Function main = (Function) scope.get("main", scope); Function main = (Function) scope.get("main", scope);
ScriptRuntime.doTopCall(main, rhinoContext, scope, scope, ScriptRuntime.doTopCall(main, rhinoContext, scope, scope,