From 16a20ee8b08df919e93ea1b878013f9a2ad709fa Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Sat, 24 Aug 2024 03:54:25 +0800 Subject: Added fancier tuple argument specs. Made ArgSpec use visitor pattern to avoid problems the best way. Merged empty tuple and non-empty tuple classes. --- ast/src/main/java/lv/enes/orang/ast/ArgSpec.java | 38 ++------------ .../java/lv/enes/orang/ast/ArgSpecIgnored.java | 13 +++++ .../main/java/lv/enes/orang/ast/ArgSpecNamed.java | 11 ++++ .../main/java/lv/enes/orang/ast/ArgSpecTuple.java | 12 +++++ .../java/lv/enes/orang/ast/ArgSpecVisitor.java | 13 +++++ .../java/lv/enes/orang/ast/EmptyTupleLiteral.java | 14 ----- .../java/lv/enes/orang/ast/ExpressionVisitor.java | 1 - .../java/lv/enes/orang/ast/TupleExpression.java | 5 +- .../main/java/lv/enes/orang/checker/Checker.java | 43 ++++++++++++---- .../java/lv/enes/orang/evaluator/EmptyTuple.java | 21 -------- .../java/lv/enes/orang/evaluator/Evaluator.java | 8 +-- .../lv/enes/orang/evaluator/PartialFunction.java | 45 +++++++++++++---- .../main/java/lv/enes/orang/evaluator/Tuple.java | 12 ++++- .../main/java/lv/enes/orang/evaluator/Value.java | 2 +- grammar.bnf | 7 ++- orang/src/main/java/lv/enes/orang/Main.java | 3 +- .../src/main/java/lv/enes/orang/parser/Parser.java | 59 ++++++++++++++++------ 17 files changed, 185 insertions(+), 122 deletions(-) create mode 100644 ast/src/main/java/lv/enes/orang/ast/ArgSpecIgnored.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/ArgSpecNamed.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/ArgSpecTuple.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/ArgSpecVisitor.java delete mode 100644 ast/src/main/java/lv/enes/orang/ast/EmptyTupleLiteral.java delete mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java 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 @@ package lv.enes.orang.ast; -public sealed interface ArgSpec { - enum Type { - IGNORED, - NOTHING, - NAMED, - } +import lv.enes.orang.core.OrangException; - Type getType(); - - static Ignored ignored() { - return Ignored.INSTANCE; - } - - static Named named(String name) { - return new Named(name); - } - - static Nothing nothing() { - return Nothing.INSTANCE; - } - - final class Ignored implements ArgSpec { - public static final Ignored INSTANCE = new Ignored(); - private Ignored() {} - @Override public Type getType() { return Type.IGNORED; } - } - - record Named(String name) implements ArgSpec { - @Override public Type getType() { return Type.NAMED; } - } - - final class Nothing implements ArgSpec { - public static final Nothing INSTANCE = new Nothing(); - private Nothing() {} - @Override public Type getType() { return Type.NOTHING; } - } +public interface ArgSpec { + R accept(ArgSpecVisitor visitor) throws E; } 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 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public final class ArgSpecIgnored implements ArgSpec { + public static final ArgSpecIgnored INSTANCE = new ArgSpecIgnored(); + private ArgSpecIgnored() {} + + @Override + public R accept(ArgSpecVisitor visitor) throws E { + return visitor.visitIgnored(); + } +} 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 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record ArgSpecNamed(String name) implements ArgSpec { + @Override + public R accept(ArgSpecVisitor visitor) throws E { + return visitor.visitNamed(this); + } +} + 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 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +import java.util.List; + +public record ArgSpecTuple(List children) implements ArgSpec { + @Override + public R accept(ArgSpecVisitor visitor) throws E { + return visitor.visitTuple(this); + } +} 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 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public interface ArgSpecVisitor { + default R visit(ArgSpec argSpec) throws E { + return argSpec.accept(this); + } + + R visitIgnored() throws E; + R visitNamed(ArgSpecNamed named) throws E; + R visitTuple(ArgSpecTuple tuple) throws E; +} 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 @@ -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 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 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; 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 @@ 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 +import java.util.List; +public record TupleExpression(List children) implements Expression { @Override public R accept(ExpressionVisitor visitor) throws E { 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 @@ -64,12 +64,6 @@ 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()); @@ -78,13 +72,40 @@ public class Checker implements ExpressionVisitor, State @Override public Void visitFnExpression(FnExpression expr) throws CheckerException { - var newScope = new HashSet<>(definitions); - for (var arg : expr.args()) { - if (arg instanceof ArgSpec.Named(String name)) { - newScope.add(name); + class Helper implements ArgSpecVisitor, CheckerException> { + private final HashSet scope = new HashSet<>(); + + @Override + public HashSet visitIgnored() { + // do nothing + return scope; + } + + @Override + public HashSet visitNamed(ArgSpecNamed named) throws CheckerException { + if (scope.contains(named.name())) { + throw new CheckerException(STR."Redefined argument \{named.name()}!"); + } + scope.add(named.name()); + return scope; + } + + @Override + public HashSet visitTuple(ArgSpecTuple tuple) throws CheckerException { + for (var child : tuple.children()) { + visit(child); + } + return scope; } } - new Checker(newScope).visit(expr.body()); + + var helper = new Helper(); + for (var arg : expr.args()) { + helper.visit(arg); + } + + helper.scope.addAll(definitions); + new Checker(helper.scope).visit(expr.body()); 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 deleted file mode 100644 index f8e043b..0000000 --- a/evaluator/src/main/java/lv/enes/orang/evaluator/EmptyTuple.java +++ /dev/null @@ -1,21 +0,0 @@ -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 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; 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; @@ -71,11 +70,6 @@ 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())); @@ -148,7 +142,7 @@ public record Evaluator(Map scope, Value lastResult) implements E for (var tailExpr : expr.children()) { values.add(visit(tailExpr)); } - return new Tuple(new NonEmptyList<>(values)); + return new Tuple(values); } @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 @@ package lv.enes.orang.evaluator; -import lv.enes.orang.ast.ArgSpec; -import lv.enes.orang.ast.Expression; +import lv.enes.orang.ast.*; import lv.enes.orang.core.OrangRuntimeException; import lv.enes.orang.utils.NonEmptyList; @@ -10,17 +9,45 @@ import java.util.Map; public record PartialFunction(Map scope, NonEmptyList remainingArgs, Expression body) implements Value { public static Value of(Map scope, NonEmptyList remainingArgs, Expression body, Value param) throws OrangRuntimeException { - var spec = remainingArgs.getFirst(); - var newScope = new HashMap<>(scope); - switch (spec.getType()) { - case NAMED -> newScope.put(((ArgSpec.Named)spec).name(), param); - case NOTHING -> { - if (!(param instanceof EmptyTuple)) { - throw new OrangRuntimeException(STR."Expected () as a parameter but got \{param.typeName()}"); + class Helper implements ArgSpecVisitor { + private final HashMap newScope; + private final Value param; + + public Helper(HashMap newScope, Value param) { + this.newScope = newScope; + this.param = param; + } + + @Override + public Void visitIgnored() { + // Do nothing :3 + return null; + } + + @Override + public Void visitNamed(ArgSpecNamed named) { + newScope.put(named.name(), param); + return null; + } + + @Override + public Void visitTuple(ArgSpecTuple tuple) throws OrangRuntimeException { + if (param instanceof Tuple(var children)) { + if (children.size() == tuple.children().size()) { + for (var i = 0; i < children.size(); i++) { + new Helper(newScope, children.get(i)).visit(tuple.children().get(i)); + } + return null; + } } + throw new OrangRuntimeException(STR."Expected a tuple with \{tuple.children().size()} members as a parameter but got \{param.typeName()}"); } } + var spec = remainingArgs.getFirst(); + var newScope = new HashMap<>(scope); + new Helper(newScope, param).visit(spec); + if (remainingArgs.size() == 1) { return new Evaluator(newScope).visit(body); } 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 @@ package lv.enes.orang.evaluator; -import lv.enes.orang.utils.NonEmptyList; +import java.util.List; -public record Tuple(NonEmptyList contents) implements Value { +public record Tuple(List contents) implements Value { @Override public String typeName() { + if (contents.isEmpty()) { + return "()"; + } + var sb = new StringBuilder("("); sb.append(contents.getFirst().typeName()); for (var i = 1; i < contents.size(); i++) { @@ -15,6 +19,10 @@ public record Tuple(NonEmptyList contents) implements Value { @Override public String stringify() { + if (contents.isEmpty()) { + return "()"; + } + var sb = new StringBuilder("("); sb.append(contents.getFirst().stringify()); 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; import lv.enes.orang.core.OrangRuntimeException; public sealed interface Value - permits Array, BuiltinFunction, EmptyTuple, Function, OrangBoolean, OrangInteger, OrangString, + permits Array, BuiltinFunction, Function, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction, PartialFunction, Tuple, Undefined { String typeName(); 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; definition ::= 'def' def-spec '=' expression | 'def' '_' '=' expression; def-spec ::= IDENTIFIER arg-spec*; -arg-spec ::= IDENTIFIER | '(' ')' | '_'; +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)*; @@ -37,4 +38,6 @@ fn-expression ::= 'fn' arg-spec+ '->' expression do-expression ::= 'do' expression (';' expression)* 'end'; repl-program ::= (repl-statement ';'?)* '\n'; // currently, hard-limited by the newline :sweat_smile: -repl-statement ::= statement | expression; \ No newline at end of file +repl-statement ::= statement | expression; + +// 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 @@ package lv.enes.orang; import lv.enes.orang.core.OrangException; +import lv.enes.orang.evaluator.Undefined; import java.io.FileReader; import java.io.IOException; @@ -64,7 +65,7 @@ public class Main { try { pipeline.run(line, true); - if (pipeline.lastResult() != null) { + if (pipeline.lastResult() != Undefined.INSTANCE) { STDOUT.print("-> "); STDOUT.println(pipeline.lastResult().stringify()); } 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 { private List parseArgSpecs() throws ParserException { var argSpecs = new ArrayList(); - while (true) { - if (input.peek().type() == Token.Type.IDENTIFIER) { - argSpecs.add(ArgSpec.named(input.next().literal())); - } else if (input.peek().type() == Token.Type.PAREN_LEFT) { - consumeToken(Token.Type.PAREN_LEFT); - consumeToken(Token.Type.PAREN_RIGHT); - argSpecs.add(ArgSpec.nothing()); - } else if (input.peek().type() == Token.Type.UNDERSCORE) { - consumeToken(Token.Type.UNDERSCORE); - argSpecs.add(ArgSpec.ignored()); - } else { - break; - } + while (couldStartArgSpec(input.peek().type())) { + argSpecs.add(parseArgSpec()); } + argSpecs.trimToSize(); return Collections.unmodifiableList(argSpecs); } + private boolean couldStartArgSpec(Token.Type type) { + return switch (type) { + case IDENTIFIER, PAREN_LEFT, UNDERSCORE -> true; + default -> false; + }; + } + + private ArgSpec parseArgSpec() throws ParserException { + var token = input.next(); + return switch (token.type()) { + case IDENTIFIER -> new ArgSpecNamed(token.literal()); + case PAREN_LEFT -> { + if (maybeConsumeToken(Token.Type.PAREN_RIGHT)) { + yield new ArgSpecTuple(List.of()); + } + var argspec = parseArgSpec(); + if (maybeConsumeToken(Token.Type.COMMA)) { + yield parseArgSpecTuple(argspec); + } + consumeToken(Token.Type.PAREN_RIGHT); + yield argspec; + } + case UNDERSCORE -> ArgSpecIgnored.INSTANCE; + default -> throw new ParserException(STR."Unexpected token when parsing argspecs: \{token}"); + }; + } + + private ArgSpec parseArgSpecTuple(ArgSpec first) throws ParserException { + var specs = new ArrayList(); + specs.add(first); + do { + specs.add(parseArgSpec()); + maybeConsumeToken(Token.Type.COMMA); + } while (!maybeConsumeToken(Token.Type.PAREN_RIGHT)); + specs.trimToSize(); + return new ArgSpecTuple(specs); + } + private Expression parseBinaryExpression() throws ParserException { var lhs = parseCallExpression(); if (isNotBinaryOp(input.peek())) { @@ -283,7 +311,7 @@ public class Parser { case PAREN_LEFT -> { consumeToken(Token.Type.PAREN_LEFT); if (maybeConsumeToken(Token.Type.PAREN_RIGHT)) { - yield EmptyTupleLiteral.INSTANCE; + yield new TupleExpression(List.of()); } var expr = parseExpression(); if (maybeConsumeToken(Token.Type.COMMA)) { @@ -346,7 +374,8 @@ public class Parser { exprs.add(parseExpression()); maybeConsumeToken(Token.Type.COMMA); } while (!maybeConsumeToken(Token.Type.PAREN_RIGHT)); - return new TupleExpression(new NonEmptyList<>(exprs)); + exprs.trimToSize(); + return new TupleExpression(exprs); } private Expression parseUnaryExpression() throws ParserException { -- cgit v1.2.3