mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-23 00:24:11 -08:00
Improve JSBody implementation
This commit is contained in:
parent
d4309bb564
commit
49f05cbb6e
|
@ -656,6 +656,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
|
||||||
}
|
}
|
||||||
writer.append(";").softNewLine();
|
writer.append(";").softNewLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
end = true;
|
end = true;
|
||||||
currentPart = 0;
|
currentPart = 0;
|
||||||
method.getBody().acceptVisitor(Renderer.this);
|
method.getBody().acceptVisitor(Renderer.this);
|
||||||
|
|
|
@ -95,7 +95,7 @@ public class AstWriter {
|
||||||
private static final int PRECEDENCE_ASSIGN = 17;
|
private static final int PRECEDENCE_ASSIGN = 17;
|
||||||
private static final int PRECEDENCE_COMMA = 18;
|
private static final int PRECEDENCE_COMMA = 18;
|
||||||
private SourceWriter writer;
|
private SourceWriter writer;
|
||||||
private Map<String, String> nameMap = new HashMap<>();
|
private Map<String, NameEmitter> nameMap = new HashMap<>();
|
||||||
private Set<String> aliases = new HashSet<>();
|
private Set<String> aliases = new HashSet<>();
|
||||||
|
|
||||||
public AstWriter(SourceWriter writer) {
|
public AstWriter(SourceWriter writer) {
|
||||||
|
@ -107,13 +107,13 @@ public class AstWriter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (aliases.add(name)) {
|
if (aliases.add(name)) {
|
||||||
nameMap.put(name, name);
|
nameMap.put(name, () -> writer.append(name));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int i = 0;; ++i) {
|
for (int i = 0;; ++i) {
|
||||||
String alias = name + "_" + i;
|
String alias = name + "_" + i;
|
||||||
if (aliases.add(alias)) {
|
if (aliases.add(alias)) {
|
||||||
nameMap.put(name, alias);
|
nameMap.put(name, () -> writer.append(alias));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,16 @@ public class AstWriter {
|
||||||
if (!aliases.add(alias)) {
|
if (!aliases.add(alias)) {
|
||||||
throw new IllegalArgumentException("Alias " + alias + " is already occupied");
|
throw new IllegalArgumentException("Alias " + alias + " is already occupied");
|
||||||
}
|
}
|
||||||
nameMap.put(name, alias);
|
nameMap.put(name, () -> writer.append(alias));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reserveName(String name) {
|
||||||
|
aliases.add(name);
|
||||||
|
nameMap.put(name, () -> writer.append(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void declareNameEmitter(String name, NameEmitter emitter) {
|
||||||
|
nameMap.put(name, emitter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hoist(AstNode node) {
|
public void hoist(AstNode node) {
|
||||||
|
@ -181,7 +190,11 @@ public class AstWriter {
|
||||||
writer.append("false");
|
writer.append("false");
|
||||||
break;
|
break;
|
||||||
case Token.THIS:
|
case Token.THIS:
|
||||||
writer.append(nameMap.containsKey("this") ? nameMap.get("this") : "this");
|
if (nameMap.containsKey("this")) {
|
||||||
|
nameMap.get("this").emit();
|
||||||
|
} else {
|
||||||
|
writer.append("this");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Token.NULL:
|
case Token.NULL:
|
||||||
writer.append("null");
|
writer.append("null");
|
||||||
|
@ -481,7 +494,7 @@ 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, String> oldNameMap = nameMap;
|
Map<String, NameEmitter> oldNameMap = nameMap;
|
||||||
nameMap = Collections.emptyMap();
|
nameMap = Collections.emptyMap();
|
||||||
print(node.getRight());
|
print(node.getRight());
|
||||||
nameMap = oldNameMap;
|
nameMap = oldNameMap;
|
||||||
|
@ -583,11 +596,11 @@ public class AstWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void print(Name node) throws IOException {
|
private void print(Name node) throws IOException {
|
||||||
String alias = nameMap.get(node.getIdentifier());
|
NameEmitter alias = nameMap.get(node.getIdentifier());
|
||||||
if (alias == null) {
|
if (alias == null) {
|
||||||
alias = node.getIdentifier();
|
alias = () -> writer.append(node.getIdentifier());
|
||||||
}
|
}
|
||||||
writer.append(alias);
|
alias.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void print(RegExpLiteral node) throws IOException {
|
private void print(RegExpLiteral node) throws IOException {
|
||||||
|
@ -618,7 +631,7 @@ public class AstWriter {
|
||||||
} else if (node.isSetterMethod()) {
|
} else if (node.isSetterMethod()) {
|
||||||
writer.append("set ");
|
writer.append("set ");
|
||||||
}
|
}
|
||||||
Map<String, String> oldNameMap = nameMap;
|
Map<String, NameEmitter> oldNameMap = nameMap;
|
||||||
nameMap = Collections.emptyMap();
|
nameMap = Collections.emptyMap();
|
||||||
print(node.getLeft());
|
print(node.getLeft());
|
||||||
nameMap = oldNameMap;
|
nameMap = oldNameMap;
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* 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.io.IOException;
|
||||||
|
import org.mozilla.javascript.ast.AstRoot;
|
||||||
|
import org.teavm.codegen.SourceWriter;
|
||||||
|
import org.teavm.javascript.spi.GeneratorContext;
|
||||||
|
import org.teavm.javascript.spi.InjectorContext;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Alexey Andreev
|
||||||
|
*/
|
||||||
|
class JSBodyAstEmitter implements JSBodyEmitter {
|
||||||
|
private boolean isStatic;
|
||||||
|
private AstRoot ast;
|
||||||
|
private String[] parameterNames;
|
||||||
|
|
||||||
|
public JSBodyAstEmitter(boolean isStatic, AstRoot ast, String[] parameterNames) {
|
||||||
|
this.isStatic = isStatic;
|
||||||
|
this.ast = ast;
|
||||||
|
this.parameterNames = parameterNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void emit(InjectorContext context) throws IOException {
|
||||||
|
AstWriter astWriter = new AstWriter(context.getWriter());
|
||||||
|
int paramIndex = 0;
|
||||||
|
if (!isStatic) {
|
||||||
|
int index = paramIndex++;
|
||||||
|
astWriter.declareNameEmitter("this", () -> context.writeExpr(context.getArgument(index)));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < parameterNames.length; ++i) {
|
||||||
|
int index = paramIndex++;
|
||||||
|
astWriter.declareNameEmitter(parameterNames[i], () -> context.writeExpr(context.getArgument(index)));
|
||||||
|
}
|
||||||
|
astWriter.hoist(ast);
|
||||||
|
astWriter.print(ast);
|
||||||
|
context.getWriter().softNewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
|
||||||
|
AstWriter astWriter = new AstWriter(writer);
|
||||||
|
int paramIndex = 1;
|
||||||
|
if (!isStatic) {
|
||||||
|
int index = paramIndex++;
|
||||||
|
astWriter.declareNameEmitter("this", () -> writer.append(context.getParameterName(index)));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < parameterNames.length; ++i) {
|
||||||
|
int index = paramIndex++;
|
||||||
|
astWriter.declareNameEmitter(parameterNames[i], () -> writer.append(context.getParameterName(index)));
|
||||||
|
}
|
||||||
|
astWriter.hoist(ast);
|
||||||
|
astWriter.print(ast);
|
||||||
|
writer.softNewLine();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* 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.io.IOException;
|
||||||
|
import org.teavm.codegen.SourceWriter;
|
||||||
|
import org.teavm.javascript.spi.GeneratorContext;
|
||||||
|
import org.teavm.javascript.spi.InjectorContext;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Alexey Andreev
|
||||||
|
*/
|
||||||
|
class JSBodyBloatedEmitter implements JSBodyEmitter {
|
||||||
|
private boolean isStatic;
|
||||||
|
private MethodReference method;
|
||||||
|
private String script;
|
||||||
|
private String[] parameterNames;
|
||||||
|
|
||||||
|
public JSBodyBloatedEmitter(boolean isStatic, MethodReference method, String script, String[] parameterNames) {
|
||||||
|
this.isStatic = isStatic;
|
||||||
|
this.method = method;
|
||||||
|
this.script = script;
|
||||||
|
this.parameterNames = parameterNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void emit(InjectorContext context) throws IOException {
|
||||||
|
emit(context.getWriter(), index -> context.writeExpr(context.getArgument(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
|
||||||
|
emit(writer, index -> writer.append(context.getParameterName(index + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void emit(SourceWriter writer, EmissionStrategy strategy) throws IOException {
|
||||||
|
int bodyParamCount = isStatic ? method.parameterCount() : method.parameterCount() - 1;
|
||||||
|
|
||||||
|
writer.append("if (!").appendMethodBody(method).append(".$native)").ws().append('{').indent().newLine();
|
||||||
|
writer.appendMethodBody(method).append(".$native").ws().append('=').ws().append("function(");
|
||||||
|
int count = method.parameterCount();
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
writer.append(',').ws();
|
||||||
|
}
|
||||||
|
writer.append('_').append(i);
|
||||||
|
}
|
||||||
|
writer.append(')').ws().append('{').softNewLine().indent();
|
||||||
|
|
||||||
|
writer.append("return (function(");
|
||||||
|
for (int i = 0; i < bodyParamCount; ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
writer.append(',').ws();
|
||||||
|
}
|
||||||
|
String name = parameterNames[i];
|
||||||
|
writer.append(name);
|
||||||
|
}
|
||||||
|
writer.append(')').ws().append('{').softNewLine().indent();
|
||||||
|
writer.append(script).softNewLine();
|
||||||
|
writer.outdent().append("})");
|
||||||
|
if (!isStatic) {
|
||||||
|
writer.append(".call");
|
||||||
|
}
|
||||||
|
writer.append('(');
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
writer.append(',').ws();
|
||||||
|
}
|
||||||
|
writer.append('_').append(i);
|
||||||
|
}
|
||||||
|
writer.append(");").softNewLine();
|
||||||
|
writer.outdent().append("};").softNewLine();
|
||||||
|
writer.appendMethodBody(method).ws().append('=').ws().appendMethodBody(method).append(".$native;")
|
||||||
|
.softNewLine();
|
||||||
|
writer.outdent().append("}").softNewLine();
|
||||||
|
|
||||||
|
writer.append("return ").appendMethodBody(method).append('(');
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
writer.append(',').ws();
|
||||||
|
}
|
||||||
|
strategy.emitArgument(i);
|
||||||
|
}
|
||||||
|
writer.append(");").softNewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmissionStrategy {
|
||||||
|
void emitArgument(int argument) throws IOException;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.io.IOException;
|
||||||
|
import org.teavm.codegen.SourceWriter;
|
||||||
|
import org.teavm.javascript.spi.GeneratorContext;
|
||||||
|
import org.teavm.javascript.spi.InjectorContext;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Alexey Andreev
|
||||||
|
*/
|
||||||
|
interface JSBodyEmitter {
|
||||||
|
void emit(InjectorContext context) throws IOException;
|
||||||
|
|
||||||
|
void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException;
|
||||||
|
}
|
|
@ -16,113 +16,29 @@
|
||||||
package org.teavm.jso.plugin;
|
package org.teavm.jso.plugin;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
|
||||||
import java.util.List;
|
|
||||||
import org.mozilla.javascript.CompilerEnvirons;
|
|
||||||
import org.mozilla.javascript.Context;
|
|
||||||
import org.mozilla.javascript.ast.AstRoot;
|
|
||||||
import org.teavm.codegen.SourceWriter;
|
import org.teavm.codegen.SourceWriter;
|
||||||
import org.teavm.javascript.spi.Generator;
|
import org.teavm.javascript.spi.Generator;
|
||||||
import org.teavm.javascript.spi.GeneratorContext;
|
import org.teavm.javascript.spi.GeneratorContext;
|
||||||
import org.teavm.model.AnnotationReader;
|
import org.teavm.javascript.spi.Injector;
|
||||||
import org.teavm.model.AnnotationValue;
|
import org.teavm.javascript.spi.InjectorContext;
|
||||||
import org.teavm.model.CallLocation;
|
|
||||||
import org.teavm.model.ClassReader;
|
|
||||||
import org.teavm.model.MethodReader;
|
|
||||||
import org.teavm.model.MethodReference;
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Alexey Andreev
|
* @author Alexey Andreev
|
||||||
*/
|
*/
|
||||||
public class JSBodyGenerator implements Generator {
|
public class JSBodyGenerator implements Injector, Generator {
|
||||||
|
@Override
|
||||||
|
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
||||||
|
JSBodyRepository emitterRepository = context.getService(JSBodyRepository.class);
|
||||||
|
JSBodyEmitter emitter = emitterRepository.emitters.get(methodRef);
|
||||||
|
emitter.emit(context);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
|
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
|
||||||
ClassReader cls = context.getClassSource().get(methodRef.getClassName());
|
JSBodyRepository emitterRepository = context.getService(JSBodyRepository.class);
|
||||||
MethodReader method = cls.getMethod(methodRef.getDescriptor());
|
JSBodyEmitter emitter = emitterRepository.emitters.get(methodRef);
|
||||||
AnnotationReader annot = method.getAnnotations().get(JSBodyImpl.class.getName());
|
emitter.emit(context, writer, methodRef);
|
||||||
boolean isStatic = annot.getValue("isStatic").getBoolean();
|
|
||||||
List<AnnotationValue> paramNames = annot.getValue("params").getList();
|
|
||||||
String script = annot.getValue("script").getString();
|
|
||||||
|
|
||||||
TeaVMErrorReporter errorReporter = new TeaVMErrorReporter(context.getDiagnostics(),
|
|
||||||
new CallLocation(methodRef));
|
|
||||||
CompilerEnvirons env = new CompilerEnvirons();
|
|
||||||
env.setRecoverFromErrors(true);
|
|
||||||
env.setLanguageVersion(Context.VERSION_1_8);
|
|
||||||
env.setIdeMode(true);
|
|
||||||
JSParser parser = new JSParser(env, errorReporter);
|
|
||||||
parser.enterFunction();
|
|
||||||
AstRoot rootNode = parser.parse(new StringReader(script), null, 0);
|
|
||||||
parser.exitFunction();
|
|
||||||
if (errorReporter.hasErrors()) {
|
|
||||||
generateBloated(context, writer, methodRef, method, isStatic, paramNames, script);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AstWriter astWriter = new AstWriter(writer);
|
|
||||||
int paramIndex = 1;
|
|
||||||
if (!isStatic) {
|
|
||||||
astWriter.declareAlias("this", context.getParameterName(paramIndex++));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < paramNames.size(); ++i) {
|
|
||||||
astWriter.declareAlias(paramNames.get(i).getString(), context.getParameterName(paramIndex++));
|
|
||||||
}
|
|
||||||
astWriter.hoist(rootNode);
|
|
||||||
astWriter.print(rootNode);
|
|
||||||
writer.softNewLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateBloated(GeneratorContext context, SourceWriter writer, MethodReference methodRef,
|
|
||||||
MethodReader method, boolean isStatic, List<AnnotationValue> paramNames, String script)
|
|
||||||
throws IOException {
|
|
||||||
int bodyParamCount = isStatic ? method.parameterCount() : method.parameterCount() - 1;
|
|
||||||
|
|
||||||
writer.append("if (!").appendMethodBody(methodRef).append(".$native)").ws().append('{').indent().newLine();
|
|
||||||
writer.appendMethodBody(methodRef).append(".$native").ws().append('=').ws().append("function(");
|
|
||||||
int count = method.parameterCount();
|
|
||||||
for (int i = 0; i < count; ++i) {
|
|
||||||
if (i > 0) {
|
|
||||||
writer.append(',').ws();
|
|
||||||
}
|
|
||||||
writer.append('_').append(context.getParameterName(i + 1));
|
|
||||||
}
|
|
||||||
writer.append(')').ws().append('{').softNewLine().indent();
|
|
||||||
|
|
||||||
writer.append("return (function(");
|
|
||||||
for (int i = 0; i < bodyParamCount; ++i) {
|
|
||||||
if (i > 0) {
|
|
||||||
writer.append(',').ws();
|
|
||||||
}
|
|
||||||
String name = paramNames.get(i).getString();
|
|
||||||
writer.append(name);
|
|
||||||
}
|
|
||||||
writer.append(')').ws().append('{').softNewLine().indent();
|
|
||||||
writer.append(script).softNewLine();
|
|
||||||
writer.outdent().append("})");
|
|
||||||
if (!isStatic) {
|
|
||||||
writer.append(".call");
|
|
||||||
}
|
|
||||||
writer.append('(');
|
|
||||||
for (int i = 0; i < count; ++i) {
|
|
||||||
if (i > 0) {
|
|
||||||
writer.append(',').ws();
|
|
||||||
}
|
|
||||||
writer.append('_').append(context.getParameterName(i + 1));
|
|
||||||
}
|
|
||||||
writer.append(");").softNewLine();
|
|
||||||
writer.outdent().append("};").softNewLine();
|
|
||||||
writer.appendMethodBody(methodRef).ws().append('=').ws().appendMethodBody(methodRef).append(".$native;")
|
|
||||||
.softNewLine();
|
|
||||||
writer.outdent().append("}").softNewLine();
|
|
||||||
|
|
||||||
writer.append("return ").appendMethodBody(methodRef).append('(');
|
|
||||||
for (int i = 0; i < count; ++i) {
|
|
||||||
if (i > 0) {
|
|
||||||
writer.append(',').ws();
|
|
||||||
}
|
|
||||||
writer.append(context.getParameterName(i + 1));
|
|
||||||
}
|
|
||||||
writer.append(");").softNewLine();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,6 @@ import java.lang.annotation.Target;
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
@interface JSBodyImpl {
|
@interface JSBodyRef {
|
||||||
String[] params();
|
String method();
|
||||||
|
|
||||||
String script();
|
|
||||||
|
|
||||||
boolean isStatic();
|
|
||||||
}
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Alexey Andreev
|
||||||
|
*/
|
||||||
|
class JSBodyRepository {
|
||||||
|
public final Map<MethodReference, JSBodyEmitter> emitters = new HashMap<>();
|
||||||
|
public final Map<MethodReference, MethodReference> methodMap = new HashMap<>();
|
||||||
|
public final Set<MethodReference> processedMethods = new HashSet<>();
|
||||||
|
}
|
|
@ -25,7 +25,9 @@ import org.teavm.vm.spi.TeaVMPlugin;
|
||||||
public class JSOPlugin implements TeaVMPlugin {
|
public class JSOPlugin implements TeaVMPlugin {
|
||||||
@Override
|
@Override
|
||||||
public void install(TeaVMHost host) {
|
public void install(TeaVMHost host) {
|
||||||
host.add(new JSObjectClassTransformer());
|
JSBodyRepository repository = new JSBodyRepository();
|
||||||
|
host.registerService(JSBodyRepository.class, repository);
|
||||||
|
host.add(new JSObjectClassTransformer(repository));
|
||||||
JSODependencyListener dependencyListener = new JSODependencyListener();
|
JSODependencyListener dependencyListener = new JSODependencyListener();
|
||||||
JSOAliasRenderer aliasRenderer = new JSOAliasRenderer(dependencyListener);
|
JSOAliasRenderer aliasRenderer = new JSOAliasRenderer(dependencyListener);
|
||||||
host.add(dependencyListener);
|
host.add(dependencyListener);
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
package org.teavm.jso.plugin;
|
package org.teavm.jso.plugin;
|
||||||
|
|
||||||
import org.teavm.diagnostics.Diagnostics;
|
import org.teavm.diagnostics.Diagnostics;
|
||||||
import org.teavm.jso.JSBody;
|
import org.teavm.model.ClassHolder;
|
||||||
import org.teavm.model.*;
|
import org.teavm.model.ClassHolderTransformer;
|
||||||
|
import org.teavm.model.ClassReaderSource;
|
||||||
|
import org.teavm.model.MethodHolder;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -25,6 +28,11 @@ import org.teavm.model.*;
|
||||||
*/
|
*/
|
||||||
public class JSObjectClassTransformer implements ClassHolderTransformer {
|
public class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||||
private JavascriptNativeProcessor processor;
|
private JavascriptNativeProcessor processor;
|
||||||
|
private JSBodyRepository repository;
|
||||||
|
|
||||||
|
public JSObjectClassTransformer(JSBodyRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
|
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
|
||||||
|
@ -46,12 +54,10 @@ public class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
|
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
|
||||||
if (method.getAnnotations().get(JSBody.class.getName()) != null) {
|
if (method.getProgram() != null) {
|
||||||
processor.processJSBody(cls, method);
|
processor.processProgram(repository, method);
|
||||||
} else if (method.getProgram() != null
|
|
||||||
&& method.getAnnotations().get(JSBodyImpl.class.getName()) == null) {
|
|
||||||
processor.processProgram(method);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
processor.createJSMethods(repository, cls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.jso.plugin;
|
package org.teavm.jso.plugin;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -23,6 +25,9 @@ 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 java.util.function.Function;
|
||||||
|
import org.mozilla.javascript.CompilerEnvirons;
|
||||||
|
import org.mozilla.javascript.Context;
|
||||||
|
import org.mozilla.javascript.ast.AstRoot;
|
||||||
import org.teavm.diagnostics.Diagnostics;
|
import org.teavm.diagnostics.Diagnostics;
|
||||||
import org.teavm.javascript.spi.GeneratedBy;
|
import org.teavm.javascript.spi.GeneratedBy;
|
||||||
import org.teavm.javascript.spi.Sync;
|
import org.teavm.javascript.spi.Sync;
|
||||||
|
@ -60,7 +65,6 @@ import org.teavm.model.Variable;
|
||||||
import org.teavm.model.instructions.AssignInstruction;
|
import org.teavm.model.instructions.AssignInstruction;
|
||||||
import org.teavm.model.instructions.CastInstruction;
|
import org.teavm.model.instructions.CastInstruction;
|
||||||
import org.teavm.model.instructions.ClassConstantInstruction;
|
import org.teavm.model.instructions.ClassConstantInstruction;
|
||||||
import org.teavm.model.instructions.ExitInstruction;
|
|
||||||
import org.teavm.model.instructions.InvocationType;
|
import org.teavm.model.instructions.InvocationType;
|
||||||
import org.teavm.model.instructions.InvokeInstruction;
|
import org.teavm.model.instructions.InvokeInstruction;
|
||||||
import org.teavm.model.instructions.StringConstantInstruction;
|
import org.teavm.model.instructions.StringConstantInstruction;
|
||||||
|
@ -275,7 +279,7 @@ class JavascriptNativeProcessor {
|
||||||
return staticSignature;
|
return staticSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processProgram(MethodHolder methodToProcess) {
|
public void processProgram(JSBodyRepository repository, MethodHolder methodToProcess) {
|
||||||
program = methodToProcess.getProgram();
|
program = methodToProcess.getProgram();
|
||||||
for (int i = 0; i < program.basicBlockCount(); ++i) {
|
for (int i = 0; i < program.basicBlockCount(); ++i) {
|
||||||
BasicBlock block = program.basicBlockAt(i);
|
BasicBlock block = program.basicBlockAt(i);
|
||||||
|
@ -286,146 +290,218 @@ class JavascriptNativeProcessor {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
InvokeInstruction invoke = (InvokeInstruction) insn;
|
InvokeInstruction invoke = (InvokeInstruction) insn;
|
||||||
if (!nativeRepos.isJavaScriptClass(invoke.getMethod().getClassName())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
replacement.clear();
|
|
||||||
|
|
||||||
MethodReader method = getMethod(invoke.getMethod());
|
MethodReader method = getMethod(invoke.getMethod());
|
||||||
if (method == null || method.hasModifier(ElementModifier.STATIC)) {
|
if (method == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method.hasModifier(ElementModifier.FINAL)) {
|
|
||||||
MethodReader overriden = getOverridenMethod(method);
|
|
||||||
if (overriden != null) {
|
|
||||||
CallLocation callLocation = new CallLocation(methodToProcess.getReference(),
|
|
||||||
insn.getLocation());
|
|
||||||
diagnostics.error(callLocation, "JS final method {{m0}} overrides {{M1}}. "
|
|
||||||
+ "Overriding final method of overlay types is prohibited.",
|
|
||||||
method.getReference(), overriden.getReference());
|
|
||||||
}
|
|
||||||
if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
|
|
||||||
invoke.setMethod(new MethodReference(method.getOwnerName(), method.getName() + "$static",
|
|
||||||
getStaticSignature(method.getReference())));
|
|
||||||
invoke.getArguments().add(0, invoke.getInstance());
|
|
||||||
invoke.setInstance(null);
|
|
||||||
}
|
|
||||||
invoke.setType(InvocationType.SPECIAL);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
|
CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
|
||||||
if (method.getAnnotations().get(JSProperty.class.getName()) != null) {
|
replacement.clear();
|
||||||
if (isProperGetter(method.getDescriptor())) {
|
if (processInvocation(repository, method, callLocation, invoke)) {
|
||||||
String propertyName;
|
block.getInstructions().set(j, replacement.get(0));
|
||||||
AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName());
|
block.getInstructions().addAll(j + 1, replacement.subList(1, replacement.size()));
|
||||||
if (annot.getValue("value") != null) {
|
j += replacement.size() - 1;
|
||||||
propertyName = annot.getValue("value").getString();
|
|
||||||
} else {
|
|
||||||
propertyName = method.getName().charAt(0) == 'i' ? cutPrefix(method.getName(), 2)
|
|
||||||
: cutPrefix(method.getName(), 3);
|
|
||||||
}
|
|
||||||
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
|
||||||
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
|
|
||||||
if (result != null) {
|
|
||||||
result = unwrap(callLocation, result, method.getResultType());
|
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
|
||||||
}
|
|
||||||
} else if (isProperSetter(method.getDescriptor())) {
|
|
||||||
String propertyName;
|
|
||||||
AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName());
|
|
||||||
if (annot.getValue("value") != null) {
|
|
||||||
propertyName = annot.getValue("value").getString();
|
|
||||||
} else {
|
|
||||||
propertyName = cutPrefix(method.getName(), 3);
|
|
||||||
}
|
|
||||||
Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0),
|
|
||||||
method.parameterType(0));
|
|
||||||
addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation());
|
|
||||||
} else {
|
|
||||||
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript property "
|
|
||||||
+ "declaration", invoke.getMethod());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (method.getAnnotations().get(JSIndexer.class.getName()) != null) {
|
|
||||||
if (isProperGetIndexer(method.getDescriptor())) {
|
|
||||||
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
|
||||||
addIndexerGet(invoke.getInstance(), wrap(invoke.getArguments().get(0),
|
|
||||||
method.parameterType(0), invoke.getLocation()), result, invoke.getLocation());
|
|
||||||
if (result != null) {
|
|
||||||
result = unwrap(callLocation, result, method.getResultType());
|
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
|
||||||
}
|
|
||||||
} else if (isProperSetIndexer(method.getDescriptor())) {
|
|
||||||
Variable index = wrap(invoke.getArguments().get(0), method.parameterType(0),
|
|
||||||
invoke.getLocation());
|
|
||||||
Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1),
|
|
||||||
invoke.getLocation());
|
|
||||||
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
|
|
||||||
} else {
|
|
||||||
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript indexer "
|
|
||||||
+ "declaration", invoke.getMethod());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String name = method.getName();
|
|
||||||
|
|
||||||
AnnotationReader methodAnnot = method.getAnnotations().get(JSMethod.class.getName());
|
|
||||||
if (methodAnnot != null) {
|
|
||||||
AnnotationValue redefinedMethodName = methodAnnot.getValue("value");
|
|
||||||
if (redefinedMethodName != null) {
|
|
||||||
name = redefinedMethodName.getString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (method.getResultType() != ValueType.VOID && !isSupportedType(method.getResultType())) {
|
|
||||||
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
|
|
||||||
+ "declaration", invoke.getMethod());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ValueType arg : method.getParameterTypes()) {
|
|
||||||
if (!isSupportedType(arg)) {
|
|
||||||
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
|
|
||||||
+ "or constructor declaration", invoke.getMethod());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
|
||||||
InvokeInstruction newInvoke = new InvokeInstruction();
|
|
||||||
ValueType[] signature = new ValueType[method.parameterCount() + 3];
|
|
||||||
Arrays.fill(signature, ValueType.object(JSObject.class.getName()));
|
|
||||||
newInvoke.setMethod(new MethodReference(JS.class.getName(), "invoke", signature));
|
|
||||||
newInvoke.setType(InvocationType.SPECIAL);
|
|
||||||
newInvoke.setReceiver(result);
|
|
||||||
newInvoke.getArguments().add(invoke.getInstance());
|
|
||||||
newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()),
|
|
||||||
invoke.getLocation()));
|
|
||||||
newInvoke.setLocation(invoke.getLocation());
|
|
||||||
for (int k = 0; k < invoke.getArguments().size(); ++k) {
|
|
||||||
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k),
|
|
||||||
method.parameterType(k));
|
|
||||||
newInvoke.getArguments().add(arg);
|
|
||||||
}
|
|
||||||
replacement.add(newInvoke);
|
|
||||||
if (result != null) {
|
|
||||||
result = unwrap(callLocation, result, method.getResultType());
|
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
block.getInstructions().set(j, replacement.get(0));
|
|
||||||
block.getInstructions().addAll(j + 1, replacement.subList(1, replacement.size()));
|
|
||||||
j += replacement.size() - 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processJSBody(ClassHolder cls, MethodHolder methodToProcess) {
|
private boolean processInvocation(JSBodyRepository repository, MethodReader method, CallLocation callLocation,
|
||||||
|
InvokeInstruction invoke) {
|
||||||
|
if (method.getAnnotations().get(JSBody.class.getName()) != null) {
|
||||||
|
return processJSBodyInvocation(repository, method, callLocation, invoke);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nativeRepos.isJavaScriptClass(invoke.getMethod().getClassName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == null || method.hasModifier(ElementModifier.STATIC)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.hasModifier(ElementModifier.FINAL)) {
|
||||||
|
MethodReader overriden = getOverridenMethod(method);
|
||||||
|
if (overriden != null) {
|
||||||
|
diagnostics.error(callLocation, "JS final method {{m0}} overrides {{M1}}. "
|
||||||
|
+ "Overriding final method of overlay types is prohibited.",
|
||||||
|
method.getReference(), overriden.getReference());
|
||||||
|
}
|
||||||
|
if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
|
||||||
|
invoke.setMethod(new MethodReference(method.getOwnerName(), method.getName() + "$static",
|
||||||
|
getStaticSignature(method.getReference())));
|
||||||
|
invoke.getArguments().add(0, invoke.getInstance());
|
||||||
|
invoke.setInstance(null);
|
||||||
|
}
|
||||||
|
invoke.setType(InvocationType.SPECIAL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.getAnnotations().get(JSProperty.class.getName()) != null) {
|
||||||
|
return processProperty(method, callLocation, invoke);
|
||||||
|
} else if (method.getAnnotations().get(JSIndexer.class.getName()) != null) {
|
||||||
|
return processIndexer(method, callLocation, invoke);
|
||||||
|
} else {
|
||||||
|
return processMethod(method, callLocation, invoke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processJSBodyInvocation(JSBodyRepository repository, MethodReader method,
|
||||||
|
CallLocation callLocation, InvokeInstruction invoke) {
|
||||||
|
requireJSBody(repository, diagnostics, method);
|
||||||
|
MethodReference delegate = repository.methodMap.get(method.getReference());
|
||||||
|
if (delegate == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
||||||
|
InvokeInstruction newInvoke = new InvokeInstruction();
|
||||||
|
ValueType[] signature = new ValueType[method.parameterCount() + 3];
|
||||||
|
Arrays.fill(signature, ValueType.object(JSObject.class.getName()));
|
||||||
|
newInvoke.setMethod(delegate);
|
||||||
|
newInvoke.setType(InvocationType.SPECIAL);
|
||||||
|
newInvoke.setReceiver(result);
|
||||||
|
newInvoke.setLocation(invoke.getLocation());
|
||||||
|
if (invoke.getInstance() != null) {
|
||||||
|
Variable arg = wrapArgument(callLocation, invoke.getInstance(), ValueType.object(method.getOwnerName()));
|
||||||
|
newInvoke.getArguments().add(arg);
|
||||||
|
}
|
||||||
|
for (int k = 0; k < invoke.getArguments().size(); ++k) {
|
||||||
|
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k),
|
||||||
|
method.parameterType(k));
|
||||||
|
newInvoke.getArguments().add(arg);
|
||||||
|
}
|
||||||
|
replacement.add(newInvoke);
|
||||||
|
if (result != null) {
|
||||||
|
result = unwrap(callLocation, result, method.getResultType());
|
||||||
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processProperty(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
|
||||||
|
if (isProperGetter(method.getDescriptor())) {
|
||||||
|
String propertyName;
|
||||||
|
AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName());
|
||||||
|
if (annot.getValue("value") != null) {
|
||||||
|
propertyName = annot.getValue("value").getString();
|
||||||
|
} else {
|
||||||
|
propertyName = method.getName().charAt(0) == 'i' ? cutPrefix(method.getName(), 2)
|
||||||
|
: cutPrefix(method.getName(), 3);
|
||||||
|
}
|
||||||
|
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
||||||
|
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
|
||||||
|
if (result != null) {
|
||||||
|
result = unwrap(callLocation, result, method.getResultType());
|
||||||
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isProperSetter(method.getDescriptor())) {
|
||||||
|
String propertyName;
|
||||||
|
AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName());
|
||||||
|
if (annot.getValue("value") != null) {
|
||||||
|
propertyName = annot.getValue("value").getString();
|
||||||
|
} else {
|
||||||
|
propertyName = cutPrefix(method.getName(), 3);
|
||||||
|
}
|
||||||
|
Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0),
|
||||||
|
method.parameterType(0));
|
||||||
|
addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript property "
|
||||||
|
+ "declaration", invoke.getMethod());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
|
||||||
|
if (isProperGetIndexer(method.getDescriptor())) {
|
||||||
|
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
||||||
|
addIndexerGet(invoke.getInstance(), wrap(invoke.getArguments().get(0),
|
||||||
|
method.parameterType(0), invoke.getLocation()), result, invoke.getLocation());
|
||||||
|
if (result != null) {
|
||||||
|
result = unwrap(callLocation, result, method.getResultType());
|
||||||
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isProperSetIndexer(method.getDescriptor())) {
|
||||||
|
Variable index = wrap(invoke.getArguments().get(0), method.parameterType(0),
|
||||||
|
invoke.getLocation());
|
||||||
|
Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1),
|
||||||
|
invoke.getLocation());
|
||||||
|
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript indexer "
|
||||||
|
+ "declaration", invoke.getMethod());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processMethod(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
|
||||||
|
String name = method.getName();
|
||||||
|
|
||||||
|
AnnotationReader methodAnnot = method.getAnnotations().get(JSMethod.class.getName());
|
||||||
|
if (methodAnnot != null) {
|
||||||
|
AnnotationValue redefinedMethodName = methodAnnot.getValue("value");
|
||||||
|
if (redefinedMethodName != null) {
|
||||||
|
name = redefinedMethodName.getString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method.getResultType() != ValueType.VOID && !isSupportedType(method.getResultType())) {
|
||||||
|
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
|
||||||
|
+ "declaration", invoke.getMethod());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ValueType arg : method.getParameterTypes()) {
|
||||||
|
if (!isSupportedType(arg)) {
|
||||||
|
diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
|
||||||
|
+ "or constructor declaration", invoke.getMethod());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
||||||
|
InvokeInstruction newInvoke = new InvokeInstruction();
|
||||||
|
ValueType[] signature = new ValueType[method.parameterCount() + 3];
|
||||||
|
Arrays.fill(signature, ValueType.object(JSObject.class.getName()));
|
||||||
|
newInvoke.setMethod(new MethodReference(JS.class.getName(), "invoke", signature));
|
||||||
|
newInvoke.setType(InvocationType.SPECIAL);
|
||||||
|
newInvoke.setReceiver(result);
|
||||||
|
newInvoke.getArguments().add(invoke.getInstance());
|
||||||
|
newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()),
|
||||||
|
invoke.getLocation()));
|
||||||
|
newInvoke.setLocation(invoke.getLocation());
|
||||||
|
for (int k = 0; k < invoke.getArguments().size(); ++k) {
|
||||||
|
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k),
|
||||||
|
method.parameterType(k));
|
||||||
|
newInvoke.getArguments().add(arg);
|
||||||
|
}
|
||||||
|
replacement.add(newInvoke);
|
||||||
|
if (result != null) {
|
||||||
|
result = unwrap(callLocation, result, method.getResultType());
|
||||||
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireJSBody(JSBodyRepository repository, Diagnostics diagnostics, MethodReader methodToProcess) {
|
||||||
|
if (!repository.processedMethods.add(methodToProcess.getReference())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processJSBody(repository, diagnostics, methodToProcess);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processJSBody(JSBodyRepository repository, Diagnostics diagnostics, MethodReader methodToProcess) {
|
||||||
CallLocation location = new CallLocation(methodToProcess.getReference());
|
CallLocation location = new CallLocation(methodToProcess.getReference());
|
||||||
boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC);
|
boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC);
|
||||||
|
|
||||||
// validate parameter names
|
// validate parameter names
|
||||||
AnnotationHolder bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName());
|
AnnotationReader bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName());
|
||||||
int jsParamCount = bodyAnnot.getValue("params").getList().size();
|
int jsParamCount = bodyAnnot.getValue("params").getList().size();
|
||||||
if (methodToProcess.parameterCount() != jsParamCount) {
|
if (methodToProcess.parameterCount() != jsParamCount) {
|
||||||
diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount()
|
diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount()
|
||||||
|
@ -433,10 +509,6 @@ class JavascriptNativeProcessor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove annotation and make non-native
|
|
||||||
methodToProcess.getAnnotations().remove(JSBody.class.getName());
|
|
||||||
methodToProcess.getModifiers().remove(ElementModifier.NATIVE);
|
|
||||||
|
|
||||||
// generate parameter types for original method and validate
|
// generate parameter types for original method and validate
|
||||||
int paramCount = methodToProcess.parameterCount();
|
int paramCount = methodToProcess.parameterCount();
|
||||||
if (!isStatic) {
|
if (!isStatic) {
|
||||||
|
@ -445,11 +517,11 @@ class JavascriptNativeProcessor {
|
||||||
ValueType[] paramTypes = new ValueType[paramCount];
|
ValueType[] paramTypes = new ValueType[paramCount];
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
if (!isStatic) {
|
if (!isStatic) {
|
||||||
ValueType paramType = ValueType.object(cls.getName());
|
ValueType paramType = ValueType.object(methodToProcess.getOwnerName());
|
||||||
paramTypes[offset++] = paramType;
|
paramTypes[offset++] = paramType;
|
||||||
if (!isSupportedType(paramType)) {
|
if (!isSupportedType(paramType)) {
|
||||||
diagnostics.error(location, "Non-static JSBody method {{m0}} is owned by non-JS class {{c1}}",
|
diagnostics.error(location, "Non-static JSBody method {{m0}} is owned by non-JS class {{c1}}",
|
||||||
methodToProcess.getReference(), cls.getName());
|
methodToProcess.getReference(), methodToProcess.getOwnerName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (methodToProcess.getResultType() != ValueType.VOID && !isSupportedType(methodToProcess.getResultType())) {
|
if (methodToProcess.getResultType() != ValueType.VOID && !isSupportedType(methodToProcess.getResultType())) {
|
||||||
|
@ -470,52 +542,58 @@ class JavascriptNativeProcessor {
|
||||||
: ValueType.parse(JSObject.class);
|
: ValueType.parse(JSObject.class);
|
||||||
|
|
||||||
// create proxy method
|
// create proxy method
|
||||||
MethodHolder proxyMethod = new MethodHolder("$js_body$_" + methodIndexGenerator++, proxyParamTypes);
|
MethodReference proxyMethod = new MethodReference(methodToProcess.getOwnerName(),
|
||||||
proxyMethod.getModifiers().add(ElementModifier.NATIVE);
|
methodToProcess.getName() + "$js_body$_" + methodIndexGenerator++, proxyParamTypes);
|
||||||
proxyMethod.getModifiers().add(ElementModifier.STATIC);
|
String script = bodyAnnot.getValue("script").getString();
|
||||||
AnnotationHolder genBodyAnnot = new AnnotationHolder(JSBodyImpl.class.getName());
|
String[] parameterNames = bodyAnnot.getValue("params").getList().stream()
|
||||||
genBodyAnnot.getValues().put("script", bodyAnnot.getValue("script"));
|
.map(ann -> ann.getString())
|
||||||
genBodyAnnot.getValues().put("params", bodyAnnot.getValue("params"));
|
.toArray(sz -> new String[sz]);
|
||||||
genBodyAnnot.getValues().put("isStatic", new AnnotationValue(isStatic));
|
|
||||||
AnnotationHolder generatorAnnot = new AnnotationHolder(GeneratedBy.class.getName());
|
|
||||||
generatorAnnot.getValues().put("value", new AnnotationValue(ValueType.parse(JSBodyGenerator.class)));
|
|
||||||
proxyMethod.getAnnotations().add(genBodyAnnot);
|
|
||||||
proxyMethod.getAnnotations().add(generatorAnnot);
|
|
||||||
cls.addMethod(proxyMethod);
|
|
||||||
|
|
||||||
// create program that invokes proxy method
|
// Parse JS script
|
||||||
program = new Program();
|
TeaVMErrorReporter errorReporter = new TeaVMErrorReporter(diagnostics,
|
||||||
BasicBlock block = program.createBasicBlock();
|
new CallLocation(methodToProcess.getReference()));
|
||||||
for (int i = 0; i < paramCount; ++i) {
|
CompilerEnvirons env = new CompilerEnvirons();
|
||||||
program.createVariable();
|
env.setRecoverFromErrors(true);
|
||||||
|
env.setLanguageVersion(Context.VERSION_1_8);
|
||||||
|
env.setIdeMode(true);
|
||||||
|
JSParser parser = new JSParser(env, errorReporter);
|
||||||
|
parser.enterFunction();
|
||||||
|
AstRoot rootNode;
|
||||||
|
try {
|
||||||
|
rootNode = parser.parse(new StringReader(script), null, 0);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("IO Error occured", e);
|
||||||
}
|
}
|
||||||
if (isStatic) {
|
parser.exitFunction();
|
||||||
program.createVariable();
|
|
||||||
}
|
|
||||||
methodToProcess.setProgram(program);
|
|
||||||
|
|
||||||
// Generate invoke instruction
|
if (errorReporter.hasErrors()) {
|
||||||
replacement.clear();
|
repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod,
|
||||||
InvokeInstruction invoke = new InvokeInstruction();
|
script, parameterNames));
|
||||||
invoke.setType(InvocationType.SPECIAL);
|
} else {
|
||||||
invoke.setMethod(proxyMethod.getReference());
|
repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, rootNode,
|
||||||
for (int i = 0; i < paramCount; ++i) {
|
parameterNames));
|
||||||
Variable var = program.variableAt(isStatic ? i + 1 : i);
|
|
||||||
invoke.getArguments().add(wrapArgument(location, var, paramTypes[i]));
|
|
||||||
}
|
}
|
||||||
block.getInstructions().addAll(replacement);
|
repository.methodMap.put(methodToProcess.getReference(), proxyMethod);
|
||||||
block.getInstructions().add(invoke);
|
}
|
||||||
|
|
||||||
// Generate return
|
public void createJSMethods(JSBodyRepository repository, ClassHolder cls) {
|
||||||
ExitInstruction exit = new ExitInstruction();
|
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
|
||||||
if (methodToProcess.getResultType() != ValueType.VOID) {
|
MethodReference methodRef = method.getReference();
|
||||||
replacement.clear();
|
if (method.getAnnotations().get(JSBody.class.getName()) != null) {
|
||||||
Variable result = program.createVariable();
|
requireJSBody(repository, diagnostics, method);
|
||||||
invoke.setReceiver(result);
|
if (repository.methodMap.containsKey(method.getReference())) {
|
||||||
exit.setValueToReturn(unwrap(location, result, methodToProcess.getResultType()));
|
MethodReference proxyRef = repository.methodMap.get(methodRef);
|
||||||
block.getInstructions().addAll(replacement);
|
MethodHolder proxyMethod = new MethodHolder(proxyRef.getDescriptor());
|
||||||
|
proxyMethod.getModifiers().add(ElementModifier.NATIVE);
|
||||||
|
proxyMethod.getModifiers().add(ElementModifier.STATIC);
|
||||||
|
AnnotationHolder generatorAnnot = new AnnotationHolder(GeneratedBy.class.getName());
|
||||||
|
generatorAnnot.getValues().put("value",
|
||||||
|
new AnnotationValue(ValueType.parse(JSBodyGenerator.class)));
|
||||||
|
proxyMethod.getAnnotations().add(generatorAnnot);
|
||||||
|
cls.addMethod(proxyMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
block.getInstructions().add(exit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
|
private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
|
||||||
|
@ -971,6 +1049,9 @@ class JavascriptNativeProcessor {
|
||||||
|
|
||||||
private MethodReader getMethod(MethodReference ref) {
|
private MethodReader getMethod(MethodReference ref) {
|
||||||
ClassReader cls = classSource.get(ref.getClassName());
|
ClassReader cls = classSource.get(ref.getClassName());
|
||||||
|
if (cls == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
MethodReader method = cls.getMethod(ref.getDescriptor());
|
MethodReader method = cls.getMethod(ref.getDescriptor());
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
return method;
|
return method;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* 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.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Alexey Andreev
|
||||||
|
*/
|
||||||
|
interface NameEmitter {
|
||||||
|
void emit() throws IOException;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user