diff options
| author | 2024-08-24 03:08:23 +0800 | |
|---|---|---|
| committer | 2024-08-24 03:08:23 +0800 | |
| commit | f37d49367c1ce59ae35dda124d85f9732d0cb484 (patch) | |
| tree | d7ed316a75a5a3533378ba3890b36f5d5a2f4bf1 | |
| parent | Add lombok.config. (diff) | |
| download | orang-f37d49367c1ce59ae35dda124d85f9732d0cb484.tar.gz orang-f37d49367c1ce59ae35dda124d85f9732d0cb484.tar.xz orang-f37d49367c1ce59ae35dda124d85f9732d0cb484.zip | |
Add tuples.
| -rw-r--r-- | ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.java | 14 | ||||
| -rw-r--r-- | ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java | 3 | ||||
| -rw-r--r-- | ast/src/main/java/lv/enes/orang/ast/TupleExpression.java | 13 | ||||
| -rw-r--r-- | ast/src/main/java/lv/enes/orang/ast/VoidExpression.java | 14 | ||||
| -rw-r--r-- | checker/src/main/java/lv/enes/orang/checker/Checker.java | 20 | ||||
| -rw-r--r-- | evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java (renamed from evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java) | 6 | ||||
| -rw-r--r-- | evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java | 24 | ||||
| -rw-r--r-- | evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java | 2 | ||||
| -rw-r--r-- | evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java | 25 | ||||
| -rw-r--r-- | evaluator/src/main/java/lv/enes/orang/evaluator/Value.java | 3 | ||||
| -rw-r--r-- | grammar.bnf | 6 | ||||
| -rw-r--r-- | parser/src/main/java/lv/enes/orang/parser/Parser.java | 15 |
12 files changed, 110 insertions, 35 deletions
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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.core.OrangException; | ||
| 4 | |||
| 5 | public final class EmptyTupleLiteral implements Expression { | ||
| 6 | public static final EmptyTupleLiteral INSTANCE = new EmptyTupleLiteral(); | ||
| 7 | |||
| 8 | private EmptyTupleLiteral() {} | ||
| 9 | |||
| 10 | @Override | ||
| 11 | public <R, E extends OrangException> R accept(ExpressionVisitor<R, E> visitor) throws E { | ||
| 12 | return visitor.visitEmptyTupleExpression(); | ||
| 13 | } | ||
| 14 | } | ||
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, E extends OrangException> { | |||
| 12 | R visitBinaryExpression(BinaryExpression expr) throws E; | 12 | R visitBinaryExpression(BinaryExpression expr) throws E; |
| 13 | R visitCallExpression(CallExpression expr) throws E; | 13 | R visitCallExpression(CallExpression expr) throws E; |
| 14 | R visitDoExpression(DoExpression expr) throws E; | 14 | R visitDoExpression(DoExpression expr) throws E; |
| 15 | R visitEmptyTupleExpression() throws E; | ||
| 15 | R visitFnExpression(FnExpression expr) throws E; | 16 | R visitFnExpression(FnExpression expr) throws E; |
| 16 | R visitIfElseExpression(IfElseExpression expr) throws E; | 17 | R visitIfElseExpression(IfElseExpression expr) throws E; |
| 17 | R visitIntLiteral(IntLiteral expr) throws E; | 18 | R visitIntLiteral(IntLiteral expr) throws E; |
| 18 | R visitLetInExpression(LetInExpression expr) throws E; | 19 | R visitLetInExpression(LetInExpression expr) throws E; |
| 19 | R visitStringLiteral(StringLiteral expr) throws E; | 20 | R visitStringLiteral(StringLiteral expr) throws E; |
| 21 | R visitTupleExpression(TupleExpression expr) throws E; | ||
| 20 | R visitUnaryExpression(UnaryExpression expr) throws E; | 22 | R visitUnaryExpression(UnaryExpression expr) throws E; |
| 21 | R visitVariable(VariableExpression expr) throws E; | 23 | R visitVariable(VariableExpression expr) throws E; |
| 22 | R visitVoidExpression() throws E; | ||
| 23 | } | 24 | } |
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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.core.OrangException; | ||
| 4 | import lv.enes.orang.utils.NonEmptyList; | ||
| 5 | |||
| 6 | public record TupleExpression(NonEmptyList<Expression> children) implements Expression { | ||
| 7 | // assert children.size() >= 2 | ||
| 8 | |||
| 9 | @Override | ||
| 10 | public <R, E extends OrangException> R accept(ExpressionVisitor<R, E> visitor) throws E { | ||
| 11 | return visitor.visitTupleExpression(this); | ||
| 12 | } | ||
| 13 | } | ||
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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.core.OrangException; | ||
| 4 | |||
| 5 | public final class VoidExpression implements Expression { | ||
| 6 | public static final VoidExpression INSTANCE = new VoidExpression(); | ||
| 7 | |||
| 8 | private VoidExpression() {} | ||
| 9 | |||
| 10 | @Override | ||
| 11 | public <R, E extends OrangException> R accept(ExpressionVisitor<R, E> visitor) throws E { | ||
| 12 | return visitor.visitVoidExpression(); | ||
| 13 | } | ||
| 14 | } | ||
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 | |||
| @@ -65,6 +65,12 @@ public class Checker implements ExpressionVisitor<Void, CheckerException>, State | |||
| 65 | } | 65 | } |
| 66 | 66 | ||
| 67 | @Override | 67 | @Override |
| 68 | public Void visitEmptyTupleExpression() { | ||
| 69 | // Always ok | ||
| 70 | return null; | ||
| 71 | } | ||
| 72 | |||
| 73 | @Override | ||
| 68 | public Checker visitExpression(ExpressionStatement expr) throws CheckerException { | 74 | public Checker visitExpression(ExpressionStatement expr) throws CheckerException { |
| 69 | visit(expr.expr()); | 75 | visit(expr.expr()); |
| 70 | return this; | 76 | return this; |
| @@ -122,6 +128,14 @@ public class Checker implements ExpressionVisitor<Void, CheckerException>, State | |||
| 122 | } | 128 | } |
| 123 | 129 | ||
| 124 | @Override | 130 | @Override |
| 131 | public Void visitTupleExpression(TupleExpression expr) throws CheckerException { | ||
| 132 | for (var child : expr.children()) { | ||
| 133 | visit(child); | ||
| 134 | } | ||
| 135 | return null; | ||
| 136 | } | ||
| 137 | |||
| 138 | @Override | ||
| 125 | public Void visitUnaryExpression(UnaryExpression expr) throws CheckerException { | 139 | public Void visitUnaryExpression(UnaryExpression expr) throws CheckerException { |
| 126 | visit(expr.child()); | 140 | visit(expr.child()); |
| 127 | return null; | 141 | return null; |
| @@ -134,10 +148,4 @@ public class Checker implements ExpressionVisitor<Void, CheckerException>, State | |||
| 134 | } | 148 | } |
| 135 | return null; | 149 | return null; |
| 136 | } | 150 | } |
| 137 | |||
| 138 | @Override | ||
| 139 | public Void visitVoidExpression() { | ||
| 140 | // Always ok | ||
| 141 | return null; | ||
| 142 | } | ||
| 143 | } | 151 | } |
diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java b/evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java index c971649..f8e043b 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java | |||
| @@ -3,10 +3,10 @@ package lv.enes.orang.evaluator; | |||
| 3 | import lombok.EqualsAndHashCode; | 3 | import lombok.EqualsAndHashCode; |
| 4 | 4 | ||
| 5 | @EqualsAndHashCode | 5 | @EqualsAndHashCode |
| 6 | public final class Nothing implements Value { | 6 | public final class EmptyTuple implements Value { |
| 7 | public static final Nothing INSTANCE = new Nothing(); | 7 | public static final EmptyTuple INSTANCE = new EmptyTuple(); |
| 8 | 8 | ||
| 9 | private Nothing() { | 9 | private EmptyTuple() { |
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | @Override | 12 | @Override |
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; | |||
| 2 | 2 | ||
| 3 | import lv.enes.orang.ast.*; | 3 | import lv.enes.orang.ast.*; |
| 4 | import lv.enes.orang.core.OrangRuntimeException; | 4 | import lv.enes.orang.core.OrangRuntimeException; |
| 5 | import lv.enes.orang.utils.NonEmptyList; | ||
| 5 | 6 | ||
| 6 | import java.util.ArrayList; | 7 | import java.util.ArrayList; |
| 7 | import java.util.Collections; | 8 | import java.util.Collections; |
| @@ -71,6 +72,11 @@ public record Evaluator(Map<String, Value> scope, Value lastResult) implements E | |||
| 71 | } | 72 | } |
| 72 | 73 | ||
| 73 | @Override | 74 | @Override |
| 75 | public Value visitEmptyTupleExpression() { | ||
| 76 | return EmptyTuple.INSTANCE; | ||
| 77 | } | ||
| 78 | |||
| 79 | @Override | ||
| 74 | public Evaluator visitExpression(ExpressionStatement expr) throws OrangRuntimeException { | 80 | public Evaluator visitExpression(ExpressionStatement expr) throws OrangRuntimeException { |
| 75 | return new Evaluator(this.scope(), visit(expr.expr())); | 81 | return new Evaluator(this.scope(), visit(expr.expr())); |
| 76 | } | 82 | } |
| @@ -122,6 +128,11 @@ public record Evaluator(Map<String, Value> scope, Value lastResult) implements E | |||
| 122 | } | 128 | } |
| 123 | 129 | ||
| 124 | @Override | 130 | @Override |
| 131 | public Value visitStringLiteral(StringLiteral expr) { | ||
| 132 | return new OrangString(expr.value()); | ||
| 133 | } | ||
| 134 | |||
| 135 | @Override | ||
| 125 | public Value visitUnaryExpression(UnaryExpression expr) throws OrangRuntimeException { | 136 | public Value visitUnaryExpression(UnaryExpression expr) throws OrangRuntimeException { |
| 126 | var child = visit(expr.child()); | 137 | var child = visit(expr.child()); |
| 127 | return switch (expr.operator()) { | 138 | return switch (expr.operator()) { |
| @@ -132,8 +143,12 @@ public record Evaluator(Map<String, Value> scope, Value lastResult) implements E | |||
| 132 | } | 143 | } |
| 133 | 144 | ||
| 134 | @Override | 145 | @Override |
| 135 | public Value visitStringLiteral(StringLiteral expr) { | 146 | public Value visitTupleExpression(TupleExpression expr) throws OrangRuntimeException { |
| 136 | return new OrangString(expr.value()); | 147 | var values = new ArrayList<Value>(); |
| 148 | for (var tailExpr : expr.children()) { | ||
| 149 | values.add(visit(tailExpr)); | ||
| 150 | } | ||
| 151 | return new Tuple(new NonEmptyList<>(values)); | ||
| 137 | } | 152 | } |
| 138 | 153 | ||
| 139 | @Override | 154 | @Override |
| @@ -144,9 +159,4 @@ public record Evaluator(Map<String, Value> scope, Value lastResult) implements E | |||
| 144 | 159 | ||
| 145 | throw new OrangRuntimeException(STR."Value named \{expr.name()} is not defined!"); | 160 | throw new OrangRuntimeException(STR."Value named \{expr.name()} is not defined!"); |
| 146 | } | 161 | } |
| 147 | |||
| 148 | @Override | ||
| 149 | public Value visitVoidExpression() { | ||
| 150 | return Nothing.INSTANCE; | ||
| 151 | } | ||
| 152 | } | 162 | } |
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<String, Value> scope, NonEmptyList<ArgSpec> re | |||
| 15 | switch (spec.getType()) { | 15 | switch (spec.getType()) { |
| 16 | case NAMED -> newScope.put(((ArgSpec.Named)spec).name(), param); | 16 | case NAMED -> newScope.put(((ArgSpec.Named)spec).name(), param); |
| 17 | case NOTHING -> { | 17 | case NOTHING -> { |
| 18 | if (!(param instanceof Nothing)) { | 18 | if (!(param instanceof EmptyTuple)) { |
| 19 | throw new OrangRuntimeException(STR."Expected () as a parameter but got \{param.typeName()}"); | 19 | throw new OrangRuntimeException(STR."Expected () as a parameter but got \{param.typeName()}"); |
| 20 | } | 20 | } |
| 21 | } | 21 | } |
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 @@ | |||
| 1 | package lv.enes.orang.evaluator; | ||
| 2 | |||
| 3 | import lv.enes.orang.utils.NonEmptyList; | ||
| 4 | |||
| 5 | public record Tuple(NonEmptyList<Value> contents) implements Value { | ||
| 6 | @Override | ||
| 7 | public String typeName() { | ||
| 8 | var sb = new StringBuilder("("); | ||
| 9 | sb.append(contents.getFirst().typeName()); | ||
| 10 | for (var i = 1; i < contents.size(); i++) { | ||
| 11 | sb.append(", ").append(contents.get(i).typeName()); | ||
| 12 | } | ||
| 13 | return sb.append(")").toString(); | ||
| 14 | } | ||
| 15 | |||
| 16 | @Override | ||
| 17 | public String stringify() { | ||
| 18 | var sb = new StringBuilder("("); | ||
| 19 | sb.append(contents.getFirst().stringify()); | ||
| 20 | for (var i = 1; i < contents.size(); i++) { | ||
| 21 | sb.append(", ").append(contents.get(i).stringify()); | ||
| 22 | } | ||
| 23 | return sb.append(")").toString(); | ||
| 24 | } | ||
| 25 | } | ||
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; | |||
| 3 | import lv.enes.orang.core.OrangRuntimeException; | 3 | import lv.enes.orang.core.OrangRuntimeException; |
| 4 | 4 | ||
| 5 | public sealed interface Value | 5 | public sealed interface Value |
| 6 | permits Array, BuiltinFunction, Function, Nothing, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction, PartialFunction, Undefined { | 6 | permits Array, BuiltinFunction, EmptyTuple, Function, OrangBoolean, OrangInteger, OrangString, |
| 7 | PartialBuiltinFunction, PartialFunction, Tuple, Undefined { | ||
| 7 | String typeName(); | 8 | String typeName(); |
| 8 | String stringify(); | 9 | String stringify(); |
| 9 | 10 | ||
diff --git a/grammar.bnf b/grammar.bnf index a9e21cb..8715ad2 100644 --- a/grammar.bnf +++ b/grammar.bnf | |||
| @@ -14,17 +14,21 @@ binop ::= '*' | '/' | '+' | '-' | '?=' | '/=' | '>' | '>=' | '<' | '<='; | |||
| 14 | unop ::= '+' | '-' | '!'; | 14 | unop ::= '+' | '-' | '!'; |
| 15 | simple-expression ::= '(' expression ')' | 15 | simple-expression ::= '(' expression ')' |
| 16 | | 'true' | 'false' | 16 | | 'true' | 'false' |
| 17 | | '(' ')' | ||
| 18 | | INTEGER | 17 | | INTEGER |
| 19 | | IDENTIFIER | 18 | | IDENTIFIER |
| 20 | | STRING | 19 | | STRING |
| 21 | | array | 20 | | array |
| 21 | | tuple | ||
| 22 | | if-else-expression | 22 | | if-else-expression |
| 23 | | let-in-expression | 23 | | let-in-expression |
| 24 | | fn-expression | 24 | | fn-expression |
| 25 | | do-expression; | 25 | | do-expression; |
| 26 | 26 | ||
| 27 | array ::= '[' ']' | '[' expression (',' expression)* ','? ']'; | 27 | array ::= '[' ']' | '[' expression (',' expression)* ','? ']'; |
| 28 | // Note that tuples always have either zero or at least two elements | ||
| 29 | // A tuple with only one element is just '(' expression ')' -- a parenthese-wrapped expression | ||
| 30 | // Also '(' expression ',' ')' is illegal | ||
| 31 | tuple ::= '(' ')' | '(' expression ',' expression (',' expression)* ','? ')'; | ||
| 28 | 32 | ||
| 29 | if-else-expression ::= 'if' expression 'then' expression 'else' expression; | 33 | if-else-expression ::= 'if' expression 'then' expression 'else' expression; |
| 30 | let-in-expression ::= 'let' def-spec '=' expression ('and' def-spec '=' expression)* 'in' expression; | 34 | 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 { | |||
| 283 | case PAREN_LEFT -> { | 283 | case PAREN_LEFT -> { |
| 284 | consumeToken(Token.Type.PAREN_LEFT); | 284 | consumeToken(Token.Type.PAREN_LEFT); |
| 285 | if (maybeConsumeToken(Token.Type.PAREN_RIGHT)) { | 285 | if (maybeConsumeToken(Token.Type.PAREN_RIGHT)) { |
| 286 | yield VoidExpression.INSTANCE; | 286 | yield EmptyTupleLiteral.INSTANCE; |
| 287 | } | 287 | } |
| 288 | var expr = parseExpression(); | 288 | var expr = parseExpression(); |
| 289 | if (maybeConsumeToken(Token.Type.COMMA)) { | ||
| 290 | yield parseTupleExpression(expr); | ||
| 291 | } | ||
| 289 | consumeToken(Token.Type.PAREN_RIGHT); | 292 | consumeToken(Token.Type.PAREN_RIGHT); |
| 290 | yield expr; | 293 | yield expr; |
| 291 | } | 294 | } |
| @@ -336,6 +339,16 @@ public class Parser { | |||
| 336 | return new StringLiteral(sb.toString()); | 339 | return new StringLiteral(sb.toString()); |
| 337 | } | 340 | } |
| 338 | 341 | ||
| 342 | private TupleExpression parseTupleExpression(Expression first) throws ParserException { | ||
| 343 | var exprs = new ArrayList<Expression>(); | ||
| 344 | exprs.add(first); | ||
| 345 | do { | ||
| 346 | exprs.add(parseExpression()); | ||
| 347 | maybeConsumeToken(Token.Type.COMMA); | ||
| 348 | } while (!maybeConsumeToken(Token.Type.PAREN_RIGHT)); | ||
| 349 | return new TupleExpression(new NonEmptyList<>(exprs)); | ||
| 350 | } | ||
| 351 | |||
| 339 | private Expression parseUnaryExpression() throws ParserException { | 352 | private Expression parseUnaryExpression() throws ParserException { |
| 340 | if (isUnaryOp(input.peek())) { | 353 | if (isUnaryOp(input.peek())) { |
| 341 | var op = toUnaryOp(input.next()); | 354 | var op = toUnaryOp(input.next()); |