First version of Java callbacks, without dependency checker hook and

parameter conversion.
This commit is contained in:
Alexey Andreev 2015-09-25 22:56:41 +03:00
parent ffbd68cf69
commit bae388a778
9 changed files with 324 additions and 68 deletions

View File

@ -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;

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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++;

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}