aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/org/zwobble/couscous/ast/NodeTypes.java86
-rw-r--r--src/main/java/org/zwobble/couscous/ast/visitors/DynamicNodeVisitor.java205
-rw-r--r--src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java42
-rw-r--r--src/main/java/org/zwobble/couscous/util/ExtraIterables.java2
-rw-r--r--src/main/java/org/zwobble/couscous/util/ExtraMaps.java2
-rw-r--r--src/main/java/org/zwobble/couscous/util/asm/StackManipulationFrame.java25
-rw-r--r--src/main/java/org/zwobble/couscous/util/asm/StackManipulationLabel.java26
-rw-r--r--src/main/java/org/zwobble/couscous/util/asm/StackManipulationLookupTable.java30
-rw-r--r--src/main/java/org/zwobble/couscous/util/asm/StackManipulationSwitch.java72
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);
+ }
+}