IF!!!
This commit is contained in:
		
							parent
							
								
									4adfed238e
								
							
						
					
					
						commit
						19c9409ccd
					
				
					 18 changed files with 375 additions and 209 deletions
				
			
		|  | @ -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)); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
|  | @ -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 { | ||||
| } | ||||
|  |  | |||
|  | @ -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 { | ||||
| 
 | ||||
| } | ||||
|  | @ -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 { | ||||
| 
 | ||||
| } | ||||
|  | @ -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 { | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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 { | ||||
| 
 | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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++); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,6 @@ | |||
| package ch.fritteli.gombaila.domain.generator; | ||||
| 
 | ||||
| import org.jetbrains.annotations.NotNull; | ||||
| 
 | ||||
| record Variable(@NotNull String name, int stackLocation) { | ||||
| } | ||||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue