From 0aeff1a757908bdc8972bca20330752858cbb903 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Fri, 16 Aug 2024 23:29:12 +0300 Subject: Big Refactoring, added support for comments --- ast/build.gradle.kts | 20 ++ .../java/lv/enes/orang/ast/ArrayExpression.java | 12 + .../java/lv/enes/orang/ast/BinaryExpression.java | 38 +++ .../java/lv/enes/orang/ast/BooleanLiteral.java | 10 + .../java/lv/enes/orang/ast/CallExpression.java | 10 + ast/src/main/java/lv/enes/orang/ast/DefSpec.java | 6 + .../main/java/lv/enes/orang/ast/Definition.java | 10 + .../main/java/lv/enes/orang/ast/DoExpression.java | 14 + .../main/java/lv/enes/orang/ast/Expression.java | 7 + .../lv/enes/orang/ast/ExpressionStatement.java | 10 + .../java/lv/enes/orang/ast/ExpressionVisitor.java | 22 ++ .../main/java/lv/enes/orang/ast/FnExpression.java | 11 + .../java/lv/enes/orang/ast/IfElseExpression.java | 10 + .../main/java/lv/enes/orang/ast/IntLiteral.java | 10 + .../java/lv/enes/orang/ast/LetInExpression.java | 14 + ast/src/main/java/lv/enes/orang/ast/Program.java | 12 + ast/src/main/java/lv/enes/orang/ast/Statement.java | 7 + .../java/lv/enes/orang/ast/StatementVisitor.java | 13 + .../main/java/lv/enes/orang/ast/StringLiteral.java | 10 + .../java/lv/enes/orang/ast/UnaryExpression.java | 16 ++ .../java/lv/enes/orang/ast/VariableExpression.java | 10 + ast/src/main/java/module-info.java | 6 + build.gradle.kts | 47 ---- core/build.gradle.kts | 15 ++ .../java/lv/enes/orang/core/OrangException.java | 11 + core/src/main/java/module-info.java | 3 + grammar.bnf | 2 + orang/build.gradle.kts | 53 ++++ orang/src/main/java/lv/enes/orang/Builtins.java | 95 +++++++ orang/src/main/java/lv/enes/orang/Codepoint.java | 28 ++ orang/src/main/java/lv/enes/orang/Evaluator.java | 144 ++++++++++ .../main/java/lv/enes/orang/ImmutableScope.java | 45 ++++ orang/src/main/java/lv/enes/orang/Lexer.java | 174 ++++++++++++ orang/src/main/java/lv/enes/orang/Main.java | 79 ++++++ .../src/main/java/lv/enes/orang/MutableScope.java | 24 ++ .../java/lv/enes/orang/OrangRuntimeException.java | 13 + orang/src/main/java/lv/enes/orang/Parser.java | 292 +++++++++++++++++++++ .../main/java/lv/enes/orang/ParserException.java | 9 + .../main/java/lv/enes/orang/PeekableStream.java | 38 +++ orang/src/main/java/lv/enes/orang/Scope.java | 34 +++ orang/src/main/java/lv/enes/orang/State.java | 12 + orang/src/main/java/lv/enes/orang/Token.java | 15 ++ orang/src/main/java/lv/enes/orang/TokenType.java | 91 +++++++ orang/src/main/java/lv/enes/orang/value/Array.java | 65 +++++ .../java/lv/enes/orang/value/BuiltinFunction.java | 31 +++ .../main/java/lv/enes/orang/value/Function.java | 31 +++ .../java/lv/enes/orang/value/OrangBoolean.java | 55 ++++ .../java/lv/enes/orang/value/OrangInteger.java | 70 +++++ .../main/java/lv/enes/orang/value/OrangString.java | 49 ++++ .../enes/orang/value/PartialBuiltinFunction.java | 32 +++ .../java/lv/enes/orang/value/PartialFunction.java | 40 +++ .../main/java/lv/enes/orang/value/Undefined.java | 21 ++ orang/src/main/java/lv/enes/orang/value/Value.java | 77 ++++++ orang/src/main/java/module-info.java | 14 + .../src/main/resources/lv/enes/orang/prelude.orang | 24 ++ settings.gradle.kts | 1 + src/main/java/lv/enes/orang/Builtins.java | 95 ------- src/main/java/lv/enes/orang/Codepoint.java | 24 -- src/main/java/lv/enes/orang/Lexer.java | 168 ------------ src/main/java/lv/enes/orang/Main.java | 77 ------ src/main/java/lv/enes/orang/NonEmptyList.java | 28 -- src/main/java/lv/enes/orang/OrangException.java | 11 - .../java/lv/enes/orang/OrangRuntimeException.java | 11 - src/main/java/lv/enes/orang/Parser.java | 291 -------------------- src/main/java/lv/enes/orang/ParserException.java | 7 - src/main/java/lv/enes/orang/PeekableStream.java | 38 --- src/main/java/lv/enes/orang/Scope.java | 54 ---- src/main/java/lv/enes/orang/State.java | 12 - src/main/java/lv/enes/orang/Token.java | 15 -- src/main/java/lv/enes/orang/TokenType.java | 91 ------- .../java/lv/enes/orang/ast/ArrayExpression.java | 21 -- .../java/lv/enes/orang/ast/BinaryExpression.java | 53 ---- .../java/lv/enes/orang/ast/BooleanLiteral.java | 12 - .../java/lv/enes/orang/ast/CallExpression.java | 12 - src/main/java/lv/enes/orang/ast/DefSpec.java | 6 - src/main/java/lv/enes/orang/ast/Definition.java | 15 -- src/main/java/lv/enes/orang/ast/DoExpression.java | 19 -- src/main/java/lv/enes/orang/ast/Expression.java | 14 - src/main/java/lv/enes/orang/ast/FnExpression.java | 14 - .../java/lv/enes/orang/ast/IfElseExpression.java | 22 -- src/main/java/lv/enes/orang/ast/IntLiteral.java | 12 - .../java/lv/enes/orang/ast/LetInExpression.java | 27 -- src/main/java/lv/enes/orang/ast/Program.java | 17 -- src/main/java/lv/enes/orang/ast/Statement.java | 8 - src/main/java/lv/enes/orang/ast/StringLiteral.java | 13 - .../java/lv/enes/orang/ast/UnaryExpression.java | 23 -- .../java/lv/enes/orang/ast/VariableExpression.java | 12 - src/main/java/lv/enes/orang/value/Array.java | 65 ----- .../java/lv/enes/orang/value/BuiltinFunction.java | 31 --- src/main/java/lv/enes/orang/value/Function.java | 28 -- .../java/lv/enes/orang/value/OrangBoolean.java | 55 ---- .../java/lv/enes/orang/value/OrangInteger.java | 70 ----- src/main/java/lv/enes/orang/value/OrangString.java | 49 ---- .../enes/orang/value/PartialBuiltinFunction.java | 32 --- .../java/lv/enes/orang/value/PartialFunction.java | 37 --- src/main/java/lv/enes/orang/value/Undefined.java | 21 -- src/main/java/lv/enes/orang/value/Value.java | 77 ------ src/main/java/module-info.java | 9 - src/main/resources/lv/enes/orang/prelude.orang | 20 -- utils/build.gradle.kts | 20 ++ .../java/lv/enes/orang/utils/NonEmptyList.java | 28 ++ utils/src/main/java/module-info.java | 5 + 102 files changed, 2018 insertions(+), 1763 deletions(-) create mode 100644 ast/build.gradle.kts create mode 100644 ast/src/main/java/lv/enes/orang/ast/ArrayExpression.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/BinaryExpression.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/BooleanLiteral.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/CallExpression.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/DefSpec.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/Definition.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/DoExpression.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/Expression.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/ExpressionStatement.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/FnExpression.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/IfElseExpression.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/IntLiteral.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/LetInExpression.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/Program.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/Statement.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/StatementVisitor.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/StringLiteral.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/UnaryExpression.java create mode 100644 ast/src/main/java/lv/enes/orang/ast/VariableExpression.java create mode 100644 ast/src/main/java/module-info.java delete mode 100644 build.gradle.kts create mode 100644 core/build.gradle.kts create mode 100644 core/src/main/java/lv/enes/orang/core/OrangException.java create mode 100644 core/src/main/java/module-info.java create mode 100644 orang/build.gradle.kts create mode 100644 orang/src/main/java/lv/enes/orang/Builtins.java create mode 100644 orang/src/main/java/lv/enes/orang/Codepoint.java create mode 100644 orang/src/main/java/lv/enes/orang/Evaluator.java create mode 100644 orang/src/main/java/lv/enes/orang/ImmutableScope.java create mode 100644 orang/src/main/java/lv/enes/orang/Lexer.java create mode 100644 orang/src/main/java/lv/enes/orang/Main.java create mode 100644 orang/src/main/java/lv/enes/orang/MutableScope.java create mode 100644 orang/src/main/java/lv/enes/orang/OrangRuntimeException.java create mode 100644 orang/src/main/java/lv/enes/orang/Parser.java create mode 100644 orang/src/main/java/lv/enes/orang/ParserException.java create mode 100644 orang/src/main/java/lv/enes/orang/PeekableStream.java create mode 100644 orang/src/main/java/lv/enes/orang/Scope.java create mode 100644 orang/src/main/java/lv/enes/orang/State.java create mode 100644 orang/src/main/java/lv/enes/orang/Token.java create mode 100644 orang/src/main/java/lv/enes/orang/TokenType.java create mode 100644 orang/src/main/java/lv/enes/orang/value/Array.java create mode 100644 orang/src/main/java/lv/enes/orang/value/BuiltinFunction.java create mode 100644 orang/src/main/java/lv/enes/orang/value/Function.java create mode 100644 orang/src/main/java/lv/enes/orang/value/OrangBoolean.java create mode 100644 orang/src/main/java/lv/enes/orang/value/OrangInteger.java create mode 100644 orang/src/main/java/lv/enes/orang/value/OrangString.java create mode 100644 orang/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java create mode 100644 orang/src/main/java/lv/enes/orang/value/PartialFunction.java create mode 100644 orang/src/main/java/lv/enes/orang/value/Undefined.java create mode 100644 orang/src/main/java/lv/enes/orang/value/Value.java create mode 100644 orang/src/main/java/module-info.java create mode 100644 orang/src/main/resources/lv/enes/orang/prelude.orang delete mode 100644 src/main/java/lv/enes/orang/Builtins.java delete mode 100644 src/main/java/lv/enes/orang/Codepoint.java delete mode 100644 src/main/java/lv/enes/orang/Lexer.java delete mode 100644 src/main/java/lv/enes/orang/Main.java delete mode 100644 src/main/java/lv/enes/orang/NonEmptyList.java delete mode 100644 src/main/java/lv/enes/orang/OrangException.java delete mode 100644 src/main/java/lv/enes/orang/OrangRuntimeException.java delete mode 100644 src/main/java/lv/enes/orang/Parser.java delete mode 100644 src/main/java/lv/enes/orang/ParserException.java delete mode 100644 src/main/java/lv/enes/orang/PeekableStream.java delete mode 100644 src/main/java/lv/enes/orang/Scope.java delete mode 100644 src/main/java/lv/enes/orang/State.java delete mode 100644 src/main/java/lv/enes/orang/Token.java delete mode 100644 src/main/java/lv/enes/orang/TokenType.java delete mode 100644 src/main/java/lv/enes/orang/ast/ArrayExpression.java delete mode 100644 src/main/java/lv/enes/orang/ast/BinaryExpression.java delete mode 100644 src/main/java/lv/enes/orang/ast/BooleanLiteral.java delete mode 100644 src/main/java/lv/enes/orang/ast/CallExpression.java delete mode 100644 src/main/java/lv/enes/orang/ast/DefSpec.java delete mode 100644 src/main/java/lv/enes/orang/ast/Definition.java delete mode 100644 src/main/java/lv/enes/orang/ast/DoExpression.java delete mode 100644 src/main/java/lv/enes/orang/ast/Expression.java delete mode 100644 src/main/java/lv/enes/orang/ast/FnExpression.java delete mode 100644 src/main/java/lv/enes/orang/ast/IfElseExpression.java delete mode 100644 src/main/java/lv/enes/orang/ast/IntLiteral.java delete mode 100644 src/main/java/lv/enes/orang/ast/LetInExpression.java delete mode 100644 src/main/java/lv/enes/orang/ast/Program.java delete mode 100644 src/main/java/lv/enes/orang/ast/Statement.java delete mode 100644 src/main/java/lv/enes/orang/ast/StringLiteral.java delete mode 100644 src/main/java/lv/enes/orang/ast/UnaryExpression.java delete mode 100644 src/main/java/lv/enes/orang/ast/VariableExpression.java delete mode 100644 src/main/java/lv/enes/orang/value/Array.java delete mode 100644 src/main/java/lv/enes/orang/value/BuiltinFunction.java delete mode 100644 src/main/java/lv/enes/orang/value/Function.java delete mode 100644 src/main/java/lv/enes/orang/value/OrangBoolean.java delete mode 100644 src/main/java/lv/enes/orang/value/OrangInteger.java delete mode 100644 src/main/java/lv/enes/orang/value/OrangString.java delete mode 100644 src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java delete mode 100644 src/main/java/lv/enes/orang/value/PartialFunction.java delete mode 100644 src/main/java/lv/enes/orang/value/Undefined.java delete mode 100644 src/main/java/lv/enes/orang/value/Value.java delete mode 100644 src/main/java/module-info.java delete mode 100644 src/main/resources/lv/enes/orang/prelude.orang create mode 100644 utils/build.gradle.kts create mode 100644 utils/src/main/java/lv/enes/orang/utils/NonEmptyList.java create mode 100644 utils/src/main/java/module-info.java diff --git a/ast/build.gradle.kts b/ast/build.gradle.kts new file mode 100644 index 0000000..357e080 --- /dev/null +++ b/ast/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + java +} + +dependencies { + implementation(project(":core")) + implementation(project(":utils")) +} + +java { + sourceCompatibility = JavaVersion.VERSION_22 + targetCompatibility = JavaVersion.VERSION_22 + toolchain { + languageVersion = JavaLanguageVersion.of(22) + } +} + +tasks.withType { + options.compilerArgs.add("--enable-preview") +} \ No newline at end of file diff --git a/ast/src/main/java/lv/enes/orang/ast/ArrayExpression.java b/ast/src/main/java/lv/enes/orang/ast/ArrayExpression.java new file mode 100644 index 0000000..acd1a36 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/ArrayExpression.java @@ -0,0 +1,12 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +import java.util.List; + +public record ArrayExpression(List items) implements Expression { + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitArray(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/BinaryExpression.java b/ast/src/main/java/lv/enes/orang/ast/BinaryExpression.java new file mode 100644 index 0000000..fb8bac5 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/BinaryExpression.java @@ -0,0 +1,38 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record BinaryExpression(Operator operator, Expression lhs, Expression rhs) implements Expression { + public enum Operator { + EQUALS, + NOT_EQUALS, + GT, + GTE, + LT, + LTE, + ADD, + SUBTRACT, + MULTIPLY, + DIVIDE, + ; + + public boolean bindsStrongerThan(Operator other) { + return switch (this) { + case EQUALS, NOT_EQUALS, GT, GTE, LT, LTE -> false; + case ADD, SUBTRACT -> switch (other) { + case EQUALS, NOT_EQUALS, GT, GTE, LTE -> true; + default -> false; + }; + case MULTIPLY, DIVIDE -> switch (other) { + case EQUALS, NOT_EQUALS, GT, GTE, LT, LTE, ADD, SUBTRACT -> true; + default -> false; + }; + }; + } + } + + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitBinaryExpression(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/BooleanLiteral.java b/ast/src/main/java/lv/enes/orang/ast/BooleanLiteral.java new file mode 100644 index 0000000..16bf176 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/BooleanLiteral.java @@ -0,0 +1,10 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record BooleanLiteral(boolean value) implements Expression { + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitBoolean(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/CallExpression.java b/ast/src/main/java/lv/enes/orang/ast/CallExpression.java new file mode 100644 index 0000000..38ff7bd --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/CallExpression.java @@ -0,0 +1,10 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record CallExpression(Expression callee, Expression arg) implements Expression { + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitCallExpression(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/DefSpec.java b/ast/src/main/java/lv/enes/orang/ast/DefSpec.java new file mode 100644 index 0000000..2233d3f --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/DefSpec.java @@ -0,0 +1,6 @@ +package lv.enes.orang.ast; + +import java.util.List; + +public record DefSpec(String name, List args) { +} diff --git a/ast/src/main/java/lv/enes/orang/ast/Definition.java b/ast/src/main/java/lv/enes/orang/ast/Definition.java new file mode 100644 index 0000000..b41cb1f --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/Definition.java @@ -0,0 +1,10 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record Definition(String name, Expression body) implements Statement { + @Override + public R accept(StatementVisitor visitor) throws E { + return visitor.visitDefinition(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/DoExpression.java b/ast/src/main/java/lv/enes/orang/ast/DoExpression.java new file mode 100644 index 0000000..47eaddd --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/DoExpression.java @@ -0,0 +1,14 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +import java.util.List; + +public record DoExpression(List body) implements Expression { + // assert body.!isEmpty() + + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitDoExpression(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/Expression.java b/ast/src/main/java/lv/enes/orang/ast/Expression.java new file mode 100644 index 0000000..a3e2acc --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/Expression.java @@ -0,0 +1,7 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public interface Expression { + R accept(ExpressionVisitor visitor) throws E; +} diff --git a/ast/src/main/java/lv/enes/orang/ast/ExpressionStatement.java b/ast/src/main/java/lv/enes/orang/ast/ExpressionStatement.java new file mode 100644 index 0000000..2a9dae1 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/ExpressionStatement.java @@ -0,0 +1,10 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record ExpressionStatement(Expression expr) implements Statement { + @Override + public R accept(StatementVisitor visitor) throws E { + return visitor.visitExpression(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java b/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java new file mode 100644 index 0000000..ddb1157 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java @@ -0,0 +1,22 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public interface ExpressionVisitor { + default R visit(Expression expression) throws E { + return expression.accept(this); + } + + R visitArray(ArrayExpression array) throws E; + R visitBoolean(BooleanLiteral expr) throws E; + R visitBinaryExpression(BinaryExpression expr) throws E; + R visitCallExpression(CallExpression expr) throws E; + R visitDoExpression(DoExpression expr) throws E; + R visitFnExpression(FnExpression expr) throws E; + R visitIfElseExpression(IfElseExpression expr) throws E; + R visitIntLiteral(IntLiteral expr) throws E; + R visitLetInExpression(LetInExpression expr) throws E; + R visitStringLiteral(StringLiteral expr) throws E; + R visitUnaryExpression(UnaryExpression expr) throws E; + R visitVariable(VariableExpression expr) 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 new file mode 100644 index 0000000..68f43af --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/FnExpression.java @@ -0,0 +1,11 @@ +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 { + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitFnExpression(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/IfElseExpression.java b/ast/src/main/java/lv/enes/orang/ast/IfElseExpression.java new file mode 100644 index 0000000..86e48d4 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/IfElseExpression.java @@ -0,0 +1,10 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record IfElseExpression(Expression condition, Expression trueBranch, Expression falseBranch) implements Expression { + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitIfElseExpression(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/IntLiteral.java b/ast/src/main/java/lv/enes/orang/ast/IntLiteral.java new file mode 100644 index 0000000..12333c9 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/IntLiteral.java @@ -0,0 +1,10 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record IntLiteral(int value) implements Expression { + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitIntLiteral(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/LetInExpression.java b/ast/src/main/java/lv/enes/orang/ast/LetInExpression.java new file mode 100644 index 0000000..b292f08 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/LetInExpression.java @@ -0,0 +1,14 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +import java.util.List; + +public record LetInExpression(List bindings, Expression body) implements Expression { + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitLetInExpression(this); + } + + public record Binding(String name, Expression value) {} +} diff --git a/ast/src/main/java/lv/enes/orang/ast/Program.java b/ast/src/main/java/lv/enes/orang/ast/Program.java new file mode 100644 index 0000000..9b38f3b --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/Program.java @@ -0,0 +1,12 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +import java.util.List; + +public record Program(List statements) implements Statement { + @Override + public R accept(StatementVisitor visitor) throws E { + return visitor.visitProgram(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/Statement.java b/ast/src/main/java/lv/enes/orang/ast/Statement.java new file mode 100644 index 0000000..283fd40 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/Statement.java @@ -0,0 +1,7 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public interface Statement { + R accept(StatementVisitor visitor) throws E; +} diff --git a/ast/src/main/java/lv/enes/orang/ast/StatementVisitor.java b/ast/src/main/java/lv/enes/orang/ast/StatementVisitor.java new file mode 100644 index 0000000..fa3b2f6 --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/StatementVisitor.java @@ -0,0 +1,13 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public interface StatementVisitor { + default R visit(Statement stmt) throws E { + return stmt.accept(this); + } + + R visitDefinition(Definition def) throws E; + R visitExpression(ExpressionStatement expr) throws E; + R visitProgram(Program program) throws E; +} diff --git a/ast/src/main/java/lv/enes/orang/ast/StringLiteral.java b/ast/src/main/java/lv/enes/orang/ast/StringLiteral.java new file mode 100644 index 0000000..5c47b7e --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/StringLiteral.java @@ -0,0 +1,10 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record StringLiteral(String value) implements Expression { + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitStringLiteral(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/UnaryExpression.java b/ast/src/main/java/lv/enes/orang/ast/UnaryExpression.java new file mode 100644 index 0000000..d96ae6d --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/UnaryExpression.java @@ -0,0 +1,16 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record UnaryExpression(Operator operator, Expression child) implements Expression { + public enum Operator { + PLUS, + NEGATE, + NOT, + } + + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitUnaryExpression(this); + } +} diff --git a/ast/src/main/java/lv/enes/orang/ast/VariableExpression.java b/ast/src/main/java/lv/enes/orang/ast/VariableExpression.java new file mode 100644 index 0000000..615857b --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/VariableExpression.java @@ -0,0 +1,10 @@ +package lv.enes.orang.ast; + +import lv.enes.orang.core.OrangException; + +public record VariableExpression(String name) implements Expression { + @Override + public R accept(ExpressionVisitor visitor) throws E { + return visitor.visitVariable(this); + } +} diff --git a/ast/src/main/java/module-info.java b/ast/src/main/java/module-info.java new file mode 100644 index 0000000..7ebe37b --- /dev/null +++ b/ast/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module lv.enes.orang.ast { + exports lv.enes.orang.ast; + + requires lv.enes.orang.core; + requires lv.enes.orang.utils; +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 212943a..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -plugins { - application - java - id("io.freefair.lombok") version "8.6" - id("org.beryx.jlink") version "3.0.1" -} - -val slf4jVersion = "2.0.13" -val jakartaAnnotationVersion = "3.0.0" - -group = "lv.enes" -version = "0.1-SNAPSHOT" - -repositories { - mavenCentral() -} - -dependencies { - implementation("jakarta.annotation:jakarta.annotation-api:$jakartaAnnotationVersion") - implementation("org.slf4j:slf4j-api:$slf4jVersion") - implementation("org.slf4j:slf4j-simple:$slf4jVersion") -} - -java { - sourceCompatibility = JavaVersion.VERSION_22 - targetCompatibility = JavaVersion.VERSION_22 - toolchain { - languageVersion = JavaLanguageVersion.of(22) - } -} - -tasks.withType { - options.compilerArgs.add("--enable-preview") -} - -application { - mainModule = "lv.enes.orang" - mainClass = "lv.enes.orang.Main" -} - -jlink { - options.set(listOf("--strip-debug", "--compress", "2", "--no-header-files", "--no-man-pages")) - launcher { - name = "orang" - jvmArgs = listOf("--enable-preview") - } -} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 0000000..19bbafc --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + java +} + +java { + sourceCompatibility = JavaVersion.VERSION_22 + targetCompatibility = JavaVersion.VERSION_22 + toolchain { + languageVersion = JavaLanguageVersion.of(22) + } +} + +tasks.withType { + options.compilerArgs.add("--enable-preview") +} \ No newline at end of file diff --git a/core/src/main/java/lv/enes/orang/core/OrangException.java b/core/src/main/java/lv/enes/orang/core/OrangException.java new file mode 100644 index 0000000..d634fca --- /dev/null +++ b/core/src/main/java/lv/enes/orang/core/OrangException.java @@ -0,0 +1,11 @@ +package lv.enes.orang.core; + +public abstract class OrangException extends Exception { + protected OrangException(String message) { + super(message); + } + + protected OrangException(Throwable cause) { + super(cause); + } +} diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java new file mode 100644 index 0000000..b8b8703 --- /dev/null +++ b/core/src/main/java/module-info.java @@ -0,0 +1,3 @@ +module lv.enes.orang.core { + exports lv.enes.orang.core; +} \ No newline at end of file diff --git a/grammar.bnf b/grammar.bnf index 256e58a..7c18f8f 100644 --- a/grammar.bnf +++ b/grammar.bnf @@ -1,3 +1,5 @@ +// Comments are introduced by # and terminated by newline + program ::= (statement ';'?)*; statement ::= definition | expression; definition ::= 'def' def-spec '=' expression; diff --git a/orang/build.gradle.kts b/orang/build.gradle.kts new file mode 100644 index 0000000..f70609e --- /dev/null +++ b/orang/build.gradle.kts @@ -0,0 +1,53 @@ +plugins { + application + java + id("io.freefair.lombok") version "8.6" + id("org.beryx.jlink") version "3.0.1" +} + +val asmVersion = "9.7" +val slf4jVersion = "2.0.13" +val jakartaAnnotationVersion = "3.0.0" + +group = "lv.enes" +version = "0.1-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.ow2.asm:asm:$asmVersion") + implementation("jakarta.annotation:jakarta.annotation-api:$jakartaAnnotationVersion") + implementation("org.slf4j:slf4j-api:$slf4jVersion") + implementation("org.slf4j:slf4j-simple:$slf4jVersion") + + implementation(project(":ast")) + implementation(project(":core")) + implementation(project(":utils")) +} + +java { + sourceCompatibility = JavaVersion.VERSION_22 + targetCompatibility = JavaVersion.VERSION_22 + toolchain { + languageVersion = JavaLanguageVersion.of(22) + } +} + +tasks.withType { + options.compilerArgs.add("--enable-preview") +} + +application { + mainModule = "lv.enes.orang" + mainClass = "lv.enes.orang.Main" +} + +jlink { + options.set(listOf("--strip-debug", "--compress", "2", "--no-header-files", "--no-man-pages")) + launcher { + name = "orang" + jvmArgs = listOf("--enable-preview") + } +} \ No newline at end of file diff --git a/orang/src/main/java/lv/enes/orang/Builtins.java b/orang/src/main/java/lv/enes/orang/Builtins.java new file mode 100644 index 0000000..0a76387 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/Builtins.java @@ -0,0 +1,95 @@ +package lv.enes.orang; + +import lombok.extern.slf4j.Slf4j; +import lv.enes.orang.value.*; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import static lv.enes.orang.State.STDIN; +import static lv.enes.orang.State.STDOUT; + +@Slf4j +public final class Builtins { + public static final Map BUILTINS = Map.ofEntries( + Map.entry("__builtin_isRepl", new BuiltinFunction(1, Builtins::isRepl)), + Map.entry("__builtin_len", new BuiltinFunction(1, Builtins::len)), + Map.entry("__builtin_parseInt", new BuiltinFunction(1, Builtins::parseInt)), + Map.entry("__builtin_print", new BuiltinFunction(1, Builtins::print)), + Map.entry("__builtin_randInt", new BuiltinFunction(2, Builtins::randInt)), + Map.entry("__builtin_readLn", new BuiltinFunction(1, Builtins::readLn)) + ); + + private Builtins() {} + + private static Value isRepl(List args) { + return OrangBoolean.TRUE; + } + + private static Value len(List args) throws OrangRuntimeException { + assert args.size() == 1; + var arg = args.getFirst(); + + if (arg instanceof OrangString(var value)) { + return new OrangInteger(value.length()); + } else if (arg instanceof Array(var items)) { + return new OrangInteger(items.size()); + } else { + throw new OrangRuntimeException(STR."len \{arg.typeName()} is not implemented"); + } + } + + private static Value parseInt(List args) throws OrangRuntimeException { + assert args.size() == 1; + + var arg = args.getFirst(); + if (arg instanceof OrangInteger) { + log.warn("Attempted to parseInt an Integer!"); + return arg; + } else if (arg instanceof OrangString(var value)) { + try { + return new OrangInteger(Integer.parseInt(value.trim())); + } catch (NumberFormatException ex) { + throw new OrangRuntimeException(ex); + } + } else { + throw new OrangRuntimeException(STR."parseInt \{arg.typeName()} is not implemented"); + } + } + + private static Value print(List args) { + assert args.size() == 1; + STDOUT.print(args.getFirst().display()); + STDOUT.flush(); + return args.getFirst(); + } + + private static Value randInt(List args) throws OrangRuntimeException { + assert args.size() == 2; + + var minv = args.getFirst(); + var maxv = args.get(1); + if (minv instanceof OrangInteger(var min) && maxv instanceof OrangInteger(var max)) { + return new OrangInteger(new Random().nextInt(min, max)); + } else { + throw new OrangRuntimeException(STR."randInt \{minv.typeName()} \{maxv.typeName()} is not implemented"); + } + } + + 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) { + throw new OrangRuntimeException(e); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/Codepoint.java b/orang/src/main/java/lv/enes/orang/Codepoint.java new file mode 100644 index 0000000..7157062 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/Codepoint.java @@ -0,0 +1,28 @@ +package lv.enes.orang; + +public record Codepoint(int cp) { + @Override + public String toString() { + return Character.toString(cp); + } + + public boolean isIdentInitial() { + return Character.isLetter(cp) || cp == '_'; + } + + public boolean isIdentFinal() { + return isIdentInitial() || Character.isDigit(cp); + } + + public boolean isNewline() { + return cp == '\n'; + } + + public boolean isNumeral() { + return Character.isDigit(cp); + } + + public boolean isWhitespace() { + return Character.isWhitespace(cp); + } +} diff --git a/orang/src/main/java/lv/enes/orang/Evaluator.java b/orang/src/main/java/lv/enes/orang/Evaluator.java new file mode 100644 index 0000000..e2e96ff --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/Evaluator.java @@ -0,0 +1,144 @@ +package lv.enes.orang; + +import lv.enes.orang.ast.*; +import lv.enes.orang.value.*; + +import java.util.ArrayList; +import java.util.Collections; + +public record Evaluator(Scope scope, Value lastResult) implements ExpressionVisitor, StatementVisitor { + public Evaluator() { + this(Scope.topLevel()); + } + + public Evaluator(Scope scope) { + this(scope, Undefined.INSTANCE); + } + + @Override + public Value visitArray(ArrayExpression expr) throws OrangRuntimeException { + var values = new ArrayList(expr.items().size()); + for (var item : expr.items()) { + values.add(visit(item)); + } + values.trimToSize(); + return new Array(Collections.unmodifiableList(values)); + } + + @Override + public Value visitBinaryExpression(BinaryExpression expr) throws OrangRuntimeException { + var lhs = visit(expr.lhs()); + var rhs = visit(expr.rhs()); + return switch (expr.operator()) { + case EQUALS -> lhs.orangEquals(rhs); + case NOT_EQUALS -> lhs.notEquals(rhs); + case GT -> lhs.greaterThan(rhs); + case GTE -> lhs.greaterThanOrEqual(rhs); + case LT -> lhs.lessThan(rhs); + case LTE -> lhs.lessThanOrEqual(rhs); + case ADD -> lhs.add(rhs); + case SUBTRACT -> lhs.subtract(rhs); + case MULTIPLY -> lhs.multiply(rhs); + case DIVIDE -> lhs.divide(rhs); + }; + } + + @Override + public Value visitBoolean(BooleanLiteral expr) { + return OrangBoolean.of(expr.value()); + } + + @Override + public Value visitCallExpression(CallExpression expr) throws OrangRuntimeException { + var arg = visit(expr.arg()); + return visit(expr.callee()).call(arg); + } + + @Override + public Evaluator visitDefinition(Definition def) throws OrangRuntimeException { + var newScope = MutableScope.of(scope, def.name(), Undefined.INSTANCE); + var newEvaluator = new Evaluator(newScope); + newScope.setDefinition(def.name(), newEvaluator.visit(def.body())); + return newEvaluator; + } + + @Override + public Value visitDoExpression(DoExpression expr) throws OrangRuntimeException { + Value value = Undefined.INSTANCE; + for (var child : expr.body()) { + value = visit(child); + } + return value; + } + + @Override + public Evaluator visitExpression(ExpressionStatement expr) throws OrangRuntimeException { + return new Evaluator(this.scope(), visit(expr.expr())); + } + + @Override + public Value visitFnExpression(FnExpression expr) { + return new Function(this.scope, expr.args(), expr.body()); + } + + @Override + public Value visitIfElseExpression(IfElseExpression expr) throws OrangRuntimeException { + var cond = visit(expr.condition()); + if (cond instanceof OrangBoolean value) { + if (value.value()) { + return visit(expr.trueBranch()); + } else { + return visit(expr.falseBranch()); + } + } else { + throw new OrangRuntimeException(STR."Condition in an if-else statement should be a boolean not a \{cond.typeName()}"); + } + } + + @Override + public Value visitIntLiteral(IntLiteral expr) { + return new OrangInteger(expr.value()); + } + + @Override + public Value visitLetInExpression(LetInExpression expr) throws OrangRuntimeException { + var newScope = MutableScope.of(scope); + for (var binding : expr.bindings()) { + newScope.setDefinition(binding.name(), Undefined.INSTANCE); + } + var newEvaluator = new Evaluator(newScope); + for (var binding : expr.bindings()) { + newScope.setDefinition(binding.name(), newEvaluator.visit(binding.value())); + } + return newEvaluator.visit(expr.body()); + } + + @Override + public Evaluator visitProgram(Program prog) throws OrangRuntimeException { + var evaluator = this; + for (var statement : prog.statements()) { + evaluator = evaluator.visit(statement); + } + return evaluator; + } + + @Override + public Value visitUnaryExpression(UnaryExpression expr) throws OrangRuntimeException { + var child = visit(expr.child()); + return switch (expr.operator()) { + case PLUS -> child.plus(); + case NEGATE -> child.negate(); + case NOT -> child.not(); + }; + } + + @Override + public Value visitStringLiteral(StringLiteral expr) { + return new OrangString(expr.value()); + } + + @Override + public Value visitVariable(VariableExpression expr) throws OrangRuntimeException { + return scope.getDefinition(expr.name()); + } +} diff --git a/orang/src/main/java/lv/enes/orang/ImmutableScope.java b/orang/src/main/java/lv/enes/orang/ImmutableScope.java new file mode 100644 index 0000000..9cb6138 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/ImmutableScope.java @@ -0,0 +1,45 @@ +package lv.enes.orang; + +import lv.enes.orang.value.Value; + +import java.util.HashMap; +import java.util.Map; + +public class ImmutableScope extends Scope { + public static ImmutableScope of(Scope parent) { + if (parent instanceof ImmutableScope imm) { + return imm; + } + return ImmutableScope.of(parent, Map.of()); + } + + public static ImmutableScope of(Scope parent, String key, Value value) { + return ImmutableScope.of(parent, Map.of(key, value)); + } + + public static ImmutableScope of(Scope parent, Map definitions) { + return new ImmutableScope(parent, definitions).maybeFlattened(); + } + + protected ImmutableScope(Scope parent, Map definitions) { + super(parent, Map.copyOf(definitions)); + } + + public ImmutableScope maybeFlattened() { + if (depth > MAX_DEPTH) { + return flattened(); + } + return this; + } + + private ImmutableScope flattened() { + if (parent instanceof ImmutableScope immParent) { + var flatParent = immParent.flattened(); + var newDefs = new HashMap<>(flatParent.definitions); + newDefs.putAll(definitions); + return new ImmutableScope(flatParent.parent, newDefs); + } + + return this; + } +} diff --git a/orang/src/main/java/lv/enes/orang/Lexer.java b/orang/src/main/java/lv/enes/orang/Lexer.java new file mode 100644 index 0000000..d4e1533 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/Lexer.java @@ -0,0 +1,174 @@ +package lv.enes.orang; + +import java.io.*; +import java.util.Iterator; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class Lexer implements Iterator { + private final PeekableStream input; + + public Lexer(InputStream input) { + this(new InputStreamReader(input)); + } + + public Lexer(Reader input) { + var cpStream = new BufferedReader(input) + .lines() + .flatMapToInt(str -> IntStream.concat(str.codePoints(), IntStream.of('\n'))) + .mapToObj(Codepoint::new); + var theEof = Stream.of(new Codepoint(-1)); + this.input = new PeekableStream<>(Stream.concat(cpStream, theEof).iterator()); + } + + public Lexer(String input) { + this(new StringReader(input)); + } + + private boolean hasNext = true; + + @Override + public Token next() { + var tok = nextToken(); + if (tok.type() == TokenType.EOF) { + hasNext = false; + } + return tok; + } + + @Override + public boolean hasNext() { + return hasNext; + } + + private Token nextToken() { + skipWhitespace(); + return switch (input.peek().cp()) { + case -1 -> new Token(TokenType.EOF, ""); + + case '*' -> new Token(TokenType.ASTERISK, input.next()); + case '!' -> new Token(TokenType.BANG, input.next()); + case '[' -> new Token(TokenType.BRACKET_LEFT, input.next()); + case ']' -> new Token(TokenType.BRACKET_RIGHT, input.next()); + case ',' -> new Token(TokenType.COMMA, input.next()); + case '=' -> new Token(TokenType.EQUAL, input.next()); + case '>' -> { + var first = input.next(); + if (input.peek().cp() == '=') { + yield new Token(TokenType.GREATER_EQUAL, first, input.next()); + } else { + yield new Token(TokenType.GREATER, first); + } + } + case '<' -> { + var first = input.next(); + if (input.peek().cp() == '=') { + yield new Token(TokenType.LESS_EQUAL, first, input.next()); + } else { + yield new Token(TokenType.LESS, first); + } + } + case '-' -> { + var first = input.next(); + if (input.peek().cp() == '>') { + yield new Token(TokenType.MINUS_GREATER, first, input.next()); + } else { + yield new Token(TokenType.MINUS, first); + } + } + case '(' -> new Token(TokenType.PAREN_LEFT, input.next()); + case ')' -> new Token(TokenType.PAREN_RIGHT, input.next()); + case '+' -> new Token(TokenType.PLUS, input.next()); + case '?' -> { + var first = input.next(); + if (input.peek().cp() == '=') { + yield new Token(TokenType.QUESTION_EQUAL, first, input.next()); + } else { + yield new Token(TokenType.ILLEGAL, first, input.next()); + } + } + case ';' -> new Token(TokenType.SEMICOLON, input.next()); + case '/' -> { + var first = input.next(); + if (input.peek().cp() == '=') { + yield new Token(TokenType.SLASH_EQUAL, first, input.next()); + } else { + yield new Token(TokenType.SLASH, first); + } + } + + case '"' -> new Token(TokenType.STRING, readString()); + + default -> { + if (input.peek().isIdentInitial()) { + var ident = readIdentifier(); + var type = switch (ident) { + case "and" -> TokenType.AND; + case "def" -> TokenType.DEF; + case "do" -> TokenType.DO; + case "else" -> TokenType.ELSE; + case "end" -> TokenType.END; + case "false" -> TokenType.FALSE; + case "fn" -> TokenType.FN; + case "if" -> TokenType.IF; + case "in" -> TokenType.IN; + case "let" -> TokenType.LET; + case "then" -> TokenType.THEN; + case "true" -> TokenType.TRUE; + default -> TokenType.IDENTIFIER; + }; + yield new Token(type, ident); + } else if (input.peek().isNumeral()) { + yield new Token(TokenType.INTEGER, readInteger()); + } else { + yield new Token(TokenType.ILLEGAL, input.next()); + } + } + }; + } + + private T foldWhile(Predicate pred, T initial, BiFunction combine) { + var res = initial; + var ch = input.peek(); + while (pred.test(ch)) { + res = combine.apply(res, input.next()); + ch = input.peek(); + } + return res; + } + + private String readWhile(Predicate pred) { + return foldWhile(pred, new StringBuilder(), StringBuilder::append).toString(); + } + + private void skipWhile(Predicate pred) { + foldWhile(pred, Object.class, (x, _) -> x); + } + + private String readIdentifier() { + return readWhile(Codepoint::isIdentFinal); + } + + private String readInteger() { + return readWhile(Codepoint::isNumeral); + } + + private String readString() { + input.next(); + var literal = readWhile(cp -> cp.cp() != '"'); + input.next(); + return literal; + } + + private void skipWhitespace() { + while (true) { + skipWhile(Codepoint::isWhitespace); + if (input.peek().cp() != '#') { + return; + } + skipWhile(cp -> !cp.isNewline()); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/Main.java b/orang/src/main/java/lv/enes/orang/Main.java new file mode 100644 index 0000000..7ca14a1 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/Main.java @@ -0,0 +1,79 @@ +package lv.enes.orang; + +import lv.enes.orang.core.OrangException; + +import java.io.FileReader; +import java.io.IOException; + +import static lv.enes.orang.State.STDIN; +import static lv.enes.orang.State.STDOUT; + +public class Main { + public static final String PROMPT = ">> "; + + public static void main() throws IOException { + repl(); + } + + private static void repl() throws IOException { + var evaluator = new Evaluator(); + + try (var stream = Main.class.getResourceAsStream("prelude.orang")) { + var prog = Parser.parseProgram(stream); + evaluator = evaluator.visitProgram(prog); + } catch (OrangException ex) { + STDOUT.println(STR."While evaluating prelude: \{ex}"); + throw new RuntimeException(ex); + } + + boolean running = true; + while (running) { + STDOUT.print(PROMPT); + STDOUT.flush(); + + var line = STDIN.readLine(); + if (line == null) { + return; + } + + if (line.isEmpty()) { + continue; + } + + if (line.charAt(0) == ':') { + if (line.length() == 1) { + continue; + } + + switch (line.charAt(1)) { + case 'l': + var filename = line.substring(2).trim(); + try (var reader = new FileReader((filename))) { + var prog = Parser.parseProgram(reader); + evaluator = evaluator.visitProgram(prog); + } catch (IOException | OrangException ex) { + STDOUT.println(ex); + } + break; + case 'q': + running = false; + break; + default: + STDOUT.println("Unrecognised REPL command"); + } + continue; + } + + try { + var prog = Parser.parseProgram(line); + evaluator = evaluator.visitProgram(prog); + if (evaluator.lastResult() != null) { + STDOUT.print("-> "); + STDOUT.println(evaluator.lastResult().stringify()); + } + } catch (OrangException ex) { + STDOUT.println(ex); + } + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/MutableScope.java b/orang/src/main/java/lv/enes/orang/MutableScope.java new file mode 100644 index 0000000..8d900a4 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/MutableScope.java @@ -0,0 +1,24 @@ +package lv.enes.orang; + +import lv.enes.orang.value.Value; + +import java.util.HashMap; +import java.util.Map; + +public class MutableScope extends Scope { + public static MutableScope of(Scope parent) { + return new MutableScope(parent, Map.of()); + } + + public static MutableScope of(Scope parent, String name, Value value) { + return new MutableScope(parent, Map.of(name, value)); + } + + protected MutableScope(Scope parent, Map definitions) { + super(parent, new HashMap<>(definitions)); + } + + public void setDefinition(String key, Value value) { + definitions.put(key, value); + } +} diff --git a/orang/src/main/java/lv/enes/orang/OrangRuntimeException.java b/orang/src/main/java/lv/enes/orang/OrangRuntimeException.java new file mode 100644 index 0000000..8a7d575 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/OrangRuntimeException.java @@ -0,0 +1,13 @@ +package lv.enes.orang; + +import lv.enes.orang.core.OrangException; + +public class OrangRuntimeException extends OrangException { + public OrangRuntimeException(String message) { + super(message); + } + + public OrangRuntimeException(Throwable cause) { + super(cause); + } +} diff --git a/orang/src/main/java/lv/enes/orang/Parser.java b/orang/src/main/java/lv/enes/orang/Parser.java new file mode 100644 index 0000000..4011bd7 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/Parser.java @@ -0,0 +1,292 @@ +package lv.enes.orang; + +import lv.enes.orang.ast.*; +import lv.enes.orang.ast.IfElseExpression; +import lv.enes.orang.ast.Statement; +import lv.enes.orang.utils.NonEmptyList; + +import java.io.InputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +public class Parser { + public static Program parseProgram(InputStream in) throws ParserException { + var parser = new Parser(in); + return parser.parseProgram(); + } + + public static Program parseProgram(Reader in) throws ParserException { + var parser = new Parser(in); + return parser.parseProgram(); + } + + public static Program parseProgram(String in) throws ParserException { + var parser = new Parser(in); + return parser.parseProgram(); + } + + private final PeekableStream input; + + public Parser(InputStream in) { + this(new Lexer(in)); + } + + public Parser(Reader in) { + this(new Lexer(in)); + } + + public Parser(String in) { + this(new Lexer(in)); + } + + public Parser(Iterator input) { + this.input = new PeekableStream<>(input); + } + + public Program parseProgram() throws ParserException { + var statements = new ArrayList(); + while (!maybeConsumeToken(TokenType.EOF)) { + statements.add(parseStatement()); + maybeConsumeToken(TokenType.SEMICOLON); + } + return new Program(Collections.unmodifiableList(statements)); + } + + private Token consume(Predicate pred, String msg) throws ParserException { + var tok = input.next(); + if (!pred.test(tok)) { + throw new ParserException(STR."\{msg}, got \{tok}"); + } + return tok; + } + + private Token consumeToken(TokenType type) throws ParserException { + return consume(tok -> tok.type() == type, STR."Expected \{type}"); + } + + private boolean maybeConsumeToken(TokenType type) { + if (input.peek().type() == type) { + input.next(); + return true; + } + return false; + } + + private ArrayExpression parseArray() throws ParserException { + consumeToken(TokenType.BRACKET_LEFT); + if (maybeConsumeToken(TokenType.BRACKET_RIGHT)) { + return new ArrayExpression(List.of()); + } + + var items = new ArrayList(); + do { + items.add(parseExpression()); + } while (maybeConsumeToken(TokenType.COMMA)); + consumeToken(TokenType.BRACKET_RIGHT); + + return new ArrayExpression(Collections.unmodifiableList(items)); + } + + private List parseArgSpecs() { + var argSpecs = new ArrayList(); + while (true) { + if (input.peek().type() == TokenType.IDENTIFIER) { + argSpecs.add(input.next().literal()); + } else { + break; + } + } + return Collections.unmodifiableList(argSpecs); + } + + private Expression parseBinaryExpression() throws ParserException { + var lhs = parseCallExpression(); + if (!input.peek().type().isBinaryOp()) { + return lhs; + } + + return parseBinaryExpressionRhs(lhs, input.next().type().toBinaryOp()); + } + + private Expression parseBinaryExpressionRhs(Expression lhs, BinaryExpression.Operator op) throws ParserException { + var rhs = parseCallExpression(); + if (!input.peek().type().isBinaryOp()) { + return new BinaryExpression(op, lhs, rhs); + } + + var op2 = input.next().type().toBinaryOp(); + if (op2.bindsStrongerThan(op)) { + return new BinaryExpression(op, lhs, parseBinaryExpressionRhs(rhs, op2)); + } else { + return parseBinaryExpressionRhs(new BinaryExpression(op, lhs, rhs), op2); + } + } + + private BooleanLiteral parseBoolean() throws ParserException { + var t = consume(tok -> tok.type() == TokenType.FALSE || tok.type() == TokenType.TRUE, "Expected TRUE or FALSE"); + return new BooleanLiteral(t.type() == TokenType.TRUE); + } + + private Expression parseCallExpression() throws ParserException { + var callee = parseSimpleExpression(); + while (couldStartSimpleExpression(input.peek().type())) { + var arg = parseSimpleExpression(); + callee = new CallExpression(callee, arg); + } + return callee; + } + + private Definition parseDefinition() throws ParserException { + consumeToken(TokenType.DEF); + var defSpec = parseDefSpec(); + consumeToken(TokenType.EQUAL); + var value = parseExpression(); + if (defSpec.args().isEmpty()) { + return new Definition(defSpec.name(), value); + } else { + return new Definition(defSpec.name(), new FnExpression(new NonEmptyList<>(defSpec.args()), value)); + } + } + + private DefSpec parseDefSpec() throws ParserException { + var name = consumeToken(TokenType.IDENTIFIER).literal(); + var argSpecs = parseArgSpecs(); + return new DefSpec(name, argSpecs); + } + + private DoExpression parseDoExpression() throws ParserException { + consumeToken(TokenType.DO); + var exprs = new ArrayList(); + do { + exprs.add(parseExpression()); + } while (maybeConsumeToken(TokenType.SEMICOLON)); + consumeToken(TokenType.END); + return new DoExpression(Collections.unmodifiableList(exprs)); + } + + private Expression parseExpression() throws ParserException { + if (input.peek().type().isUnaryOp()) { + return parseUnaryExpression(); + } + return parseBinaryExpression(); + } + + private FnExpression parseFnExpression() throws ParserException { + consumeToken(TokenType.FN); + var argSpecs = parseArgSpecs(); + if (argSpecs.isEmpty()) { + throw new ParserException("Function definition with no arguments"); + } + var body = maybeConsumeToken(TokenType.MINUS_GREATER) ? parseExpression() : parseDoExpression(); + return new FnExpression(new NonEmptyList<>(argSpecs), body); + } + + private IfElseExpression parseIfElseExpression() throws ParserException { + consumeToken(TokenType.IF); + var cond = parseExpression(); + consumeToken(TokenType.THEN); + var trueBranch = parseExpression(); + consumeToken(TokenType.ELSE); + var falseBranch = parseExpression(); + return new IfElseExpression(cond, trueBranch, falseBranch); + } + + private IntLiteral parseInteger() throws ParserException { + var tok = consumeToken(TokenType.INTEGER); + return new IntLiteral(Integer.parseInt(tok.literal())); + } + + private LetInExpression parseLetInExpression() throws ParserException { + consumeToken(TokenType.LET); + var bindings = new ArrayList(); + do { + var defSpec = parseDefSpec(); + consumeToken(TokenType.EQUAL); + var value = parseExpression(); + if (defSpec.args().isEmpty()) { + bindings.add(new LetInExpression.Binding(defSpec.name(), value)); + } else { + var fn = new FnExpression(new NonEmptyList<>(defSpec.args()), value); + bindings.add(new LetInExpression.Binding(defSpec.name(), fn)); + } + } while (maybeConsumeToken(TokenType.AND)); + consumeToken(TokenType.IN); + var body = parseExpression(); + return new LetInExpression(Collections.unmodifiableList(bindings), body); + } + + private Expression parseSimpleExpression() throws ParserException { + return switch (input.peek().type()) { + case PAREN_LEFT -> { + consumeToken(TokenType.PAREN_LEFT); + if (maybeConsumeToken(TokenType.PAREN_RIGHT)) { + yield new ArrayExpression(List.of()); + } + var expr = parseExpression(); + consumeToken(TokenType.PAREN_RIGHT); + yield expr; + } + case TRUE, FALSE -> parseBoolean(); + case INTEGER -> parseInteger(); + case IDENTIFIER -> new VariableExpression(input.next().literal()); + case STRING -> parseString(); + case BRACKET_LEFT -> parseArray(); + case IF -> parseIfElseExpression(); + case LET -> parseLetInExpression(); + case FN -> parseFnExpression(); + case DO -> parseDoExpression(); + default -> throw new ParserException(STR."Unexpected token \{input.peek()}"); + }; + } + + private boolean couldStartSimpleExpression(TokenType type) { + return switch (type) { + case PAREN_LEFT, TRUE, FALSE, INTEGER, IDENTIFIER, STRING, BRACKET_LEFT, IF, LET, FN, DO -> true; + default -> false; + }; + } + + private Statement parseStatement() throws ParserException { + if (input.peek().type() == TokenType.DEF) { + return parseDefinition(); + } else { + return new ExpressionStatement(parseExpression()); + } + } + + private Expression parseString() throws ParserException { + var sb = new StringBuilder(); + var cps = input.next().literal().codePoints().iterator(); + while (cps.hasNext()) { + var cp = cps.next(); + if (cp == '\\') { + var escapeChar = cps.next(); + //noinspection UnnecessaryUnboxing + sb.append(switch (escapeChar.intValue()) { + case '\'' -> '\''; + case '"' -> '"'; + case 'r' -> '\r'; + case 'n' -> '\n'; + case 't' -> '\t'; + default -> throw new ParserException(STR."Unknown string escape '\\\{escapeChar}'"); + }); + } else { + sb.appendCodePoint(cp); + } + } + return new StringLiteral(sb.toString()); + } + + private Expression parseUnaryExpression() throws ParserException { + if (input.peek().type().isUnaryOp()) { + var op = input.next().type().toUnaryOp(); + return new UnaryExpression(op, parseUnaryExpression()); + } else { + return parseSimpleExpression(); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/ParserException.java b/orang/src/main/java/lv/enes/orang/ParserException.java new file mode 100644 index 0000000..bd65e7a --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/ParserException.java @@ -0,0 +1,9 @@ +package lv.enes.orang; + +import lv.enes.orang.core.OrangException; + +public class ParserException extends OrangException { + public ParserException(String message) { + super(message); + } +} diff --git a/orang/src/main/java/lv/enes/orang/PeekableStream.java b/orang/src/main/java/lv/enes/orang/PeekableStream.java new file mode 100644 index 0000000..b77bab1 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/PeekableStream.java @@ -0,0 +1,38 @@ +package lv.enes.orang; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; + +public class PeekableStream implements Iterator { + private final Iterator input; + private final Deque buffer = new ArrayDeque<>(); + + public PeekableStream(Iterator input) { + this.input = input; + } + + @Override + public boolean hasNext() { + return !buffer.isEmpty() || input.hasNext(); + } + + @Override + public T next() { + if (!buffer.isEmpty()) { + return buffer.pop(); + } else { + return input.next(); + } + } + + public T peek() { + var value = next(); + putBack(value); + return value; + } + + public void putBack(T value) { + buffer.push(value); + } +} diff --git a/orang/src/main/java/lv/enes/orang/Scope.java b/orang/src/main/java/lv/enes/orang/Scope.java new file mode 100644 index 0000000..7fe57c7 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/Scope.java @@ -0,0 +1,34 @@ +package lv.enes.orang; + +import lv.enes.orang.value.Value; + +import java.util.HashMap; +import java.util.Map; + +public abstract class Scope { + public static final int MAX_DEPTH = 4; + + protected final Scope parent; + protected final Map definitions; + protected final int depth; + + public static MutableScope topLevel() { + return new MutableScope(null, new HashMap<>(Builtins.BUILTINS)); + } + + protected Scope(Scope parent, Map definitions) { + this.parent = parent; + this.definitions = definitions; + this.depth = parent == null ? 0 : parent.depth + 1; + } + + public Value getDefinition(String name) throws OrangRuntimeException { + if (definitions.containsKey(name)) { + return definitions.get(name); + } else if (parent != null) { + return parent.getDefinition(name); + } else { + throw new OrangRuntimeException(STR."Value named \{name} is not defined!"); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/State.java b/orang/src/main/java/lv/enes/orang/State.java new file mode 100644 index 0000000..46b115b --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/State.java @@ -0,0 +1,12 @@ +package lv.enes.orang; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintWriter; + +public final class State { + public static final BufferedReader STDIN = new BufferedReader(new InputStreamReader(System.in)); + public static final PrintWriter STDOUT = new PrintWriter(System.out); + + private State() {} +} diff --git a/orang/src/main/java/lv/enes/orang/Token.java b/orang/src/main/java/lv/enes/orang/Token.java new file mode 100644 index 0000000..4456b8f --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/Token.java @@ -0,0 +1,15 @@ +package lv.enes.orang; + +public record Token(TokenType type, String literal) { + public Token(TokenType type, Codepoint... cps) { + this(type, codepointsToString(cps)); + } + + private static String codepointsToString(Codepoint... cps) { + var sb = new StringBuilder(cps.length); + for (var cp : cps) { + sb.append(cp); + } + return sb.toString(); + } +} diff --git a/orang/src/main/java/lv/enes/orang/TokenType.java b/orang/src/main/java/lv/enes/orang/TokenType.java new file mode 100644 index 0000000..960435e --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/TokenType.java @@ -0,0 +1,91 @@ +package lv.enes.orang; + +import lv.enes.orang.ast.BinaryExpression; +import lv.enes.orang.ast.UnaryExpression; + +public enum TokenType { + ILLEGAL, + EOF, + + // Literals + IDENTIFIER, + INTEGER, + STRING, + + // Keywords + AND, + DEF, + DO, + ELSE, + END, + FALSE, + FN, + IF, + IN, + LET, + THEN, + TRUE, + + // Special chars + ASTERISK, + BANG, + BRACKET_LEFT, + BRACKET_RIGHT, + COMMA, + EQUAL, + GREATER, + GREATER_EQUAL, + LESS, + LESS_EQUAL, + MINUS, + MINUS_GREATER, + PAREN_LEFT, + PAREN_RIGHT, + PLUS, + QUESTION_EQUAL, + SEMICOLON, + SLASH, + SLASH_EQUAL, + + ; + + public boolean isBinaryOp() { + return switch (this) { + case ASTERISK, SLASH, PLUS, MINUS, QUESTION_EQUAL, SLASH_EQUAL, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL + -> true; + default -> false; + }; + } + + public BinaryExpression.Operator toBinaryOp() { + return switch (this) { + case ASTERISK -> BinaryExpression.Operator.MULTIPLY; + case SLASH -> BinaryExpression.Operator.DIVIDE; + case PLUS -> BinaryExpression.Operator.ADD; + case MINUS -> BinaryExpression.Operator.SUBTRACT; + case QUESTION_EQUAL -> BinaryExpression.Operator.EQUALS; + case SLASH_EQUAL -> BinaryExpression.Operator.NOT_EQUALS; + case GREATER -> BinaryExpression.Operator.GT; + case GREATER_EQUAL -> BinaryExpression.Operator.GTE; + case LESS -> BinaryExpression.Operator.LT; + case LESS_EQUAL -> BinaryExpression.Operator.LTE; + default -> throw new IllegalStateException("Token " + this + " is not a binary operator"); + }; + } + + public boolean isUnaryOp() { + return switch (this) { + case PLUS, MINUS, BANG -> true; + default -> false; + }; + } + + public UnaryExpression.Operator toUnaryOp() { + return switch (this) { + case PLUS -> UnaryExpression.Operator.PLUS; + case MINUS -> UnaryExpression.Operator.NEGATE; + case BANG -> UnaryExpression.Operator.NOT; + default -> throw new IllegalStateException("Token " + this + " is not a unary operator"); + }; + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/Array.java b/orang/src/main/java/lv/enes/orang/value/Array.java new file mode 100644 index 0000000..c3b645d --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/Array.java @@ -0,0 +1,65 @@ +package lv.enes.orang.value; + +import lv.enes.orang.OrangRuntimeException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public record Array(List items) implements Value { + @Override + public String typeName() { + return "Array"; + } + + @Override + public String stringify() { + if (items.isEmpty()) { + return "[]"; + } + var sb = new StringBuilder("["); + sb.append(items.getFirst().stringify()); + for (int i = 1; i < items.size(); i++) { + sb.append(", ").append(items.get(i).stringify()); + } + sb.append("]"); + return sb.toString(); + } + + @Override + public String display() { + if (items.isEmpty()) { + return ""; + } + var sb = new StringBuilder(); + sb.append(items.getFirst().display()); + for (var i = 1; i < items.size(); i++) { + sb.append(" ").append(items.get(i).display()); + } + return sb.toString(); + } + + @Override + public Value add(Value rhs) throws OrangRuntimeException { + if (rhs instanceof Array(var rhsi)) { + var newItems = new ArrayList<>(this.items); + newItems.addAll(rhsi); + return new Array(Collections.unmodifiableList(newItems)); + } else { + throw new OrangRuntimeException(STR."add not implemented for Array and \{rhs.typeName()}"); + } + } + + @Override + public Value multiply(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangInteger(var repeat)) { + var newItems = new ArrayList(items.size() * repeat); + for (var i = 0; i < repeat; i++) { + newItems.addAll(items); + } + return new Array(Collections.unmodifiableList(newItems)); + } else { + throw new OrangRuntimeException(STR."multiply not implemented for Array and \{rhs.typeName()}"); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/BuiltinFunction.java b/orang/src/main/java/lv/enes/orang/value/BuiltinFunction.java new file mode 100644 index 0000000..92ccc2e --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/BuiltinFunction.java @@ -0,0 +1,31 @@ +package lv.enes.orang.value; + +import lv.enes.orang.OrangRuntimeException; + +import java.util.List; + +public record BuiltinFunction(int argCount, Impl impl) implements Value { + @FunctionalInterface + public interface Impl { + Value apply(List args) throws OrangRuntimeException; + } + + @Override + public String typeName() { + return "BuiltinFunction"; + } + + @Override + public String stringify() { + return "#builtinFunction"; + } + + @Override + public Value call(Value param) throws OrangRuntimeException { + if (argCount == 1) { + return impl.apply(List.of(param)); + } else { + return new PartialBuiltinFunction(argCount, List.of(param), impl); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/Function.java b/orang/src/main/java/lv/enes/orang/value/Function.java new file mode 100644 index 0000000..901776e --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/Function.java @@ -0,0 +1,31 @@ +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.ast.Expression; + +import java.util.List; + +public record Function(Scope scope, List args, Expression body) implements Value { + @Override + public String typeName() { + return "Function"; + } + + @Override + public String stringify() { + return "#function"; + } + + @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); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/OrangBoolean.java b/orang/src/main/java/lv/enes/orang/value/OrangBoolean.java new file mode 100644 index 0000000..8a4776c --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/OrangBoolean.java @@ -0,0 +1,55 @@ +package lv.enes.orang.value; + +import lombok.EqualsAndHashCode; +import lv.enes.orang.OrangRuntimeException; + +@EqualsAndHashCode +public final class OrangBoolean implements Value { + public final static OrangBoolean TRUE = new OrangBoolean(true); + public final static OrangBoolean FALSE = new OrangBoolean(false); + + private final boolean value; + + private OrangBoolean(boolean value) { + this.value = value; + } + + public static OrangBoolean of(boolean value) { + if (value) { + return TRUE; + } + return FALSE; + } + + public boolean value() { + return value; + } + + @Override + public String typeName() { + return "Boolean"; + } + + @Override + public String stringify() { + if (value) { + return "true"; + } else { + return "false"; + } + } + + @Override + public OrangBoolean not() { + return new OrangBoolean(!value); + } + + @Override + public OrangBoolean or(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangBoolean rhsb) { + return new OrangBoolean(value || rhsb.value); + } else { + throw new OrangRuntimeException(STR."or is not implemented for Boolean and \{rhs.typeName()}"); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/OrangInteger.java b/orang/src/main/java/lv/enes/orang/value/OrangInteger.java new file mode 100644 index 0000000..9b8d505 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/OrangInteger.java @@ -0,0 +1,70 @@ +package lv.enes.orang.value; + +import lv.enes.orang.OrangRuntimeException; + +public record OrangInteger(int value) implements Value { + @Override + public String typeName() { + return "Integer"; + } + + @Override + public String stringify() { + return Integer.toString(value); + } + + @Override + public OrangInteger negate() { + return new OrangInteger(-value); + } + + @Override + public OrangInteger plus() { + return this; + } + + @Override + public OrangInteger add(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangInteger(int rhsi)) { + return new OrangInteger(value + rhsi); + } else { + throw new OrangRuntimeException(STR."add is not implemented for Integer and \{rhs.typeName()}"); + } + } + + @Override + public OrangInteger divide(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangInteger(int rhsi)) { + return new OrangInteger(value / rhsi); + } else { + throw new OrangRuntimeException(STR."divide is not implemented for Integer and \{rhs.typeName()}"); + } + } + + @Override + public OrangInteger multiply(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangInteger(int rhsi)) { + return new OrangInteger(value * rhsi); + } else { + throw new OrangRuntimeException(STR."multiply is not implemented for Integer and \{rhs.typeName()}"); + } + } + + @Override + public OrangInteger subtract(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangInteger(int rhsi)) { + return new OrangInteger(value - rhsi); + } else { + throw new OrangRuntimeException(STR."subtract is not implemented for Integer and \{rhs.typeName()}"); + } + } + + @Override + public OrangBoolean greaterThan(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangInteger(int rhsi)) { + return OrangBoolean.of(value > rhsi); + } else { + throw new OrangRuntimeException(STR."greaterThan is not implemented for Integer and \{rhs.typeName()}"); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/OrangString.java b/orang/src/main/java/lv/enes/orang/value/OrangString.java new file mode 100644 index 0000000..c03f7ac --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/OrangString.java @@ -0,0 +1,49 @@ +package lv.enes.orang.value; + +import lv.enes.orang.OrangRuntimeException; + +public record OrangString(String value) implements Value { + @Override + public String typeName() { + return "String"; + } + + @Override + public String stringify() { + var sb = new StringBuilder("\""); + var cps = value.codePoints().iterator(); + while (cps.hasNext()) { + var cp = cps.next(); + if (cp == '"') { + sb.append("\\\""); + } else { + sb.appendCodePoint(cp); + } + } + sb.append('"'); + return sb.toString(); + } + + @Override + public String display() { + return value; + } + + @Override + public OrangString add(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangString(String rhss)) { + return new OrangString(value + rhss); + } else { + throw new OrangRuntimeException(STR."add is not implemented for Integer and \{rhs.typeName()}"); + } + } + + @Override + public Value multiply(Value rhs) throws OrangRuntimeException { + if (rhs instanceof OrangInteger(var repeat)) { + return new OrangString(value.repeat(Math.max(0, repeat))); + } else { + throw new OrangRuntimeException(STR."multiply not implemented for Array and \{rhs.typeName()}"); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java b/orang/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java new file mode 100644 index 0000000..37278a4 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java @@ -0,0 +1,32 @@ +package lv.enes.orang.value; + +import lv.enes.orang.OrangRuntimeException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public record PartialBuiltinFunction(int argCount, List params, BuiltinFunction.Impl impl) implements Value { + @Override + public String typeName() { + return "BuiltinFunction"; + } + + @Override + public String stringify() { + return "#builtinFunction"; + } + + @Override + public Value call(Value param) throws OrangRuntimeException { + List newParams = new ArrayList<>(params); + newParams.add(param); + newParams = Collections.unmodifiableList(newParams); + + if (newParams.size() == argCount) { + return impl.apply(newParams); + } else { + return new PartialBuiltinFunction(argCount, newParams, impl); + } + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/PartialFunction.java b/orang/src/main/java/lv/enes/orang/value/PartialFunction.java new file mode 100644 index 0000000..0dbf530 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/PartialFunction.java @@ -0,0 +1,40 @@ +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.ast.Expression; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +public record PartialFunction(Scope scope, List args, List params, Expression body) implements Value { + @Override + public String typeName() { + return "Function"; + } + + @Override + public String stringify() { + return "#function"; + } + + @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); + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/Undefined.java b/orang/src/main/java/lv/enes/orang/value/Undefined.java new file mode 100644 index 0000000..a341ee8 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/Undefined.java @@ -0,0 +1,21 @@ +package lv.enes.orang.value; + +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode +public final class Undefined implements Value { + public static final Undefined INSTANCE = new Undefined(); + + private Undefined() { + } + + @Override + public String typeName() { + return "Undefined"; + } + + @Override + public String stringify() { + return "#undefined"; + } +} diff --git a/orang/src/main/java/lv/enes/orang/value/Value.java b/orang/src/main/java/lv/enes/orang/value/Value.java new file mode 100644 index 0000000..fa8275c --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/Value.java @@ -0,0 +1,77 @@ +package lv.enes.orang.value; + +import lv.enes.orang.OrangRuntimeException; + +public sealed interface Value + permits Array, BuiltinFunction, Function, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction, + PartialFunction, Undefined { + String typeName(); + String stringify(); + + default String display() { + return stringify(); + } + + + default Value negate() throws OrangRuntimeException { + throw new OrangRuntimeException(STR."negate is not implemented for \{typeName()}"); + } + + default Value not() throws OrangRuntimeException { + throw new OrangRuntimeException(STR."not is not implemented for \{typeName()}"); + } + + default Value plus() throws OrangRuntimeException { + throw new OrangRuntimeException(STR."plus is not implemented for \{typeName()}"); + } + + + default Value add(Value rhs) throws OrangRuntimeException { + throw new OrangRuntimeException(STR."add is not implemented for \{typeName()} and \{rhs.typeName()}"); + } + + default Value call(Value rhs) throws OrangRuntimeException { + throw new OrangRuntimeException(STR."call is not implemented for \{typeName()} and \{rhs.typeName()}"); + } + + default Value divide(Value rhs) throws OrangRuntimeException { + throw new OrangRuntimeException(STR."divide is not implemented for \{typeName()} and \{rhs.typeName()}"); + } + + default Value multiply(Value rhs) throws OrangRuntimeException { + throw new OrangRuntimeException(STR."multiply is not implemented for \{typeName()} and \{rhs.typeName()}"); + } + + default Value or(Value rhs) throws OrangRuntimeException { + throw new OrangRuntimeException(STR."or is not implemented for \{typeName()} and \{rhs.typeName()}"); + } + + default Value subtract(Value rhs) throws OrangRuntimeException { + throw new OrangRuntimeException(STR."subtract is not implemented for \{typeName()} and \{rhs.typeName()}"); + } + + + default OrangBoolean greaterThan(Value rhs) throws OrangRuntimeException { + throw new OrangRuntimeException(STR."greater than is not implemented for \{typeName()} and \{rhs.typeName()}"); + } + + default OrangBoolean greaterThanOrEqual(Value rhs) throws OrangRuntimeException { + return greaterThan(rhs).or(orangEquals(rhs)); + } + + default OrangBoolean lessThan(Value rhs) throws OrangRuntimeException { + return greaterThanOrEqual(rhs).not(); + } + + default OrangBoolean lessThanOrEqual(Value rhs) throws OrangRuntimeException { + return greaterThan(rhs).not(); + } + + default OrangBoolean orangEquals(Value rhs) { + return OrangBoolean.of(this.equals(rhs)); + } + + default OrangBoolean notEquals(Value rhs) { + return orangEquals(rhs).not(); + } +} diff --git a/orang/src/main/java/module-info.java b/orang/src/main/java/module-info.java new file mode 100644 index 0000000..060657f --- /dev/null +++ b/orang/src/main/java/module-info.java @@ -0,0 +1,14 @@ +module lv.enes.orang { + exports lv.enes.orang; + exports lv.enes.orang.value; + + requires lv.enes.orang.ast; + requires lv.enes.orang.core; + requires lv.enes.orang.utils; + + requires static jakarta.annotation; + requires static lombok; + + requires org.slf4j; + requires org.slf4j.simple; +} \ No newline at end of file diff --git a/orang/src/main/resources/lv/enes/orang/prelude.orang b/orang/src/main/resources/lv/enes/orang/prelude.orang new file mode 100644 index 0000000..d29eef3 --- /dev/null +++ b/orang/src/main/resources/lv/enes/orang/prelude.orang @@ -0,0 +1,24 @@ +# builtins +def isRepl = __builtin_isRepl () +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 () + +# standard library +def readInt _ = parseInt (readLn ()) +def printLn x = do print x; print "\n"; x end + +# repl intro :) +def _ = + if isRepl then do + printLn "Hello! This is the Orang Programming Language!"; + printLn "You can execute code right here at the prompt."; + printLn "If you wish to load & execute a file, use `:l filename.orang'."; + printLn "If you wish to quit, use `:q'."; + printLn "Have a nice day :)"; + printLn "" + end else + () + diff --git a/settings.gradle.kts b/settings.gradle.kts index 4c4ac7d..fc692fa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,3 @@ rootProject.name = "orang" +include("ast", "core", "orang", "utils") \ No newline at end of file diff --git a/src/main/java/lv/enes/orang/Builtins.java b/src/main/java/lv/enes/orang/Builtins.java deleted file mode 100644 index 0a76387..0000000 --- a/src/main/java/lv/enes/orang/Builtins.java +++ /dev/null @@ -1,95 +0,0 @@ -package lv.enes.orang; - -import lombok.extern.slf4j.Slf4j; -import lv.enes.orang.value.*; - -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Random; - -import static lv.enes.orang.State.STDIN; -import static lv.enes.orang.State.STDOUT; - -@Slf4j -public final class Builtins { - public static final Map BUILTINS = Map.ofEntries( - Map.entry("__builtin_isRepl", new BuiltinFunction(1, Builtins::isRepl)), - Map.entry("__builtin_len", new BuiltinFunction(1, Builtins::len)), - Map.entry("__builtin_parseInt", new BuiltinFunction(1, Builtins::parseInt)), - Map.entry("__builtin_print", new BuiltinFunction(1, Builtins::print)), - Map.entry("__builtin_randInt", new BuiltinFunction(2, Builtins::randInt)), - Map.entry("__builtin_readLn", new BuiltinFunction(1, Builtins::readLn)) - ); - - private Builtins() {} - - private static Value isRepl(List args) { - return OrangBoolean.TRUE; - } - - private static Value len(List args) throws OrangRuntimeException { - assert args.size() == 1; - var arg = args.getFirst(); - - if (arg instanceof OrangString(var value)) { - return new OrangInteger(value.length()); - } else if (arg instanceof Array(var items)) { - return new OrangInteger(items.size()); - } else { - throw new OrangRuntimeException(STR."len \{arg.typeName()} is not implemented"); - } - } - - private static Value parseInt(List args) throws OrangRuntimeException { - assert args.size() == 1; - - var arg = args.getFirst(); - if (arg instanceof OrangInteger) { - log.warn("Attempted to parseInt an Integer!"); - return arg; - } else if (arg instanceof OrangString(var value)) { - try { - return new OrangInteger(Integer.parseInt(value.trim())); - } catch (NumberFormatException ex) { - throw new OrangRuntimeException(ex); - } - } else { - throw new OrangRuntimeException(STR."parseInt \{arg.typeName()} is not implemented"); - } - } - - private static Value print(List args) { - assert args.size() == 1; - STDOUT.print(args.getFirst().display()); - STDOUT.flush(); - return args.getFirst(); - } - - private static Value randInt(List args) throws OrangRuntimeException { - assert args.size() == 2; - - var minv = args.getFirst(); - var maxv = args.get(1); - if (minv instanceof OrangInteger(var min) && maxv instanceof OrangInteger(var max)) { - return new OrangInteger(new Random().nextInt(min, max)); - } else { - throw new OrangRuntimeException(STR."randInt \{minv.typeName()} \{maxv.typeName()} is not implemented"); - } - } - - 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) { - throw new OrangRuntimeException(e); - } - } -} diff --git a/src/main/java/lv/enes/orang/Codepoint.java b/src/main/java/lv/enes/orang/Codepoint.java deleted file mode 100644 index 0dfaa2b..0000000 --- a/src/main/java/lv/enes/orang/Codepoint.java +++ /dev/null @@ -1,24 +0,0 @@ -package lv.enes.orang; - -public record Codepoint(int cp) { - @Override - public String toString() { - return Character.toString(cp); - } - - public boolean isIdentInitial() { - return Character.isLetter(cp) || cp == '_'; - } - - public boolean isIdentFinal() { - return isIdentInitial() || Character.isDigit(cp); - } - - public boolean isNumeral() { - return Character.isDigit(cp); - } - - public boolean isWhitespace() { - return Character.isWhitespace(cp); - } -} diff --git a/src/main/java/lv/enes/orang/Lexer.java b/src/main/java/lv/enes/orang/Lexer.java deleted file mode 100644 index 0ee2503..0000000 --- a/src/main/java/lv/enes/orang/Lexer.java +++ /dev/null @@ -1,168 +0,0 @@ -package lv.enes.orang; - -import java.io.*; -import java.util.Iterator; -import java.util.function.BiFunction; -import java.util.function.Predicate; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -public class Lexer implements Iterator { - private final PeekableStream input; - - public Lexer(InputStream input) { - this(new InputStreamReader(input)); - } - - public Lexer(Reader input) { - var cpStream = new BufferedReader(input) - .lines() - .flatMapToInt(str -> IntStream.concat(str.codePoints(), IntStream.of('\n'))) - .mapToObj(Codepoint::new); - var theEof = Stream.of(new Codepoint(-1)); - this.input = new PeekableStream<>(Stream.concat(cpStream, theEof).iterator()); - } - - public Lexer(String input) { - this(new StringReader(input)); - } - - private boolean hasNext = true; - - @Override - public Token next() { - var tok = nextToken(); - if (tok.type() == TokenType.EOF) { - hasNext = false; - } - return tok; - } - - @Override - public boolean hasNext() { - return hasNext; - } - - private Token nextToken() { - skipWhitespace(); - return switch (input.peek().cp()) { - case -1 -> new Token(TokenType.EOF, ""); - - case '*' -> new Token(TokenType.ASTERISK, input.next()); - case '!' -> new Token(TokenType.BANG, input.next()); - case '[' -> new Token(TokenType.BRACKET_LEFT, input.next()); - case ']' -> new Token(TokenType.BRACKET_RIGHT, input.next()); - case ',' -> new Token(TokenType.COMMA, input.next()); - case '=' -> new Token(TokenType.EQUAL, input.next()); - case '>' -> { - var first = input.next(); - if (input.peek().cp() == '=') { - yield new Token(TokenType.GREATER_EQUAL, first, input.next()); - } else { - yield new Token(TokenType.GREATER, first); - } - } - case '<' -> { - var first = input.next(); - if (input.peek().cp() == '=') { - yield new Token(TokenType.LESS_EQUAL, first, input.next()); - } else { - yield new Token(TokenType.LESS, first); - } - } - case '-' -> { - var first = input.next(); - if (input.peek().cp() == '>') { - yield new Token(TokenType.MINUS_GREATER, first, input.next()); - } else { - yield new Token(TokenType.MINUS, first); - } - } - case '(' -> new Token(TokenType.PAREN_LEFT, input.next()); - case ')' -> new Token(TokenType.PAREN_RIGHT, input.next()); - case '+' -> new Token(TokenType.PLUS, input.next()); - case '?' -> { - var first = input.next(); - if (input.peek().cp() == '=') { - yield new Token(TokenType.QUESTION_EQUAL, first, input.next()); - } else { - yield new Token(TokenType.ILLEGAL, first, input.next()); - } - } - case ';' -> new Token(TokenType.SEMICOLON, input.next()); - case '/' -> { - var first = input.next(); - if (input.peek().cp() == '=') { - yield new Token(TokenType.SLASH_EQUAL, first, input.next()); - } else { - yield new Token(TokenType.SLASH, first); - } - } - - case '"' -> new Token(TokenType.STRING, readString()); - - default -> { - if (input.peek().isIdentInitial()) { - var ident = readIdentifier(); - var type = switch (ident) { - case "and" -> TokenType.AND; - case "def" -> TokenType.DEF; - case "do" -> TokenType.DO; - case "else" -> TokenType.ELSE; - case "end" -> TokenType.END; - case "false" -> TokenType.FALSE; - case "fn" -> TokenType.FN; - case "if" -> TokenType.IF; - case "in" -> TokenType.IN; - case "let" -> TokenType.LET; - case "then" -> TokenType.THEN; - case "true" -> TokenType.TRUE; - default -> TokenType.IDENTIFIER; - }; - yield new Token(type, ident); - } else if (input.peek().isNumeral()) { - yield new Token(TokenType.INTEGER, readInteger()); - } else { - yield new Token(TokenType.ILLEGAL, input.next()); - } - } - }; - } - - private T foldWhile(Predicate pred, T initial, BiFunction combine) { - var res = initial; - var ch = input.peek(); - while (pred.test(ch)) { - res = combine.apply(res, input.next()); - ch = input.peek(); - } - return res; - } - - private String readWhile(Predicate pred) { - return foldWhile(pred, new StringBuilder(), StringBuilder::append).toString(); - } - - private void skipWhile(Predicate pred) { - foldWhile(pred, Object.class, (x, _) -> x); - } - - private String readIdentifier() { - return readWhile(Codepoint::isIdentFinal); - } - - private String readInteger() { - return readWhile(Codepoint::isNumeral); - } - - private String readString() { - input.next(); - var literal = readWhile(cp -> cp.cp() != '"'); - input.next(); - return literal; - } - - private void skipWhitespace() { - skipWhile(Codepoint::isWhitespace); - } -} diff --git a/src/main/java/lv/enes/orang/Main.java b/src/main/java/lv/enes/orang/Main.java deleted file mode 100644 index 9a607ed..0000000 --- a/src/main/java/lv/enes/orang/Main.java +++ /dev/null @@ -1,77 +0,0 @@ -package lv.enes.orang; - -import java.io.FileReader; -import java.io.IOException; - -import static lv.enes.orang.State.STDIN; -import static lv.enes.orang.State.STDOUT; - -public class Main { - public static final String PROMPT = ">> "; - - public static void main() throws IOException { - repl(); - } - - private static void repl() throws IOException { - var scope = new Scope(); - - try (var stream = Main.class.getResourceAsStream("prelude.orang")) { - var prog = Parser.parseProgram(stream); - scope = prog.runStatement(scope); - } catch (OrangException ex) { - STDOUT.println(STR."While evaluating prelude: \{ex}"); - throw new RuntimeException(ex); - } - - boolean running = true; - while (running) { - STDOUT.print(PROMPT); - STDOUT.flush(); - - var line = STDIN.readLine(); - if (line == null) { - return; - } - - if (line.isEmpty()) { - continue; - } - - if (line.charAt(0) == ':') { - if (line.length() == 1) { - continue; - } - - switch (line.charAt(1)) { - case 'l': - var filename = line.substring(2).trim(); - try (var reader = new FileReader((filename))) { - var prog = Parser.parseProgram(reader); - scope = prog.runStatement(scope); - } catch (IOException | OrangException ex) { - STDOUT.println(ex); - } - break; - case 'q': - running = false; - break; - default: - STDOUT.println("Unrecognised REPL command"); - } - continue; - } - - try { - var prog = Parser.parseProgram(line); - scope = prog.runStatement(scope); - if (scope.getLastResult() != null) { - STDOUT.print("-> "); - STDOUT.println(scope.getLastResult().stringify()); - } - } catch (OrangException ex) { - STDOUT.println(ex); - } - } - } -} diff --git a/src/main/java/lv/enes/orang/NonEmptyList.java b/src/main/java/lv/enes/orang/NonEmptyList.java deleted file mode 100644 index 860db28..0000000 --- a/src/main/java/lv/enes/orang/NonEmptyList.java +++ /dev/null @@ -1,28 +0,0 @@ -package lv.enes.orang; - -import lombok.EqualsAndHashCode; - -import java.util.AbstractList; -import java.util.List; - -@EqualsAndHashCode(callSuper = true) -public class NonEmptyList extends AbstractList { - private final List delegate; - - public NonEmptyList(List delegate) { - if (delegate.isEmpty()) { - throw new IllegalArgumentException("Empty list"); - } - this.delegate = List.copyOf(delegate); - } - - @Override - public E get(int index) { - return delegate.get(index); - } - - @Override - public int size() { - return delegate.size(); - } -} diff --git a/src/main/java/lv/enes/orang/OrangException.java b/src/main/java/lv/enes/orang/OrangException.java deleted file mode 100644 index 9308643..0000000 --- a/src/main/java/lv/enes/orang/OrangException.java +++ /dev/null @@ -1,11 +0,0 @@ -package lv.enes.orang; - -public abstract class OrangException extends Exception { - protected OrangException(String message) { - super(message); - } - - protected OrangException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/lv/enes/orang/OrangRuntimeException.java b/src/main/java/lv/enes/orang/OrangRuntimeException.java deleted file mode 100644 index 9648c30..0000000 --- a/src/main/java/lv/enes/orang/OrangRuntimeException.java +++ /dev/null @@ -1,11 +0,0 @@ -package lv.enes.orang; - -public class OrangRuntimeException extends OrangException { - public OrangRuntimeException(String message) { - super(message); - } - - public OrangRuntimeException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/lv/enes/orang/Parser.java b/src/main/java/lv/enes/orang/Parser.java deleted file mode 100644 index fd4b9bb..0000000 --- a/src/main/java/lv/enes/orang/Parser.java +++ /dev/null @@ -1,291 +0,0 @@ -package lv.enes.orang; - -import lv.enes.orang.ast.*; -import lv.enes.orang.ast.IfElseExpression; -import lv.enes.orang.ast.Statement; - -import java.io.InputStream; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.function.Predicate; - -public class Parser { - public static Program parseProgram(InputStream in) throws ParserException { - var parser = new Parser(in); - return parser.parseProgram(); - } - - public static Program parseProgram(Reader in) throws ParserException { - var parser = new Parser(in); - return parser.parseProgram(); - } - - public static Program parseProgram(String in) throws ParserException { - var parser = new Parser(in); - return parser.parseProgram(); - } - - private final PeekableStream input; - - public Parser(InputStream in) { - this(new Lexer(in)); - } - - public Parser(Reader in) { - this(new Lexer(in)); - } - - public Parser(String in) { - this(new Lexer(in)); - } - - public Parser(Iterator input) { - this.input = new PeekableStream<>(input); - } - - public Program parseProgram() throws ParserException { - var statements = new ArrayList(); - while (!maybeConsumeToken(TokenType.EOF)) { - statements.add(parseStatement()); - maybeConsumeToken(TokenType.SEMICOLON); - } - return new Program(Collections.unmodifiableList(statements)); - } - - private Token consume(Predicate pred, String msg) throws ParserException { - var tok = input.next(); - if (!pred.test(tok)) { - throw new ParserException(STR."\{msg}, got \{tok}"); - } - return tok; - } - - private Token consumeToken(TokenType type) throws ParserException { - return consume(tok -> tok.type() == type, STR."Expected \{type}"); - } - - private boolean maybeConsumeToken(TokenType type) { - if (input.peek().type() == type) { - input.next(); - return true; - } - return false; - } - - private ArrayExpression parseArray() throws ParserException { - consumeToken(TokenType.BRACKET_LEFT); - if (maybeConsumeToken(TokenType.BRACKET_RIGHT)) { - return new ArrayExpression(List.of()); - } - - var items = new ArrayList(); - do { - items.add(parseExpression()); - } while (maybeConsumeToken(TokenType.COMMA)); - consumeToken(TokenType.BRACKET_RIGHT); - - return new ArrayExpression(Collections.unmodifiableList(items)); - } - - private List parseArgSpecs() { - var argSpecs = new ArrayList(); - while (true) { - if (input.peek().type() == TokenType.IDENTIFIER) { - argSpecs.add(input.next().literal()); - } else { - break; - } - } - return Collections.unmodifiableList(argSpecs); - } - - private Expression parseBinaryExpression() throws ParserException { - var lhs = parseCallExpression(); - if (!input.peek().type().isBinaryOp()) { - return lhs; - } - - return parseBinaryExpressionRhs(lhs, input.next().type().toBinaryOp()); - } - - private Expression parseBinaryExpressionRhs(Expression lhs, BinaryExpression.Operator op) throws ParserException { - var rhs = parseCallExpression(); - if (!input.peek().type().isBinaryOp()) { - return new BinaryExpression(op, lhs, rhs); - } - - var op2 = input.next().type().toBinaryOp(); - if (op2.bindsStrongerThan(op)) { - return new BinaryExpression(op, lhs, parseBinaryExpressionRhs(rhs, op2)); - } else { - return parseBinaryExpressionRhs(new BinaryExpression(op, lhs, rhs), op2); - } - } - - private BooleanLiteral parseBoolean() throws ParserException { - var t = consume(tok -> tok.type() == TokenType.FALSE || tok.type() == TokenType.TRUE, "Expected TRUE or FALSE"); - return new BooleanLiteral(t.type() == TokenType.TRUE); - } - - private Expression parseCallExpression() throws ParserException { - var callee = parseSimpleExpression(); - while (couldStartSimpleExpression(input.peek().type())) { - var arg = parseSimpleExpression(); - callee = new CallExpression(callee, arg); - } - return callee; - } - - private Definition parseDefinition() throws ParserException { - consumeToken(TokenType.DEF); - var defSpec = parseDefSpec(); - consumeToken(TokenType.EQUAL); - var value = parseExpression(); - if (defSpec.args().isEmpty()) { - return new Definition(defSpec.name(), value); - } else { - return new Definition(defSpec.name(), new FnExpression(new NonEmptyList<>(defSpec.args()), value)); - } - } - - private DefSpec parseDefSpec() throws ParserException { - var name = consumeToken(TokenType.IDENTIFIER).literal(); - var argSpecs = parseArgSpecs(); - return new DefSpec(name, argSpecs); - } - - private DoExpression parseDoExpression() throws ParserException { - consumeToken(TokenType.DO); - var exprs = new ArrayList(); - do { - exprs.add(parseExpression()); - } while (maybeConsumeToken(TokenType.SEMICOLON)); - consumeToken(TokenType.END); - return new DoExpression(Collections.unmodifiableList(exprs)); - } - - private Expression parseExpression() throws ParserException { - if (input.peek().type().isUnaryOp()) { - return parseUnaryExpression(); - } - return parseBinaryExpression(); - } - - private FnExpression parseFnExpression() throws ParserException { - consumeToken(TokenType.FN); - var argSpecs = parseArgSpecs(); - if (argSpecs.isEmpty()) { - throw new ParserException("Function definition with no arguments"); - } - var body = maybeConsumeToken(TokenType.MINUS_GREATER) ? parseExpression() : parseDoExpression(); - return new FnExpression(new NonEmptyList<>(argSpecs), body); - } - - private IfElseExpression parseIfElseExpression() throws ParserException { - consumeToken(TokenType.IF); - var cond = parseExpression(); - consumeToken(TokenType.THEN); - var trueBranch = parseExpression(); - consumeToken(TokenType.ELSE); - var falseBranch = parseExpression(); - return new IfElseExpression(cond, trueBranch, falseBranch); - } - - private IntLiteral parseInteger() throws ParserException { - var tok = consumeToken(TokenType.INTEGER); - return new IntLiteral(Integer.parseInt(tok.literal())); - } - - private LetInExpression parseLetInExpression() throws ParserException { - consumeToken(TokenType.LET); - var bindings = new ArrayList(); - do { - var defSpec = parseDefSpec(); - consumeToken(TokenType.EQUAL); - var value = parseExpression(); - if (defSpec.args().isEmpty()) { - bindings.add(new LetInExpression.Binding(defSpec.name(), value)); - } else { - var fn = new FnExpression(new NonEmptyList<>(defSpec.args()), value); - bindings.add(new LetInExpression.Binding(defSpec.name(), fn)); - } - } while (maybeConsumeToken(TokenType.AND)); - consumeToken(TokenType.IN); - var body = parseExpression(); - return new LetInExpression(Collections.unmodifiableList(bindings), body); - } - - private Expression parseSimpleExpression() throws ParserException { - return switch (input.peek().type()) { - case PAREN_LEFT -> { - consumeToken(TokenType.PAREN_LEFT); - if (maybeConsumeToken(TokenType.PAREN_RIGHT)) { - yield new ArrayExpression(List.of()); - } - var expr = parseExpression(); - consumeToken(TokenType.PAREN_RIGHT); - yield expr; - } - case TRUE, FALSE -> parseBoolean(); - case INTEGER -> parseInteger(); - case IDENTIFIER -> new VariableExpression(input.next().literal()); - case STRING -> parseString(); - case BRACKET_LEFT -> parseArray(); - case IF -> parseIfElseExpression(); - case LET -> parseLetInExpression(); - case FN -> parseFnExpression(); - case DO -> parseDoExpression(); - default -> throw new ParserException(STR."Unexpected token \{input.peek()}"); - }; - } - - private boolean couldStartSimpleExpression(TokenType type) { - return switch (type) { - case PAREN_LEFT, TRUE, FALSE, INTEGER, IDENTIFIER, STRING, BRACKET_LEFT, IF, LET, FN, DO -> true; - default -> false; - }; - } - - private Statement parseStatement() throws ParserException { - if (input.peek().type() == TokenType.DEF) { - return parseDefinition(); - } else { - return parseExpression(); - } - } - - private Expression parseString() throws ParserException { - var sb = new StringBuilder(); - var cps = input.next().literal().codePoints().iterator(); - while (cps.hasNext()) { - var cp = cps.next(); - if (cp == '\\') { - var escapeChar = cps.next(); - //noinspection UnnecessaryUnboxing - sb.append(switch (escapeChar.intValue()) { - case '\'' -> '\''; - case '"' -> '"'; - case 'r' -> '\r'; - case 'n' -> '\n'; - case 't' -> '\t'; - default -> throw new ParserException(STR."Unknown string escape '\\\{escapeChar}'"); - }); - } else { - sb.appendCodePoint(cp); - } - } - return new StringLiteral(sb.toString()); - } - - private Expression parseUnaryExpression() throws ParserException { - if (input.peek().type().isUnaryOp()) { - var op = input.next().type().toUnaryOp(); - return new UnaryExpression(op, parseUnaryExpression()); - } else { - return parseSimpleExpression(); - } - } -} diff --git a/src/main/java/lv/enes/orang/ParserException.java b/src/main/java/lv/enes/orang/ParserException.java deleted file mode 100644 index 9000a93..0000000 --- a/src/main/java/lv/enes/orang/ParserException.java +++ /dev/null @@ -1,7 +0,0 @@ -package lv.enes.orang; - -public class ParserException extends OrangException { - public ParserException(String message) { - super(message); - } -} diff --git a/src/main/java/lv/enes/orang/PeekableStream.java b/src/main/java/lv/enes/orang/PeekableStream.java deleted file mode 100644 index b77bab1..0000000 --- a/src/main/java/lv/enes/orang/PeekableStream.java +++ /dev/null @@ -1,38 +0,0 @@ -package lv.enes.orang; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Iterator; - -public class PeekableStream implements Iterator { - private final Iterator input; - private final Deque buffer = new ArrayDeque<>(); - - public PeekableStream(Iterator input) { - this.input = input; - } - - @Override - public boolean hasNext() { - return !buffer.isEmpty() || input.hasNext(); - } - - @Override - public T next() { - if (!buffer.isEmpty()) { - return buffer.pop(); - } else { - return input.next(); - } - } - - public T peek() { - var value = next(); - putBack(value); - return value; - } - - public void putBack(T value) { - buffer.push(value); - } -} diff --git a/src/main/java/lv/enes/orang/Scope.java b/src/main/java/lv/enes/orang/Scope.java deleted file mode 100644 index 3625c69..0000000 --- a/src/main/java/lv/enes/orang/Scope.java +++ /dev/null @@ -1,54 +0,0 @@ -package lv.enes.orang; - -import jakarta.annotation.Nullable; -import lv.enes.orang.value.Value; - -import java.util.HashMap; -import java.util.Map; - -public class Scope { - private final Map definitions; - - private final Value lastResult; - - public Scope() { - this(new HashMap<>(Builtins.BUILTINS), null); - } - - private Scope(Map definitions, Value lastResult) { - this.definitions = definitions; - this.lastResult = lastResult; - } - - public Value getDefinition(String name) throws OrangRuntimeException { - if (definitions.containsKey(name)) { - return definitions.get(name); - } - throw new OrangRuntimeException(STR."Value named \{name} is not defined!"); - } - - @Nullable - public Value getLastResult() { - return lastResult; - } - - public Scope withDefinition(String key, Value value) { - var newDefs = new HashMap<>(definitions); - newDefs.put(key, value); - return new Scope(newDefs, null); - } - - public Scope withDefinitions(Map definitions) { - var newDefs = new HashMap<>(this.definitions); - newDefs.putAll(definitions); - return new Scope(newDefs, null); - } - - public void setDefinition(String key, Value value) { - definitions.put(key, value); - } - - public Scope withLastResult(Value value) { - return new Scope(definitions, value); - } -} diff --git a/src/main/java/lv/enes/orang/State.java b/src/main/java/lv/enes/orang/State.java deleted file mode 100644 index 46b115b..0000000 --- a/src/main/java/lv/enes/orang/State.java +++ /dev/null @@ -1,12 +0,0 @@ -package lv.enes.orang; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.PrintWriter; - -public final class State { - public static final BufferedReader STDIN = new BufferedReader(new InputStreamReader(System.in)); - public static final PrintWriter STDOUT = new PrintWriter(System.out); - - private State() {} -} diff --git a/src/main/java/lv/enes/orang/Token.java b/src/main/java/lv/enes/orang/Token.java deleted file mode 100644 index 4456b8f..0000000 --- a/src/main/java/lv/enes/orang/Token.java +++ /dev/null @@ -1,15 +0,0 @@ -package lv.enes.orang; - -public record Token(TokenType type, String literal) { - public Token(TokenType type, Codepoint... cps) { - this(type, codepointsToString(cps)); - } - - private static String codepointsToString(Codepoint... cps) { - var sb = new StringBuilder(cps.length); - for (var cp : cps) { - sb.append(cp); - } - return sb.toString(); - } -} diff --git a/src/main/java/lv/enes/orang/TokenType.java b/src/main/java/lv/enes/orang/TokenType.java deleted file mode 100644 index 960435e..0000000 --- a/src/main/java/lv/enes/orang/TokenType.java +++ /dev/null @@ -1,91 +0,0 @@ -package lv.enes.orang; - -import lv.enes.orang.ast.BinaryExpression; -import lv.enes.orang.ast.UnaryExpression; - -public enum TokenType { - ILLEGAL, - EOF, - - // Literals - IDENTIFIER, - INTEGER, - STRING, - - // Keywords - AND, - DEF, - DO, - ELSE, - END, - FALSE, - FN, - IF, - IN, - LET, - THEN, - TRUE, - - // Special chars - ASTERISK, - BANG, - BRACKET_LEFT, - BRACKET_RIGHT, - COMMA, - EQUAL, - GREATER, - GREATER_EQUAL, - LESS, - LESS_EQUAL, - MINUS, - MINUS_GREATER, - PAREN_LEFT, - PAREN_RIGHT, - PLUS, - QUESTION_EQUAL, - SEMICOLON, - SLASH, - SLASH_EQUAL, - - ; - - public boolean isBinaryOp() { - return switch (this) { - case ASTERISK, SLASH, PLUS, MINUS, QUESTION_EQUAL, SLASH_EQUAL, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL - -> true; - default -> false; - }; - } - - public BinaryExpression.Operator toBinaryOp() { - return switch (this) { - case ASTERISK -> BinaryExpression.Operator.MULTIPLY; - case SLASH -> BinaryExpression.Operator.DIVIDE; - case PLUS -> BinaryExpression.Operator.ADD; - case MINUS -> BinaryExpression.Operator.SUBTRACT; - case QUESTION_EQUAL -> BinaryExpression.Operator.EQUALS; - case SLASH_EQUAL -> BinaryExpression.Operator.NOT_EQUALS; - case GREATER -> BinaryExpression.Operator.GT; - case GREATER_EQUAL -> BinaryExpression.Operator.GTE; - case LESS -> BinaryExpression.Operator.LT; - case LESS_EQUAL -> BinaryExpression.Operator.LTE; - default -> throw new IllegalStateException("Token " + this + " is not a binary operator"); - }; - } - - public boolean isUnaryOp() { - return switch (this) { - case PLUS, MINUS, BANG -> true; - default -> false; - }; - } - - public UnaryExpression.Operator toUnaryOp() { - return switch (this) { - case PLUS -> UnaryExpression.Operator.PLUS; - case MINUS -> UnaryExpression.Operator.NEGATE; - case BANG -> UnaryExpression.Operator.NOT; - default -> throw new IllegalStateException("Token " + this + " is not a unary operator"); - }; - } -} diff --git a/src/main/java/lv/enes/orang/ast/ArrayExpression.java b/src/main/java/lv/enes/orang/ast/ArrayExpression.java deleted file mode 100644 index d2437cf..0000000 --- a/src/main/java/lv/enes/orang/ast/ArrayExpression.java +++ /dev/null @@ -1,21 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Array; -import lv.enes.orang.value.Value; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public record ArrayExpression(List items) implements Expression { - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - var values = new ArrayList(); - for (var item : items) { - values.add(item.evaluateExpression(scope)); - } - return new Array(Collections.unmodifiableList(values)); - } -} diff --git a/src/main/java/lv/enes/orang/ast/BinaryExpression.java b/src/main/java/lv/enes/orang/ast/BinaryExpression.java deleted file mode 100644 index c0b988c..0000000 --- a/src/main/java/lv/enes/orang/ast/BinaryExpression.java +++ /dev/null @@ -1,53 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Value; - -public record BinaryExpression(Operator operator, Expression lhs, Expression rhs) implements Expression { - public enum Operator { - EQUALS, - NOT_EQUALS, - GT, - GTE, - LT, - LTE, - ADD, - SUBTRACT, - MULTIPLY, - DIVIDE, - ; - - public boolean bindsStrongerThan(Operator other) { - return switch (this) { - case EQUALS, NOT_EQUALS, GT, GTE, LT, LTE -> false; - case ADD, SUBTRACT -> switch (other) { - case EQUALS, NOT_EQUALS, GT, GTE, LTE -> true; - default -> false; - }; - case MULTIPLY, DIVIDE -> switch (other) { - case EQUALS, NOT_EQUALS, GT, GTE, LT, LTE, ADD, SUBTRACT -> true; - default -> false; - }; - }; - } - } - - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - var lhs = lhs().evaluateExpression(scope); - var rhs = rhs().evaluateExpression(scope); - return switch (operator) { - case EQUALS -> lhs.orangEquals(rhs); - case NOT_EQUALS -> lhs.notEquals(rhs); - case GT -> lhs.greaterThan(rhs); - case GTE -> lhs.greaterThanOrEqual(rhs); - case LT -> lhs.lessThan(rhs); - case LTE -> lhs.lessThanOrEqual(rhs); - case ADD -> lhs.add(rhs); - case SUBTRACT -> lhs.subtract(rhs); - case MULTIPLY -> lhs.multiply(rhs); - case DIVIDE -> lhs.divide(rhs); - }; - } -} diff --git a/src/main/java/lv/enes/orang/ast/BooleanLiteral.java b/src/main/java/lv/enes/orang/ast/BooleanLiteral.java deleted file mode 100644 index 9110f67..0000000 --- a/src/main/java/lv/enes/orang/ast/BooleanLiteral.java +++ /dev/null @@ -1,12 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.Scope; -import lv.enes.orang.value.OrangBoolean; -import lv.enes.orang.value.Value; - -public record BooleanLiteral(boolean value) implements Expression { - @Override - public Value evaluateExpression(Scope scope) { - return OrangBoolean.of(value); - } -} diff --git a/src/main/java/lv/enes/orang/ast/CallExpression.java b/src/main/java/lv/enes/orang/ast/CallExpression.java deleted file mode 100644 index 8f05496..0000000 --- a/src/main/java/lv/enes/orang/ast/CallExpression.java +++ /dev/null @@ -1,12 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Value; - -public record CallExpression(Expression callee, Expression arg) implements Expression { - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - return callee.evaluateExpression(scope).call(arg.evaluateExpression(scope)); - } -} diff --git a/src/main/java/lv/enes/orang/ast/DefSpec.java b/src/main/java/lv/enes/orang/ast/DefSpec.java deleted file mode 100644 index 2233d3f..0000000 --- a/src/main/java/lv/enes/orang/ast/DefSpec.java +++ /dev/null @@ -1,6 +0,0 @@ -package lv.enes.orang.ast; - -import java.util.List; - -public record DefSpec(String name, List args) { -} diff --git a/src/main/java/lv/enes/orang/ast/Definition.java b/src/main/java/lv/enes/orang/ast/Definition.java deleted file mode 100644 index 715daae..0000000 --- a/src/main/java/lv/enes/orang/ast/Definition.java +++ /dev/null @@ -1,15 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Undefined; - -public record Definition(String name, Expression body) implements Statement { - @Override - public Scope runStatement(Scope scope) throws OrangRuntimeException { - scope.setDefinition(name, Undefined.INSTANCE); - var value = body.evaluateExpression(scope); - scope.setDefinition(name, value); - return scope; - } -} diff --git a/src/main/java/lv/enes/orang/ast/DoExpression.java b/src/main/java/lv/enes/orang/ast/DoExpression.java deleted file mode 100644 index 9d69a3e..0000000 --- a/src/main/java/lv/enes/orang/ast/DoExpression.java +++ /dev/null @@ -1,19 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Value; - -import java.util.List; - -public record DoExpression(List body) implements Expression { - // assert body.!isEmpty() - - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - for (var i = 0; i < body.size() - 1; i++) { - body.get(i).evaluateExpression(scope); - } - return body.getLast().evaluateExpression(scope); - } -} diff --git a/src/main/java/lv/enes/orang/ast/Expression.java b/src/main/java/lv/enes/orang/ast/Expression.java deleted file mode 100644 index a0fc3af..0000000 --- a/src/main/java/lv/enes/orang/ast/Expression.java +++ /dev/null @@ -1,14 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Value; - -public interface Expression extends Statement { - Value evaluateExpression(Scope scope) throws OrangRuntimeException; - - @Override - default Scope runStatement(Scope scope) throws OrangRuntimeException { - return scope.withLastResult(evaluateExpression(scope)); - } -} diff --git a/src/main/java/lv/enes/orang/ast/FnExpression.java b/src/main/java/lv/enes/orang/ast/FnExpression.java deleted file mode 100644 index 3c3522f..0000000 --- a/src/main/java/lv/enes/orang/ast/FnExpression.java +++ /dev/null @@ -1,14 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.NonEmptyList; -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Function; -import lv.enes.orang.value.Value; - -public record FnExpression(NonEmptyList args, Expression body) implements Expression { - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - return new Function(scope, args, body); - } -} diff --git a/src/main/java/lv/enes/orang/ast/IfElseExpression.java b/src/main/java/lv/enes/orang/ast/IfElseExpression.java deleted file mode 100644 index 9b52cc5..0000000 --- a/src/main/java/lv/enes/orang/ast/IfElseExpression.java +++ /dev/null @@ -1,22 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.OrangBoolean; -import lv.enes.orang.value.Value; - -public record IfElseExpression(Expression condition, Expression trueBranch, Expression falseBranch) implements Expression { - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - var condValue = condition.evaluateExpression(scope); - if (condValue instanceof OrangBoolean value) { - if (value.value()) { - return trueBranch.evaluateExpression(scope); - } else { - return falseBranch.evaluateExpression(scope); - } - } else { - throw new OrangRuntimeException(STR."Condition in an if should be a Boolean not a \{condValue.typeName()}"); - } - } -} diff --git a/src/main/java/lv/enes/orang/ast/IntLiteral.java b/src/main/java/lv/enes/orang/ast/IntLiteral.java deleted file mode 100644 index 339ba3b..0000000 --- a/src/main/java/lv/enes/orang/ast/IntLiteral.java +++ /dev/null @@ -1,12 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.Scope; -import lv.enes.orang.value.OrangInteger; -import lv.enes.orang.value.Value; - -public record IntLiteral(int value) implements Expression { - @Override - public Value evaluateExpression(Scope scope) { - return new OrangInteger(value); - } -} diff --git a/src/main/java/lv/enes/orang/ast/LetInExpression.java b/src/main/java/lv/enes/orang/ast/LetInExpression.java deleted file mode 100644 index 2379902..0000000 --- a/src/main/java/lv/enes/orang/ast/LetInExpression.java +++ /dev/null @@ -1,27 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Undefined; -import lv.enes.orang.value.Value; - -import java.util.HashMap; -import java.util.List; - -public record LetInExpression(List bindings, Expression body) implements Expression { - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - var newDefs = new HashMap(); - for (var binding : bindings) { - newDefs.put(binding.name, Undefined.INSTANCE); - } - var newState = scope.withDefinitions(newDefs); - for (var binding : bindings) { - var value = binding.value.evaluateExpression(newState); - newState.setDefinition(binding.name, value); - } - return body.evaluateExpression(newState); - } - - public record Binding(String name, Expression value) {} -} diff --git a/src/main/java/lv/enes/orang/ast/Program.java b/src/main/java/lv/enes/orang/ast/Program.java deleted file mode 100644 index ae5d31e..0000000 --- a/src/main/java/lv/enes/orang/ast/Program.java +++ /dev/null @@ -1,17 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; - -import java.util.List; - -public record Program(List statements) implements Statement { - @Override - public Scope runStatement(Scope inScope) throws OrangRuntimeException { - var state = inScope; - for (var statement : statements) { - state = statement.runStatement(state); - } - return state; - } -} diff --git a/src/main/java/lv/enes/orang/ast/Statement.java b/src/main/java/lv/enes/orang/ast/Statement.java deleted file mode 100644 index 0f52fc3..0000000 --- a/src/main/java/lv/enes/orang/ast/Statement.java +++ /dev/null @@ -1,8 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; - -public interface Statement { - Scope runStatement(Scope scope) throws OrangRuntimeException; -} diff --git a/src/main/java/lv/enes/orang/ast/StringLiteral.java b/src/main/java/lv/enes/orang/ast/StringLiteral.java deleted file mode 100644 index 367dff5..0000000 --- a/src/main/java/lv/enes/orang/ast/StringLiteral.java +++ /dev/null @@ -1,13 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.OrangString; -import lv.enes.orang.value.Value; - -public record StringLiteral(String value) implements Expression { - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - return new OrangString(value); - } -} diff --git a/src/main/java/lv/enes/orang/ast/UnaryExpression.java b/src/main/java/lv/enes/orang/ast/UnaryExpression.java deleted file mode 100644 index a0f565b..0000000 --- a/src/main/java/lv/enes/orang/ast/UnaryExpression.java +++ /dev/null @@ -1,23 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Value; - -public record UnaryExpression(Operator operator, Expression child) implements Expression { - public enum Operator { - PLUS, - NEGATE, - NOT, - } - - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - var child = child().evaluateExpression(scope); - return switch (operator) { - case PLUS -> child.plus(); - case NEGATE -> child.negate(); - case NOT -> child.not(); - }; - } -} diff --git a/src/main/java/lv/enes/orang/ast/VariableExpression.java b/src/main/java/lv/enes/orang/ast/VariableExpression.java deleted file mode 100644 index 1f5334d..0000000 --- a/src/main/java/lv/enes/orang/ast/VariableExpression.java +++ /dev/null @@ -1,12 +0,0 @@ -package lv.enes.orang.ast; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.value.Value; - -public record VariableExpression(String name) implements Expression { - @Override - public Value evaluateExpression(Scope scope) throws OrangRuntimeException { - return scope.getDefinition(name); - } -} diff --git a/src/main/java/lv/enes/orang/value/Array.java b/src/main/java/lv/enes/orang/value/Array.java deleted file mode 100644 index c3b645d..0000000 --- a/src/main/java/lv/enes/orang/value/Array.java +++ /dev/null @@ -1,65 +0,0 @@ -package lv.enes.orang.value; - -import lv.enes.orang.OrangRuntimeException; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public record Array(List items) implements Value { - @Override - public String typeName() { - return "Array"; - } - - @Override - public String stringify() { - if (items.isEmpty()) { - return "[]"; - } - var sb = new StringBuilder("["); - sb.append(items.getFirst().stringify()); - for (int i = 1; i < items.size(); i++) { - sb.append(", ").append(items.get(i).stringify()); - } - sb.append("]"); - return sb.toString(); - } - - @Override - public String display() { - if (items.isEmpty()) { - return ""; - } - var sb = new StringBuilder(); - sb.append(items.getFirst().display()); - for (var i = 1; i < items.size(); i++) { - sb.append(" ").append(items.get(i).display()); - } - return sb.toString(); - } - - @Override - public Value add(Value rhs) throws OrangRuntimeException { - if (rhs instanceof Array(var rhsi)) { - var newItems = new ArrayList<>(this.items); - newItems.addAll(rhsi); - return new Array(Collections.unmodifiableList(newItems)); - } else { - throw new OrangRuntimeException(STR."add not implemented for Array and \{rhs.typeName()}"); - } - } - - @Override - public Value multiply(Value rhs) throws OrangRuntimeException { - if (rhs instanceof OrangInteger(var repeat)) { - var newItems = new ArrayList(items.size() * repeat); - for (var i = 0; i < repeat; i++) { - newItems.addAll(items); - } - return new Array(Collections.unmodifiableList(newItems)); - } else { - throw new OrangRuntimeException(STR."multiply not implemented for Array and \{rhs.typeName()}"); - } - } -} diff --git a/src/main/java/lv/enes/orang/value/BuiltinFunction.java b/src/main/java/lv/enes/orang/value/BuiltinFunction.java deleted file mode 100644 index 92ccc2e..0000000 --- a/src/main/java/lv/enes/orang/value/BuiltinFunction.java +++ /dev/null @@ -1,31 +0,0 @@ -package lv.enes.orang.value; - -import lv.enes.orang.OrangRuntimeException; - -import java.util.List; - -public record BuiltinFunction(int argCount, Impl impl) implements Value { - @FunctionalInterface - public interface Impl { - Value apply(List args) throws OrangRuntimeException; - } - - @Override - public String typeName() { - return "BuiltinFunction"; - } - - @Override - public String stringify() { - return "#builtinFunction"; - } - - @Override - public Value call(Value param) throws OrangRuntimeException { - if (argCount == 1) { - return impl.apply(List.of(param)); - } else { - return new PartialBuiltinFunction(argCount, List.of(param), impl); - } - } -} diff --git a/src/main/java/lv/enes/orang/value/Function.java b/src/main/java/lv/enes/orang/value/Function.java deleted file mode 100644 index 1f288c6..0000000 --- a/src/main/java/lv/enes/orang/value/Function.java +++ /dev/null @@ -1,28 +0,0 @@ -package lv.enes.orang.value; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.ast.Expression; - -import java.util.List; - -public record Function(Scope scope, List args, Expression body) implements Value { - @Override - public String typeName() { - return "Function"; - } - - @Override - public String stringify() { - return "#function"; - } - - @Override - public Value call(Value param) throws OrangRuntimeException { - if (args.size() == 1) { - return body.evaluateExpression(scope.withDefinition(args.getFirst(), param)); - } else { - return new PartialFunction(scope, args, List.of(param), body); - } - } -} diff --git a/src/main/java/lv/enes/orang/value/OrangBoolean.java b/src/main/java/lv/enes/orang/value/OrangBoolean.java deleted file mode 100644 index 8a4776c..0000000 --- a/src/main/java/lv/enes/orang/value/OrangBoolean.java +++ /dev/null @@ -1,55 +0,0 @@ -package lv.enes.orang.value; - -import lombok.EqualsAndHashCode; -import lv.enes.orang.OrangRuntimeException; - -@EqualsAndHashCode -public final class OrangBoolean implements Value { - public final static OrangBoolean TRUE = new OrangBoolean(true); - public final static OrangBoolean FALSE = new OrangBoolean(false); - - private final boolean value; - - private OrangBoolean(boolean value) { - this.value = value; - } - - public static OrangBoolean of(boolean value) { - if (value) { - return TRUE; - } - return FALSE; - } - - public boolean value() { - return value; - } - - @Override - public String typeName() { - return "Boolean"; - } - - @Override - public String stringify() { - if (value) { - return "true"; - } else { - return "false"; - } - } - - @Override - public OrangBoolean not() { - return new OrangBoolean(!value); - } - - @Override - public OrangBoolean or(Value rhs) throws OrangRuntimeException { - if (rhs instanceof OrangBoolean rhsb) { - return new OrangBoolean(value || rhsb.value); - } else { - throw new OrangRuntimeException(STR."or is not implemented for Boolean and \{rhs.typeName()}"); - } - } -} diff --git a/src/main/java/lv/enes/orang/value/OrangInteger.java b/src/main/java/lv/enes/orang/value/OrangInteger.java deleted file mode 100644 index 9b8d505..0000000 --- a/src/main/java/lv/enes/orang/value/OrangInteger.java +++ /dev/null @@ -1,70 +0,0 @@ -package lv.enes.orang.value; - -import lv.enes.orang.OrangRuntimeException; - -public record OrangInteger(int value) implements Value { - @Override - public String typeName() { - return "Integer"; - } - - @Override - public String stringify() { - return Integer.toString(value); - } - - @Override - public OrangInteger negate() { - return new OrangInteger(-value); - } - - @Override - public OrangInteger plus() { - return this; - } - - @Override - public OrangInteger add(Value rhs) throws OrangRuntimeException { - if (rhs instanceof OrangInteger(int rhsi)) { - return new OrangInteger(value + rhsi); - } else { - throw new OrangRuntimeException(STR."add is not implemented for Integer and \{rhs.typeName()}"); - } - } - - @Override - public OrangInteger divide(Value rhs) throws OrangRuntimeException { - if (rhs instanceof OrangInteger(int rhsi)) { - return new OrangInteger(value / rhsi); - } else { - throw new OrangRuntimeException(STR."divide is not implemented for Integer and \{rhs.typeName()}"); - } - } - - @Override - public OrangInteger multiply(Value rhs) throws OrangRuntimeException { - if (rhs instanceof OrangInteger(int rhsi)) { - return new OrangInteger(value * rhsi); - } else { - throw new OrangRuntimeException(STR."multiply is not implemented for Integer and \{rhs.typeName()}"); - } - } - - @Override - public OrangInteger subtract(Value rhs) throws OrangRuntimeException { - if (rhs instanceof OrangInteger(int rhsi)) { - return new OrangInteger(value - rhsi); - } else { - throw new OrangRuntimeException(STR."subtract is not implemented for Integer and \{rhs.typeName()}"); - } - } - - @Override - public OrangBoolean greaterThan(Value rhs) throws OrangRuntimeException { - if (rhs instanceof OrangInteger(int rhsi)) { - return OrangBoolean.of(value > rhsi); - } else { - throw new OrangRuntimeException(STR."greaterThan is not implemented for Integer and \{rhs.typeName()}"); - } - } -} diff --git a/src/main/java/lv/enes/orang/value/OrangString.java b/src/main/java/lv/enes/orang/value/OrangString.java deleted file mode 100644 index c03f7ac..0000000 --- a/src/main/java/lv/enes/orang/value/OrangString.java +++ /dev/null @@ -1,49 +0,0 @@ -package lv.enes.orang.value; - -import lv.enes.orang.OrangRuntimeException; - -public record OrangString(String value) implements Value { - @Override - public String typeName() { - return "String"; - } - - @Override - public String stringify() { - var sb = new StringBuilder("\""); - var cps = value.codePoints().iterator(); - while (cps.hasNext()) { - var cp = cps.next(); - if (cp == '"') { - sb.append("\\\""); - } else { - sb.appendCodePoint(cp); - } - } - sb.append('"'); - return sb.toString(); - } - - @Override - public String display() { - return value; - } - - @Override - public OrangString add(Value rhs) throws OrangRuntimeException { - if (rhs instanceof OrangString(String rhss)) { - return new OrangString(value + rhss); - } else { - throw new OrangRuntimeException(STR."add is not implemented for Integer and \{rhs.typeName()}"); - } - } - - @Override - public Value multiply(Value rhs) throws OrangRuntimeException { - if (rhs instanceof OrangInteger(var repeat)) { - return new OrangString(value.repeat(Math.max(0, repeat))); - } else { - throw new OrangRuntimeException(STR."multiply not implemented for Array and \{rhs.typeName()}"); - } - } -} diff --git a/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java b/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java deleted file mode 100644 index 37278a4..0000000 --- a/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java +++ /dev/null @@ -1,32 +0,0 @@ -package lv.enes.orang.value; - -import lv.enes.orang.OrangRuntimeException; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public record PartialBuiltinFunction(int argCount, List params, BuiltinFunction.Impl impl) implements Value { - @Override - public String typeName() { - return "BuiltinFunction"; - } - - @Override - public String stringify() { - return "#builtinFunction"; - } - - @Override - public Value call(Value param) throws OrangRuntimeException { - List newParams = new ArrayList<>(params); - newParams.add(param); - newParams = Collections.unmodifiableList(newParams); - - if (newParams.size() == argCount) { - return impl.apply(newParams); - } else { - return new PartialBuiltinFunction(argCount, newParams, impl); - } - } -} diff --git a/src/main/java/lv/enes/orang/value/PartialFunction.java b/src/main/java/lv/enes/orang/value/PartialFunction.java deleted file mode 100644 index 59e1466..0000000 --- a/src/main/java/lv/enes/orang/value/PartialFunction.java +++ /dev/null @@ -1,37 +0,0 @@ -package lv.enes.orang.value; - -import lv.enes.orang.OrangRuntimeException; -import lv.enes.orang.Scope; -import lv.enes.orang.ast.Expression; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -public record PartialFunction(Scope scope, List args, List params, Expression body) implements Value { - @Override - public String typeName() { - return "Function"; - } - - @Override - public String stringify() { - return "#function"; - } - - @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)); - } - return body.evaluateExpression(scope.withDefinitions(newDefs)); - } - - return new PartialFunction(scope, args, Collections.unmodifiableList(newParams), body); - } -} diff --git a/src/main/java/lv/enes/orang/value/Undefined.java b/src/main/java/lv/enes/orang/value/Undefined.java deleted file mode 100644 index a341ee8..0000000 --- a/src/main/java/lv/enes/orang/value/Undefined.java +++ /dev/null @@ -1,21 +0,0 @@ -package lv.enes.orang.value; - -import lombok.EqualsAndHashCode; - -@EqualsAndHashCode -public final class Undefined implements Value { - public static final Undefined INSTANCE = new Undefined(); - - private Undefined() { - } - - @Override - public String typeName() { - return "Undefined"; - } - - @Override - public String stringify() { - return "#undefined"; - } -} diff --git a/src/main/java/lv/enes/orang/value/Value.java b/src/main/java/lv/enes/orang/value/Value.java deleted file mode 100644 index fa8275c..0000000 --- a/src/main/java/lv/enes/orang/value/Value.java +++ /dev/null @@ -1,77 +0,0 @@ -package lv.enes.orang.value; - -import lv.enes.orang.OrangRuntimeException; - -public sealed interface Value - permits Array, BuiltinFunction, Function, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction, - PartialFunction, Undefined { - String typeName(); - String stringify(); - - default String display() { - return stringify(); - } - - - default Value negate() throws OrangRuntimeException { - throw new OrangRuntimeException(STR."negate is not implemented for \{typeName()}"); - } - - default Value not() throws OrangRuntimeException { - throw new OrangRuntimeException(STR."not is not implemented for \{typeName()}"); - } - - default Value plus() throws OrangRuntimeException { - throw new OrangRuntimeException(STR."plus is not implemented for \{typeName()}"); - } - - - default Value add(Value rhs) throws OrangRuntimeException { - throw new OrangRuntimeException(STR."add is not implemented for \{typeName()} and \{rhs.typeName()}"); - } - - default Value call(Value rhs) throws OrangRuntimeException { - throw new OrangRuntimeException(STR."call is not implemented for \{typeName()} and \{rhs.typeName()}"); - } - - default Value divide(Value rhs) throws OrangRuntimeException { - throw new OrangRuntimeException(STR."divide is not implemented for \{typeName()} and \{rhs.typeName()}"); - } - - default Value multiply(Value rhs) throws OrangRuntimeException { - throw new OrangRuntimeException(STR."multiply is not implemented for \{typeName()} and \{rhs.typeName()}"); - } - - default Value or(Value rhs) throws OrangRuntimeException { - throw new OrangRuntimeException(STR."or is not implemented for \{typeName()} and \{rhs.typeName()}"); - } - - default Value subtract(Value rhs) throws OrangRuntimeException { - throw new OrangRuntimeException(STR."subtract is not implemented for \{typeName()} and \{rhs.typeName()}"); - } - - - default OrangBoolean greaterThan(Value rhs) throws OrangRuntimeException { - throw new OrangRuntimeException(STR."greater than is not implemented for \{typeName()} and \{rhs.typeName()}"); - } - - default OrangBoolean greaterThanOrEqual(Value rhs) throws OrangRuntimeException { - return greaterThan(rhs).or(orangEquals(rhs)); - } - - default OrangBoolean lessThan(Value rhs) throws OrangRuntimeException { - return greaterThanOrEqual(rhs).not(); - } - - default OrangBoolean lessThanOrEqual(Value rhs) throws OrangRuntimeException { - return greaterThan(rhs).not(); - } - - default OrangBoolean orangEquals(Value rhs) { - return OrangBoolean.of(this.equals(rhs)); - } - - default OrangBoolean notEquals(Value rhs) { - return orangEquals(rhs).not(); - } -} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java deleted file mode 100644 index a4bdb66..0000000 --- a/src/main/java/module-info.java +++ /dev/null @@ -1,9 +0,0 @@ -module lv.enes.orang { - exports lv.enes.orang; - - requires static jakarta.annotation; - requires static lombok; - - requires org.slf4j; - requires org.slf4j.simple; -} \ No newline at end of file diff --git a/src/main/resources/lv/enes/orang/prelude.orang b/src/main/resources/lv/enes/orang/prelude.orang deleted file mode 100644 index 36bf197..0000000 --- a/src/main/resources/lv/enes/orang/prelude.orang +++ /dev/null @@ -1,20 +0,0 @@ -def isRepl = __builtin_isRepl () -def len arrayOrString = __builtin_len arrayOrString -def parseInt stringOrInt = __builtin_parseInt stringOrInt -def print anything = __builtin_print anything -def printLn x = do print x; print "\n"; x end -def randInt min max = __builtin_randInt min max -def readInt _ = parseInt (readLn ()) -def readLn _ = __builtin_readLn () - -def _ = - if isRepl then do - printLn "Hello! This is the Orang Programming Language!"; - printLn "You can execute code right here at the prompt."; - printLn "If you wish to load & execute a file, use `:l filename.orang'."; - printLn "If you wish to quit, use `:q'."; - printLn "Have a nice day :)"; - printLn "" - end else - () - diff --git a/utils/build.gradle.kts b/utils/build.gradle.kts new file mode 100644 index 0000000..04cda43 --- /dev/null +++ b/utils/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + java + id("io.freefair.lombok") version "8.6" +} + +repositories { + mavenCentral() +} + +java { + sourceCompatibility = JavaVersion.VERSION_22 + targetCompatibility = JavaVersion.VERSION_22 + toolchain { + languageVersion = JavaLanguageVersion.of(22) + } +} + +tasks.withType { + options.compilerArgs.add("--enable-preview") +} \ No newline at end of file diff --git a/utils/src/main/java/lv/enes/orang/utils/NonEmptyList.java b/utils/src/main/java/lv/enes/orang/utils/NonEmptyList.java new file mode 100644 index 0000000..197491f --- /dev/null +++ b/utils/src/main/java/lv/enes/orang/utils/NonEmptyList.java @@ -0,0 +1,28 @@ +package lv.enes.orang.utils; + +import lombok.EqualsAndHashCode; + +import java.util.AbstractList; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +public class NonEmptyList extends AbstractList { + private final List delegate; + + public NonEmptyList(List delegate) { + if (delegate.isEmpty()) { + throw new IllegalArgumentException("Empty list"); + } + this.delegate = List.copyOf(delegate); + } + + @Override + public E get(int index) { + return delegate.get(index); + } + + @Override + public int size() { + return delegate.size(); + } +} diff --git a/utils/src/main/java/module-info.java b/utils/src/main/java/module-info.java new file mode 100644 index 0000000..47e37e0 --- /dev/null +++ b/utils/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module lv.enes.orang.utils { + exports lv.enes.orang.utils; + + requires static lombok; +} \ No newline at end of file -- cgit v1.2.3