diff --git a/src/main/java/ch/fritteli/gombaila/domain/generator/Generator.java b/src/main/java/ch/fritteli/gombaila/domain/generator/Generator.java index a9a2657..9c62799 100644 --- a/src/main/java/ch/fritteli/gombaila/domain/generator/Generator.java +++ b/src/main/java/ch/fritteli/gombaila/domain/generator/Generator.java @@ -14,29 +14,19 @@ 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 org.jetbrains.annotations.NotNull; +import java.time.ZonedDateTime; import java.util.HashMap; import java.util.Map; public class Generator { @NotNull - private static final String header = """ - global _start - _start: - """; - @NotNull - private static final String defaultExitStmt = """ - mov rax, 60 - mov rdi, 0 - syscall - """; - + private final Printer printer = new Printer(); @NotNull private final NodeProg nodeProg; @NotNull - private final StringBuilder asm; - @NotNull private final ExprVisitor exprVisitor; @NotNull private final StmtVisitor stmtVisitor; @@ -46,18 +36,43 @@ public class Generator { public Generator(@NotNull final NodeProg nodeProg) { this.nodeProg = nodeProg; - this.asm = new StringBuilder(header); this.exprVisitor = new ExprVisitor(); this.stmtVisitor = new StmtVisitor(); } @NotNull public String generate() { + this.generateHeader(); + for (final NodeStmt stmt : this.nodeProg.stmts()) { this.generateStmt(stmt); } - this.asm.append(defaultExitStmt); - return this.asm.toString(); + + this.generateFooter(); + + return this.printer.toString(); + } + + private void generateHeader() { + this.printer.comment("Program generated by Gombaila on %1$tF, %1$tT".formatted(ZonedDateTime.now())); + this.printer.section(".data"); + this.printer.line("EXIT_SUCCESS", "equ", 0); + this.printer.line("SYSCALL_exit", "equ", 60); + this.printer.blank(); + this.printer.section(".text"); + this.printer.rawLine("global _start"); + this.printer.blank(); + this.printer.label("_start"); + + } + + private void generateFooter() { + this.printer.commentedLine("Prepare for successful exit", "mov", "rdi, EXIT_SUCCESS"); + this.printer.commentedLine("Jump to the default exit routine", "jmp", "exit"); + this.printer.blank(); + this.printer.label("exit"); + this.printer.commentedLine("Prepare for the EXIT syscall", "mov", "rax, SYSCALL_exit"); + this.printer.line("syscall"); } private void generateStmt(@NotNull final NodeStmt stmt) { @@ -69,27 +84,15 @@ public class Generator { } private void push(@NotNull final String reg) { - this.line("push %s", reg); + this.printer.commentedLine("push %s onto top of the stack".formatted(reg), "push", reg); this.stackSize++; } private void pop(@NotNull final String reg) { - this.line("pop %s", reg); + this.printer.commentedLine("pop top of the stack into %s".formatted(reg), "pop", reg); this.stackSize--; } - private void label(@NotNull final String name) { - this.asm.append(name) - .append(":") - .append("\n"); - } - - private void line(@NotNull final String line, @NotNull final Object... params) { - this.asm.append(" ") - .append(line.formatted(params)) - .append("\n"); - } - private record Variable(int stackLocation) { } @@ -108,31 +111,33 @@ public class Generator { private void visit(@NotNull final NodeStmtExit stmt) { generateExpr(stmt.expr()); - line("mov rax, 60"); pop("rdi"); - line("syscall"); + 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) { - if (identifierStackposition.containsKey(stmt.ident().value().get())) { + final Variable previousValue = identifierStackposition.put(((String) stmt.ident().value().get()), new Variable(stackSize)); + if (previousValue != null) { throw new GeneratorException("Identifier already used", stmt); } - identifierStackposition.put(((String) stmt.ident().value().get()), new Variable(stackSize)); 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"); +// 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); } } @@ -158,11 +163,11 @@ public class Generator { if (variable == null) { throw new GeneratorException("Undeclared identifier", expr); } - push("QWORD [rsp+%d]".formatted(8 * (stackSize - variable.stackLocation - 1))); + push("qword [rsp+%d]".formatted(8 * (stackSize - variable.stackLocation - 1))); } private void visit(@NotNull final NodeExprIntLit expr) { - line("mov rax, %s", expr.value().value().get()); + printer.commentedLine("store the value in rax", "mov", "rax, %s".formatted(expr.value().value().get())); push("rax"); } @@ -171,7 +176,7 @@ public class Generator { generateExpr(expr.rhs()); pop("rbx"); pop("rax"); - line("add rax, rbx"); + printer.commentedLine("add rbx to rax", "add", "rax, rbx"); push("rax"); } @@ -180,20 +185,35 @@ public class Generator { generateExpr(expr.rhs()); pop("rbx"); pop("rax"); - line("sub rax, rbx"); + printer.commentedLine("subtract rbx from rax", "sub", "rax, rbx"); push("rax"); } private void visit(@NotNull final NodeBinExprMult expr) { - throw new GeneratorException("Not yet implemented!", 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) { - throw new GeneratorException("Not yet implemented!", 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) { - throw new GeneratorException("Not yet implemented!", 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) { diff --git a/src/main/java/ch/fritteli/gombaila/domain/printer/BlankLine.java b/src/main/java/ch/fritteli/gombaila/domain/printer/BlankLine.java new file mode 100644 index 0000000..3c3a1fa --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/printer/BlankLine.java @@ -0,0 +1,21 @@ +package ch.fritteli.gombaila.domain.printer; + +import org.jetbrains.annotations.NotNull; + +record BlankLine() implements Line { + + @Override + public int indentationLevel() { + return 0; + } + + @Override + public void updateWithMap() { + } + + @NotNull + @Override + public String toString() { + return ""; + } +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/printer/FormattedLine.java b/src/main/java/ch/fritteli/gombaila/domain/printer/FormattedLine.java new file mode 100644 index 0000000..6fd7cd0 --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/printer/FormattedLine.java @@ -0,0 +1,49 @@ +package ch.fritteli.gombaila.domain.printer; + +import io.vavr.collection.Seq; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +record FormattedLine(@NotNull Seq tokens, @Nullable String comment, int indentationLevel, + @NotNull WidthMap widthMap) implements Line { + @Override + public void updateWithMap() { + this.tokens + .forEachWithIndex((token, index) -> { + this.widthMap.updateWidthForTokenIndex(index, token.length()); + }); + } + + @NotNull + @Override + public String toString() { + final Seq lineElements; + if (this.comment == null) { + lineElements = this.tokens.dropRight(1) + .zipWithIndex() + .map(tuple -> tuple.map2(this.widthMap::getTokenWidthByIndex)) + .map(tuple -> { + final String token = tuple._1(); + final int width = tuple._2(); + return String.format("%-" + width + "s", token); + }) + .appendAll(this.tokens.lastOption()); + } else { + lineElements = this.tokens.zipWithIndex() + .map(tuple -> tuple.map2(this.widthMap::getTokenWidthByIndex)) + .map(tuple -> { + final String token = tuple._1(); + final int width = tuple._2(); + return String.format("%-" + width + "s", token); + }) + .append(";") + .append(this.comment); + } + final String line = lineElements.mkString(" "); + final int indents = this.widthMap.getIndentationSize() * this.indentationLevel; + if (indents == 0) { + return line; + } + return ("%-" + indents + "s").formatted("") + line; + } +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/printer/Line.java b/src/main/java/ch/fritteli/gombaila/domain/printer/Line.java new file mode 100644 index 0000000..7b62c74 --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/printer/Line.java @@ -0,0 +1,5 @@ +package ch.fritteli.gombaila.domain.printer; + +interface Line extends Printable { + int indentationLevel(); +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/printer/Printable.java b/src/main/java/ch/fritteli/gombaila/domain/printer/Printable.java new file mode 100644 index 0000000..f54f3b7 --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/printer/Printable.java @@ -0,0 +1,10 @@ +package ch.fritteli.gombaila.domain.printer; + +import org.jetbrains.annotations.NotNull; + +public interface Printable { + void updateWithMap(); + + @NotNull + String toString(); +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/printer/Printer.java b/src/main/java/ch/fritteli/gombaila/domain/printer/Printer.java new file mode 100644 index 0000000..77d9c7d --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/printer/Printer.java @@ -0,0 +1,69 @@ +package ch.fritteli.gombaila.domain.printer; + +import io.vavr.collection.List; +import lombok.NonNull; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; + +public class Printer { + private static final int INDENTATION_SIZE = 4; + private final java.util.List printables = new ArrayList<>(); + private WidthMap widthMap = new WidthMap(INDENTATION_SIZE); + private int indentationLevel = 0; + + public void increaseIndent() { + this.indentationLevel++; + } + + public void decreaseIndent() { + this.indentationLevel--; + } + + public void resetIndent() { + this.indentationLevel = 0; + } + + public void comment(@NotNull final String line) { + this.printables.add(new RawLine("; %s".formatted(line), this.indentationLevel, this.widthMap)); + } + + public void section(@NotNull final String sectionName) { + this.resetIndent(); + this.widthMap = new WidthMap(INDENTATION_SIZE); + this.printables.add(new RawLine("section %s".formatted(sectionName), this.indentationLevel, this.widthMap)); + } + + public void label(@NotNull final String label) { + this.resetIndent(); + this.widthMap = new WidthMap(INDENTATION_SIZE); + this.printables.add(new RawLine("%s:".formatted(label), this.indentationLevel, this.widthMap)); + this.increaseIndent(); + } + + public void blank() { + this.printables.add(new RawLine("", 0, this.widthMap)); + } + + public void line(@NonNull final Object... instructionTokens) { + final List tokens = List.of(instructionTokens) + .map(String::valueOf); + this.printables.add(new FormattedLine(tokens, null, this.indentationLevel, this.widthMap)); + } + + public void commentedLine(@NotNull final String comment, @NotNull final Object... instructionTokens) { + final List tokens = List.of(instructionTokens) + .map(String::valueOf); + this.printables.add(new FormattedLine(tokens, comment, this.indentationLevel, this.widthMap)); + } + + public void rawLine(@NotNull final String line) { + this.printables.add(new RawLine(line, this.indentationLevel, this.widthMap)); + } + + @Override + public String toString() { + this.printables.forEach(Printable::updateWithMap); + return List.ofAll(this.printables).mkString("\n"); + } +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/printer/RawLine.java b/src/main/java/ch/fritteli/gombaila/domain/printer/RawLine.java new file mode 100644 index 0000000..dbabe3e --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/printer/RawLine.java @@ -0,0 +1,20 @@ +package ch.fritteli.gombaila.domain.printer; + +import org.jetbrains.annotations.NotNull; + +record RawLine(@NotNull String line, int indentationLevel, @NotNull WidthMap widthMap) implements Line{ + @Override + public void updateWithMap() { + // nop + } + + @NotNull + @Override + public String toString() { + final int indents = this.widthMap.getIndentationSize() * this.indentationLevel; + if (indents == 0) { + return this.line; + } + return ("%-" + indents + "s%s").formatted("", this.line); + } +} diff --git a/src/main/java/ch/fritteli/gombaila/domain/printer/WidthMap.java b/src/main/java/ch/fritteli/gombaila/domain/printer/WidthMap.java new file mode 100644 index 0000000..6566361 --- /dev/null +++ b/src/main/java/ch/fritteli/gombaila/domain/printer/WidthMap.java @@ -0,0 +1,38 @@ +package ch.fritteli.gombaila.domain.printer; + +import io.vavr.collection.HashMap; +import io.vavr.collection.Map; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +public class WidthMap { + @Getter + private final int indentationSize; + @NotNull + private Map widthByTokenIndex = HashMap.empty(); + + public WidthMap(final int indentationSize) { + this.indentationSize = indentationSize; + } + + int getMaxLineLengthWithoutComment() { + return this.widthByTokenIndex.values() + .sum() + .intValue(); + } + + int getTokenWidthByIndex(final int tokenIndex) { + return this.widthByTokenIndex.get(tokenIndex).getOrElse(0); + } + + void updateWidthForTokenIndex(final int tokenIndex, final int width) { + if (!this.hasBiggerWidthAt(tokenIndex, width)) { + this.widthByTokenIndex = this.widthByTokenIndex.put(tokenIndex, width); + } + } + + private boolean hasBiggerWidthAt(final int tokenIndex, final int width) { + return this.widthByTokenIndex.get(tokenIndex) + .exists(existingWidth -> existingWidth > width); + } +} diff --git a/src/main/resources/docs/readme.md b/src/main/resources/docs/readme.md index 83c0dec..d123183 100644 --- a/src/main/resources/docs/readme.md +++ b/src/main/resources/docs/readme.md @@ -8,7 +8,12 @@ let ident = [Expr]; int_lit ident [BinExpr] +([Expr]) [BinExpr] -> -[Expr] * [Expr]; prec=1 -[Expr] + [Expr]; prec=0 +[Expr] ^ [Expr]; prec=3 assoc=right +[Expr] * [Expr]; prec=2 assoc=left +[Expr] / [Expr]; prec=2 assoc=left +[Expr] % [Expr]; prec=2 assoc=left +[Expr] + [Expr]; prec=1 assoc=left +[Expr] - [Expr]; prec=1 assoc=left diff --git a/src/main/resources/gombaila/simple.gb b/src/main/resources/gombaila/simple.gb index a960348..1cb4c84 100644 --- a/src/main/resources/gombaila/simple.gb +++ b/src/main/resources/gombaila/simple.gb @@ -1,3 +1,4 @@ -let x = 1 + 2; -let y = 8 + 2 * x ^ (2 - 1) ^ 2 - 1 * 0; -exit(y + x % 2); +let foo = 10; +let bar = 2; +let baz = foo / bar; +exit(baz);