diff options
| author | Michael Williamson <mike@zwobble.org> | 2015-10-06 16:24:37 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2015-10-06 16:24:37 +0100 |
| commit | e6f463257720bee91a1b4954d97684dbb3398cbf (patch) | |
| tree | c8d1f04dfe8928d33361108aefee7e7572dae1c7 /src/main/java/org/zwobble/couscous/interpreter | |
| parent | 9685c8fb7fa14d5b75d828f8443dd948715ac4af (diff) | |
Add support for constructors and fields
Diffstat (limited to 'src/main/java/org/zwobble/couscous/interpreter')
12 files changed, 165 insertions, 27 deletions
diff --git a/src/main/java/org/zwobble/couscous/interpreter/Environment.java b/src/main/java/org/zwobble/couscous/interpreter/Environment.java index 6da54ce..0bbc808 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Environment.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Environment.java @@ -19,16 +19,25 @@ import lombok.val; public class Environment { private final Project project; + private final Optional<InterpreterValue> thisValue; private final Map<Integer, VariableEntry> stackFrame; - public Environment(Project project, Map<VariableDeclaration, Optional<InterpreterValue>> stackFrame) { + public Environment( + Project project, + Optional<InterpreterValue> thisValue, + Map<VariableDeclaration, Optional<InterpreterValue>> stackFrame) { this.project = project; + this.thisValue = thisValue; this.stackFrame = stackFrame.entrySet() .stream() .collect(toMap( entry -> entry.getKey().getId(), entry -> VariableEntry.of(entry.getKey().getType(), entry.getValue()))); } + + public Optional<InterpreterValue> getThis() { + return thisValue; + } public InterpreterValue get(int variableId) { checkVariableIsInScope(variableId); @@ -51,8 +60,8 @@ public class Environment { return project.findClass(className); } - public Environment withStackFrame(Map<VariableDeclaration, Optional<InterpreterValue>> stackFrame) { - return new Environment(project, stackFrame); + public Environment withStackFrame(Optional<InterpreterValue> thisValue, Map<VariableDeclaration, Optional<InterpreterValue>> stackFrame) { + return new Environment(project, thisValue, stackFrame); } private void checkVariableIsInScope(int variableId) { diff --git a/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java b/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java index ee67da0..18d7109 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java @@ -6,15 +6,20 @@ import java.util.stream.Collectors; import org.zwobble.couscous.ast.AssignmentNode; import org.zwobble.couscous.ast.ConstructorCallNode; import org.zwobble.couscous.ast.ExpressionNode; +import org.zwobble.couscous.ast.FieldAccessNode; import org.zwobble.couscous.ast.LiteralNode; import org.zwobble.couscous.ast.MethodCallNode; import org.zwobble.couscous.ast.StaticMethodCallNode; import org.zwobble.couscous.ast.TernaryConditionalNode; +import org.zwobble.couscous.ast.ThisReferenceNode; import org.zwobble.couscous.ast.VariableReferenceNode; +import org.zwobble.couscous.ast.visitors.AssignableExpressionNodeVisitor; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; import org.zwobble.couscous.interpreter.values.BooleanInterpreterValue; import org.zwobble.couscous.interpreter.values.InterpreterValue; import org.zwobble.couscous.interpreter.values.InterpreterValues; +import org.zwobble.couscous.interpreter.values.ObjectInterpreterValue; +import org.zwobble.couscous.util.Casts; import lombok.val; @@ -44,9 +49,28 @@ public class Evaluator implements ExpressionNodeMapper<InterpreterValue> { } @Override + public InterpreterValue visit(ThisReferenceNode reference) { + return environment.getThis().get(); + } + + @Override public InterpreterValue visit(AssignmentNode assignment) { val value = eval(assignment.getValue()); - environment.put(assignment.getTarget().getReferentId(), value); + assignment.getTarget().accept(new AssignableExpressionNodeVisitor() { + @Override + public void visit(FieldAccessNode fieldAccess) { + val maybeLeft = Casts.tryCast( + ObjectInterpreterValue.class, + eval(fieldAccess.getLeft())); + val left = maybeLeft.orElseThrow(() -> new UnsupportedOperationException()); + left.setField(fieldAccess.getFieldName(), value); + } + + @Override + public void visit(VariableReferenceNode reference) { + environment.put(reference.getReferentId(), value); + } + }); return value; } @@ -84,6 +108,14 @@ public class Evaluator implements ExpressionNodeMapper<InterpreterValue> { return clazz.callConstructor(environment, arguments); } + @Override + public InterpreterValue visit(FieldAccessNode fieldAccess) { + // TODO: handle unbound fields + // TODO: handle non-existent fields + val left = eval(fieldAccess.getLeft()); + return left.getField(fieldAccess.getFieldName()); + } + private List<InterpreterValue> evalArguments(List<ExpressionNode> arguments) { return arguments .stream() diff --git a/src/main/java/org/zwobble/couscous/interpreter/Executor.java b/src/main/java/org/zwobble/couscous/interpreter/Executor.java index e9304cb..5c26244 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Executor.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Executor.java @@ -4,9 +4,9 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; +import org.zwobble.couscous.ast.CallableNode; 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; @@ -19,8 +19,13 @@ import static org.zwobble.couscous.interpreter.Evaluator.eval; import lombok.val; public class Executor implements StatementNodeMapper<Optional<InterpreterValue>> { - public static InterpreterValue callMethod(Environment environment, MethodNode method, PositionalArguments arguments) { - val innerEnvironment = buildEnvironment(environment, method, arguments); + public static InterpreterValue callMethod( + Environment environment, + CallableNode method, + Optional<InterpreterValue> thisValue, + PositionalArguments arguments) { + + val innerEnvironment = buildEnvironment(environment, method, thisValue, arguments); for (val statement : method.getBody()) { val result = exec(innerEnvironment, statement); @@ -32,9 +37,10 @@ public class Executor implements StatementNodeMapper<Optional<InterpreterValue>> } private static Environment buildEnvironment( - final Environment environment, - final MethodNode method, - PositionalArguments arguments) { + final Environment environment, + final CallableNode method, + Optional<InterpreterValue> thisValue, + PositionalArguments arguments) { val stackFrame = new StackFrameBuilder(); for (int index = 0; index < method.getArguments().size(); index++) { @@ -44,7 +50,7 @@ public class Executor implements StatementNodeMapper<Optional<InterpreterValue>> findDeclarations(method.getBody()).forEach(declaration -> stackFrame.declare(declaration)); - return environment.withStackFrame(stackFrame.build()); + return environment.withStackFrame(thisValue, stackFrame.build()); } private static Stream<VariableNode> findDeclarations(List<StatementNode> body) { diff --git a/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java b/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java index 38ea2ac..88e9d0e 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Interpreter.java @@ -1,6 +1,7 @@ package org.zwobble.couscous.interpreter; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.zwobble.couscous.Project; @@ -28,7 +29,7 @@ public class Interpreter { .stream() .<ExpressionNode>map(argument -> literal(argument.toPrimitiveValue().get())) .collect(Collectors.toList()); - return eval(new Environment(project, ImmutableMap.of()), + return eval(new Environment(project, Optional.empty(), ImmutableMap.of()), staticMethodCall(className, methodName, argumentExpressions)); } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/NoSuchField.java b/src/main/java/org/zwobble/couscous/interpreter/NoSuchField.java new file mode 100644 index 0000000..31feaab --- /dev/null +++ b/src/main/java/org/zwobble/couscous/interpreter/NoSuchField.java @@ -0,0 +1,12 @@ +package org.zwobble.couscous.interpreter; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(callSuper=false) +public class NoSuchField extends InterpreterException { + private static final long serialVersionUID = 1L; + + String fieldName; +} diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/BooleanInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/BooleanInterpreterValue.java index b98eb20..06e8bee 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/BooleanInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/BooleanInterpreterValue.java @@ -2,6 +2,7 @@ package org.zwobble.couscous.interpreter.values; import java.util.Optional; +import org.zwobble.couscous.interpreter.NoSuchField; import org.zwobble.couscous.values.PrimitiveValue; import org.zwobble.couscous.values.PrimitiveValues; @@ -30,4 +31,9 @@ public class BooleanInterpreterValue implements InterpreterValue { public Optional<PrimitiveValue> toPrimitiveValue() { return Optional.of(PrimitiveValues.value(value)); } + + @Override + public InterpreterValue getField(String fieldName) { + throw new NoSuchField(fieldName); + } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/ConcreteType.java b/src/main/java/org/zwobble/couscous/interpreter/values/ConcreteType.java index 70ba96a..bd63d7f 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/ConcreteType.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/ConcreteType.java @@ -2,10 +2,12 @@ package org.zwobble.couscous.interpreter.values; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.BiFunction; import java.util.stream.Collectors; import org.zwobble.couscous.ast.ClassNode; +import org.zwobble.couscous.ast.FormalArgumentNode; import org.zwobble.couscous.ast.TypeName; import org.zwobble.couscous.interpreter.PositionalArguments; import org.zwobble.couscous.interpreter.Environment; @@ -15,6 +17,7 @@ import org.zwobble.couscous.interpreter.UnexpectedValueType; import org.zwobble.couscous.interpreter.WrongNumberOfArguments; import org.zwobble.couscous.util.Casts; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import static java.util.stream.Collectors.toMap; @@ -57,22 +60,38 @@ public class ConcreteType { } public ConcreteType build() { - return new ConcreteType(name, methods.build(), staticMethods.build()); + return new ConcreteType( + name, + new MethodValue( + ImmutableList.of(), + (environment, arguments) -> InterpreterValues.UNIT), + methods.build(), + staticMethods.build()); } } public static ConcreteType fromNode(ClassNode classNode) { + val constructor = new MethodValue( + getArgumentTypes(classNode.getConstructor().getArguments()), + (environment, arguments) -> Executor.callMethod( + environment, + classNode.getConstructor(), + Optional.of(arguments.getReceiver()), + arguments.getPositionalArguments())); + val methods = classNode.getMethods().stream() .filter(method -> !method.isStatic()) .collect(toMap( method -> method.getName(), method -> { - List<TypeName> argumentTypes = method.getArguments() - .stream() - .map(arg -> arg.getType()) - .collect(Collectors.toList()); + val argumentTypes = getArgumentTypes(method.getArguments()); + return new MethodValue(argumentTypes, (environment, arguments) -> { - return Executor.callMethod(environment, method, arguments.getPositionalArguments()); + return Executor.callMethod( + environment, + method, + Optional.of(arguments.getReceiver()), + arguments.getPositionalArguments()); }); })); @@ -82,29 +101,42 @@ public class ConcreteType { .collect(toMap( method -> method.getName(), method -> { - List<TypeName> argumentTypes = method.getArguments() - .stream() - .map(arg -> arg.getType()) - .collect(Collectors.toList()); + val argumentTypes = getArgumentTypes(method.getArguments()); + return new StaticMethodValue(argumentTypes, (environment, arguments) -> { - return Executor.callMethod(environment, method, arguments); + return Executor.callMethod( + environment, + method, + Optional.empty(), + arguments); }); })); return new ConcreteType( classNode.getName(), + constructor, methods, staticMethods); } + + private static List<TypeName> getArgumentTypes(List<FormalArgumentNode> arguments) { + return arguments + .stream() + .map(arg -> arg.getType()) + .collect(Collectors.toList()); + } - private TypeName name; - private Map<String, MethodValue> methods; - private Map<String, StaticMethodValue> staticMethods; + private final TypeName name; + private final MethodValue constructor; + private final Map<String, MethodValue> methods; + private final Map<String, StaticMethodValue> staticMethods; public ConcreteType( TypeName name, + MethodValue constructor, Map<String, MethodValue> methods, Map<String, StaticMethodValue> staticMethods) { this.name = name; + this.constructor = constructor; this.methods = methods; this.staticMethods = staticMethods; } @@ -136,7 +168,11 @@ public class ConcreteType { public InterpreterValue callConstructor( Environment environment, List<InterpreterValue> arguments) { - return new ObjectInterpreterValue(this); + val object = new ObjectInterpreterValue(this); + constructor.apply( + environment, + MethodCallArguments.of(object, new PositionalArguments(arguments))); + return object; } private static <T extends Callable> T findMethod(Map<String, T> methods, String methodName, List<InterpreterValue> arguments) { diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/IntegerInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/IntegerInterpreterValue.java index 92ba306..1effe6a 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/IntegerInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/IntegerInterpreterValue.java @@ -2,6 +2,7 @@ package org.zwobble.couscous.interpreter.values; import java.util.Optional; +import org.zwobble.couscous.interpreter.NoSuchField; import org.zwobble.couscous.values.IntegerValue; import org.zwobble.couscous.values.PrimitiveValue; import org.zwobble.couscous.values.PrimitiveValues; @@ -34,4 +35,9 @@ public class IntegerInterpreterValue implements InterpreterValue { public Optional<PrimitiveValue> toPrimitiveValue() { return Optional.of(PrimitiveValues.value(value)); } + + @Override + public InterpreterValue getField(String fieldName) { + throw new NoSuchField(fieldName); + } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/InterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/InterpreterValue.java index 6c3c2ec..78d27d2 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/InterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/InterpreterValue.java @@ -7,4 +7,5 @@ import org.zwobble.couscous.values.PrimitiveValue; public interface InterpreterValue { ConcreteType getType(); Optional<PrimitiveValue> toPrimitiveValue(); + InterpreterValue getField(String fieldName); } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/ObjectInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/ObjectInterpreterValue.java index 4383353..acb6ef7 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/ObjectInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/ObjectInterpreterValue.java @@ -1,14 +1,18 @@ package org.zwobble.couscous.interpreter.values; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import org.zwobble.couscous.values.PrimitiveValue; public class ObjectInterpreterValue implements InterpreterValue { private final ConcreteType type; + private final Map<String, InterpreterValue> fields; public ObjectInterpreterValue(ConcreteType type) { this.type = type; + this.fields = new HashMap<>(); } @Override @@ -20,4 +24,17 @@ public class ObjectInterpreterValue implements InterpreterValue { public Optional<PrimitiveValue> toPrimitiveValue() { return Optional.empty(); } + + @Override + public InterpreterValue getField(String fieldName) { + if (fields.containsKey(fieldName)) { + return fields.get(fieldName); + } else { + throw new UnsupportedOperationException(); + } + } + + public void setField(String fieldName, InterpreterValue value) { + fields.put(fieldName, value); + } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/StringInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/StringInterpreterValue.java index aa3432a..a4dde19 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/StringInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/StringInterpreterValue.java @@ -2,6 +2,7 @@ package org.zwobble.couscous.interpreter.values; import java.util.Optional; +import org.zwobble.couscous.interpreter.NoSuchField; import org.zwobble.couscous.values.IntegerValue; import org.zwobble.couscous.values.PrimitiveValue; import org.zwobble.couscous.values.PrimitiveValues; @@ -39,4 +40,9 @@ public class StringInterpreterValue implements InterpreterValue { public Optional<PrimitiveValue> toPrimitiveValue() { return Optional.of(PrimitiveValues.value(value)); } + + @Override + public InterpreterValue getField(String fieldName) { + throw new NoSuchField(fieldName); + } } diff --git a/src/main/java/org/zwobble/couscous/interpreter/values/UnitInterpreterValue.java b/src/main/java/org/zwobble/couscous/interpreter/values/UnitInterpreterValue.java index 253932f..e221fee 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/values/UnitInterpreterValue.java +++ b/src/main/java/org/zwobble/couscous/interpreter/values/UnitInterpreterValue.java @@ -2,6 +2,7 @@ package org.zwobble.couscous.interpreter.values; import java.util.Optional; +import org.zwobble.couscous.interpreter.NoSuchField; import org.zwobble.couscous.values.PrimitiveValue; import org.zwobble.couscous.values.PrimitiveValues; @@ -26,4 +27,9 @@ public class UnitInterpreterValue implements InterpreterValue { public Optional<PrimitiveValue> toPrimitiveValue() { return Optional.of(PrimitiveValues.UNIT); } + + @Override + public InterpreterValue getField(String fieldName) { + throw new NoSuchField(fieldName); + } } |
