Improve JS templating so that it could be used in native generators

This commit is contained in:
Alexey Andreev 2023-10-29 17:16:38 +01:00
parent b006cbb206
commit eb0f4fb090
14 changed files with 406 additions and 78 deletions

View File

@ -0,0 +1,38 @@
/*
* 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.classlib.java.lang;
import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyNode;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency;
public class SystemDependencyPlugin implements DependencyPlugin {
@Override
public void methodReached(DependencyAgent agent, MethodDependency method) {
switch (method.getReference().getName()) {
case "doArrayCopy":
reachArrayCopy(method);
break;
}
}
private void reachArrayCopy(MethodDependency method) {
DependencyNode src = method.getVariable(1);
DependencyNode dest = method.getVariable(3);
src.getArrayItem().connect(dest.getArrayItem());
}
}

View File

@ -19,70 +19,20 @@ import java.io.IOException;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.dependency.DependencyAgent; import org.teavm.backend.javascript.templating.JavaScriptTemplate;
import org.teavm.dependency.DependencyNode; import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
public class SystemNativeGenerator implements Generator, DependencyPlugin { public class SystemNativeGenerator implements Generator {
private JavaScriptTemplate template;
public SystemNativeGenerator(JavaScriptTemplateFactory templateFactory) throws IOException {
template = templateFactory.createFromResource("org/teavm/classlib/java/lang/System.js");
}
@Override @Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) { var fragment = template.builder(methodRef.getName()).withContext(context).build();
case "doArrayCopy": fragment.write(writer, 0);
generateArrayCopy(context, writer);
break;
case "currentTimeMillis":
generateCurrentTimeMillis(writer);
break;
}
}
@Override
public void methodReached(DependencyAgent agent, MethodDependency method) {
switch (method.getReference().getName()) {
case "doArrayCopy":
reachArrayCopy(method);
break;
}
}
private void generateArrayCopy(GeneratorContext context, SourceWriter writer) throws IOException {
String src = context.getParameterName(1);
String srcPos = context.getParameterName(2);
String dest = context.getParameterName(3);
String destPos = context.getParameterName(4);
String length = context.getParameterName(5);
writer.append("if").ws().append("(").append(length).ws().append("===").ws().append("0)").ws().append("{")
.indent().softNewLine();
writer.append("return;").ws().softNewLine();
writer.outdent().append("}").ws().append("else ");
writer.append("if").ws().append("(typeof " + src + ".data.buffer").ws().append("!==").ws()
.append("'undefined')").ws().append("{").indent().softNewLine();
writer.append(dest + ".data.set(" + src + ".data.subarray(" + srcPos + ",").ws()
.append(srcPos).ws().append("+").ws().append(length).append("),").ws()
.append(destPos).append(");").softNewLine();
writer.outdent().append("}").ws().append("else ");
writer.append("if (" + src + " !== " + dest + " || " + destPos + " < " + srcPos + ") {").indent().newLine();
writer.append("for (var i = 0; i < " + length + "; i = (i + 1) | 0) {").indent().softNewLine();
writer.append(dest + ".data[" + destPos + "++] = " + src + ".data[" + srcPos + "++];").softNewLine();
writer.outdent().append("}").softNewLine();
writer.outdent().append("}").ws().append("else").ws().append("{").indent().softNewLine();
writer.append(srcPos + " = (" + srcPos + " + " + length + ") | 0;").softNewLine();
writer.append(destPos + " = (" + destPos + " + " + length + ") | 0;").softNewLine();
writer.append("for (var i = 0; i < " + length + "; i = (i + 1) | 0) {").indent().softNewLine();
writer.append(dest + ".data[--" + destPos + "] = " + src + ".data[--" + srcPos + "];").softNewLine();
writer.outdent().append("}").softNewLine();
writer.outdent().append("}").softNewLine();
}
private void generateCurrentTimeMillis(SourceWriter writer) throws IOException {
writer.append("return Long_fromNumber(new Date().getTime());").softNewLine();
}
private void reachArrayCopy(MethodDependency method) {
DependencyNode src = method.getVariable(1);
DependencyNode dest = method.getVariable(3);
src.getArrayItem().connect(dest.getArrayItem());
} }
} }

View File

@ -55,7 +55,7 @@ public class TRuntime {
* its best effort to recycle all discarded objects. The name gc stands for * its best effort to recycle all discarded objects. The name gc stands for
* "garbage collector". The Java Virtual Machine performs this recycling * "garbage collector". The Java Virtual Machine performs this recycling
* process automatically as needed even if the gc method is not invoked * process automatically as needed even if the gc method is not invoked
* explicitly. The method System.gc() is the conventional and convenient * explicitly. The method System.js.gc() is the conventional and convenient
* means of invoking this method. * means of invoking this method.
*/ */
public void gc() { public void gc() {

View File

@ -31,6 +31,7 @@ import org.teavm.classlib.java.io.TInputStream;
import org.teavm.classlib.java.io.TOutputStream; import org.teavm.classlib.java.io.TOutputStream;
import org.teavm.classlib.java.io.TPrintStream; import org.teavm.classlib.java.io.TPrintStream;
import org.teavm.classlib.java.lang.reflect.TArray; import org.teavm.classlib.java.lang.reflect.TArray;
import org.teavm.dependency.PluggableDependency;
import org.teavm.interop.Address; import org.teavm.interop.Address;
import org.teavm.interop.DelegateTo; import org.teavm.interop.DelegateTo;
import org.teavm.interop.Import; import org.teavm.interop.Import;
@ -125,6 +126,7 @@ public final class TSystem extends TObject {
} }
@GeneratedBy(SystemNativeGenerator.class) @GeneratedBy(SystemNativeGenerator.class)
@PluggableDependency(SystemDependencyPlugin.class)
@DelegateTo("doArrayCopyLowLevel") @DelegateTo("doArrayCopyLowLevel")
@NoSideEffects @NoSideEffects
static native void doArrayCopy(Object src, int srcPos, Object dest, int destPos, int length); static native void doArrayCopy(Object src, int srcPos, Object dest, int destPos, int length);

View File

@ -0,0 +1,20 @@
function doArrayCopy(src, srcPos, dest, destPos, length) {
if (length !== 0) {
if (typeof src.data.buffer !== 'undefined') {
dest.data.set(src.data.subarray(srcPos, srcPos + length), destPos);
} else if (src !== dest || destPos < srcPos) {
for (let i = 0; i < length; i = (i + 1) | 0) {
dest.data[destPos++] = src.data[srcPos++];
}
} else {
srcPos = (srcPos + length) | 0;
destPos = (destPos + length) | 0;
for (let i = 0; i < length; i = (i + 1) | 0) {
dest.data[--destPos] = src.data[--srcPos];
}
}
}
}
function currentTimeMillis() {
return Long_fromNumber(new (teavm_globals.Date)().getTime());
}

View File

@ -24,6 +24,7 @@ import java.io.Writer;
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
@ -70,11 +71,13 @@ import org.teavm.backend.javascript.spi.ModuleImporter;
import org.teavm.backend.javascript.spi.ModuleImporterContext; import org.teavm.backend.javascript.spi.ModuleImporterContext;
import org.teavm.backend.javascript.spi.VirtualMethodContributor; import org.teavm.backend.javascript.spi.VirtualMethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; import org.teavm.backend.javascript.spi.VirtualMethodContributorContext;
import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
import org.teavm.cache.AstCacheEntry; import org.teavm.cache.AstCacheEntry;
import org.teavm.cache.AstDependencyExtractor; import org.teavm.cache.AstDependencyExtractor;
import org.teavm.cache.CacheStatus; import org.teavm.cache.CacheStatus;
import org.teavm.cache.EmptyMethodNodeCache; import org.teavm.cache.EmptyMethodNodeCache;
import org.teavm.cache.MethodNodeCache; import org.teavm.cache.MethodNodeCache;
import org.teavm.common.ServiceRepository;
import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.debugging.information.SourceLocation; import org.teavm.debugging.information.SourceLocation;
@ -148,6 +151,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion(); private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion();
private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY); private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY);
private final Map<String, String> importedModules = new LinkedHashMap<>(); private final Map<String, String> importedModules = new LinkedHashMap<>();
private Map<String, Generator> generatorCache = new HashMap<>();
private JavaScriptTemplateFactory templateFactory;
@Override @Override
public List<ClassHolderTransformer> getTransformers() { public List<ClassHolderTransformer> getTransformers() {
@ -175,6 +180,9 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
var refQueueGenerator = new ReferenceQueueGenerator(); var refQueueGenerator = new ReferenceQueueGenerator();
methodGenerators.put(new MethodReference(ReferenceQueue.class, "<init>", void.class), refQueueGenerator); methodGenerators.put(new MethodReference(ReferenceQueue.class, "<init>", void.class), refQueueGenerator);
methodGenerators.put(new MethodReference(ReferenceQueue.class, "poll", Reference.class), refQueueGenerator); methodGenerators.put(new MethodReference(ReferenceQueue.class, "poll", Reference.class), refQueueGenerator);
templateFactory = new JavaScriptTemplateFactory(controller.getClassLoader(),
controller.getDependencyInfo().getClassSource());
} }
@Override @Override
@ -629,7 +637,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
break; break;
} }
} }
classNodes.add(decompile(decompiler, cls)); classNodes.add(decompile(decompiler, cls, classes));
} }
return classNodes; return classNodes;
} }
@ -660,7 +668,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
order.add(className); order.add(className);
} }
private PreparedClass decompile(Decompiler decompiler, ClassHolder cls) { private PreparedClass decompile(Decompiler decompiler, ClassHolder cls, ClassReaderSource classes) {
PreparedClass clsNode = new PreparedClass(cls); PreparedClass clsNode = new PreparedClass(cls);
for (MethodHolder method : cls.getMethods()) { for (MethodHolder method : cls.getMethods()) {
if (method.getModifiers().contains(ElementModifier.ABSTRACT)) { if (method.getModifiers().contains(ElementModifier.ABSTRACT)) {
@ -675,14 +683,14 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
} }
PreparedMethod preparedMethod = method.hasModifier(ElementModifier.NATIVE) PreparedMethod preparedMethod = method.hasModifier(ElementModifier.NATIVE)
? decompileNative(method) ? decompileNative(method, classes)
: decompile(decompiler, method); : decompile(decompiler, method);
clsNode.getMethods().add(preparedMethod); clsNode.getMethods().add(preparedMethod);
} }
return clsNode; return clsNode;
} }
private PreparedMethod decompileNative(MethodHolder method) { private PreparedMethod decompileNative(MethodHolder method, ClassReaderSource classes) {
MethodReference reference = method.getReference(); MethodReference reference = method.getReference();
Generator generator = methodGenerators.get(reference); Generator generator = methodGenerators.get(reference);
if (generator == null && !isBootstrap()) { if (generator == null && !isBootstrap()) {
@ -693,18 +701,62 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
} }
ValueType annotValue = annotHolder.getValues().get("value").getJavaClass(); ValueType annotValue = annotHolder.getValues().get("value").getJavaClass();
String generatorClassName = ((ValueType.Object) annotValue).getClassName(); String generatorClassName = ((ValueType.Object) annotValue).getClassName();
try { generator = generatorCache.computeIfAbsent(generatorClassName,
Class<?> generatorClass = Class.forName(generatorClassName, true, controller.getClassLoader()); name -> createGenerator(name, method, classes));
generator = (Generator) generatorClass.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new DecompilationException("Error instantiating generator " + generatorClassName
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor());
}
} }
return new PreparedMethod(method, null, generator, asyncMethods.contains(reference), null); return new PreparedMethod(method, null, generator, asyncMethods.contains(reference), null);
} }
private Generator createGenerator(String name, MethodHolder method, ClassReaderSource classes) {
Class<?> generatorClass;
try {
generatorClass = Class.forName(name, true, controller.getClassLoader());
} catch (ClassNotFoundException e) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor());
}
var constructors = generatorClass.getConstructors();
if (constructors.length != 1) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor());
}
var constructor = constructors[0];
var parameterTypes = constructor.getParameterTypes();
var arguments = new Object[parameterTypes.length];
for (var i = 0; i < arguments.length; ++i) {
var parameterType = parameterTypes[i];
if (parameterType.equals(ClassReaderSource.class)) {
arguments[i] = classes;
} else if (parameterType.equals(Properties.class)) {
arguments[i] = controller.getProperties();
} else if (parameterType.equals(DependencyInfo.class)) {
arguments[i] = controller.getDependencyInfo();
} else if (parameterType.equals(ServiceRepository.class)) {
arguments[i] = controller.getServices();
} else if (parameterType.equals(JavaScriptTemplateFactory.class)) {
arguments[i] = templateFactory;
} else {
var service = controller.getServices().getService(parameterType);
if (service == null) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor() + ". "
+ "Its constructor requires " + parameterType + " as its parameter #" + (i + 1)
+ " which is not available.");
}
}
}
try {
return (Generator) constructor.newInstance(arguments);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new DecompilationException("Error instantiating generator " + name
+ " for native method " + method.getOwnerName() + "." + method.getDescriptor(), e);
}
}
private PreparedMethod decompile(Decompiler decompiler, MethodHolder method) { private PreparedMethod decompile(Decompiler decompiler, MethodHolder method) {
MethodReference reference = method.getReference(); MethodReference reference = method.getReference();
if (asyncMethods.contains(reference)) { if (asyncMethods.contains(reference)) {

View File

@ -0,0 +1,43 @@
/*
* 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.io.IOException;
import java.nio.charset.StandardCharsets;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstNode;
import org.teavm.backend.javascript.rendering.JSParser;
public final class AstUtil {
private AstUtil() {
}
public static AstNode parse(String string) {
var env = new CompilerEnvirons();
env.setRecoverFromErrors(true);
env.setLanguageVersion(Context.VERSION_1_8);
var factory = new JSParser(env);
return factory.parse(string, null, 0);
}
public static AstNode parseFromResources(ClassLoader classLoader, String path) throws IOException {
try (var input = classLoader.getResourceAsStream(path)) {
return parse(new String(input.readAllBytes(), StandardCharsets.UTF_8));
}
}
}

View File

@ -502,7 +502,7 @@ public class AstWriter {
writer.append(']'); writer.append(']');
} }
private void print(PropertyGet node) throws IOException { public void print(PropertyGet node) throws IOException {
print(node.getLeft(), PRECEDENCE_MEMBER); print(node.getLeft(), PRECEDENCE_MEMBER);
writer.append('.'); writer.append('.');
var oldRootScope = rootScope; var oldRootScope = rootScope;
@ -651,8 +651,8 @@ public class AstWriter {
writer.append(node.getQuoteCharacter()); writer.append(node.getQuoteCharacter());
} }
private void print(Name node, int precedence) throws IOException { public void print(Name node, int precedence) throws IOException {
if (rootScope) { if (rootScope && node.getDefiningScope() == null) {
var alias = nameMap.get(node.getIdentifier()); var alias = nameMap.get(node.getIdentifier());
if (alias == null) { if (alias == null) {
if (globalNameWriter != null) { if (globalNameWriter != null) {
@ -667,6 +667,10 @@ public class AstWriter {
} }
} }
protected final boolean isRootScope() {
return rootScope;
}
private void print(RegExpLiteral node) throws IOException { private void print(RegExpLiteral node) throws IOException {
writer.append('/').append(node.getValue()).append('/').append(node.getFlags()); writer.append('/').append(node.getValue()).append('/').append(node.getFlags());
} }

View File

@ -51,7 +51,7 @@ public class RuntimeRenderer {
AstRoot ast = parseRuntime(name); AstRoot ast = parseRuntime(name);
ast.visit(new StringConstantElimination()); ast.visit(new StringConstantElimination());
new TemplatingAstTransformer(classSource).visit(ast); new TemplatingAstTransformer(classSource).visit(ast);
var astWriter = new TemplatingAstWriter(writer); var astWriter = new TemplatingAstWriter(writer, null, null);
astWriter.hoist(ast); astWriter.hoist(ast);
astWriter.print(ast); astWriter.print(ast);
} }

View File

@ -0,0 +1,87 @@
/*
* 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 java.util.HashMap;
import java.util.function.Function;
import java.util.function.IntFunction;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Name;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.model.ClassReaderSource;
public class JavaScriptTemplate {
private TemplatingFunctionIndex functionIndex = new TemplatingFunctionIndex();
public JavaScriptTemplate(AstNode node, ClassReaderSource classSource) {
new TemplatingAstTransformer(classSource).visit(node);
functionIndex.visit(node);
}
public FragmentBuilder builder(String functionName) {
var function = functionIndex.getFunction(functionName);
if (function == null) {
throw new IllegalArgumentException("Function " + functionName + " was not found in JS template");
}
return new FragmentBuilder(function);
}
public static class FragmentBuilder {
private FunctionNode node;
private IntFunction<SourceFragment> parameters;
private FragmentBuilder(FunctionNode node) {
this.node = node;
}
public FragmentBuilder withParameters(IntFunction<SourceFragment> parameters) {
this.parameters = parameters;
return this;
}
public FragmentBuilder withContext(GeneratorContext context) {
return withParameters(param -> (writer, precedence) -> writer.append(context.getParameterName(param + 1)));
}
public SourceFragment build() {
var intParameters = parameters;
var paramNameToIndex = new HashMap<String, Integer>();
for (var i = 0; i < node.getParams().size(); ++i) {
var param = node.getParams().get(i);
if (param instanceof Name) {
paramNameToIndex.put(((Name) param).getIdentifier(), i);
}
}
Function<String, SourceFragment> nameParameters = name -> {
var index = paramNameToIndex.get(name);
return index != null ? intParameters.apply(index) : null;
};
var thisFragment = parameters.apply(0);
var body = node.getBody();
return (writer, precedence) -> {
var astWriter = new TemplatingAstWriter(writer, nameParameters, node);
if (thisFragment != null) {
astWriter.declareNameEmitter("this", thisPrecedence -> thisFragment.write(writer, thisPrecedence));
}
for (var child = body.getFirstChild(); child != null; child = child.getNext()) {
astWriter.print((AstNode) child);
writer.softNewLine();
}
};
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2023 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.templating;
import java.io.IOException;
import org.teavm.backend.javascript.ast.AstUtil;
import org.teavm.model.ClassReaderSource;
public class JavaScriptTemplateFactory {
private ClassLoader classLoader;
private ClassReaderSource classSource;
public JavaScriptTemplateFactory(ClassLoader classLoader, ClassReaderSource classSource) {
this.classLoader = classLoader;
this.classSource = classSource;
}
public JavaScriptTemplate createFromResource(String path) throws IOException {
return new JavaScriptTemplate(AstUtil.parseFromResources(classLoader, path), classSource);
}
}

View File

@ -0,0 +1,23 @@
/*
* 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 java.io.IOException;
import org.teavm.backend.javascript.codegen.SourceWriter;
public interface SourceFragment {
void write(SourceWriter writer, int precedence) throws IOException;
}

View File

@ -16,9 +16,12 @@
package org.teavm.backend.javascript.templating; package org.teavm.backend.javascript.templating;
import java.io.IOException; import java.io.IOException;
import java.util.function.Function;
import org.mozilla.javascript.ast.ElementGet; import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.FunctionCall; import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.Name; 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.mozilla.javascript.ast.StringLiteral;
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;
@ -28,8 +31,13 @@ import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
public class TemplatingAstWriter extends AstWriter { public class TemplatingAstWriter extends AstWriter {
public TemplatingAstWriter(SourceWriter writer) { private Function<String, SourceFragment> names;
private Scope scope;
public TemplatingAstWriter(SourceWriter writer, Function<String, SourceFragment> names, Scope scope) {
super(writer, new DefaultGlobalNameWriter(writer)); super(writer, new DefaultGlobalNameWriter(writer));
this.names = names;
this.scope = scope;
} }
@Override @Override
@ -137,6 +145,19 @@ public class TemplatingAstWriter extends AstWriter {
super.print(node); super.print(node);
} }
@Override
public void print(PropertyGet node) throws IOException {
if (node.getTarget() instanceof Name) {
var name = (Name) node.getTarget();
if (name.getDefiningScope() == null && name.getIdentifier().equals("teavm_globals")) {
writer.append("$rt_globals").append(".");
print(node.getProperty());
return;
}
}
super.print(node);
}
private boolean writeJavaVirtualMethod(ElementGet get, FunctionCall call) throws IOException { private boolean writeJavaVirtualMethod(ElementGet get, FunctionCall call) throws IOException {
var arg = call.getArguments().get(0); var arg = call.getArguments().get(0);
if (!(arg instanceof StringLiteral)) { if (!(arg instanceof StringLiteral)) {
@ -163,4 +184,22 @@ public class TemplatingAstWriter extends AstWriter {
writer.append('.').appendField(new FieldReference(className, fieldName)); writer.append('.').appendField(new FieldReference(className, fieldName));
return true; return true;
} }
@Override
public void print(Name node, int precedence) throws IOException {
if (isRootScope()) {
if (names != null && node.getDefiningScope() == scope) {
var fragment = names.apply(node.getIdentifier());
if (fragment != null) {
fragment.write(writer, precedence);
return;
}
}
if (node.getDefiningScope() == null && scope != null) {
writer.appendFunction(node.getIdentifier());
return;
}
}
super.print(node, precedence);
}
} }

View File

@ -0,0 +1,36 @@
/*
* 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 java.util.HashMap;
import java.util.Map;
import org.mozilla.javascript.ast.FunctionNode;
import org.teavm.backend.javascript.ast.AstVisitor;
public class TemplatingFunctionIndex extends AstVisitor {
private Map<String, FunctionNode> functions = new HashMap<>();
@Override
public void visit(FunctionNode node) {
if (node.getName() != null) {
functions.put(node.getName(), node);
}
}
public FunctionNode getFunction(String name) {
return functions.get(name);
}
}