From 19c9409ccd814fd58994b74004aad1ca33dd0a62 Mon Sep 17 00:00:00 2001
From: Manuel Friedli <manuel@fritteli.ch>
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<T, E> {
     @NotNull
     private final BiPredicate<T, Integer> hasNextPredicate;
     @NotNull
-    private final BiPredicate<T, Integer> hasPrevPredicate;
-    @NotNull
     private final BiFunction<T, Integer, E> atFunction;
     private int index = 0;
 
     public ElementWalker(@NotNull final T back,
                          @NotNull final BiPredicate<T, Integer> hasNextPredicate,
-                         @NotNull final BiPredicate<T, Integer> hasPrevPredicate,
                          @NotNull final BiFunction<T, Integer, E> atFunction) {
         this.back = back;
         this.hasNextPredicate = hasNextPredicate;
-        this.hasPrevPredicate = hasPrevPredicate;
         this.atFunction = atFunction;
     }
 
@@ -40,18 +36,4 @@ public class ElementWalker<T, E> {
     public Option<E> 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<E> 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<E> extends ElementWalker<Seq<E>, 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<NodeStmt> 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<Object> value) {
-    public Token(@NotNull final TokenType type) {
-        this(type, Option.none());
+public record Token(@NotNull TokenType type, @NotNull Option<Object> 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> 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<Integer> 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<String, Variable> identifierStackposition = new HashMap<>();
-    private int stackSize = 0;
+    Seq<Variable> 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> 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<Character> 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.<Character>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<String, Character> {
+    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<Token> tokens;
 
     public Parser(@NotNull final Seq<Token> 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<NodeStmt> nodeStmts = this.parseStatements();
+        this.assertAndConsumeNextTokenType(TokenType.CLOSE_CURLY);
+        return new NodeStmtScope(nodeStmts);
+    }
+
+    @NotNull
+    private Seq<NodeStmt> parseStatements() {
+        Seq<NodeStmt> 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);