Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
creating:operators [2025/05/20 20:22] – Change-of-base counting algorithm for set enumeration & binding ahelwer | creating:operators [2025/06/13 21:25] (current) – Removed PrintStream parameter from Interpreter constructor ahelwer | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ======= Operators | + | ======= |
We now rejoin //Crafting Interpreters// | We now rejoin //Crafting Interpreters// | ||
Line 101: | Line 101: | ||
In [[https:// | In [[https:// | ||
+ | First, add some imports to the top of '' | ||
+ | <code java [highlight_lines_extra=" | ||
+ | import java.util.Set; | ||
+ | import java.util.HashSet; | ||
+ | import java.util.ArrayList; | ||
+ | import java.util.List; | ||
+ | import java.util.Map; | ||
+ | import java.util.HashMap; | ||
+ | </ | ||
- | First let's implement | + | Implement |
<code java> | <code java> | ||
@Override | @Override | ||
Line 232: | Line 241: | ||
We've already implemented all our native TLA⁺ functions in the // | We've already implemented all our native TLA⁺ functions in the // | ||
Due to the '' | Due to the '' | ||
- | <code java [highlight_lines_extra=" | + | <code java [highlight_lines_extra=" |
class Interpreter implements Expr.Visitor< | class Interpreter implements Expr.Visitor< | ||
| | ||
final Environment globals; | final Environment globals; | ||
private Environment environment; | private Environment environment; | ||
- | private final PrintStream out; | ||
- | public Interpreter(PrintStream out, boolean replMode) { | + | public Interpreter(boolean replMode) { |
this.globals = new Environment(replMode); | this.globals = new Environment(replMode); | ||
this.environment = this.globals; | this.environment = this.globals; | ||
- | this.out = out; | ||
} | } | ||
</ | </ | ||
Line 431: | Line 438: | ||
return null; | return null; | ||
} | } | ||
+ | </ | ||
+ | |||
+ | Additional TLA⁺-specific validation is necessary here. | ||
+ | While we allow redefining operators // | ||
+ | So, if an operator of name '' | ||
+ | We should also disallow operator definitions with duplicate parameter names like '' | ||
+ | Define a new helper for this near the bottom of the '' | ||
+ | <code java> | ||
+ | private void checkNotDefined(List< | ||
+ | for (Token name : names) { | ||
+ | if (environment.isDefined(name)) { | ||
+ | throw new RuntimeError(name, | ||
+ | } | ||
+ | } | ||
+ | |||
+ | for (int i = 0; i < names.size() - 1; i++) { | ||
+ | for (int j = i + 1; j < names.size(); | ||
+ | if (names.get(i).lexeme.equals(names.get(j).lexeme)) { | ||
+ | throw new RuntimeError(names.get(i), | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | This helper requires a new method in the '' | ||
+ | <code java> | ||
+ | boolean isDefined(Token name) { | ||
+ | return values.containsKey(name.lexeme) | ||
+ | || (enclosing != null && enclosing.isDefined(name)); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Add a '' | ||
+ | <code java [highlight_lines_extra=" | ||
+ | public Void visitOpDefStmt(Stmt.OpDef stmt) { | ||
+ | checkNotDefined(stmt.params); | ||
+ | for (Token param : stmt.params) { | ||
+ | if (param.lexeme.equals(stmt.name.lexeme)) { | ||
+ | throw new RuntimeError(param, | ||
+ | } | ||
+ | } | ||
+ | |||
+ | TlaOperator op = new TlaOperator(stmt); | ||
</ | </ | ||
And that takes care of operators! | And that takes care of operators! | ||
Now to handle functions. | Now to handle functions. | ||
- | We still need to implement our '' | + | We still need to implement our '' |
+ | Similar | ||
+ | We also ensure the function parameter names are not shadowing an existing identifier: | ||
<code java> | <code java> | ||
@Override | @Override | ||
public Object visitQuantFnExpr(Expr.QuantFn expr) { | public Object visitQuantFnExpr(Expr.QuantFn expr) { | ||
+ | checkNotDefined(expr.params); | ||
Object set = evaluate(expr.set); | Object set = evaluate(expr.set); | ||
checkSetOperand(expr.op, | checkSetOperand(expr.op, | ||
Line 524: | Line 578: | ||
Object junctResult = executeBlock(expr.body, | Object junctResult = executeBlock(expr.body, | ||
checkBooleanOperand(expr.op, | checkBooleanOperand(expr.op, | ||
- | result |= (Boolean)junctResult; | + | result |= (boolean)junctResult; |
} | } | ||
return result; | return result; | ||
Line 540: | Line 594: | ||
} case FOR_ALL: { | } case FOR_ALL: { | ||
</ | </ | ||
+ | |||
+ | ====== Set Enumeration ====== | ||
Now that we've finished all that, let's get to the real difficult part: implementing an enumeration algorithm in '' | Now that we've finished all that, let's get to the real difficult part: implementing an enumeration algorithm in '' | ||
Line 547: | Line 603: | ||
For example, imagine you had a set of three values '' | For example, imagine you had a set of three values '' | ||
This is isomorphic to iterating through all possible values of a two-digit number in base three. | This is isomorphic to iterating through all possible values of a two-digit number in base three. | ||
- | Confusing? Here's a table illustrating the connection: | + | The number of digits corresponds to the number of variables to bind, and the base corresponds to the number of elements in the set. |
+ | |||
+ | This is confusing, but we will walk through it step by step. | ||
+ | First we must assign an arbitrary order to the set so we can map single digits in base three to an element of the set: | ||
+ | ^ Digit ^ Element ^ | ||
+ | | 0 | a | | ||
+ | | 1 | b | | ||
+ | | 2 | c | | ||
+ | |||
+ | Using that mapping, here's a table showing how counting up through all two-digit base three numbers decomposes into binding all possible combinations of values to '' | ||
^ Number (base 3) ^ As set elements ^ x (0th-significant digit) ^ y (1st-significant digit) ^ | ^ Number (base 3) ^ As set elements ^ x (0th-significant digit) ^ y (1st-significant digit) ^ | ||
| 00 | aa | a | a | | | 00 | aa | a | a | | ||
Line 559: | Line 624: | ||
| 22 | cc | c | c | | | 22 | cc | c | c | | ||
- | As you can see, by counting up through all two-digit base-three numbers, we generate | + | As you can see, we generated |
Each identifier is assigned a particular digit in the number, and their value in a binding is given by that digit' | Each identifier is assigned a particular digit in the number, and their value in a binding is given by that digit' | ||
- | So, for a set '' | ||
We're almost there, but not quite; Java doesn' | We're almost there, but not quite; Java doesn' | ||
We will need to increment a regular '' | We will need to increment a regular '' | ||
Actually the well-known [[https:// | Actually the well-known [[https:// | ||
- | It is quite elegant; given a number '' | + | It is quite elegant; given a number '' |
- | Then, assign '' | + | Then, assign '' |
Confused? Here's how it looks as a table for converting the enumeration value 6 to a binding from our previous base 3 example: | Confused? Here's how it looks as a table for converting the enumeration value 6 to a binding from our previous base 3 example: | ||
^ Iteration ^ '' | ^ Iteration ^ '' | ||
Line 575: | Line 639: | ||
Note that the value of '' | Note that the value of '' | ||
Reading the '' | Reading the '' | ||
- | Now associate each value in the set with a fixed value between '' | + | Consulting |
- | In our case, 0 maps to '' | + | '' |
- | The base 3 digit is identified by the iteration value. | + | '' |
- | The value of '' | + | |
- | The value of '' | + | |
- | So, enumeration value 6 binds value '' | + | |
- | You should consult the original table and try some other values to convince yourself that our algorithm successfully calculates the binding. | + | |
- | Now to implement it! | + | The iteration number actually corresponds to digit significance. |
+ | So, in iteration 0, '' | ||
+ | In iteration 1, '' | ||
+ | If this is still too abstract, try some other values to convince yourself that our algorithm successfully calculates the binding! | ||
+ | |||
+ | Now to implement it. | ||
First, add another field to the '' | First, add another field to the '' | ||
<code java [highlight_lines_extra=" | <code java [highlight_lines_extra=" | ||
Line 596: | Line 661: | ||
Then, implement '' | Then, implement '' | ||
This is required by the '' | This is required by the '' | ||
- | In our case, we test that '' | + | In our case, we test that '' |
+ | For '' | ||
<code java> | <code java> | ||
@Override | @Override | ||
Line 619: | Line 685: | ||
} | } | ||
</ | </ | ||
+ | |||
+ | Note that '' | ||
+ | A nice little terse code trick. | ||
And we're done! | And we're done! | ||
You can now run your interpreter and enjoy the full power of functions & operators with parameters, along with the new universal & existential quantification expressions! | You can now run your interpreter and enjoy the full power of functions & operators with parameters, along with the new universal & existential quantification expressions! | ||
+ | See the current expected state of your source code [[https:// | ||
+ | On to the next chapter: [[creating: | ||
+ | |||
+ | ====== Challenges ====== | ||
+ | |||
+ | - In the full TLA⁺ language, quantified functions can be even more complicated; | ||
+ | - Modify the change-of-base algorithm to handle enumerating multiple sets of varying cardinality, | ||
+ | - Our enumeration algorithm is a " | ||
+ | - Often, sets in TLA⁺ models are very large. Instead of immediately evaluating sets constructed with the '' | ||
+ | |||
+ | [[creating: | ||