Pretty print!
This commit is contained in:
parent
29a3e4c0fd
commit
4adfed238e
10 changed files with 295 additions and 57 deletions
|
@ -14,29 +14,19 @@ import ch.fritteli.gombaila.domain.common.NodeStmt;
|
||||||
import ch.fritteli.gombaila.domain.common.NodeStmtExit;
|
import ch.fritteli.gombaila.domain.common.NodeStmtExit;
|
||||||
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.printer.Printer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class Generator {
|
public class Generator {
|
||||||
@NotNull
|
@NotNull
|
||||||
private static final String header = """
|
private final Printer printer = new Printer();
|
||||||
global _start
|
|
||||||
_start:
|
|
||||||
""";
|
|
||||||
@NotNull
|
|
||||||
private static final String defaultExitStmt = """
|
|
||||||
mov rax, 60
|
|
||||||
mov rdi, 0
|
|
||||||
syscall
|
|
||||||
""";
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private final NodeProg nodeProg;
|
private final NodeProg nodeProg;
|
||||||
@NotNull
|
@NotNull
|
||||||
private final StringBuilder asm;
|
|
||||||
@NotNull
|
|
||||||
private final ExprVisitor exprVisitor;
|
private final ExprVisitor exprVisitor;
|
||||||
@NotNull
|
@NotNull
|
||||||
private final StmtVisitor stmtVisitor;
|
private final StmtVisitor stmtVisitor;
|
||||||
|
@ -46,18 +36,43 @@ public class Generator {
|
||||||
|
|
||||||
public Generator(@NotNull final NodeProg nodeProg) {
|
public Generator(@NotNull final NodeProg nodeProg) {
|
||||||
this.nodeProg = nodeProg;
|
this.nodeProg = nodeProg;
|
||||||
this.asm = new StringBuilder(header);
|
|
||||||
this.exprVisitor = new ExprVisitor();
|
this.exprVisitor = new ExprVisitor();
|
||||||
this.stmtVisitor = new StmtVisitor();
|
this.stmtVisitor = new StmtVisitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public String generate() {
|
public String generate() {
|
||||||
|
this.generateHeader();
|
||||||
|
|
||||||
for (final NodeStmt stmt : this.nodeProg.stmts()) {
|
for (final NodeStmt stmt : this.nodeProg.stmts()) {
|
||||||
this.generateStmt(stmt);
|
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) {
|
private void generateStmt(@NotNull final NodeStmt stmt) {
|
||||||
|
@ -69,27 +84,15 @@ public class Generator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void push(@NotNull final String reg) {
|
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++;
|
this.stackSize++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pop(@NotNull final String reg) {
|
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--;
|
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) {
|
private record Variable(int stackLocation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,31 +111,33 @@ public class Generator {
|
||||||
|
|
||||||
private void visit(@NotNull final NodeStmtExit stmt) {
|
private void visit(@NotNull final NodeStmtExit stmt) {
|
||||||
generateExpr(stmt.expr());
|
generateExpr(stmt.expr());
|
||||||
line("mov rax, 60");
|
|
||||||
pop("rdi");
|
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) {
|
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);
|
throw new GeneratorException("Identifier already used", stmt);
|
||||||
}
|
}
|
||||||
identifierStackposition.put(((String) stmt.ident().value().get()), new Variable(stackSize));
|
|
||||||
generateExpr(stmt.expr());
|
generateExpr(stmt.expr());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void visit(@NotNull final NodeStmtPrint stmt) {
|
private void visit(@NotNull final NodeStmtPrint stmt) {
|
||||||
generateExpr(stmt.nodeExpr());
|
// generateExpr(stmt.nodeExpr());
|
||||||
// rdx: length of output
|
// // rdx: length of output
|
||||||
line("mov rdx, 8");
|
// line("mov rdx, 8");
|
||||||
// rsi: output
|
// // rsi: output
|
||||||
pop("rsi");
|
// pop("rsi");
|
||||||
// rdi: fd-value (STDOUT=1)
|
// // rdi: fd-value (STDOUT=1)
|
||||||
line("mov rdi, 1");
|
// line("mov rdi, 1");
|
||||||
// rax: syscall (SYS_WRITE=1)
|
// // rax: syscall (SYS_WRITE=1)
|
||||||
line("mov rax, 1");
|
// line("mov rax, 1");
|
||||||
// syscall
|
// // syscall
|
||||||
line("syscall");
|
// line("syscall");
|
||||||
|
throw new GeneratorException("Not implemented yet", stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,11 +163,11 @@ public class Generator {
|
||||||
if (variable == null) {
|
if (variable == null) {
|
||||||
throw new GeneratorException("Undeclared identifier", expr);
|
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) {
|
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");
|
push("rax");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +176,7 @@ public class Generator {
|
||||||
generateExpr(expr.rhs());
|
generateExpr(expr.rhs());
|
||||||
pop("rbx");
|
pop("rbx");
|
||||||
pop("rax");
|
pop("rax");
|
||||||
line("add rax, rbx");
|
printer.commentedLine("add rbx to rax", "add", "rax, rbx");
|
||||||
push("rax");
|
push("rax");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,20 +185,35 @@ public class Generator {
|
||||||
generateExpr(expr.rhs());
|
generateExpr(expr.rhs());
|
||||||
pop("rbx");
|
pop("rbx");
|
||||||
pop("rax");
|
pop("rax");
|
||||||
line("sub rax, rbx");
|
printer.commentedLine("subtract rbx from rax", "sub", "rax, rbx");
|
||||||
push("rax");
|
push("rax");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void visit(@NotNull final NodeBinExprMult expr) {
|
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) {
|
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) {
|
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) {
|
private void visit(@NotNull final NodeBinExprExp expr) {
|
||||||
|
|
|
@ -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 "";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String> 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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ch.fritteli.gombaila.domain.printer;
|
||||||
|
|
||||||
|
interface Line extends Printable {
|
||||||
|
int indentationLevel();
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ch.fritteli.gombaila.domain.printer;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface Printable {
|
||||||
|
void updateWithMap();
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
String toString();
|
||||||
|
}
|
|
@ -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<Printable> 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<String> 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<String> 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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Integer, Integer> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,12 @@ let ident = [Expr];
|
||||||
int_lit
|
int_lit
|
||||||
ident
|
ident
|
||||||
[BinExpr]
|
[BinExpr]
|
||||||
|
([Expr])
|
||||||
|
|
||||||
[BinExpr] ->
|
[BinExpr] ->
|
||||||
[Expr] * [Expr]; prec=1
|
[Expr] ^ [Expr]; prec=3 assoc=right
|
||||||
[Expr] + [Expr]; prec=0
|
[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
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
let x = 1 + 2;
|
let foo = 10;
|
||||||
let y = 8 + 2 * x ^ (2 - 1) ^ 2 - 1 * 0;
|
let bar = 2;
|
||||||
exit(y + x % 2);
|
let baz = foo / bar;
|
||||||
|
exit(baz);
|
||||||
|
|
Loading…
Reference in a new issue