diff options
Diffstat (limited to 'src/main/java')
9 files changed, 454 insertions, 36 deletions
diff --git a/src/main/java/org/zwobble/couscous/ast/NodeTypes.java b/src/main/java/org/zwobble/couscous/ast/NodeTypes.java new file mode 100644 index 0000000..a82cd1c --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/NodeTypes.java @@ -0,0 +1,86 @@ +package org.zwobble.couscous.ast; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class NodeTypes { + public static final int ANNOTATION = 10000; + public static final int ARRAY = 10001; + public static final int ASSIGNMENT = 10002; + public static final int CAST = 10003; + public static final int CLASS = 10004; + public static final int CONSTRUCTOR_CALL = 10005; + public static final int CONSTRUCTOR = 10006; + public static final int ENUM = 10007; + public static final int EXCEPTION_HANDLER = 10008; + public static final int EXPRESSION_STATEMENT = 10009; + public static final int FIELD_ACCESS = 10010; + public static final int FIELD_DECLARATION = 10011; + public static final int FORMAL_ARGUMENT = 10012; + public static final int FORMAL_TYPE_PARAMETER = 10013; + public static final int IF_STATEMENT = 10014; + public static final int INSTANCE_OF = 10015; + public static final int INSTANCE_RECEIVER = 10016; + public static final int INTERFACE = 10017; + public static final int LITERAL = 10018; + public static final int LOCAL_VARIABLE_DECLARATION = 10019; + public static final int METHOD_CALL = 10020; + public static final int METHOD = 10031; + public static final int OPERATION = 10021; + public static final int RETURN = 10022; + public static final int STATIC_RECEIVER = 10023; + public static final int TERNARY_CONDITIONAL = 10024; + public static final int THIS_REFERENCE = 10025; + public static final int THROW = 10026; + public static final int TRY = 10027; + public static final int TYPE_COERCION = 10028; + public static final int VARIABLE_REFERENCE = 10029; + public static final int WHILE = 10030; + + private NodeTypes() { + } + + private static final Map<Class<? extends Node>, Integer> NODE_TYPES = ImmutableMap.<Class<? extends Node>, Integer>builder() + .put(AnnotationNode.class, ANNOTATION) + .put(ArrayNode.class, ARRAY) + .put(AssignmentNode.class, ASSIGNMENT) + .put(CastNode.class, CAST) + .put(ClassNode.class, CLASS) + .put(ConstructorCallNode.class, CONSTRUCTOR_CALL) + .put(ConstructorNode.class, CONSTRUCTOR) + .put(EnumNode.class, ENUM) + //.put(ExceptionHandlerNode.class, EXCEPTION_HANDLER) + .put(ExpressionStatementNode.class, EXPRESSION_STATEMENT) + .put(FieldAccessNode.class, FIELD_ACCESS) + .put(FieldDeclarationNode.class, FIELD_DECLARATION) + .put(FormalArgumentNode.class, FORMAL_ARGUMENT) + .put(FormalTypeParameterNode.class, FORMAL_TYPE_PARAMETER) + .put(IfStatementNode.class, IF_STATEMENT) + .put(InstanceOfNode.class, INSTANCE_OF) + .put(InstanceReceiver.class, INSTANCE_RECEIVER) + .put(InterfaceNode.class, INTERFACE) + .put(LiteralNode.class, LITERAL) + .put(LocalVariableDeclarationNode.class, LOCAL_VARIABLE_DECLARATION) + .put(MethodCallNode.class, METHOD_CALL) + .put(MethodNode.class, METHOD) + .put(OperationNode.class, OPERATION) + .put(ReturnNode.class, RETURN) + .put(StaticReceiver.class, STATIC_RECEIVER) + .put(TernaryConditionalNode.class, TERNARY_CONDITIONAL) + .put(ThisReferenceNode.class, THIS_REFERENCE) + .put(ThrowNode.class, THROW) + .put(TryNode.class, TRY) + .put(TypeCoercionNode.class, TYPE_COERCION) + .put(VariableReferenceNode.class, VARIABLE_REFERENCE) + .put(WhileNode.class, WHILE) + .build(); + + public static int forClass(Class<?> nodeClass) { + if (NODE_TYPES.containsKey(nodeClass)) { + return NODE_TYPES.get(nodeClass); + } else { + throw new UnsupportedOperationException("Unknown node class: " + nodeClass); + } + } +} diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/DynamicNodeVisitor.java b/src/main/java/org/zwobble/couscous/ast/visitors/DynamicNodeVisitor.java new file mode 100644 index 0000000..f9e406d --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/visitors/DynamicNodeVisitor.java @@ -0,0 +1,205 @@ +package org.zwobble.couscous.ast.visitors; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.modifier.FieldManifestation; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.scaffold.InstrumentedType; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.ByteCodeAppender; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.implementation.bytecode.assign.TypeCasting; +import net.bytebuddy.implementation.bytecode.member.FieldAccess; +import net.bytebuddy.implementation.bytecode.member.MethodInvocation; +import net.bytebuddy.implementation.bytecode.member.MethodReturn; +import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess; +import net.bytebuddy.jar.asm.Label; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.matcher.ElementMatchers; +import org.zwobble.couscous.ast.Node; +import org.zwobble.couscous.ast.NodeTypes; +import org.zwobble.couscous.util.asm.StackManipulationSwitch; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.function.Consumer; + +import static java.util.Arrays.asList; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.zwobble.couscous.util.ExtraLists.eagerFilter; +import static org.zwobble.couscous.util.ExtraLists.eagerMap; + +/** + * Dynamically generate a method to dispatch methods based on node type. + * + * <p>For instance, let's say you've defined two methods in a class:</p> + * + * <blockquote><pre> + * void visit(LiteralNode); + * void visit(VariableReferenceNode); + * </pre></blockquote> + * + * Calling: + * + * <blockquote><pre> + * DynamicNodeVisitor.build(NodeWriter.class, "visit").instantiate(nodeWriter) + * </pre></blockquote> + * + * will generate a function accepting a node equivalent to: + * + * <blockquote><pre> + * switch (node.nodeType()) { + * case NodeTypes.LITERAL: + * nodeWriter.visit((LiteralNode) node); + * return; + * case NodeTypes.VARIABLE_REFERENCE: + * nodeWriter.visit((VariableReferenceNode) node); + * return; + * default; + * return; + * } + * </pre></blockquote> + * + * <p> + * Note that {@code build} is comparatively expensive, + * so should normally be called once separately from {@code instantiate}. + * </p> + * + * <p> + * In answer to "why not define a visitor interface instead?": + * this allows visitors to be defined over a subset of nodes, + * rather than requiring a visit method to be defined for every node. + * Further, it allows extension of the Node interface, + * making it easier to mix and match transformations that may add or remove particular node types + * (which affects which node types later stages are required to handle). + * </p> + */ +public class DynamicNodeVisitor<T> { + public static <T> DynamicNodeVisitor<T> build(Class<T> clazz, String methodName) { + List<Method> visitMethods = eagerFilter( + asList(clazz.getMethods()), + method -> isVisitMethod(methodName, method) + ); + Label literalLabel = new Label(); + + Class<? extends Consumer> visitor = new ByteBuddy() + .subclass(Consumer.class) + + .defineField("visitor", clazz, Visibility.PRIVATE, FieldManifestation.FINAL) + + .defineConstructor(Visibility.PUBLIC) + .withParameters(clazz) + .intercept(new Implementation() { + @Override + public InstrumentedType prepare(InstrumentedType instrumentedType) { + return instrumentedType; + } + + @Override + public ByteCodeAppender appender(Target implementationTarget) { + return new ByteCodeAppender() { + @Override + public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { + StackManipulation.Size size = new StackManipulation.Compound( + // this() + MethodVariableAccess.REFERENCE.loadOffset(0), + MethodInvocation.invoke(new TypeDescription.ForLoadedType(Object.class) + .getDeclaredMethods() + .filter(isConstructor().and(takesArguments(0))).getOnly()), + + // this.visitor = visitor + MethodVariableAccess.REFERENCE.loadOffset(0), + MethodVariableAccess.REFERENCE.loadOffset(1), + FieldAccess.forField(implementationTarget.getInstrumentedType() + .getDeclaredFields() + .filter(ElementMatchers.named("visitor")) + .getOnly()).putter(), + + // return + MethodReturn.VOID + ).apply(methodVisitor, implementationContext); + return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize()); + } + }; + } + }) + + .method(ElementMatchers.named("accept")) + .intercept(new Implementation() { + @Override + public InstrumentedType prepare(InstrumentedType instrumentedType) { + return instrumentedType; + } + + @Override + public ByteCodeAppender appender(Target target) { + return new ByteCodeAppender() { + @Override + public Size apply(MethodVisitor methodVisitor, Context context, MethodDescription instrumentedMethod) { + MethodDescription typeMethod = new TypeDescription.ForLoadedType(Node.class) + .getDeclaredMethods() + .filter(ElementMatchers.named("type").and(ElementMatchers.takesArguments(0))) + .getOnly(); + StackManipulation.Size size = new StackManipulation.Compound( + MethodVariableAccess.REFERENCE.loadOffset(1), + MethodInvocation.invoke(typeMethod), + new StackManipulationSwitch( + // TODO: raise exception in default case + MethodReturn.VOID, + eagerMap(visitMethods, method -> { + Class<?> nodeClass = method.getParameterTypes()[0]; + return StackManipulationSwitch.switchCase( + NodeTypes.forClass(nodeClass), + new StackManipulation.Compound( + MethodVariableAccess.REFERENCE.loadOffset(0), + FieldAccess.forField(target.getInstrumentedType() + .getDeclaredFields() + .filter(ElementMatchers.named("visitor")) + .getOnly()).getter(), + MethodVariableAccess.REFERENCE.loadOffset(1), + TypeCasting.to(new TypeDescription.ForLoadedType(nodeClass)), + MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(method)), + MethodReturn.VOID + ) + ); + }) + ) + ).apply(methodVisitor, context); + return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize()); + } + }; + } + }) + .make() + .load(DynamicNodeVisitor.class.getClassLoader()) + .getLoaded(); + + return new DynamicNodeVisitor<T>(clazz, visitor); + } + + private static boolean isVisitMethod(String methodName, Method method) { + return + method.getName().equals(methodName) && + method.getParameterCount() == 1 && + Node.class.isAssignableFrom(method.getParameterTypes()[0]); + } + + private final Class<T> clazz; + private final Class<? extends Consumer> visitor; + + public DynamicNodeVisitor(Class<T> clazz, Class<? extends Consumer> visitor) { + this.clazz = clazz; + this.visitor = visitor; + } + + public Consumer<Node> instantiate(T instance) { + try { + return visitor.getConstructor(clazz).newInstance(instance); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java index 4ec0790..6c8a3ab 100644 --- a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java +++ b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java @@ -2,8 +2,8 @@ package org.zwobble.couscous.backends.csharp; import com.google.common.collect.ImmutableSet; import org.zwobble.couscous.ast.*; +import org.zwobble.couscous.ast.visitors.DynamicNodeVisitor; import org.zwobble.couscous.ast.visitors.NodeMapper; -import org.zwobble.couscous.ast.visitors.NodeVisitor; import org.zwobble.couscous.backends.SourceCodeWriter; import org.zwobble.couscous.types.*; import org.zwobble.couscous.util.Action; @@ -11,13 +11,14 @@ import org.zwobble.couscous.values.PrimitiveValue; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import static java.util.Arrays.asList; import static org.zwobble.couscous.util.ExtraIterables.lazyFilter; import static org.zwobble.couscous.util.ExtraLists.list; import static org.zwobble.couscous.util.ExtraSets.set; -public class CsharpSerializer implements NodeVisitor { +public class CsharpSerializer { private static final Set<String> RESERVED_TYPE_IDENTIFIERS = set( "bool", "byte", @@ -126,16 +127,19 @@ public class CsharpSerializer implements NodeVisitor { } private final SourceCodeWriter writer; + private final Consumer<Node> write; private CsharpSerializer(SourceCodeWriter writer) { this.writer = writer; + this.write = WRITE_BUILDER.instantiate(this); } + private static final DynamicNodeVisitor<CsharpSerializer> WRITE_BUILDER = DynamicNodeVisitor.build(CsharpSerializer.class, "visit"); + private void write(Node node) { - node.accept(this); + write.accept(node); } - @Override public void visit(LiteralNode literal) { literal.getValue().accept(new PrimitiveValue.Visitor<Void>() { @Override @@ -172,17 +176,14 @@ public class CsharpSerializer implements NodeVisitor { }); } - @Override public void visit(VariableReferenceNode variableReference) { writer.writeIdentifier(variableReference.getReferent().getName()); } - @Override public void visit(ThisReferenceNode reference) { writer.writeKeyword("this"); } - @Override public void visit(ArrayNode array) { writer.writeKeyword("new"); writer.writeSpace(); @@ -195,7 +196,6 @@ public class CsharpSerializer implements NodeVisitor { writer.writeSymbol("}"); } - @Override public void visit(AssignmentNode assignment) { write(assignment.getTarget()); writer.writeSpace(); @@ -204,7 +204,6 @@ public class CsharpSerializer implements NodeVisitor { writeParenthesized(assignment.getValue(), assignment); } - @Override public void visit(TernaryConditionalNode ternaryConditional) { writeParenthesized(ternaryConditional.getCondition(), ternaryConditional); writer.writeSpace(); @@ -217,7 +216,6 @@ public class CsharpSerializer implements NodeVisitor { writeParenthesized(ternaryConditional.getIfFalse(), ternaryConditional); } - @Override public void visit(MethodCallNode methodCall) { writeParenthesized(methodCall.getReceiver(), methodCall); writer.writeSymbol("."); @@ -234,7 +232,6 @@ public class CsharpSerializer implements NodeVisitor { } } - @Override public void visit(ConstructorCallNode call) { writer.writeKeyword("new"); writer.writeSpace(); @@ -242,7 +239,6 @@ public class CsharpSerializer implements NodeVisitor { writeArguments(call.getArguments()); } - @Override public void visit(OperationNode operation) { switch (operation.getOperatorType()) { case PREFIX: @@ -261,7 +257,6 @@ public class CsharpSerializer implements NodeVisitor { } } - @Override public void visit(InstanceOfNode instanceOf) { writeParenthesized(instanceOf.getLeft(), instanceOf); writer.writeSpace(); @@ -276,19 +271,16 @@ public class CsharpSerializer implements NodeVisitor { writer.writeSymbol(")"); } - @Override public void visit(FieldAccessNode fieldAccess) { writeParenthesized(fieldAccess.getLeft(), fieldAccess); writer.writeSymbol("."); writer.writeIdentifier(fieldAccess.getFieldName()); } - @Override public void visit(TypeCoercionNode typeCoercion) { write(typeCoercion.getExpression()); } - @Override public void visit(CastNode cast) { writer.writeSymbol("("); writeTypeReference(cast.getType()); @@ -297,17 +289,14 @@ public class CsharpSerializer implements NodeVisitor { writeParenthesized(cast.getExpression(), cast); } - @Override public void visit(InstanceReceiver receiver) { write(receiver.getExpression()); } - @Override public void visit(StaticReceiver receiver) { writeTypeReference(receiver.getType()); } - @Override public void visit(ReturnNode returnNode) { writer.writeStatement(() -> { writer.writeKeyword("return"); @@ -321,7 +310,6 @@ public class CsharpSerializer implements NodeVisitor { }); } - @Override public void visit(ThrowNode throwNode) { writer.writeStatement(() -> { writer.writeKeyword("throw"); @@ -331,7 +319,6 @@ public class CsharpSerializer implements NodeVisitor { }); } - @Override public void visit(ExpressionStatementNode expressionStatement) { writer.writeStatement(() -> { write(expressionStatement.getExpression()); @@ -339,7 +326,6 @@ public class CsharpSerializer implements NodeVisitor { }); } - @Override public void visit(LocalVariableDeclarationNode localVariableDeclaration) { writer.writeStatement(() -> { writeTypeReference(localVariableDeclaration.getType()); @@ -353,7 +339,6 @@ public class CsharpSerializer implements NodeVisitor { }); } - @Override public void visit(IfStatementNode ifStatement) { writer.writeStatement(() -> { writeIfContent(ifStatement); @@ -382,7 +367,6 @@ public class CsharpSerializer implements NodeVisitor { } } - @Override public void visit(WhileNode whileLoop) { writer.writeStatement(() -> { writer.writeKeyword("while"); @@ -395,7 +379,6 @@ public class CsharpSerializer implements NodeVisitor { }); } - @Override public void visit(TryNode tryStatement) { writer.writeStatement(() -> { writer.writeKeyword("try"); @@ -419,24 +402,20 @@ public class CsharpSerializer implements NodeVisitor { }); } - @Override public void visit(FormalTypeParameterNode parameter) { writer.writeIdentifier(parameter.getName()); } - @Override public void visit(FormalArgumentNode argument) { writeTypeReference(argument.getType()); writer.writeSpace(); writer.writeIdentifier(argument.getName()); } - @Override public void visit(AnnotationNode annotation) { throw new UnsupportedOperationException(); } - @Override public void visit(MethodNode method) { writer.writeStatement(() -> { writer.writeKeyword("public"); @@ -472,12 +451,10 @@ public class CsharpSerializer implements NodeVisitor { writer.writeSymbol(")"); } - @Override public void visit(ConstructorNode constructorNode) { throw new UnsupportedOperationException(); } - @Override public void visit(FieldDeclarationNode declaration) { writer.writeStatement(() -> { writer.writeKeyword("internal"); @@ -493,7 +470,6 @@ public class CsharpSerializer implements NodeVisitor { }); } - @Override public void visit(ClassNode classNode) { if (classNode.getTypeParameters().isEmpty()) { writeInstanceType(classNode, "class", () -> { @@ -527,7 +503,6 @@ public class CsharpSerializer implements NodeVisitor { } } - @Override public void visit(InterfaceNode interfaceNode) { writeInstanceType(interfaceNode, "interface", () -> { interfaceNode.getMethods().forEach(method -> { @@ -539,7 +514,6 @@ public class CsharpSerializer implements NodeVisitor { }); } - @Override public void visit(EnumNode enumNode) { writeInstanceType(enumNode, "enum", () -> { writer.writeStatement(() -> { diff --git a/src/main/java/org/zwobble/couscous/util/ExtraIterables.java b/src/main/java/org/zwobble/couscous/util/ExtraIterables.java index a0c4593..d8635c9 100644 --- a/src/main/java/org/zwobble/couscous/util/ExtraIterables.java +++ b/src/main/java/org/zwobble/couscous/util/ExtraIterables.java @@ -26,7 +26,7 @@ public class ExtraIterables { } } - public static <T1, T2, R> Iterable<R> map(Iterable<T1> iterable1, Iterable<T2> iterable2, BiFunction<T1, T2, R> function) { + public static <T1, T2, R> Iterable<R> lazyMap(Iterable<T1> iterable1, Iterable<T2> iterable2, BiFunction<T1, T2, R> function) { return new Iterable<R>() { @Override public Iterator<R> iterator() { diff --git a/src/main/java/org/zwobble/couscous/util/ExtraMaps.java b/src/main/java/org/zwobble/couscous/util/ExtraMaps.java index f25f06f..a659a01 100644 --- a/src/main/java/org/zwobble/couscous/util/ExtraMaps.java +++ b/src/main/java/org/zwobble/couscous/util/ExtraMaps.java @@ -29,7 +29,7 @@ public class ExtraMaps { } public static <T, K, V> Map<K, V> toMap(Iterable<K> keys, Iterable<V> values) { - return toMap(ExtraIterables.map(keys, values, ExtraMaps::entry)); + return toMap(ExtraIterables.lazyMap(keys, values, ExtraMaps::entry)); } public static <T, K, V> Map<K, V> toMap(Iterable<Map.Entry<K, V>> entries) { diff --git a/src/main/java/org/zwobble/couscous/util/asm/StackManipulationFrame.java b/src/main/java/org/zwobble/couscous/util/asm/StackManipulationFrame.java new file mode 100644 index 0000000..186c752 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/util/asm/StackManipulationFrame.java @@ -0,0 +1,25 @@ +package org.zwobble.couscous.util.asm; + +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.implementation.bytecode.StackSize; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; + +public class StackManipulationFrame implements StackManipulation { + public static final StackManipulation SAME = new StackManipulationFrame(); + + private StackManipulationFrame() { + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { + methodVisitor.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]); + return StackSize.ZERO.toIncreasingSize(); + } +} diff --git a/src/main/java/org/zwobble/couscous/util/asm/StackManipulationLabel.java b/src/main/java/org/zwobble/couscous/util/asm/StackManipulationLabel.java new file mode 100644 index 0000000..13d27bb --- /dev/null +++ b/src/main/java/org/zwobble/couscous/util/asm/StackManipulationLabel.java @@ -0,0 +1,26 @@ +package org.zwobble.couscous.util.asm; + +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.implementation.bytecode.StackSize; +import net.bytebuddy.jar.asm.Label; +import net.bytebuddy.jar.asm.MethodVisitor; + +public class StackManipulationLabel implements StackManipulation { + private final Label label; + + public StackManipulationLabel(Label label) { + this.label = label; + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { + methodVisitor.visitLabel(label); + return StackSize.ZERO.toIncreasingSize(); + } +} diff --git a/src/main/java/org/zwobble/couscous/util/asm/StackManipulationLookupTable.java b/src/main/java/org/zwobble/couscous/util/asm/StackManipulationLookupTable.java new file mode 100644 index 0000000..f3fc2c2 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/util/asm/StackManipulationLookupTable.java @@ -0,0 +1,30 @@ +package org.zwobble.couscous.util.asm; + +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.implementation.bytecode.StackSize; +import net.bytebuddy.jar.asm.Label; +import net.bytebuddy.jar.asm.MethodVisitor; + +public class StackManipulationLookupTable implements StackManipulation { + private final Label dflt; + private final int[] keys; + private final Label[] labels; + + public StackManipulationLookupTable(Label dflt, int[] keys, Label[] labels) { + this.dflt = dflt; + this.keys = keys; + this.labels = labels; + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { + methodVisitor.visitLookupSwitchInsn(dflt, keys, labels); + return StackSize.SINGLE.toDecreasingSize(); + } +} diff --git a/src/main/java/org/zwobble/couscous/util/asm/StackManipulationSwitch.java b/src/main/java/org/zwobble/couscous/util/asm/StackManipulationSwitch.java new file mode 100644 index 0000000..2dbcd70 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/util/asm/StackManipulationSwitch.java @@ -0,0 +1,72 @@ +package org.zwobble.couscous.util.asm; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Ordering; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.jar.asm.Label; +import net.bytebuddy.jar.asm.MethodVisitor; + +import java.util.List; + +import static java.util.Arrays.asList; +import static org.zwobble.couscous.util.ExtraIterables.lazyMap; +import static org.zwobble.couscous.util.ExtraLists.list; + +public class StackManipulationSwitch implements StackManipulation { + public static Case switchCase(int key, StackManipulation stackManipulation) { + return new Case(key, stackManipulation); + } + + public static class Case { + private final int key; + private final StackManipulation stackManipulation; + + private Case(int key, StackManipulation stackManipulation) { + this.key = key; + this.stackManipulation = stackManipulation; + } + } + + private final StackManipulation defaultCase; + private final List<Case> cases; + + public StackManipulationSwitch(StackManipulation defaultCase, List<Case> cases) { + this.defaultCase = defaultCase; + this.cases = Ordering.<Case>from((first, second) -> first.key - second.key).immutableSortedCopy(cases); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { + Label defaultLabel = new Label(); + Label[] caseLabels = Iterables.toArray(lazyMap(cases, switchCase -> new Label()), Label.class); + + return new StackManipulation.Compound(ImmutableList.copyOf(Iterables.concat( + list( + new StackManipulationLookupTable( + defaultLabel, + cases.stream().mapToInt(switchCase -> switchCase.key).toArray(), + caseLabels + ) + ), + + lazyMap(cases, asList(caseLabels), (switchCase, label) -> new StackManipulation.Compound( + new StackManipulationLabel(label), + StackManipulationFrame.SAME, + switchCase.stackManipulation + )), + + list( + new StackManipulationLabel(defaultLabel), + StackManipulationFrame.SAME, + defaultCase + ) + ))).apply(methodVisitor, implementationContext); + } +} |
