diff --git a/src/main/java/de/dhbwstuttgart/target/bytecode/Codegen.java b/src/main/java/de/dhbwstuttgart/target/bytecode/Codegen.java index ee5ed659b..cbc35ad74 100755 --- a/src/main/java/de/dhbwstuttgart/target/bytecode/Codegen.java +++ b/src/main/java/de/dhbwstuttgart/target/bytecode/Codegen.java @@ -1,17 +1,18 @@ package de.dhbwstuttgart.target.bytecode; -import de.dhbwstuttgart.target.tree.TargetClass; -import de.dhbwstuttgart.target.tree.TargetConstructor; -import de.dhbwstuttgart.target.tree.TargetField; -import de.dhbwstuttgart.target.tree.TargetMethod; +import de.dhbwstuttgart.target.tree.*; import de.dhbwstuttgart.target.tree.expression.*; +import de.dhbwstuttgart.target.tree.type.TargetFunNType; import de.dhbwstuttgart.target.tree.type.TargetRefType; import de.dhbwstuttgart.target.tree.type.TargetType; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.*; +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.objectweb.asm.Opcodes.*; @@ -56,6 +57,7 @@ public class Codegen { private static class State { Scope scope = new Scope(null); int localCounter; + int lambdaCounter; MethodVisitor mv; State(MethodVisitor mv, int localCounter) { @@ -216,7 +218,8 @@ public class Codegen { else if (dest.equals(TargetType.Double)) mv.visitInsn(I2D); } else { - mv.visitTypeInsn(CHECKCAST, ((TargetRefType) dest).getName()); + mv.visitTypeInsn(CHECKCAST, dest.getName()); + unboxPrimitive(state, dest); } } @@ -515,6 +518,38 @@ public class Codegen { } private void generateLambdaExpression(State state, TargetLambdaExpression lambda) { + var mv = state.mv; + var name = "lambda$" + state.lambdaCounter; + var impl = new TargetMethod( + ACC_PRIVATE, new TargetRefType(clazz.qualifiedName()), name, + lambda.params(), lambda.returnType(), lambda.block() + ); + generateMethod(impl); + + var mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, + MethodType.class, MethodType.class, MethodHandle.class, MethodType.class); + + var bootstrap = new Handle(H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory", + mt.toMethodDescriptorString(), false); + var handle = new Handle( + H_INVOKEVIRTUAL, clazz.getName(), name, + impl.getDescriptor(), false + ); + + // TODO maybe make this a function? + var desugared = "("; + for (var param : lambda.params()) + desugared += "Ljava/lang/Object;"; + desugared += ")"; + if (lambda.returnType() != null) + desugared += "Ljava/lang/Object;"; + else desugared += "V"; + + mv.visitVarInsn(ALOAD, 0); + mv.visitInvokeDynamicInsn("apply", TargetMethod.getDescriptor(lambda.type(), new TargetRefType(clazz.qualifiedName())), + bootstrap, Type.getType(desugared), handle, Type.getType(impl.getDescriptor())); + + state.lambdaCounter++; } private void generate(State state, TargetExpression expr) { @@ -542,7 +577,7 @@ public class Codegen { convertTo(state, cast.expr().type(), cast.type()); break; case TargetInstanceOf instanceOf: - mv.visitTypeInsn(INSTANCEOF, ((TargetRefType) instanceOf.right()).getName()); + mv.visitTypeInsn(INSTANCEOF, instanceOf.right().getName()); break; case TargetLiteral literal: switch (literal) { @@ -595,13 +630,12 @@ public class Codegen { } case TargetFieldVar dot: { generate(state, dot.left()); - TargetRefType type = (TargetRefType) dot.type(); generate(state, assign.right()); boxPrimitive(state, assign.right().type()); if (dot.isStatic()) mv.visitInsn(DUP); else mv.visitInsn(DUP_X1); - mv.visitFieldInsn(dot.isStatic() ? PUTSTATIC : PUTFIELD, dot.right(), type.getName(), type.toSignature()); + mv.visitFieldInsn(dot.isStatic() ? PUTSTATIC : PUTFIELD, dot.owner().getName(), dot.right(), dot.type().toSignature()); break; } default: @@ -618,8 +652,7 @@ public class Codegen { case TargetFieldVar dot: { if (!dot.isStatic()) generate(state, dot.left()); - var type = (TargetRefType) dot.left().type(); - mv.visitFieldInsn(dot.isStatic() ? GETSTATIC : GETFIELD, type.getName(), dot.right(), dot.type().toSignature()); + mv.visitFieldInsn(dot.isStatic() ? GETSTATIC : GETFIELD, dot.left().type().getName(), dot.right(), dot.type().toSignature()); break; } case TargetFor _for: { @@ -685,7 +718,7 @@ public class Codegen { break; } case TargetSuper _super: { - // TODO + mv.visitVarInsn(ALOAD, 0); break; } case TargetMethodCall call: { @@ -694,12 +727,29 @@ public class Codegen { generate(state, e); boxPrimitive(state, e.type()); } - mv.visitMethodInsn(call.isStatic() ? INVOKESTATIC: INVOKEVIRTUAL, call.owner().getName(), call.name(), call.descriptor(), false); + if (call.expr() instanceof TargetThis) + mv.visitMethodInsn(INVOKESPECIAL, clazz.getName(), "", call.getDescriptor(), false); + else if (call.expr() instanceof TargetSuper) + mv.visitMethodInsn(INVOKESPECIAL, clazz.superType().getName(), "", call.getDescriptor(), false); + else mv.visitMethodInsn(call.isInterface() ? INVOKEINTERFACE : call.isStatic() ? INVOKESTATIC: INVOKEVIRTUAL, + call.owner().getName(), call.name(), call.getDescriptor(), call.isInterface()); + if (call.type() != null) + unboxPrimitive(state, call.type()); break; } case TargetLambdaExpression lambda: generateLambdaExpression(state, lambda); break; + case TargetNew _new: { + mv.visitTypeInsn(NEW, _new.type().getName()); + mv.visitInsn(DUP); + for (TargetExpression e : _new.params()) { + generate(state, e); + boxPrimitive(state, e.type()); + } + mv.visitMethodInsn(INVOKESPECIAL, _new.type().getName(), "", _new.getDescriptor(), false); + break; + } default: throw new CodeGenException("Unexpected value: " + expr); } @@ -709,12 +759,26 @@ public class Codegen { cw.visitField(field.access(), field.name(), field.getDescriptor(), null, null); } + private boolean hasThisOrSuperCall(TargetBlock block) { + if (block.statements().size() == 0) return false; + TargetExpression first = block.statements().get(0); + if (!(first instanceof TargetMethodCall)) return false; + var methodCall = (TargetMethodCall) first; + if (methodCall.expr() instanceof TargetThis || methodCall.expr() instanceof TargetSuper) + return true; + return false; + } + private void generateConstructor(TargetConstructor constructor) { MethodVisitor mv = cw.visitMethod(constructor.access(), "", constructor.getDescriptor(), null, null); mv.visitCode(); var state = new State(mv, 1); for (var param: constructor.parameters()) state.createVariable(param.name(), param.type()); + if (!hasThisOrSuperCall(constructor.block())) { + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, clazz.superType().getName(), "", "()V", false); + } generate(state, constructor.block()); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); @@ -736,20 +800,11 @@ public class Codegen { public byte[] generate() { cw.visit(V1_8, clazz.modifiers(), clazz.qualifiedName(), - null, ((TargetRefType) clazz.superType()).getName(), + null, clazz.superType().getName(), clazz.implementingInterfaces().stream().map(TargetType::toSignature).toArray(String[]::new) ); clazz.fields().forEach(this::generateField); - if (clazz.constructors().size() == 0) { - var mv = cw.visitMethod(ACC_PROTECTED, "", "()V", null, null); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKESPECIAL, ((TargetRefType) clazz.superType()).getName(), "", "()V", false); - mv.visitInsn(RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - else clazz.constructors().forEach(this::generateConstructor); + clazz.constructors().forEach(this::generateConstructor); clazz.methods().forEach(this::generateMethod); cw.visitEnd(); diff --git a/src/main/java/de/dhbwstuttgart/target/tree/TargetClass.java b/src/main/java/de/dhbwstuttgart/target/tree/TargetClass.java index 630101368..e7f990c0b 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/TargetClass.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/TargetClass.java @@ -17,6 +17,10 @@ public record TargetClass(int modifiers, String qualifiedName, TargetType superT this(modifiers, qualifiedName, TargetType.Object, implementingInterfaces, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); } + public String getName() { + return qualifiedName.replaceAll("\\.", "/"); + } + public void addMethod(int access, String name, List parameterTypes, TargetType returnType, TargetBlock block) { this.methods.add(new TargetMethod(access, new TargetRefType(this.qualifiedName, List.of()), name, parameterTypes, returnType, block)); } @@ -25,7 +29,7 @@ public record TargetClass(int modifiers, String qualifiedName, TargetType superT this.constructors.add(new TargetConstructor(access, new TargetRefType(this.qualifiedName, List.of()), paramterTypes, block)); } - public void addField(int access, TargetType type, String name) { + public void addField(int access, TargetRefType type, String name) { this.fields.add(new TargetField(access, new TargetRefType(this.qualifiedName, List.of()), type, name)); } } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/TargetConstructor.java b/src/main/java/de/dhbwstuttgart/target/tree/TargetConstructor.java index 3a242c1ed..3c353262e 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/TargetConstructor.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/TargetConstructor.java @@ -2,14 +2,14 @@ package de.dhbwstuttgart.target.tree; import de.dhbwstuttgart.target.tree.expression.TargetBlock; import de.dhbwstuttgart.target.tree.type.TargetRefType; +import de.dhbwstuttgart.target.tree.type.TargetType; import java.util.List; -public record TargetConstructor(int access, TargetRefType owner, List parameters, TargetBlock block) { +public record TargetConstructor(int access, TargetType owner, List parameters, TargetBlock block) { public String getDescriptor() { - // TODO - return null; + return TargetMethod.getDescriptor(null, parameters.stream().map(MethodParameter::type).toArray(TargetType[]::new)); } } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/TargetField.java b/src/main/java/de/dhbwstuttgart/target/tree/TargetField.java index 2c183dcbd..489192a23 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/TargetField.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/TargetField.java @@ -1,13 +1,11 @@ package de.dhbwstuttgart.target.tree; -import de.dhbwstuttgart.target.tree.type.TargetRefType; import de.dhbwstuttgart.target.tree.type.TargetType; import org.objectweb.asm.Opcodes; -public record TargetField(int access, TargetRefType owner, TargetType type, String name) { +public record TargetField(int access, TargetType owner, TargetType type, String name) { public String getDescriptor() { - // TODO - return null; + return type.toSignature(); } public boolean isStatic() { diff --git a/src/main/java/de/dhbwstuttgart/target/tree/TargetMethod.java b/src/main/java/de/dhbwstuttgart/target/tree/TargetMethod.java index bfd8f3b41..52ea71a0a 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/TargetMethod.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/TargetMethod.java @@ -8,7 +8,7 @@ import org.objectweb.asm.Opcodes; import java.util.List; import java.util.stream.Collectors; -public record TargetMethod(int access, TargetRefType owner, String name, List parameters, TargetType returnType, TargetBlock block) { +public record TargetMethod(int access, TargetType owner, String name, List parameters, TargetType returnType, TargetBlock block) { public static String getDescriptor(TargetType returnType, TargetType... parameters) { String ret = "("; for (var parameterType : parameters) { diff --git a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetBinaryOp.java b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetBinaryOp.java index 814a4d673..12d56d5b5 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetBinaryOp.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetBinaryOp.java @@ -35,6 +35,7 @@ public sealed interface TargetBinaryOp extends TargetExpression { } // Comparison + // exprType is the type that both arguments get converted to before comparison record Equal(TargetType exprType, TargetExpression left, TargetExpression right) implements TargetRelationalOp {} record Greater(TargetType exprType, TargetExpression left, TargetExpression right) implements TargetRelationalOp {} record GreaterOrEqual(TargetType exprType, TargetExpression left, TargetExpression right) implements TargetRelationalOp {} diff --git a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetFieldVar.java b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetFieldVar.java index 5d41b1b4e..0a72118ef 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetFieldVar.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetFieldVar.java @@ -1,6 +1,7 @@ package de.dhbwstuttgart.target.tree.expression; +import de.dhbwstuttgart.target.tree.type.TargetRefType; import de.dhbwstuttgart.target.tree.type.TargetType; -public record TargetFieldVar(TargetType type, boolean isStatic, TargetExpression left, String right) implements TargetExpression { +public record TargetFieldVar(TargetType type, TargetRefType owner, boolean isStatic, TargetExpression left, String right) implements TargetExpression { } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetLambdaExpression.java b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetLambdaExpression.java index 13da5b9c6..c0a5a8118 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetLambdaExpression.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetLambdaExpression.java @@ -5,5 +5,5 @@ import de.dhbwstuttgart.target.tree.type.TargetType; import java.util.List; -public record TargetLambdaExpression(TargetType type, List params, TargetExpression block) implements TargetExpression { +public record TargetLambdaExpression(TargetType type, List params, TargetType returnType, TargetBlock block) implements TargetExpression { } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetMethodCall.java b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetMethodCall.java index 132ec11b5..123259629 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetMethodCall.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetMethodCall.java @@ -1,10 +1,19 @@ package de.dhbwstuttgart.target.tree.expression; +import de.dhbwstuttgart.target.tree.MethodParameter; import de.dhbwstuttgart.target.tree.TargetMethod; import de.dhbwstuttgart.target.tree.type.TargetRefType; import de.dhbwstuttgart.target.tree.type.TargetType; import java.util.List; -public record TargetMethodCall(TargetType type, TargetExpression expr, List args, TargetRefType owner, String name, String descriptor, boolean isStatic) implements TargetStatementExpression { +public record TargetMethodCall(TargetType type, List parameterTypes, TargetExpression expr, List args, TargetType owner, String name, boolean isStatic, boolean isInterface) implements TargetStatementExpression { + public TargetMethodCall(TargetType type, TargetExpression expr, List args, TargetType owner, String name, boolean isStatic, boolean isInterface) { + this(type, args.stream().map(TargetExpression::type).toList(), expr, args, owner, name, isStatic, isInterface); + } + + + public String getDescriptor() { + return TargetMethod.getDescriptor(type, parameterTypes.toArray(TargetType[]::new)); + } } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetNew.java b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetNew.java index 30fb295c8..e9d4ce8ce 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetNew.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetNew.java @@ -1,8 +1,12 @@ package de.dhbwstuttgart.target.tree.expression; +import de.dhbwstuttgart.target.tree.TargetMethod; import de.dhbwstuttgart.target.tree.type.TargetType; import java.util.List; public record TargetNew(TargetType type, List params) implements TargetStatementExpression { + public String getDescriptor() { + return TargetMethod.getDescriptor(null, params.stream().map(TargetExpression::type).toArray(TargetType[]::new)); + } } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetExtendsWildcard.java b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetExtendsWildcard.java index 4dc033de8..c9d70f199 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetExtendsWildcard.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetExtendsWildcard.java @@ -1,11 +1,14 @@ package de.dhbwstuttgart.target.tree.type; -import java.util.List; - public record TargetExtendsWildcard(TargetType innerType) implements TargetType { @Override public String toSignature() { return null; } + + @Override + public String getName() { + return null; + } } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java index 763948368..cfdf2b3cf 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetFunNType.java @@ -2,9 +2,14 @@ package de.dhbwstuttgart.target.tree.type; import java.util.List; -public record TargetFunNType(int N, List params) implements TargetType { +public record TargetFunNType(int N, List params) implements TargetType { + @Override + public String getName() { + return "Fun" + N + "$$"; + } + @Override public String toSignature() { - return "Fun$$" + N; + return "L" + getName() + ";"; } } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetGenericType.java b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetGenericType.java index 122696327..ab2e3c2b9 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetGenericType.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetGenericType.java @@ -5,4 +5,9 @@ public record TargetGenericType(String name) implements TargetType { public String toSignature() { return null; } + + @Override + public java.lang.String getName() { + return null; + } } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetSuperWildcard.java b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetSuperWildcard.java index 1414daabc..fee113758 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetSuperWildcard.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetSuperWildcard.java @@ -5,4 +5,9 @@ public record TargetSuperWildcard(TargetType innerType) implements TargetType { public String toSignature() { return null; } + + @Override + public String getName() { + return null; + } } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java index f3d6092f4..5d12cd347 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java @@ -18,4 +18,5 @@ public sealed interface TargetType TargetRefType Object = new TargetRefType("java.lang.Object"); String toSignature(); + String getName(); } diff --git a/src/test/java/targetast/ByteArrayClassLoader.java b/src/test/java/targetast/ByteArrayClassLoader.java index 227b7094a..66937cf4a 100644 --- a/src/test/java/targetast/ByteArrayClassLoader.java +++ b/src/test/java/targetast/ByteArrayClassLoader.java @@ -1,9 +1,16 @@ package targetast; -import java.util.Map; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; public class ByteArrayClassLoader extends ClassLoader { public Class loadClass(byte[] code) { return this.defineClass(null, code, 0, code.length); } + + public Class loadClass(Path path) throws IOException { + var code = Files.readAllBytes(path); + return this.defineClass(null, code, 0, code.length); + } } diff --git a/src/test/java/targetast/Fun1$$.java b/src/test/java/targetast/Fun1$$.java new file mode 100644 index 000000000..882112f24 --- /dev/null +++ b/src/test/java/targetast/Fun1$$.java @@ -0,0 +1,3 @@ +public interface Fun1$$ { + public R apply(T t); +} diff --git a/src/test/java/targetast/TestCodegen.java b/src/test/java/targetast/TestCodegen.java index 0171032ff..1806fb33f 100644 --- a/src/test/java/targetast/TestCodegen.java +++ b/src/test/java/targetast/TestCodegen.java @@ -3,7 +3,6 @@ package targetast; import de.dhbwstuttgart.target.bytecode.Codegen; import de.dhbwstuttgart.target.tree.MethodParameter; import de.dhbwstuttgart.target.tree.TargetClass; -import de.dhbwstuttgart.target.tree.TargetMethod; import de.dhbwstuttgart.target.tree.expression.*; import de.dhbwstuttgart.target.tree.type.TargetRefType; import de.dhbwstuttgart.target.tree.type.TargetType; @@ -20,13 +19,15 @@ import java.util.List; public class TestCodegen { + private static ByteArrayClassLoader loader = new ByteArrayClassLoader(); + private static Class generateClass(TargetClass clazz) throws IOException { var codegen = new Codegen(clazz); var bytes = codegen.generate(); var path = Path.of(System.getProperty("user.dir"), "src/test/resources/target/"); Files.createDirectories(path); Files.write(path.resolve(clazz.qualifiedName() + ".class"), bytes, StandardOpenOption.CREATE); - return new ByteArrayClassLoader().loadClass(bytes); + return loader.loadClass(bytes); } @Test @@ -85,6 +86,34 @@ public class TestCodegen { assertEquals(clazz.getDeclaredMethod("rem", Integer.class, Integer.class).invoke(null, 10, 3), 1); } + @Test + public void testConditional() throws Exception { + var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "Conditional"); + + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "and", + List.of(new MethodParameter(TargetType.Boolean, "a"), new MethodParameter(TargetType.Boolean, "b")), + TargetType.Boolean, + new TargetBlock(List.of(new TargetReturn( + new TargetBinaryOp.And(TargetType.Boolean, new TargetLocalVar(TargetType.Boolean, "a"), new TargetLocalVar(TargetType.Boolean, "b"))) + )) + ); + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "or", + List.of(new MethodParameter(TargetType.Boolean, "a"), new MethodParameter(TargetType.Boolean, "b")), + TargetType.Boolean, + new TargetBlock(List.of(new TargetReturn( + new TargetBinaryOp.Or(TargetType.Boolean, new TargetLocalVar(TargetType.Boolean, "a"), new TargetLocalVar(TargetType.Boolean, "b"))) + )) + ); + + var clazz = generateClass(targetClass); + var and = clazz.getDeclaredMethod("and", Boolean.class, Boolean.class); + var or = clazz.getDeclaredMethod("or", Boolean.class, Boolean.class); + assertEquals(and.invoke(null, true, false), false); + assertEquals(and.invoke(null, true, true), true); + assertEquals(or.invoke(null, false, false), false); + assertEquals(or.invoke(null, true, false), true); + } + // When adding two numbers and the return type is Long it needs to convert both values to Long @Test public void testArithmeticConvert() throws Exception { @@ -104,15 +133,16 @@ public class TestCodegen { targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "helloWorld", List.of(), null, new TargetBlock(List.of(new TargetMethodCall(null, new TargetFieldVar( - new TargetRefType("java.io.PrintStream"), true, + new TargetRefType("java.io.PrintStream"), + new TargetRefType("java.lang.System"), + true, new TargetClassName(new TargetRefType("java.lang.System")), "out" ), List.of(new TargetLiteral.StringLiteral("Hello World!")), new TargetRefType("java.io.PrintStream"), "println", - TargetMethod.getDescriptor(null, TargetType.String), - false + false, false ))) ); @@ -190,4 +220,72 @@ public class TestCodegen { var clazz = generateClass(targetClass); assertEquals(clazz.getDeclaredMethod("whileLoop").invoke(null), 10); } + + @Test + public void testNew() throws Exception { + var pointType = new TargetRefType("Point"); + var pointTarget = new TargetClass(Opcodes.ACC_PUBLIC, "Point"); + pointTarget.addField(Opcodes.ACC_PUBLIC, TargetType.Integer, "x"); + pointTarget.addField(Opcodes.ACC_PUBLIC, TargetType.Integer, "y"); + pointTarget.addConstructor(Opcodes.ACC_PUBLIC, + List.of(new MethodParameter(TargetType.Integer, "x"), new MethodParameter(TargetType.Integer, "y")), + new TargetBlock(List.of( + new TargetAssign(TargetType.Integer, + new TargetFieldVar(TargetType.Integer, pointType, false, new TargetThis(pointType), "x"), + new TargetLocalVar(TargetType.Integer, "x") + ), + new TargetAssign(TargetType.Integer, + new TargetFieldVar(TargetType.Integer, pointType, false, new TargetThis(pointType), "y"), + new TargetLocalVar(TargetType.Integer, "y") + ) + )) + ); + + var mainTarget = new TargetClass(Opcodes.ACC_PUBLIC, "New"); + mainTarget.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "makePoint", + List.of(new MethodParameter(TargetType.Integer, "x"), new MethodParameter(TargetType.Integer, "y")), pointType, + new TargetBlock(List.of( + new TargetReturn(new TargetNew(pointType, List.of( + new TargetLocalVar(TargetType.Integer, "x"), + new TargetLocalVar(TargetType.Integer, "y") + ))) + )) + ); + + var pointClass = generateClass(pointTarget); + var mainClass = generateClass(mainTarget); + + var point = mainClass.getDeclaredMethod("makePoint", Integer.class, Integer.class).invoke(null, 10, 20); + assertEquals(point.getClass().getDeclaredField("x").get(point), 10); + assertEquals(point.getClass().getDeclaredField("y").get(point), 20); + } + + @Test + public void testLambda() throws Exception { + var fun = loader.loadClass(Path.of(System.getProperty("user.dir"), "src/test/java/targetast/Fun1$$.class")); + var interfaceType = new TargetRefType("Fun1$$"); + + var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "Lambda"); + targetClass.addConstructor(Opcodes.ACC_PUBLIC, List.of(), new TargetBlock(List.of())); + targetClass.addMethod(Opcodes.ACC_PUBLIC, "lambda", List.of(), TargetType.Integer, + new TargetBlock(List.of( + new TargetVarDecl(interfaceType, "by2", + new TargetLambdaExpression(interfaceType, List.of(new MethodParameter(TargetType.Integer, "num")), TargetType.Integer, + new TargetBlock(List.of( + new TargetReturn(new TargetBinaryOp.Mul(TargetType.Integer, + new TargetLocalVar(TargetType.Integer, "num"), + new TargetLiteral.IntLiteral(2) + )) + ) + )) + ), + new TargetReturn(new TargetCast(TargetType.Integer, new TargetMethodCall(TargetType.Object, List.of(TargetType.Object), new TargetLocalVar(interfaceType, "by2"), List.of( + new TargetLiteral.IntLiteral(10) + ), interfaceType, "apply", false, true))) + )) + ); + var clazz = generateClass(targetClass); + var instance = clazz.getConstructor().newInstance(); + assertEquals(clazz.getDeclaredMethod("lambda").invoke(instance), 20); + } } diff --git a/src/test/resources/target/Test.java b/src/test/resources/target/Test.java new file mode 100644 index 000000000..f7e1a0bc8 --- /dev/null +++ b/src/test/resources/target/Test.java @@ -0,0 +1,9 @@ +public class Test { + public void lambda() { + Interface mul2 = (Integer a) -> a * 2; + } +} + +interface Interface { + R apply(T t); +} \ No newline at end of file