aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/org/zwobble/couscous/ast/NodeTypes.java2
-rw-r--r--src/main/java/org/zwobble/couscous/ast/Operations.java6
-rw-r--r--src/main/java/org/zwobble/couscous/ast/sugar/SwitchCaseNode.java36
-rw-r--r--src/main/java/org/zwobble/couscous/ast/sugar/SwitchNode.java48
-rw-r--r--src/main/java/org/zwobble/couscous/ast/visitors/NodeTransformer.java28
-rw-r--r--src/main/java/org/zwobble/couscous/backends/csharp/CsharpCodeGenerator.java6
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java6
-rw-r--r--src/main/java/org/zwobble/couscous/frontends/java/JavaStatementReader.java44
-rw-r--r--src/main/java/org/zwobble/couscous/interpreter/JavaProject.java7
-rw-r--r--src/main/java/org/zwobble/couscous/transforms/DesugarSwitchToIfElse.java90
-rw-r--r--src/main/java/org/zwobble/couscous/util/ExtraIterables.java39
-rw-r--r--src/main/java/org/zwobble/couscous/util/ExtraLists.java4
-rw-r--r--src/test/java/org/zwobble/couscous/tests/BackendMethodTests.java4
-rw-r--r--src/test/java/org/zwobble/couscous/tests/CompilerTests.java24
-rw-r--r--src/test/java/org/zwobble/couscous/tests/frontends/java/JavaReaderTests.java31
-rw-r--r--src/test/java/org/zwobble/couscous/tests/frontends/java/NodeMatchers.java37
-rw-r--r--src/test/java/org/zwobble/couscous/tests/transforms/DesugarSwitchToIfElseTests.java129
-rw-r--r--src/test/resources/java/switch-statements/com/example/SwitchStatements.java30
18 files changed, 502 insertions, 69 deletions
diff --git a/src/main/java/org/zwobble/couscous/ast/NodeTypes.java b/src/main/java/org/zwobble/couscous/ast/NodeTypes.java
index a82cd1c..86703ff 100644
--- a/src/main/java/org/zwobble/couscous/ast/NodeTypes.java
+++ b/src/main/java/org/zwobble/couscous/ast/NodeTypes.java
@@ -30,6 +30,8 @@ public class NodeTypes {
public static final int OPERATION = 10021;
public static final int RETURN = 10022;
public static final int STATIC_RECEIVER = 10023;
+ public static final int SWITCH = 10032;
+ public static final int SWITCH_CASE = 10033;
public static final int TERNARY_CONDITIONAL = 10024;
public static final int THIS_REFERENCE = 10025;
public static final int THROW = 10026;
diff --git a/src/main/java/org/zwobble/couscous/ast/Operations.java b/src/main/java/org/zwobble/couscous/ast/Operations.java
index 0ef9b88..308d070 100644
--- a/src/main/java/org/zwobble/couscous/ast/Operations.java
+++ b/src/main/java/org/zwobble/couscous/ast/Operations.java
@@ -4,7 +4,9 @@ import org.zwobble.couscous.types.ScalarType;
import org.zwobble.couscous.types.Types;
import org.zwobble.couscous.values.InternalCouscousValue;
+import static org.zwobble.couscous.ast.MethodCallNode.methodCall;
import static org.zwobble.couscous.ast.MethodCallNode.staticMethodCall;
+import static org.zwobble.couscous.ast.TypeCoercionNode.coerce;
import static org.zwobble.couscous.ast.TypeCoercionNode.typeCoercion;
import static org.zwobble.couscous.util.ExtraLists.list;
@@ -94,4 +96,8 @@ public class Operations {
public static ExpressionNode instanceOf(ExpressionNode value, ScalarType type) {
return new InstanceOfNode(value, type);
}
+
+ public static ExpressionNode methodEquals(ExpressionNode left, ExpressionNode right) {
+ return methodCall(left, "equals", list(coerce(right, Types.OBJECT)), Types.BOOLEAN);
+ }
}
diff --git a/src/main/java/org/zwobble/couscous/ast/sugar/SwitchCaseNode.java b/src/main/java/org/zwobble/couscous/ast/sugar/SwitchCaseNode.java
index 534f244..3aa9bdc 100644
--- a/src/main/java/org/zwobble/couscous/ast/sugar/SwitchCaseNode.java
+++ b/src/main/java/org/zwobble/couscous/ast/sugar/SwitchCaseNode.java
@@ -1,12 +1,25 @@
package org.zwobble.couscous.ast.sugar;
+import com.google.common.collect.Iterables;
import org.zwobble.couscous.ast.ExpressionNode;
+import org.zwobble.couscous.ast.Node;
+import org.zwobble.couscous.ast.NodeTypes;
import org.zwobble.couscous.ast.StatementNode;
+import org.zwobble.couscous.ast.visitors.NodeTransformer;
+import org.zwobble.couscous.util.ExtraIterables;
import java.util.List;
import java.util.Optional;
-public class SwitchCaseNode {
+public class SwitchCaseNode implements Node {
+ public static SwitchCaseNode switchCase(ExpressionNode value, List<StatementNode> statements) {
+ return switchCase(Optional.of(value), statements);
+ }
+
+ public static SwitchCaseNode switchCase(List<StatementNode> statements) {
+ return switchCase(Optional.empty(), statements);
+ }
+
public static SwitchCaseNode switchCase(Optional<ExpressionNode> value, List<StatementNode> statements) {
return new SwitchCaseNode(value, statements);
}
@@ -30,4 +43,25 @@ public class SwitchCaseNode {
public List<StatementNode> getStatements() {
return statements;
}
+
+ @Override
+ public int type() {
+ return NodeTypes.SWITCH_CASE;
+ }
+
+ @Override
+ public Iterable<? extends Node> childNodes() {
+ return Iterables.concat(
+ ExtraIterables.iterable(value),
+ statements
+ );
+ }
+
+ @Override
+ public SwitchCaseNode transform(NodeTransformer transformer) {
+ return new SwitchCaseNode(
+ value.map(transformer::transformExpression),
+ transformer.transformStatements(statements)
+ );
+ }
}
diff --git a/src/main/java/org/zwobble/couscous/ast/sugar/SwitchNode.java b/src/main/java/org/zwobble/couscous/ast/sugar/SwitchNode.java
new file mode 100644
index 0000000..8e1e226
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/ast/sugar/SwitchNode.java
@@ -0,0 +1,48 @@
+package org.zwobble.couscous.ast.sugar;
+
+import org.zwobble.couscous.ast.ExpressionNode;
+import org.zwobble.couscous.ast.Node;
+import org.zwobble.couscous.ast.NodeTypes;
+import org.zwobble.couscous.ast.StatementNode;
+import org.zwobble.couscous.ast.visitors.NodeTransformer;
+
+import java.util.List;
+
+import static org.zwobble.couscous.util.ExtraIterables.lazyCons;
+import static org.zwobble.couscous.util.ExtraLists.eagerMap;
+
+public class SwitchNode implements StatementNode {
+ private final ExpressionNode value;
+ private final List<SwitchCaseNode> cases;
+
+ public SwitchNode(ExpressionNode value, List<SwitchCaseNode> cases) {
+ this.value = value;
+ this.cases = cases;
+ }
+
+ public ExpressionNode getValue() {
+ return value;
+ }
+
+ public List<SwitchCaseNode> getCases() {
+ return cases;
+ }
+
+ @Override
+ public int type() {
+ return NodeTypes.SWITCH;
+ }
+
+ @Override
+ public Iterable<? extends Node> childNodes() {
+ return lazyCons(value, cases);
+ }
+
+ @Override
+ public StatementNode transform(NodeTransformer transformer) {
+ return new SwitchNode(
+ transformer.transformExpression(value),
+ eagerMap(cases, caseNode -> caseNode.transform(transformer))
+ );
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/NodeTransformer.java b/src/main/java/org/zwobble/couscous/ast/visitors/NodeTransformer.java
index 721ed8e..78bd6ef 100644
--- a/src/main/java/org/zwobble/couscous/ast/visitors/NodeTransformer.java
+++ b/src/main/java/org/zwobble/couscous/ast/visitors/NodeTransformer.java
@@ -14,7 +14,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import static org.zwobble.couscous.util.ExtraLists.eagerMap;
+import static org.zwobble.couscous.util.ExtraLists.*;
public class NodeTransformer {
public static NodeTransformer replaceExpressions(Map<ExpressionNode, ExpressionNode> replacements) {
@@ -29,6 +29,7 @@ public class NodeTransformer {
}
public static class Builder {
+ private Function<StatementNode, Optional<List<StatementNode>>> transformStatement = statement -> Optional.empty();
private Function<ExpressionNode, Optional<ExpressionNode>> transformExpression = expression -> Optional.empty();
private Function<Type, Type> transformType = type -> type;
private Function<MethodSignature, String> transformMethodName = MethodSignature::getName;
@@ -41,6 +42,13 @@ public class NodeTransformer {
return this;
}
+ public Builder transformStatement(
+ Function<StatementNode, Optional<List<StatementNode>>> transformStatement)
+ {
+ this.transformStatement = transformStatement;
+ return this;
+ }
+
public Builder transformType(
Function<Type, Type> transformType)
{
@@ -59,22 +67,31 @@ public class NodeTransformer {
}
public NodeTransformer build() {
- return new NodeTransformer(transformExpression, transformType, transformMethodName, transformFieldName);
+ return new NodeTransformer(
+ transformExpression,
+ transformStatement,
+ transformType,
+ transformMethodName,
+ transformFieldName
+ );
}
}
private final Function<ExpressionNode, Optional<ExpressionNode>> transformExpression;
+ private final Function<StatementNode, Optional<List<StatementNode>>> transformStatement;
private final Function<Type, Type> transformType;
private final Function<MethodSignature, String> transformMethodName;
private final Function<String, String> transformFieldName;
private NodeTransformer(
Function<ExpressionNode, Optional<ExpressionNode>> transformExpression,
+ Function<StatementNode, Optional<List<StatementNode>>> transformStatement,
Function<Type, Type> transformType,
Function<MethodSignature, String> transformMethodName,
Function<String, String> transformFieldName)
{
this.transformExpression = transformExpression;
+ this.transformStatement = transformStatement;
this.transformType = transformType;
this.transformMethodName = transformMethodName;
this.transformFieldName = transformFieldName;
@@ -121,8 +138,9 @@ public class NodeTransformer {
return VariableDeclaration.var(declaration.getId(), declaration.getName(), transform(declaration.getType()));
}
- public final StatementNode transformStatement(StatementNode statement) {
- return statement.transform(this);
+ public final Iterable<StatementNode> transformStatement(StatementNode statement) {
+ return transformStatement.apply(statement)
+ .orElseGet(() -> list(statement.transform(this)));
}
public FormalTypeParameterNode transformFormalTypeParameter(FormalTypeParameterNode typeParameter) {
@@ -178,7 +196,7 @@ public class NodeTransformer {
}
public List<StatementNode> transformStatements(List<StatementNode> body) {
- return transformList(body, this::transformStatement);
+ return eagerFlatMap(body, this::transformStatement);
}
public List<ExpressionNode> transformExpressions(List<? extends ExpressionNode> expressions) {
diff --git a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpCodeGenerator.java b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpCodeGenerator.java
index 537184c..23743d0 100644
--- a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpCodeGenerator.java
+++ b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpCodeGenerator.java
@@ -7,6 +7,8 @@ import org.zwobble.couscous.ast.Node;
import org.zwobble.couscous.ast.Receiver;
import org.zwobble.couscous.ast.visitors.NodeTransformer;
import org.zwobble.couscous.backends.naming.Naming;
+import org.zwobble.couscous.frontends.java.Scope;
+import org.zwobble.couscous.transforms.DesugarSwitchToIfElse;
import org.zwobble.couscous.types.*;
import java.util.Map;
@@ -48,7 +50,9 @@ public class CsharpCodeGenerator {
}
private Node generateCode(Node node) {
- return node.transform(nodeTransformer);
+ return node
+ .transform(DesugarSwitchToIfElse.transformer(Scope.create()))
+ .transform(nodeTransformer);
}
private Type transformType(Type type) {
diff --git a/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java
index 7b016bf..949506a 100644
--- a/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java
+++ b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java
@@ -9,6 +9,8 @@ import org.zwobble.couscous.ast.structure.NodeStructure;
import org.zwobble.couscous.ast.visitors.DynamicNodeMapper;
import org.zwobble.couscous.backends.naming.Names;
import org.zwobble.couscous.backends.python.ast.*;
+import org.zwobble.couscous.frontends.java.Scope;
+import org.zwobble.couscous.transforms.DesugarSwitchToIfElse;
import org.zwobble.couscous.types.ScalarType;
import org.zwobble.couscous.types.Type;
import org.zwobble.couscous.types.Types;
@@ -56,7 +58,9 @@ import static org.zwobble.couscous.util.ExtraIterables.*;
import static org.zwobble.couscous.util.ExtraLists.*;
public class PythonCodeGenerator {
- public static PythonModuleNode generateCode(TypeNode typeNode) {
+ public static PythonModuleNode generateCode(TypeNode sugaredTypeNode) {
+ TypeNode typeNode = sugaredTypeNode.transform(DesugarSwitchToIfElse.transformer(Scope.create()));
+
Iterator<PythonImportNode> imports = generateImports(typeNode).iterator();
PythonImportNode internalsImport = pythonImport(
diff --git a/src/main/java/org/zwobble/couscous/frontends/java/JavaStatementReader.java b/src/main/java/org/zwobble/couscous/frontends/java/JavaStatementReader.java
index 512030d..5692f8a 100644
--- a/src/main/java/org/zwobble/couscous/frontends/java/JavaStatementReader.java
+++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaStatementReader.java
@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
import org.eclipse.jdt.core.dom.*;
import org.zwobble.couscous.ast.*;
import org.zwobble.couscous.ast.sugar.SwitchCaseNode;
+import org.zwobble.couscous.ast.sugar.SwitchNode;
import org.zwobble.couscous.types.Type;
import org.zwobble.couscous.types.Types;
@@ -11,24 +12,20 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
-import static com.google.common.collect.Iterables.filter;
-import static com.google.common.collect.Iterables.tryFind;
import static org.zwobble.couscous.ast.ExceptionHandlerNode.exceptionHandler;
import static org.zwobble.couscous.ast.ExpressionStatementNode.expressionStatement;
import static org.zwobble.couscous.ast.IfStatementNode.ifStatement;
import static org.zwobble.couscous.ast.MethodCallNode.methodCall;
import static org.zwobble.couscous.ast.TryNode.tryStatement;
-import static org.zwobble.couscous.ast.TypeCoercionNode.coerce;
import static org.zwobble.couscous.ast.VariableReferenceNode.reference;
import static org.zwobble.couscous.ast.WhileNode.whileLoop;
import static org.zwobble.couscous.ast.sugar.SwitchCaseNode.switchCase;
import static org.zwobble.couscous.frontends.java.JavaTypes.typeOf;
import static org.zwobble.couscous.types.BoundTypeParameter.boundTypeParameter;
+import static org.zwobble.couscous.util.ExtraIterables.takeUntil;
import static org.zwobble.couscous.util.ExtraLists.*;
-import static org.zwobble.couscous.util.Fold.foldRight;
import static org.zwobble.couscous.util.Tails.tail;
import static org.zwobble.couscous.util.Tails.tails;
-import static org.zwobble.couscous.util.UpToAndIncludingIterable.upToAndIncluding;
class JavaStatementReader {
private final Scope scope;
@@ -120,8 +117,6 @@ class JavaStatementReader {
private List<StatementNode> readSwitchStatement(SwitchStatement switchStatement) {
ExpressionNode switchValue = readExpressionWithoutBoxing(switchStatement.getExpression());
- LocalVariableDeclarationNode switchValueAssignment = scope.temporaryVariable(switchValue);
-
@SuppressWarnings("unchecked")
List<Statement> statements = switchStatement.statements();
List<SwitchCaseNode> cases = eagerFlatMap(
@@ -134,42 +129,19 @@ class JavaStatementReader {
return list();
}
});
-
- List<StatementNode> handleDefault = tryFind(cases, currentCase -> currentCase.isDefault())
- .transform(currentCase -> currentCase.getStatements())
- .or(list());
-
- return cons(
- switchValueAssignment,
- foldRight(cases, handleDefault, (handle, currentCase) ->
- currentCase.getValue()
- .map(value -> list(ifStatement(
- methodCall(
- reference(switchValueAssignment),
- "equals",
- list(coerce(value, Types.OBJECT)),
- Types.BOOLEAN),
- currentCase.getStatements(), handle)))
- .orElse(handle)));
+ return list(new SwitchNode(switchValue, cases));
}
- private SwitchCaseNode readSwitchCase(SwitchCase caseStatement, List<Statement> statements)
- {
- Iterable<Statement> statementsForCase = upToAndIncluding(
- filter(
- statements,
- statement -> statement.getNodeType() != ASTNode.SWITCH_CASE),
- JavaStatementReader::isEndOfCase);
+ private SwitchCaseNode readSwitchCase(SwitchCase caseStatement, List<Statement> statements) {
+ Iterable<Statement> statementsForCase = takeUntil(
+ statements,
+ statement -> statement.getNodeType() == ASTNode.SWITCH_CASE
+ );
return switchCase(
Optional.ofNullable(caseStatement.getExpression()).map(this::readExpressionWithoutBoxing),
readStatements(statementsForCase));
}
- private static boolean isEndOfCase(Statement statement) {
- // TODO: do this on transformed nodes instead?
- return statement.getNodeType() == ASTNode.RETURN_STATEMENT;
- }
-
private List<StatementNode> readTryStatement(TryStatement statement) {
@SuppressWarnings("unchecked")
List<VariableDeclarationExpression> resources = statement.resources();
diff --git a/src/main/java/org/zwobble/couscous/interpreter/JavaProject.java b/src/main/java/org/zwobble/couscous/interpreter/JavaProject.java
index f96f0c4..42f0d83 100644
--- a/src/main/java/org/zwobble/couscous/interpreter/JavaProject.java
+++ b/src/main/java/org/zwobble/couscous/interpreter/JavaProject.java
@@ -2,11 +2,13 @@ package org.zwobble.couscous.interpreter;
import com.google.common.collect.Iterables;
import org.zwobble.couscous.ast.TypeNode;
+import org.zwobble.couscous.frontends.java.Scope;
import org.zwobble.couscous.interpreter.types.InterpreterType;
import org.zwobble.couscous.interpreter.types.IntrinsicInterpreterType;
import org.zwobble.couscous.interpreter.types.UserDefinedInterpreterType;
import org.zwobble.couscous.interpreter.values.BoxedIntegerInterpreterValue;
import org.zwobble.couscous.interpreter.values.InternalCouscousInterpreterValue;
+import org.zwobble.couscous.transforms.DesugarSwitchToIfElse;
import java.util.List;
@@ -22,7 +24,10 @@ public class JavaProject {
}
public static Project of(List<TypeNode> classNodes) {
- Iterable<InterpreterType> concreteTypes = Iterables.transform(classNodes, UserDefinedInterpreterType::new);
+ Iterable<InterpreterType> concreteTypes = Iterables.transform(
+ classNodes,
+ classNode -> new UserDefinedInterpreterType(classNode.transform(DesugarSwitchToIfElse.transformer(Scope.create())))
+ );
return builder()
.addClasses(concreteTypes)
.build();
diff --git a/src/main/java/org/zwobble/couscous/transforms/DesugarSwitchToIfElse.java b/src/main/java/org/zwobble/couscous/transforms/DesugarSwitchToIfElse.java
new file mode 100644
index 0000000..3ffaad5
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/transforms/DesugarSwitchToIfElse.java
@@ -0,0 +1,90 @@
+package org.zwobble.couscous.transforms;
+
+import org.zwobble.couscous.ast.LocalVariableDeclarationNode;
+import org.zwobble.couscous.ast.NodeTypes;
+import org.zwobble.couscous.ast.StatementNode;
+import org.zwobble.couscous.ast.sugar.SwitchCaseNode;
+import org.zwobble.couscous.ast.sugar.SwitchNode;
+import org.zwobble.couscous.ast.visitors.NodeTransformer;
+import org.zwobble.couscous.frontends.java.Scope;
+import org.zwobble.couscous.types.Types;
+import org.zwobble.couscous.util.ExtraLists;
+
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.collect.Iterables.tryFind;
+import static org.zwobble.couscous.ast.IfStatementNode.ifStatement;
+import static org.zwobble.couscous.ast.MethodCallNode.methodCall;
+import static org.zwobble.couscous.ast.TypeCoercionNode.coerce;
+import static org.zwobble.couscous.ast.VariableReferenceNode.reference;
+import static org.zwobble.couscous.ast.sugar.SwitchCaseNode.switchCase;
+import static org.zwobble.couscous.util.ExtraIterables.lazyFlatMap;
+import static org.zwobble.couscous.util.ExtraLists.*;
+import static org.zwobble.couscous.util.Fold.foldRight;
+import static org.zwobble.couscous.util.Tails.tails;
+import static org.zwobble.couscous.util.UpToAndIncludingIterable.upToAndIncluding;
+
+public class DesugarSwitchToIfElse {
+ // TODO: ensure globally unique IDs, and locally unique variable names
+
+ public static NodeTransformer transformer(Scope scope) {
+ return NodeTransformer.builder()
+ .transformStatement(statement -> {
+ if (statement.type() == NodeTypes.SWITCH) {
+ return Optional.of(desugar(scope, ((SwitchNode) statement)));
+ } else {
+ return Optional.empty();
+ }
+ })
+ .build();
+ }
+
+ public static List<StatementNode> desugar(Scope scope, SwitchNode switchNode) {
+ LocalVariableDeclarationNode switchValueAssignment = scope.temporaryVariable(switchNode.getValue());
+
+ List<StatementNode> handleDefault = tryFind(switchNode.getCases(), SwitchCaseNode::isDefault)
+ .transform(SwitchCaseNode::getStatements)
+ .or(list());
+
+ List<SwitchCaseNode> cases = eagerMap(
+ tails(switchNode.getCases()),
+ remainingCases -> readSwitchCase(remainingCases.get(0), remainingCases)
+ );
+
+ return cons(
+ switchValueAssignment,
+ foldRight(cases, handleDefault, (handle, currentCase) ->
+ currentCase.getValue()
+ .map(value -> list(ifStatement(
+ methodCall(
+ reference(switchValueAssignment),
+ "equals",
+ list(coerce(value, Types.OBJECT)),
+ Types.BOOLEAN
+ ),
+ currentCase.getStatements(),
+ handle
+ )))
+ .orElse(handle)
+ )
+ );
+ }
+
+ private static SwitchCaseNode readSwitchCase(SwitchCaseNode caseStatement, List<SwitchCaseNode> cases)
+ {
+ Iterable<StatementNode> statementsForCase = upToAndIncluding(
+ lazyFlatMap(cases, SwitchCaseNode::getStatements),
+ DesugarSwitchToIfElse::isEndOfCase
+ );
+ return switchCase(
+ caseStatement.getValue(),
+ ExtraLists.copyOf(statementsForCase)
+ );
+ }
+
+ private static boolean isEndOfCase(StatementNode statement) {
+ // TODO: add attribute onto statement node to detect exit
+ return statement.type() == NodeTypes.RETURN;
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/util/ExtraIterables.java b/src/main/java/org/zwobble/couscous/util/ExtraIterables.java
index 1140462..3881d29 100644
--- a/src/main/java/org/zwobble/couscous/util/ExtraIterables.java
+++ b/src/main/java/org/zwobble/couscous/util/ExtraIterables.java
@@ -2,8 +2,11 @@ package org.zwobble.couscous.util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.PeekingIterator;
import java.util.Iterator;
+import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -162,4 +165,40 @@ public class ExtraIterables {
public static <T> Iterable<T> of(T e1, T e2, T e3) {
return ImmutableList.of(e1, e2, e3);
}
+
+ public static <T> Iterable<T> iterable(Optional<T> optional) {
+ if (optional.isPresent()) {
+ return of(optional.get());
+ } else {
+ return empty();
+ }
+ }
+
+ public static <T> Iterable<T> takeUntil(Iterable<T> iterable, Predicate<T> predicate) {
+ return new Iterable<T>() {
+ @Override
+ public Iterator<T> iterator() {
+ return takeUntil(iterable.iterator(), predicate);
+ }
+ };
+ }
+
+ public static <T> Iterator<T> takeUntil(Iterator<T> iterator, Predicate<T> predicate) {
+ PeekingIterator<T> peekingIterator = Iterators.peekingIterator(iterator);
+ return new Iterator<T>() {
+ @Override
+ public boolean hasNext() {
+ return peekingIterator.hasNext() && !predicate.test(peekingIterator.peek());
+ }
+
+ @Override
+ public T next() {
+ if (hasNext()) {
+ return peekingIterator.next();
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+ };
+ }
}
diff --git a/src/main/java/org/zwobble/couscous/util/ExtraLists.java b/src/main/java/org/zwobble/couscous/util/ExtraLists.java
index 76d3de7..d52c099 100644
--- a/src/main/java/org/zwobble/couscous/util/ExtraLists.java
+++ b/src/main/java/org/zwobble/couscous/util/ExtraLists.java
@@ -143,4 +143,8 @@ public class ExtraLists {
this.exception = exception;
}
}
+
+ public static <T> List<T> copyOf(Iterable<T> iterable) {
+ return ImmutableList.copyOf(iterable);
+ }
}
diff --git a/src/test/java/org/zwobble/couscous/tests/BackendMethodTests.java b/src/test/java/org/zwobble/couscous/tests/BackendMethodTests.java
index dcb7d9e..990f376 100644
--- a/src/test/java/org/zwobble/couscous/tests/BackendMethodTests.java
+++ b/src/test/java/org/zwobble/couscous/tests/BackendMethodTests.java
@@ -146,7 +146,7 @@ public abstract class BackendMethodTests {
.statement(returns(reference(x)));
assertEquals(value(1), runMethod(method));
}
-
+
protected PrimitiveValue runMethod(MethodNode.Builder methodBuilder, PrimitiveValue... arguments) {
MethodNode method = methodBuilder.build();
String className = "com.example.Program";
@@ -156,4 +156,4 @@ public abstract class BackendMethodTests {
}
protected abstract MethodRunner buildMethodRunner();
-} \ No newline at end of file
+}
diff --git a/src/test/java/org/zwobble/couscous/tests/CompilerTests.java b/src/test/java/org/zwobble/couscous/tests/CompilerTests.java
index be3ca29..94c6e6a 100644
--- a/src/test/java/org/zwobble/couscous/tests/CompilerTests.java
+++ b/src/test/java/org/zwobble/couscous/tests/CompilerTests.java
@@ -289,6 +289,30 @@ public abstract class CompilerTests {
}
@Test
+ public void switchStatements() throws Exception {
+ switchStatementTest("simpleReturn", "one", 1);
+ switchStatementTest("simpleReturn", "zero", 0);
+
+ switchStatementTest("simpleFallthroughReturn", "one", 1);
+ switchStatementTest("simpleFallthroughReturn", "two", 1);
+ switchStatementTest("simpleFallthroughReturn", "zero", 0);
+
+ switchStatementTest("noDefaultReturn", "one", 1);
+ switchStatementTest("noDefaultReturn", "zero", 0);
+ }
+
+ private void switchStatementTest(String methodName, String input, int expected) throws Exception {
+ assertEquals(
+ value(expected),
+ execTestProgram(
+ "switch-statements",
+ ScalarType.of("com.example.SwitchStatements"),
+ methodName,
+ list(value(input)),
+ Types.INT));
+ }
+
+ @Test
public void stringAdd() throws Exception {
assertEquals(value("42"), evalExpression(Types.STRING, "\"4\" + \"2\""));
}
diff --git a/src/test/java/org/zwobble/couscous/tests/frontends/java/JavaReaderTests.java b/src/test/java/org/zwobble/couscous/tests/frontends/java/JavaReaderTests.java
index d455301..acda60c 100644
--- a/src/test/java/org/zwobble/couscous/tests/frontends/java/JavaReaderTests.java
+++ b/src/test/java/org/zwobble/couscous/tests/frontends/java/JavaReaderTests.java
@@ -179,25 +179,18 @@ public class JavaReaderTests {
}
@Test
- public void switchStatementWithoutFallThroughIsReadAsIfElseStatement() {
- assertEquals(
- readStatements(
- "int",
- "String _couscous_tmp_0 = \"one\";" +
- "if (_couscous_tmp_0.equals(\"one\")) { return 0; }" +
- "else if (_couscous_tmp_0.equals(\"two\")) { return 0; }" +
- "else { return 1; }"),
- readStatements("int", "switch (\"one\") { case \"one\": case \"two\": return 0; default: return 1; }"));
- }
-
- @Test
- public void switchStatementWithoutDefaultIsReadAsIfStatementWithoutElseStatement() {
- assertEquals(
- readStatements(
- "int",
- "String _couscous_tmp_0 = \"one\";" +
- "if (_couscous_tmp_0.equals(\"one\")) { return 0; } return 1;"),
- readStatements("int", "switch (\"one\") { case \"one\": return 0; } return 1;"));
+ public void canReadSwitchStatements() {
+ assertThat(
+ readStatement("int", "switch (\"one\") { case \"one\": case \"two\": return 0; default: return 1; }"),
+ isSwitch(
+ hasSwitchValue(equalTo(literal("one"))),
+ hasSwitchCases(
+ isCase(literal("one"), list()),
+ isCase(literal("two"), list(returns(literal(0)))),
+ isDefaultCase(list(returns(literal(1))))
+ )
+ )
+ );
}
@Test
diff --git a/src/test/java/org/zwobble/couscous/tests/frontends/java/NodeMatchers.java b/src/test/java/org/zwobble/couscous/tests/frontends/java/NodeMatchers.java
index e9c77f0..5815e68 100644
--- a/src/test/java/org/zwobble/couscous/tests/frontends/java/NodeMatchers.java
+++ b/src/test/java/org/zwobble/couscous/tests/frontends/java/NodeMatchers.java
@@ -3,13 +3,15 @@ package org.zwobble.couscous.tests.frontends.java;
import org.hamcrest.Matcher;
import org.zwobble.couscous.ast.*;
import org.zwobble.couscous.ast.identifiers.Identifier;
+import org.zwobble.couscous.ast.sugar.SwitchCaseNode;
+import org.zwobble.couscous.ast.sugar.SwitchNode;
import org.zwobble.couscous.types.Type;
import java.util.List;
+import java.util.Optional;
-import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.equalTo;
+import static java.util.Arrays.asList;
+import static org.hamcrest.Matchers.*;
import static org.zwobble.couscous.tests.util.ExtraMatchers.hasFeature;
import static org.zwobble.couscous.tests.util.ExtraMatchers.isInstance;
@@ -86,4 +88,33 @@ public class NodeMatchers {
public static Matcher<TryNode> hasFinally(StatementNode... statements) {
return hasFeature("finally clause", TryNode::getFinallyBody, contains(statements));
}
+
+ @SafeVarargs
+ public static Matcher<StatementNode> isSwitch(Matcher<SwitchNode>... matchers) {
+ return isInstance(SwitchNode.class, allOf(matchers));
+ }
+
+ public static Matcher<SwitchNode> hasSwitchValue(Matcher<ExpressionNode> matcher) {
+ return hasFeature("switch value", SwitchNode::getValue, matcher);
+ }
+
+ @SafeVarargs
+ public static Matcher<SwitchNode> hasSwitchCases(Matcher<SwitchCaseNode>... matchers) {
+ return hasFeature("switch cases", SwitchNode::getCases, contains(asList(matchers)));
+ }
+
+ public static Matcher<SwitchCaseNode> isCase(ExpressionNode value, List<StatementNode> statements) {
+ return isCase(Optional.of(value), statements);
+ }
+
+ public static Matcher<SwitchCaseNode> isDefaultCase(List<StatementNode> statements) {
+ return isCase(Optional.empty(), statements);
+ }
+
+ private static Matcher<SwitchCaseNode> isCase(Optional<ExpressionNode> value, List<StatementNode> statements) {
+ return allOf(
+ hasFeature("value", SwitchCaseNode::getValue, equalTo(value)),
+ hasFeature("statements", SwitchCaseNode::getStatements, equalTo(statements))
+ );
+ }
}
diff --git a/src/test/java/org/zwobble/couscous/tests/transforms/DesugarSwitchToIfElseTests.java b/src/test/java/org/zwobble/couscous/tests/transforms/DesugarSwitchToIfElseTests.java
new file mode 100644
index 0000000..9e458e7
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/transforms/DesugarSwitchToIfElseTests.java
@@ -0,0 +1,129 @@
+package org.zwobble.couscous.tests.transforms;
+
+import org.junit.Test;
+import org.zwobble.couscous.ast.Operations;
+import org.zwobble.couscous.ast.VariableDeclaration;
+import org.zwobble.couscous.ast.identifiers.Identifiers;
+import org.zwobble.couscous.ast.sugar.SwitchNode;
+import org.zwobble.couscous.frontends.java.Scope;
+import org.zwobble.couscous.transforms.DesugarSwitchToIfElse;
+import org.zwobble.couscous.types.Types;
+
+import static org.junit.Assert.assertEquals;
+import static org.zwobble.couscous.ast.ExpressionStatementNode.expressionStatement;
+import static org.zwobble.couscous.ast.IfStatementNode.ifStatement;
+import static org.zwobble.couscous.ast.LiteralNode.literal;
+import static org.zwobble.couscous.ast.LocalVariableDeclarationNode.localVariableDeclaration;
+import static org.zwobble.couscous.ast.ReturnNode.returns;
+import static org.zwobble.couscous.ast.VariableDeclaration.var;
+import static org.zwobble.couscous.ast.VariableReferenceNode.reference;
+import static org.zwobble.couscous.ast.sugar.SwitchCaseNode.switchCase;
+import static org.zwobble.couscous.util.ExtraLists.list;
+
+public class DesugarSwitchToIfElseTests {
+ @Test
+ public void switchStatementWithoutFallThroughIsReadAsIfElseStatement() {
+ VariableDeclaration tmp = var(
+ Identifiers.variable(Identifiers.TOP, "_couscous_tmp_0"),
+ "_couscous_tmp_0",
+ Types.STRING
+ );
+ assertEquals(
+ list(
+ localVariableDeclaration(tmp, literal("one")),
+ ifStatement(
+ Operations.methodEquals(reference(tmp), literal("one")),
+ list(returns(literal(1))),
+ list(
+ ifStatement(
+ Operations.methodEquals(reference(tmp), literal("two")),
+ list(returns(literal(2))),
+ list(
+ returns(literal(0))
+ )
+ )
+ )
+ )
+ ),
+ DesugarSwitchToIfElse.desugar(
+ Scope.create(),
+ new SwitchNode(
+ literal("one"),
+ list(
+ switchCase(literal("one"), list(returns(literal(1)))),
+ switchCase(literal("two"), list(returns(literal(2)))),
+ switchCase(list(returns(literal(0))))
+ )
+ )
+ )
+ );
+ }
+
+ @Test
+ public void switchStatementWithoutDefaultIsReadAsIfStatementWithoutElseStatement() {
+ VariableDeclaration tmp = var(
+ Identifiers.variable(Identifiers.TOP, "_couscous_tmp_0"),
+ "_couscous_tmp_0",
+ Types.STRING
+ );
+ assertEquals(
+ list(
+ localVariableDeclaration(tmp, literal("one")),
+ ifStatement(
+ Operations.methodEquals(reference(tmp), literal("two")),
+ list(returns(literal(0))),
+ list()
+ )
+ ),
+ DesugarSwitchToIfElse.desugar(
+ Scope.create(),
+ new SwitchNode(
+ literal("one"),
+ list(
+ switchCase(literal("two"), list(returns(literal(0))))
+ )
+ )
+ )
+ );
+ }
+
+ @Test
+ public void casesCanFallthrough() {
+ VariableDeclaration tmp = var(
+ Identifiers.variable(Identifiers.TOP, "_couscous_tmp_0"),
+ "_couscous_tmp_0",
+ Types.STRING
+ );
+ assertEquals(
+ list(
+ localVariableDeclaration(tmp, literal("one")),
+ ifStatement(
+ Operations.methodEquals(reference(tmp), literal("one")),
+ list(
+ expressionStatement(literal(1)),
+ returns(literal(2))
+ ),
+ list(
+ ifStatement(
+ Operations.methodEquals(reference(tmp), literal("two")),
+ list(
+ returns(literal(2))
+ ),
+ list()
+ )
+ )
+ )
+ ),
+ DesugarSwitchToIfElse.desugar(
+ Scope.create(),
+ new SwitchNode(
+ literal("one"),
+ list(
+ switchCase(literal("one"), list(expressionStatement(literal(1)))),
+ switchCase(literal("two"), list(returns(literal(2))))
+ )
+ )
+ )
+ );
+ }
+}
diff --git a/src/test/resources/java/switch-statements/com/example/SwitchStatements.java b/src/test/resources/java/switch-statements/com/example/SwitchStatements.java
new file mode 100644
index 0000000..bebccf9
--- /dev/null
+++ b/src/test/resources/java/switch-statements/com/example/SwitchStatements.java
@@ -0,0 +1,30 @@
+package com.example;
+
+public class SwitchStatements {
+ public static int simpleReturn(String value) {
+ switch (value) {
+ case "one":
+ return 1;
+ default:
+ return 0;
+ }
+ }
+
+ public static int simpleFallthroughReturn(String value) {
+ switch (value) {
+ case "one":
+ case "two":
+ return 1;
+ default:
+ return 0;
+ }
+ }
+
+ public static int noDefaultReturn(String value) {
+ switch (value) {
+ case "one":
+ return 1;
+ }
+ return 0;
+ }
+}