Introduces concept of an *injector*. Uses injectors to make generated

JavaScript code neat.
This commit is contained in:
konsoletyper 2014-02-07 00:44:06 +04:00
parent ae2e669ec3
commit c9f78c5cdf
8 changed files with 319 additions and 65 deletions

View File

@ -20,6 +20,7 @@ import org.teavm.common.*;
import org.teavm.javascript.ast.*; import org.teavm.javascript.ast.*;
import org.teavm.javascript.ni.GeneratedBy; import org.teavm.javascript.ni.GeneratedBy;
import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.InjectedBy;
import org.teavm.model.*; import org.teavm.model.*;
import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.ProgramUtils;
@ -117,6 +118,9 @@ public class Decompiler {
if (method.getModifiers().contains(ElementModifier.ABSTRACT)) { if (method.getModifiers().contains(ElementModifier.ABSTRACT)) {
continue; continue;
} }
if (method.getAnnotations().get(InjectedBy.class.getName()) != null) {
continue;
}
clsNode.getMethods().add(decompile(method)); clsNode.getMethods().add(decompile(method));
} }
clsNode.getInterfaces().addAll(cls.getInterfaces()); clsNode.getInterfaces().addAll(cls.getInterfaces());

View File

@ -96,7 +96,6 @@ public class JavascriptBuilder {
SourceWriterBuilder builder = new SourceWriterBuilder(naming); SourceWriterBuilder builder = new SourceWriterBuilder(naming);
builder.setMinified(minifying); builder.setMinified(minifying);
SourceWriter sourceWriter = builder.build(writer); SourceWriter sourceWriter = builder.build(writer);
Renderer renderer = new Renderer(sourceWriter, classSource);
dependencyChecker.attachMethodGraph(new MethodReference("java.lang.Class", new MethodDescriptor("createNew", dependencyChecker.attachMethodGraph(new MethodReference("java.lang.Class", new MethodDescriptor("createNew",
ValueType.object("java.lang.Class")))); ValueType.object("java.lang.Class"))));
dependencyChecker.attachMethodGraph(new MethodReference("java.lang.String", new MethodDescriptor("<init>", dependencyChecker.attachMethodGraph(new MethodReference("java.lang.String", new MethodDescriptor("<init>",
@ -104,6 +103,7 @@ public class JavascriptBuilder {
executor.complete(); executor.complete();
ListableClassHolderSource classSet = dependencyChecker.cutUnachievableClasses(); ListableClassHolderSource classSet = dependencyChecker.cutUnachievableClasses();
Decompiler decompiler = new Decompiler(classSet, classLoader, executor); Decompiler decompiler = new Decompiler(classSet, classLoader, executor);
Renderer renderer = new Renderer(sourceWriter, classSet, classLoader);
ClassSetOptimizer optimizer = new ClassSetOptimizer(executor); ClassSetOptimizer optimizer = new ClassSetOptimizer(executor);
optimizer.optimizeAll(classSet); optimizer.optimizeAll(classSet);
executor.complete(); executor.complete();

View File

@ -16,13 +16,20 @@
package org.teavm.javascript; package org.teavm.javascript;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.teavm.codegen.NamingException; import org.teavm.codegen.NamingException;
import org.teavm.codegen.NamingStrategy; import org.teavm.codegen.NamingStrategy;
import org.teavm.codegen.SourceWriter; import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.ast.*; import org.teavm.javascript.ast.*;
import org.teavm.javascript.ni.GeneratorContext; import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.javascript.ni.InjectedBy;
import org.teavm.javascript.ni.Injector;
import org.teavm.javascript.ni.InjectorContext;
import org.teavm.model.*; import org.teavm.model.*;
/** /**
@ -34,12 +41,23 @@ public class Renderer implements ExprVisitor, StatementVisitor {
private NamingStrategy naming; private NamingStrategy naming;
private SourceWriter writer; private SourceWriter writer;
private ClassHolderSource classSource; private ClassHolderSource classSource;
private ClassLoader classLoader;
private boolean minifying; private boolean minifying;
private Map<MethodReference, InjectorHolder> injectorMap = new HashMap<>();
public Renderer(SourceWriter writer, ClassHolderSource classSource) { private static class InjectorHolder {
public final Injector injector;
public InjectorHolder(Injector injector) {
this.injector = injector;
}
}
public Renderer(SourceWriter writer, ClassHolderSource classSource, ClassLoader classLoader) {
this.naming = writer.getNaming(); this.naming = writer.getNaming();
this.writer = writer; this.writer = writer;
this.classSource = classSource; this.classSource = classSource;
this.classLoader = classLoader;
} }
public SourceWriter getWriter() { public SourceWriter getWriter() {
@ -193,6 +211,7 @@ public class Renderer implements ExprVisitor, StatementVisitor {
} }
writer.outdent().append("}").newLine(); writer.outdent().append("}").newLine();
for (MethodNode method : cls.getMethods()) { for (MethodNode method : cls.getMethods()) {
cls.getMethods();
if (!method.getModifiers().contains(NodeModifier.STATIC)) { if (!method.getModifiers().contains(NodeModifier.STATIC)) {
renderDeclaration(method); renderDeclaration(method);
} }
@ -947,6 +966,11 @@ public class Renderer implements ExprVisitor, StatementVisitor {
@Override @Override
public void visit(InvocationExpr expr) { public void visit(InvocationExpr expr) {
try { try {
Injector injector = getInjector(expr.getMethod());
if (injector != null) {
injector.generate(new InjectorContextImpl(expr.getArguments()), expr.getMethod());
return;
}
String className = naming.getNameFor(expr.getMethod().getClassName()); String className = naming.getNameFor(expr.getMethod().getClassName());
String name = naming.getNameFor(expr.getMethod()); String name = naming.getNameFor(expr.getMethod());
String fullName = naming.getFullNameFor(expr.getMethod()); String fullName = naming.getFullNameFor(expr.getMethod());
@ -1124,4 +1148,77 @@ public class Renderer implements ExprVisitor, StatementVisitor {
throw new RenderingException("IO error occured", e); throw new RenderingException("IO error occured", e);
} }
} }
private Injector getInjector(MethodReference ref) {
InjectorHolder holder = injectorMap.get(ref);
if (holder == null) {
MethodHolder method = classSource.getClassHolder(ref.getClassName()).getMethod(ref.getDescriptor());
AnnotationHolder injectedByAnnot = method.getAnnotations().get(InjectedBy.class.getName());
if (injectedByAnnot != null) {
ValueType type = injectedByAnnot.getValues().get("value").getJavaClass();
holder = new InjectorHolder(instantiateInjector(((ValueType.Object)type).getClassName()));
} else {
holder = new InjectorHolder(null);
}
injectorMap.put(ref, holder);
}
return holder.injector;
}
private Injector instantiateInjector(String type) {
try {
Class<? extends Injector> cls = Class.forName(type, true, classLoader).asSubclass(Injector.class);
Constructor<? extends Injector> cons = cls.getConstructor();
return cons.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException("Illegal injector: " + type, e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Default constructor was not found in the " + type + " injector", e);
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException("Error instantiating injector " + type, e);
}
}
private class InjectorContextImpl implements InjectorContext {
private List<Expr> arguments;
public InjectorContextImpl(List<Expr> arguments) {
this.arguments = arguments;
}
@Override
public Expr getArgument(int index) {
return arguments.get(index);
}
@Override
public boolean isMinifying() {
return minifying;
}
@Override
public SourceWriter getWriter() {
return writer;
}
@Override
public void writeEscaped(String str) throws IOException {
writer.append(escapeString(str));
}
@Override
public void writeType(ValueType type) throws IOException {
writer.append(typeToClsString(naming, type));
}
@Override
public void writeExpr(Expr expr) throws IOException {
expr.acceptVisitor(Renderer.this);
}
@Override
public int argumentCount() {
return arguments.size();
}
}
} }

View File

@ -0,0 +1,31 @@
/*
* Copyright 2014 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.javascript.ni;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InjectedBy {
Class<? extends Injector> value();
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2014 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.javascript.ni;
import java.io.IOException;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public interface Injector {
void generate(InjectorContext context, MethodReference methodRef) throws IOException;
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2014 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.javascript.ni;
import java.io.IOException;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.ast.Expr;
import org.teavm.model.ValueType;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public interface InjectorContext {
Expr getArgument(int index);
int argumentCount();
boolean isMinifying();
SourceWriter getWriter();
void writeEscaped(String str) throws IOException;
void writeType(ValueType type) throws IOException;
void writeExpr(Expr expr) throws IOException;
}

View File

@ -45,25 +45,25 @@ public final class JS {
public static native <T extends JSObject> JSArray<T> createArray(int size); public static native <T extends JSObject> JSArray<T> createArray(int size);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject getTypeName(JSObject obj); public static native JSObject getTypeName(JSObject obj);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject getGlobal(); public static native JSObject getGlobal();
@GeneratedBy(JSNativeGenerator.class) @GeneratedBy(JSNativeGenerator.class)
public static native JSObject wrap(String str); public static native JSObject wrap(String str);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(char c); public static native JSObject wrap(char c);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(int num); public static native JSObject wrap(int num);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(float num); public static native JSObject wrap(float num);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(double num); public static native JSObject wrap(double num);
public static <T extends JSObject> JSArray<T> wrap(T[] array) { public static <T extends JSObject> JSArray<T> wrap(T[] array) {
@ -90,7 +90,7 @@ public final class JS {
return result; return result;
} }
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native boolean unwrapBoolean(JSObject obj); public static native boolean unwrapBoolean(JSObject obj);
public static byte unwrapByte(JSObject obj) { public static byte unwrapByte(JSObject obj) {
@ -101,53 +101,53 @@ public final class JS {
return (short)unwrapInt(obj); return (short)unwrapInt(obj);
} }
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native int unwrapInt(JSObject obj); public static native int unwrapInt(JSObject obj);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native float unwrapFloat(JSObject obj); public static native float unwrapFloat(JSObject obj);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native double unwrapDouble(JSObject obj); public static native double unwrapDouble(JSObject obj);
@GeneratedBy(JSNativeGenerator.class) @GeneratedBy(JSNativeGenerator.class)
public static native String unwrapString(JSObject obj); public static native String unwrapString(JSObject obj);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native char unwrapCharacter(JSObject obj); public static native char unwrapCharacter(JSObject obj);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native boolean isUndefined(JSObject obj); public static native boolean isUndefined(JSObject obj);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method); public static native JSObject invoke(JSObject instance, JSObject method);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a); public static native JSObject invoke(JSObject instance, JSObject method, JSObject a);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b); public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c); public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d); JSObject d);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e); JSObject d, JSObject e);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e, JSObject f); JSObject d, JSObject e, JSObject f);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e, JSObject f, JSObject g); JSObject d, JSObject e, JSObject f, JSObject g);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e, JSObject f, JSObject g, JSObject h); JSObject d, JSObject e, JSObject f, JSObject g, JSObject h);
@ -170,9 +170,9 @@ public final class JS {
}; };
} }
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject get(JSObject instance, JSObject index); public static native JSObject get(JSObject instance, JSObject index);
@GeneratedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native void set(JSObject instance, JSObject index, JSObject obj); public static native void set(JSObject instance, JSObject index, JSObject obj);
} }

View File

@ -17,6 +17,9 @@ package org.teavm.javascript.ni;
import java.io.IOException; import java.io.IOException;
import org.teavm.codegen.SourceWriter; import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.ast.ConstantExpr;
import org.teavm.javascript.ast.Expr;
import org.teavm.javascript.ast.InvocationExpr;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
@ -24,47 +27,62 @@ import org.teavm.model.MethodReference;
* *
* @author Alexey Andreev * @author Alexey Andreev
*/ */
public class JSNativeGenerator implements Generator { public class JSNativeGenerator implements Generator, Injector {
@Override @Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef)
throws IOException { throws IOException {
switch (methodRef.getName()) { if (methodRef.getName().equals("wrap")) {
case "getTypeName":
writer.append("return typeof ").append(context.getParameterName(1)).append(";").softNewLine();
break;
case "getGlobal":
writer.append("return window;").softNewLine();
break;
case "wrap":
if (methodRef.getParameterTypes()[0].isObject("java.lang.String")) {
generateWrapString(context, writer); generateWrapString(context, writer);
} else { } else if (methodRef.getName().equals("unwrapString")) {
writer.append("return ").append(context.getParameterName(1)).append(";").softNewLine();
}
break;
case "get":
writer.append("return ").append(context.getParameterName(1)).append("[")
.append(context.getParameterName(2)).append("];").softNewLine();
break;
case "set":
writer.append(context.getParameterName(1)).append("[").append(context.getParameterName(2))
.append("] = ").append(context.getParameterName(3)).softNewLine();
break;
case "invoke":
generateInvoke(context, writer, methodRef.parameterCount() - 2);
break;
case "isUndefined":
writer.append("return ").append(context.getParameterName(1)).append(" === undefined;");
break;
default:
if (methodRef.getName().startsWith("unwrap")) {
if (methodRef.getDescriptor().getResultType().isObject("java.lang.String")) {
writer.append("return $rt_str(").append(context.getParameterName(1)).append(");") writer.append("return $rt_str(").append(context.getParameterName(1)).append(");")
.softNewLine(); .softNewLine();
} else {
writer.append("return ").append(context.getParameterName(1)).append(";").softNewLine();
} }
} }
@Override
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
SourceWriter writer = context.getWriter();
switch (methodRef.getName()) {
case "getGlobal":
writer.append("window");
break;
case "isUndefined":
writer.append("(");
context.writeExpr(context.getArgument(0));
writer.ws().append("===").ws().append("undefined)");
break;
case "getTypeName":
writer.append("(typeof ");
context.writeExpr(context.getArgument(0));
writer.append(")");
break;
case "get":
context.writeExpr(context.getArgument(0));
renderProperty(context.getArgument(1), context);
break;
case "set":
writer.append('(');
context.writeExpr(context.getArgument(0));
renderProperty(context.getArgument(1), context);
writer.ws().append('=').ws();
context.writeExpr(context.getArgument(2));
writer.append(')');
break;
case "invoke":
context.writeExpr(context.getArgument(0));
renderProperty(context.getArgument(1), context);
writer.append('(');
for (int i = 2; i < context.argumentCount(); ++i) {
if (i > 2) {
writer.append(',').ws();
}
context.writeExpr(context.getArgument(i));
}
writer.append(')');
break;
case "wrap":
case "unwrap":
context.writeExpr(context.getArgument(0));
break; break;
} }
} }
@ -80,15 +98,51 @@ public class JSNativeGenerator implements Generator {
writer.append("return result;").softNewLine(); writer.append("return result;").softNewLine();
} }
private void generateInvoke(GeneratorContext context, SourceWriter writer, int argNum) throws IOException { private void renderProperty(Expr property, InjectorContext context) throws IOException {
writer.append("return ").append(context.getParameterName(1)).append("[") SourceWriter writer = context.getWriter();
.append(context.getParameterName(2)).append("]("); String name = extractPropertyName(property);
for (int i = 0; i < argNum; ++i) { if (name == null) {
if (i > 0) { writer.append('[');
writer.append(",").ws(); context.writeExpr(property);
} writer.append(']');
writer.append(context.getParameterName(i + 3)); } else if (!isIdentifier(name)) {
} writer.append("[\"");
writer.append(");").softNewLine(); context.writeEscaped(name);
writer.append("\"]");
} else {
writer.append(".").append(name);
}
}
private String extractPropertyName(Expr propertyName) {
if (!(propertyName instanceof InvocationExpr)) {
return null;
}
InvocationExpr invoke = (InvocationExpr)propertyName;
if (!invoke.getMethod().getClassName().equals(JS.class.getName())) {
return null;
}
if (!invoke.getMethod().getName().equals("wrap") ||
!invoke.getMethod().getDescriptor().parameterType(0).isObject("java.lang.String")) {
return null;
}
Expr arg = invoke.getArguments().get(0);
if (!(arg instanceof ConstantExpr)) {
return null;
}
ConstantExpr constant = (ConstantExpr)arg;
return constant.getValue() instanceof String ? (String)constant.getValue() : null;
}
private boolean isIdentifier(String name) {
if (name.isEmpty() || !Character.isJavaIdentifierStart(name.charAt(0))) {
return false;
}
for (int i = 1; i < name.length(); ++i) {
if (!Character.isJavaIdentifierPart(name.charAt(i))) {
return false;
}
}
return true;
} }
} }