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…
	
	Add table
		Add a link
		
	
		Reference in a new issue