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

View File

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

View File

@ -16,13 +16,20 @@
package org.teavm.javascript;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.teavm.codegen.NamingException;
import org.teavm.codegen.NamingStrategy;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.ast.*;
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.*;
/**
@ -34,12 +41,23 @@ public class Renderer implements ExprVisitor, StatementVisitor {
private NamingStrategy naming;
private SourceWriter writer;
private ClassHolderSource classSource;
private ClassLoader classLoader;
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.writer = writer;
this.classSource = classSource;
this.classLoader = classLoader;
}
public SourceWriter getWriter() {
@ -193,6 +211,7 @@ public class Renderer implements ExprVisitor, StatementVisitor {
}
writer.outdent().append("}").newLine();
for (MethodNode method : cls.getMethods()) {
cls.getMethods();
if (!method.getModifiers().contains(NodeModifier.STATIC)) {
renderDeclaration(method);
}
@ -947,6 +966,11 @@ public class Renderer implements ExprVisitor, StatementVisitor {
@Override
public void visit(InvocationExpr expr) {
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 name = naming.getNameFor(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);
}
}
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);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native JSObject getTypeName(JSObject obj);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native JSObject getGlobal();
@GeneratedBy(JSNativeGenerator.class)
public static native JSObject wrap(String str);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(char c);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(int num);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(float num);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(double num);
public static <T extends JSObject> JSArray<T> wrap(T[] array) {
@ -90,7 +90,7 @@ public final class JS {
return result;
}
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native boolean unwrapBoolean(JSObject obj);
public static byte unwrapByte(JSObject obj) {
@ -101,53 +101,53 @@ public final class JS {
return (short)unwrapInt(obj);
}
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native int unwrapInt(JSObject obj);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native float unwrapFloat(JSObject obj);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native double unwrapDouble(JSObject obj);
@GeneratedBy(JSNativeGenerator.class)
public static native String unwrapString(JSObject obj);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native char unwrapCharacter(JSObject obj);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native boolean isUndefined(JSObject obj);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
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);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
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);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
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,
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,
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,
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);
@GeneratedBy(JSNativeGenerator.class)
@InjectedBy(JSNativeGenerator.class)
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 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.MethodReference;
@ -24,47 +27,62 @@ import org.teavm.model.MethodReference;
*
* @author Alexey Andreev
*/
public class JSNativeGenerator implements Generator {
public class JSNativeGenerator implements Generator, Injector {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef)
throws IOException {
switch (methodRef.getName()) {
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")) {
if (methodRef.getName().equals("wrap")) {
generateWrapString(context, writer);
} else {
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")) {
} else if (methodRef.getName().equals("unwrapString")) {
writer.append("return $rt_str(").append(context.getParameterName(1)).append(");")
.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;
}
}
@ -80,15 +98,51 @@ public class JSNativeGenerator implements Generator {
writer.append("return result;").softNewLine();
}
private void generateInvoke(GeneratorContext context, SourceWriter writer, int argNum) throws IOException {
writer.append("return ").append(context.getParameterName(1)).append("[")
.append(context.getParameterName(2)).append("](");
for (int i = 0; i < argNum; ++i) {
if (i > 0) {
writer.append(",").ws();
private void renderProperty(Expr property, InjectorContext context) throws IOException {
SourceWriter writer = context.getWriter();
String name = extractPropertyName(property);
if (name == null) {
writer.append('[');
context.writeExpr(property);
writer.append(']');
} else if (!isIdentifier(name)) {
writer.append("[\"");
context.writeEscaped(name);
writer.append("\"]");
} else {
writer.append(".").append(name);
}
writer.append(context.getParameterName(i + 3));
}
writer.append(");").softNewLine();
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;
}
}