From ce423971e6ea3b4126806eea4b6874daee9f07b2 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Mon, 19 Aug 2024 01:15:51 +0800 Subject: Added a checker module. NOTE: I think I should look at guava or commons for some sort of sealable Map instead of the current Scope :D --- checker/build.gradle.kts | 30 +++++ .../main/java/lv/enes/orang/checker/Checker.java | 146 +++++++++++++++++++++ .../lv/enes/orang/checker/CheckerException.java | 9 ++ checker/src/main/java/module-info.java | 9 ++ 4 files changed, 194 insertions(+) create mode 100644 checker/build.gradle.kts create mode 100644 checker/src/main/java/lv/enes/orang/checker/Checker.java create mode 100644 checker/src/main/java/lv/enes/orang/checker/CheckerException.java create mode 100644 checker/src/main/java/module-info.java (limited to 'checker') diff --git a/checker/build.gradle.kts b/checker/build.gradle.kts new file mode 100644 index 0000000..08e9b33 --- /dev/null +++ b/checker/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + java + id("io.freefair.lombok") version "8.6" +} + +val slf4jVersion = "2.0.13" + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.slf4j:slf4j-api:$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") +} \ No newline at end of file diff --git a/checker/src/main/java/lv/enes/orang/checker/Checker.java b/checker/src/main/java/lv/enes/orang/checker/Checker.java new file mode 100644 index 0000000..446d3f1 --- /dev/null +++ b/checker/src/main/java/lv/enes/orang/checker/Checker.java @@ -0,0 +1,146 @@ +package lv.enes.orang.checker; + +import lv.enes.orang.ast.*; +import lv.enes.orang.core.ImmutableScope; +import lv.enes.orang.core.Scope; + +import java.util.HashMap; +import java.util.Map; + +public class Checker implements ExpressionVisitor, StatementVisitor { + private final Scope definitions; + + public static Checker of(Map builtins) { + var boolMap = builtins.keySet() + .stream() + .>collect( + HashMap::new, + (map, elem) -> map.put(elem, true), + Map::putAll + ); + return new Checker(ImmutableScope.of(boolMap)); + } + + private Checker(Scope definitions) { + this.definitions = definitions; + } + + @Override + public Void visitArray(ArrayExpression array) throws CheckerException { + for (var expr : array.items()) { + visit(expr); + } + return null; + } + + @Override + public Void visitBoolean(BooleanLiteral expr) { + // Always ok + return null; + } + + @Override + public Void visitBinaryExpression(BinaryExpression expr) throws CheckerException { + visit(expr.lhs()); + visit(expr.rhs()); + return null; + } + + @Override + public Void visitCallExpression(CallExpression expr) throws CheckerException { + visit(expr.callee()); + visit(expr.arg()); + return null; + } + + @Override + public Checker visitDefinition(Definition def) throws CheckerException { + if (definitions.hasDefinition(def.name())) { + throw new CheckerException(STR."Top-level definition '\{def.name()}' redefined!"); + } + return new Checker(ImmutableScope.of(definitions, def.name(), true)); + } + + @Override + public Void visitDoExpression(DoExpression expr) throws CheckerException { + for (var child : expr.body()) { + visit(child); + } + return null; + } + + @Override + public Checker visitExpression(ExpressionStatement expr) throws CheckerException { + visit(expr.expr()); + return this; + } + + @Override + public Void visitFnExpression(FnExpression expr) throws CheckerException { + var args = new HashMap(); + for (var arg : expr.args()) { + args.put(arg.name, true); + } + new Checker(ImmutableScope.of(definitions, args)).visit(expr.body()); + return null; + } + + @Override + public Void visitIfElseExpression(IfElseExpression expr) throws CheckerException { + visit(expr.condition()); + visit(expr.trueBranch()); + visit(expr.falseBranch()); + return null; + } + + @Override + public Void visitIntLiteral(IntLiteral expr) { + // Always ok + return null; + } + + @Override + public Void visitLetInExpression(LetInExpression expr) throws CheckerException { + var locals = new HashMap(); + for (var local : expr.bindings()) { + locals.put(local.name(), true); + } + new Checker(ImmutableScope.of(definitions, locals)).visit(expr.body()); + return null; + } + + @Override + public Checker visitProgram(Program program) throws CheckerException { + var checker = this; + for (var stmt : program.statements()) { + checker = checker.visit(stmt); + } + return checker; + } + + @Override + public Void visitStringLiteral(StringLiteral expr) { + // Always ok + return null; + } + + @Override + public Void visitUnaryExpression(UnaryExpression expr) throws CheckerException { + visit(expr.child()); + return null; + } + + @Override + public Void visitVariable(VariableExpression expr) throws CheckerException { + if (!definitions.hasDefinition(expr.name())) { + throw new CheckerException(STR."Variable named '\{expr.name()}' not defined!"); + } + return null; + } + + @Override + public Void visitVoidExpression() { + // Always ok + return null; + } +} diff --git a/checker/src/main/java/lv/enes/orang/checker/CheckerException.java b/checker/src/main/java/lv/enes/orang/checker/CheckerException.java new file mode 100644 index 0000000..9354f27 --- /dev/null +++ b/checker/src/main/java/lv/enes/orang/checker/CheckerException.java @@ -0,0 +1,9 @@ +package lv.enes.orang.checker; + +import lv.enes.orang.core.OrangException; + +public class CheckerException extends OrangException { + public CheckerException(String message) { + super(message); + } +} diff --git a/checker/src/main/java/module-info.java b/checker/src/main/java/module-info.java new file mode 100644 index 0000000..6e5fa47 --- /dev/null +++ b/checker/src/main/java/module-info.java @@ -0,0 +1,9 @@ +module lv.enes.orang.checker { + exports lv.enes.orang.checker; + + 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