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