From 6c584f92e99910d2d90157451b76a349a2eb70b2 Mon Sep 17 00:00:00 2001 From: Victorious3 Date: Mon, 16 May 2022 14:18:58 +0200 Subject: [PATCH] More tests --- .../target/bytecode/Codegen.java | 228 +++++++++++++++--- .../target/generate/ASTToTargetAST.java | 2 +- .../target/tree/TargetClass.java | 8 +- .../target/tree/TargetConstructor.java | 3 +- .../target/tree/TargetField.java | 3 +- .../target/tree/TargetMethod.java | 22 +- .../tree/expression/TargetBinaryOp.java | 20 +- .../target/tree/expression/TargetLiteral.java | 23 +- .../tree/expression/TargetMethodCall.java | 3 +- .../target/tree/expression/TargetVarDecl.java | 2 +- .../target/tree/type/TargetRefType.java | 10 +- .../target/tree/type/TargetType.java | 20 +- src/test/java/targetast/TestCodegen.java | 178 +++++++++++++- 13 files changed, 455 insertions(+), 67 deletions(-) diff --git a/src/main/java/de/dhbwstuttgart/target/bytecode/Codegen.java b/src/main/java/de/dhbwstuttgart/target/bytecode/Codegen.java index c8bf7fd8..ee5ed659 100755 --- a/src/main/java/de/dhbwstuttgart/target/bytecode/Codegen.java +++ b/src/main/java/de/dhbwstuttgart/target/bytecode/Codegen.java @@ -11,10 +11,12 @@ import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; +import java.util.HashMap; import java.util.Map; import static org.objectweb.asm.Opcodes.*; import static de.dhbwstuttgart.target.tree.expression.TargetBinaryOp.*; +import static de.dhbwstuttgart.target.tree.expression.TargetLiteral.*; public class Codegen { private TargetClass clazz; @@ -25,11 +27,11 @@ public class Codegen { this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); } - private static record LocalVar(int index, String name, TargetType type) {} + private record LocalVar(int index, String name, TargetType type) {} private static class Scope { Scope parent; - Map locals; + Map locals = new HashMap<>(); Scope(Scope parent) { this.parent = parent; @@ -41,7 +43,7 @@ public class Codegen { LocalVar get(String name) { var local = locals.get(name); - if (local != null ){ + if (local != null) { return local; } if (parent != null) { @@ -53,11 +55,12 @@ public class Codegen { private static class State { Scope scope = new Scope(null); - int localCounter = 1; + int localCounter; MethodVisitor mv; - State(MethodVisitor mv) { + State(MethodVisitor mv, int localCounter) { this.mv = mv; + this.localCounter = localCounter; } void enterScope() { @@ -68,9 +71,11 @@ public class Codegen { this.scope = this.scope.parent; } - void createVariable(String name, TargetType type) { - scope.add(new LocalVar(localCounter, name, type)); + LocalVar createVariable(String name, TargetType type) { + var local = new LocalVar(localCounter, name, type); + scope.add(local); localCounter += 1; + return local; } } @@ -83,9 +88,9 @@ public class Codegen { } else if (type.equals(TargetType.Double)) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); } else if (type.equals(TargetType.Long)) { - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(L)Ljava/lang/Long;", false); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); } else if (type.equals(TargetType.Integer)) { - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(L)Ljava/lang/Integer;", false); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); } else if (type.equals(TargetType.Float)) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); } else if (type.equals(TargetType.Short)) { @@ -104,7 +109,7 @@ public class Codegen { } else if (type.equals(TargetType.Double)) { mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false); } else if (type.equals(TargetType.Long)) { - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()L", false); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false); } else if (type.equals(TargetType.Integer)) { mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); } else if (type.equals(TargetType.Float)) { @@ -116,12 +121,14 @@ public class Codegen { } } - private void generateRelationalOperator(State state, TargetBinaryOp op, int code) { + private void generateRelationalOperator(State state, TargetRelationalOp op, int code) { var mv = state.mv; Label if_true = new Label(); Label end = new Label(); generate(state, op.left()); + convertTo(state, op.exprType(), op.type()); generate(state, op.right()); + convertTo(state, op.exprType(), op.type()); mv.visitJumpInsn(code, if_true); mv.visitInsn(ICONST_0); mv.visitJumpInsn(GOTO, end); @@ -130,12 +137,14 @@ public class Codegen { mv.visitLabel(end); } - private void generateRelationalOperator(State state, TargetBinaryOp op, int cmp, int code) { + private void generateRelationalOperator(State state, TargetRelationalOp op, int cmp, int code) { var mv = state.mv; Label if_true = new Label(); Label end = new Label(); generate(state, op.left()); + convertTo(state, op.left().type(), op.exprType()); generate(state, op.right()); + convertTo(state, op.right().type(), op.exprType()); mv.visitInsn(cmp); mv.visitInsn(code); mv.visitJumpInsn(code, if_true); @@ -146,14 +155,82 @@ public class Codegen { mv.visitLabel(end); } + private void convertTo(State state, TargetType source, TargetType dest) { + var mv = state.mv; + if (source.equals(dest)) + return; + if (source.equals(TargetType.Long)) { + if (dest.equals(TargetType.Integer)) + mv.visitInsn(L2I); + else if (dest.equals(TargetType.Float)) + mv.visitInsn(L2F); + else if (dest.equals(TargetType.Double)) + mv.visitInsn(L2D); + else if (dest.equals(TargetType.Byte) + || dest.equals(TargetType.Char) + || dest.equals(TargetType.Short)) { + mv.visitInsn(L2I); + source = TargetType.Integer; + } + } else if (source.equals(TargetType.Float)) { + if (dest.equals(TargetType.Integer)) + mv.visitInsn(F2I); + else if (dest.equals(TargetType.Double)) + mv.visitInsn(F2D); + else if (dest.equals(TargetType.Long)) + mv.visitInsn(F2L); + else if (dest.equals(TargetType.Byte) + || dest.equals(TargetType.Char) + || dest.equals(TargetType.Short)) { + mv.visitInsn(F2I); + source = TargetType.Integer; + } + } else if (source.equals(TargetType.Double)) { + if (dest.equals(TargetType.Integer)) + mv.visitInsn(D2I); + else if (dest.equals(TargetType.Float)) + mv.visitInsn(D2F); + else if (dest.equals(TargetType.Long)) + mv.visitInsn(D2L); + else if (dest.equals(TargetType.Byte) + || dest.equals(TargetType.Char) + || dest.equals(TargetType.Short)) { + mv.visitInsn(D2I); + source = TargetType.Integer; + } + } + if (source.equals(TargetType.Byte) + || source.equals(TargetType.Char) + || source.equals(TargetType.Short) + || source.equals(TargetType.Integer)) { + if (dest.equals(TargetType.Byte)) + mv.visitInsn(I2B); + else if (dest.equals(TargetType.Char)) + mv.visitInsn(I2C); + else if (dest.equals(TargetType.Short)) + mv.visitInsn(I2S); + else if (dest.equals(TargetType.Long)) + mv.visitInsn(I2L); + else if (dest.equals(TargetType.Float)) + mv.visitInsn(I2F); + else if (dest.equals(TargetType.Double)) + mv.visitInsn(I2D); + } else { + mv.visitTypeInsn(CHECKCAST, ((TargetRefType) dest).getName()); + } + } + private void generateBinaryOp(State state, TargetBinaryOp op) { var mv = state.mv; switch (op) { case Add add: { generate(state, add.left()); + convertTo(state, add.left().type(), op.type()); generate(state, add.right()); + convertTo(state, add.right().type(), op.type()); var type = add.type(); if (type.equals(TargetType.Byte) + || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) { mv.visitInsn(IADD); @@ -170,9 +247,12 @@ public class Codegen { } case Sub sub: { generate(state, sub.left()); + convertTo(state, sub.left().type(), op.type()); generate(state, sub.right()); + convertTo(state, sub.right().type(), op.type()); var type = sub.type(); if (type.equals(TargetType.Byte) + || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) { mv.visitInsn(ISUB); @@ -189,9 +269,12 @@ public class Codegen { } case Div div: { generate(state, div.left()); + convertTo(state, div.left().type(), op.type()); generate(state, div.right()); + convertTo(state, div.right().type(), op.type()); var type = div.type(); if (type.equals(TargetType.Byte) + || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) { mv.visitInsn(IDIV); @@ -208,9 +291,12 @@ public class Codegen { } case Mul mul: { generate(state, mul.left()); + convertTo(state, mul.left().type(), op.type()); generate(state, mul.right()); + convertTo(state, mul.right().type(), op.type()); var type = mul.type(); if (type.equals(TargetType.Byte) + || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) { mv.visitInsn(IMUL); @@ -227,9 +313,12 @@ public class Codegen { } case Rem rem: { generate(state, rem.left()); + convertTo(state, rem.left().type(), op.type()); generate(state, rem.right()); + convertTo(state, rem.right().type(), op.type()); var type = rem.type(); if (type.equals(TargetType.Byte) + || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) { mv.visitInsn(IREM); @@ -276,7 +365,9 @@ public class Codegen { } case BAnd band: { generate(state, band.left()); + convertTo(state, band.left().type(), op.type()); generate(state, band.right()); + convertTo(state, band.right().type(), op.type()); if (band.type().equals(TargetType.Long)) mv.visitInsn(LAND); else mv.visitInsn(IAND); @@ -284,7 +375,9 @@ public class Codegen { } case BOr bor: { generate(state, bor.left()); + convertTo(state, bor.left().type(), op.type()); generate(state, bor.right()); + convertTo(state, bor.right().type(), op.type()); if (bor.type().equals(TargetType.Long)) mv.visitInsn(LOR); else mv.visitInsn(IOR); @@ -292,7 +385,9 @@ public class Codegen { } case XOr xor: { generate(state, xor.left()); + convertTo(state, xor.left().type(), op.type()); generate(state, xor.right()); + convertTo(state, xor.right().type(), op.type()); if (xor.type().equals(TargetType.Long)) mv.visitInsn(LXOR); else mv.visitInsn(IXOR); @@ -300,7 +395,9 @@ public class Codegen { } case Shl shl: { generate(state, shl.left()); + convertTo(state, shl.left().type(), op.type()); generate(state, shl.right()); + convertTo(state, shl.right().type(), op.type()); if (shl.type().equals(TargetType.Long)) mv.visitInsn(LSHL); else mv.visitInsn(ISHL); @@ -308,7 +405,9 @@ public class Codegen { } case Shr shr: { generate(state, shr.left()); + convertTo(state, shr.left().type(), op.type()); generate(state, shr.right()); + convertTo(state, shr.right().type(), op.type()); if (shr.type().equals(TargetType.Long)) mv.visitInsn(LSHR); else mv.visitInsn(ISHR); @@ -316,14 +415,16 @@ public class Codegen { } case UShr ushr: { generate(state, ushr.left()); + convertTo(state, ushr.left().type(), op.type()); generate(state, ushr.right()); + convertTo(state, ushr.right().type(), op.type()); if (ushr.type().equals(TargetType.Long)) mv.visitInsn(LUSHR); else mv.visitInsn(IUSHR); break; } case Greater greater: { - var type = greater.type(); + var type = greater.exprType(); if (type.equals(TargetType.Long)) { generateRelationalOperator(state, greater, LCMP, IFGT); } else if (type.equals(TargetType.Float)) { @@ -336,7 +437,7 @@ public class Codegen { break; } case Less less: { - var type = less.type(); + var type = less.exprType(); if (type.equals(TargetType.Long)) { generateRelationalOperator(state, less, LCMP, IFLT); } else if (type.equals(TargetType.Float)) { @@ -349,7 +450,7 @@ public class Codegen { break; } case GreaterOrEqual greaterOrEqual: { - var type = greaterOrEqual.type(); + var type = greaterOrEqual.exprType(); if (type.equals(TargetType.Long)) { generateRelationalOperator(state, greaterOrEqual, LCMP, IFGE); } else if (type.equals(TargetType.Float)) { @@ -362,7 +463,7 @@ public class Codegen { break; } case LessOrEqual lessOrEqual: { - var type = lessOrEqual.type(); + var type = lessOrEqual.exprType(); if (type.equals(TargetType.Long)) { generateRelationalOperator(state, lessOrEqual, LCMP, IFLE); } else if (type.equals(TargetType.Float)) { @@ -375,7 +476,7 @@ public class Codegen { break; } case Equal equal: { - var type = equal.type(); + var type = equal.exprType(); if (type.equals(TargetType.Long)) { generateRelationalOperator(state, equal, LCMP, IFEQ); } else if (type.equals(TargetType.Float)) { @@ -393,7 +494,7 @@ public class Codegen { break; } case NotEqual notEqual: { - var type = notEqual.type(); + var type = notEqual.exprType(); if (type.equals(TargetType.Long)) { generateRelationalOperator(state, notEqual, LCMP, IFNE); } else if (type.equals(TargetType.Float)) { @@ -419,6 +520,8 @@ public class Codegen { private void generate(State state, TargetExpression expr) { var mv = state.mv; switch (expr) { + case TargetClassName className: + break; // NOP case TargetBlock block: { var localCounter = state.localCounter; state.enterScope(); @@ -434,18 +537,60 @@ public class Codegen { state.localCounter = localCounter; break; } - case TargetVarDecl varDecl: - state.createVariable(varDecl.name(), varDecl.varType()); + case TargetCast cast: + generate(state, cast.expr()); + convertTo(state, cast.expr().type(), cast.type()); break; + case TargetInstanceOf instanceOf: + mv.visitTypeInsn(INSTANCEOF, ((TargetRefType) instanceOf.right()).getName()); + break; + case TargetLiteral literal: + switch (literal) { + case IntLiteral intLiteral: + mv.visitLdcInsn(intLiteral.value()); + break; + case FloatLiteral floatLiteral: + mv.visitLdcInsn(floatLiteral.value()); + break; + case LongLiteral longLiteral: + mv.visitLdcInsn(longLiteral.value()); + break; + case StringLiteral stringLiteral: + mv.visitLdcInsn(stringLiteral.value()); + break; + case CharLiteral charLiteral: + mv.visitIntInsn(BIPUSH, charLiteral.value()); + break; + case DoubleLiteral doubleLiteral: + mv.visitLdcInsn(doubleLiteral.value()); + break; + case BooleanLiteral booleanLiteral: + if (booleanLiteral.value()) { + mv.visitInsn(ICONST_1); + } else { + mv.visitInsn(ICONST_0); + } + break; + } + break; + case TargetVarDecl varDecl: { + var local = state.createVariable(varDecl.name(), varDecl.varType()); + generate(state, varDecl.value()); + boxPrimitive(state, varDecl.varType()); + mv.visitVarInsn(ASTORE, local.index()); + break; + } case TargetBinaryOp op: generateBinaryOp(state, op); break; case TargetAssign assign: { switch (assign.left()) { - case TargetLocalVar local: { + case TargetLocalVar localVar: { generate(state, assign.right()); boxPrimitive(state, assign.right().type()); - mv.visitInsn(ASTORE); + var local = state.scope.get(localVar.name()); + mv.visitInsn(DUP); + mv.visitVarInsn(ASTORE, local.index()); break; } case TargetFieldVar dot: { @@ -456,7 +601,7 @@ public class Codegen { if (dot.isStatic()) mv.visitInsn(DUP); else mv.visitInsn(DUP_X1); - mv.visitFieldInsn(dot.isStatic() ? PUTSTATIC : PUTFIELD, dot.right(), type.name(), type.toSignature()); + mv.visitFieldInsn(dot.isStatic() ? PUTSTATIC : PUTFIELD, dot.right(), type.getName(), type.toSignature()); break; } default: @@ -474,7 +619,7 @@ public class Codegen { if (!dot.isStatic()) generate(state, dot.left()); var type = (TargetRefType) dot.left().type(); - mv.visitFieldInsn(dot.isStatic() ? GETSTATIC : GETFIELD, type.name(), dot.right(), dot.type().toSignature()); + mv.visitFieldInsn(dot.isStatic() ? GETSTATIC : GETFIELD, type.getName(), dot.right(), dot.type().toSignature()); break; } case TargetFor _for: { @@ -528,9 +673,11 @@ public class Codegen { } case TargetReturn ret: { generate(state, ret.expression()); - if (ret.expression() != null) + + if (ret.expression() != null) { + boxPrimitive(state, ret.expression().type()); mv.visitInsn(ARETURN); - else mv.visitInsn(RETURN); + } else mv.visitInsn(RETURN); break; } case TargetThis _this: { @@ -542,13 +689,12 @@ public class Codegen { break; } case TargetMethodCall call: { - var method = call.method(); generate(state, call.expr()); for (TargetExpression e : call.args()) { generate(state, e); boxPrimitive(state, e.type()); } - mv.visitMethodInsn(method.isStatic() ? INVOKESTATIC: INVOKEVIRTUAL, method.owner().qualifiedName(), method.name(), method.getDescriptor(), false); + mv.visitMethodInsn(call.isStatic() ? INVOKESTATIC: INVOKEVIRTUAL, call.owner().getName(), call.name(), call.descriptor(), false); break; } case TargetLambdaExpression lambda: @@ -566,7 +712,11 @@ public class Codegen { private void generateConstructor(TargetConstructor constructor) { MethodVisitor mv = cw.visitMethod(constructor.access(), "", constructor.getDescriptor(), null, null); mv.visitCode(); - generate(new State(mv), constructor.block()); + var state = new State(mv, 1); + for (var param: constructor.parameters()) + state.createVariable(param.name(), param.type()); + generate(state, constructor.block()); + mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } @@ -574,18 +724,32 @@ public class Codegen { private void generateMethod(TargetMethod method) { MethodVisitor mv = cw.visitMethod(method.access(), method.name(), method.getDescriptor(), null, null); mv.visitCode(); - generate(new State(mv), method.block()); + var state = new State(mv, method.isStatic() ? 0 : 1); + for (var param: method.parameters()) + state.createVariable(param.name(), param.type()); + generate(state, method.block()); + if (method.returnType() == null) + mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } public byte[] generate() { cw.visit(V1_8, clazz.modifiers(), clazz.qualifiedName(), - null, clazz.superType().toSignature(), + null, ((TargetRefType) clazz.superType()).getName(), clazz.implementingInterfaces().stream().map(TargetType::toSignature).toArray(String[]::new) ); clazz.fields().forEach(this::generateField); - clazz.constructors().forEach(this::generateConstructor); + 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.methods().forEach(this::generateMethod); cw.visitEnd(); diff --git a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java index 19420501..709a3350 100644 --- a/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java +++ b/src/main/java/de/dhbwstuttgart/target/generate/ASTToTargetAST.java @@ -31,7 +31,7 @@ public class ASTToTargetAST { List params = input.getParameterList().getFormalparalist().stream() .map(param -> new MethodParameter(convert(param.getType(), sigma), param.getName())).collect(Collectors.toList()); // TODO - return new TargetMethod(0, null, input.name, params, convert(input.block)); + return new TargetMethod(0, null, input.name, params, null, convert(input.block)); } private TargetBlock convert(Block block) { diff --git a/src/main/java/de/dhbwstuttgart/target/tree/TargetClass.java b/src/main/java/de/dhbwstuttgart/target/tree/TargetClass.java index b6c88b2d..63010136 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/TargetClass.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/TargetClass.java @@ -17,16 +17,16 @@ public record TargetClass(int modifiers, String qualifiedName, TargetType superT this(modifiers, qualifiedName, TargetType.Object, implementingInterfaces, new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); } - public void addMethod(int access, String name, List parameterTypes, TargetBlock block) { - this.methods.add(new TargetMethod(access, this, name, parameterTypes, block)); + 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)); } public void addConstructor(int access, List paramterTypes, TargetBlock block) { - this.constructors.add(new TargetConstructor(access, this, paramterTypes, block)); + this.constructors.add(new TargetConstructor(access, new TargetRefType(this.qualifiedName, List.of()), paramterTypes, block)); } public void addField(int access, TargetType type, String name) { - this.fields.add(new TargetField(access, this, type, 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 4972fbed..3a242c1e 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/TargetConstructor.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/TargetConstructor.java @@ -1,10 +1,11 @@ package de.dhbwstuttgart.target.tree; import de.dhbwstuttgart.target.tree.expression.TargetBlock; +import de.dhbwstuttgart.target.tree.type.TargetRefType; import java.util.List; -public record TargetConstructor(int access, TargetClass owner, List parameterTypes, TargetBlock block) { +public record TargetConstructor(int access, TargetRefType owner, List parameters, TargetBlock block) { public String getDescriptor() { // TODO diff --git a/src/main/java/de/dhbwstuttgart/target/tree/TargetField.java b/src/main/java/de/dhbwstuttgart/target/tree/TargetField.java index 08b8e0d7..2c183dcb 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/TargetField.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/TargetField.java @@ -1,9 +1,10 @@ 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, TargetClass owner, TargetType type, String name) { +public record TargetField(int access, TargetRefType owner, TargetType type, String name) { public String getDescriptor() { // TODO return null; diff --git a/src/main/java/de/dhbwstuttgart/target/tree/TargetMethod.java b/src/main/java/de/dhbwstuttgart/target/tree/TargetMethod.java index fefe7d2e..bfd8f3b4 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/TargetMethod.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/TargetMethod.java @@ -1,14 +1,30 @@ 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 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 static String getDescriptor(TargetType returnType, TargetType... parameters) { + String ret = "("; + for (var parameterType : parameters) { + ret += parameterType.toSignature(); + } + ret += ")"; + if (returnType == null) { + ret += "V"; + } else { + ret += returnType.toSignature(); + } + return ret; + } -public record TargetMethod(int access, TargetClass owner, String name, List parameterTypes, TargetBlock block) { public String getDescriptor() { - // TODO - return null; + return getDescriptor(returnType, parameters.stream().map(MethodParameter::type).toArray(TargetType[]::new)); } public boolean isStatic() { 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 fc06719b..814a4d67 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetBinaryOp.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetBinaryOp.java @@ -26,13 +26,21 @@ public sealed interface TargetBinaryOp extends TargetExpression { record And(TargetType type, TargetExpression left, TargetExpression right) implements TargetBinaryOp {} record Or(TargetType type, TargetExpression left, TargetExpression right) implements TargetBinaryOp {} + sealed interface TargetRelationalOp extends TargetBinaryOp { + @Override + default TargetType type() { + return TargetType.Boolean; + } + TargetType exprType(); + } + // Comparison - record Equal(TargetType type, TargetExpression left, TargetExpression right) implements TargetBinaryOp {} - record Greater(TargetType type, TargetExpression left, TargetExpression right) implements TargetBinaryOp {} - record GreaterOrEqual(TargetType type, TargetExpression left, TargetExpression right) implements TargetBinaryOp {} - record Less(TargetType type, TargetExpression left, TargetExpression right) implements TargetBinaryOp {} - record LessOrEqual(TargetType type, TargetExpression left, TargetExpression right) implements TargetBinaryOp {} - record NotEqual(TargetType type, TargetExpression left, TargetExpression right) implements TargetBinaryOp {} + 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 {} + record Less(TargetType exprType, TargetExpression left, TargetExpression right) implements TargetRelationalOp {} + record LessOrEqual(TargetType exprType, TargetExpression left, TargetExpression right) implements TargetRelationalOp {} + record NotEqual(TargetType exprType, TargetExpression left, TargetExpression right) implements TargetRelationalOp {} } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetLiteral.java b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetLiteral.java index 31ccb1f7..c4673682 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetLiteral.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetLiteral.java @@ -5,7 +5,14 @@ import de.dhbwstuttgart.target.tree.type.TargetType; public sealed interface TargetLiteral extends TargetExpression { Object value(); - record CharLiteral(Integer value) implements TargetLiteral { + record BooleanLiteral(Boolean value) implements TargetLiteral { + @Override + public TargetType type() { + return TargetType.Boolean; + } + } + + record CharLiteral(Character value) implements TargetLiteral { @Override public TargetType type() { return TargetType.Char; @@ -26,6 +33,20 @@ public sealed interface TargetLiteral extends TargetExpression { } } + record FloatLiteral(Float value) implements TargetLiteral { + @Override + public TargetType type() { + return TargetType.Float; + } + } + + record DoubleLiteral(Double value) implements TargetLiteral { + @Override + public TargetType type() { + return TargetType.Double; + } + } + record StringLiteral(String value) implements TargetLiteral { @Override public TargetType type() { 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 0e7359f8..132ec11b 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetMethodCall.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetMethodCall.java @@ -1,9 +1,10 @@ package de.dhbwstuttgart.target.tree.expression; 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, TargetMethod method) implements TargetStatementExpression { +public record TargetMethodCall(TargetType type, TargetExpression expr, List args, TargetRefType owner, String name, String descriptor, boolean isStatic) implements TargetStatementExpression { } diff --git a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetVarDecl.java b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetVarDecl.java index b0c15225..b1eda5e7 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetVarDecl.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/expression/TargetVarDecl.java @@ -2,7 +2,7 @@ package de.dhbwstuttgart.target.tree.expression; import de.dhbwstuttgart.target.tree.type.TargetType; -public record TargetVarDecl(String name, TargetType varType) implements TargetExpression { +public record TargetVarDecl(TargetType varType, String name, TargetExpression value) implements TargetExpression { @Override public TargetType type() { diff --git a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetRefType.java b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetRefType.java index 1e892a25..5944fdbe 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetRefType.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetRefType.java @@ -5,8 +5,16 @@ import de.dhbwstuttgart.target.tree.type.TargetType; import java.util.List; public record TargetRefType(String name, List params) implements TargetType { + public TargetRefType(String name) { + this(name, List.of()); + } + + public String getName() { + return this.name.replaceAll("\\.", "/"); + } + @Override public String toSignature() { - return "L" + this.name.replaceAll("\\.", "/") + ";"; + return "L" + getName() + ";"; } } 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 49ed4106..f3d6092f 100644 --- a/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java +++ b/src/main/java/de/dhbwstuttgart/target/tree/type/TargetType.java @@ -6,16 +6,16 @@ public sealed interface TargetType permits TargetExtendsWildcard, TargetFunNType, TargetGenericType, TargetRefType, TargetSuperWildcard { // Builtin types - TargetRefType Boolean = new TargetRefType("java.lang.Boolean", List.of()); - TargetRefType Char = new TargetRefType("java.lang.Character", List.of()); - TargetRefType Byte = new TargetRefType("java.lang.Byte", List.of()); - TargetRefType Short = new TargetRefType("java.lang.Short", List.of()); - TargetRefType Integer = new TargetRefType("java.lang.Integer", List.of()); - TargetRefType Long = new TargetRefType("java.lang.Long", List.of()); - TargetRefType Float = new TargetRefType("java.lang.Float", List.of()); - TargetRefType Double = new TargetRefType("java.lang.Double", List.of()); - TargetRefType String = new TargetRefType("java.lang.String", List.of()); - TargetRefType Object = new TargetRefType("java.lang.Object", List.of()); + TargetRefType Boolean = new TargetRefType("java.lang.Boolean"); + TargetRefType Char = new TargetRefType("java.lang.Character"); + TargetRefType Byte = new TargetRefType("java.lang.Byte"); + TargetRefType Short = new TargetRefType("java.lang.Short"); + TargetRefType Integer = new TargetRefType("java.lang.Integer"); + TargetRefType Long = new TargetRefType("java.lang.Long"); + TargetRefType Float = new TargetRefType("java.lang.Float"); + TargetRefType Double = new TargetRefType("java.lang.Double"); + TargetRefType String = new TargetRefType("java.lang.String"); + TargetRefType Object = new TargetRefType("java.lang.Object"); String toSignature(); } diff --git a/src/test/java/targetast/TestCodegen.java b/src/test/java/targetast/TestCodegen.java index dc5f03f8..0171032f 100644 --- a/src/test/java/targetast/TestCodegen.java +++ b/src/test/java/targetast/TestCodegen.java @@ -1,25 +1,193 @@ 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.TargetBlock; +import de.dhbwstuttgart.target.tree.expression.*; +import de.dhbwstuttgart.target.tree.type.TargetRefType; +import de.dhbwstuttgart.target.tree.type.TargetType; import org.junit.Test; +import static org.junit.Assert.*; import org.objectweb.asm.Opcodes; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.List; + public class TestCodegen { - private static Class generateClass(TargetClass clazz) { + private static Class generateClass(TargetClass clazz) throws IOException { var codegen = new Codegen(clazz); - return new ByteArrayClassLoader().loadClass(codegen.generate()); + 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); } @Test public void testEmptyClass() throws Exception { - var clazz = new TargetClass(Opcodes.ACC_PUBLIC, "Main"); - clazz.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "main", List.of(), new TargetBlock(List.of())); + var clazz = new TargetClass(Opcodes.ACC_PUBLIC, "Empty"); + clazz.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "main", List.of(), null, new TargetBlock(List.of())); generateClass(clazz).getDeclaredMethod("main").invoke(null); } + + @Test + public void testArithmetic() throws Exception { + var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "Arithmetic"); + + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "add", + List.of(new MethodParameter(TargetType.Integer, "a"), new MethodParameter(TargetType.Integer, "b")), + TargetType.Integer, + new TargetBlock(List.of(new TargetReturn( + new TargetBinaryOp.Add(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "a"), new TargetLocalVar(TargetType.Integer, "b"))) + )) + ); + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "sub", + List.of(new MethodParameter(TargetType.Integer, "a"), new MethodParameter(TargetType.Integer, "b")), + TargetType.Integer, + new TargetBlock(List.of(new TargetReturn( + new TargetBinaryOp.Sub(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "a"), new TargetLocalVar(TargetType.Integer, "b"))) + )) + ); + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "div", + List.of(new MethodParameter(TargetType.Integer, "a"), new MethodParameter(TargetType.Integer, "b")), + TargetType.Integer, + new TargetBlock(List.of(new TargetReturn( + new TargetBinaryOp.Div(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "a"), new TargetLocalVar(TargetType.Integer, "b"))) + )) + ); + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "mul", + List.of(new MethodParameter(TargetType.Integer, "a"), new MethodParameter(TargetType.Integer, "b")), + TargetType.Integer, + new TargetBlock(List.of(new TargetReturn( + new TargetBinaryOp.Mul(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "a"), new TargetLocalVar(TargetType.Integer, "b"))) + )) + ); + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "rem", + List.of(new MethodParameter(TargetType.Integer, "a"), new MethodParameter(TargetType.Integer, "b")), + TargetType.Integer, + new TargetBlock(List.of(new TargetReturn( + new TargetBinaryOp.Rem(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "a"), new TargetLocalVar(TargetType.Integer, "b"))) + )) + ); + + + var clazz = generateClass(targetClass); + assertEquals(clazz.getDeclaredMethod("add", Integer.class, Integer.class).invoke(null, 10, 10), 20); + assertEquals(clazz.getDeclaredMethod("sub", Integer.class, Integer.class).invoke(null, 20, 10), 10); + assertEquals(clazz.getDeclaredMethod("div", Integer.class, Integer.class).invoke(null, 20, 10), 2); + assertEquals(clazz.getDeclaredMethod("mul", Integer.class, Integer.class).invoke(null, 20, 10), 200); + assertEquals(clazz.getDeclaredMethod("rem", Integer.class, Integer.class).invoke(null, 10, 3), 1); + } + + // When adding two numbers and the return type is Long it needs to convert both values to Long + @Test + public void testArithmeticConvert() throws Exception { + var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "ArithmeticConvert"); + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "add", List.of(), TargetType.Long, + new TargetBlock(List.of(new TargetReturn( + new TargetBinaryOp.Add(TargetType.Long, new TargetLiteral.CharLiteral((char)10), new TargetLiteral.LongLiteral((long)20)) + ))) + ); + var clazz = generateClass(targetClass); + assertEquals(clazz.getDeclaredMethod("add").invoke(null), (long)30); + } + + @Test + public void testMethodCall() throws Exception { + var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "HelloWorld"); + 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 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 + ))) + ); + + var clazz = generateClass(targetClass); + clazz.getDeclaredMethod("helloWorld").invoke(null); + } + + @Test + public void testIfStatement() throws Exception { + var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "IfStmt"); + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "ifStmt", + List.of(new MethodParameter(TargetType.Integer, "val")), + TargetType.Integer, + new TargetBlock(List.of(new TargetIf( + new TargetBinaryOp.Equal(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "val"), new TargetLiteral.IntLiteral(10)), + new TargetReturn(new TargetLiteral.IntLiteral(1)), + new TargetIf( + new TargetBinaryOp.Less(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "val"), new TargetLiteral.IntLiteral(5)), + new TargetReturn(new TargetLiteral.IntLiteral(2)), + new TargetReturn(new TargetLiteral.IntLiteral(3)) + ) + ))) + ); + var clazz = generateClass(targetClass); + var ifStmt = clazz.getDeclaredMethod("ifStmt", Integer.class); + assertEquals(ifStmt.invoke(null, 10), 1); + assertEquals(ifStmt.invoke(null, 3), 2); + assertEquals(ifStmt.invoke(null, 20), 3); + } + + @Test + public void testFor() throws Exception { + var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "For"); + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "forLoop", List.of(), TargetType.Integer, + new TargetBlock(List.of( + new TargetVarDecl(TargetType.Integer, "sum", new TargetLiteral.IntLiteral(0)), + new TargetFor( + new TargetVarDecl(TargetType.Integer, "i", new TargetLiteral.IntLiteral(0)), + new TargetBinaryOp.Less(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "i"), new TargetLiteral.IntLiteral(10)), + new TargetAssign(TargetType.Integer, + new TargetLocalVar(TargetType.Integer, "i"), + new TargetBinaryOp.Add(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "i"), new TargetLiteral.IntLiteral(1))), + new TargetBlock(List.of( + new TargetAssign(TargetType.Integer, + new TargetLocalVar(TargetType.Integer, "sum"), + new TargetBinaryOp.Add(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "sum"), new TargetLocalVar(TargetType.Integer, "i")) + ) + )) + ), + new TargetReturn(new TargetLocalVar(TargetType.Integer, "sum")) + )) + ); + var clazz = generateClass(targetClass); + assertEquals(clazz.getDeclaredMethod("forLoop").invoke(null), 45); + } + + @Test + public void testWhile() throws Exception { + var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "While"); + targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "whileLoop", List.of(), TargetType.Integer, + new TargetBlock(List.of( + new TargetVarDecl(TargetType.Integer, "i", new TargetLiteral.IntLiteral(0)), + new TargetWhile( + new TargetBinaryOp.Less(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "i"), new TargetLiteral.IntLiteral(10)), + new TargetBlock(List.of( + new TargetAssign(TargetType.Integer, + new TargetLocalVar(TargetType.Integer, "i"), + new TargetBinaryOp.Add(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "i"), new TargetLiteral.IntLiteral(1)) + ) + )) + ), + new TargetReturn(new TargetLocalVar(TargetType.Integer, "i")) + )) + ); + var clazz = generateClass(targetClass); + assertEquals(clazz.getDeclaredMethod("whileLoop").invoke(null), 10); + } }