From ccac9247fe4663874be8026ab9cc968ecec7157b Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Tue, 29 Sep 2015 20:14:34 +0100 Subject: Initial commit --- .classpath | 22 ++++ .gitignore | 1 + .project | 23 ++++ .settings/org.eclipse.core.resources.prefs | 4 + .settings/org.eclipse.jdt.core.prefs | 8 ++ .settings/org.eclipse.m2e.core.prefs | 4 + pom.xml | 37 +++++++ src/main/java/org/zwobble/couscous/Project.java | 7 ++ .../java/org/zwobble/couscous/ast/Assignment.java | 16 +++ .../java/org/zwobble/couscous/ast/CallNode.java | 5 + .../java/org/zwobble/couscous/ast/ClassNode.java | 14 +++ .../org/zwobble/couscous/ast/ExpressionNode.java | 7 ++ .../couscous/ast/ExpressionStatementNode.java | 15 +++ .../zwobble/couscous/ast/FormalArgumentNode.java | 9 ++ .../java/org/zwobble/couscous/ast/LiteralNode.java | 26 +++++ .../java/org/zwobble/couscous/ast/MethodNode.java | 22 ++++ .../java/org/zwobble/couscous/ast/ReturnNode.java | 15 +++ .../org/zwobble/couscous/ast/StatementNode.java | 7 ++ .../org/zwobble/couscous/ast/VariableNode.java | 5 + .../couscous/ast/VariableReferenceNode.java | 19 ++++ .../ast/visitors/ExpressionNodeVisitor.java | 11 ++ .../ast/visitors/StatementNodeVisitor.java | 9 ++ .../zwobble/couscous/interpreter/Interpreter.java | 110 +++++++++++++++++++ .../couscous/interpreter/InterpreterException.java | 6 ++ .../interpreter/WrongNumberOfArguments.java | 13 +++ .../org/zwobble/couscous/values/IntegerValue.java | 8 ++ .../zwobble/couscous/values/InterpreterValue.java | 5 + .../org/zwobble/couscous/values/StringValue.java | 8 ++ .../org/zwobble/couscous/values/UnitValue.java | 11 ++ .../tests/interpreter/InterpreterTests.java | 117 +++++++++++++++++++++ .../zwobble/couscous/tests/util/ExtraAsserts.java | 21 ++++ 31 files changed, 585 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 pom.xml create mode 100644 src/main/java/org/zwobble/couscous/Project.java create mode 100644 src/main/java/org/zwobble/couscous/ast/Assignment.java create mode 100644 src/main/java/org/zwobble/couscous/ast/CallNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/ClassNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/ExpressionNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/ExpressionStatementNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/FormalArgumentNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/LiteralNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/MethodNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/ReturnNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/StatementNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/VariableNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java create mode 100644 src/main/java/org/zwobble/couscous/ast/visitors/ExpressionNodeVisitor.java create mode 100644 src/main/java/org/zwobble/couscous/ast/visitors/StatementNodeVisitor.java create mode 100644 src/main/java/org/zwobble/couscous/interpreter/Interpreter.java create mode 100644 src/main/java/org/zwobble/couscous/interpreter/InterpreterException.java create mode 100644 src/main/java/org/zwobble/couscous/interpreter/WrongNumberOfArguments.java create mode 100644 src/main/java/org/zwobble/couscous/values/IntegerValue.java create mode 100644 src/main/java/org/zwobble/couscous/values/InterpreterValue.java create mode 100644 src/main/java/org/zwobble/couscous/values/StringValue.java create mode 100644 src/main/java/org/zwobble/couscous/values/UnitValue.java create mode 100644 src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java create mode 100644 src/test/java/org/zwobble/couscous/tests/util/ExtraAsserts.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..a67b19b --- /dev/null +++ b/.classpath @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.project b/.project new file mode 100644 index 0000000..b727570 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + couscous + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..f9fe345 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..6e80039 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..204af41 --- /dev/null +++ b/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + org.zwobble.couscous + couscous + 0.0.1-SNAPSHOT + jar + + couscous + http://maven.apache.org + + + UTF-8 + + + + + junit + junit + 4.12 + test + + + org.projectlombok + lombok + 1.16.6 + provided + + + com.google.guava + guava + 18.0 + runtime + + + diff --git a/src/main/java/org/zwobble/couscous/Project.java b/src/main/java/org/zwobble/couscous/Project.java new file mode 100644 index 0000000..6afac6e --- /dev/null +++ b/src/main/java/org/zwobble/couscous/Project.java @@ -0,0 +1,7 @@ +package org.zwobble.couscous; + +import org.zwobble.couscous.ast.ClassNode; + +public interface Project { + ClassNode findClass(String name); +} diff --git a/src/main/java/org/zwobble/couscous/ast/Assignment.java b/src/main/java/org/zwobble/couscous/ast/Assignment.java new file mode 100644 index 0000000..aa7f89b --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/Assignment.java @@ -0,0 +1,16 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.ExpressionNodeVisitor; + +import lombok.Value; + +@Value +public class Assignment implements ExpressionNode { + VariableReferenceNode target; + ExpressionNode value; + + @Override + public T accept(ExpressionNodeVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/org/zwobble/couscous/ast/CallNode.java b/src/main/java/org/zwobble/couscous/ast/CallNode.java new file mode 100644 index 0000000..d71c891 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/CallNode.java @@ -0,0 +1,5 @@ +package org.zwobble.couscous.ast; + +public class CallNode { + +} diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNode.java b/src/main/java/org/zwobble/couscous/ast/ClassNode.java new file mode 100644 index 0000000..ffa5165 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/ClassNode.java @@ -0,0 +1,14 @@ +package org.zwobble.couscous.ast; + +import java.util.List; + +import lombok.Builder; +import lombok.Singular; +import lombok.Value; + +@Builder +@Value +public class ClassNode { + @Singular + List methods; +} diff --git a/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java b/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java new file mode 100644 index 0000000..097377a --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java @@ -0,0 +1,7 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.ExpressionNodeVisitor; + +public interface ExpressionNode { + T accept(ExpressionNodeVisitor visitor); +} diff --git a/src/main/java/org/zwobble/couscous/ast/ExpressionStatementNode.java b/src/main/java/org/zwobble/couscous/ast/ExpressionStatementNode.java new file mode 100644 index 0000000..ec04663 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/ExpressionStatementNode.java @@ -0,0 +1,15 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.StatementNodeVisitor; + +import lombok.Value; + +@Value +public class ExpressionStatementNode implements StatementNode { + ExpressionNode expression; + + @Override + public T accept(StatementNodeVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/org/zwobble/couscous/ast/FormalArgumentNode.java b/src/main/java/org/zwobble/couscous/ast/FormalArgumentNode.java new file mode 100644 index 0000000..da3a838 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/FormalArgumentNode.java @@ -0,0 +1,9 @@ +package org.zwobble.couscous.ast; + +import lombok.Value; + +@Value +public class FormalArgumentNode implements VariableNode { + int id; + String name; +} diff --git a/src/main/java/org/zwobble/couscous/ast/LiteralNode.java b/src/main/java/org/zwobble/couscous/ast/LiteralNode.java new file mode 100644 index 0000000..38688d8 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/LiteralNode.java @@ -0,0 +1,26 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.ExpressionNodeVisitor; +import org.zwobble.couscous.values.IntegerValue; +import org.zwobble.couscous.values.InterpreterValue; +import org.zwobble.couscous.values.StringValue; + +import lombok.Value; + +@Value +public class LiteralNode implements ExpressionNode { + public static LiteralNode of(String value) { + return new LiteralNode(new StringValue(value)); + } + + public static LiteralNode of(int value) { + return new LiteralNode(new IntegerValue(value)); + } + + InterpreterValue value; + + @Override + public T accept(ExpressionNodeVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/org/zwobble/couscous/ast/MethodNode.java b/src/main/java/org/zwobble/couscous/ast/MethodNode.java new file mode 100644 index 0000000..91acbd5 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/MethodNode.java @@ -0,0 +1,22 @@ +package org.zwobble.couscous.ast; + +import java.util.List; + +import lombok.Builder; +import lombok.Singular; +import lombok.Value; + +@Builder +@Value +public class MethodNode { + public static MethodNodeBuilder staticMethod(String name) { + return builder().isStatic(true).name(name); + } + + boolean isStatic; + String name; + @Singular + List arguments; + @Singular("statement") + List body; +} diff --git a/src/main/java/org/zwobble/couscous/ast/ReturnNode.java b/src/main/java/org/zwobble/couscous/ast/ReturnNode.java new file mode 100644 index 0000000..529dbb5 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/ReturnNode.java @@ -0,0 +1,15 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.StatementNodeVisitor; + +import lombok.Value; + +@Value +public class ReturnNode implements StatementNode { + ExpressionNode value; + + @Override + public T accept(StatementNodeVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/org/zwobble/couscous/ast/StatementNode.java b/src/main/java/org/zwobble/couscous/ast/StatementNode.java new file mode 100644 index 0000000..773420e --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/StatementNode.java @@ -0,0 +1,7 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.StatementNodeVisitor; + +public interface StatementNode { + T accept(StatementNodeVisitor visitor); +} diff --git a/src/main/java/org/zwobble/couscous/ast/VariableNode.java b/src/main/java/org/zwobble/couscous/ast/VariableNode.java new file mode 100644 index 0000000..4c689c5 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/VariableNode.java @@ -0,0 +1,5 @@ +package org.zwobble.couscous.ast; + +public interface VariableNode { + int getId(); +} diff --git a/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java b/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java new file mode 100644 index 0000000..536a7c3 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java @@ -0,0 +1,19 @@ +package org.zwobble.couscous.ast; + +import org.zwobble.couscous.ast.visitors.ExpressionNodeVisitor; + +import lombok.Value; + +@Value +public class VariableReferenceNode implements ExpressionNode { + public static VariableReferenceNode reference(VariableNode variable) { + return new VariableReferenceNode(variable.getId()); + } + + int referentId; + + @Override + public T accept(ExpressionNodeVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/ExpressionNodeVisitor.java b/src/main/java/org/zwobble/couscous/ast/visitors/ExpressionNodeVisitor.java new file mode 100644 index 0000000..0167abc --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/visitors/ExpressionNodeVisitor.java @@ -0,0 +1,11 @@ +package org.zwobble.couscous.ast.visitors; + +import org.zwobble.couscous.ast.Assignment; +import org.zwobble.couscous.ast.LiteralNode; +import org.zwobble.couscous.ast.VariableReferenceNode; + +public interface ExpressionNodeVisitor { + T visit(LiteralNode literal); + T visit(VariableReferenceNode variableReference); + T visit(Assignment assignment); +} diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/StatementNodeVisitor.java b/src/main/java/org/zwobble/couscous/ast/visitors/StatementNodeVisitor.java new file mode 100644 index 0000000..6cec404 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/visitors/StatementNodeVisitor.java @@ -0,0 +1,9 @@ +package org.zwobble.couscous.ast.visitors; + +import org.zwobble.couscous.ast.ExpressionStatementNode; +import org.zwobble.couscous.ast.ReturnNode; + +public interface StatementNodeVisitor { + T visit(ReturnNode returnNode); + T visit(ExpressionStatementNode expressionStatement); +} diff --git a/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java b/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java new file mode 100644 index 0000000..49c651b --- /dev/null +++ b/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java @@ -0,0 +1,110 @@ +package org.zwobble.couscous.interpreter; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.IntStream; + +import org.zwobble.couscous.Project; +import org.zwobble.couscous.ast.Assignment; +import org.zwobble.couscous.ast.ExpressionNode; +import org.zwobble.couscous.ast.ExpressionStatementNode; +import org.zwobble.couscous.ast.LiteralNode; +import org.zwobble.couscous.ast.MethodNode; +import org.zwobble.couscous.ast.ReturnNode; +import org.zwobble.couscous.ast.VariableReferenceNode; +import org.zwobble.couscous.ast.visitors.ExpressionNodeVisitor; +import org.zwobble.couscous.ast.visitors.StatementNodeVisitor; +import org.zwobble.couscous.values.InterpreterValue; + +import static com.google.common.collect.Iterables.find; +import static java.util.stream.Collectors.toMap; +import static org.zwobble.couscous.values.UnitValue.UNIT; + +import lombok.val; + +public class Interpreter { + private Project project; + + public Interpreter(Project project) { + this.project = project; + } + + public InterpreterValue run(String className, String methodName, List arguments) { + val clazz = project.findClass(className); + val method = find(clazz.getMethods(), + method -> method.getName().equals(methodName)); + val stackFrame = buildStackFrame(method, arguments); + + for (val statement : method.getBody()) { + val result = statement.accept(new Executor(new Evaluator(stackFrame))); + if (result.isPresent()) { + return result.get(); + } + } + return UNIT; + } + + private Map buildStackFrame( + final MethodNode method, + List arguments) { + + if (method.getArguments().size() != arguments.size()) { + throw new WrongNumberOfArguments(method.getArguments().size(), arguments.size()); + } + + return IntStream.range(0, method.getArguments().size()) + .boxed() + .collect(toMap( + index -> method.getArguments().get(index).getId(), + index -> arguments.get(index))); + } + + private class Executor implements StatementNodeVisitor> { + private Evaluator evaluator; + + public Executor(Evaluator evaluator) { + this.evaluator = evaluator; + } + + @Override + public Optional visit(ReturnNode returnNode) { + return Optional.of(evaluator.eval(returnNode.getValue())); + } + + @Override + public Optional visit(ExpressionStatementNode expressionStatement) { + evaluator.eval(expressionStatement.getExpression()); + return Optional.empty(); + } + } + + private class Evaluator implements ExpressionNodeVisitor { + private final Map stackFrame; + + public Evaluator(Map stackFrame) { + this.stackFrame = stackFrame; + } + + public InterpreterValue eval(ExpressionNode expression) { + return expression.accept(this); + } + + @Override + public InterpreterValue visit(LiteralNode literal) { + return literal.getValue(); + } + + @Override + public InterpreterValue visit(VariableReferenceNode variableReference) { + return stackFrame.get(variableReference.getReferentId()); + } + + @Override + public InterpreterValue visit(Assignment assignment) { + val value = eval(assignment.getValue()); + stackFrame.put(assignment.getTarget().getReferentId(), value); + return value; + } + } +} diff --git a/src/main/java/org/zwobble/couscous/interpreter/InterpreterException.java b/src/main/java/org/zwobble/couscous/interpreter/InterpreterException.java new file mode 100644 index 0000000..d6d7020 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/interpreter/InterpreterException.java @@ -0,0 +1,6 @@ +package org.zwobble.couscous.interpreter; + +public class InterpreterException extends RuntimeException { + private static final long serialVersionUID = 1L; + +} diff --git a/src/main/java/org/zwobble/couscous/interpreter/WrongNumberOfArguments.java b/src/main/java/org/zwobble/couscous/interpreter/WrongNumberOfArguments.java new file mode 100644 index 0000000..06d8079 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/interpreter/WrongNumberOfArguments.java @@ -0,0 +1,13 @@ +package org.zwobble.couscous.interpreter; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper=false) +public class WrongNumberOfArguments extends InterpreterException { + private static final long serialVersionUID = 1L; + + int expected; + int actual; +} diff --git a/src/main/java/org/zwobble/couscous/values/IntegerValue.java b/src/main/java/org/zwobble/couscous/values/IntegerValue.java new file mode 100644 index 0000000..062d137 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/values/IntegerValue.java @@ -0,0 +1,8 @@ +package org.zwobble.couscous.values; + +import lombok.Value; + +@Value +public class IntegerValue implements InterpreterValue { + int value; +} diff --git a/src/main/java/org/zwobble/couscous/values/InterpreterValue.java b/src/main/java/org/zwobble/couscous/values/InterpreterValue.java new file mode 100644 index 0000000..64b0e91 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/values/InterpreterValue.java @@ -0,0 +1,5 @@ +package org.zwobble.couscous.values; + +public interface InterpreterValue { + +} diff --git a/src/main/java/org/zwobble/couscous/values/StringValue.java b/src/main/java/org/zwobble/couscous/values/StringValue.java new file mode 100644 index 0000000..1478d41 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/values/StringValue.java @@ -0,0 +1,8 @@ +package org.zwobble.couscous.values; + +import lombok.Value; + +@Value +public class StringValue implements InterpreterValue { + String value; +} diff --git a/src/main/java/org/zwobble/couscous/values/UnitValue.java b/src/main/java/org/zwobble/couscous/values/UnitValue.java new file mode 100644 index 0000000..1cf9dc7 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/values/UnitValue.java @@ -0,0 +1,11 @@ +package org.zwobble.couscous.values; + +import lombok.ToString; + +@ToString +public class UnitValue implements InterpreterValue { + public static final UnitValue UNIT = new UnitValue(); + + private UnitValue() { + } +} diff --git a/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java b/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java new file mode 100644 index 0000000..d3bc21c --- /dev/null +++ b/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java @@ -0,0 +1,117 @@ +package org.zwobble.couscous.tests.interpreter; + +import java.util.Map; + +import org.junit.Test; +import org.zwobble.couscous.Project; +import org.zwobble.couscous.ast.Assignment; +import org.zwobble.couscous.ast.ClassNode; +import org.zwobble.couscous.ast.ExpressionStatementNode; +import org.zwobble.couscous.ast.FormalArgumentNode; +import org.zwobble.couscous.ast.LiteralNode; +import org.zwobble.couscous.ast.MethodNode; +import org.zwobble.couscous.ast.ReturnNode; +import org.zwobble.couscous.interpreter.Interpreter; +import org.zwobble.couscous.interpreter.WrongNumberOfArguments; +import org.zwobble.couscous.values.InterpreterValue; +import org.zwobble.couscous.values.StringValue; + +import com.google.common.collect.ImmutableMap; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.zwobble.couscous.ast.MethodNode.staticMethod; +import static org.zwobble.couscous.ast.VariableReferenceNode.reference; +import static org.zwobble.couscous.tests.util.ExtraAsserts.assertThrows; +import static org.zwobble.couscous.values.UnitValue.UNIT; + +import lombok.val; + +public class InterpreterTests { + @Test + public void canReturnLiteralValue() { + val method = staticMethod("hello") + .statement(new ReturnNode(LiteralNode.of("hello, world!"))); + val result = runMethod(method); + + assertEquals(new StringValue("hello, world!"), result); + } + + @Test + public void canPassValueToMethod() { + val arg = new FormalArgumentNode(42, "x"); + val method = staticMethod("hello") + .argument(arg) + .statement(new ReturnNode(reference(arg))); + val result = runMethod(method, new StringValue("hello, world!")); + + assertEquals(new StringValue("hello, world!"), result); + } + + @Test + public void errorIfWrongNumberOfArgumentsArePassed() { + val method = staticMethod("hello"); + + val exception = assertThrows(WrongNumberOfArguments.class, + () -> runMethod(method, new StringValue("hello, world!"))); + + assertEquals(new WrongNumberOfArguments(0, 1), exception); + } + + @Test + public void canReassignValueToArgument() { + val arg = new FormalArgumentNode(42, "x"); + val method = staticMethod("hello") + .argument(arg) + .statement(new ExpressionStatementNode(new Assignment(reference(arg), LiteralNode.of("[updated value]")))) + .statement(new ReturnNode(reference(arg))); + val result = runMethod(method, new StringValue("[initial value]")); + + assertEquals(new StringValue("[updated value]"), result); + } + + @Test + public void valueOfAssignmentExpressionIsNewValue() { + val arg = new FormalArgumentNode(42, "x"); + val method = staticMethod("hello") + .argument(arg) + .statement(new ReturnNode(new Assignment(reference(arg), LiteralNode.of("[updated value]")))); + val result = runMethod(method, new StringValue("[initial value]")); + + assertEquals(new StringValue("[updated value]"), result); + } + + @Test + public void methodWithNoStatementsReturnsUnit() { + val method = staticMethod("hello"); + val result = runMethod(method); + + assertEquals(UNIT, result); + } + + private InterpreterValue runMethod(MethodNode.MethodNodeBuilder methodBuilder, InterpreterValue... arguments) { + val method = methodBuilder.build(); + val classNode = ClassNode.builder() + .method(method) + .build(); + val className = "com.example.Program"; + val interpreter = new Interpreter(new MapBackedProject(ImmutableMap.of( + className, classNode))); + + return interpreter.run(className, method.getName(), asList(arguments)); + } + + private static class MapBackedProject implements Project { + private Map classes; + + public MapBackedProject(Map classes) { + this.classes = classes; + } + + @Override + public ClassNode findClass(String name) { + return classes.get(name); + } + + } +} diff --git a/src/test/java/org/zwobble/couscous/tests/util/ExtraAsserts.java b/src/test/java/org/zwobble/couscous/tests/util/ExtraAsserts.java new file mode 100644 index 0000000..8b1c16c --- /dev/null +++ b/src/test/java/org/zwobble/couscous/tests/util/ExtraAsserts.java @@ -0,0 +1,21 @@ +package org.zwobble.couscous.tests.util; + +public class ExtraAsserts { + public interface Action { + void run(); + } + + @SuppressWarnings("unchecked") + public static T assertThrows(Class exceptionType, Action action) { + try { + action.run(); + throw new AssertionError("Expected exception"); + } catch (Exception exception) { + if (exceptionType.isInstance(exception)) { + return (T)exception; + } else { + throw exception; + } + } + } +} -- cgit v1.2.3