Pretty print!

This commit is contained in:
Manuel Friedli 2024-03-24 02:09:04 +01:00
parent 29a3e4c0fd
commit 4adfed238e
Signed by: manuel
GPG key ID: 41D08ABA75634DA1
10 changed files with 295 additions and 57 deletions

View file

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

View file

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

View file

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

View file

@ -0,0 +1,5 @@
package ch.fritteli.gombaila.domain.printer;
interface Line extends Printable {
int indentationLevel();
}

View file

@ -0,0 +1,10 @@
package ch.fritteli.gombaila.domain.printer;
import org.jetbrains.annotations.NotNull;
public interface Printable {
void updateWithMap();
@NotNull
String toString();
}

View file

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

View file

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

View file

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

View file

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

View file

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