From 97b4f889cd2c3a692da98e681016414587ebf204 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Sun, 25 Aug 2024 17:56:34 +0800 Subject: Added maps --- .../java/lv/enes/orang/ast/ExpressionVisitor.java | 1 + .../main/java/lv/enes/orang/ast/MapExpression.java | 13 ++++++ .../main/java/lv/enes/orang/checker/Checker.java | 9 +++++ .../main/java/lv/enes/orang/evaluator/Array.java | 6 +-- .../java/lv/enes/orang/evaluator/Evaluator.java | 11 ++++++ .../java/lv/enes/orang/evaluator/OrangBoolean.java | 8 ++-- .../java/lv/enes/orang/evaluator/OrangInteger.java | 20 +++++----- .../java/lv/enes/orang/evaluator/OrangMap.java | 42 ++++++++++++++++++++ .../java/lv/enes/orang/evaluator/OrangString.java | 6 +-- .../main/java/lv/enes/orang/evaluator/Value.java | 10 ++--- grammar.bnf | 19 ++++++--- lexer/src/main/java/lv/enes/orang/lexer/Lexer.java | 2 + lexer/src/main/java/lv/enes/orang/lexer/Token.java | 2 + .../src/main/java/lv/enes/orang/parser/Parser.java | 46 ++++++++++++++++------ 14 files changed, 152 insertions(+), 43 deletions(-) create mode 100644 ast/src/main/java/lv/enes/orang/ast/MapExpression.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/OrangMap.java diff --git a/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java b/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java index 60ec3c2..410512f 100644 --- a/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java +++ b/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java @@ -17,6 +17,7 @@ public interface ExpressionVisitor { R visitIfElseExpression(IfElseExpression expr) throws E; R visitIntLiteral(IntLiteral expr) throws E; R visitLetInExpression(LetInExpression expr) throws E; + R visitMap(MapExpression expr) throws E; R visitStringLiteral(StringLiteral expr) throws E; R visitTupleExpression(TupleExpression expr) throws E; R visitUnaryExpression(UnaryExpression expr) throws E; diff --git a/ast/src/main/java/lv/enes/orang/ast/MapExpression.java b/ast/src/main/java/lv/enes/orang/ast/MapExpression.java new file mode 100644 index 0000000..c758788 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/MapExpression.java @@ -0,0 +1,13 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +import java.util.List; + +public record MapExpression(List entries) implements Expression { + public record MapPair(Expression from, Expression to) {} + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitMap(this); + } +} diff --git a/checker/src/main/java/lv/enes/orang/checker/Checker.java b/checker/src/main/java/lv/enes/orang/checker/Checker.java index 1c7303e..f035e41 100644 --- a/checker/src/main/java/lv/enes/orang/checker/Checker.java +++ b/checker/src/main/java/lv/enes/orang/checker/Checker.java @@ -140,6 +140,15 @@ public class Checker implements ExpressionVisitor, State return null; } + @Override + public Void visitMap(MapExpression expr) throws CheckerException { + for (var entry : expr.entries()) { + visit(entry.from()); + visit(entry.to()); + } + return null; + } + @Override public Checker visitProgram(Program program) throws CheckerException { var checker = this; diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Array.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Array.java index ab0d896..265bbd9 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/Array.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Array.java @@ -46,7 +46,7 @@ public record Array(List items) implements Value { newItems.addAll(rhsi); return new Array(Collections.unmodifiableList(newItems)); } else { - throw new OrangRuntimeException(STR."add not implemented for Array and \{rhs.typeName()}"); + return Value.super.add(rhs); } } @@ -58,7 +58,7 @@ public record Array(List items) implements Value { } return items.get(i); } else { - throw new OrangRuntimeException(STR."array access not implemented for Array and \{idx.typeName()}"); + return Value.super.arrayAccess(idx); } } @@ -71,7 +71,7 @@ public record Array(List items) implements Value { } return new Array(Collections.unmodifiableList(newItems)); } else { - throw new OrangRuntimeException(STR."multiply not implemented for Array and \{rhs.typeName()}"); + return Value.super.multiply(rhs); } } } diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java index 69c03e8..8fd61e8 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java @@ -119,6 +119,17 @@ public record Evaluator(Map scope, Value lastResult) implements E return newEvaluator.visit(expr.body()); } + @Override + public Value visitMap(MapExpression expr) throws OrangRuntimeException { + var map = new HashMap(); + for (var entry : expr.entries()) { + var from = visit(entry.from()); + var to = visit(entry.to()); + map.put(from, to); + } + return new OrangMap(Collections.unmodifiableMap(map)); + } + @Override public Evaluator visitProgram(Program prog) throws OrangRuntimeException { var evaluator = this; diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/OrangBoolean.java b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangBoolean.java index 3564a9c..46c887c 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/OrangBoolean.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangBoolean.java @@ -5,8 +5,8 @@ import lv.enes.orang.core.OrangRuntimeException; @EqualsAndHashCode public final class OrangBoolean implements Value { - public final static OrangBoolean TRUE = new OrangBoolean(true); - public final static OrangBoolean FALSE = new OrangBoolean(false); + public static final OrangBoolean TRUE = new OrangBoolean(true); + public static final OrangBoolean FALSE = new OrangBoolean(false); private final boolean value; @@ -45,11 +45,11 @@ public final class OrangBoolean implements Value { } @Override - public OrangBoolean or(Value rhs) throws OrangRuntimeException { + public Value or(Value rhs) throws OrangRuntimeException { if (rhs instanceof OrangBoolean rhsb) { return new OrangBoolean(value || rhsb.value); } else { - throw new OrangRuntimeException(STR."or is not implemented for Boolean and \{rhs.typeName()}"); + return Value.super.or(rhs); } } } diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/OrangInteger.java b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangInteger.java index 90de0b5..872fce2 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/OrangInteger.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangInteger.java @@ -24,47 +24,47 @@ public record OrangInteger(int value) implements Value { } @Override - public OrangInteger add(Value rhs) throws OrangRuntimeException { + public Value add(Value rhs) throws OrangRuntimeException { if (rhs instanceof OrangInteger(int rhsi)) { return new OrangInteger(value + rhsi); } else { - throw new OrangRuntimeException(STR."add is not implemented for Integer and \{rhs.typeName()}"); + return Value.super.add(rhs); } } @Override - public OrangInteger divide(Value rhs) throws OrangRuntimeException { + public Value divide(Value rhs) throws OrangRuntimeException { if (rhs instanceof OrangInteger(int rhsi)) { return new OrangInteger(value / rhsi); } else { - throw new OrangRuntimeException(STR."divide is not implemented for Integer and \{rhs.typeName()}"); + return Value.super.divide(rhs); } } @Override - public OrangInteger multiply(Value rhs) throws OrangRuntimeException { + public Value multiply(Value rhs) throws OrangRuntimeException { if (rhs instanceof OrangInteger(int rhsi)) { return new OrangInteger(value * rhsi); } else { - throw new OrangRuntimeException(STR."multiply is not implemented for Integer and \{rhs.typeName()}"); + return Value.super.multiply(rhs); } } @Override - public OrangInteger subtract(Value rhs) throws OrangRuntimeException { + public Value subtract(Value rhs) throws OrangRuntimeException { if (rhs instanceof OrangInteger(int rhsi)) { return new OrangInteger(value - rhsi); } else { - throw new OrangRuntimeException(STR."subtract is not implemented for Integer and \{rhs.typeName()}"); + return Value.super.subtract(rhs); } } @Override - public OrangBoolean greaterThan(Value rhs) throws OrangRuntimeException { + public Value greaterThan(Value rhs) throws OrangRuntimeException { if (rhs instanceof OrangInteger(int rhsi)) { return OrangBoolean.of(value > rhsi); } else { - throw new OrangRuntimeException(STR."greaterThan is not implemented for Integer and \{rhs.typeName()}"); + return Value.super.greaterThan(rhs); } } } diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/OrangMap.java b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangMap.java new file mode 100644 index 0000000..27f9f35 --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangMap.java @@ -0,0 +1,42 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.core.OrangRuntimeException; + +import java.util.HashMap; +import java.util.Map; + +public record OrangMap(Map map) implements Value { + @Override + public String typeName() { + return "Map"; + } + + @Override + public String stringify() { + var sb = new StringBuilder("{ "); + for (var entry : map.entrySet()) { + sb.append(entry.getKey().stringify()).append(" -> ").append(entry.getValue().stringify()).append(", "); + } + return sb.append("}").toString(); + } + + @Override + public Value add(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangMap(var rhsm)) { + var newMap = new HashMap<>(map); + newMap.putAll(rhsm); + return new OrangMap(newMap); + } else { + return Value.super.add(rhs); + } + } + + @Override + public Value arrayAccess(Value idx) throws OrangRuntimeException { + if (map.containsKey(idx)) { + return map.get(idx); + } else { + throw new OrangRuntimeException(STR."No value associated with key \{idx.stringify()} in \{stringify()}"); + } + } +} diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/OrangString.java b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangString.java index 5c38842..fe03c1f 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/OrangString.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangString.java @@ -30,11 +30,11 @@ public record OrangString(String value) implements Value { } @Override - public OrangString add(Value rhs) throws OrangRuntimeException { + public Value add(Value rhs) throws OrangRuntimeException { if (rhs instanceof OrangString(String rhss)) { return new OrangString(value + rhss); } else { - throw new OrangRuntimeException(STR."add is not implemented for Integer and \{rhs.typeName()}"); + return Value.super.add(rhs); } } @@ -43,7 +43,7 @@ public record OrangString(String value) implements Value { if (rhs instanceof OrangInteger(var repeat)) { return new OrangString(value.repeat(Math.max(0, repeat))); } else { - throw new OrangRuntimeException(STR."multiply not implemented for Array and \{rhs.typeName()}"); + return Value.super.multiply(rhs); } } } diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Value.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Value.java index 35af35b..da44a19 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/Value.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Value.java @@ -3,7 +3,7 @@ package lv.enes.orang.evaluator; import lv.enes.orang.core.OrangRuntimeException; public sealed interface Value - permits Array, BuiltinFunction, Function, OrangBoolean, OrangInteger, OrangString, + permits Array, BuiltinFunction, Function, OrangBoolean, OrangInteger, OrangMap, OrangString, PartialBuiltinFunction, PartialFunction, Tuple, Undefined { String typeName(); String stringify(); @@ -56,19 +56,19 @@ public sealed interface Value } - default OrangBoolean greaterThan(Value rhs) throws OrangRuntimeException { + default Value greaterThan(Value rhs) throws OrangRuntimeException { throw new OrangRuntimeException(STR."greater than is not implemented for \{typeName()} and \{rhs.typeName()}"); } - default OrangBoolean greaterThanOrEqual(Value rhs) throws OrangRuntimeException { + default Value greaterThanOrEqual(Value rhs) throws OrangRuntimeException { return greaterThan(rhs).or(orangEquals(rhs)); } - default OrangBoolean lessThan(Value rhs) throws OrangRuntimeException { + default Value lessThan(Value rhs) throws OrangRuntimeException { return greaterThanOrEqual(rhs).not(); } - default OrangBoolean lessThanOrEqual(Value rhs) throws OrangRuntimeException { + default Value lessThanOrEqual(Value rhs) throws OrangRuntimeException { return greaterThan(rhs).not(); } diff --git a/grammar.bnf b/grammar.bnf index 3dcb1c6..d798f9d 100644 --- a/grammar.bnf +++ b/grammar.bnf @@ -3,10 +3,13 @@ program ::= (statement ';'?)*; statement ::= definition; definition ::= 'def' def-spec '=' expression - | 'def' '_' '=' expression; + | 'def' '_' '=' expression + ; + def-spec ::= IDENTIFIER arg-spec*; arg-spec ::= '(' arg-spec ')' | IDENTIFIER | arg-spec-tuple | '_'; arg-spec-tuple ::= '(' ')' | '(' arg-spec ',' arg-spec (',' arg-spec)* ','? ')'; + expression ::= binary-expression | unary-expression; unary-expression ::= unop+ simple-expression; binary-expression ::= call-expression (binop call-expression)*; @@ -21,23 +24,27 @@ simple-expression ::= '(' expression ')' | IDENTIFIER | STRING | array + | map | tuple + | do-expression + | fn-expression | if-else-expression | let-in-expression - | fn-expression - | do-expression; + ; array ::= '[' ']' | '[' expression (',' expression)* ','? ']'; +map ::= '{' '}' | '{' expression '->' expression (',' expression '->' expression) ','? '}'; // Note that tuples always have either zero or at least two elements // A tuple with only one element is just '(' expression ')' -- a parenthese-wrapped expression // Also '(' expression ',' ')' is illegal tuple ::= '(' ')' | '(' expression ',' expression (',' expression)* ','? ')'; +do-expression ::= 'do' expression (';' expression)* 'end'; +fn-expression ::= 'fn' arg-spec+ '->' expression + | 'fn' arg-spec+ do-expression + ; if-else-expression ::= 'if' expression 'then' expression 'else' expression; let-in-expression ::= 'let' def-spec '=' expression ('and' def-spec '=' expression)* 'in' expression; -fn-expression ::= 'fn' arg-spec+ '->' expression - | 'fn' arg-spec+ do-expression; -do-expression ::= 'do' expression (';' expression)* 'end'; repl-program ::= (repl-statement ';'?)* '\n'; // currently, hard-limited by the newline :sweat_smile: repl-statement ::= statement | expression; diff --git a/lexer/src/main/java/lv/enes/orang/lexer/Lexer.java b/lexer/src/main/java/lv/enes/orang/lexer/Lexer.java index 5cea0a3..f0e1ece 100644 --- a/lexer/src/main/java/lv/enes/orang/lexer/Lexer.java +++ b/lexer/src/main/java/lv/enes/orang/lexer/Lexer.java @@ -65,6 +65,8 @@ public class Lexer implements Iterator { case '*' -> new Token(Token.Type.ASTERISK, input.next()); case '!' -> new Token(Token.Type.BANG, input.next()); + case '{' -> new Token(Token.Type.BRACE_LEFT, input.next()); + case '}' -> new Token(Token.Type.BRACE_RIGHT, input.next()); case '[' -> new Token(Token.Type.BRACKET_LEFT, input.next()); case ']' -> new Token(Token.Type.BRACKET_RIGHT, input.next()); case ',' -> new Token(Token.Type.COMMA, input.next()); diff --git a/lexer/src/main/java/lv/enes/orang/lexer/Token.java b/lexer/src/main/java/lv/enes/orang/lexer/Token.java index 65e0f1d..d00aecd 100644 --- a/lexer/src/main/java/lv/enes/orang/lexer/Token.java +++ b/lexer/src/main/java/lv/enes/orang/lexer/Token.java @@ -42,6 +42,8 @@ public record Token(Type type, String literal) { // Special chars ASTERISK, BANG, + BRACE_LEFT, + BRACE_RIGHT, BRACKET_LEFT, BRACKET_RIGHT, COMMA, diff --git a/parser/src/main/java/lv/enes/orang/parser/Parser.java b/parser/src/main/java/lv/enes/orang/parser/Parser.java index 3d1d42b..55da6fb 100644 --- a/parser/src/main/java/lv/enes/orang/parser/Parser.java +++ b/parser/src/main/java/lv/enes/orang/parser/Parser.java @@ -116,16 +116,15 @@ public class Parser { private ArrayExpression parseArray() throws ParserException { consumeToken(Token.Type.BRACKET_LEFT); - if (maybeConsumeToken(Token.Type.BRACKET_RIGHT)) { - return new ArrayExpression(List.of()); - } - var items = new ArrayList(); - do { + while (!maybeConsumeToken(Token.Type.BRACKET_RIGHT)) { items.add(parseExpression()); - } while (maybeConsumeToken(Token.Type.COMMA)); - consumeToken(Token.Type.BRACKET_RIGHT); - + if (!maybeConsumeToken(Token.Type.COMMA)) { + consumeToken(Token.Type.BRACKET_RIGHT); + break; + } + } + items.trimToSize(); return new ArrayExpression(Collections.unmodifiableList(items)); } @@ -298,6 +297,24 @@ public class Parser { return new LetInExpression(Collections.unmodifiableList(bindings), body); } + private MapExpression parseMap() throws ParserException { + consumeToken(Token.Type.BRACE_LEFT); + var entries = new ArrayList(); + while (!maybeConsumeToken(Token.Type.BRACE_RIGHT)) { + var from = parseExpression(); + consumeToken(Token.Type.MINUS_GREATER); + var to = parseExpression(); + entries.add(new MapExpression.MapPair(from, to)); + + if (!maybeConsumeToken(Token.Type.COMMA)) { + consumeToken(Token.Type.BRACE_RIGHT); + break; + } + } + entries.trimToSize(); + return new MapExpression(Collections.unmodifiableList(entries)); + } + private Expression parseMemberAccessExpression() throws ParserException { var expr = parseSimpleExpression(); while (maybeConsumeToken(Token.Type.PERIOD_BRACKET_LEFT)) { @@ -335,17 +352,19 @@ public class Parser { case IDENTIFIER -> new VariableExpression(input.next().literal()); case STRING -> parseString(); case BRACKET_LEFT -> parseArray(); + case BRACE_LEFT -> parseMap(); + case DO -> parseDoExpression(); + case FN -> parseFnExpression(); case IF -> parseIfElseExpression(); case LET -> parseLetInExpression(); - case FN -> parseFnExpression(); - case DO -> parseDoExpression(); default -> throw new ParserException(STR."Unexpected token \{input.peek()}"); }; } private boolean couldStartSimpleExpression(Token.Type type) { return switch (type) { - case PAREN_LEFT, TRUE, FALSE, INTEGER, IDENTIFIER, STRING, BRACKET_LEFT, IF, LET, FN, DO -> true; + case PAREN_LEFT, TRUE, FALSE, INTEGER, IDENTIFIER, STRING, BRACKET_LEFT, BRACE_LEFT, DO, FN, IF, LET + -> true; default -> false; }; } @@ -382,7 +401,10 @@ public class Parser { exprs.add(first); do { exprs.add(parseExpression()); - maybeConsumeToken(Token.Type.COMMA); + if (!maybeConsumeToken(Token.Type.COMMA)) { + consumeToken(Token.Type.PAREN_RIGHT); + break; + } } while (!maybeConsumeToken(Token.Type.PAREN_RIGHT)); exprs.trimToSize(); return new TupleExpression(exprs); -- cgit v1.2.3