From 5f4b45ec7634f2728693e93f862419e495db6979 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Thu, 22 Aug 2024 22:55:13 +0800 Subject: Move Evaluator to a new module --- .../main/java/lv/enes/orang/evaluator/Array.java | 65 +++++++++ .../lv/enes/orang/evaluator/BuiltinFunction.java | 31 +++++ .../java/lv/enes/orang/evaluator/Evaluator.java | 152 +++++++++++++++++++++ .../java/lv/enes/orang/evaluator/Function.java | 25 ++++ .../main/java/lv/enes/orang/evaluator/Nothing.java | 21 +++ .../java/lv/enes/orang/evaluator/OrangBoolean.java | 55 ++++++++ .../java/lv/enes/orang/evaluator/OrangInteger.java | 70 ++++++++++ .../java/lv/enes/orang/evaluator/OrangString.java | 49 +++++++ .../orang/evaluator/PartialBuiltinFunction.java | 32 +++++ .../lv/enes/orang/evaluator/PartialFunction.java | 45 ++++++ .../java/lv/enes/orang/evaluator/Undefined.java | 21 +++ .../main/java/lv/enes/orang/evaluator/Value.java | 76 +++++++++++ evaluator/src/main/java/module-info.java | 9 ++ 13 files changed, 651 insertions(+) create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/Array.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/BuiltinFunction.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/Function.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/OrangBoolean.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/OrangInteger.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/OrangString.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/PartialBuiltinFunction.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/Undefined.java create mode 100644 evaluator/src/main/java/lv/enes/orang/evaluator/Value.java create mode 100644 evaluator/src/main/java/module-info.java (limited to 'evaluator/src/main/java') diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Array.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Array.java new file mode 100644 index 0000000..4c76eff --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Array.java @@ -0,0 +1,65 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.core.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/evaluator/src/main/java/lv/enes/orang/evaluator/BuiltinFunction.java b/evaluator/src/main/java/lv/enes/orang/evaluator/BuiltinFunction.java new file mode 100644 index 0000000..1f37280 --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/BuiltinFunction.java @@ -0,0 +1,31 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.core.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/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java new file mode 100644 index 0000000..6925bac --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Evaluator.java @@ -0,0 +1,152 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.ast.*; +import lv.enes.orang.core.OrangRuntimeException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public record Evaluator(Map scope, Value lastResult) implements ExpressionVisitor, StatementVisitor { + public Evaluator(Map 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 = new HashMap<>(scope); + newScope.put(def.name(), Undefined.INSTANCE); + var newEvaluator = new Evaluator(newScope); + newScope.put(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 = new HashMap<>(scope); + for (var binding : expr.bindings()) { + newScope.put(binding.name(), Undefined.INSTANCE); + } + var newEvaluator = new Evaluator(newScope); + for (var binding : expr.bindings()) { + newScope.put(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 { + if (scope.containsKey(expr.name())) { + return scope.get(expr.name()); + } + + throw new OrangRuntimeException(STR."Value named \{expr.name()} is not defined!"); + } + + @Override + public Value visitVoidExpression() { + return Nothing.INSTANCE; + } +} diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Function.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Function.java new file mode 100644 index 0000000..33102fb --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Function.java @@ -0,0 +1,25 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.ast.ArgSpec; +import lv.enes.orang.ast.Expression; +import lv.enes.orang.core.OrangRuntimeException; +import lv.enes.orang.utils.NonEmptyList; + +import java.util.Map; + +public record Function(Map scope, NonEmptyList 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 { + return PartialFunction.of(scope, args, body, param); + } +} diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java new file mode 100644 index 0000000..c971649 --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Nothing.java @@ -0,0 +1,21 @@ +package lv.enes.orang.evaluator; + +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode +public final class Nothing implements Value { + public static final Nothing INSTANCE = new Nothing(); + + private Nothing() { + } + + @Override + public String typeName() { + return "Nothing"; + } + + @Override + public String stringify() { + return "()"; + } +} diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/OrangBoolean.java b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangBoolean.java new file mode 100644 index 0000000..3564a9c --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangBoolean.java @@ -0,0 +1,55 @@ +package lv.enes.orang.evaluator; + +import lombok.EqualsAndHashCode; +import lv.enes.orang.core.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/evaluator/src/main/java/lv/enes/orang/evaluator/OrangInteger.java b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangInteger.java new file mode 100644 index 0000000..90de0b5 --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangInteger.java @@ -0,0 +1,70 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.core.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/evaluator/src/main/java/lv/enes/orang/evaluator/OrangString.java b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangString.java new file mode 100644 index 0000000..5c38842 --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/OrangString.java @@ -0,0 +1,49 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.core.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/evaluator/src/main/java/lv/enes/orang/evaluator/PartialBuiltinFunction.java b/evaluator/src/main/java/lv/enes/orang/evaluator/PartialBuiltinFunction.java new file mode 100644 index 0000000..f1e8cc6 --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/PartialBuiltinFunction.java @@ -0,0 +1,32 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.core.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/evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java b/evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java new file mode 100644 index 0000000..d0125f6 --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/PartialFunction.java @@ -0,0 +1,45 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.ast.ArgSpec; +import lv.enes.orang.ast.Expression; +import lv.enes.orang.core.OrangRuntimeException; +import lv.enes.orang.utils.NonEmptyList; + +import java.util.HashMap; +import java.util.Map; + +public record PartialFunction(Map scope, NonEmptyList remainingArgs, Expression body) implements Value { + public static Value of(Map scope, NonEmptyList remainingArgs, Expression body, Value param) throws OrangRuntimeException { + var spec = remainingArgs.getFirst(); + var newScope = new HashMap<>(scope); + switch (spec.getType()) { + case NAMED -> newScope.put(((ArgSpec.Named)spec).name(), param); + case NOTHING -> { + if (!(param instanceof Nothing)) { + throw new OrangRuntimeException(STR."Expected () as a parameter but got \{param.typeName()}"); + } + } + } + + if (remainingArgs.size() == 1) { + return new Evaluator(newScope).visit(body); + } else { + return new PartialFunction(newScope, new NonEmptyList<>(remainingArgs.subList(1, remainingArgs.size())), body); + } + } + + @Override + public String typeName() { + return "Function"; + } + + @Override + public String stringify() { + return "#function"; + } + + @Override + public Value call(Value param) throws OrangRuntimeException { + return PartialFunction.of(scope, remainingArgs, body, param); + } +} diff --git a/evaluator/src/main/java/lv/enes/orang/evaluator/Undefined.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Undefined.java new file mode 100644 index 0000000..2aec74b --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Undefined.java @@ -0,0 +1,21 @@ +package lv.enes.orang.evaluator; + +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/evaluator/src/main/java/lv/enes/orang/evaluator/Value.java b/evaluator/src/main/java/lv/enes/orang/evaluator/Value.java new file mode 100644 index 0000000..d8c8b9c --- /dev/null +++ b/evaluator/src/main/java/lv/enes/orang/evaluator/Value.java @@ -0,0 +1,76 @@ +package lv.enes.orang.evaluator; + +import lv.enes.orang.core.OrangRuntimeException; + +public sealed interface Value + permits Array, BuiltinFunction, Function, Nothing, 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/evaluator/src/main/java/module-info.java b/evaluator/src/main/java/module-info.java new file mode 100644 index 0000000..4cb992c --- /dev/null +++ b/evaluator/src/main/java/module-info.java @@ -0,0 +1,9 @@ +module lv.enes.orang.evaluator { + exports lv.enes.orang.evaluator; + + requires lv.enes.orang.ast; + requires lv.enes.orang.core; + requires lv.enes.orang.utils; + + requires static lombok; +} \ No newline at end of file -- cgit v1.2.3