From 87f73abf5e7dcec6a9f5ebd213f4a9eb0a5d45db Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 4 Oct 2015 17:26:27 +0100 Subject: Error if trying to assign to variable not in scope --- .../zwobble/couscous/interpreter/Environment.java | 20 +++++++---- .../org/zwobble/couscous/interpreter/Executor.java | 40 +++++++++++++++++----- .../couscous/interpreter/StackFrameBuilder.java | 29 ++++++++++++++++ .../couscous/interpreter/VariableNotInScope.java | 12 +++++++ .../couscous/tests/interpreter/EvaluatorTests.java | 3 +- .../tests/interpreter/InterpreterTests.java | 17 ++++++++- 6 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/zwobble/couscous/interpreter/StackFrameBuilder.java create mode 100644 src/main/java/org/zwobble/couscous/interpreter/VariableNotInScope.java diff --git a/src/main/java/org/zwobble/couscous/interpreter/Environment.java b/src/main/java/org/zwobble/couscous/interpreter/Environment.java index ced87c8..5985a94 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Environment.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Environment.java @@ -2,6 +2,7 @@ package org.zwobble.couscous.interpreter; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import org.zwobble.couscous.Project; import org.zwobble.couscous.ast.VariableNode; @@ -9,24 +10,25 @@ import org.zwobble.couscous.values.ConcreteType; import org.zwobble.couscous.values.InterpreterValue; public class Environment { - private final Map stackFrame; + private final Map> stackFrame; private final Project project; - public Environment(Project project, Map stackFrame) { + public Environment(Project project, Map> stackFrame) { this.project = project; this.stackFrame = new HashMap<>(stackFrame); } public InterpreterValue get(int referentId) { - return stackFrame.get(referentId); + return stackFrame.get(referentId).get(); } public void put(int variableId, InterpreterValue value) { - stackFrame.put(variableId, value); + checkVariable(variableId); + stackFrame.put(variableId, Optional.of(value)); } public void put(VariableNode variable, InterpreterValue value) { - stackFrame.put(variable.getId(), value); + put(variable.getId(), value); } public ConcreteType findClass(String className) { @@ -34,7 +36,13 @@ public class Environment { return project.findClass(className); } - public Environment withStackFrame(Map stackFrame) { + public Environment withStackFrame(Map> stackFrame) { return new Environment(project, stackFrame); } + + private void checkVariable(int variableId) { + if (!stackFrame.containsKey(variableId)) { + throw new VariableNotInScope(variableId); + } + } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/Executor.java b/src/main/java/org/zwobble/couscous/interpreter/Executor.java index 49eca3f..95eddad 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Executor.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Executor.java @@ -1,18 +1,19 @@ package org.zwobble.couscous.interpreter; +import java.util.List; import java.util.Optional; -import java.util.stream.IntStream; +import java.util.stream.Stream; import org.zwobble.couscous.ast.ExpressionStatementNode; import org.zwobble.couscous.ast.LocalVariableDeclarationNode; import org.zwobble.couscous.ast.MethodNode; import org.zwobble.couscous.ast.ReturnNode; import org.zwobble.couscous.ast.StatementNode; +import org.zwobble.couscous.ast.VariableNode; import org.zwobble.couscous.ast.visitors.StatementNodeVisitor; import org.zwobble.couscous.values.InterpreterValue; import org.zwobble.couscous.values.UnitValue; -import static java.util.stream.Collectors.toMap; import static org.zwobble.couscous.interpreter.Evaluator.eval; import lombok.val; @@ -35,14 +36,37 @@ public class Executor implements StatementNodeVisitor final MethodNode method, Arguments arguments) { - val stackFrame = IntStream.range(0, method.getArguments().size()) - .boxed() - .collect(toMap( - index -> method.getArguments().get(index).getId(), - index -> arguments.get(index))); - return environment.withStackFrame(stackFrame); + val stackFrame = new StackFrameBuilder(); + for (int index = 0; index < method.getArguments().size(); index++) { + stackFrame.declare(method.getArguments().get(index), arguments.get(index)); + } + + findDeclarations(method.getBody()).forEach(declaration -> + stackFrame.declare(declaration)); + + return environment.withStackFrame(stackFrame.build()); } + private static Stream findDeclarations(List body) { + return body.stream() + .flatMap(statement -> statement.accept(new StatementNodeVisitor>() { + @Override + public Stream visit(ReturnNode returnNode) { + return Stream.empty(); + } + + @Override + public Stream visit(ExpressionStatementNode expressionStatement) { + return Stream.empty(); + } + + @Override + public Stream visit(LocalVariableDeclarationNode localVariableDeclaration) { + return Stream.of(localVariableDeclaration); + } + })); + } + public static Optional exec(Environment environment, StatementNode statement) { return statement.accept(new Executor(environment)); } diff --git a/src/main/java/org/zwobble/couscous/interpreter/StackFrameBuilder.java b/src/main/java/org/zwobble/couscous/interpreter/StackFrameBuilder.java new file mode 100644 index 0000000..ac425cd --- /dev/null +++ b/src/main/java/org/zwobble/couscous/interpreter/StackFrameBuilder.java @@ -0,0 +1,29 @@ +package org.zwobble.couscous.interpreter; + +import java.util.Map; + +import org.zwobble.couscous.ast.VariableNode; +import org.zwobble.couscous.values.InterpreterValue; + +import com.google.common.collect.ImmutableMap; + +import java.util.Optional; + +public class StackFrameBuilder { + private final ImmutableMap.Builder> variables + = ImmutableMap.builder(); + + public StackFrameBuilder declare(VariableNode variable, InterpreterValue value) { + variables.put(variable.getId(), Optional.of(value)); + return this; + } + + public StackFrameBuilder declare(VariableNode variable) { + variables.put(variable.getId(), Optional.empty()); + return this; + } + + public Map> build() { + return variables.build(); + } +} diff --git a/src/main/java/org/zwobble/couscous/interpreter/VariableNotInScope.java b/src/main/java/org/zwobble/couscous/interpreter/VariableNotInScope.java new file mode 100644 index 0000000..7760326 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/interpreter/VariableNotInScope.java @@ -0,0 +1,12 @@ +package org.zwobble.couscous.interpreter; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper=false) +public class VariableNotInScope extends InterpreterException { + private static final long serialVersionUID = 1L; + + int variableId; +} diff --git a/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java b/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java index 7656e89..ff74d1b 100644 --- a/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java +++ b/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java @@ -11,6 +11,7 @@ import org.zwobble.couscous.ast.TernaryConditionalNode; import org.zwobble.couscous.interpreter.ConditionMustBeBoolean; import org.zwobble.couscous.interpreter.Environment; import org.zwobble.couscous.interpreter.NoSuchMethod; +import org.zwobble.couscous.interpreter.StackFrameBuilder; import org.zwobble.couscous.interpreter.UnexpectedValueType; import org.zwobble.couscous.interpreter.WrongNumberOfArguments; import org.zwobble.couscous.values.ConcreteType; @@ -35,7 +36,7 @@ public class EvaluatorTests { val arg = new FormalArgumentNode(42, StringValue.REF, "x"); val environment = new Environment( new MapBackedProject(ImmutableMap.of()), - ImmutableMap.of(arg.getId(), new StringValue("[initial value]"))); + new StackFrameBuilder().declare(arg, new StringValue("[initial value]")).build()); val result = eval(environment, assign(arg, literal("[updated value]"))); assertEquals(new StringValue("[updated value]"), result); } diff --git a/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java b/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java index d461221..66a1f8b 100644 --- a/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java +++ b/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java @@ -10,6 +10,7 @@ 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.VariableNotInScope; import org.zwobble.couscous.interpreter.WrongNumberOfArguments; import org.zwobble.couscous.values.ConcreteType; import org.zwobble.couscous.values.InterpreterValue; @@ -19,6 +20,7 @@ import com.google.common.collect.ImmutableMap; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static org.zwobble.couscous.ast.LiteralNode.literal; import static org.zwobble.couscous.ast.LocalVariableDeclarationNode.localVariableDeclaration; import static org.zwobble.couscous.ast.MethodNode.staticMethod; import static org.zwobble.couscous.ast.VariableReferenceNode.reference; @@ -91,7 +93,7 @@ public class InterpreterTests { } @Test - public void canDeclareVariableAndAssignValues() { + public void canDeclareVariableAndThenAssignValues() { val localVariableDeclaration = localVariableDeclaration( 42, StringValue.REF, "x", LiteralNode.literal("[initial value]")); val method = staticMethod("hello") @@ -102,6 +104,19 @@ public class InterpreterTests { assertEquals(new StringValue("[updated value]"), result); } + + @Test + public void errorIfTryingToAssignToVariableNotInScope() { + val localVariableDeclaration = localVariableDeclaration( + 42, StringValue.REF, "x", literal("")); + val method = staticMethod("hello") + .statement(new ExpressionStatementNode(new Assignment(reference(localVariableDeclaration), LiteralNode.literal("[updated value]")))); + + val exception = assertThrows(VariableNotInScope.class, + () -> runMethod(method)); + + assertEquals(new VariableNotInScope(42), exception); + } private InterpreterValue runMethod(MethodNode.MethodNodeBuilder methodBuilder, InterpreterValue... arguments) { val method = methodBuilder.build(); -- cgit v1.2.3