This commit is contained in:
Manuel Friedli 2024-03-24 04:15:19 +01:00
parent 4adfed238e
commit 19c9409ccd
Signed by: manuel
GPG key ID: 41D08ABA75634DA1
18 changed files with 375 additions and 209 deletions

View file

@ -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));
}
} }

View file

@ -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
); );
} }

View file

@ -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 {
} }

View file

@ -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 {
}

View file

@ -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 {
}

View file

@ -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 {
} }

View file

@ -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 {
}

View file

@ -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);
} }
} }

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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);
} }
} }

View file

@ -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++);
}
}

View file

@ -0,0 +1,6 @@
package ch.fritteli.gombaila.domain.generator;
import org.jetbrains.annotations.NotNull;
record Variable(@NotNull String name, int stackLocation) {
}

View file

@ -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) {

View file

@ -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;
}
} }

View file

@ -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;

View file

@ -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);