diff --git a/app/src/main/java/com/annimon/hotarufx/exceptions/TypeException.java b/app/src/main/java/com/annimon/hotarufx/exceptions/TypeException.java index 45b76e2..2839504 100644 --- a/app/src/main/java/com/annimon/hotarufx/exceptions/TypeException.java +++ b/app/src/main/java/com/annimon/hotarufx/exceptions/TypeException.java @@ -1,8 +1,14 @@ package com.annimon.hotarufx.exceptions; +import com.annimon.hotarufx.lexer.SourcePosition; + public class TypeException extends HotaruRuntimeException { public TypeException(String message) { super(message); } + + public TypeException(String message, SourcePosition start, SourcePosition end) { + super(message, start, end); + } } \ No newline at end of file diff --git a/app/src/main/java/com/annimon/hotarufx/parser/visitors/InterpreterVisitor.java b/app/src/main/java/com/annimon/hotarufx/parser/visitors/InterpreterVisitor.java index 04650ab..1235a19 100644 --- a/app/src/main/java/com/annimon/hotarufx/parser/visitors/InterpreterVisitor.java +++ b/app/src/main/java/com/annimon/hotarufx/parser/visitors/InterpreterVisitor.java @@ -1,5 +1,6 @@ package com.annimon.hotarufx.parser.visitors; +import com.annimon.hotarufx.exceptions.TypeException; import com.annimon.hotarufx.exceptions.VariableNotFoundException; import com.annimon.hotarufx.lib.Context; import com.annimon.hotarufx.lib.MapValue; @@ -9,14 +10,17 @@ import com.annimon.hotarufx.lib.Types; import com.annimon.hotarufx.lib.Value; import com.annimon.hotarufx.parser.ast.*; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; import lombok.val; public class InterpreterVisitor implements ResultVisitor { @Override public Value visit(AccessNode node, Context context) { - return NumberValue.ZERO; + return node.get(this, context); } @Override @@ -88,12 +92,42 @@ public class InterpreterVisitor implements ResultVisitor { @Override public Value get(AccessNode node, Context context) { - return null; + Value container = node.root.accept(this, context); + return getContainer(node.indices, context, container) + .orElseThrow(() -> new TypeException("Map expected", node.start(), node.end())); } @Override public Value set(AccessNode node, Value value, Context context) { - return null; + Value container = node.root.accept(this, context); + int lastIndex = node.indices.size() - 1; + Supplier exceptionSupplier = () -> + new TypeException("Map expected", node.start(), node.end()); + container = getContainer(node.indices.subList(0, lastIndex), context, container) + .orElseThrow(exceptionSupplier); + switch (container.type()) { + case Types.MAP: + val key = node.indices.get(lastIndex).accept(this, context).asString(); + ((MapValue) container).getMap().put(key, value); + break; + default: + throw exceptionSupplier.get(); + } + return value; + } + + private Optional getContainer(List nodes, Context context, Value container) { + for (Node index : nodes) { + switch (container.type()) { + case Types.MAP: + val key = index.accept(this, context).asString(); + container = ((MapValue) container).getMap().get(key); + break; + default: + return Optional.empty(); + } + } + return Optional.of(container); } @Override diff --git a/app/src/test/java/com/annimon/hotarufx/parser/visitors/InterpreterVisitorTest.java b/app/src/test/java/com/annimon/hotarufx/parser/visitors/InterpreterVisitorTest.java index b63c5f2..72c6056 100644 --- a/app/src/test/java/com/annimon/hotarufx/parser/visitors/InterpreterVisitorTest.java +++ b/app/src/test/java/com/annimon/hotarufx/parser/visitors/InterpreterVisitorTest.java @@ -61,6 +61,29 @@ class InterpreterVisitorTest { assertThat(map, hasEntry("text", new StringValue("hello"))); } + @Test + void testMapAccess() { + Context context = new Context(); + eval("A = {x: 0, y: 22, z: 0, text: 'hello'}\n" + + "A.x = 20\n" + + "A.z = A.y\n" + + "A.newKey = 'newValue'", context); + Value value = context.variables().get("A"); + + assertThat(value, instanceOf(MapValue.class)); + + Map map = ((MapValue) value).getMap(); + assertThat(map, allOf( + hasEntry("x", NumberValue.of(20)), + hasEntry("y", NumberValue.of(22)), + hasEntry("z", NumberValue.of(22)) + )); + assertThat(map, allOf( + hasEntry("text", new StringValue("hello")), + hasEntry("newKey", new StringValue("newValue")) + )); + } + @Test void testRuntimeErrors() { assertThrows(VariableNotFoundException.class, () -> eval("A = B"));