Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| creating:evaluation [2025/04/17 15:34] – Split evaluation tutorial into its own page ahelwer | creating:evaluation [2025/06/19 16:31] (current) – LEFT_BRACE interpretation splits out Object value = ahelwer | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ======= | + | ======= |
| - | This tutorial page covers | + | Now that we can parse expressions, |
| - | - [[https:// | + | Here we follow |
| - | - [[https://craftinginterpreters.com/parsing-expressions.html|Chapter 6: Parsing Expressions]] | + | It's an exciting chapter, so let's jump in. |
| - | Same as the book, we //could// build a parser for our entire minimal TLA⁺ language subset before moving on to interpreting it, but that would be boring! | + | ====== Section 7.1: Representing Values ====== |
| - | Instead we focus on a simple vertical slice of the language: expressions. | + | |
| - | And not just any expressions, | + | |
| - | Just primitive literal values stuck together, not too much more advanced than a simple calculator app. | + | |
| - | This will give us a skeleton on which to hang the rest of the language. | + | |
| - | Each section in this tutorial corresponds to one or more sections of //Crafting Interpreters/ | + | In [[https://craftinginterpreters.com/evaluating-expressions.html# |
| - | First read the section of the book, then read the corresponding commentary & modifications given by this tutorial. | + | Our mapping for TLA⁺ is fairly similar to Lox, although we use '' |
| + | TLA⁺ thankfully lacks '' | ||
| - | ====== Chapter 5: Representing Code ====== | + | ^ TLA⁺ type ^ Java representation |
| + | | Any TLA⁺ value | '' | ||
| + | | Boolean | ||
| + | | number | ||
| + | | set | '' | ||
| - | [[https:// | + | Java does a huge amount of work for us with the capabilities of the '' |
| - | However, since this tutorial is intended to be self-contained code-wise, all necessary code is reproduced. | + | |
| - | ===== Section | + | ====== Section |
| - | Everything before | + | [[https:// |
| - | The ambiguous first-draft grammar for Lox expressions can be adapted to TLA⁺: | + | Create a new file '' |
| - | <code eiffel> | + | |
| - | expression | + | |
| - | | unary | + | |
| - | | binary | + | |
| - | | ternary | + | |
| - | | variadic | + | |
| - | | grouping ; | + | |
| - | literal | + | <code java [highlight_lines_extra="1,3,4"]> |
| - | grouping | + | package tla; |
| - | unary → (( " | + | |
| - | binary | + | |
| - | ternary | + | |
| - | variadic | + | |
| - | operator | + | |
| - | </ | + | |
| - | There are a few interesting differences. | + | |
| - | The '' | + | |
| - | The '' | + | |
| - | The operators are changed to the set of operators defined in our TLA⁺ implementation. | + | |
| - | These are all the expressions we can use without introducing the concept of identifiers referring to something else. | + | |
| - | There is also the '' | + | import java.util.Set; |
| - | This one is so named because it's where we'll put operators accepting varying numbers of parameters. | + | import java.util.HashSet; |
| - | It's kind of weird to think of the finite set literal '' | + | |
| - | The only difference between '' | + | |
| - | This is the perspective of a language implementer. | + | |
| - | Later on we will extend the definition of '' | + | |
| - | ===== Section 5.2: Implementing Syntax Trees ===== | + | class Interpreter implements Expr.Visitor< |
| + | } | ||
| + | </ | ||
| - | While some programmers might have an aversion to generating code, the approach taken in the book is actually very convenient - as you will discover if you take some time to prototype & play around with different class representations of the parse tree! | + | If your IDE supports it, get it to automatically add all necessary stub methods to implement |
| - | In [[https:// | + | |
| - | <code java [highlight_lines_extra=" | + | Let's start with defining the value of literals. |
| - | package tool; | + | Our code is completely unchanged from the book; add this '' |
| - | import | + | < |
| - | import java.io.PrintWriter; | + | |
| - | import java.util.Arrays; | + | public |
| - | import java.util.List; | + | |
| - | + | ||
| - | public class GenerateAst { | + | |
| - | public | + | |
| - | if (args.length != 1) { | + | |
| - | System.err.println(" | + | |
| - | System.exit(64); | + | |
| - | | + | |
| - | String outputDir = args[0]; | + | |
| - | defineAst(outputDir, | + | |
| - | " | + | |
| - | " | + | |
| - | " | + | |
| - | " | + | |
| - | " | + | |
| - | " | + | |
| - | )); | + | |
| } | } | ||
| - | } | ||
| </ | </ | ||
| - | In the '' | + | Our code for evaluating grouping (parentheses) is similarly unchanged from the book: |
| - | <code java [highlight_lines_extra=" | + | <code java> |
| - | | + | |
| - | | + | |
| - | throws IOException { | + | |
| - | String path = outputDir + "/" | + | |
| - | PrintWriter writer = new PrintWriter(path, " | + | |
| - | + | ||
| - | writer.println(" | + | |
| - | writer.println(); | + | |
| - | writer.println(" | + | |
| - | writer.println(); | + | |
| - | writer.println(" | + | |
| - | + | ||
| - | + | ||
| - | | + | |
| - | for (String type : types) { | + | |
| - | String className = type.split(":" | + | |
| - | String fields = type.split(":" | + | |
| - | defineType(writer, | + | |
| - | } | + | |
| - | writer.println(" | + | |
| - | writer.close(); | + | |
| } | } | ||
| </ | </ | ||
| - | Finally, | + | This uses the '' |
| <code java> | <code java> | ||
| - | private | + | private |
| - | PrintWriter writer, String baseName, | + | |
| - | String className, String fieldList) { | + | |
| - | | + | |
| - | baseName + " {"); | + | |
| - | + | ||
| - | // Constructor. | + | |
| - | writer.println(" | + | |
| - | + | ||
| - | // Store parameters in fields. | + | |
| - | String[] fields = fieldList.split(", | + | |
| - | for (String field : fields) { | + | |
| - | String name = field.split(" | + | |
| - | writer.println(" | + | |
| - | } | + | |
| - | + | ||
| - | writer.println(" | + | |
| - | + | ||
| - | // Fields. | + | |
| - | writer.println(); | + | |
| - | for (String field : fields) { | + | |
| - | writer.println(" | + | |
| - | } | + | |
| - | + | ||
| - | writer.println(" | + | |
| } | } | ||
| </ | </ | ||
| + | ==== Evaluating Unary Operators ==== | ||
| - | ===== Section 5.3: Working with Trees ===== | + | Our first real difference from the book is in the '' |
| + | Recall that since our '' | ||
| + | We also don't pre-emptively evaluate the inner expression at the top of the method; this is important for when we later fully implement the prime and enabled operators. | ||
| + | The method is structured as a switch statement handling all possible unary operators: | ||
| + | <code java> | ||
| + | @Override | ||
| + | public Object visitUnaryExpr(Expr.Unary expr) { | ||
| + | switch (expr.operator.type) { | ||
| + | case PRIME: { | ||
| - | [[https:// | + | } case ENABLED: { |
| - | No TLA⁺-specific differences are necessary when modifying '' | + | |
| - | Insert this line in '' | + | |
| - | <code java [highlight_lines_extra=" | + | |
| - | writer.println(" | + | |
| - | defineVisitor(writer, | + | } case NOT: { |
| - | // The AST classes. | + | } case MINUS: { |
| - | </ | + | |
| - | This calls the '' | + | } default: { |
| - | <code java> | + | // Unreachable. |
| - | private static void defineVisitor( | + | |
| - | PrintWriter writer, String baseName, List< | + | |
| - | | + | |
| - | + | ||
| - | for (String type : types) { | + | |
| - | String typeName = type.split(":" | + | |
| - | | + | |
| - | typeName + " " + baseName.toLowerCase() + " | + | |
| } | } | ||
| - | |||
| - | writer.println(" | ||
| } | } | ||
| </ | </ | ||
| - | Again insert some lines in '' | + | Here's how we define the negative prefix operator; while the book casts the operand to a '' |
| - | <code java [highlight_lines_extra=" | + | <code java [highlight_lines_extra=" |
| - | | + | } case MINUS: { |
| - | | + | |
| - | + | | |
| - | // The base accept() method. | + | } default: { |
| - | writer.println(); | + | |
| - | | + | |
| - | + | ||
| - | writer.println(" | + | |
| </ | </ | ||
| - | Finally, insert some lines in '' | + | Note that the negative prefix operator is not actually |
| - | <code java [highlight_lines_extra=" | + | It is defined in the '' |
| - | writer.println(" | + | However, because our minimal TLA⁺ language subset lacks module importing, for convenience we just define some common arithmetic operators as built-in. |
| - | + | You can find more information about the built-in TLA⁺ operators in chapter 16 of //[[https:// | |
| - | | + | |
| - | writer.println(); | + | |
| - | writer.println(" | + | |
| - | writer.println(" | + | |
| - | writer.println(" | + | |
| - | className + baseName + "(this);" | + | |
| - | writer.println(" | + | |
| - | // Fields. | + | Our logical-not prefix operator is denoted by the '' |
| + | We also have no use for the '' | ||
| + | Add this code to '' | ||
| + | <code java [highlight_lines_extra=" | ||
| + | } case NOT: { | ||
| + | Object operand = evaluate(expr.expr); | ||
| + | return !(boolean)operand; | ||
| + | } case MINUS: { | ||
| </ | </ | ||
| - | Our generated syntax tree node types now support the visitor pattern! | ||
| - | ===== Section 5.4: A (Not Very) Pretty Printer ===== | + | We still have two more unary operators to define: '' |
| - | + | Both are trivial in this domain but will become much more complicated later on. | |
| - | [[https:// | + | For constant expressions, '' |
| - | There are a few TLA⁺-specific modifications, starting of course with the package name: | + | Add the highlighted code to '' |
| - | + | <code java [highlight_lines_extra=" | |
| - | <code java [highlight_lines_extra=" | + | case PRIME: |
| - | package tla; | + | |
| - | + | } case ENABLED: | |
| - | class AstPrinter implements Expr.Visitor< | + | return |
| - | | + | } case NOT: { |
| - | return | + | |
| - | } | + | |
| - | } | + | |
| </ | </ | ||
| - | We also have some modifications and additions to the '' | + | ==== Evaluating Binary Operators ==== |
| - | <code java [highlight_lines_extra=" | + | Unlike Lox, addition in TLA⁺ is only defined between two numbers (at least in its standard '' |
| + | Here's how we define our basic binary arithmetic & comparison operators; add a '' | ||
| + | <code java [highlight_lines_extra=" | ||
| @Override | @Override | ||
| - | public | + | public |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | @Override | + | switch |
| - | public String visitGroupingExpr(Expr.Grouping | + | case MINUS: |
| - | return | + | |
| - | } | + | case PLUS: |
| + | return (int)left + (int)right; | ||
| + | case LESS_THAN: | ||
| + | return (int)left < (int)right; | ||
| + | case EQUAL: | ||
| + | return left.equals(right); | ||
| + | } | ||
| - | @Override | + | // Unreachable. |
| - | public String visitLiteralExpr(Expr.Literal expr) { | + | |
| - | | + | |
| - | return expr.value.toString(); | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public String visitUnaryExpr(Expr.Unary expr) { | + | |
| - | return parenthesize(expr.operator.lexeme, | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public string visitTernaryExpr(Expr.Ternary expr) { | + | |
| - | return parenthesize(expr.operator.lexeme, | + | |
| - | expr.second, | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public string visitVariadicExpr(Expr.Variadic expr) { | + | |
| - | return parenthesize(expr.operator.lexeme, | + | |
| - | expr.parameters.toArray(Expr[]:: | + | |
| } | } | ||
| </ | </ | ||
| - | The '' | + | Note that since we don't have to worry about '' |
| + | However, this actually gives us different equality semantics from TLC. | ||
| + | In TLC, evaluating things like '' | ||
| + | In the interest of simplicity we are fine with keeping these looser semantics. | ||
| + | See the challenges at the end of this tutorial page if you're interested in more closely matching the semantics of TLC. | ||
| - | <code java> | + | Let's take our first foray into sets by defining the set membership operator in '' |
| - | private String parenthesize(String name, Expr... exprs) { | + | |
| - | StringBuilder builder = new StringBuilder(); | + | |
| - | builder.append("(").append(name); | + | <code java [highlight_lines_extra="2,3"]> |
| - | | + | |
| - | | + | |
| - | | + | |
| - | } | + | case MINUS: |
| - | builder.append(" | + | |
| - | + | ||
| - | return builder.toString(); | + | |
| - | } | + | |
| </ | </ | ||
| - | It isn' | + | The question mark is some unusual Java syntax you probably haven' |
| + | It has to do with // | ||
| + | Think of it as casting the right operand to //some kind// of set, so we can access the '' | ||
| + | It is tempting to cast it to '' | ||
| - | ====== Chapter 6: Parsing Expressions ====== | + | Similar to equality, we get looser semantics here than with TLC. |
| + | TLC will emit a runtime error when evaluating the expression '' | ||
| + | Again this is acceptable. | ||
| - | In [[https:// | + | Now for something more complicated. |
| + | Here's the set construction helper operator '' | ||
| - | ===== Section 6.1: Ambiguity and the Parsing Game ===== | + | <code java [highlight_lines_extra="2,3,4,5,6,7,8,9,10, |
| - | + | switch (expr.operator.type) { | |
| - | First, as in [[https:// | + | case DOT_DOT: |
| - | The way that precedence works in TLA⁺ is different from Lox, and indeed different from most other languages! | + | |
| - | One side-quote from this section of the book reads: | + | int lower = (int)left; |
| - | + | int higher = (int)right; | |
| - | >While not common these days, some languages specify that certain pairs of operators have no relative precedence. That makes it a syntax error to mix those operators in an expression without using explicit grouping. | + | if (lower <= higher) { |
| - | + | for (int i = lower; i <= higher; i++) { | |
| - | > | + | set.add(i); |
| - | + | } | |
| - | TLA⁺ has both of these features! | + | } |
| - | Instead of operators occupying a slot in some hierarchy of precedence, each operator has a precedence //range//. | + | return set; |
| - | When the precedence ranges of two operators overlap it is a parse error. | + | case IN: |
| - | For example, the '' | + | |
| - | < | + | |
| - | ENABLED x' | + | |
| </ | </ | ||
| - | Users must add parentheses to indicate their desired grouping in the expression. | ||
| - | Similarly, some operators like '' | ||
| - | Both of these factors combine to make our operator parsing code quite a bit different from that given in the book. | ||
| - | Worry not, it can still be made terse and understandable! | ||
| - | ===== Section 6.2: Recursive Descent Parsing ===== | + | So expressions like '' |
| + | We use '' | ||
| + | It's incredible how much we get for free here; Java sets even implement value-based de-duplication and equality comparison for us for arbitrarily-nested sets! | ||
| - | [[https:// | + | ==== Evaluating Other Operators ==== |
| - | Recursive descent can seem a bit magical even if you're used to reasoning about recursive functions! | + | |
| - | Unfortunately TLA⁺ requires us to push this magic even further. | + | |
| - | We'll take it step by step. | + | |
| - | We start with the same basic '' | + | Our '' |
| - | We also add an import for '' | + | Add this '' |
| - | <code java [highlight_lines_extra=" | + | <code java> |
| - | package tla; | + | |
| - | + | | |
| - | import java.util.List; | + | |
| - | import java.util.ArrayList; | + | case IF: |
| - | + | Object conditional = evaluate(expr.first); | |
| - | import static tla.TokenType.*; | + | |
| - | + | | |
| - | class Parser { | + | |
| - | | + | // Unreachable. |
| - | private int current = 0; | + | |
| - | + | | |
| - | Parser(List< | + | |
| - | | + | |
| } | } | ||
| - | } | ||
| </ | </ | ||
| - | Now we hit our first major difference. | + | '' |
| - | In Lox, precedence is given by a small hierarchy of named rules like '' | + | Note our definition |
| - | TLA⁺ is more complicated than that. | + | |
| - | The full language | + | Finally, here's how we define the set construction operator; add this '' |
| - | If we wanted to match the book' | + | |
| - | <code java> | + | |
| - | private Expr operatorExpressionPrec1() { ... } | + | |
| - | private Expr operatorExpressionPrec2() { ... } | + | |
| - | private Expr operatorExpressionPrec3() { ... } | + | |
| - | ... | + | |
| - | private Expr operatorExpressionPrec15() { ... } | + | |
| - | </ | + | |
| - | where each '' | + | |
| - | Life is too short for this. | + | |
| - | Instead, we'll adopt a technique alluded to in the text: | + | |
| - | >If you wanted to do some clever Java 8, you could create a helper method for parsing a left-associative series of binary operators given a list of token types, and an operand method handle to simplify this redundant code. | + | |
| - | Here's the skeleton of our operator parsing function; the trick is to make the precedence a parameter to the function instead of a component of the name. | ||
| - | Add this code after the '' | ||
| <code java> | <code java> | ||
| - | | + | |
| - | | + | public Object visitVariadicExpr(Expr.Variadic expr) { |
| - | } | + | |
| - | + | case LEFT_BRACE: | |
| - | | + | |
| - | if (prec == 16) return primary(); | + | |
| - | + | | |
| - | Expr expr = operatorExpression(prec + 1); | + | |
| - | + | } | |
| - | return | + | return |
| + | default: | ||
| + | // Unreachable. | ||
| + | return null; | ||
| + | } | ||
| } | } | ||
| </ | </ | ||
| - | Before filling out '' | + | ====== Section 7.3: Runtime Errors ====== |
| - | <code java> | + | In [[https:// |
| - | private boolean match(TokenType... types) { | + | |
| - | for (TokenType type : types) { | + | |
| - | if (check(type)) { | + | |
| - | advance(); | + | |
| - | return true; | + | |
| - | } | + | |
| - | } | + | |
| - | return false; | + | Here's our variant of the '' |
| - | } | + | |
| - | | + | <code java [highlight_lines_extra=" |
| - | if (isAtEnd()) return | + | |
| - | | + | if (operand instanceof Integer) return; |
| + | | ||
| } | } | ||
| - | private Token advance() { | + | private |
| - | if (!isAtEnd()) current++; | + | |
| - | | + | if (left instanceof Integer && right instanceof Integer) return; |
| - | } | + | |
| - | + | ||
| - | private boolean isAtEnd() { | + | |
| - | return peek().type == EOF; | + | |
| - | } | + | |
| - | + | ||
| - | private Token peek() { | + | |
| - | return tokens.get(current); | + | |
| - | } | + | |
| - | private Token previous() { | + | throw new RuntimeError(operator, " |
| - | return tokens.get(current - 1); | + | |
| } | } | ||
| </ | </ | ||
| - | Now we have to define a table of operators with their details. | + | Create |
| - | For this, create | + | Contents are identical to that give in the book except for the package name: |
| - | <code java> | + | <code java [highlight_lines_extra=" |
| package tla; | package tla; | ||
| - | enum Fix { | + | class RuntimeError extends RuntimeException |
| - | | + | |
| - | } | + | |
| - | class Operator { | + | RuntimeError(Token token, |
| - | final Fix fix; | + | |
| - | final TokenType token; | + | |
| - | final boolean assoc; | + | |
| - | final int lowPrec; | + | |
| - | final int highPrec; | + | |
| - | + | ||
| - | public Operator(Fix fix, TokenType | + | |
| - | int lowPrec, int highPrec) { | + | |
| - | | + | |
| this.token = token; | this.token = token; | ||
| - | this.assoc = assoc; | ||
| - | this.lowPrec = lowPrec; | ||
| - | this.highPrec = highPrec; | ||
| } | } | ||
| } | } | ||
| </ | </ | ||
| - | For convenience, | + | We also need a helper for booleans since TLA⁺ is more strict about them than Lox; add this to the '' |
| - | + | ||
| - | <code java [highlight_lines_extra=" | + | |
| - | package tla; | + | |
| - | + | ||
| - | import java.util.List; | + | |
| - | import java.util.ArrayList; | + | |
| - | + | ||
| - | import static tla.TokenType.*; | + | |
| - | import static tla.Fix.*; | + | |
| - | + | ||
| - | class Parser { | + | |
| - | </ | + | |
| - | + | ||
| - | + | ||
| - | You can find operator attributes on page 271 of // | + | |
| - | We use a small subset of these operators. | + | |
| - | Record their attributes in a table in '' | + | |
| <code java> | <code java> | ||
| - | private | + | private |
| - | new Operator(PREFIX, | + | |
| - | | + | |
| - | new Operator(PREFIX, | + | } |
| - | new Operator(INFIX, | + | |
| - | new Operator(INFIX, | + | |
| - | new Operator(INFIX, | + | |
| - | new Operator(INFIX, | + | |
| - | new Operator(INFIX, | + | |
| - | new Operator(INFIX, | + | |
| - | new Operator(INFIX, | + | |
| - | new Operator(INFIX, | + | |
| - | new Operator(POSTFIX, | + | |
| - | }; | + | |
| </ | </ | ||
| - | Here's something a bit odd. | + | Finally, we need a helper for checking whether an operand |
| - | In TLA⁺, the infix minus operator (subtraction) has higher precedence at 11-11 than the infix plus operator (addition) at 10-10! | + | |
| - | In grade school you probably learned acronyms like PEMDAS or BEDMAS to remember the order of operations in arithmetic. | + | |
| - | Really, you learned parsing rules! | + | |
| - | Now when writing our own parsing algorithm | + | |
| - | TLA⁺ subverts this by parsing the expression '' | + | |
| - | While this design decision is unusual, it is [[https:// | + | |
| - | We need one more helper method before we can start working on '' | ||
| - | Add this code above '' | ||
| <code java> | <code java> | ||
| - | private | + | private |
| - | | + | if (operand instanceof Set<?> |
| - | | + | throw new RuntimeError(operator, " |
| - | | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | return null; | + | |
| } | } | ||
| </ | </ | ||
| - | Okay, we're all set for the main event! | + | Now add these checks to our interpreter code. |
| - | Here is how we modify | + | Here's negative: |
| - | You can see a strong resemblance to the infix operator parsing code given in the book (new lines highlighted): | + | <code java [highlight_lines_extra=" |
| - | <code java [highlight_lines_extra=" | + | } case MINUS: |
| - | | + | |
| - | if (prec == 16) return primary(); | + | |
| - | + | return | |
| - | Operator op; | + | } default: { |
| - | + | ||
| - | Expr expr = operatorExpression(prec + 1); | + | |
| - | | + | |
| - | Token operator = previous(); | + | |
| - | Expr right = operatorExpression(op.highPrec + 1); | + | |
| - | | + | |
| - | } | + | |
| - | + | ||
| - | | + | |
| - | } | + | |
| </ | </ | ||
| - | + | Logical | |
| - | We use Java's combined conditional-assignment atop the '' | + | <code java [highlight_lines_extra=" |
| - | The interior of the '' | + | } case NOT: { |
| - | It takes a bit of thinking to understand why this works for parsing expressions according to the precedence rules we want, but it does. | + | |
| - | Take a minute to ponder it. | + | |
| - | If you still don't get it, wait until we have a fully-functional expression parser then play around with this line to see how it changes the behavior. | + | |
| - | + | } case MINUS: { | |
| - | Our code implements precedence ranges, but assumes all infix operators are associative. | + | |
| - | We need to modify the loop to return immediately if the infix operator is not associative: | + | |
| - | <code java [highlight_lines_extra=" | + | |
| - | while ((op = matchOp(INFIX, | + | |
| - | Token operator | + | |
| - | Expr right = operatorExpression(op.highPrec + 1); | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| </ | </ | ||
| - | + | Subtraction: | |
| - | And that's how we parse infix operators! | + | <code java [highlight_lines_extra=" |
| - | That was the most complicated case, so let's move on to prefix operators. | + | case MINUS: |
| - | Again our code resembles the prefix operator parsing logic given in the book. | + | |
| - | Add this snippet near the top of '' | + | return (int)left - (int)right; |
| - | + | ||
| - | <code java [highlight_lines_extra=" | + | |
| - | | + | |
| - | | + | |
| - | + | ||
| - | Operator op; | + | |
| - | if ((op = matchOp(PREFIX, | + | |
| - | Token opToken = previous(); | + | |
| - | Expr expr = operatorExpression( | + | |
| - | op.assoc ? op.lowPrec : op.highPrec + 1); | + | |
| - | return | + | |
| - | } | + | |
| - | + | ||
| - | Expr expr = operatorExpression(prec + 1); | + | |
| </ | </ | ||
| - | + | Addition: | |
| - | Of note is the expression '' | + | <code java [highlight_lines_extra=" |
| - | To understand this expression, it is helpful to consider an example. | + | case PLUS: |
| - | The negative prefix operator '' | + | |
| - | In order to do that after consuming the first '' | + | |
| - | Then we will consume the second '' | + | |
| - | In contrast, the prefix operator '' | + | |
| - | In that case we want to reject the expression '' | + | |
| - | Thus the second '' | + | |
| - | + | ||
| - | All that remains is to parse postfix operators. | + | |
| - | These are not covered in the book, but they pretty much resemble infix operators without matching an expression on the right-hand side of the operator. | + | |
| - | Add the highlighted code below the '' | + | |
| - | + | ||
| - | <code java [highlight_lines_extra=" | + | |
| - | } | + | |
| - | + | ||
| - | | + | |
| - | Token opToken = previous(); | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | + | ||
| - | return expr | + | |
| - | } | + | |
| </ | </ | ||
| - | + | Less than: | |
| - | And that completes our operator parsing logic! | + | <code java [highlight_lines_extra=" |
| - | All that remains is our '' | + | case LESS_THAN: |
| - | It is fairly similar to that given in the book, with some omissions since our minimal TLA⁺ subset does not contain strings or null values. | + | |
| - | Add this code after '' | + | return (int)left < (int)right; |
| - | + | ||
| - | <code java> | + | |
| - | | + | |
| - | | + | |
| - | if (match(TRUE)) | + | |
| - | + | ||
| - | if (match(NUMBER)) { | + | |
| - | return new Expr.Literal(previous().literal); | + | |
| - | } | + | |
| - | + | ||
| - | if (match(LEFT_PAREN)) { | + | |
| - | Expr expr = expression(); | + | |
| - | consume(RIGHT_PAREN, | + | |
| - | return new Expr.Grouping(expr); | + | |
| - | } | + | |
| - | } | + | |
| </ | </ | ||
| - | + | The '' | |
| - | We have two final additions to '' | + | <code java [highlight_lines_extra=" |
| - | Here's how we parse '' | + | case DOT_DOT: |
| - | + | checkNumberOperands(expr.operator, | |
| - | <code java [highlight_lines_extra=" | + | |
| - | | + | </ |
| - | } | + | Set membership: |
| - | + | <code java [highlight_lines_extra=" | |
| - | if (match(IF)) { | + | |
| - | Token operator = previous(); | + | checkSetOperand(expr.operator, right); |
| - | Expr condition = expression(); | + | |
| - | | + | </ |
| - | | + | Finally, '' |
| - | | + | <code java [highlight_lines_extra="3" |
| - | Expr no = expression(); | + | |
| - | | + | Object conditional |
| - | } | + | |
| - | } | + | |
| + | | ||
| </ | </ | ||
| - | And here's how we parse the set construction operator. | + | ====== Section 7.4: Hooking Up the Interpreter ====== |
| - | Again add the highlighted code to the bottom of '' | + | |
| - | <code java [highlight_lines_extra=" | + | We're very close! |
| - | | + | In [[https:// |
| - | | + | First, add this public '' |
| - | + | <code java [highlight_lines_extra=" | |
| - | if (match(LEFT_BRACE)) | + | void interpret(Expr expression) { |
| - | | + | |
| - | | + | |
| - | if (RIGHT_BRACE != peek().type) { | + | |
| - | do { | + | } catch (RuntimeError error) { |
| - | elements.add(expression()); | + | |
| - | } while (match(COMMA)); | + | |
| - | | + | |
| - | consume(RIGHT_BRACE, | + | |
| - | return new Expr.Variadic(operator, elements); | + | |
| } | } | ||
| } | } | ||
| </ | </ | ||
| - | This will handle expressions like '' | + | In contrast to the book, our '' |
| - | + | ||
| - | The '' | + | |
| - | + | ||
| - | ===== Section 6.3: Syntax Errors ===== | + | |
| - | + | ||
| - | We don't need to modify most of the error reporting code given in [[https:// | + | |
| - | Here it is; add the '' | + | |
| <code java> | <code java> | ||
| - | private | + | private |
| - | | + | return |
| - | + | ||
| - | throw error(peek(), | + | |
| } | } | ||
| </ | </ | ||
| - | Then add the '' | + | Add a '' |
| <code java> | <code java> | ||
| - | | + | |
| - | | + | |
| - | | + | " |
| + | | ||
| } | } | ||
| </ | </ | ||
| - | In the '' | + | Then add a static '' |
| - | + | ||
| - | <code java> | + | |
| - | | + | |
| - | if (token.type == TokenType.EOF) { | + | |
| - | report(token.line, | + | |
| - | } else { | + | |
| - | report(token.line, | + | |
| - | } | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Add the '' | + | |
| <code java [highlight_lines_extra=" | <code java [highlight_lines_extra=" | ||
| - | class Parser { | + | static boolean hadError = false; |
| - | | + | static |
| - | | + | |
| </ | </ | ||
| - | One piece of code we //do// need to modify is the '' | + | And exit from the '' |
| - | TLA⁺ doesn' | + | |
| - | <code java> | + | <code java [highlight_lines_extra=" |
| - | | + | run(new String(bytes, |
| - | advance(); | + | |
| - | | + | |
| - | if (previous().type == EQUAL_EQUAL) return; | + | if (hadError) System.exit(65); |
| - | + | | |
| - | advance(); | + | |
| - | } | + | |
| } | } | ||
| </ | </ | ||
| - | Fully-featured TLA⁺ parsers generally look ahead to identify the start of a new operator definition (not trivial!) and insert a synthetic zero-width token for the parser to consume. | + | Finally, create |
| - | For an example, see SANY's very complicated | + | |
| - | We won't be doing that here, but it's a good technique to know about; looking ahead then inserting synthetic tokens to disambiguate syntax is a common method when parsing complicated & ambiguous languages like TLA⁺. | + | |
| - | ===== Section 6.4: Wiring up the Parser ===== | + | <code java [highlight_lines_extra=" |
| + | public class TlaPlus { | ||
| + | private static final Interpreter interpreter | ||
| + | static boolean hadError | ||
| + | </ | ||
| - | In [[https:// | + | Then finish it off by actually interpreting |
| - | In the '' | + | |
| - | + | ||
| - | <code java [highlight_lines_extra=" | + | |
| - | List< | + | |
| - | + | ||
| - | Parser parser = new Parser(tokens); | + | |
| - | Expr expression = parser.parse(); | + | |
| + | <code java [highlight_lines_extra=" | ||
| // Stop if there was a syntax error. | // Stop if there was a syntax error. | ||
| if (hadError) return; | if (hadError) return; | ||
| - | | + | |
| } | } | ||
| </ | </ | ||
| - | Fire up the interpreter and parse a TLA⁺ expression! | + | Voila! |
| + | Your program will now interpret any constant | ||
| + | Try it out: | ||
| < | < | ||
| - | > {1 + 2, IF TRUE THEN 3 ELSE 4, {}} | + | > {0 .. 2, IF TRUE THEN 1 ELSE 2, {}} |
| - | ({ (+ 1 2) (IF true 3 4) ({)) | + | [[], 1, [0, 1, 2]] |
| </ | </ | ||
| - | If you got out of sync, you can find a snapshot of the expected state of the code in [[https:// | + | |
| - | Next tutorial: [[https:// | + | If you got out of sync, you can find a snapshot of the expected state of the code in [[https:// |
| + | Next tutorial: [[creating: | ||
| ====== Challenges ====== | ====== Challenges ====== | ||
| Here are some optional challenges to flesh out your TLA⁺ interpreter, | Here are some optional challenges to flesh out your TLA⁺ interpreter, | ||
| + | You should save a copy of your code before attempting these. | ||
| + | - TLC evaluates cross-type equality comparison as a runtime error. For example, '' | ||
| + | - TLC requires set elements to all be of the same type. Trying to construct the set '' | ||
| - | [[https:// | + | [[creating:expressions|< Previous Page]] | [[creating:start# |