diff --git a/app/src/main/java/com/annimon/hotarufx/exceptions/TypeException.java b/app/src/main/java/com/annimon/hotarufx/exceptions/TypeException.java new file mode 100644 index 0000000..45b76e2 --- /dev/null +++ b/app/src/main/java/com/annimon/hotarufx/exceptions/TypeException.java @@ -0,0 +1,8 @@ +package com.annimon.hotarufx.exceptions; + +public class TypeException extends HotaruRuntimeException { + + public TypeException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/annimon/hotarufx/lib/MapValue.java b/app/src/main/java/com/annimon/hotarufx/lib/MapValue.java new file mode 100644 index 0000000..fb79d17 --- /dev/null +++ b/app/src/main/java/com/annimon/hotarufx/lib/MapValue.java @@ -0,0 +1,98 @@ +package com.annimon.hotarufx.lib; + +import com.annimon.hotarufx.exceptions.TypeException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +public class MapValue implements Value, Iterable> { + + public static final MapValue EMPTY = new MapValue(1); + + public static MapValue merge(MapValue map1, MapValue map2) { + final MapValue result = new MapValue(map1.size() + map2.size()); + result.map.putAll(map1.map); + result.map.putAll(map2.map); + return result; + } + + private final Map map; + + public MapValue(int size) { + this.map = new HashMap<>(size); + } + + public MapValue(Map map) { + this.map = map; + } + + @Override + public int type() { + return Types.MAP; + } + + public int size() { + return map.size(); + } + + public Map getMap() { + return map; + } + + @Override + public Object raw() { + return map; + } + + @Override + public int asInt() { + throw new TypeException("Cannot cast map to integer"); + } + + @Override + public double asNumber() { + throw new TypeException("Cannot cast map to number"); + } + + @Override + public String asString() { + return map.toString(); + } + + @Override + public Iterator> iterator() { + return map.entrySet().iterator(); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 37 * hash + Objects.hashCode(this.map); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) + return false; + final MapValue other = (MapValue) obj; + return Objects.equals(this.map, other.map); + } + + @Override + public int compareTo(Value o) { + if (o.type() == Types.MAP) { + final int lengthCompare = Integer.compare(size(), ((MapValue) o).size()); + if (lengthCompare != 0) return lengthCompare; + } + return asString().compareTo(o.asString()); + } + + @Override + public String toString() { + return asString(); + } +} \ 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 2e4d1ff..04650ab 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 @@ -2,11 +2,14 @@ package com.annimon.hotarufx.parser.visitors; import com.annimon.hotarufx.exceptions.VariableNotFoundException; import com.annimon.hotarufx.lib.Context; +import com.annimon.hotarufx.lib.MapValue; import com.annimon.hotarufx.lib.NumberValue; import com.annimon.hotarufx.lib.StringValue; 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.Map; import lombok.val; public class InterpreterVisitor implements ResultVisitor { @@ -38,7 +41,11 @@ public class InterpreterVisitor implements ResultVisitor { @Override public Value visit(MapNode node, Context context) { - return NumberValue.ZERO; + Map map = new HashMap<>(node.elements.size()); + for (Map.Entry entry : node.elements.entrySet()) { + map.put(entry.getKey(), entry.getValue().accept(this, context)); + } + return new MapValue(map); } @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 bef389f..b63c5f2 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 @@ -3,13 +3,17 @@ package com.annimon.hotarufx.parser.visitors; import com.annimon.hotarufx.exceptions.VariableNotFoundException; import com.annimon.hotarufx.lexer.HotaruLexer; import com.annimon.hotarufx.lib.Context; +import com.annimon.hotarufx.lib.MapValue; import com.annimon.hotarufx.lib.NumberValue; +import com.annimon.hotarufx.lib.StringValue; import com.annimon.hotarufx.lib.Value; import com.annimon.hotarufx.parser.HotaruParser; +import java.util.Map; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -44,6 +48,19 @@ class InterpreterVisitorTest { assertThat(eval("A = -1").asInt(), is(-1)); } + @Test + void testMap() { + Value value = eval("A = {x: 0, y: 1, text: 'hello'}"); + assertThat(value, instanceOf(MapValue.class)); + + Map map = ((MapValue) value).getMap(); + assertThat(map, allOf( + hasEntry("x", NumberValue.of(0)), + hasEntry("y", NumberValue.of(1)) + )); + assertThat(map, hasEntry("text", new StringValue("hello"))); + } + @Test void testRuntimeErrors() { assertThrows(VariableNotFoundException.class, () -> eval("A = B"));