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
|
@NotNull
|
||||||
private final BiPredicate<T, Integer> hasNextPredicate;
|
private final BiPredicate<T, Integer> hasNextPredicate;
|
||||||
@NotNull
|
@NotNull
|
||||||
private final BiPredicate<T, Integer> hasPrevPredicate;
|
|
||||||
@NotNull
|
|
||||||
private final BiFunction<T, Integer, E> atFunction;
|
private final BiFunction<T, Integer, E> atFunction;
|
||||||
private int index = 0;
|
private int index = 0;
|
||||||
|
|
||||||
public ElementWalker(@NotNull final T back,
|
public ElementWalker(@NotNull final T back,
|
||||||
@NotNull final BiPredicate<T, Integer> hasNextPredicate,
|
@NotNull final BiPredicate<T, Integer> hasNextPredicate,
|
||||||
@NotNull final BiPredicate<T, Integer> hasPrevPredicate,
|
|
||||||
@NotNull final BiFunction<T, Integer, E> atFunction) {
|
@NotNull final BiFunction<T, Integer, E> atFunction) {
|
||||||
this.back = back;
|
this.back = back;
|
||||||
this.hasNextPredicate = hasNextPredicate;
|
this.hasNextPredicate = hasNextPredicate;
|
||||||
this.hasPrevPredicate = hasPrevPredicate;
|
|
||||||
this.atFunction = atFunction;
|
this.atFunction = atFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,18 +36,4 @@ public class ElementWalker<T, E> {
|
||||||
public Option<E> peekNext() {
|
public Option<E> peekNext() {
|
||||||
return Option.when(this.hasNext(), () -> this.atFunction.apply(this.back, this.index));
|
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(
|
super(
|
||||||
seq,
|
seq,
|
||||||
(back, index) -> index < back.length(),
|
(back, index) -> index < back.length(),
|
||||||
(back, index) -> index > 0,
|
|
||||||
Seq::get
|
Seq::get
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ch.fritteli.gombaila.domain.common;
|
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;
|
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 io.vavr.control.Option;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public record Token(@NotNull TokenType type, @NotNull Option<Object> value) {
|
public record Token(@NotNull TokenType type, @NotNull Option<Object> value, int line, int column) {
|
||||||
public Token(@NotNull final TokenType type) {
|
public Token(@NotNull final TokenType type, final int line, final int column) {
|
||||||
this(type, Option.none());
|
this(type, Option.none(), line, column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ public enum TokenType {
|
||||||
SEMI,
|
SEMI,
|
||||||
OPEN_PAREN,
|
OPEN_PAREN,
|
||||||
CLOSE_PAREN,
|
CLOSE_PAREN,
|
||||||
|
OPEN_CURLY,
|
||||||
|
CLOSE_CURLY,
|
||||||
EQUALS,
|
EQUALS,
|
||||||
PLUS,
|
PLUS,
|
||||||
MINUS,
|
MINUS,
|
||||||
|
@ -23,7 +25,8 @@ public enum TokenType {
|
||||||
INT_LIT,
|
INT_LIT,
|
||||||
IDENTIFIER,
|
IDENTIFIER,
|
||||||
// the rest
|
// the rest
|
||||||
WHITESPACE;
|
WHITESPACE,
|
||||||
|
IF;
|
||||||
|
|
||||||
public boolean isBinaryOperator() {
|
public boolean isBinaryOperator() {
|
||||||
return switch (this) {
|
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;
|
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.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.NodeProg;
|
||||||
import ch.fritteli.gombaila.domain.common.NodeStmt;
|
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 ch.fritteli.gombaila.domain.printer.Printer;
|
||||||
|
import io.vavr.collection.List;
|
||||||
|
import io.vavr.collection.Seq;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.HashMap;
|
import java.util.Stack;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class Generator {
|
public class Generator {
|
||||||
@NotNull
|
@NotNull
|
||||||
private final Printer printer = new Printer();
|
final Printer printer = new Printer();
|
||||||
|
@NotNull
|
||||||
|
final Stack<Integer> scopes = new Stack<>();
|
||||||
@NotNull
|
@NotNull
|
||||||
private final NodeProg nodeProg;
|
private final NodeProg nodeProg;
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -31,13 +23,13 @@ public class Generator {
|
||||||
@NotNull
|
@NotNull
|
||||||
private final StmtVisitor stmtVisitor;
|
private final StmtVisitor stmtVisitor;
|
||||||
@NotNull
|
@NotNull
|
||||||
private final Map<String, Variable> identifierStackposition = new HashMap<>();
|
Seq<Variable> vars = List.empty();
|
||||||
private int stackSize = 0;
|
int stackSize = 0;
|
||||||
|
|
||||||
public Generator(@NotNull final NodeProg nodeProg) {
|
public Generator(@NotNull final NodeProg nodeProg) {
|
||||||
this.nodeProg = nodeProg;
|
this.nodeProg = nodeProg;
|
||||||
this.exprVisitor = new ExprVisitor();
|
this.exprVisitor = new ExprVisitor(this);
|
||||||
this.stmtVisitor = new StmtVisitor();
|
this.stmtVisitor = new StmtVisitor(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -75,149 +67,36 @@ public class Generator {
|
||||||
this.printer.line("syscall");
|
this.printer.line("syscall");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateStmt(@NotNull final NodeStmt stmt) {
|
void generateStmt(@NotNull final NodeStmt stmt) {
|
||||||
this.stmtVisitor.visit(stmt);
|
this.stmtVisitor.visit(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateExpr(@NotNull final NodeExpr expr) {
|
void generateExpr(@NotNull final NodeExpr expr) {
|
||||||
this.exprVisitor.visit(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.printer.commentedLine("push %s onto top of the stack".formatted(reg), "push", reg);
|
||||||
this.stackSize++;
|
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.printer.commentedLine("pop top of the stack into %s".formatted(reg), "pop", reg);
|
||||||
this.stackSize--;
|
this.stackSize--;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Variable(int stackLocation) {
|
void beginScope() {
|
||||||
|
this.scopes.push(this.vars.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class StmtVisitor {
|
void endScope() {
|
||||||
|
final int popCount = this.vars.size() - this.scopes.pop();
|
||||||
private StmtVisitor() {
|
if (popCount > 0) {
|
||||||
}
|
this.printer.commentedLine(
|
||||||
|
"Reset the stack pointer to discard out-of-scope variables",
|
||||||
void visit(@NotNull final NodeStmt stmt) {
|
"add", "rsp, %d".formatted(popCount * 8));
|
||||||
switch (stmt) {
|
this.stackSize -= popCount;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
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;
|
Option<Character> next;
|
||||||
while ((next = this.content.peekNext()).isDefined()) {
|
while ((next = this.content.peekNext()).isDefined()) {
|
||||||
final char c = next.get();
|
final char c = next.get();
|
||||||
|
final int line = this.content.line();
|
||||||
|
final int column = this.content.column();
|
||||||
if (Character.isAlphabetic(c) || c == '_') {
|
if (Character.isAlphabetic(c) || c == '_') {
|
||||||
this.handleAlphabeticOrUnderscore();
|
this.handleAlphabeticOrUnderscore(line, column);
|
||||||
} else if (Character.isDigit(c)) {
|
} else if (Character.isDigit(c)) {
|
||||||
this.handleDigit();
|
this.handleDigit(line, column);
|
||||||
} else if (Character.isWhitespace(c)) {
|
} else if (Character.isWhitespace(c)) {
|
||||||
this.handleWhitespace();
|
this.handleWhitespace(line, column);
|
||||||
} else if (c == ';') {
|
} else if (c == ';') {
|
||||||
this.handleSimple(TokenType.SEMI);
|
this.handleSimple(TokenType.SEMI, line, column);
|
||||||
} else if (c == '(') {
|
} else if (c == '(') {
|
||||||
this.handleSimple(TokenType.OPEN_PAREN);
|
this.handleSimple(TokenType.OPEN_PAREN, line, column);
|
||||||
} else if (c == ')') {
|
} 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 == '=') {
|
} else if (c == '=') {
|
||||||
this.handleSimple(TokenType.EQUALS);
|
this.handleSimple(TokenType.EQUALS, line, column);
|
||||||
} else if (c == '+') {
|
} else if (c == '+') {
|
||||||
this.handleSimple(TokenType.PLUS);
|
this.handleSimple(TokenType.PLUS, line, column);
|
||||||
} else if (c == '-') {
|
} else if (c == '-') {
|
||||||
this.handleSimple(TokenType.MINUS);
|
this.handleSimple(TokenType.MINUS, line, column);
|
||||||
} else if (c == '*') {
|
} else if (c == '*') {
|
||||||
this.handleSimple(TokenType.MULT);
|
this.handleSimple(TokenType.MULT, line, column);
|
||||||
} else if (c == '/') {
|
} else if (c == '/') {
|
||||||
this.handleSimple(TokenType.DIV);
|
this.handleSimple(TokenType.DIV, line, column);
|
||||||
} else if (c == '%') {
|
} else if (c == '%') {
|
||||||
this.handleSimple(TokenType.MOD);
|
this.handleSimple(TokenType.MOD, line, column);
|
||||||
} else if (c == '^') {
|
} else if (c == '^') {
|
||||||
this.handleSimple(TokenType.EXP);
|
this.handleSimple(TokenType.EXP, line, column);
|
||||||
} else {
|
} else {
|
||||||
throw this.error(c);
|
throw this.error(c, line, column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.tokens;
|
return this.tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LexerException error(final char c) {
|
private LexerException error(final char c, final int line, final int column) {
|
||||||
return new LexerException("Error parsing input: Unexpected character '%c'.".formatted(c), -1, -1);
|
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();
|
final StringBuilder s = new StringBuilder();
|
||||||
while (this.content.peekNext().exists(Predicates.<Character>anyOf(
|
while (this.content.peekNext().exists(Predicates.<Character>anyOf(
|
||||||
Character::isAlphabetic,
|
Character::isAlphabetic,
|
||||||
|
@ -70,32 +76,34 @@ public class Lexer {
|
||||||
s.append(this.content.next());
|
s.append(this.content.next());
|
||||||
}
|
}
|
||||||
switch (s.toString()) {
|
switch (s.toString()) {
|
||||||
case "exit" -> this.appendToken(new Token(TokenType.EXIT));
|
case "exit" -> this.appendToken(new Token(TokenType.EXIT, line, column));
|
||||||
case "let" -> this.appendToken(new Token(TokenType.LET));
|
case "let" -> this.appendToken(new Token(TokenType.LET, line, column));
|
||||||
case "print" -> this.appendToken(new Token(TokenType.PRINT));
|
case "print" -> this.appendToken(new Token(TokenType.PRINT, line, column));
|
||||||
case final String value -> this.appendToken(new Token(TokenType.IDENTIFIER, Option.of(value)));
|
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();
|
final StringBuilder s = new StringBuilder();
|
||||||
while (this.content.peekNext().exists(Character::isDigit)) {
|
while (this.content.peekNext().exists(Character::isDigit)) {
|
||||||
s.append(this.content.next());
|
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();
|
final StringBuilder s = new StringBuilder();
|
||||||
while (this.content.peekNext().exists(Character::isWhitespace)) {
|
while (this.content.peekNext().exists(Character::isWhitespace)) {
|
||||||
s.append(this.content.next());
|
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.content.next();
|
||||||
this.appendToken(new Token(tokenType));
|
this.appendToken(new Token(tokenType, line, column));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendToken(@NotNull final Token token) {
|
private void appendToken(@NotNull final Token token) {
|
||||||
|
|
|
@ -4,13 +4,35 @@ import ch.fritteli.gombaila.ElementWalker;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class StringWalker extends ElementWalker<String, Character> {
|
public class StringWalker extends ElementWalker<String, Character> {
|
||||||
|
private int line = 0;
|
||||||
|
private int column = 0;
|
||||||
|
|
||||||
public StringWalker(@NotNull final String string) {
|
public StringWalker(@NotNull final String string) {
|
||||||
super(
|
super(
|
||||||
string,
|
string,
|
||||||
(back, index) -> index < back.length(),
|
(back, index) -> index < back.length(),
|
||||||
(back, index) -> index > 0,
|
|
||||||
String::charAt
|
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.NodeExprIntLit;
|
||||||
import ch.fritteli.gombaila.domain.common.NodeProg;
|
import ch.fritteli.gombaila.domain.common.NodeProg;
|
||||||
import ch.fritteli.gombaila.domain.common.NodeStmt;
|
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.NodeStmtExit;
|
||||||
|
import ch.fritteli.gombaila.domain.common.NodeStmtIf;
|
||||||
import ch.fritteli.gombaila.domain.common.NodeStmtLet;
|
import ch.fritteli.gombaila.domain.common.NodeStmtLet;
|
||||||
import ch.fritteli.gombaila.domain.common.NodeStmtPrint;
|
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.Token;
|
||||||
import ch.fritteli.gombaila.domain.common.TokenType;
|
import ch.fritteli.gombaila.domain.common.TokenType;
|
||||||
import io.vavr.collection.List;
|
import io.vavr.collection.List;
|
||||||
|
@ -26,7 +29,6 @@ public class Parser {
|
||||||
private final SeqWalker<Token> tokens;
|
private final SeqWalker<Token> tokens;
|
||||||
|
|
||||||
public Parser(@NotNull final Seq<Token> tokens) {
|
public Parser(@NotNull final Seq<Token> tokens) {
|
||||||
|
|
||||||
this.tokens = new SeqWalker<>(tokens.reject(token -> TokenType.WHITESPACE.equals(token.type())));
|
this.tokens = new SeqWalker<>(tokens.reject(token -> TokenType.WHITESPACE.equals(token.type())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,23 +51,58 @@ public class Parser {
|
||||||
this.assertAndConsumeNextTokenType(TokenType.CLOSE_PAREN);
|
this.assertAndConsumeNextTokenType(TokenType.CLOSE_PAREN);
|
||||||
result = new NodeStmtExit(nodeExpr);
|
result = new NodeStmtExit(nodeExpr);
|
||||||
} else if (this.checkNextTokenTypeConsuming(TokenType.LET)) {
|
} else if (this.checkNextTokenTypeConsuming(TokenType.LET)) {
|
||||||
final Token identifier = this.assertAndConsumeNextTokenType(TokenType.IDENTIFIER);
|
final NodeStmtAssign nodeStmtAssign = this.parseStmtAssign();
|
||||||
this.assertAndConsumeNextTokenType(TokenType.EQUALS);
|
result = new NodeStmtLet(nodeStmtAssign);
|
||||||
final NodeExpr nodeExpr = this.parseExpr(1);
|
|
||||||
result = new NodeStmtLet(identifier, nodeExpr);
|
|
||||||
} else if (this.checkNextTokenTypeConsuming(TokenType.PRINT)) {
|
} else if (this.checkNextTokenTypeConsuming(TokenType.PRINT)) {
|
||||||
this.assertAndConsumeNextTokenType(TokenType.OPEN_PAREN);
|
this.assertAndConsumeNextTokenType(TokenType.OPEN_PAREN);
|
||||||
final NodeExpr nodeExpr = this.parseExpr(1);
|
final NodeExpr nodeExpr = this.parseExpr(1);
|
||||||
this.assertAndConsumeNextTokenType(TokenType.CLOSE_PAREN);
|
this.assertAndConsumeNextTokenType(TokenType.CLOSE_PAREN);
|
||||||
result = new NodeStmtPrint(nodeExpr);
|
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 {
|
} else {
|
||||||
throw new ParserException("Could not parse statement", null);
|
throw new ParserException("Could not parse statement", this.tokens.peekNext().getOrNull());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.assertAndConsumeNextTokenType(TokenType.SEMI);
|
this.assertAndConsumeNextTokenType(TokenType.SEMI);
|
||||||
return result;
|
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
|
@NotNull
|
||||||
private NodeExpr parseExpr(final int minPrecedence) {
|
private NodeExpr parseExpr(final int minPrecedence) {
|
||||||
NodeExpr result;
|
NodeExpr result;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
let foo = 10;
|
let x = (10 - 2 * 3) / 2;
|
||||||
let bar = 2;
|
if (x-2) {
|
||||||
let baz = foo / bar;
|
exit (69);
|
||||||
exit(baz);
|
}
|
||||||
|
exit(1);
|
||||||
|
|
Loading…
Reference in a new issue