diff --git a/teavm-jso/src/main/java/org/teavm/jso/JS.java b/teavm-jso/src/main/java/org/teavm/jso/JS.java index fddc36eff..3ca2a1d76 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/JS.java +++ b/teavm-jso/src/main/java/org/teavm/jso/JS.java @@ -18,6 +18,7 @@ package org.teavm.jso; import java.lang.reflect.Array; import java.util.Iterator; import org.teavm.dependency.PluggableDependency; +import org.teavm.javascript.spi.GeneratedBy; import org.teavm.javascript.spi.InjectedBy; import org.teavm.jso.plugin.JSNativeGenerator; @@ -557,7 +558,7 @@ public final class JS { @InjectedBy(JSNativeGenerator.class) public static native void set(JSObject instance, JSObject index, JSObject obj); - @InjectedBy(JSNativeGenerator.class) + @GeneratedBy(JSNativeGenerator.class) @PluggableDependency(JSNativeGenerator.class) public static native JSObject function(JSObject instance, JSObject property); } diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/FunctorImpl.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/FunctorImpl.java new file mode 100644 index 000000000..1f51130f4 --- /dev/null +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/FunctorImpl.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.plugin; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author Alexey Andreev + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@interface FunctorImpl { + String value(); +} diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java index b92f19187..db80fa652 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java @@ -21,6 +21,8 @@ import org.teavm.dependency.*; import org.teavm.javascript.ast.ConstantExpr; import org.teavm.javascript.ast.Expr; import org.teavm.javascript.ast.InvocationExpr; +import org.teavm.javascript.spi.Generator; +import org.teavm.javascript.spi.GeneratorContext; import org.teavm.javascript.spi.Injector; import org.teavm.javascript.spi.InjectorContext; import org.teavm.jso.JS; @@ -33,7 +35,39 @@ import org.teavm.model.MethodReference; * * @author Alexey Andreev */ -public class JSNativeGenerator implements Injector, DependencyPlugin { +public class JSNativeGenerator implements Injector, DependencyPlugin, Generator { + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) + throws IOException { + switch (methodRef.getName()) { + case "function": + writeFunction(context, writer); + break; + } + } + + private void writeFunction(GeneratorContext context, SourceWriter writer) throws IOException { + String thisName = context.getParameterName(1); + String methodName = context.getParameterName(2); + writer.append("var name").ws().append('=').ws().append("'jso$functor$'").ws().append('+').ws() + .append(methodName).append(';').softNewLine(); + writer.append("if").ws().append("(!").append(thisName).append("[name])").ws().append('{') + .indent().softNewLine(); + + writer.append("var fn").ws().append('=').ws().append("function()").ws().append('{') + .indent().softNewLine(); + writer.append("return ").append(thisName).append('[').append(methodName).append(']') + .append(".apply(").append(thisName).append(',').ws().append("arguments);").softNewLine(); + writer.outdent().append("};").softNewLine(); + writer.append(thisName).append("[name]").ws().append('=').ws().append("function()").ws().append('{') + .indent().softNewLine(); + writer.append("return fn;").softNewLine(); + writer.outdent().append("};").softNewLine(); + + writer.outdent().append('}').softNewLine(); + writer.append("return ").append(thisName).append("[name]();").softNewLine(); + } + @Override public void generate(InjectorContext context, MethodReference methodRef) throws IOException { SourceWriter writer = context.getWriter(); diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSOAliasRenderer.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSOAliasRenderer.java index 04c76fdf2..9ad375ac9 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSOAliasRenderer.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSOAliasRenderer.java @@ -29,6 +29,7 @@ import org.teavm.vm.spi.RendererListener; * @author Alexey Andreev */ class JSOAliasRenderer implements RendererListener { + private static String variableChars = "abcdefghijklmnopqrstuvwxyz"; private JSODependencyListener dependencyListener; private SourceWriter writer; @@ -50,15 +51,64 @@ class JSOAliasRenderer implements RendererListener { writer.append("(function()").ws().append("{").softNewLine().indent(); writer.append("var c;").softNewLine(); for (Map.Entry entry : dependencyListener.getExposedClasses().entrySet()) { - if (entry.getValue().methods.isEmpty()) { + ExposedClass cls = entry.getValue(); + if (cls.methods.isEmpty()) { continue; } writer.append("c").ws().append("=").ws().appendClass(entry.getKey()).append(".prototype;").softNewLine(); - for (Map.Entry aliasEntry : entry.getValue().methods.entrySet()) { + for (Map.Entry aliasEntry : cls.methods.entrySet()) { writer.append("c.").append(aliasEntry.getValue()).ws().append("=").ws().append("c.") .appendMethod(aliasEntry.getKey()).append(";").softNewLine(); } + + if (cls.functorField != null) { + writeFunctor(cls); + } } writer.outdent().append("})();").newLine(); } + + private void writeFunctor(ExposedClass cls) throws IOException { + String alias = cls.methods.get(cls.functorMethod); + if (alias == null) { + return; + } + + writer.append("c.jso$functor$").append(alias).ws().append("=").ws().append("function()").ws().append("{") + .indent().softNewLine(); + writer.append("if").ws().append("(!this.").appendField(cls.functorField).append(")").ws().append("{") + .indent().softNewLine(); + writer.append("var self").ws().append('=').ws().append("this;").softNewLine(); + + writer.append("this.").appendField(cls.functorField).ws().append('=').ws().append("function("); + appendArguments(cls.functorMethod.parameterCount()); + writer.append(")").ws().append('{').indent().softNewLine(); + writer.append("return self.").appendMethod(cls.functorMethod).append('('); + appendArguments(cls.functorMethod.parameterCount()); + writer.append(");").softNewLine(); + writer.outdent().append("};").softNewLine(); + + writer.outdent().append("}").softNewLine(); + writer.append("return this.").appendField(cls.functorField).append(';').softNewLine(); + writer.outdent().append("};").softNewLine(); + } + + private void appendArguments(int count) throws IOException { + for (int i = 0; i < count; ++i) { + if (i > 0) { + writer.append(',').ws(); + } + writer.append(variableName(i)); + } + } + + private String variableName(int index) { + StringBuilder sb = new StringBuilder(); + sb.append(variableChars.charAt(index % variableChars.length())); + index /= variableChars.length(); + if (index > 0) { + sb.append(index); + } + return sb.toString(); + } } diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSODependencyListener.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSODependencyListener.java index 997323275..29a3b615c 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSODependencyListener.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSODependencyListener.java @@ -60,6 +60,8 @@ class JSODependencyListener implements DependencyListener { Map inheritedMethods = new HashMap<>(); Map methods = new HashMap<>(); Set implementedInterfaces = new HashSet<>(); + FieldReference functorField; + MethodDescriptor functorMethod; } private ExposedClass getExposedClass(String name) { @@ -92,6 +94,14 @@ class JSODependencyListener implements DependencyListener { methodDep.use(); } } + if (exposedCls.functorField == null) { + FieldReader functorField = cls.getField("$$jso_functor$$"); + if (functorField != null) { + exposedCls.functorField = functorField.getReference(); + AnnotationReader annot = cls.getAnnotations().get(FunctorImpl.class.getName()); + exposedCls.functorMethod = MethodDescriptor.parse(annot.getValue("value").getString()); + } + } return exposedCls; } diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java index 8c023c08f..cef81ecf4 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java @@ -39,6 +39,12 @@ public class JSObjectClassTransformer implements ClassHolderTransformer { if (processor.isNativeImplementation(cls.getName())) { processor.makeSync(cls); } + MethodReference functorMethod = processor.isFunctor(cls.getName()); + if (functorMethod != null) { + if (processor.isFunctor(cls.getParent()) == null) { + processor.addFunctorField(cls, functorMethod); + } + } for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { if (method.getAnnotations().get(JSBody.class.getName()) != null) { processor.processJSBody(cls, method); diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java index f8915f76b..74719f170 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java @@ -59,6 +59,48 @@ class JavascriptNativeProcessor { this.diagnostics = diagnostics; } + public MethodReference isFunctor(String className) { + if (!nativeRepos.isJavaScriptImplementation(className)) { + return null; + } + ClassReader cls = classSource.get(className); + if (cls == null) { + return null; + } + Map methods = new HashMap<>(); + getFunctorMethods(className, new HashSet(), methods); + if (methods.size() == 1) { + return methods.values().iterator().next(); + } + return null; + } + + private void getFunctorMethods(String className, Set visited, + Map methods) { + if (!visited.add(className)) { + return; + } + + ClassReader cls = classSource.get(className); + if (cls == null) { + return; + } + + if (cls.getAnnotations().get(JSFunctor.class.getName()) != null && isProperFunctor(cls)) { + MethodReference method = cls.getMethods().iterator().next().getReference(); + if (!methods.containsKey(method.getDescriptor())) { + methods.put(method.getDescriptor(), method); + } + } + + if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) { + getFunctorMethods(cls.getParent(), visited, methods); + } + for (String iface : cls.getInterfaces()) { + getFunctorMethods(iface, visited, methods); + } + } + public void processClass(ClassHolder cls) { Set preservedMethods = new HashSet<>(); for (String iface : cls.getInterfaces()) { @@ -126,6 +168,17 @@ class JavascriptNativeProcessor { } } + public void addFunctorField(ClassHolder cls, MethodReference method) { + FieldHolder field = new FieldHolder("$$jso_functor$$"); + field.setLevel(AccessLevel.PUBLIC); + field.setType(ValueType.parse(JSObject.class)); + cls.addField(field); + + AnnotationHolder annot = new AnnotationHolder(FunctorImpl.class.getName()); + annot.getValues().put("value", new AnnotationValue(method.getDescriptor().toString())); + cls.getAnnotations().add(annot); + } + public void makeSync(ClassHolder cls) { Set methods = new HashSet<>(); findInheritedMethods(cls, methods, new HashSet()); @@ -664,8 +717,12 @@ class JavascriptNativeProcessor { return wrap(var, type, location.getSourceLocation()); } + private boolean isProperFunctor(ClassReader type) { + return type.hasModifier(ElementModifier.INTERFACE) && type.getMethods().size() == 1; + } + private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type) { - if (!type.hasModifier(ElementModifier.INTERFACE) || type.getMethods().size() != 1) { + if (!isProperFunctor(type)) { diagnostics.error(location, "Wrong functor: {{c0}}", type.getName()); return var; } diff --git a/teavm-samples/teavm-samples-benchmark/pom.xml b/teavm-samples/teavm-samples-benchmark/pom.xml index 00c095804..1ba0d82a5 100644 --- a/teavm-samples/teavm-samples-benchmark/pom.xml +++ b/teavm-samples/teavm-samples-benchmark/pom.xml @@ -120,7 +120,7 @@ ${project.build.directory}/generated/js/teavm org.teavm.samples.benchmark.teavm.BenchmarkStarter SEPARATE - true + false true