aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2015-09-29 20:14:34 +0100
committerMichael Williamson <mike@zwobble.org>2015-09-29 20:14:34 +0100
commitccac9247fe4663874be8026ab9cc968ecec7157b (patch)
tree6e15ebf9eb130034c45f84372ce4d3776f401328
Initial commit
-rw-r--r--.classpath22
-rw-r--r--.gitignore1
-rw-r--r--.project23
-rw-r--r--.settings/org.eclipse.core.resources.prefs4
-rw-r--r--.settings/org.eclipse.jdt.core.prefs8
-rw-r--r--.settings/org.eclipse.m2e.core.prefs4
-rw-r--r--pom.xml37
-rw-r--r--src/main/java/org/zwobble/couscous/Project.java7
-rw-r--r--src/main/java/org/zwobble/couscous/ast/Assignment.java16
-rw-r--r--src/main/java/org/zwobble/couscous/ast/CallNode.java5
-rw-r--r--src/main/java/org/zwobble/couscous/ast/ClassNode.java14
-rw-r--r--src/main/java/org/zwobble/couscous/ast/ExpressionNode.java7
-rw-r--r--src/main/java/org/zwobble/couscous/ast/ExpressionStatementNode.java15
-rw-r--r--src/main/java/org/zwobble/couscous/ast/FormalArgumentNode.java9
-rw-r--r--src/main/java/org/zwobble/couscous/ast/LiteralNode.java26
-rw-r--r--src/main/java/org/zwobble/couscous/ast/MethodNode.java22
-rw-r--r--src/main/java/org/zwobble/couscous/ast/ReturnNode.java15
-rw-r--r--src/main/java/org/zwobble/couscous/ast/StatementNode.java7
-rw-r--r--src/main/java/org/zwobble/couscous/ast/VariableNode.java5
-rw-r--r--src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java19
-rw-r--r--src/main/java/org/zwobble/couscous/ast/visitors/ExpressionNodeVisitor.java11
-rw-r--r--src/main/java/org/zwobble/couscous/ast/visitors/StatementNodeVisitor.java9
-rw-r--r--src/main/java/org/zwobble/couscous/interpreter/Interpreter.java110
-rw-r--r--src/main/java/org/zwobble/couscous/interpreter/InterpreterException.java6
-rw-r--r--src/main/java/org/zwobble/couscous/interpreter/WrongNumberOfArguments.java13
-rw-r--r--src/main/java/org/zwobble/couscous/values/IntegerValue.java8
-rw-r--r--src/main/java/org/zwobble/couscous/values/InterpreterValue.java5
-rw-r--r--src/main/java/org/zwobble/couscous/values/StringValue.java8
-rw-r--r--src/main/java/org/zwobble/couscous/values/UnitValue.java11
-rw-r--r--src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java117
-rw-r--r--src/test/java/org/zwobble/couscous/tests/util/ExtraAsserts.java21
31 files changed, 585 insertions, 0 deletions
diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..a67b19b
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" output="target/classes" path="src/main/java">
+ <attributes>
+ <attribute name="optional" value="true"/>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="src" output="target/test-classes" path="src/test/java">
+ <attributes>
+ <attribute name="optional" value="true"/>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+ <attributes>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>couscous</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ </natures>
+</projectDescription>
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/<project>=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 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.zwobble.couscous</groupId>
+ <artifactId>couscous</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>couscous</name>
+ <url>http://maven.apache.org</url>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>1.16.6</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>18.0</version>
+ <scope>runtime</scope>
+ </dependency>
+ </dependencies>
+</project>
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> T accept(ExpressionNodeVisitor<T> 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<MethodNode> 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> T accept(ExpressionNodeVisitor<T> 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> T accept(StatementNodeVisitor<T> 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> T accept(ExpressionNodeVisitor<T> 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<FormalArgumentNode> arguments;
+ @Singular("statement")
+ List<StatementNode> 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> T accept(StatementNodeVisitor<T> 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> T accept(StatementNodeVisitor<T> 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> T accept(ExpressionNodeVisitor<T> 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> {
+ 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> {
+ 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<InterpreterValue> 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<Integer, InterpreterValue> buildStackFrame(
+ final MethodNode method,
+ List<InterpreterValue> 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<Optional<InterpreterValue>> {
+ private Evaluator evaluator;
+
+ public Executor(Evaluator evaluator) {
+ this.evaluator = evaluator;
+ }
+
+ @Override
+ public Optional<InterpreterValue> visit(ReturnNode returnNode) {
+ return Optional.of(evaluator.eval(returnNode.getValue()));
+ }
+
+ @Override
+ public Optional<InterpreterValue> visit(ExpressionStatementNode expressionStatement) {
+ evaluator.eval(expressionStatement.getExpression());
+ return Optional.empty();
+ }
+ }
+
+ private class Evaluator implements ExpressionNodeVisitor<InterpreterValue> {
+ private final Map<Integer, InterpreterValue> stackFrame;
+
+ public Evaluator(Map<Integer, InterpreterValue> 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<String, ClassNode> classes;
+
+ public MapBackedProject(Map<String, ClassNode> 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 extends Throwable> T assertThrows(Class<T> exceptionType, Action action) {
+ try {
+ action.run();
+ throw new AssertionError("Expected exception");
+ } catch (Exception exception) {
+ if (exceptionType.isInstance(exception)) {
+ return (T)exception;
+ } else {
+ throw exception;
+ }
+ }
+ }
+}