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

View File

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

View File

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

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

View File

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

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

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

View File

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

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

View File

@ -2,7 +2,7 @@ package ch.fritteli.gombaila.domain.printer;
import org.jetbrains.annotations.NotNull;
record RawLine(@NotNull String line, int indentationLevel, @NotNull WidthMap widthMap) implements Line{
record RawLine(@NotNull String line, int indentationLevel, @NotNull WidthMap widthMap) implements Line {
@Override
public void updateWithMap() {
// nop

View File

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