mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
First version of Java callbacks, without dependency checker hook and
parameter conversion.
This commit is contained in:
parent
ffbd68cf69
commit
bae388a778
|
@ -96,24 +96,46 @@ public class MethodDescriptor {
|
|||
}
|
||||
|
||||
public static MethodDescriptor parse(String text) {
|
||||
int parenIndex = text.indexOf('(');
|
||||
if (parenIndex < 0) {
|
||||
MethodDescriptor desc = parseIfPossible(text);
|
||||
if (desc == null) {
|
||||
throw new IllegalArgumentException("Wrong method descriptor: " + text);
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
public static MethodDescriptor parseIfPossible(String text) {
|
||||
int parenIndex = text.indexOf('(');
|
||||
if (parenIndex < 1) {
|
||||
return null;
|
||||
}
|
||||
return new MethodDescriptor(text.substring(0, parenIndex),
|
||||
parseSignature(text.substring(parenIndex)));
|
||||
}
|
||||
|
||||
public static ValueType[] parseSignature(String text) {
|
||||
ValueType[] signature = parseSignatureIfPossible(text);
|
||||
if (signature == null) {
|
||||
throw new IllegalArgumentException("Illegal method signature: " + text);
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
public static ValueType[] parseSignatureIfPossible(String text) {
|
||||
if (text.charAt(0) != '(') {
|
||||
throw new IllegalArgumentException("Wrong method descriptor: " + text);
|
||||
return null;
|
||||
}
|
||||
int index = text.indexOf(')', 1);
|
||||
if (index == -1) {
|
||||
throw new IllegalArgumentException("Wrong method descriptor: " + text);
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
ValueType[] params = ValueType.parseManyIfPossible(text.substring(1, index));
|
||||
if (params == null) {
|
||||
return null;
|
||||
}
|
||||
ValueType[] params = ValueType.parseMany(text.substring(1, index));
|
||||
ValueType result = ValueType.parse(text.substring(index + 1));
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
ValueType[] signature = new ValueType[params.length + 1];
|
||||
System.arraycopy(params, 0, signature, 0, params.length);
|
||||
signature[params.length] = result;
|
||||
|
|
|
@ -140,9 +140,21 @@ public class MethodReference {
|
|||
}
|
||||
|
||||
public static MethodReference parse(String string) {
|
||||
MethodReference reference = parseIfPossible(string);
|
||||
if (reference == null) {
|
||||
throw new IllegalArgumentException("Illegal method reference: " + string);
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
|
||||
public static MethodReference parseIfPossible(String string) {
|
||||
int index = string.lastIndexOf('.');
|
||||
if (index < 1) {
|
||||
return null;
|
||||
}
|
||||
String className = string.substring(0, index);
|
||||
return new MethodReference(className, MethodDescriptor.parse(string.substring(index + 1)));
|
||||
MethodDescriptor desc = MethodDescriptor.parseIfPossible(string.substring(index + 1));
|
||||
return desc != null ? new MethodReference(className, desc) : null;
|
||||
}
|
||||
|
||||
public String signatureToString() {
|
||||
|
|
|
@ -212,11 +212,23 @@ public abstract class ValueType {
|
|||
}
|
||||
|
||||
public static ValueType[] parseMany(String text) {
|
||||
ValueType[] types = parseManyIfPossible(text);
|
||||
if (types == null) {
|
||||
throw new IllegalArgumentException("Illegal method type signature: " + text);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
public static ValueType[] parseManyIfPossible(String text) {
|
||||
List<ValueType> types = new ArrayList<>();
|
||||
int index = 0;
|
||||
while (index < text.length()) {
|
||||
int nextIndex = cut(text, index);
|
||||
types.add(parse(text.substring(index, nextIndex)));
|
||||
ValueType type = parse(text.substring(index, nextIndex));
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
types.add(type);
|
||||
index = nextIndex;
|
||||
}
|
||||
return types.toArray(new ValueType[types.size()]);
|
||||
|
@ -224,18 +236,30 @@ public abstract class ValueType {
|
|||
|
||||
private static int cut(String text, int index) {
|
||||
while (text.charAt(index) == '[') {
|
||||
++index;
|
||||
if (++index >= text.length()) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
if (text.charAt(index) != 'L') {
|
||||
return index + 1;
|
||||
}
|
||||
while (text.charAt(index) != ';') {
|
||||
++index;
|
||||
if (++index >= text.length()) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
public static ValueType parse(String string) {
|
||||
ValueType type = parseIfPossible(string);
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Illegal type signature: " + string);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public static ValueType parseIfPossible(String string) {
|
||||
int arrayDegree = 0;
|
||||
int left = 0;
|
||||
while (string.charAt(left) == '[') {
|
||||
|
@ -243,7 +267,13 @@ public abstract class ValueType {
|
|||
++left;
|
||||
}
|
||||
string = string.substring(left);
|
||||
if (string.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
ValueType type = parseImpl(string);
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
while (arrayDegree-- > 0) {
|
||||
type = arrayOf(type);
|
||||
}
|
||||
|
@ -295,7 +325,7 @@ public abstract class ValueType {
|
|||
}
|
||||
return object(string.substring(1, string.length() - 1).replace('/', '.'));
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,8 +71,6 @@ import org.mozilla.javascript.ast.VariableDeclaration;
|
|||
import org.mozilla.javascript.ast.VariableInitializer;
|
||||
import org.mozilla.javascript.ast.WhileLoop;
|
||||
import org.teavm.codegen.SourceWriter;
|
||||
import org.teavm.diagnostics.Diagnostics;
|
||||
import org.teavm.model.CallLocation;
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
/**
|
||||
|
@ -97,13 +95,11 @@ public class AstWriter {
|
|||
private static final int PRECEDENCE_COND = 16;
|
||||
private static final int PRECEDENCE_ASSIGN = 17;
|
||||
private static final int PRECEDENCE_COMMA = 18;
|
||||
private Diagnostics diagnostics;
|
||||
private SourceWriter writer;
|
||||
private Map<String, NameEmitter> nameMap = new HashMap<>();
|
||||
private Set<String> aliases = new HashSet<>();
|
||||
|
||||
public AstWriter(Diagnostics diagnostics, SourceWriter writer) {
|
||||
this.diagnostics = diagnostics;
|
||||
public AstWriter(SourceWriter writer) {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
|
@ -506,12 +502,8 @@ public class AstWriter {
|
|||
}
|
||||
|
||||
private void print(FunctionCall node, int precedence) throws IOException {
|
||||
if (node.getTarget() instanceof PropertyGet) {
|
||||
PropertyGet propertyGet = (PropertyGet) node.getTarget();
|
||||
MethodReference methodRef = getJavaMethodSelector(propertyGet.getTarget());
|
||||
if (methodRef != null && propertyGet.getProperty().getIdentifier().equals("invoke")) {
|
||||
return;
|
||||
}
|
||||
if (tryJavaInvocation(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (precedence < PRECEDENCE_FUNCTION) {
|
||||
|
@ -537,55 +529,59 @@ public class AstWriter {
|
|||
}
|
||||
}
|
||||
|
||||
private MethodReference getJavaMethodSelector(AstNode node) {
|
||||
if (!(node instanceof FunctionCall)) {
|
||||
return null;
|
||||
}
|
||||
FunctionCall call = (FunctionCall) node;
|
||||
if (!isJavaMethodRepository(call.getTarget())) {
|
||||
return null;
|
||||
}
|
||||
if (call.getArguments().size() != 1) {
|
||||
diagnostics.warning(new CallLocation(null), "JavaMethods.get method should take exactly one argument");
|
||||
return null;
|
||||
}
|
||||
StringBuilder nameBuilder = new StringBuilder();
|
||||
if (!extractMethodName(call.getArguments().get(0), nameBuilder)) {
|
||||
diagnostics.warning(new CallLocation(null), "JavaMethods.get method should take string constant");
|
||||
return null;
|
||||
}
|
||||
return MethodReference.parse(nameBuilder.toString());
|
||||
}
|
||||
|
||||
private boolean isJavaMethodRepository(AstNode node) {
|
||||
if (!(node instanceof PropertyGet)) {
|
||||
return false;
|
||||
}
|
||||
PropertyGet propertyGet = (PropertyGet) node;
|
||||
|
||||
if (propertyGet.getLeft() instanceof Name) {
|
||||
return false;
|
||||
}
|
||||
if (!((Name) propertyGet.getTarget()).getIdentifier().equals("JavaMethods")) {
|
||||
return false;
|
||||
}
|
||||
if (!propertyGet.getProperty().getIdentifier().equals("get")) {
|
||||
private boolean tryJavaInvocation(FunctionCall node) throws IOException {
|
||||
if (!(node.getTarget() instanceof PropertyGet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
PropertyGet propertyGet = (PropertyGet) node.getTarget();
|
||||
String callMethod = getJavaMethod(propertyGet.getTarget());
|
||||
if (callMethod == null || !propertyGet.getProperty().getIdentifier().equals("invoke")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean extractMethodName(AstNode node, StringBuilder sb) {
|
||||
if (node.getType() == Token.ADD) {
|
||||
InfixExpression infix = (InfixExpression) node;
|
||||
return extractMethodName(infix.getLeft(), sb) && extractMethodName(infix.getRight(), sb);
|
||||
} else if (node.getType() == Token.STRING) {
|
||||
sb.append(((StringLiteral) node).getValue());
|
||||
return true;
|
||||
boolean isStatic;
|
||||
if (callMethod.startsWith("S")) {
|
||||
isStatic = true;
|
||||
} else if (callMethod.startsWith("V")) {
|
||||
isStatic = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
callMethod = callMethod.substring(1);
|
||||
MethodReference method = MethodReference.parseIfPossible(callMethod);
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isStatic) {
|
||||
writer.appendMethodBody(method).append('(');
|
||||
printList(node.getArguments());
|
||||
writer.append(')');
|
||||
} else {
|
||||
print(node.getArguments().get(0));
|
||||
writer.append('.').appendMethod(method.getDescriptor()).append('(');
|
||||
if (node.getArguments().size() > 1) {
|
||||
print(node.getArguments().get(1));
|
||||
}
|
||||
for (int i = 2; i < node.getArguments().size(); ++i) {
|
||||
writer.append(',').ws();
|
||||
print(node.getArguments().get(i));
|
||||
}
|
||||
writer.append(')');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getJavaMethod(AstNode node) {
|
||||
if (!(node instanceof StringLiteral)) {
|
||||
return null;
|
||||
}
|
||||
String str = ((StringLiteral) node).getValue();
|
||||
if (!str.startsWith("$$JSO$$_")) {
|
||||
return null;
|
||||
}
|
||||
return str.substring("$$JSO$$_".length());
|
||||
}
|
||||
|
||||
private void print(ConditionalExpression node, int precedence) throws IOException {
|
||||
|
|
|
@ -39,7 +39,7 @@ class JSBodyAstEmitter implements JSBodyEmitter {
|
|||
|
||||
@Override
|
||||
public void emit(InjectorContext context) throws IOException {
|
||||
AstWriter astWriter = new AstWriter(null, context.getWriter());
|
||||
AstWriter astWriter = new AstWriter(context.getWriter());
|
||||
int paramIndex = 0;
|
||||
if (!isStatic) {
|
||||
int index = paramIndex++;
|
||||
|
@ -55,7 +55,7 @@ class JSBodyAstEmitter implements JSBodyEmitter {
|
|||
|
||||
@Override
|
||||
public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
|
||||
AstWriter astWriter = new AstWriter(context.getDiagnostics(), writer);
|
||||
AstWriter astWriter = new AstWriter(writer);
|
||||
int paramIndex = 1;
|
||||
if (!isStatic) {
|
||||
int index = paramIndex++;
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* 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 org.mozilla.javascript.Token;
|
||||
import org.mozilla.javascript.ast.AstNode;
|
||||
import org.mozilla.javascript.ast.FunctionCall;
|
||||
import org.mozilla.javascript.ast.InfixExpression;
|
||||
import org.mozilla.javascript.ast.Name;
|
||||
import org.mozilla.javascript.ast.NodeVisitor;
|
||||
import org.mozilla.javascript.ast.PropertyGet;
|
||||
import org.mozilla.javascript.ast.StringLiteral;
|
||||
import org.teavm.diagnostics.Diagnostics;
|
||||
import org.teavm.model.CallLocation;
|
||||
import org.teavm.model.ClassReaderSource;
|
||||
import org.teavm.model.ElementModifier;
|
||||
import org.teavm.model.MethodReader;
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexey Andreev
|
||||
*/
|
||||
class JavaInvocationValidator implements NodeVisitor {
|
||||
private ClassReaderSource classSource;
|
||||
private Diagnostics diagnostics;
|
||||
private CallLocation location;
|
||||
|
||||
public JavaInvocationValidator(ClassReaderSource classSource, Diagnostics diagnostics) {
|
||||
this.classSource = classSource;
|
||||
this.diagnostics = diagnostics;
|
||||
}
|
||||
|
||||
public void validate(CallLocation location, AstNode root) {
|
||||
this.location = location;
|
||||
root.visit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(AstNode node) {
|
||||
if (node instanceof FunctionCall) {
|
||||
return validateCall((FunctionCall) node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateCall(FunctionCall call) {
|
||||
if (!(call.getTarget() instanceof PropertyGet)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PropertyGet propertyGet = (PropertyGet) call.getTarget();
|
||||
MethodReference methodRef = getJavaMethodSelector(propertyGet.getTarget());
|
||||
if (methodRef == null || !propertyGet.getProperty().getIdentifier().equals("invoke")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (AstNode arg : call.getArguments()) {
|
||||
arg.visit(this);
|
||||
}
|
||||
|
||||
MethodReader method = classSource.resolve(methodRef);
|
||||
if (method == null) {
|
||||
diagnostics.error(location, "Java method not found: {{m0}}", methodRef);
|
||||
return false;
|
||||
}
|
||||
|
||||
int requiredParams = methodRef.parameterCount();
|
||||
if (!method.hasModifier(ElementModifier.STATIC)) {
|
||||
++requiredParams;
|
||||
}
|
||||
if (call.getArguments().size() != requiredParams) {
|
||||
diagnostics.error(location, "Invalid number of arguments for method {{m0}}. Expected: " + requiredParams
|
||||
+ ", encountered: " + call.getArguments().size(), methodRef);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder("$$JSO$$_");
|
||||
sb.append(method.hasModifier(ElementModifier.STATIC) ? 'S' : "V");
|
||||
sb.append(method.getReference().toString());
|
||||
StringLiteral newTarget = new StringLiteral();
|
||||
newTarget.setValue(sb.toString());
|
||||
propertyGet.setTarget(newTarget);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private MethodReference getJavaMethodSelector(AstNode node) {
|
||||
if (!(node instanceof FunctionCall)) {
|
||||
return null;
|
||||
}
|
||||
FunctionCall call = (FunctionCall) node;
|
||||
if (!isJavaMethodRepository(call.getTarget())) {
|
||||
return null;
|
||||
}
|
||||
if (call.getArguments().size() != 1) {
|
||||
diagnostics.error(location, "javaMethods.get method should take exactly one argument");
|
||||
return null;
|
||||
}
|
||||
StringBuilder nameBuilder = new StringBuilder();
|
||||
if (!extractMethodName(call.getArguments().get(0), nameBuilder)) {
|
||||
diagnostics.error(location, "javaMethods.get method should take string constant");
|
||||
return null;
|
||||
}
|
||||
|
||||
MethodReference method = MethodReference.parseIfPossible(nameBuilder.toString());
|
||||
if (method == null) {
|
||||
diagnostics.error(location, "Wrong method reference: " + nameBuilder);
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private boolean extractMethodName(AstNode node, StringBuilder sb) {
|
||||
if (node.getType() == Token.ADD) {
|
||||
InfixExpression infix = (InfixExpression) node;
|
||||
return extractMethodName(infix.getLeft(), sb) && extractMethodName(infix.getRight(), sb);
|
||||
} else if (node.getType() == Token.STRING) {
|
||||
sb.append(((StringLiteral) node).getValue());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJavaMethodRepository(AstNode node) {
|
||||
if (!(node instanceof PropertyGet)) {
|
||||
return false;
|
||||
}
|
||||
PropertyGet propertyGet = (PropertyGet) node;
|
||||
|
||||
if (!(propertyGet.getLeft() instanceof Name)) {
|
||||
return false;
|
||||
}
|
||||
if (!((Name) propertyGet.getTarget()).getIdentifier().equals("javaMethods")) {
|
||||
return false;
|
||||
}
|
||||
if (!propertyGet.getProperty().getIdentifier().equals("get")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -318,7 +318,7 @@ class JavascriptNativeProcessor {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (method == null || method.hasModifier(ElementModifier.STATIC)) {
|
||||
if (method.hasModifier(ElementModifier.STATIC)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -579,6 +579,8 @@ class JavascriptNativeProcessor {
|
|||
} else {
|
||||
expr = rootNode;
|
||||
}
|
||||
JavaInvocationValidator javaValidator = new JavaInvocationValidator(classSource, diagnostics);
|
||||
javaValidator.validate(location, expr);
|
||||
repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, expr, parameterNames));
|
||||
}
|
||||
repository.methodMap.put(methodToProcess.getReference(), proxyMethod);
|
||||
|
|
|
@ -39,7 +39,7 @@ public class AstWriterTest {
|
|||
SourceWriterBuilder builder = new SourceWriterBuilder(null);
|
||||
builder.setMinified(true);
|
||||
sourceWriter = builder.build(sb);
|
||||
writer = new AstWriter(null, sourceWriter);
|
||||
writer = new AstWriter(sourceWriter);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
import org.teavm.jso.JSBody;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexey Andreev
|
||||
*/
|
||||
public class JavaInvocationTest {
|
||||
@Test
|
||||
public void callStaticMethod() {
|
||||
assertEquals(7, staticInvocation(5));
|
||||
}
|
||||
|
||||
@JSBody(params = { "a" }, script = "return javaMethods.get('org.teavm.jso.test.JavaInvocationTest.sum(II)I')"
|
||||
+ ".invoke(a, 2);")
|
||||
private static native int staticInvocation(int a);
|
||||
|
||||
private static int sum(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user