From 19c9409ccd814fd58994b74004aad1ca33dd0a62 Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Sun, 24 Mar 2024 04:15:19 +0100 Subject: [PATCH] IF!!! --- .../ch/fritteli/gombaila/ElementWalker.java | 18 -- .../fritteli/gombaila/domain/SeqWalker.java | 1 - .../gombaila/domain/common/NodeStmt.java | 2 +- .../domain/common/NodeStmtAssign.java | 7 + .../gombaila/domain/common/NodeStmtIf.java | 7 + .../gombaila/domain/common/NodeStmtLet.java | 2 +- .../gombaila/domain/common/NodeStmtScope.java | 8 + .../gombaila/domain/common/Token.java | 6 +- .../gombaila/domain/common/TokenType.java | 5 +- .../domain/generator/ExprVisitor.java | 100 +++++++++++ .../gombaila/domain/generator/Generator.java | 169 +++--------------- .../domain/generator/StmtVisitor.java | 107 +++++++++++ .../gombaila/domain/generator/Variable.java | 6 + .../fritteli/gombaila/domain/lexer/Lexer.java | 62 ++++--- .../gombaila/domain/lexer/StringWalker.java | 24 ++- .../gombaila/domain/parser/Parser.java | 49 ++++- .../gombaila/domain/printer/RawLine.java | 2 +- src/main/resources/gombaila/simple.gb | 9 +- 18 files changed, 375 insertions(+), 209 deletions(-) create mode 100644 src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtAssign.java create mode 100644 src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtIf.java create mode 100644 src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtScope.java create mode 100644 src/main/java/ch/fritteli/gombaila/domain/generator/ExprVisitor.java create mode 100644 src/main/java/ch/fritteli/gombaila/domain/generator/StmtVisitor.java create mode 100644 src/main/java/ch/fritteli/gombaila/domain/generator/Variable.java diff --git a/src/main/java/ch/fritteli/gombaila/ElementWalker.java b/src/main/java/ch/fritteli/gombaila/ElementWalker.java index 86eaac3..a8611a4 100644 --- a/src/main/java/ch/fritteli/gombaila/ElementWalker.java +++ b/src/main/java/ch/fritteli/gombaila/ElementWalker.java @@ -12,18 +12,14 @@ public class ElementWalker { @NotNull private final BiPredicate hasNextPredicate; @NotNull - private final BiPredicate hasPrevPredicate; - @NotNull private final BiFunction atFunction; private int index = 0; public ElementWalker(@NotNull final T back, @NotNull final BiPredicate hasNextPredicate, - @NotNull final BiPredicate hasPrevPredicate, @NotNull final BiFunction atFunction) { this.back = back; this.hasNextPredicate = hasNextPredicate; - this.hasPrevPredicate = hasPrevPredicate; this.atFunction = atFunction; } @@ -40,18 +36,4 @@ public class ElementWalker { public Option peekNext() { return Option.when(this.hasNext(), () -> this.atFunction.apply(this.back, this.index)); } - - public boolean hasPrev() { - return this.hasPrevPredicate.test(this.back, this.index); - } - - @NotNull - public E prev() { - return this.atFunction.apply(this.back, --this.index); - } - - @NotNull - public Option peekPrev() { - return Option.when(this.hasPrev(), () -> this.atFunction.apply(this.back, this.index - 1)); - } } diff --git a/src/main/java/ch/fritteli/gombaila/domain/SeqWalker.java b/src/main/java/ch/fritteli/gombaila/domain/SeqWalker.java index 2f78c76..91ed3b5 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/SeqWalker.java +++ b/src/main/java/ch/fritteli/gombaila/domain/SeqWalker.java @@ -9,7 +9,6 @@ public class SeqWalker extends ElementWalker, E> { super( seq, (back, index) -> index < back.length(), - (back, index) -> index > 0, Seq::get ); } diff --git a/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmt.java b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmt.java index 5801f44..6fdc1db 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmt.java +++ b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmt.java @@ -1,4 +1,4 @@ package ch.fritteli.gombaila.domain.common; -public sealed interface NodeStmt extends Node permits NodeStmtExit, NodeStmtLet, NodeStmtPrint { +public sealed interface NodeStmt extends Node permits NodeStmtAssign, NodeStmtExit, NodeStmtIf, NodeStmtLet, NodeStmtPrint, NodeStmtScope { } diff --git a/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtAssign.java b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtAssign.java new file mode 100644 index 0000000..73fe334 --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtAssign.java @@ -0,0 +1,7 @@ +package ch.fritteli.gombaila.domain.common; + +import org.jetbrains.annotations.NotNull; + +public record NodeStmtAssign(@NotNull Token ident, @NotNull NodeExpr expr) implements NodeStmt { + +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtIf.java b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtIf.java new file mode 100644 index 0000000..07af22d --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtIf.java @@ -0,0 +1,7 @@ +package ch.fritteli.gombaila.domain.common; + +import org.jetbrains.annotations.NotNull; + +public record NodeStmtIf(@NotNull NodeExpr expr, @NotNull NodeStmtScope scope) implements NodeStmt { + +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtLet.java b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtLet.java index 58845d6..86601e0 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtLet.java +++ b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtLet.java @@ -2,6 +2,6 @@ package ch.fritteli.gombaila.domain.common; import org.jetbrains.annotations.NotNull; -public record NodeStmtLet(@NotNull Token ident, @NotNull NodeExpr expr) implements NodeStmt { +public record NodeStmtLet(@NotNull NodeStmtAssign stmtAssign) implements NodeStmt { } diff --git a/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtScope.java b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtScope.java new file mode 100644 index 0000000..936ff32 --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/common/NodeStmtScope.java @@ -0,0 +1,8 @@ +package ch.fritteli.gombaila.domain.common; + +import io.vavr.collection.Seq; +import org.jetbrains.annotations.NotNull; + +public record NodeStmtScope(@NotNull Seq stmts) implements NodeStmt { + +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/common/Token.java b/src/main/java/ch/fritteli/gombaila/domain/common/Token.java index 90b474a..c34aa60 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/common/Token.java +++ b/src/main/java/ch/fritteli/gombaila/domain/common/Token.java @@ -3,8 +3,8 @@ package ch.fritteli.gombaila.domain.common; import io.vavr.control.Option; import org.jetbrains.annotations.NotNull; -public record Token(@NotNull TokenType type, @NotNull Option value) { - public Token(@NotNull final TokenType type) { - this(type, Option.none()); +public record Token(@NotNull TokenType type, @NotNull Option value, int line, int column) { + public Token(@NotNull final TokenType type, final int line, final int column) { + this(type, Option.none(), line, column); } } diff --git a/src/main/java/ch/fritteli/gombaila/domain/common/TokenType.java b/src/main/java/ch/fritteli/gombaila/domain/common/TokenType.java index 926e9f0..9e0a82e 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/common/TokenType.java +++ b/src/main/java/ch/fritteli/gombaila/domain/common/TokenType.java @@ -12,6 +12,8 @@ public enum TokenType { SEMI, OPEN_PAREN, CLOSE_PAREN, + OPEN_CURLY, + CLOSE_CURLY, EQUALS, PLUS, MINUS, @@ -23,7 +25,8 @@ public enum TokenType { INT_LIT, IDENTIFIER, // the rest - WHITESPACE; + WHITESPACE, + IF; public boolean isBinaryOperator() { return switch (this) { diff --git a/src/main/java/ch/fritteli/gombaila/domain/generator/ExprVisitor.java b/src/main/java/ch/fritteli/gombaila/domain/generator/ExprVisitor.java new file mode 100644 index 0000000..e6747c2 --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/generator/ExprVisitor.java @@ -0,0 +1,100 @@ +package ch.fritteli.gombaila.domain.generator; + +import ch.fritteli.gombaila.domain.common.NodeBinExprAdd; +import ch.fritteli.gombaila.domain.common.NodeBinExprDiv; +import ch.fritteli.gombaila.domain.common.NodeBinExprExp; +import ch.fritteli.gombaila.domain.common.NodeBinExprMinus; +import ch.fritteli.gombaila.domain.common.NodeBinExprMod; +import ch.fritteli.gombaila.domain.common.NodeBinExprMult; +import ch.fritteli.gombaila.domain.common.NodeExpr; +import ch.fritteli.gombaila.domain.common.NodeExprIdent; +import ch.fritteli.gombaila.domain.common.NodeExprIntLit; +import io.vavr.control.Option; +import org.jetbrains.annotations.NotNull; + +final class ExprVisitor { + @NotNull + private final Generator generator; + + ExprVisitor(@NotNull final Generator generator) { + this.generator = generator; + } + + void visit(@NotNull final NodeExpr expr) { + switch (expr) { + case final NodeExprIdent exprIdent -> visit(exprIdent); + case final NodeExprIntLit exprIntLit -> visit(exprIntLit); + case final NodeBinExprAdd exprAdd -> visit(exprAdd); + case final NodeBinExprMinus exprMinus -> visit(exprMinus); + case final NodeBinExprMult exprMult -> visit(exprMult); + case final NodeBinExprDiv exprDiv -> visit(exprDiv); + case final NodeBinExprMod exprMod -> visit(exprMod); + case final NodeBinExprExp exprExp -> visit(exprExp); + } + } + + private void visit(@NotNull final NodeExprIdent expr) { + final String varName = (String) expr.ident().value().get(); + final Option variable = this.generator.vars.find(var -> var.name().equals(varName)); + + if (variable.isEmpty()) { + throw new GeneratorException("Undeclared identifier: %s".formatted(varName), expr); + } + + generator.push("qword [rsp+%d]".formatted(8 * (this.generator.stackSize - variable.get().stackLocation() - 1))); + } + + private void visit(@NotNull final NodeExprIntLit expr) { + this.generator.printer.commentedLine("store the value in rax", "mov", "rax, %s".formatted(expr.value().value().get())); + generator.push("rax"); + } + + private void visit(@NotNull final NodeBinExprAdd expr) { + generator.generateExpr(expr.lhs()); + generator.generateExpr(expr.rhs()); + generator.pop("rbx"); + generator.pop("rax"); + generator.printer.commentedLine("add rbx to rax", "add", "rax, rbx"); + generator.push("rax"); + } + + private void visit(@NotNull final NodeBinExprMinus expr) { + generator.generateExpr(expr.lhs()); + generator.generateExpr(expr.rhs()); + generator.pop("rbx"); + generator.pop("rax"); + generator.printer.commentedLine("subtract rbx from rax", "sub", "rax, rbx"); + generator.push("rax"); + } + + private void visit(@NotNull final NodeBinExprMult expr) { + generator.generateExpr(expr.lhs()); + generator.generateExpr(expr.rhs()); + generator.pop("rbx"); + generator.pop("rax"); + generator.printer.commentedLine("multiply rax by rbx", "mul", "rbx"); + generator.push("rax"); + } + + private void visit(@NotNull final NodeBinExprDiv expr) { + generator.generateExpr(expr.lhs()); + generator.generateExpr(expr.rhs()); + generator.pop("rbx"); + generator.pop("rax"); + generator.printer.commentedLine("divide rax by rbx", "div", "rbx"); + generator.push("rax"); + } + + private void visit(@NotNull final NodeBinExprMod expr) { + generator.generateExpr(expr.lhs()); + generator.generateExpr(expr.rhs()); + generator.pop("rbx"); + generator.pop("rax"); + generator.printer.commentedLine("divide rax by rbx. The remainder will be in rdx.", "div", "rbx"); + generator.push("rdx"); + } + + private void visit(@NotNull final NodeBinExprExp expr) { + throw new GeneratorException("Not yet implemented!", expr); + } +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/generator/Generator.java b/src/main/java/ch/fritteli/gombaila/domain/generator/Generator.java index 9c62799..b8af489 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/generator/Generator.java +++ b/src/main/java/ch/fritteli/gombaila/domain/generator/Generator.java @@ -1,29 +1,21 @@ package ch.fritteli.gombaila.domain.generator; -import ch.fritteli.gombaila.domain.common.NodeBinExprAdd; -import ch.fritteli.gombaila.domain.common.NodeBinExprDiv; -import ch.fritteli.gombaila.domain.common.NodeBinExprExp; -import ch.fritteli.gombaila.domain.common.NodeBinExprMinus; -import ch.fritteli.gombaila.domain.common.NodeBinExprMod; -import ch.fritteli.gombaila.domain.common.NodeBinExprMult; import ch.fritteli.gombaila.domain.common.NodeExpr; -import ch.fritteli.gombaila.domain.common.NodeExprIdent; -import ch.fritteli.gombaila.domain.common.NodeExprIntLit; import ch.fritteli.gombaila.domain.common.NodeProg; import ch.fritteli.gombaila.domain.common.NodeStmt; -import ch.fritteli.gombaila.domain.common.NodeStmtExit; -import ch.fritteli.gombaila.domain.common.NodeStmtLet; -import ch.fritteli.gombaila.domain.common.NodeStmtPrint; import ch.fritteli.gombaila.domain.printer.Printer; +import io.vavr.collection.List; +import io.vavr.collection.Seq; import org.jetbrains.annotations.NotNull; import java.time.ZonedDateTime; -import java.util.HashMap; -import java.util.Map; +import java.util.Stack; public class Generator { @NotNull - private final Printer printer = new Printer(); + final Printer printer = new Printer(); + @NotNull + final Stack scopes = new Stack<>(); @NotNull private final NodeProg nodeProg; @NotNull @@ -31,13 +23,13 @@ public class Generator { @NotNull private final StmtVisitor stmtVisitor; @NotNull - private final Map identifierStackposition = new HashMap<>(); - private int stackSize = 0; + Seq vars = List.empty(); + int stackSize = 0; public Generator(@NotNull final NodeProg nodeProg) { this.nodeProg = nodeProg; - this.exprVisitor = new ExprVisitor(); - this.stmtVisitor = new StmtVisitor(); + this.exprVisitor = new ExprVisitor(this); + this.stmtVisitor = new StmtVisitor(this); } @NotNull @@ -75,149 +67,36 @@ public class Generator { this.printer.line("syscall"); } - private void generateStmt(@NotNull final NodeStmt stmt) { + void generateStmt(@NotNull final NodeStmt stmt) { this.stmtVisitor.visit(stmt); } - private void generateExpr(@NotNull final NodeExpr expr) { + void generateExpr(@NotNull final NodeExpr expr) { this.exprVisitor.visit(expr); } - private void push(@NotNull final String reg) { + void push(@NotNull final String reg) { this.printer.commentedLine("push %s onto top of the stack".formatted(reg), "push", reg); this.stackSize++; } - private void pop(@NotNull final String reg) { + void pop(@NotNull final String reg) { this.printer.commentedLine("pop top of the stack into %s".formatted(reg), "pop", reg); this.stackSize--; } - private record Variable(int stackLocation) { + void beginScope() { + this.scopes.push(this.vars.size()); } - private final class StmtVisitor { - - private StmtVisitor() { - } - - void visit(@NotNull final NodeStmt stmt) { - switch (stmt) { - case final NodeStmtLet stmtLet -> visit(stmtLet); - case final NodeStmtExit stmtExit -> visit(stmtExit); - case final NodeStmtPrint stmtPrint -> visit(stmtPrint); - } - } - - private void visit(@NotNull final NodeStmtExit stmt) { - generateExpr(stmt.expr()); - pop("rdi"); - printer.commentedLine("Jump to the default exit routine", "jmp", "exit"); -// printer.commentedLine("Prepare for the EXIT syscall", "mov", "rax, SYSCALL_exit"); -// printer.line("syscall"); - } - - private void visit(@NotNull final NodeStmtLet stmt) { - final Variable previousValue = identifierStackposition.put(((String) stmt.ident().value().get()), new Variable(stackSize)); - if (previousValue != null) { - throw new GeneratorException("Identifier already used", stmt); - } - generateExpr(stmt.expr()); - } - - private void visit(@NotNull final NodeStmtPrint stmt) { -// generateExpr(stmt.nodeExpr()); -// // rdx: length of output -// line("mov rdx, 8"); -// // rsi: output -// pop("rsi"); -// // rdi: fd-value (STDOUT=1) -// line("mov rdi, 1"); -// // rax: syscall (SYS_WRITE=1) -// line("mov rax, 1"); -// // syscall -// line("syscall"); - throw new GeneratorException("Not implemented yet", stmt); - } - } - - private final class ExprVisitor { - private ExprVisitor() { - } - - void visit(@NotNull final NodeExpr expr) { - switch (expr) { - case final NodeExprIdent exprIdent -> visit(exprIdent); - case final NodeExprIntLit exprIntLit -> visit(exprIntLit); - case final NodeBinExprAdd exprAdd -> visit(exprAdd); - case final NodeBinExprMinus exprMinus -> visit(exprMinus); - case final NodeBinExprMult exprMult -> visit(exprMult); - case final NodeBinExprDiv exprDiv -> visit(exprDiv); - case final NodeBinExprMod exprMod -> visit(exprMod); - case final NodeBinExprExp exprExp -> visit(exprExp); - } - } - - private void visit(@NotNull final NodeExprIdent expr) { - final Variable variable = identifierStackposition.get(expr.ident().value().get()); - if (variable == null) { - throw new GeneratorException("Undeclared identifier", expr); - } - push("qword [rsp+%d]".formatted(8 * (stackSize - variable.stackLocation - 1))); - } - - private void visit(@NotNull final NodeExprIntLit expr) { - printer.commentedLine("store the value in rax", "mov", "rax, %s".formatted(expr.value().value().get())); - push("rax"); - } - - private void visit(@NotNull final NodeBinExprAdd expr) { - generateExpr(expr.lhs()); - generateExpr(expr.rhs()); - pop("rbx"); - pop("rax"); - printer.commentedLine("add rbx to rax", "add", "rax, rbx"); - push("rax"); - } - - private void visit(@NotNull final NodeBinExprMinus expr) { - generateExpr(expr.lhs()); - generateExpr(expr.rhs()); - pop("rbx"); - pop("rax"); - printer.commentedLine("subtract rbx from rax", "sub", "rax, rbx"); - push("rax"); - } - - private void visit(@NotNull final NodeBinExprMult expr) { - generateExpr(expr.lhs()); - generateExpr(expr.rhs()); - pop("rbx"); - pop("rax"); - printer.commentedLine("multiply rax by rbx", "mul", "rbx"); - push("rax"); - } - - private void visit(@NotNull final NodeBinExprDiv expr) { - generateExpr(expr.lhs()); - generateExpr(expr.rhs()); - pop("rbx"); - pop("rax"); - printer.commentedLine("divide rax by rbx", "div", "rbx"); - push("rax"); - } - - private void visit(@NotNull final NodeBinExprMod expr) { - generateExpr(expr.lhs()); - generateExpr(expr.rhs()); - pop("rbx"); - pop("rax"); - printer.commentedLine("divide rax by rbx. The remainder will be in rdx.", "div", "rbx"); - push("rdx"); - } - - private void visit(@NotNull final NodeBinExprExp expr) { - throw new GeneratorException("Not yet implemented!", expr); + void endScope() { + final int popCount = this.vars.size() - this.scopes.pop(); + if (popCount > 0) { + this.printer.commentedLine( + "Reset the stack pointer to discard out-of-scope variables", + "add", "rsp, %d".formatted(popCount * 8)); + this.stackSize -= popCount; } + this.vars = this.vars.dropRight(popCount); } } diff --git a/src/main/java/ch/fritteli/gombaila/domain/generator/StmtVisitor.java b/src/main/java/ch/fritteli/gombaila/domain/generator/StmtVisitor.java new file mode 100644 index 0000000..e79b0ed --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/generator/StmtVisitor.java @@ -0,0 +1,107 @@ +package ch.fritteli.gombaila.domain.generator; + +import ch.fritteli.gombaila.domain.common.NodeStmt; +import ch.fritteli.gombaila.domain.common.NodeStmtAssign; +import ch.fritteli.gombaila.domain.common.NodeStmtExit; +import ch.fritteli.gombaila.domain.common.NodeStmtIf; +import ch.fritteli.gombaila.domain.common.NodeStmtLet; +import ch.fritteli.gombaila.domain.common.NodeStmtPrint; +import ch.fritteli.gombaila.domain.common.NodeStmtScope; +import io.vavr.control.Option; +import org.jetbrains.annotations.NotNull; + +final class StmtVisitor { + @NotNull + private final Generator generator; + private int labelCounter = 0; + + StmtVisitor(@NotNull final Generator generator) { + this.generator = generator; + } + + void visit(@NotNull final NodeStmt stmt) { + switch (stmt) { + case final NodeStmtLet stmtLet -> visit(stmtLet); + case final NodeStmtAssign stmtAssign -> visit(stmtAssign); + case final NodeStmtExit stmtExit -> visit(stmtExit); + case final NodeStmtPrint stmtPrint -> visit(stmtPrint); + case final NodeStmtScope stmtScope -> visit(stmtScope); + case final NodeStmtIf stmtIf -> visit(stmtIf); + } + } + + private void visit(@NotNull final NodeStmtLet stmt) { + final String varName = (String) stmt.stmtAssign().ident().value().get(); + if (this.generator.vars.exists(var -> var.name().equals(varName))) { + throw new GeneratorException("Identifier already used: %s".formatted(varName), stmt); + } + + this.generator.vars = this.generator.vars.append(new Variable(varName, generator.stackSize)); + + this.generator.generateExpr(stmt.stmtAssign().expr()); + } + + private void visit(@NotNull final NodeStmtAssign stmt) { + final String varName = (String) stmt.ident().value().get(); + final Option variable = this.generator.vars.find(var -> var.name().equals(varName)); + if (variable.isEmpty()) { + throw new GeneratorException("Undeclared identifier: %s".formatted(varName), stmt); + } + + final int stackLocation = variable.get().stackLocation(); + final int targetLocation = 8 * (this.generator.stackSize - stackLocation - 1); + this.generator.generateExpr(stmt.expr()); + this.generator.pop("rbx"); + this.generator.printer.commentedLine( + "Overwrite the old value of %s in the stack", + "mov", + "[rsp+%d], rbx".formatted(targetLocation) + ); + } + + private void visit(@NotNull final NodeStmtExit stmt) { + this.generator.generateExpr(stmt.expr()); + this.generator.pop("rdi"); + this.generator.printer.commentedLine("Jump to the default exit routine", "jmp", "exit"); + } + + private void visit(@NotNull final NodeStmtPrint stmt) { +// generateExpr(stmt.nodeExpr()); +// // rdx: length of output +// line("mov rdx, 8"); +// // rsi: output +// pop("rsi"); +// // rdi: fd-value (STDOUT=1) +// line("mov rdi, 1"); +// // rax: syscall (SYS_WRITE=1) +// line("mov rax, 1"); +// // syscall +// line("syscall"); + throw new GeneratorException("Not implemented yet", stmt); + } + + private void visit(@NotNull final NodeStmtScope stmt) { + this.generator.beginScope(); + stmt.stmts().forEach(this.generator::generateStmt); + this.generator.endScope(); + } + + private void visit(@NotNull final NodeStmtIf stmt) { + final String label = this.createNextLabel(); + this.generator.generateExpr(stmt.expr()); + this.generator.pop("rax"); + this.generator.printer.commentedLine("test the condition", + "test", "rax, rax"); + this.generator.printer.commentedLine( + "skip to %s if condition is not met".formatted(label), + "jz", label + ); + this.visit(stmt.scope()); + this.generator.printer.label(label); + + } + + private String createNextLabel() { + return "label" + (this.labelCounter++); + } +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/generator/Variable.java b/src/main/java/ch/fritteli/gombaila/domain/generator/Variable.java new file mode 100644 index 0000000..4da0974 --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/generator/Variable.java @@ -0,0 +1,6 @@ +package ch.fritteli.gombaila.domain.generator; + +import org.jetbrains.annotations.NotNull; + +record Variable(@NotNull String name, int stackLocation) { +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/lexer/Lexer.java b/src/main/java/ch/fritteli/gombaila/domain/lexer/Lexer.java index f7e5951..579f088 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/lexer/Lexer.java +++ b/src/main/java/ch/fritteli/gombaila/domain/lexer/Lexer.java @@ -23,44 +23,50 @@ public class Lexer { Option next; while ((next = this.content.peekNext()).isDefined()) { final char c = next.get(); + final int line = this.content.line(); + final int column = this.content.column(); if (Character.isAlphabetic(c) || c == '_') { - this.handleAlphabeticOrUnderscore(); + this.handleAlphabeticOrUnderscore(line, column); } else if (Character.isDigit(c)) { - this.handleDigit(); + this.handleDigit(line, column); } else if (Character.isWhitespace(c)) { - this.handleWhitespace(); + this.handleWhitespace(line, column); } else if (c == ';') { - this.handleSimple(TokenType.SEMI); + this.handleSimple(TokenType.SEMI, line, column); } else if (c == '(') { - this.handleSimple(TokenType.OPEN_PAREN); + this.handleSimple(TokenType.OPEN_PAREN, line, column); } else if (c == ')') { - this.handleSimple(TokenType.CLOSE_PAREN); + this.handleSimple(TokenType.CLOSE_PAREN, line, column); + } else if (c == '{') { + this.handleSimple(TokenType.OPEN_CURLY, line, column); + } else if (c == '}') { + this.handleSimple(TokenType.CLOSE_CURLY, line, column); } else if (c == '=') { - this.handleSimple(TokenType.EQUALS); + this.handleSimple(TokenType.EQUALS, line, column); } else if (c == '+') { - this.handleSimple(TokenType.PLUS); + this.handleSimple(TokenType.PLUS, line, column); } else if (c == '-') { - this.handleSimple(TokenType.MINUS); + this.handleSimple(TokenType.MINUS, line, column); } else if (c == '*') { - this.handleSimple(TokenType.MULT); + this.handleSimple(TokenType.MULT, line, column); } else if (c == '/') { - this.handleSimple(TokenType.DIV); + this.handleSimple(TokenType.DIV, line, column); } else if (c == '%') { - this.handleSimple(TokenType.MOD); + this.handleSimple(TokenType.MOD, line, column); } else if (c == '^') { - this.handleSimple(TokenType.EXP); + this.handleSimple(TokenType.EXP, line, column); } else { - throw this.error(c); + throw this.error(c, line, column); } } return this.tokens; } - private LexerException error(final char c) { - return new LexerException("Error parsing input: Unexpected character '%c'.".formatted(c), -1, -1); + private LexerException error(final char c, final int line, final int column) { + return new LexerException("Error parsing input: Unexpected character '%c'.".formatted(c), line, column); } - private void handleAlphabeticOrUnderscore() { + private void handleAlphabeticOrUnderscore(final int line, final int column) { final StringBuilder s = new StringBuilder(); while (this.content.peekNext().exists(Predicates.anyOf( Character::isAlphabetic, @@ -70,32 +76,34 @@ public class Lexer { s.append(this.content.next()); } switch (s.toString()) { - case "exit" -> this.appendToken(new Token(TokenType.EXIT)); - case "let" -> this.appendToken(new Token(TokenType.LET)); - case "print" -> this.appendToken(new Token(TokenType.PRINT)); - case final String value -> this.appendToken(new Token(TokenType.IDENTIFIER, Option.of(value))); + case "exit" -> this.appendToken(new Token(TokenType.EXIT, line, column)); + case "let" -> this.appendToken(new Token(TokenType.LET, line, column)); + case "print" -> this.appendToken(new Token(TokenType.PRINT, line, column)); + case "if" -> this.appendToken(new Token(TokenType.IF, line, column)); + case final String value -> + this.appendToken(new Token(TokenType.IDENTIFIER, Option.of(value), line, column)); } } - private void handleDigit() { + private void handleDigit(final int line, final int column) { final StringBuilder s = new StringBuilder(); while (this.content.peekNext().exists(Character::isDigit)) { s.append(this.content.next()); } - this.appendToken(new Token(TokenType.INT_LIT, Option.of(Long.parseLong(s.toString())))); + this.appendToken(new Token(TokenType.INT_LIT, Option.of(Long.parseLong(s.toString())), line, column)); } - private void handleWhitespace() { + private void handleWhitespace(final int line, final int column) { final StringBuilder s = new StringBuilder(); while (this.content.peekNext().exists(Character::isWhitespace)) { s.append(this.content.next()); } - this.appendToken(new Token(TokenType.WHITESPACE, Option.of(s.toString()))); + this.appendToken(new Token(TokenType.WHITESPACE, Option.of(s.toString()), line, column)); } - private void handleSimple(@NotNull final TokenType tokenType) { + private void handleSimple(@NotNull final TokenType tokenType, final int line, final int column) { this.content.next(); - this.appendToken(new Token(tokenType)); + this.appendToken(new Token(tokenType, line, column)); } private void appendToken(@NotNull final Token token) { diff --git a/src/main/java/ch/fritteli/gombaila/domain/lexer/StringWalker.java b/src/main/java/ch/fritteli/gombaila/domain/lexer/StringWalker.java index 4109a6f..fad2054 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/lexer/StringWalker.java +++ b/src/main/java/ch/fritteli/gombaila/domain/lexer/StringWalker.java @@ -4,13 +4,35 @@ import ch.fritteli.gombaila.ElementWalker; import org.jetbrains.annotations.NotNull; public class StringWalker extends ElementWalker { + private int line = 0; + private int column = 0; public StringWalker(@NotNull final String string) { super( string, (back, index) -> index < back.length(), - (back, index) -> index > 0, String::charAt ); } + + public int line() { + return this.line + 1; + } + + public int column() { + return this.column + 1; + } + + @NotNull + @Override + public Character next() { + final char c = super.next(); + if (c == '\n') { + this.line++; + this.column = 0; + } else { + this.column++; + } + return c; + } } diff --git a/src/main/java/ch/fritteli/gombaila/domain/parser/Parser.java b/src/main/java/ch/fritteli/gombaila/domain/parser/Parser.java index db74416..6e4f3c8 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/parser/Parser.java +++ b/src/main/java/ch/fritteli/gombaila/domain/parser/Parser.java @@ -12,9 +12,12 @@ import ch.fritteli.gombaila.domain.common.NodeExprIdent; import ch.fritteli.gombaila.domain.common.NodeExprIntLit; import ch.fritteli.gombaila.domain.common.NodeProg; import ch.fritteli.gombaila.domain.common.NodeStmt; +import ch.fritteli.gombaila.domain.common.NodeStmtAssign; import ch.fritteli.gombaila.domain.common.NodeStmtExit; +import ch.fritteli.gombaila.domain.common.NodeStmtIf; import ch.fritteli.gombaila.domain.common.NodeStmtLet; import ch.fritteli.gombaila.domain.common.NodeStmtPrint; +import ch.fritteli.gombaila.domain.common.NodeStmtScope; import ch.fritteli.gombaila.domain.common.Token; import ch.fritteli.gombaila.domain.common.TokenType; import io.vavr.collection.List; @@ -26,7 +29,6 @@ public class Parser { private final SeqWalker tokens; public Parser(@NotNull final Seq tokens) { - this.tokens = new SeqWalker<>(tokens.reject(token -> TokenType.WHITESPACE.equals(token.type()))); } @@ -49,23 +51,58 @@ public class Parser { this.assertAndConsumeNextTokenType(TokenType.CLOSE_PAREN); result = new NodeStmtExit(nodeExpr); } else if (this.checkNextTokenTypeConsuming(TokenType.LET)) { - final Token identifier = this.assertAndConsumeNextTokenType(TokenType.IDENTIFIER); - this.assertAndConsumeNextTokenType(TokenType.EQUALS); - final NodeExpr nodeExpr = this.parseExpr(1); - result = new NodeStmtLet(identifier, nodeExpr); + final NodeStmtAssign nodeStmtAssign = this.parseStmtAssign(); + result = new NodeStmtLet(nodeStmtAssign); } else if (this.checkNextTokenTypeConsuming(TokenType.PRINT)) { this.assertAndConsumeNextTokenType(TokenType.OPEN_PAREN); final NodeExpr nodeExpr = this.parseExpr(1); this.assertAndConsumeNextTokenType(TokenType.CLOSE_PAREN); result = new NodeStmtPrint(nodeExpr); + } else if (this.checkNextTokenType(TokenType.OPEN_CURLY)) { + // NB: We do NOT expect a SEMI here, so we return directly. + return this.parseScope(); + } else if (this.checkNextTokenTypeConsuming(TokenType.IF)) { + this.assertAndConsumeNextTokenType(TokenType.OPEN_PAREN); + final NodeExpr expr = this.parseExpr(1); + this.assertAndConsumeNextTokenType(TokenType.CLOSE_PAREN); + final NodeStmtScope scope = this.parseScope(); + // NB: We do NOT expect a SEMI here, so we return directly. + return new NodeStmtIf(expr, scope); + } else if (this.checkNextTokenType(TokenType.IDENTIFIER)) { + result = this.parseStmtAssign(); } else { - throw new ParserException("Could not parse statement", null); + throw new ParserException("Could not parse statement", this.tokens.peekNext().getOrNull()); } this.assertAndConsumeNextTokenType(TokenType.SEMI); return result; } + @NotNull + private NodeStmtAssign parseStmtAssign() { + final Token identifier = this.assertAndConsumeNextTokenType(TokenType.IDENTIFIER); + this.assertAndConsumeNextTokenType(TokenType.EQUALS); + final NodeExpr nodeExpr = this.parseExpr(1); + return new NodeStmtAssign(identifier, nodeExpr); + } + + @NotNull + private NodeStmtScope parseScope() { + this.assertAndConsumeNextTokenType(TokenType.OPEN_CURLY); + final Seq nodeStmts = this.parseStatements(); + this.assertAndConsumeNextTokenType(TokenType.CLOSE_CURLY); + return new NodeStmtScope(nodeStmts); + } + + @NotNull + private Seq parseStatements() { + Seq result = List.empty(); + while (!this.checkNextTokenType(TokenType.CLOSE_CURLY)) { + result = result.append(this.parseStmt()); + } + return result; + } + @NotNull private NodeExpr parseExpr(final int minPrecedence) { NodeExpr result; diff --git a/src/main/java/ch/fritteli/gombaila/domain/printer/RawLine.java b/src/main/java/ch/fritteli/gombaila/domain/printer/RawLine.java index dbabe3e..26cdfb4 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/printer/RawLine.java +++ b/src/main/java/ch/fritteli/gombaila/domain/printer/RawLine.java @@ -2,7 +2,7 @@ package ch.fritteli.gombaila.domain.printer; import org.jetbrains.annotations.NotNull; -record RawLine(@NotNull String line, int indentationLevel, @NotNull WidthMap widthMap) implements Line{ +record RawLine(@NotNull String line, int indentationLevel, @NotNull WidthMap widthMap) implements Line { @Override public void updateWithMap() { // nop diff --git a/src/main/resources/gombaila/simple.gb b/src/main/resources/gombaila/simple.gb index 1cb4c84..a1dc50b 100644 --- a/src/main/resources/gombaila/simple.gb +++ b/src/main/resources/gombaila/simple.gb @@ -1,4 +1,5 @@ -let foo = 10; -let bar = 2; -let baz = foo / bar; -exit(baz); +let x = (10 - 2 * 3) / 2; +if (x-2) { + exit (69); +} +exit(1);