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.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) {
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue