From f37d49367c1ce59ae35dda124d85f9732d0cb484 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Sat, 24 Aug 2024 03:08:23 +0800 Subject: Add tuples. --- .../java/lv/enes/orang/ast/EmptyTupleLiteral.java | 14 ++++++++++++ .../java/lv/enes/orang/ast/ExpressionVisitor.java | 3 ++- .../java/lv/enes/orang/ast/TupleExpression.java | 13 +++++++++++ .../java/lv/enes/orang/ast/VoidExpression.java | 14 ------------ .../main/java/lv/enes/orang/checker/Checker.java | 20 +++++++++++------ .../java/lv/enes/orang/evaluator/EmptyTuple.java | 21 ++++++++++++++++++ .../java/lv/enes/orang/evaluator/Evaluator.java | 24 +++++++++++++++------ .../main/java/lv/enes/orang/evaluator/Nothing.java | 21 ------------------ .../lv/enes/orang/evaluator/PartialFunction.java | 2 +- .../main/java/lv/enes/orang/evaluator/Tuple.java | 25 ++++++++++++++++++++++ .../main/java/lv/enes/orang/evaluator/Value.java | 3 ++- grammar.bnf | 6 +++++- .../src/main/java/lv/enes/orang/parser/Parser.java | 15 ++++++++++++- 13 files changed, 128 insertions(+), 53 deletions(-) create mode 100644 ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/TupleExpression.java delete mode 100644 ast/src/main/java/lv/enes/orang/ast/VoidExpression.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java delete mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java diff --git a/ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.java b/ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.java new file mode 100644 index 0000000..a96580f --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.java @@ -0,0 +1,14 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public final class EmptyTupleLiteral implements Expression { + public static final EmptyTupleLiteral INSTANCE = new EmptyTupleLiteral(); + + private EmptyTupleLiteral() {} + + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitEmptyTupleExpression(); + } +} 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 70f8a5e..c32b429 100644 --- a/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java +++ b/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java @@ -12,12 +12,13 @@ public interface ExpressionVisitor { R visitBinaryExpression(BinaryExpression expr) throws E; R visitCallExpression(CallExpression expr) throws E; R visitDoExpression(DoExpression expr) throws E; + R visitEmptyTupleExpression() throws E; R visitFnExpression(FnExpression expr) throws E; R visitIfElseExpression(IfElseExpression expr) throws E; R visitIntLiteral(IntLiteral expr) throws E; R visitLetInExpression(LetInExpression expr) throws E; R visitStringLiteral(StringLiteral expr) throws E; + R visitTupleExpression(TupleExpression expr) throws E; R visitUnaryExpression(UnaryExpression expr) throws E; R visitVariable(VariableExpression expr) throws E; - R visitVoidExpression() throws E; } diff --git a/ast/src/main/java/lv/enes/orang/ast/TupleExpression.java b/ast/src/main/java/lv/enes/orang/ast/TupleExpression.java new file mode 100644 index 0000000..9df418d --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/TupleExpression.java @@ -0,0 +1,13 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; +import lv.enes.orang.utils.NonEmptyList; + +public record TupleExpression(NonEmptyList children) implements Expression { + // assert children.size() >= 2 + + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitTupleExpression(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/VoidExpression.java b/ast/src/main/java/lv/enes/orang/ast/VoidExpression.java deleted file mode 100644 index 39d164d..0000000 --- a/ast/src/main/java/lv/enes/orang/ast/VoidExpression.java +++ /dev/null @@ -1,14 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.core.OrangException; - -public final class VoidExpression implements Expression { - public static final VoidExpression INSTANCE = new VoidExpression(); - - private VoidExpression() {} - - @Override - public R accept(ExpressionVisitor visitor) throws E { - return visitor.visitVoidExpression(); - } -} 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 e15fab9..66b7dac 100644 --- a/checker/src/main/java/lv/enes/orang/checker/Checker.java +++ b/checker/src/main/java/lv/enes/orang/checker/Checker.java @@ -64,6 +64,12 @@ public class Checker implements ExpressionVisitor, State return null; } + @Override + public Void visitEmptyTupleExpression() { + // Always ok + return null; + } + @Override public Checker visitExpression(ExpressionStatement expr) throws CheckerException { visit(expr.expr()); @@ -121,6 +127,14 @@ public class Checker implements ExpressionVisitor, State return null; } + @Override + public Void visitTupleExpression(TupleExpression expr) throws CheckerException { + for (var child : expr.children()) { + visit(child); + } + return null; + } + @Override public Void visitUnaryExpression(UnaryExpression expr) throws CheckerException { visit(expr.child()); @@ -134,10 +148,4 @@ public class Checker implements ExpressionVisitor, State } return null; } - - @Override - public Void visitVoidExpression() { - // Always ok - return null; - } } diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java b/evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java new file mode 100644 index 0000000..f8e043b --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java @@ -0,0 +1,21 @@ +package lv.enes.orang.evaluator; + +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode +public final class EmptyTuple implements Value { + public static final EmptyTuple INSTANCE = new EmptyTuple(); + + private EmptyTuple() { + } + + @Override + public String typeName() { + return "Nothing"; + } + + @Override + public String stringify() { + return "()"; + } +} 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 6925bac..f0299b4 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java @@ -2,6 +2,7 @@ package lv.enes.orang.evaluator; import lv.enes.orang.ast.*; import lv.enes.orang.core.OrangRuntimeException; +import lv.enes.orang.utils.NonEmptyList; import java.util.ArrayList; import java.util.Collections; @@ -70,6 +71,11 @@ public record Evaluator(Map scope, Value lastResult) implements E return value; } + @Override + public Value visitEmptyTupleExpression() { + return EmptyTuple.INSTANCE; + } + @Override public Evaluator visitExpression(ExpressionStatement expr) throws OrangRuntimeException { return new Evaluator(this.scope(), visit(expr.expr())); @@ -121,6 +127,11 @@ public record Evaluator(Map scope, Value lastResult) implements E return evaluator; } + @Override + public Value visitStringLiteral(StringLiteral expr) { + return new OrangString(expr.value()); + } + @Override public Value visitUnaryExpression(UnaryExpression expr) throws OrangRuntimeException { var child = visit(expr.child()); @@ -132,8 +143,12 @@ public record Evaluator(Map scope, Value lastResult) implements E } @Override - public Value visitStringLiteral(StringLiteral expr) { - return new OrangString(expr.value()); + public Value visitTupleExpression(TupleExpression expr) throws OrangRuntimeException { + var values = new ArrayList(); + for (var tailExpr : expr.children()) { + values.add(visit(tailExpr)); + } + return new Tuple(new NonEmptyList<>(values)); } @Override @@ -144,9 +159,4 @@ public record Evaluator(Map scope, Value lastResult) implements E throw new OrangRuntimeException(STR."Value named \{expr.name()} is not defined!"); } - - @Override - public Value visitVoidExpression() { - return Nothing.INSTANCE; - } } diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java deleted file mode 100644 index c971649..0000000 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java +++ /dev/null @@ -1,21 +0,0 @@ -package lv.enes.orang.evaluator; - -import lombok.EqualsAndHashCode; - -@EqualsAndHashCode -public final class Nothing implements Value { - public static final Nothing INSTANCE = new Nothing(); - - private Nothing() { - } - - @Override - public String typeName() { - return "Nothing"; - } - - @Override - public String stringify() { - return "()"; - } -} diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java b/evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java index d0125f6..7c0ca20 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java @@ -15,7 +15,7 @@ public record PartialFunction(Map scope, NonEmptyList re switch (spec.getType()) { case NAMED -> newScope.put(((ArgSpec.Named)spec).name(), param); case NOTHING -> { - if (!(param instanceof Nothing)) { + if (!(param instanceof EmptyTuple)) { throw new OrangRuntimeException(STR."Expected () as a parameter but got \{param.typeName()}"); } } diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java new file mode 100644 index 0000000..d5a1df5 --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java @@ -0,0 +1,25 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.utils.NonEmptyList; + +public record Tuple(NonEmptyList contents) implements Value { + @Override + public String typeName() { + var sb = new StringBuilder("("); + sb.append(contents.getFirst().typeName()); + for (var i = 1; i < contents.size(); i++) { + sb.append(", ").append(contents.get(i).typeName()); + } + return sb.append(")").toString(); + } + + @Override + public String stringify() { + var sb = new StringBuilder("("); + sb.append(contents.getFirst().stringify()); + for (var i = 1; i < contents.size(); i++) { + sb.append(", ").append(contents.get(i).stringify()); + } + return sb.append(")").toString(); + } +} 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 d8c8b9c..2ef5136 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,8 @@ package lv.enes.orang.evaluator; import lv.enes.orang.core.OrangRuntimeException; public sealed interface Value - permits Array, BuiltinFunction, Function, Nothing, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction, PartialFunction, Undefined { + permits Array, BuiltinFunction, EmptyTuple, Function, OrangBoolean, OrangInteger, OrangString, + PartialBuiltinFunction, PartialFunction, Tuple, Undefined { String typeName(); String stringify(); diff --git a/grammar.bnf b/grammar.bnf index a9e21cb..8715ad2 100644 --- a/grammar.bnf +++ b/grammar.bnf @@ -14,17 +14,21 @@ binop ::= '*' | '/' | '+' | '-' | '?=' | '/=' | '>' | '>=' | '<' | '<='; unop ::= '+' | '-' | '!'; simple-expression ::= '(' expression ')' | 'true' | 'false' - | '(' ')' | INTEGER | IDENTIFIER | STRING | array + | tuple | if-else-expression | let-in-expression | fn-expression | do-expression; array ::= '[' ']' | '[' 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)* ','? ')'; if-else-expression ::= 'if' expression 'then' expression 'else' expression; let-in-expression ::= 'let' def-spec '=' expression ('and' def-spec '=' expression)* 'in' expression; 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 25b57d4..0ff1f78 100644 --- a/parser/src/main/java/lv/enes/orang/parser/Parser.java +++ b/parser/src/main/java/lv/enes/orang/parser/Parser.java @@ -283,9 +283,12 @@ public class Parser { case PAREN_LEFT -> { consumeToken(Token.Type.PAREN_LEFT); if (maybeConsumeToken(Token.Type.PAREN_RIGHT)) { - yield VoidExpression.INSTANCE; + yield EmptyTupleLiteral.INSTANCE; } var expr = parseExpression(); + if (maybeConsumeToken(Token.Type.COMMA)) { + yield parseTupleExpression(expr); + } consumeToken(Token.Type.PAREN_RIGHT); yield expr; } @@ -336,6 +339,16 @@ public class Parser { return new StringLiteral(sb.toString()); } + private TupleExpression parseTupleExpression(Expression first) throws ParserException { + var exprs = new ArrayList(); + exprs.add(first); + do { + exprs.add(parseExpression()); + maybeConsumeToken(Token.Type.COMMA); + } while (!maybeConsumeToken(Token.Type.PAREN_RIGHT)); + return new TupleExpression(new NonEmptyList<>(exprs)); + } + private Expression parseUnaryExpression() throws ParserException { if (isUnaryOp(input.peek())) { var op = toUnaryOp(input.next()); -- cgit v1.2.3