diff options
| author | 2024-08-24 03:54:25 +0800 | |
|---|---|---|
| committer | 2024-08-24 03:54:25 +0800 | |
| commit | 16a20ee8b08df919e93ea1b878013f9a2ad709fa (patch) | |
| tree | 80d59e93c566ddba1e2116e8d64222b8036b216e | |
| parent | Add tuples. (diff) | |
| download | orang-16a20ee8b08df919e93ea1b878013f9a2ad709fa.tar.gz orang-16a20ee8b08df919e93ea1b878013f9a2ad709fa.tar.xz orang-16a20ee8b08df919e93ea1b878013f9a2ad709fa.zip | |
Added fancier tuple argument specs.
Made ArgSpec use visitor pattern to avoid problems the best way.
Merged empty tuple and non-empty tuple classes.
17 files changed, 185 insertions, 122 deletions
diff --git a/ast/src/main/java/lv/enes/orang/ast/ArgSpec.java b/ast/src/main/java/lv/enes/orang/ast/ArgSpec.java index 8fa52be..fd1c8f9 100644 --- a/ast/src/main/java/lv/enes/orang/ast/ArgSpec.java +++ b/ast/src/main/java/lv/enes/orang/ast/ArgSpec.java | |||
| @@ -1,39 +1,7 @@ | |||
| 1 | package lv.enes.orang.ast; | 1 | package lv.enes.orang.ast; |
| 2 | 2 | ||
| 3 | public sealed interface ArgSpec { | 3 | import lv.enes.orang.core.OrangException; |
| 4 | enum Type { | ||
| 5 | IGNORED, | ||
| 6 | NOTHING, | ||
| 7 | NAMED, | ||
| 8 | } | ||
| 9 | 4 | ||
| 10 | Type getType(); | 5 | public interface ArgSpec { |
| 11 | 6 | <R, E extends OrangException> R accept(ArgSpecVisitor<R, E> visitor) throws E; | |
| 12 | static Ignored ignored() { | ||
| 13 | return Ignored.INSTANCE; | ||
| 14 | } | ||
| 15 | |||
| 16 | static Named named(String name) { | ||
| 17 | return new Named(name); | ||
| 18 | } | ||
| 19 | |||
| 20 | static Nothing nothing() { | ||
| 21 | return Nothing.INSTANCE; | ||
| 22 | } | ||
| 23 | |||
| 24 | final class Ignored implements ArgSpec { | ||
| 25 | public static final Ignored INSTANCE = new Ignored(); | ||
| 26 | private Ignored() {} | ||
| 27 | @Override public Type getType() { return Type.IGNORED; } | ||
| 28 | } | ||
| 29 | |||
| 30 | record Named(String name) implements ArgSpec { | ||
| 31 | @Override public Type getType() { return Type.NAMED; } | ||
| 32 | } | ||
| 33 | |||
| 34 | final class Nothing implements ArgSpec { | ||
| 35 | public static final Nothing INSTANCE = new Nothing(); | ||
| 36 | private Nothing() {} | ||
| 37 | @Override public Type getType() { return Type.NOTHING; } | ||
| 38 | } | ||
| 39 | } | 7 | } |
diff --git a/ast/src/main/java/lv/enes/orang/ast/ArgSpecIgnored.java b/ast/src/main/java/lv/enes/orang/ast/ArgSpecIgnored.java new file mode 100644 index 0000000..95ad114 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/ArgSpecIgnored.java | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.core.OrangException; | ||
| 4 | |||
| 5 | public final class ArgSpecIgnored implements ArgSpec { | ||
| 6 | public static final ArgSpecIgnored INSTANCE = new ArgSpecIgnored(); | ||
| 7 | private ArgSpecIgnored() {} | ||
| 8 | |||
| 9 | @Override | ||
| 10 | public <R, E extends OrangException> R accept(ArgSpecVisitor<R, E> visitor) throws E { | ||
| 11 | return visitor.visitIgnored(); | ||
| 12 | } | ||
| 13 | } | ||
diff --git a/ast/src/main/java/lv/enes/orang/ast/ArgSpecNamed.java b/ast/src/main/java/lv/enes/orang/ast/ArgSpecNamed.java new file mode 100644 index 0000000..7c83275 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/ArgSpecNamed.java | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.core.OrangException; | ||
| 4 | |||
| 5 | public record ArgSpecNamed(String name) implements ArgSpec { | ||
| 6 | @Override | ||
| 7 | public <R, E extends OrangException> R accept(ArgSpecVisitor<R, E> visitor) throws E { | ||
| 8 | return visitor.visitNamed(this); | ||
| 9 | } | ||
| 10 | } | ||
| 11 | |||
diff --git a/ast/src/main/java/lv/enes/orang/ast/ArgSpecTuple.java b/ast/src/main/java/lv/enes/orang/ast/ArgSpecTuple.java new file mode 100644 index 0000000..6703def --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/ArgSpecTuple.java | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.core.OrangException; | ||
| 4 | |||
| 5 | import java.util.List; | ||
| 6 | |||
| 7 | public record ArgSpecTuple(List<ArgSpec> children) implements ArgSpec { | ||
| 8 | @Override | ||
| 9 | public <R, E extends OrangException> R accept(ArgSpecVisitor<R, E> visitor) throws E { | ||
| 10 | return visitor.visitTuple(this); | ||
| 11 | } | ||
| 12 | } | ||
diff --git a/ast/src/main/java/lv/enes/orang/ast/ArgSpecVisitor.java b/ast/src/main/java/lv/enes/orang/ast/ArgSpecVisitor.java new file mode 100644 index 0000000..33cb6d6 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/ArgSpecVisitor.java | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.core.OrangException; | ||
| 4 | |||
| 5 | public interface ArgSpecVisitor<R, E extends OrangException> { | ||
| 6 | default R visit(ArgSpec argSpec) throws E { | ||
| 7 | return argSpec.accept(this); | ||
| 8 | } | ||
| 9 | |||
| 10 | R visitIgnored() throws E; | ||
| 11 | R visitNamed(ArgSpecNamed named) throws E; | ||
| 12 | R visitTuple(ArgSpecTuple tuple) throws E; | ||
| 13 | } | ||
diff --git a/ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.java b/ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.java deleted file mode 100644 index a96580f..0000000 --- a/ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.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 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 c32b429..226b340 100644 --- a/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java +++ b/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java | |||
| @@ -12,7 +12,6 @@ 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; | ||
| 16 | R visitFnExpression(FnExpression expr) throws E; | 15 | R visitFnExpression(FnExpression expr) throws E; |
| 17 | R visitIfElseExpression(IfElseExpression expr) throws E; | 16 | R visitIfElseExpression(IfElseExpression expr) throws E; |
| 18 | R visitIntLiteral(IntLiteral expr) throws E; | 17 | R visitIntLiteral(IntLiteral expr) 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 index 9df418d..84f2ceb 100644 --- a/ast/src/main/java/lv/enes/orang/ast/TupleExpression.java +++ b/ast/src/main/java/lv/enes/orang/ast/TupleExpression.java | |||
| @@ -1,11 +1,10 @@ | |||
| 1 | package lv.enes.orang.ast; | 1 | package lv.enes.orang.ast; |
| 2 | 2 | ||
| 3 | import lv.enes.orang.core.OrangException; | 3 | import lv.enes.orang.core.OrangException; |
| 4 | import lv.enes.orang.utils.NonEmptyList; | ||
| 5 | 4 | ||
| 6 | public record TupleExpression(NonEmptyList<Expression> children) implements Expression { | 5 | import java.util.List; |
| 7 | // assert children.size() >= 2 | ||
| 8 | 6 | ||
| 7 | public record TupleExpression(List<Expression> children) implements Expression { | ||
| 9 | @Override | 8 | @Override |
| 10 | public <R, E extends OrangException> R accept(ExpressionVisitor<R, E> visitor) throws E { | 9 | public <R, E extends OrangException> R accept(ExpressionVisitor<R, E> visitor) throws E { |
| 11 | return visitor.visitTupleExpression(this); | 10 | return visitor.visitTupleExpression(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 66b7dac..f926f59 100644 --- a/checker/src/main/java/lv/enes/orang/checker/Checker.java +++ b/checker/src/main/java/lv/enes/orang/checker/Checker.java | |||
| @@ -65,12 +65,6 @@ 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 | ||
| 74 | public Checker visitExpression(ExpressionStatement expr) throws CheckerException { | 68 | public Checker visitExpression(ExpressionStatement expr) throws CheckerException { |
| 75 | visit(expr.expr()); | 69 | visit(expr.expr()); |
| 76 | return this; | 70 | return this; |
| @@ -78,13 +72,40 @@ public class Checker implements ExpressionVisitor<Void, CheckerException>, State | |||
| 78 | 72 | ||
| 79 | @Override | 73 | @Override |
| 80 | public Void visitFnExpression(FnExpression expr) throws CheckerException { | 74 | public Void visitFnExpression(FnExpression expr) throws CheckerException { |
| 81 | var newScope = new HashSet<>(definitions); | 75 | class Helper implements ArgSpecVisitor<HashSet<String>, CheckerException> { |
| 82 | for (var arg : expr.args()) { | 76 | private final HashSet<String> scope = new HashSet<>(); |
| 83 | if (arg instanceof ArgSpec.Named(String name)) { | 77 | |
| 84 | newScope.add(name); | 78 | @Override |
| 79 | public HashSet<String> visitIgnored() { | ||
| 80 | // do nothing | ||
| 81 | return scope; | ||
| 82 | } | ||
| 83 | |||
| 84 | @Override | ||
| 85 | public HashSet<String> visitNamed(ArgSpecNamed named) throws CheckerException { | ||
| 86 | if (scope.contains(named.name())) { | ||
| 87 | throw new CheckerException(STR."Redefined argument \{named.name()}!"); | ||
| 88 | } | ||
| 89 | scope.add(named.name()); | ||
| 90 | return scope; | ||
| 91 | } | ||
| 92 | |||
| 93 | @Override | ||
| 94 | public HashSet<String> visitTuple(ArgSpecTuple tuple) throws CheckerException { | ||
| 95 | for (var child : tuple.children()) { | ||
| 96 | visit(child); | ||
| 97 | } | ||
| 98 | return scope; | ||
| 85 | } | 99 | } |
| 86 | } | 100 | } |
| 87 | new Checker(newScope).visit(expr.body()); | 101 | |
| 102 | var helper = new Helper(); | ||
| 103 | for (var arg : expr.args()) { | ||
| 104 | helper.visit(arg); | ||
| 105 | } | ||
| 106 | |||
| 107 | helper.scope.addAll(definitions); | ||
| 108 | new Checker(helper.scope).visit(expr.body()); | ||
| 88 | return null; | 109 | return null; |
| 89 | } | 110 | } |
| 90 | 111 | ||
diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java b/evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java deleted file mode 100644 index f8e043b..0000000 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java +++ /dev/null | |||
| @@ -1,21 +0,0 @@ | |||
| 1 | package lv.enes.orang.evaluator; | ||
| 2 | |||
| 3 | import lombok.EqualsAndHashCode; | ||
| 4 | |||
| 5 | @EqualsAndHashCode | ||
| 6 | public final class EmptyTuple implements Value { | ||
| 7 | public static final EmptyTuple INSTANCE = new EmptyTuple(); | ||
| 8 | |||
| 9 | private EmptyTuple() { | ||
| 10 | } | ||
| 11 | |||
| 12 | @Override | ||
| 13 | public String typeName() { | ||
| 14 | return "Nothing"; | ||
| 15 | } | ||
| 16 | |||
| 17 | @Override | ||
| 18 | public String stringify() { | ||
| 19 | return "()"; | ||
| 20 | } | ||
| 21 | } | ||
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 f0299b4..1f51b9d 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java | |||
| @@ -2,7 +2,6 @@ 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; | ||
| 6 | 5 | ||
| 7 | import java.util.ArrayList; | 6 | import java.util.ArrayList; |
| 8 | import java.util.Collections; | 7 | import java.util.Collections; |
| @@ -72,11 +71,6 @@ public record Evaluator(Map<String, Value> scope, Value lastResult) implements E | |||
| 72 | } | 71 | } |
| 73 | 72 | ||
| 74 | @Override | 73 | @Override |
| 75 | public Value visitEmptyTupleExpression() { | ||
| 76 | return EmptyTuple.INSTANCE; | ||
| 77 | } | ||
| 78 | |||
| 79 | @Override | ||
| 80 | public Evaluator visitExpression(ExpressionStatement expr) throws OrangRuntimeException { | 74 | public Evaluator visitExpression(ExpressionStatement expr) throws OrangRuntimeException { |
| 81 | return new Evaluator(this.scope(), visit(expr.expr())); | 75 | return new Evaluator(this.scope(), visit(expr.expr())); |
| 82 | } | 76 | } |
| @@ -148,7 +142,7 @@ public record Evaluator(Map<String, Value> scope, Value lastResult) implements E | |||
| 148 | for (var tailExpr : expr.children()) { | 142 | for (var tailExpr : expr.children()) { |
| 149 | values.add(visit(tailExpr)); | 143 | values.add(visit(tailExpr)); |
| 150 | } | 144 | } |
| 151 | return new Tuple(new NonEmptyList<>(values)); | 145 | return new Tuple(values); |
| 152 | } | 146 | } |
| 153 | 147 | ||
| 154 | @Override | 148 | @Override |
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 7c0ca20..bea3d02 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java | |||
| @@ -1,7 +1,6 @@ | |||
| 1 | package lv.enes.orang.evaluator; | 1 | package lv.enes.orang.evaluator; |
| 2 | 2 | ||
| 3 | import lv.enes.orang.ast.ArgSpec; | 3 | import lv.enes.orang.ast.*; |
| 4 | import lv.enes.orang.ast.Expression; | ||
| 5 | import lv.enes.orang.core.OrangRuntimeException; | 4 | import lv.enes.orang.core.OrangRuntimeException; |
| 6 | import lv.enes.orang.utils.NonEmptyList; | 5 | import lv.enes.orang.utils.NonEmptyList; |
| 7 | 6 | ||
| @@ -10,17 +9,45 @@ import java.util.Map; | |||
| 10 | 9 | ||
| 11 | public record PartialFunction(Map<String, Value> scope, NonEmptyList<ArgSpec> remainingArgs, Expression body) implements Value { | 10 | public record PartialFunction(Map<String, Value> scope, NonEmptyList<ArgSpec> remainingArgs, Expression body) implements Value { |
| 12 | public static Value of(Map<String, Value> scope, NonEmptyList<ArgSpec> remainingArgs, Expression body, Value param) throws OrangRuntimeException { | 11 | public static Value of(Map<String, Value> scope, NonEmptyList<ArgSpec> remainingArgs, Expression body, Value param) throws OrangRuntimeException { |
| 13 | var spec = remainingArgs.getFirst(); | 12 | class Helper implements ArgSpecVisitor<Void, OrangRuntimeException> { |
| 14 | var newScope = new HashMap<>(scope); | 13 | private final HashMap<String, Value> newScope; |
| 15 | switch (spec.getType()) { | 14 | private final Value param; |
| 16 | case NAMED -> newScope.put(((ArgSpec.Named)spec).name(), param); | 15 | |
| 17 | case NOTHING -> { | 16 | public Helper(HashMap<String, Value> newScope, Value param) { |
| 18 | if (!(param instanceof EmptyTuple)) { | 17 | this.newScope = newScope; |
| 19 | throw new OrangRuntimeException(STR."Expected () as a parameter but got \{param.typeName()}"); | 18 | this.param = param; |
| 19 | } | ||
| 20 | |||
| 21 | @Override | ||
| 22 | public Void visitIgnored() { | ||
| 23 | // Do nothing :3 | ||
| 24 | return null; | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public Void visitNamed(ArgSpecNamed named) { | ||
| 29 | newScope.put(named.name(), param); | ||
| 30 | return null; | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | public Void visitTuple(ArgSpecTuple tuple) throws OrangRuntimeException { | ||
| 35 | if (param instanceof Tuple(var children)) { | ||
| 36 | if (children.size() == tuple.children().size()) { | ||
| 37 | for (var i = 0; i < children.size(); i++) { | ||
| 38 | new Helper(newScope, children.get(i)).visit(tuple.children().get(i)); | ||
| 39 | } | ||
| 40 | return null; | ||
| 41 | } | ||
| 20 | } | 42 | } |
| 43 | throw new OrangRuntimeException(STR."Expected a tuple with \{tuple.children().size()} members as a parameter but got \{param.typeName()}"); | ||
| 21 | } | 44 | } |
| 22 | } | 45 | } |
| 23 | 46 | ||
| 47 | var spec = remainingArgs.getFirst(); | ||
| 48 | var newScope = new HashMap<>(scope); | ||
| 49 | new Helper(newScope, param).visit(spec); | ||
| 50 | |||
| 24 | if (remainingArgs.size() == 1) { | 51 | if (remainingArgs.size() == 1) { |
| 25 | return new Evaluator(newScope).visit(body); | 52 | return new Evaluator(newScope).visit(body); |
| 26 | } else { | 53 | } else { |
diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java index d5a1df5..fb607db 100644 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java | |||
| @@ -1,10 +1,14 @@ | |||
| 1 | package lv.enes.orang.evaluator; | 1 | package lv.enes.orang.evaluator; |
| 2 | 2 | ||
| 3 | import lv.enes.orang.utils.NonEmptyList; | 3 | import java.util.List; |
| 4 | 4 | ||
| 5 | public record Tuple(NonEmptyList<Value> contents) implements Value { | 5 | public record Tuple(List<Value> contents) implements Value { |
| 6 | @Override | 6 | @Override |
| 7 | public String typeName() { | 7 | public String typeName() { |
| 8 | if (contents.isEmpty()) { | ||
| 9 | return "()"; | ||
| 10 | } | ||
| 11 | |||
| 8 | var sb = new StringBuilder("("); | 12 | var sb = new StringBuilder("("); |
| 9 | sb.append(contents.getFirst().typeName()); | 13 | sb.append(contents.getFirst().typeName()); |
| 10 | for (var i = 1; i < contents.size(); i++) { | 14 | for (var i = 1; i < contents.size(); i++) { |
| @@ -15,6 +19,10 @@ public record Tuple(NonEmptyList<Value> contents) implements Value { | |||
| 15 | 19 | ||
| 16 | @Override | 20 | @Override |
| 17 | public String stringify() { | 21 | public String stringify() { |
| 22 | if (contents.isEmpty()) { | ||
| 23 | return "()"; | ||
| 24 | } | ||
| 25 | |||
| 18 | var sb = new StringBuilder("("); | 26 | var sb = new StringBuilder("("); |
| 19 | sb.append(contents.getFirst().stringify()); | 27 | sb.append(contents.getFirst().stringify()); |
| 20 | for (var i = 1; i < contents.size(); i++) { | 28 | for (var i = 1; i < contents.size(); i++) { |
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 2ef5136..1a1aad6 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; | |||
| 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, EmptyTuple, Function, OrangBoolean, OrangInteger, OrangString, | 6 | permits Array, BuiltinFunction, Function, OrangBoolean, OrangInteger, OrangString, |
| 7 | PartialBuiltinFunction, PartialFunction, Tuple, Undefined { | 7 | PartialBuiltinFunction, PartialFunction, Tuple, Undefined { |
| 8 | String typeName(); | 8 | String typeName(); |
| 9 | String stringify(); | 9 | String stringify(); |
diff --git a/grammar.bnf b/grammar.bnf index 8715ad2..30b48f6 100644 --- a/grammar.bnf +++ b/grammar.bnf | |||
| @@ -5,7 +5,8 @@ statement ::= definition; | |||
| 5 | definition ::= 'def' def-spec '=' expression | 5 | definition ::= 'def' def-spec '=' expression |
| 6 | | 'def' '_' '=' expression; | 6 | | 'def' '_' '=' expression; |
| 7 | def-spec ::= IDENTIFIER arg-spec*; | 7 | def-spec ::= IDENTIFIER arg-spec*; |
| 8 | arg-spec ::= IDENTIFIER | '(' ')' | '_'; | 8 | arg-spec ::= '(' arg-spec ')' | IDENTIFIER | arg-spec-tuple | '_'; |
| 9 | arg-spec-tuple ::= '(' ')' | '(' arg-spec ',' arg-spec (',' arg-spec)* ','? ')'; | ||
| 9 | expression ::= binary-expression | unary-expression; | 10 | expression ::= binary-expression | unary-expression; |
| 10 | unary-expression ::= unop+ simple-expression; | 11 | unary-expression ::= unop+ simple-expression; |
| 11 | binary-expression ::= call-expression (binop call-expression)*; | 12 | binary-expression ::= call-expression (binop call-expression)*; |
| @@ -37,4 +38,6 @@ fn-expression ::= 'fn' arg-spec+ '->' expression | |||
| 37 | do-expression ::= 'do' expression (';' expression)* 'end'; | 38 | do-expression ::= 'do' expression (';' expression)* 'end'; |
| 38 | 39 | ||
| 39 | repl-program ::= (repl-statement ';'?)* '\n'; // currently, hard-limited by the newline :sweat_smile: | 40 | repl-program ::= (repl-statement ';'?)* '\n'; // currently, hard-limited by the newline :sweat_smile: |
| 40 | repl-statement ::= statement | expression; \ No newline at end of file | 41 | repl-statement ::= statement | expression; |
| 42 | |||
| 43 | // TODO: Note that arg-spec and simple-expression are getting similar, both in grammar and in parser code \ No newline at end of file | ||
diff --git a/orang/src/main/java/lv/enes/orang/Main.java b/orang/src/main/java/lv/enes/orang/Main.java index 6890500..ef6db5e 100644 --- a/orang/src/main/java/lv/enes/orang/Main.java +++ b/orang/src/main/java/lv/enes/orang/Main.java | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | package lv.enes.orang; | 1 | package lv.enes.orang; |
| 2 | 2 | ||
| 3 | import lv.enes.orang.core.OrangException; | 3 | import lv.enes.orang.core.OrangException; |
| 4 | import lv.enes.orang.evaluator.Undefined; | ||
| 4 | 5 | ||
| 5 | import java.io.FileReader; | 6 | import java.io.FileReader; |
| 6 | import java.io.IOException; | 7 | import java.io.IOException; |
| @@ -64,7 +65,7 @@ public class Main { | |||
| 64 | 65 | ||
| 65 | try { | 66 | try { |
| 66 | pipeline.run(line, true); | 67 | pipeline.run(line, true); |
| 67 | if (pipeline.lastResult() != null) { | 68 | if (pipeline.lastResult() != Undefined.INSTANCE) { |
| 68 | STDOUT.print("-> "); | 69 | STDOUT.print("-> "); |
| 69 | STDOUT.println(pipeline.lastResult().stringify()); | 70 | STDOUT.println(pipeline.lastResult().stringify()); |
| 70 | } | 71 | } |
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 0ff1f78..06183a3 100644 --- a/parser/src/main/java/lv/enes/orang/parser/Parser.java +++ b/parser/src/main/java/lv/enes/orang/parser/Parser.java | |||
| @@ -131,23 +131,51 @@ public class Parser { | |||
| 131 | 131 | ||
| 132 | private List<ArgSpec> parseArgSpecs() throws ParserException { | 132 | private List<ArgSpec> parseArgSpecs() throws ParserException { |
| 133 | var argSpecs = new ArrayList<ArgSpec>(); | 133 | var argSpecs = new ArrayList<ArgSpec>(); |
| 134 | while (true) { | 134 | while (couldStartArgSpec(input.peek().type())) { |
| 135 | if (input.peek().type() == Token.Type.IDENTIFIER) { | 135 | argSpecs.add(parseArgSpec()); |
| 136 | argSpecs.add(ArgSpec.named(input.next().literal())); | ||
| 137 | } else if (input.peek().type() == Token.Type.PAREN_LEFT) { | ||
| 138 | consumeToken(Token.Type.PAREN_LEFT); | ||
| 139 | consumeToken(Token.Type.PAREN_RIGHT); | ||
| 140 | argSpecs.add(ArgSpec.nothing()); | ||
| 141 | } else if (input.peek().type() == Token.Type.UNDERSCORE) { | ||
| 142 | consumeToken(Token.Type.UNDERSCORE); | ||
| 143 | argSpecs.add(ArgSpec.ignored()); | ||
| 144 | } else { | ||
| 145 | break; | ||
| 146 | } | ||
| 147 | } | 136 | } |
| 137 | argSpecs.trimToSize(); | ||
| 148 | return Collections.unmodifiableList(argSpecs); | 138 | return Collections.unmodifiableList(argSpecs); |
| 149 | } | 139 | } |
| 150 | 140 | ||
| 141 | private boolean couldStartArgSpec(Token.Type type) { | ||
| 142 | return switch (type) { | ||
| 143 | case IDENTIFIER, PAREN_LEFT, UNDERSCORE -> true; | ||
| 144 | default -> false; | ||
| 145 | }; | ||
| 146 | } | ||
| 147 | |||
| 148 | private ArgSpec parseArgSpec() throws ParserException { | ||
| 149 | var token = input.next(); | ||
| 150 | return switch (token.type()) { | ||
| 151 | case IDENTIFIER -> new ArgSpecNamed(token.literal()); | ||
| 152 | case PAREN_LEFT -> { | ||
| 153 | if (maybeConsumeToken(Token.Type.PAREN_RIGHT)) { | ||
| 154 | yield new ArgSpecTuple(List.of()); | ||
| 155 | } | ||
| 156 | var argspec = parseArgSpec(); | ||
| 157 | if (maybeConsumeToken(Token.Type.COMMA)) { | ||
| 158 | yield parseArgSpecTuple(argspec); | ||
| 159 | } | ||
| 160 | consumeToken(Token.Type.PAREN_RIGHT); | ||
| 161 | yield argspec; | ||
| 162 | } | ||
| 163 | case UNDERSCORE -> ArgSpecIgnored.INSTANCE; | ||
| 164 | default -> throw new ParserException(STR."Unexpected token when parsing argspecs: \{token}"); | ||
| 165 | }; | ||
| 166 | } | ||
| 167 | |||
| 168 | private ArgSpec parseArgSpecTuple(ArgSpec first) throws ParserException { | ||
| 169 | var specs = new ArrayList<ArgSpec>(); | ||
| 170 | specs.add(first); | ||
| 171 | do { | ||
| 172 | specs.add(parseArgSpec()); | ||
| 173 | maybeConsumeToken(Token.Type.COMMA); | ||
| 174 | } while (!maybeConsumeToken(Token.Type.PAREN_RIGHT)); | ||
| 175 | specs.trimToSize(); | ||
| 176 | return new ArgSpecTuple(specs); | ||
| 177 | } | ||
| 178 | |||
| 151 | private Expression parseBinaryExpression() throws ParserException { | 179 | private Expression parseBinaryExpression() throws ParserException { |
| 152 | var lhs = parseCallExpression(); | 180 | var lhs = parseCallExpression(); |
| 153 | if (isNotBinaryOp(input.peek())) { | 181 | if (isNotBinaryOp(input.peek())) { |
| @@ -283,7 +311,7 @@ public class Parser { | |||
| 283 | case PAREN_LEFT -> { | 311 | case PAREN_LEFT -> { |
| 284 | consumeToken(Token.Type.PAREN_LEFT); | 312 | consumeToken(Token.Type.PAREN_LEFT); |
| 285 | if (maybeConsumeToken(Token.Type.PAREN_RIGHT)) { | 313 | if (maybeConsumeToken(Token.Type.PAREN_RIGHT)) { |
| 286 | yield EmptyTupleLiteral.INSTANCE; | 314 | yield new TupleExpression(List.of()); |
| 287 | } | 315 | } |
| 288 | var expr = parseExpression(); | 316 | var expr = parseExpression(); |
| 289 | if (maybeConsumeToken(Token.Type.COMMA)) { | 317 | if (maybeConsumeToken(Token.Type.COMMA)) { |
| @@ -346,7 +374,8 @@ public class Parser { | |||
| 346 | exprs.add(parseExpression()); | 374 | exprs.add(parseExpression()); |
| 347 | maybeConsumeToken(Token.Type.COMMA); | 375 | maybeConsumeToken(Token.Type.COMMA); |
| 348 | } while (!maybeConsumeToken(Token.Type.PAREN_RIGHT)); | 376 | } while (!maybeConsumeToken(Token.Type.PAREN_RIGHT)); |
| 349 | return new TupleExpression(new NonEmptyList<>(exprs)); | 377 | exprs.trimToSize(); |
| 378 | return new TupleExpression(exprs); | ||
| 350 | } | 379 | } |
| 351 | 380 | ||
| 352 | private Expression parseUnaryExpression() throws ParserException { | 381 | private Expression parseUnaryExpression() throws ParserException { |