From 8565a173591e4748ac910b4b982dff96bb5dfd60 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Sun, 18 Aug 2024 23:01:58 +0800 Subject: Added Nothing/empty-tuple arguments --- ast/src/main/java/lv/enes/orang/ast/ArgSpec.java | 26 +++++++++++++ ast/src/main/java/lv/enes/orang/ast/DefSpec.java | 2 +- .../java/lv/enes/orang/ast/ExpressionVisitor.java | 1 + .../main/java/lv/enes/orang/ast/FnExpression.java | 2 +- .../java/lv/enes/orang/ast/VoidExpression.java | 14 +++++++ dice.orang | 6 +-- grammar.bnf | 5 ++- orang/src/main/java/lv/enes/orang/Builtins.java | 7 ---- orang/src/main/java/lv/enes/orang/Evaluator.java | 5 +++ orang/src/main/java/lv/enes/orang/Parser.java | 12 ++++-- .../main/java/lv/enes/orang/value/Function.java | 18 +++------ .../src/main/java/lv/enes/orang/value/Nothing.java | 21 +++++++++++ .../java/lv/enes/orang/value/PartialFunction.java | 44 +++++++++++----------- orang/src/main/java/lv/enes/orang/value/Value.java | 3 +- .../src/main/resources/lv/enes/orang/prelude.orang | 4 +- 15 files changed, 114 insertions(+), 56 deletions(-) create mode 100644 ast/src/main/java/lv/enes/orang/ast/ArgSpec.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/VoidExpression.java create mode 100644 orang/src/main/java/lv/enes/orang/value/Nothing.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 new file mode 100644 index 0000000..e167e8f --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/ArgSpec.java @@ -0,0 +1,26 @@ +package lv.enes.orang.ast; + +public class ArgSpec { + public final Type type; + public final String name; + + private static final ArgSpec NOTHING = new ArgSpec(Type.NOTHING, null); + + public static ArgSpec nothing() { + return NOTHING; + } + + public static ArgSpec named(String name) { + return new ArgSpec(Type.NAMED, name); + } + + private ArgSpec(Type type, String name) { + this.type = type; + this.name = name; + } + + public enum Type { + NOTHING, + NAMED, + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/DefSpec.java b/ast/src/main/java/lv/enes/orang/ast/DefSpec.java index 2233d3f..5e6cc68 100644 --- a/ast/src/main/java/lv/enes/orang/ast/DefSpec.java +++ b/ast/src/main/java/lv/enes/orang/ast/DefSpec.java @@ -2,5 +2,5 @@ package lv.enes.orang.ast; import java.util.List; -public record DefSpec(String name, List args) { +public record DefSpec(String name, List args) { } 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 ddb1157..70f8a5e 100644 --- a/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java +++ b/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java @@ -19,4 +19,5 @@ public interface ExpressionVisitor { R visitStringLiteral(StringLiteral 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/FnExpression.java b/ast/src/main/java/lv/enes/orang/ast/FnExpression.java index 68f43af..0bf322c 100644 --- a/ast/src/main/java/lv/enes/orang/ast/FnExpression.java +++ b/ast/src/main/java/lv/enes/orang/ast/FnExpression.java @@ -3,7 +3,7 @@ package lv.enes.orang.ast; import lv.enes.orang.utils.NonEmptyList; import lv.enes.orang.core.OrangException; -public record FnExpression(NonEmptyList args, Expression body) implements Expression { +public record FnExpression(NonEmptyList args, Expression body) implements Expression { @Override public R accept(ExpressionVisitor visitor) throws E { return visitor.visitFnExpression(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 new file mode 100644 index 0000000..39d164d --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/VoidExpression.java @@ -0,0 +1,14 @@ +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/dice.orang b/dice.orang index 0fae27b..d8e52de 100644 --- a/dice.orang +++ b/dice.orang @@ -1,14 +1,14 @@ def dice sides times = - let once _ = randInt 1 (sides + 1) in + let once () = randInt 1 (sides + 1) in let aux acc times = if times ?= 0 then acc else aux (acc + once ()) (times - 1) in aux 0 times -def guessing_game _ = +def guessing_game () = let secret = randInt 1 101 - and guessOnce _ = do + and guessOnce () = do print "Make your guess: "; let guess = readInt () in if secret > guess then do diff --git a/grammar.bnf b/grammar.bnf index 7c18f8f..9ddaffc 100644 --- a/grammar.bnf +++ b/grammar.bnf @@ -4,7 +4,7 @@ program ::= (statement ';'?)*; statement ::= definition | expression; definition ::= 'def' def-spec '=' expression; def-spec ::= IDENTIFIER arg-spec*; -arg-spec ::= IDENTIFIER; +arg-spec ::= IDENTIFIER | '(' ')'; expression ::= binary-expression | unary-expression; unary-expression ::= unop+ simple-expression; binary-expression ::= call-expression (binop call-expression)*; @@ -13,6 +13,7 @@ binop ::= '*' | '/' | '+' | '-' | '?=' | '/=' | '>' | '>=' | '<' | '<='; unop ::= '+' | '-' | '!'; simple-expression ::= '(' expression ')' | 'true' | 'false' + | '(' ')' | INTEGER | IDENTIFIER | STRING @@ -22,7 +23,7 @@ simple-expression ::= '(' expression ')' | fn-expression | do-expression; -array ::= '(' ')' | '[' ']' | '[' expression (',' expression)* ','? ']'; +array ::= '[' ']' | '[' 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/orang/src/main/java/lv/enes/orang/Builtins.java b/orang/src/main/java/lv/enes/orang/Builtins.java index 0a76387..de296ac 100644 --- a/orang/src/main/java/lv/enes/orang/Builtins.java +++ b/orang/src/main/java/lv/enes/orang/Builtins.java @@ -79,13 +79,6 @@ public final class Builtins { } private static Value readLn(List args) throws OrangRuntimeException { - assert args.size() == 1; - - var arg = args.getFirst(); - if (!(arg instanceof Array) || !((Array) arg).items().isEmpty()) { - log.warn("You should call readLn with an empty tuple like `readLn ()`"); - } - try { return new OrangString(STDIN.readLine()); } catch (IOException e) { diff --git a/orang/src/main/java/lv/enes/orang/Evaluator.java b/orang/src/main/java/lv/enes/orang/Evaluator.java index e2e96ff..5d09508 100644 --- a/orang/src/main/java/lv/enes/orang/Evaluator.java +++ b/orang/src/main/java/lv/enes/orang/Evaluator.java @@ -141,4 +141,9 @@ public record Evaluator(Scope scope, Value lastResult) implements ExpressionVisi public Value visitVariable(VariableExpression expr) throws OrangRuntimeException { return scope.getDefinition(expr.name()); } + + @Override + public Value visitVoidExpression() { + return Nothing.INSTANCE; + } } diff --git a/orang/src/main/java/lv/enes/orang/Parser.java b/orang/src/main/java/lv/enes/orang/Parser.java index 4011bd7..77abe24 100644 --- a/orang/src/main/java/lv/enes/orang/Parser.java +++ b/orang/src/main/java/lv/enes/orang/Parser.java @@ -91,11 +91,15 @@ public class Parser { return new ArrayExpression(Collections.unmodifiableList(items)); } - private List parseArgSpecs() { - var argSpecs = new ArrayList(); + private List parseArgSpecs() throws ParserException { + var argSpecs = new ArrayList(); while (true) { if (input.peek().type() == TokenType.IDENTIFIER) { - argSpecs.add(input.next().literal()); + argSpecs.add(ArgSpec.named(input.next().literal())); + } else if (input.peek().type() == TokenType.PAREN_LEFT) { + consumeToken(TokenType.PAREN_LEFT); + consumeToken(TokenType.PAREN_RIGHT); + argSpecs.add(ArgSpec.nothing()); } else { break; } @@ -224,7 +228,7 @@ public class Parser { case PAREN_LEFT -> { consumeToken(TokenType.PAREN_LEFT); if (maybeConsumeToken(TokenType.PAREN_RIGHT)) { - yield new ArrayExpression(List.of()); + yield VoidExpression.INSTANCE; } var expr = parseExpression(); consumeToken(TokenType.PAREN_RIGHT); diff --git a/orang/src/main/java/lv/enes/orang/value/Function.java b/orang/src/main/java/lv/enes/orang/value/Function.java index 901776e..6e3c83a 100644 --- a/orang/src/main/java/lv/enes/orang/value/Function.java +++ b/orang/src/main/java/lv/enes/orang/value/Function.java @@ -1,14 +1,11 @@ package lv.enes.orang.value; -import lv.enes.orang.Evaluator; -import lv.enes.orang.ImmutableScope; -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; +import lv.enes.orang.*; +import lv.enes.orang.ast.ArgSpec; import lv.enes.orang.ast.Expression; +import lv.enes.orang.utils.NonEmptyList; -import java.util.List; - -public record Function(Scope scope, List args, Expression body) implements Value { +public record Function(Scope scope, NonEmptyList args, Expression body) implements Value { @Override public String typeName() { return "Function"; @@ -21,11 +18,6 @@ public record Function(Scope scope, List args, Expression body) implemen @Override public Value call(Value param) throws OrangRuntimeException { - if (args.size() == 1) { - var eval = new Evaluator(ImmutableScope.of(scope, args.getFirst(), param)); - return eval.visit(body); - } else { - return new PartialFunction(scope, args, List.of(param), body); - } + return PartialFunction.of(scope, args, body, param); } } diff --git a/orang/src/main/java/lv/enes/orang/value/Nothing.java b/orang/src/main/java/lv/enes/orang/value/Nothing.java new file mode 100644 index 0000000..4a90010 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/Nothing.java @@ -0,0 +1,21 @@ +package lv.enes.orang.value; + +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/orang/src/main/java/lv/enes/orang/value/PartialFunction.java b/orang/src/main/java/lv/enes/orang/value/PartialFunction.java index 0dbf530..cee0cd4 100644 --- a/orang/src/main/java/lv/enes/orang/value/PartialFunction.java +++ b/orang/src/main/java/lv/enes/orang/value/PartialFunction.java @@ -1,17 +1,30 @@ package lv.enes.orang.value; -import lv.enes.orang.Evaluator; -import lv.enes.orang.ImmutableScope; -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; +import lv.enes.orang.*; +import lv.enes.orang.ast.ArgSpec; import lv.enes.orang.ast.Expression; +import lv.enes.orang.utils.NonEmptyList; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; +public record PartialFunction(Scope scope, NonEmptyList remainingArgs, Expression body) implements Value { + public static Value of(Scope scope, NonEmptyList remainingArgs, Expression body, Value param) throws OrangRuntimeException { + var spec = remainingArgs.getFirst(); + var newScope = MutableScope.of(scope); + switch (spec.type) { + case NAMED -> newScope.setDefinition(spec.name, param); + case NOTHING -> { + if (!(param instanceof Nothing)) { + throw new OrangRuntimeException(STR."Expected () as a parameter but got \{param.typeName()}"); + } + } + } + + if (remainingArgs.size() == 1) { + return new Evaluator(newScope).visit(body); + } else { + return new PartialFunction(newScope, new NonEmptyList<>(remainingArgs.subList(1, remainingArgs.size())), body); + } + } -public record PartialFunction(Scope scope, List args, List params, Expression body) implements Value { @Override public String typeName() { return "Function"; @@ -24,17 +37,6 @@ public record PartialFunction(Scope scope, List args, List params @Override public Value call(Value param) throws OrangRuntimeException { - var newParams = new ArrayList<>(params); - newParams.add(param); - if (newParams.size() == args.size()) { - var newDefs = new HashMap(); - for (var i = 0; i < args.size(); i++) { - newDefs.put(args.get(i), newParams.get(i)); - } - var eval = new Evaluator(ImmutableScope.of(scope, newDefs)); - return eval.visit(body); - } - - return new PartialFunction(scope, args, Collections.unmodifiableList(newParams), body); + return PartialFunction.of(scope, remainingArgs, body, param); } } diff --git a/orang/src/main/java/lv/enes/orang/value/Value.java b/orang/src/main/java/lv/enes/orang/value/Value.java index fa8275c..0816dc2 100644 --- a/orang/src/main/java/lv/enes/orang/value/Value.java +++ b/orang/src/main/java/lv/enes/orang/value/Value.java @@ -3,8 +3,7 @@ package lv.enes.orang.value; import lv.enes.orang.OrangRuntimeException; public sealed interface Value - permits Array, BuiltinFunction, Function, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction, - PartialFunction, Undefined { + permits Array, BuiltinFunction, Function, Nothing, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction, PartialFunction, Undefined { String typeName(); String stringify(); diff --git a/orang/src/main/resources/lv/enes/orang/prelude.orang b/orang/src/main/resources/lv/enes/orang/prelude.orang index d29eef3..5b1d2d1 100644 --- a/orang/src/main/resources/lv/enes/orang/prelude.orang +++ b/orang/src/main/resources/lv/enes/orang/prelude.orang @@ -4,10 +4,10 @@ def len arrayOrString = __builtin_len arrayOrString def parseInt stringOrInt = __builtin_parseInt stringOrInt def print anything = __builtin_print anything def randInt min max = __builtin_randInt min max -def readLn _ = __builtin_readLn () +def readLn () = __builtin_readLn () # standard library -def readInt _ = parseInt (readLn ()) +def readInt () = parseInt (readLn ()) def printLn x = do print x; print "\n"; x end # repl intro :) -- cgit v1.2.3