mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2025-01-03 05:44:10 -08:00
Wrap function by an object if returning value of native method is
a JSFunctor interface. See #280
This commit is contained in:
parent
45ba247265
commit
2992c6e406
|
@ -405,4 +405,8 @@ final class JS {
|
||||||
@GeneratedBy(JSNativeGenerator.class)
|
@GeneratedBy(JSNativeGenerator.class)
|
||||||
@PluggableDependency(JSNativeGenerator.class)
|
@PluggableDependency(JSNativeGenerator.class)
|
||||||
public static native JSObject function(JSObject instance, JSObject property);
|
public static native JSObject function(JSObject instance, JSObject property);
|
||||||
|
|
||||||
|
@GeneratedBy(JSNativeGenerator.class)
|
||||||
|
@PluggableDependency(JSNativeGenerator.class)
|
||||||
|
public static native JSObject functionAsObject(JSObject instance, JSObject property);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,6 @@ import org.teavm.model.instructions.AssignInstruction;
|
||||||
import org.teavm.model.instructions.ExitInstruction;
|
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.util.InstructionVariableMapper;
|
import org.teavm.model.util.InstructionVariableMapper;
|
||||||
import org.teavm.model.util.ModelUtils;
|
import org.teavm.model.util.ModelUtils;
|
||||||
import org.teavm.model.util.ProgramUtils;
|
import org.teavm.model.util.ProgramUtils;
|
||||||
|
@ -114,7 +113,7 @@ class JSClassProcessor {
|
||||||
|
|
||||||
private void getFunctorMethods(String className, Map<MethodDescriptor, MethodReference> methods) {
|
private void getFunctorMethods(String className, Map<MethodDescriptor, MethodReference> methods) {
|
||||||
classSource.getAncestors(className).forEach(cls -> {
|
classSource.getAncestors(className).forEach(cls -> {
|
||||||
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null && isProperFunctor(cls)) {
|
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null && marshaller.isProperFunctor(cls)) {
|
||||||
MethodReference method = cls.getMethods().iterator().next().getReference();
|
MethodReference method = cls.getMethods().iterator().next().getReference();
|
||||||
if (!methods.containsKey(method.getDescriptor())) {
|
if (!methods.containsKey(method.getDescriptor())) {
|
||||||
methods.put(method.getDescriptor(), method);
|
methods.put(method.getDescriptor(), method);
|
||||||
|
@ -241,7 +240,7 @@ class JSClassProcessor {
|
||||||
|
|
||||||
private void setCurrentProgram(Program program) {
|
private void setCurrentProgram(Program program) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
marshaller = new JSValueMarshaller(diagnostics, typeHelper, program, replacement);
|
marshaller = new JSValueMarshaller(diagnostics, typeHelper, classSource, program, replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
void processProgram(MethodHolder methodToProcess) {
|
void processProgram(MethodHolder methodToProcess) {
|
||||||
|
@ -333,18 +332,18 @@ class JSClassProcessor {
|
||||||
newInvoke.setReceiver(result);
|
newInvoke.setReceiver(result);
|
||||||
newInvoke.setLocation(invoke.getLocation());
|
newInvoke.setLocation(invoke.getLocation());
|
||||||
if (invoke.getInstance() != null) {
|
if (invoke.getInstance() != null) {
|
||||||
Variable arg = wrapArgument(callLocation, invoke.getInstance(),
|
Variable arg = marshaller.wrapArgument(callLocation, invoke.getInstance(),
|
||||||
ValueType.object(method.getOwnerName()), false);
|
ValueType.object(method.getOwnerName()), false);
|
||||||
newInvoke.getArguments().add(arg);
|
newInvoke.getArguments().add(arg);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < invoke.getArguments().size(); ++i) {
|
for (int i = 0; i < invoke.getArguments().size(); ++i) {
|
||||||
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(i),
|
Variable arg = marshaller.wrapArgument(callLocation, invoke.getArguments().get(i),
|
||||||
method.parameterType(i), byRefParams[i]);
|
method.parameterType(i), byRefParams[i]);
|
||||||
newInvoke.getArguments().add(arg);
|
newInvoke.getArguments().add(arg);
|
||||||
}
|
}
|
||||||
replacement.add(newInvoke);
|
replacement.add(newInvoke);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType());
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,7 +364,7 @@ class JSClassProcessor {
|
||||||
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
||||||
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
|
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType());
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -375,7 +374,7 @@ class JSClassProcessor {
|
||||||
if (propertyName == null) {
|
if (propertyName == null) {
|
||||||
propertyName = cutPrefix(method.getName(), 3);
|
propertyName = cutPrefix(method.getName(), 3);
|
||||||
}
|
}
|
||||||
Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0),
|
Variable wrapped = marshaller.wrapArgument(callLocation, invoke.getArguments().get(0),
|
||||||
method.parameterType(0), false);
|
method.parameterType(0), false);
|
||||||
addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation());
|
addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation());
|
||||||
return true;
|
return true;
|
||||||
|
@ -394,19 +393,19 @@ class JSClassProcessor {
|
||||||
private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
|
private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
|
||||||
if (isProperGetIndexer(method.getDescriptor())) {
|
if (isProperGetIndexer(method.getDescriptor())) {
|
||||||
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
||||||
addIndexerGet(invoke.getInstance(), marshaller.wrap(invoke.getArguments().get(0),
|
addIndexerGet(invoke.getInstance(), marshaller.wrapArgument(callLocation, invoke.getArguments().get(0),
|
||||||
method.parameterType(0), invoke.getLocation(), false), result, invoke.getLocation());
|
method.parameterType(0), false), result, invoke.getLocation());
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType());
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (isProperSetIndexer(method.getDescriptor())) {
|
if (isProperSetIndexer(method.getDescriptor())) {
|
||||||
Variable index = marshaller.wrap(invoke.getArguments().get(0), method.parameterType(0),
|
Variable index = marshaller.wrapArgument(callLocation, invoke.getArguments().get(0),
|
||||||
invoke.getLocation(), false);
|
method.parameterType(0), false);
|
||||||
Variable value = marshaller.wrap(invoke.getArguments().get(1), method.parameterType(1),
|
Variable value = marshaller.wrapArgument(callLocation, invoke.getArguments().get(1),
|
||||||
invoke.getLocation(), false);
|
method.parameterType(1), false);
|
||||||
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
|
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -469,17 +468,17 @@ class JSClassProcessor {
|
||||||
newInvoke.setType(InvocationType.SPECIAL);
|
newInvoke.setType(InvocationType.SPECIAL);
|
||||||
newInvoke.setReceiver(result);
|
newInvoke.setReceiver(result);
|
||||||
newInvoke.getArguments().add(invoke.getInstance());
|
newInvoke.getArguments().add(invoke.getInstance());
|
||||||
newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()),
|
newInvoke.getArguments().add(marshaller.addStringWrap(marshaller.addString(name, invoke.getLocation()),
|
||||||
invoke.getLocation()));
|
invoke.getLocation()));
|
||||||
newInvoke.setLocation(invoke.getLocation());
|
newInvoke.setLocation(invoke.getLocation());
|
||||||
for (int i = 0; i < invoke.getArguments().size(); ++i) {
|
for (int i = 0; i < invoke.getArguments().size(); ++i) {
|
||||||
Variable arg = wrapArgument(callLocation, invoke.getArguments().get(i),
|
Variable arg = marshaller.wrapArgument(callLocation, invoke.getArguments().get(i),
|
||||||
method.parameterType(i), byRefParams[i]);
|
method.parameterType(i), byRefParams[i]);
|
||||||
newInvoke.getArguments().add(arg);
|
newInvoke.getArguments().add(arg);
|
||||||
}
|
}
|
||||||
replacement.add(newInvoke);
|
replacement.add(newInvoke);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType());
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,11 +626,11 @@ class JSClassProcessor {
|
||||||
insn.setMethod(calleeRef);
|
insn.setMethod(calleeRef);
|
||||||
replacement.clear();
|
replacement.clear();
|
||||||
if (!callee.hasModifier(ElementModifier.STATIC)) {
|
if (!callee.hasModifier(ElementModifier.STATIC)) {
|
||||||
insn.setInstance(marshaller.unwrap(location, program.variableAt(paramIndex++),
|
insn.setInstance(marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++),
|
||||||
ValueType.object(calleeRef.getClassName())));
|
ValueType.object(calleeRef.getClassName())));
|
||||||
}
|
}
|
||||||
for (int i = 0; i < callee.parameterCount(); ++i) {
|
for (int i = 0; i < callee.parameterCount(); ++i) {
|
||||||
insn.getArguments().add(marshaller.unwrap(location, program.variableAt(paramIndex++),
|
insn.getArguments().add(marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++),
|
||||||
callee.parameterType(i)));
|
callee.parameterType(i)));
|
||||||
}
|
}
|
||||||
if (callee.getResultType() != ValueType.VOID) {
|
if (callee.getResultType() != ValueType.VOID) {
|
||||||
|
@ -655,7 +654,7 @@ class JSClassProcessor {
|
||||||
|
|
||||||
private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
|
private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
|
||||||
TextLocation location) {
|
TextLocation location) {
|
||||||
Variable nameVar = addStringWrap(addString(propertyName, location), location);
|
Variable nameVar = marshaller.addStringWrap(marshaller.addString(propertyName, location), location);
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
insn.setType(InvocationType.SPECIAL);
|
insn.setType(InvocationType.SPECIAL);
|
||||||
insn.setMethod(new MethodReference(JS.class, "get", JSObject.class, JSObject.class, JSObject.class));
|
insn.setMethod(new MethodReference(JS.class, "get", JSObject.class, JSObject.class, JSObject.class));
|
||||||
|
@ -667,7 +666,7 @@ class JSClassProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPropertySet(String propertyName, Variable instance, Variable value, TextLocation location) {
|
private void addPropertySet(String propertyName, Variable instance, Variable value, TextLocation location) {
|
||||||
Variable nameVar = addStringWrap(addString(propertyName, location), location);
|
Variable nameVar = marshaller.addStringWrap(marshaller.addString(propertyName, location), location);
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
insn.setType(InvocationType.SPECIAL);
|
insn.setType(InvocationType.SPECIAL);
|
||||||
insn.setMethod(new MethodReference(JS.class, "set", JSObject.class, JSObject.class,
|
insn.setMethod(new MethodReference(JS.class, "set", JSObject.class, JSObject.class,
|
||||||
|
@ -710,61 +709,6 @@ class JSClassProcessor {
|
||||||
replacement.add(insn);
|
replacement.add(insn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Variable addStringWrap(Variable var, TextLocation location) {
|
|
||||||
return marshaller.wrap(var, ValueType.object("java.lang.String"), location, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Variable addString(String str, TextLocation location) {
|
|
||||||
Variable var = program.createVariable();
|
|
||||||
StringConstantInstruction nameInsn = new StringConstantInstruction();
|
|
||||||
nameInsn.setReceiver(var);
|
|
||||||
nameInsn.setConstant(str);
|
|
||||||
nameInsn.setLocation(location);
|
|
||||||
replacement.add(nameInsn);
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Variable wrapArgument(CallLocation location, Variable var, ValueType type, boolean byRef) {
|
|
||||||
if (type instanceof ValueType.Object) {
|
|
||||||
String className = ((ValueType.Object) type).getClassName();
|
|
||||||
ClassReader cls = classSource.get(className);
|
|
||||||
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null) {
|
|
||||||
return wrapFunctor(location, var, cls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return marshaller.wrap(var, type, location.getSourceLocation(), byRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isProperFunctor(ClassReader type) {
|
|
||||||
if (!type.hasModifier(ElementModifier.INTERFACE)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return type.getMethods().stream()
|
|
||||||
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
|
|
||||||
.count() == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type) {
|
|
||||||
if (!isProperFunctor(type)) {
|
|
||||||
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
String name = type.getMethods().stream()
|
|
||||||
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
|
|
||||||
.findFirst().get().getName();
|
|
||||||
Variable functor = program.createVariable();
|
|
||||||
Variable nameVar = addStringWrap(addString(name, location.getSourceLocation()), location.getSourceLocation());
|
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
insn.setMethod(new MethodReference(JS.class, "function", JSObject.class, JSObject.class, JSObject.class));
|
|
||||||
insn.setReceiver(functor);
|
|
||||||
insn.getArguments().add(var);
|
|
||||||
insn.getArguments().add(nameVar);
|
|
||||||
insn.setLocation(location.getSourceLocation());
|
|
||||||
replacement.add(insn);
|
|
||||||
return functor;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (cls == null) {
|
||||||
|
|
|
@ -44,6 +44,9 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
|
||||||
case "function":
|
case "function":
|
||||||
writeFunction(context, writer);
|
writeFunction(context, writer);
|
||||||
break;
|
break;
|
||||||
|
case "functionAsObject":
|
||||||
|
writeFunctionAsObject(context, writer);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +72,16 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
|
||||||
writer.append("return ").append(thisName).append("[name]();").softNewLine();
|
writer.append("return ").append(thisName).append("[name]();").softNewLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeFunctionAsObject(GeneratorContext context, SourceWriter writer) throws IOException {
|
||||||
|
String thisName = context.getParameterName(1);
|
||||||
|
String methodName = context.getParameterName(2);
|
||||||
|
|
||||||
|
writer.append("var result").ws().append("=").ws().append("{};").softNewLine();
|
||||||
|
writer.append("result[").append(methodName).append("]").ws().append("=").ws().append(thisName)
|
||||||
|
.append(";").softNewLine();
|
||||||
|
writer.append("return result;").softNewLine();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
||||||
SourceWriter writer = context.getWriter();
|
SourceWriter writer = context.getWriter();
|
||||||
|
@ -143,9 +156,6 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
|
||||||
context.writeExpr(context.getArgument(0), context.getPrecedence());
|
context.writeExpr(context.getArgument(0), context.getPrecedence());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "function":
|
|
||||||
generateFunction(context);
|
|
||||||
break;
|
|
||||||
case "unwrapString":
|
case "unwrapString":
|
||||||
writer.append("$rt_str(");
|
writer.append("$rt_str(");
|
||||||
context.writeExpr(context.getArgument(0), Precedence.min());
|
context.writeExpr(context.getArgument(0), Precedence.min());
|
||||||
|
@ -200,17 +210,6 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateFunction(InjectorContext context) throws IOException {
|
|
||||||
SourceWriter writer = context.getWriter();
|
|
||||||
writer.append("(function($instance,").ws().append("$property)").ws().append("{").ws()
|
|
||||||
.append("return function()").ws().append("{").indent().softNewLine();
|
|
||||||
writer.append("return $instance[$property].apply($instance,").ws().append("arguments);").softNewLine();
|
|
||||||
writer.outdent().append("};})(");
|
|
||||||
context.writeExpr(context.getArgument(0));
|
|
||||||
writer.append(",").ws();
|
|
||||||
context.writeExpr(context.getArgument(1));
|
|
||||||
writer.append(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderProperty(Expr property, InjectorContext context) throws IOException {
|
private void renderProperty(Expr property, InjectorContext context) throws IOException {
|
||||||
SourceWriter writer = context.getWriter();
|
SourceWriter writer = context.getWriter();
|
||||||
|
|
|
@ -120,7 +120,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||||
|
|
||||||
BasicBlock basicBlock = program.createBasicBlock();
|
BasicBlock basicBlock = program.createBasicBlock();
|
||||||
List<Instruction> marshallInstructions = new ArrayList<>();
|
List<Instruction> marshallInstructions = new ArrayList<>();
|
||||||
JSValueMarshaller marshaller = new JSValueMarshaller(diagnostics, typeHelper, program,
|
JSValueMarshaller marshaller = new JSValueMarshaller(diagnostics, typeHelper, innerSource, program,
|
||||||
marshallInstructions);
|
marshallInstructions);
|
||||||
|
|
||||||
List<Variable> variablesToPass = new ArrayList<>();
|
List<Variable> variablesToPass = new ArrayList<>();
|
||||||
|
@ -129,7 +129,8 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < method.parameterCount(); ++i) {
|
for (int i = 0; i < method.parameterCount(); ++i) {
|
||||||
Variable var = marshaller.unwrap(callLocation, variablesToPass.get(i), method.parameterType(i));
|
Variable var = marshaller.unwrapReturnValue(callLocation, variablesToPass.get(i),
|
||||||
|
method.parameterType(i));
|
||||||
variablesToPass.set(i, var);
|
variablesToPass.set(i, var);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,8 +147,8 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||||
ExitInstruction exit = new ExitInstruction();
|
ExitInstruction exit = new ExitInstruction();
|
||||||
if (method.getResultType() != ValueType.VOID) {
|
if (method.getResultType() != ValueType.VOID) {
|
||||||
invocation.setReceiver(program.createVariable());
|
invocation.setReceiver(program.createVariable());
|
||||||
exit.setValueToReturn(marshaller.wrap(invocation.getReceiver(), method.getResultType(),
|
exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(),
|
||||||
null, false));
|
method.getResultType(), false));
|
||||||
basicBlock.addAll(marshallInstructions);
|
basicBlock.addAll(marshallInstructions);
|
||||||
marshallInstructions.clear();
|
marshallInstructions.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,14 @@ package org.teavm.jso.impl;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import org.teavm.diagnostics.Diagnostics;
|
import org.teavm.diagnostics.Diagnostics;
|
||||||
|
import org.teavm.jso.JSFunctor;
|
||||||
import org.teavm.jso.JSObject;
|
import org.teavm.jso.JSObject;
|
||||||
import org.teavm.jso.core.JSArray;
|
import org.teavm.jso.core.JSArray;
|
||||||
import org.teavm.jso.core.JSArrayReader;
|
import org.teavm.jso.core.JSArrayReader;
|
||||||
import org.teavm.model.CallLocation;
|
import org.teavm.model.CallLocation;
|
||||||
|
import org.teavm.model.ClassReader;
|
||||||
|
import org.teavm.model.ClassReaderSource;
|
||||||
|
import org.teavm.model.ElementModifier;
|
||||||
import org.teavm.model.Instruction;
|
import org.teavm.model.Instruction;
|
||||||
import org.teavm.model.MethodReference;
|
import org.teavm.model.MethodReference;
|
||||||
import org.teavm.model.Program;
|
import org.teavm.model.Program;
|
||||||
|
@ -32,22 +36,66 @@ import org.teavm.model.instructions.CastInstruction;
|
||||||
import org.teavm.model.instructions.ClassConstantInstruction;
|
import org.teavm.model.instructions.ClassConstantInstruction;
|
||||||
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;
|
||||||
|
|
||||||
class JSValueMarshaller {
|
class JSValueMarshaller {
|
||||||
private Diagnostics diagnostics;
|
private Diagnostics diagnostics;
|
||||||
private JSTypeHelper typeHelper;
|
private JSTypeHelper typeHelper;
|
||||||
|
private ClassReaderSource classSource;
|
||||||
private Program program;
|
private Program program;
|
||||||
private List<Instruction> replacement;
|
private List<Instruction> replacement;
|
||||||
|
|
||||||
JSValueMarshaller(Diagnostics diagnostics, JSTypeHelper typeHelper, Program program,
|
JSValueMarshaller(Diagnostics diagnostics, JSTypeHelper typeHelper, ClassReaderSource classSource,
|
||||||
List<Instruction> replacement) {
|
Program program, List<Instruction> replacement) {
|
||||||
this.diagnostics = diagnostics;
|
this.diagnostics = diagnostics;
|
||||||
this.typeHelper = typeHelper;
|
this.typeHelper = typeHelper;
|
||||||
|
this.classSource = classSource;
|
||||||
this.program = program;
|
this.program = program;
|
||||||
this.replacement = replacement;
|
this.replacement = replacement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Variable wrap(Variable var, ValueType type, TextLocation location, boolean byRef) {
|
Variable wrapArgument(CallLocation location, Variable var, ValueType type, boolean byRef) {
|
||||||
|
if (type instanceof ValueType.Object) {
|
||||||
|
String className = ((ValueType.Object) type).getClassName();
|
||||||
|
ClassReader cls = classSource.get(className);
|
||||||
|
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null) {
|
||||||
|
return wrapFunctor(location, var, cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wrap(var, type, location.getSourceLocation(), byRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isProperFunctor(ClassReader type) {
|
||||||
|
if (!type.hasModifier(ElementModifier.INTERFACE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return type.getMethods().stream()
|
||||||
|
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
|
||||||
|
.count() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type) {
|
||||||
|
if (!isProperFunctor(type)) {
|
||||||
|
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
String name = type.getMethods().stream()
|
||||||
|
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
|
||||||
|
.findFirst().get().getName();
|
||||||
|
Variable functor = program.createVariable();
|
||||||
|
Variable nameVar = addStringWrap(addString(name, location.getSourceLocation()), location.getSourceLocation());
|
||||||
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.setMethod(new MethodReference(JS.class, "function", JSObject.class, JSObject.class, JSObject.class));
|
||||||
|
insn.setReceiver(functor);
|
||||||
|
insn.getArguments().add(var);
|
||||||
|
insn.getArguments().add(nameVar);
|
||||||
|
insn.setLocation(location.getSourceLocation());
|
||||||
|
replacement.add(insn);
|
||||||
|
return functor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable wrap(Variable var, ValueType type, TextLocation location, boolean byRef) {
|
||||||
if (byRef) {
|
if (byRef) {
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
insn.setMethod(new MethodReference(JS.class, "arrayData", Object.class, JSObject.class));
|
insn.setMethod(new MethodReference(JS.class, "arrayData", Object.class, JSObject.class));
|
||||||
|
@ -169,6 +217,17 @@ class JSValueMarshaller {
|
||||||
return new MethodReference(JS.class, "arrayWrapper", Function.class);
|
return new MethodReference(JS.class, "arrayWrapper", Function.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Variable unwrapReturnValue(CallLocation location, Variable var, ValueType type) {
|
||||||
|
if (type instanceof ValueType.Object) {
|
||||||
|
String className = ((ValueType.Object) type).getClassName();
|
||||||
|
ClassReader cls = classSource.get(className);
|
||||||
|
if (cls.getAnnotations().get(JSFunctor.class.getName()) != null) {
|
||||||
|
return unwrapFunctor(location, var, cls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unwrap(location, var, type);
|
||||||
|
}
|
||||||
|
|
||||||
Variable unwrap(CallLocation location, Variable var, ValueType type) {
|
Variable unwrap(CallLocation location, Variable var, ValueType type) {
|
||||||
if (type instanceof ValueType.Primitive) {
|
if (type instanceof ValueType.Primitive) {
|
||||||
switch (((ValueType.Primitive) type).getKind()) {
|
switch (((ValueType.Primitive) type).getKind()) {
|
||||||
|
@ -402,4 +461,40 @@ class JSValueMarshaller {
|
||||||
replacement.add(insn);
|
replacement.add(insn);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Variable unwrapFunctor(CallLocation location, Variable var, ClassReader type) {
|
||||||
|
if (!isProperFunctor(type)) {
|
||||||
|
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
String name = type.getMethods().stream()
|
||||||
|
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
|
||||||
|
.findFirst().get().getName();
|
||||||
|
Variable functor = program.createVariable();
|
||||||
|
Variable nameVar = addStringWrap(addString(name, location.getSourceLocation()), location.getSourceLocation());
|
||||||
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.setMethod(new MethodReference(JS.class, "functionAsObject", JSObject.class, JSObject.class,
|
||||||
|
JSObject.class));
|
||||||
|
insn.setReceiver(functor);
|
||||||
|
insn.getArguments().add(var);
|
||||||
|
insn.getArguments().add(nameVar);
|
||||||
|
insn.setLocation(location.getSourceLocation());
|
||||||
|
replacement.add(insn);
|
||||||
|
return functor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable addStringWrap(Variable var, TextLocation location) {
|
||||||
|
return wrap(var, ValueType.object("java.lang.String"), location, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable addString(String str, TextLocation location) {
|
||||||
|
Variable var = program.createVariable();
|
||||||
|
StringConstantInstruction nameInsn = new StringConstantInstruction();
|
||||||
|
nameInsn.setReceiver(var);
|
||||||
|
nameInsn.setConstant(str);
|
||||||
|
nameInsn.setLocation(location);
|
||||||
|
replacement.add(nameInsn);
|
||||||
|
return var;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.jso.test;
|
package org.teavm.jso.test;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.teavm.jso.JSBody;
|
import org.teavm.jso.JSBody;
|
||||||
|
@ -33,6 +34,11 @@ public class FunctorTest {
|
||||||
assertEquals("(5)", testMethod((a, b) -> a + b, 2, 3));
|
assertEquals("(5)", testMethod((a, b) -> a + b, 2, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void functorParamsMarshaled() {
|
||||||
|
assertEquals("(q,w)", testMethod((a, b) -> a + "," + b, "q", "w"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void functorIdentityPreserved() {
|
public void functorIdentityPreserved() {
|
||||||
JSBiFunction javaFunction = (a, b) -> a + b;
|
JSBiFunction javaFunction = (a, b) -> a + b;
|
||||||
|
@ -65,9 +71,36 @@ public class FunctorTest {
|
||||||
assertEquals("baz_ok", wp.propbaz());
|
assertEquals("baz_ok", wp.propbaz());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void functorPassedBack() {
|
||||||
|
JSBiFunction function = getBiFunction();
|
||||||
|
assertEquals(23042, function.foo(23, 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void functorParamsMarshaledBack() {
|
||||||
|
JSStringBiFunction function = getStringBiFunction();
|
||||||
|
assertEquals("q,w", function.foo("q", "w"));
|
||||||
|
}
|
||||||
|
|
||||||
@JSBody(params = { "f", "a", "b" }, script = "return '(' + f(a, b) + ')';")
|
@JSBody(params = { "f", "a", "b" }, script = "return '(' + f(a, b) + ')';")
|
||||||
private static native String testMethod(JSBiFunction f, int a, int b);
|
private static native String testMethod(JSBiFunction f, int a, int b);
|
||||||
|
|
||||||
|
@JSBody(params = { "f", "a", "b" }, script = "return '(' + f(a, b) + ')';")
|
||||||
|
private static native String testMethod(JSStringBiFunction f, String a, String b);
|
||||||
|
|
||||||
|
@JSBody(script = ""
|
||||||
|
+ "return function(a, b) {"
|
||||||
|
+ "return a * 1000 + b;"
|
||||||
|
+ "};")
|
||||||
|
private static native JSBiFunction getBiFunction();
|
||||||
|
|
||||||
|
@JSBody(script = ""
|
||||||
|
+ "return function(a, b) {"
|
||||||
|
+ "return a + ',' + b;"
|
||||||
|
+ "};")
|
||||||
|
private static native JSStringBiFunction getStringBiFunction();
|
||||||
|
|
||||||
@JSBody(params = "f", script = "return f;")
|
@JSBody(params = "f", script = "return f;")
|
||||||
private static native JSObject getFunction(JSBiFunction f);
|
private static native JSObject getFunction(JSBiFunction f);
|
||||||
|
|
||||||
|
@ -79,6 +112,11 @@ public class FunctorTest {
|
||||||
int foo(int a, int b);
|
int foo(int a, int b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JSFunctor
|
||||||
|
interface JSStringBiFunction extends JSObject {
|
||||||
|
String foo(String a, String b);
|
||||||
|
}
|
||||||
|
|
||||||
@JSFunctor
|
@JSFunctor
|
||||||
interface JSFunctionWithDefaultMethod extends JSObject {
|
interface JSFunctionWithDefaultMethod extends JSObject {
|
||||||
int foo(int a);
|
int foo(int a);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user