Add classic switch
This commit is contained in:
parent
3de9fde672
commit
be55d661cb
@ -1,6 +1,7 @@
|
|||||||
package de.dhbwstuttgart.bytecode;
|
package de.dhbwstuttgart.bytecode;
|
||||||
|
|
||||||
import de.dhbwstuttgart.exceptions.NotImplementedException;
|
import de.dhbwstuttgart.exceptions.NotImplementedException;
|
||||||
|
import de.dhbwstuttgart.syntaxtree.statement.Break;
|
||||||
import de.dhbwstuttgart.target.tree.*;
|
import de.dhbwstuttgart.target.tree.*;
|
||||||
import de.dhbwstuttgart.target.tree.expression.*;
|
import de.dhbwstuttgart.target.tree.expression.*;
|
||||||
import de.dhbwstuttgart.target.tree.type.*;
|
import de.dhbwstuttgart.target.tree.type.*;
|
||||||
@ -10,6 +11,7 @@ import java.lang.invoke.CallSite;
|
|||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.sql.Array;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.*;
|
import static org.objectweb.asm.Opcodes.*;
|
||||||
@ -56,12 +58,20 @@ public class Codegen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class BreakEnv {
|
||||||
|
String labelName; // TODO This is for labeled statements (Not implemented)
|
||||||
|
Label startLabel;
|
||||||
|
Label endLabel;
|
||||||
|
}
|
||||||
|
|
||||||
private static class State {
|
private static class State {
|
||||||
Scope scope = new Scope(null);
|
Scope scope = new Scope(null);
|
||||||
int localCounter;
|
int localCounter;
|
||||||
MethodVisitor mv;
|
MethodVisitor mv;
|
||||||
TargetType returnType;
|
TargetType returnType;
|
||||||
|
|
||||||
|
Stack<BreakEnv> breakStack = new Stack<>();
|
||||||
|
|
||||||
State(TargetType returnType, MethodVisitor mv, int localCounter) {
|
State(TargetType returnType, MethodVisitor mv, int localCounter) {
|
||||||
this.returnType = returnType;
|
this.returnType = returnType;
|
||||||
this.mv = mv;
|
this.mv = mv;
|
||||||
@ -863,7 +873,15 @@ public class Codegen {
|
|||||||
else
|
else
|
||||||
mv.visitInsn(ICONST_1);
|
mv.visitInsn(ICONST_1);
|
||||||
mv.visitJumpInsn(IFEQ, end);
|
mv.visitJumpInsn(IFEQ, end);
|
||||||
|
|
||||||
|
var env = new BreakEnv();
|
||||||
|
env.startLabel = start;
|
||||||
|
env.endLabel = end;
|
||||||
|
|
||||||
|
state.breakStack.push(env);
|
||||||
generate(state, _for.body());
|
generate(state, _for.body());
|
||||||
|
state.breakStack.pop();
|
||||||
|
|
||||||
if (_for.increment() != null) {
|
if (_for.increment() != null) {
|
||||||
generate(state, _for.increment());
|
generate(state, _for.increment());
|
||||||
if (_for.increment().type() != null) {
|
if (_for.increment().type() != null) {
|
||||||
@ -872,6 +890,7 @@ public class Codegen {
|
|||||||
}
|
}
|
||||||
mv.visitJumpInsn(GOTO, start);
|
mv.visitJumpInsn(GOTO, start);
|
||||||
mv.visitLabel(end);
|
mv.visitLabel(end);
|
||||||
|
mv.visitInsn(NOP);
|
||||||
state.exitScope();
|
state.exitScope();
|
||||||
state.localCounter = localCounter;
|
state.localCounter = localCounter;
|
||||||
break;
|
break;
|
||||||
@ -882,9 +901,18 @@ public class Codegen {
|
|||||||
mv.visitLabel(start);
|
mv.visitLabel(start);
|
||||||
generate(state, _while.cond());
|
generate(state, _while.cond());
|
||||||
mv.visitJumpInsn(IFEQ, end);
|
mv.visitJumpInsn(IFEQ, end);
|
||||||
|
|
||||||
|
var env = new BreakEnv();
|
||||||
|
env.startLabel = start;
|
||||||
|
env.endLabel = end;
|
||||||
|
|
||||||
|
state.breakStack.push(env);
|
||||||
generate(state, _while.body());
|
generate(state, _while.body());
|
||||||
|
state.breakStack.pop();
|
||||||
|
|
||||||
mv.visitJumpInsn(GOTO, start);
|
mv.visitJumpInsn(GOTO, start);
|
||||||
mv.visitLabel(end);
|
mv.visitLabel(end);
|
||||||
|
mv.visitInsn(NOP);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TargetIf _if: {
|
case TargetIf _if: {
|
||||||
@ -899,6 +927,7 @@ public class Codegen {
|
|||||||
generate(state, _if.else_body());
|
generate(state, _if.else_body());
|
||||||
}
|
}
|
||||||
mv.visitLabel(end);
|
mv.visitLabel(end);
|
||||||
|
mv.visitInsn(NOP);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TargetReturn ret: {
|
case TargetReturn ret: {
|
||||||
@ -911,6 +940,22 @@ public class Codegen {
|
|||||||
mv.visitInsn(RETURN);
|
mv.visitInsn(RETURN);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TargetSwitch _switch: {
|
||||||
|
generateSwitch(state, _switch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TargetBreak brk: {
|
||||||
|
if (state.breakStack.isEmpty()) throw new CodeGenException("Break outside of switch or loop");
|
||||||
|
mv.visitJumpInsn(GOTO, state.breakStack.peek().endLabel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TargetContinue cnt: {
|
||||||
|
if (state.breakStack.isEmpty()) throw new CodeGenException("Continue outside of loop");
|
||||||
|
var env = state.breakStack.peek();
|
||||||
|
if (env.startLabel == null) throw new CodeGenException("Continue outside of loop");
|
||||||
|
mv.visitJumpInsn(GOTO, env.startLabel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case TargetThis _this: {
|
case TargetThis _this: {
|
||||||
mv.visitVarInsn(ALOAD, 0);
|
mv.visitVarInsn(ALOAD, 0);
|
||||||
break;
|
break;
|
||||||
@ -959,6 +1004,76 @@ public class Codegen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void generateClassicSwitch(State state, TargetSwitch aSwitch) {
|
||||||
|
// TODO Constant expressions are allowed, we need to evaluate them somehow...
|
||||||
|
// For now we just assume we get literals
|
||||||
|
// TODO This always uses a lookupswitch, a tableswitch may be faster in some cases but we can't generate that in all cases
|
||||||
|
var mv = state.mv;
|
||||||
|
|
||||||
|
generate(state, aSwitch.expr());
|
||||||
|
var keys = new int[aSwitch.cases().stream().mapToInt(c -> c.labels().size()).sum()];
|
||||||
|
var labels = new Label[keys.length];
|
||||||
|
var bodyLabels = new Label[aSwitch.cases().size()];
|
||||||
|
|
||||||
|
var end = new Label();
|
||||||
|
var env = new BreakEnv();
|
||||||
|
env.endLabel = end;
|
||||||
|
state.breakStack.push(env);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
var j = 0;
|
||||||
|
for (var case_ : aSwitch.cases()) {
|
||||||
|
bodyLabels[j] = new Label();
|
||||||
|
for (var label : case_.labels()) {
|
||||||
|
if (!(label instanceof TargetLiteral literal))
|
||||||
|
throw new CodeGenException("Labels may only be constants for now");
|
||||||
|
keys[i] = (int) literal.value();
|
||||||
|
labels[i] = bodyLabels[j];
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultLabel = end;
|
||||||
|
if (aSwitch.default_() != null) {
|
||||||
|
defaultLabel = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitLookupSwitchInsn(defaultLabel, keys, labels);
|
||||||
|
|
||||||
|
for (var k = 0; k < aSwitch.cases().size(); k++) {
|
||||||
|
mv.visitLabel(bodyLabels[k]);
|
||||||
|
generate(state, aSwitch.cases().get(k).body());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aSwitch.default_() != null) {
|
||||||
|
mv.visitLabel(defaultLabel);
|
||||||
|
generate(state, aSwitch.default_());
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitLabel(end);
|
||||||
|
mv.visitInsn(NOP);
|
||||||
|
state.breakStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateEnhancedSwitch(State state, TargetSwitch aSwitch) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<TargetType> wrapperTypes = Set.of(TargetType.Long, TargetType.Integer, TargetType.Byte, TargetType.Char, TargetType.Boolean, TargetType.Double, TargetType.Float);
|
||||||
|
|
||||||
|
private void generateSwitch(State state, TargetSwitch aSwitch) {
|
||||||
|
if (!wrapperTypes.contains(aSwitch.expr().type()))
|
||||||
|
generateEnhancedSwitch(state, aSwitch);
|
||||||
|
else for (var case_ : aSwitch.cases()) {
|
||||||
|
if (case_.labels().stream().anyMatch(c -> c instanceof TargetSwitch.Pattern)) {
|
||||||
|
generateEnhancedSwitch(state, aSwitch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
generateClassicSwitch(state, aSwitch);
|
||||||
|
}
|
||||||
|
|
||||||
private void generateField(TargetField field) {
|
private void generateField(TargetField field) {
|
||||||
cw.visitField(field.access() | ACC_PUBLIC, field.name(), field.type().toSignature(), field.type().toDescriptor(), null);
|
cw.visitField(field.access() | ACC_PUBLIC, field.name(), field.type().toSignature(), field.type().toDescriptor(), null);
|
||||||
}
|
}
|
||||||
|
@ -361,14 +361,14 @@ public class StatementToTargetExpression implements StatementVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(Pattern aPattern) {
|
public void visit(Pattern aPattern) {
|
||||||
result = new TargetSwitch.Pattern(converter.convert(aPattern.getType()), aPattern.getName());
|
result = new TargetSwitch.SimplePattern(converter.convert(aPattern.getType()), aPattern.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(RecordPattern aRecordPattern) {
|
public void visit(RecordPattern aRecordPattern) {
|
||||||
result = new TargetSwitch.ComplexPattern(
|
result = new TargetSwitch.ComplexPattern(
|
||||||
converter.convert(aRecordPattern.getType()),
|
converter.convert(aRecordPattern.getType()),
|
||||||
aRecordPattern.getSubPattern().stream().map(x -> (TargetSwitch.Pattern) converter.convert(x)).toList()
|
aRecordPattern.getSubPattern().stream().map(x -> (TargetSwitch.Pattern) converter.convert(x)).toList()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,6 +376,6 @@ public class StatementToTargetExpression implements StatementVisitor {
|
|||||||
public void visit(GuardedPattern aGuardedPattern) {
|
public void visit(GuardedPattern aGuardedPattern) {
|
||||||
//FIXME This isn't done properly inside the parser, really you should only have one guard (Chaining them together with && just yields another expression)
|
//FIXME This isn't done properly inside the parser, really you should only have one guard (Chaining them together with && just yields another expression)
|
||||||
//And then it also needs to be able to accept complex patterns. Because of this we only accept one condition for now.
|
//And then it also needs to be able to accept complex patterns. Because of this we only accept one condition for now.
|
||||||
result = new TargetSwitch.Guard(new TargetSwitch.Pattern(converter.convert(aGuardedPattern.getType()), aGuardedPattern.getName()), converter.convert(aGuardedPattern.getConditions().get(0)));
|
result = new TargetSwitch.Guard(new TargetSwitch.SimplePattern(converter.convert(aGuardedPattern.getType()), aGuardedPattern.getName()), converter.convert(aGuardedPattern.getConditions().get(0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,6 @@ package de.dhbwstuttgart.target.tree.expression;
|
|||||||
|
|
||||||
import de.dhbwstuttgart.target.tree.type.TargetType;
|
import de.dhbwstuttgart.target.tree.type.TargetType;
|
||||||
|
|
||||||
|
// TODO This needs a label
|
||||||
public record TargetBreak() implements TargetExpression {
|
public record TargetBreak() implements TargetExpression {
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package de.dhbwstuttgart.target.tree.expression;
|
|||||||
import de.dhbwstuttgart.target.tree.type.*;
|
import de.dhbwstuttgart.target.tree.type.*;
|
||||||
|
|
||||||
public sealed interface TargetExpression
|
public sealed interface TargetExpression
|
||||||
permits TargetBinaryOp, TargetBlock, TargetBreak, TargetCast, TargetClassName, TargetContinue, TargetFieldVar, TargetFor, TargetForEach, TargetIf, TargetInstanceOf, TargetLambdaExpression, TargetLiteral, TargetLocalVar, TargetReturn, TargetStatementExpression, TargetSuper, TargetSwitch, TargetSwitch.ComplexPattern, TargetSwitch.Guard, TargetSwitch.Pattern, TargetTernary, TargetThis, TargetUnaryOp, TargetVarDecl, TargetWhile, TargetYield {
|
permits TargetBinaryOp, TargetBlock, TargetBreak, TargetCast, TargetClassName, TargetContinue, TargetFieldVar, TargetFor, TargetForEach, TargetIf, TargetInstanceOf, TargetLambdaExpression, TargetLiteral, TargetLocalVar, TargetReturn, TargetStatementExpression, TargetSuper, TargetSwitch, TargetSwitch.Pattern, TargetTernary, TargetThis, TargetUnaryOp, TargetVarDecl, TargetWhile, TargetYield {
|
||||||
|
|
||||||
default TargetType type() {
|
default TargetType type() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -20,8 +20,10 @@ public record TargetSwitch(TargetExpression expr, List<Case> cases, TargetBlock
|
|||||||
|
|
||||||
public record Case(List<TargetExpression> labels, TargetBlock body) {}
|
public record Case(List<TargetExpression> labels, TargetBlock body) {}
|
||||||
|
|
||||||
public record Pattern(TargetType type, String name) implements TargetExpression {}
|
public sealed interface Pattern extends TargetExpression {}
|
||||||
public record ComplexPattern(TargetType type, List<Pattern> subPatterns) implements TargetExpression {}
|
|
||||||
|
|
||||||
public record Guard(TargetExpression inner, TargetExpression expression) implements TargetExpression {}
|
public record SimplePattern(TargetType type, String name) implements Pattern {}
|
||||||
|
public record ComplexPattern(TargetType type, List<Pattern> subPatterns) implements Pattern {}
|
||||||
|
|
||||||
|
public record Guard(TargetExpression inner, TargetExpression expression) implements Pattern {}
|
||||||
}
|
}
|
||||||
|
@ -213,8 +213,11 @@ public class TestCodegen {
|
|||||||
))
|
))
|
||||||
), new TargetBlock(
|
), new TargetBlock(
|
||||||
List.of(new TargetAssign(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "res"), new TargetLiteral.IntLiteral(2)), new TargetBreak())
|
List.of(new TargetAssign(TargetType.Integer, new TargetLocalVar(TargetType.Integer, "res"), new TargetLiteral.IntLiteral(2)), new TargetBreak())
|
||||||
))
|
)),
|
||||||
|
new TargetReturn(new TargetLocalVar(TargetType.Integer, "res"))
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
var clazz = generateClass(targetClass, new ByteArrayClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -222,10 +225,10 @@ public class TestCodegen {
|
|||||||
var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "Switch");
|
var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "Switch");
|
||||||
targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "switchType", List.of(new MethodParameter(TargetType.Object, "obj")), TargetType.Integer, new TargetBlock(List.of(
|
targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "switchType", List.of(new MethodParameter(TargetType.Object, "obj")), TargetType.Integer, new TargetBlock(List.of(
|
||||||
new TargetReturn(new TargetSwitch(new TargetLocalVar(TargetType.Object, "obj"), List.of(
|
new TargetReturn(new TargetSwitch(new TargetLocalVar(TargetType.Object, "obj"), List.of(
|
||||||
new TargetSwitch.Case(List.of(new TargetSwitch.Pattern(TargetType.String, "aString")), new TargetBlock(
|
new TargetSwitch.Case(List.of(new TargetSwitch.SimplePattern(TargetType.String, "aString")), new TargetBlock(
|
||||||
List.of(new TargetLiteral.IntLiteral(0))
|
List.of(new TargetLiteral.IntLiteral(0))
|
||||||
)),
|
)),
|
||||||
new TargetSwitch.Case(List.of(new TargetSwitch.Pattern(TargetType.Integer, "anInteger")), new TargetBlock(
|
new TargetSwitch.Case(List.of(new TargetSwitch.SimplePattern(TargetType.Integer, "anInteger")), new TargetBlock(
|
||||||
List.of(new TargetLiteral.IntLiteral(1))
|
List.of(new TargetLiteral.IntLiteral(1))
|
||||||
))
|
))
|
||||||
), new TargetBlock(
|
), new TargetBlock(
|
||||||
|
Loading…
Reference in New Issue
Block a user