summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ast/src/main/java/lv/enes/orang/ast/ArgSpec.java38
-rw-r--r--ast/src/main/java/lv/enes/orang/ast/ArgSpecIgnored.java13
-rw-r--r--ast/src/main/java/lv/enes/orang/ast/ArgSpecNamed.java11
-rw-r--r--ast/src/main/java/lv/enes/orang/ast/ArgSpecTuple.java12
-rw-r--r--ast/src/main/java/lv/enes/orang/ast/ArgSpecVisitor.java13
-rw-r--r--ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.java14
-rw-r--r--ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java1
-rw-r--r--ast/src/main/java/lv/enes/orang/ast/TupleExpression.java5
-rw-r--r--checker/src/main/java/lv/enes/orang/checker/Checker.java43
-rw-r--r--evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java21
-rw-r--r--evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java8
-rw-r--r--evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java45
-rw-r--r--evaluator/src/main/java/lv/enes/orang/evaluator/Tuple.java12
-rw-r--r--evaluator/src/main/java/lv/enes/orang/evaluator/Value.java2
-rw-r--r--grammar.bnf7
-rw-r--r--orang/src/main/java/lv/enes/orang/Main.java3
-rw-r--r--parser/src/main/java/lv/enes/orang/parser/Parser.java59
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 @@
1package lv.enes.orang.ast; 1package lv.enes.orang.ast;
2 2
3public sealed interface ArgSpec { 3import lv.enes.orang.core.OrangException;
4 enum Type {
5 IGNORED,
6 NOTHING,
7 NAMED,
8 }
9 4
10 Type getType(); 5public 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 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.core.OrangException;
4
5public 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 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.core.OrangException;
4
5public 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 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.core.OrangException;
4
5import java.util.List;
6
7public 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 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.core.OrangException;
4
5public 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 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.core.OrangException;
4
5public 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 @@
1package lv.enes.orang.ast; 1package lv.enes.orang.ast;
2 2
3import lv.enes.orang.core.OrangException; 3import lv.enes.orang.core.OrangException;
4import lv.enes.orang.utils.NonEmptyList;
5 4
6public record TupleExpression(NonEmptyList<Expression> children) implements Expression { 5import java.util.List;
7 // assert children.size() >= 2
8 6
7public 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 @@
1package lv.enes.orang.evaluator;
2
3import lombok.EqualsAndHashCode;
4
5@EqualsAndHashCode
6public 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
3import lv.enes.orang.ast.*; 3import lv.enes.orang.ast.*;
4import lv.enes.orang.core.OrangRuntimeException; 4import lv.enes.orang.core.OrangRuntimeException;
5import lv.enes.orang.utils.NonEmptyList;
6 5
7import java.util.ArrayList; 6import java.util.ArrayList;
8import java.util.Collections; 7import 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 @@
1package lv.enes.orang.evaluator; 1package lv.enes.orang.evaluator;
2 2
3import lv.enes.orang.ast.ArgSpec; 3import lv.enes.orang.ast.*;
4import lv.enes.orang.ast.Expression;
5import lv.enes.orang.core.OrangRuntimeException; 4import lv.enes.orang.core.OrangRuntimeException;
6import lv.enes.orang.utils.NonEmptyList; 5import lv.enes.orang.utils.NonEmptyList;
7 6
@@ -10,17 +9,45 @@ import java.util.Map;
10 9
11public record PartialFunction(Map<String, Value> scope, NonEmptyList<ArgSpec> remainingArgs, Expression body) implements Value { 10public 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 @@
1package lv.enes.orang.evaluator; 1package lv.enes.orang.evaluator;
2 2
3import lv.enes.orang.utils.NonEmptyList; 3import java.util.List;
4 4
5public record Tuple(NonEmptyList<Value> contents) implements Value { 5public 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;
3import lv.enes.orang.core.OrangRuntimeException; 3import lv.enes.orang.core.OrangRuntimeException;
4 4
5public sealed interface Value 5public 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;
5definition ::= 'def' def-spec '=' expression 5definition ::= 'def' def-spec '=' expression
6 | 'def' '_' '=' expression; 6 | 'def' '_' '=' expression;
7def-spec ::= IDENTIFIER arg-spec*; 7def-spec ::= IDENTIFIER arg-spec*;
8arg-spec ::= IDENTIFIER | '(' ')' | '_'; 8arg-spec ::= '(' arg-spec ')' | IDENTIFIER | arg-spec-tuple | '_';
9arg-spec-tuple ::= '(' ')' | '(' arg-spec ',' arg-spec (',' arg-spec)* ','? ')';
9expression ::= binary-expression | unary-expression; 10expression ::= binary-expression | unary-expression;
10unary-expression ::= unop+ simple-expression; 11unary-expression ::= unop+ simple-expression;
11binary-expression ::= call-expression (binop call-expression)*; 12binary-expression ::= call-expression (binop call-expression)*;
@@ -37,4 +38,6 @@ fn-expression ::= 'fn' arg-spec+ '->' expression
37do-expression ::= 'do' expression (';' expression)* 'end'; 38do-expression ::= 'do' expression (';' expression)* 'end';
38 39
39repl-program ::= (repl-statement ';'?)* '\n'; // currently, hard-limited by the newline :sweat_smile: 40repl-program ::= (repl-statement ';'?)* '\n'; // currently, hard-limited by the newline :sweat_smile:
40repl-statement ::= statement | expression; \ No newline at end of file 41repl-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 @@
1package lv.enes.orang; 1package lv.enes.orang;
2 2
3import lv.enes.orang.core.OrangException; 3import lv.enes.orang.core.OrangException;
4import lv.enes.orang.evaluator.Undefined;
4 5
5import java.io.FileReader; 6import java.io.FileReader;
6import java.io.IOException; 7import 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 {