diff --git a/build.gradle b/build.gradle index c2df22a..c924788 100644 --- a/build.gradle +++ b/build.gradle @@ -87,7 +87,7 @@ artifacts { } test { - useJUnitPlatform() + //useJUnitPlatform() testLogging{ events "PASSED", "FAILED", "SKIPPED" } diff --git a/src/main/java/stanhebben/zenscript/compiler/EnvironmentClass.java b/src/main/java/stanhebben/zenscript/compiler/EnvironmentClass.java index 1852f71..33c904b 100644 --- a/src/main/java/stanhebben/zenscript/compiler/EnvironmentClass.java +++ b/src/main/java/stanhebben/zenscript/compiler/EnvironmentClass.java @@ -1,6 +1,5 @@ package stanhebben.zenscript.compiler; -import jdk.internal.dynalink.support.BottomGuardingDynamicLinker; import org.objectweb.asm.ClassVisitor; import stanhebben.zenscript.*; import stanhebben.zenscript.expression.partial.IPartialExpression; diff --git a/src/main/java/stanhebben/zenscript/compiler/EnvironmentMethod.java b/src/main/java/stanhebben/zenscript/compiler/EnvironmentMethod.java index 6b3460e..97f127a 100644 --- a/src/main/java/stanhebben/zenscript/compiler/EnvironmentMethod.java +++ b/src/main/java/stanhebben/zenscript/compiler/EnvironmentMethod.java @@ -2,7 +2,8 @@ import org.objectweb.asm.ClassVisitor; import stanhebben.zenscript.*; -import stanhebben.zenscript.expression.partial.IPartialExpression; +import stanhebben.zenscript.expression.*; +import stanhebben.zenscript.expression.partial.*; import stanhebben.zenscript.symbols.*; import stanhebben.zenscript.type.ZenType; import stanhebben.zenscript.util.*; @@ -17,8 +18,8 @@ public class EnvironmentMethod implements IEnvironmentMethod { private final MethodOutput output; private final HashMap locals; - private final IEnvironmentClass environment; - private final Map local; + final IEnvironmentClass environment; + final Map local; public EnvironmentMethod(MethodOutput output, IEnvironmentClass environment) { this.output = output; diff --git a/src/main/java/stanhebben/zenscript/compiler/EnvironmentMethodLambda.java b/src/main/java/stanhebben/zenscript/compiler/EnvironmentMethodLambda.java new file mode 100644 index 0000000..f753d80 --- /dev/null +++ b/src/main/java/stanhebben/zenscript/compiler/EnvironmentMethodLambda.java @@ -0,0 +1,52 @@ +package stanhebben.zenscript.compiler; + +import stanhebben.zenscript.expression.*; +import stanhebben.zenscript.expression.partial.*; +import stanhebben.zenscript.symbols.*; +import stanhebben.zenscript.util.*; + +import java.util.*; + +public class EnvironmentMethodLambda extends EnvironmentMethod { + + private static final List> nonCapturedExpressions; + static { + nonCapturedExpressions = Arrays.asList(PartialStaticGetter.class, PartialStaticGenerated.class, PartialStaticMethod.class, ExpressionJavaStaticField.class, ExpressionJavaMethodStatic.class, ExpressionCallStatic.class); + + + } + private final List capturedVariables; + private final String clsName; + + public EnvironmentMethodLambda(MethodOutput output, IEnvironmentClass environment, String clsName) { + super(output, environment); + this.clsName = clsName; + capturedVariables = new ArrayList<>(0); + } + + @Override + public IPartialExpression getValue(String name, ZenPosition position) { + if(local.containsKey(name)) { + return local.get(name).instance(position); + } else { + final IPartialExpression value = environment.getValue(name, position); + if(value != null) { + if(nonCapturedExpressions.stream().anyMatch(c -> c.isInstance(value))) { + return value; + } + + + final SymbolCaptured capture = new SymbolCaptured(value.eval(environment), name, clsName); + capturedVariables.add(capture); + local.put(name, capture); + return capture.instance(position); + } + return null; + } + } + + public List getCapturedVariables() { + return capturedVariables; + } + +} diff --git a/src/main/java/stanhebben/zenscript/expression/ExpressionJavaLambda.java b/src/main/java/stanhebben/zenscript/expression/ExpressionJavaLambda.java index 7a6d52a..823530d 100644 --- a/src/main/java/stanhebben/zenscript/expression/ExpressionJavaLambda.java +++ b/src/main/java/stanhebben/zenscript/expression/ExpressionJavaLambda.java @@ -1,15 +1,16 @@ package stanhebben.zenscript.expression; import org.objectweb.asm.*; +import org.objectweb.asm.Type; import stanhebben.zenscript.compiler.*; import stanhebben.zenscript.definitions.ParsedFunctionArgument; import stanhebben.zenscript.statements.Statement; -import stanhebben.zenscript.symbols.SymbolArgument; +import stanhebben.zenscript.symbols.*; import stanhebben.zenscript.type.ZenType; import stanhebben.zenscript.util.*; -import java.lang.reflect.Method; -import java.util.List; +import java.lang.reflect.*; +import java.util.*; import static stanhebben.zenscript.util.ZenTypeUtil.*; @@ -18,13 +19,13 @@ */ public class ExpressionJavaLambda extends Expression { - private final Class interfaceClass; + private final Class interfaceClass; private final List arguments; private final List statements; private final ZenType type; - public ExpressionJavaLambda(ZenPosition position, Class interfaceClass, List arguments, List statements, ZenType type) { + public ExpressionJavaLambda(ZenPosition position, Class interfaceClass, List arguments, List statements, ZenType type) { super(position); this.interfaceClass = interfaceClass; @@ -52,20 +53,13 @@ public void compile(boolean result, IEnvironmentMethod environment) { ClassWriter cw = new ZenClassWriter(ClassWriter.COMPUTE_FRAMES); cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, clsName, null, "java/lang/Object", new String[]{internal(interfaceClass)}); - MethodOutput constructor = new MethodOutput(cw, Opcodes.ACC_PUBLIC, "", "()V", null, null); - constructor.start(); - constructor.loadObject(0); - constructor.invokeSpecial("java/lang/Object", "", "()V"); - constructor.ret(); - constructor.end(); - MethodOutput output = new MethodOutput(cw, Opcodes.ACC_PUBLIC, method.getName(), descriptor(method), null, null); IEnvironmentClass environmentClass = new EnvironmentClass(cw, environment); - IEnvironmentMethod environmentMethod = new EnvironmentMethod(output, environmentClass); + EnvironmentMethodLambda environmentMethod = new EnvironmentMethodLambda(output, environmentClass, clsName); for(int i = 0, j = 0; i < arguments.size(); i++) { - environmentMethod.putValue(arguments.get(i).getName(), new SymbolArgument(i + 1, environment.getType(method.getGenericParameterTypes()[i])), getPosition()); + environmentMethod.putValue(arguments.get(i).getName(), new SymbolArgument(i + j + 1, environment.getType(method.getGenericParameterTypes()[i])), getPosition()); if(environment.getType(method.getGenericParameterTypes()[i]).isLarge()) j++; } @@ -76,12 +70,50 @@ public void compile(boolean result, IEnvironmentMethod environment) { } output.ret(); output.end(); + + + final List capturedVariables = environmentMethod.getCapturedVariables(); + final StringJoiner sj = new StringJoiner("", "(", ")V"); + for(SymbolCaptured value : capturedVariables) { + cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, value.getFieldName(), Type.getDescriptor(value.getType().toJavaClass()), null, null).visitEnd(); + sj.add(Type.getDescriptor(value.getType().toJavaClass())); + } + + MethodOutput constructor = new MethodOutput(cw, Opcodes.ACC_PUBLIC, "", sj.toString(), null, null); + constructor.start(); + constructor.loadObject(0); + constructor.invokeSpecial("java/lang/Object", "", "()V"); + + { + int i = 1, j = 0; + for(SymbolCaptured capturedVariable : capturedVariables) { + final ZenType type = capturedVariable.getType(); + constructor.loadObject(0); + constructor.load(Type.getType(type.toJavaClass()), i + j); + if(type.isLarge()) { + j++; + } + constructor.putField(clsName, capturedVariable.getFieldName(), Type.getDescriptor(capturedVariable.getType().toJavaClass())); + i++; + } + } + + constructor.ret(); + constructor.end(); + environment.putClass(clsName, cw.toByteArray()); // make class instance environment.getOutput().newObject(clsName); environment.getOutput().dup(); - environment.getOutput().construct(clsName); + final String[] arguments = capturedVariables.stream() + .map(SymbolCaptured::getEvaluated) + .peek(expression -> expression.compile(true, environment)) + .map(Expression::getType) + .map(ZenType::toASMType) + .map(Type::getDescriptor) + .toArray(String[]::new); + environment.getOutput().construct(clsName, arguments); } } diff --git a/src/main/java/stanhebben/zenscript/symbols/SymbolCaptured.java b/src/main/java/stanhebben/zenscript/symbols/SymbolCaptured.java new file mode 100644 index 0000000..68995d9 --- /dev/null +++ b/src/main/java/stanhebben/zenscript/symbols/SymbolCaptured.java @@ -0,0 +1,61 @@ +package stanhebben.zenscript.symbols; + +import org.objectweb.asm.Type; +import stanhebben.zenscript.compiler.*; +import stanhebben.zenscript.expression.*; +import stanhebben.zenscript.expression.partial.*; +import stanhebben.zenscript.type.*; +import stanhebben.zenscript.util.*; + +import java.util.*; + +public class SymbolCaptured implements IZenSymbol { + + private final String fieldName; + private final String lambdaClassName; + private final Expression evaluated; + + + public SymbolCaptured(Expression original, String fieldName, String clsName) { + this.evaluated = original; + this.fieldName = fieldName; + this.lambdaClassName = clsName; + } + + public ZenType getType() { + return evaluated.getType(); + } + + @Override + public IPartialExpression instance(ZenPosition position) { + return new Expression(position) { + @Override + public void compile(boolean result, IEnvironmentMethod environment) { + if(!result) + return; + + final MethodOutput output = environment.getOutput(); + if(lambdaClassName == null || fieldName == null || evaluated == null) { + throw new IllegalStateException(String.format(Locale.ENGLISH, "Captured variable with name %s in class %s and evaluated obj %s has at least one null info", fieldName, lambdaClassName, evaluated)); + } + + + output.loadObject(0); + output.getField(lambdaClassName, fieldName, Type.getDescriptor(getType().toJavaClass())); + } + + @Override + public ZenType getType() { + return SymbolCaptured.this.getType(); + } + }; + } + + public Expression getEvaluated() { + return evaluated; + } + + public String getFieldName() { + return fieldName; + } +} diff --git a/src/main/java/stanhebben/zenscript/type/ZenTypeNative.java b/src/main/java/stanhebben/zenscript/type/ZenTypeNative.java index b725aa1..8da3b4c 100644 --- a/src/main/java/stanhebben/zenscript/type/ZenTypeNative.java +++ b/src/main/java/stanhebben/zenscript/type/ZenTypeNative.java @@ -122,6 +122,8 @@ public void complete(ITypeRegistry types) { //TODO check this for(Method method : cls.getMethods()) { + //We only want those from the class itself, the super methods originate from the implements + //for(Method method : cls.getDeclaredMethods()) { boolean isMethod = false; String methodName = method.getName(); @@ -162,7 +164,27 @@ public void complete(ITypeRegistry types) { if(method.getParameterTypes().length != 0) { // TODO: error } else { - unaryOperators.add(new ZenNativeOperator(operatorAnnotation.value(), new JavaMethod(method, types))); + final ZenNativeOperator toAdd = new ZenNativeOperator(operatorAnnotation.value(), new JavaMethod(method, types)); + boolean found = false; + for(final ListIterator iter = unaryOperators.listIterator(); iter.hasNext();) { + final ZenNativeOperator presentOperator = iter.next(); + if(presentOperator.getOperator() == operatorAnnotation.value()) { + found = true; + final Class theirRetType = presentOperator.getMethod().getReturnType().toJavaClass(); + final Class toAddRetType = method.getReturnType(); + if(!toAddRetType.isAssignableFrom(theirRetType)) { + if(theirRetType.isAssignableFrom(theirRetType)) { + //Their type is less specific, use ours + iter.set(toAdd); + } else { + System.err.println("Two operators with similar methods were found: " + toAdd + " | " + presentOperator); + } + } + } + } + if(!found) { + unaryOperators.add(toAdd); + } } break; case ADD: @@ -182,7 +204,34 @@ public void complete(ITypeRegistry types) { if(method.getParameterTypes().length != 1) { // TODO: error } else { - binaryOperators.add(new ZenNativeOperator(operatorAnnotation.value(), new JavaMethod(method, types))); + final OperatorType operatorType = operatorAnnotation.value(); + final ZenNativeOperator toAdd = new ZenNativeOperator(operatorType, new JavaMethod(method, types)); + boolean found = false; + for(final ListIterator iter = binaryOperators.listIterator(); iter.hasNext(); ) { + ZenNativeOperator binaryOperator = iter.next(); + if(binaryOperator.getOperator() == operatorType) { + final IJavaMethod presentMethod = binaryOperator.getMethod(); + if(presentMethod.getParameterTypes()[0].toJavaClass().equals(method.getParameterTypes()[0])) { + found = true; + final Class theirRetType = presentMethod.getReturnType().toJavaClass(); + final Class methodRetType = method.getReturnType(); + if(!methodRetType.isAssignableFrom(theirRetType)) { + if(theirRetType.isAssignableFrom(methodRetType)) { + //Their type is less specific, use ours + iter.set(toAdd); + } else { + System.err.println("Two operators with similar methods were found: " + toAdd + " | " + presentMethod); + } + } + + } + } + } + + if(!found) { + //Otherwise we'd already set it in the iterator or used the other method instead + binaryOperators.add(toAdd); + } } break; case INDEXSET: diff --git a/src/main/java/stanhebben/zenscript/type/natives/JavaMethod.java b/src/main/java/stanhebben/zenscript/type/natives/JavaMethod.java index 355f5ab..d1f89bd 100644 --- a/src/main/java/stanhebben/zenscript/type/natives/JavaMethod.java +++ b/src/main/java/stanhebben/zenscript/type/natives/JavaMethod.java @@ -43,10 +43,15 @@ public JavaMethod(Method method, ITypeRegistry types) { } } boolean lastOptional = false; - for(boolean optional : optional) { - if(lastOptional && !optional) - throw new IllegalArgumentException("All optionals need to be placed at the end of the method declaration: " + method.toGenericString()); - lastOptional = optional; + for(int i = 0; i < optional.length; i++) { + boolean opt = optional[i]; + if(lastOptional && !opt) { + System.err.println("All optionals need to be placed at the end of the method declaration: " + method.toGenericString() + "! Setting last parameters to optional"); + optional[i] = lastOptional = true; + //throw new IllegalArgumentException("All optionals need to be placed at the end of the method declaration: " + method.toGenericString()); + } else { + lastOptional = opt; + } } } diff --git a/src/main/java/stanhebben/zenscript/value/IntRange.java b/src/main/java/stanhebben/zenscript/value/IntRange.java index 64a1006..229c55e 100644 --- a/src/main/java/stanhebben/zenscript/value/IntRange.java +++ b/src/main/java/stanhebben/zenscript/value/IntRange.java @@ -1,5 +1,7 @@ package stanhebben.zenscript.value; +import java.util.*; + /** * @author Stan Hebben */ @@ -7,10 +9,12 @@ public class IntRange { private final int from; private final int to; + private final Random rand; public IntRange(int from, int to) { this.from = from; this.to = to; + rand = new Random(2906); } public int getFrom() { @@ -20,4 +24,16 @@ public int getFrom() { public int getTo() { return to; } + + public int getMin() { + return getFrom(); + } + + public int getMax() { + return getTo(); + } + + public int getRandom() { + return rand.nextInt((to - from) + 1) + from; + } } diff --git a/src/test/java/stanhebben/zenscript/tests/TestLambdas.java b/src/test/java/stanhebben/zenscript/tests/TestLambdas.java new file mode 100644 index 0000000..e0a7b46 --- /dev/null +++ b/src/test/java/stanhebben/zenscript/tests/TestLambdas.java @@ -0,0 +1,38 @@ +package stanhebben.zenscript.tests; + +import org.junit.jupiter.api.*; +import stanhebben.zenscript.*; +import stanhebben.zenscript.annotations.*; +import stanhebben.zenscript.expression.*; + +import static stanhebben.zenscript.TestAssertions.assertMany; + +public class TestLambdas { + + @BeforeAll + public static void setupEnvironment() { + TestHelper.setupEnvironment(); + TestHelper.registry.registerGlobal("applyFun", TestHelper.registry.getStaticFunction(TestLambdas.class, "applyFun", FunInterface.class)); + } + + @BeforeEach + public void beforeEach() { + TestHelper.beforeEach(); + } + + @Test + public void Test_FunctionExpression_Capture() { + TestHelper.run("val x = 10; print(applyFun(function(a){return a + x;}));"); + assertMany("11"); + } + + @ZenClass("tests.fun.FunInterface") + @FunctionalInterface + public interface FunInterface { + int apply(int a); + } + + public static int applyFun(FunInterface i) { + return i.apply(1); + } +}