Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| creating:actions [2025/06/19 20:44] – Rewrite Interpreter code to use simplified action method ahelwer | creating:actions [2025/11/03 23:08] (current) – One more instanceof-cast ahelwer | ||
|---|---|---|---|
| Line 146: | Line 146: | ||
| </ | </ | ||
| - | ===== Reading | + | ===== Reading, Priming, & Setting |
| To resolve variable values, modify the ever-more-complicated '' | To resolve variable values, modify the ever-more-complicated '' | ||
| Line 171: | Line 171: | ||
| The '' | The '' | ||
| We can now finally define the prime operator for real, in '' | We can now finally define the prime operator for real, in '' | ||
| - | <code java [highlight_lines_extra=" | + | <code java [highlight_lines_extra=" |
| public Object visitUnaryExpr(Expr.Unary expr) { | public Object visitUnaryExpr(Expr.Unary expr) { | ||
| switch (expr.operator.type) { | switch (expr.operator.type) { | ||
| Line 177: | Line 177: | ||
| if (primed) { | if (primed) { | ||
| throw new RuntimeError(expr.operator, | throw new RuntimeError(expr.operator, | ||
| - | " | + | |
| + | ? " | ||
| + | : " | ||
| } try { | } try { | ||
| primed = true; | primed = true; | ||
| Line 191: | Line 193: | ||
| This is why the prime operator somewhat uniquely modifies the state of the '' | This is why the prime operator somewhat uniquely modifies the state of the '' | ||
| Similar to in '' | Similar to in '' | ||
| - | |||
| - | ===== Setting State Variables ===== | ||
| We can retrieve state variable values, but what about assigning them? | We can retrieve state variable values, but what about assigning them? | ||
| Line 198: | Line 198: | ||
| This requires modifying the '' | This requires modifying the '' | ||
| If the '' | If the '' | ||
| - | <code java [highlight_lines_extra=" | + | <code java [highlight_lines_extra=" |
| case EQUAL: | case EQUAL: | ||
| - | if (left instanceof | + | if (left instanceof UnboundVariable |
| - | UnboundVariable var = (UnboundVariable)left; | + | |
| next.put(var.name().lexeme, | next.put(var.name().lexeme, | ||
| return true; | return true; | ||
| Line 208: | Line 207: | ||
| </ | </ | ||
| - | We only modify variable values in '' | + | We only modify variable values in '' |
| - | + | ||
| - | Although we won't yet use it, now is also a good time to define our '' | + | |
| - | <code java> | + | |
| - | void step(Token location) { | + | |
| - | if (!isComplete()) { | + | |
| - | throw new RuntimeError(location, | + | |
| - | " | + | |
| - | } | + | |
| - | + | ||
| - | primed = false; | + | |
| - | current = next; | + | |
| - | clearNext(); | + | |
| - | } | + | |
| - | + | ||
| - | private void isComplete() { | + | |
| - | return !variables.isEmpty() && next.values().stream() | + | |
| - | .noneMatch(v -> v instanceof | + | |
| - | } | + | |
| - | + | ||
| - | private void clearNext() { | + | |
| - | next = new HashMap<> | + | |
| - | for (Token variable : variables.values()) { | + | |
| - | next.put(variable.lexeme, | + | |
| - | } | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | '' | + | |
| - | + | ||
| - | Now we see things taking shape: '' | + | |
| - | The system' | + | |
| - | Then, '' | + | |
| - | The values of variables in '' | + | |
| ===== Nondeterminism ===== | ===== Nondeterminism ===== | ||
| Line 269: | Line 235: | ||
| </ | </ | ||
| - | We also update '' | + | When our interpreter finds multiple possible next states, it tosses them into '' |
| - | <code java [highlight_lines_extra=" | + | |
| - | private void clearNext() { | + | |
| - | possibleNext = new HashSet<> | + | |
| - | next = new HashMap<> | + | |
| - | </ | + | |
| - | + | ||
| - | When our interpreter finds multiple possible next states, it tosses them into '' | + | |
| Then some top-level loop churns through the '' | Then some top-level loop churns through the '' | ||
| We will write this very critical top-level loop now; create a new method '' | We will write this very critical top-level loop now; create a new method '' | ||
| Line 314: | Line 273: | ||
| For more on this peculiar issue, consult the Java '' | For more on this peculiar issue, consult the Java '' | ||
| - | Now we can write the central loop: | + | This code also requires a new '' |
| + | <code java> | ||
| + | private void clearNext() { | ||
| + | possibleNext = new HashSet<> | ||
| + | next = new HashMap<> | ||
| + | for (Token variable : variables.values()) { | ||
| + | next.put(variable.lexeme, | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Now we can write the central | ||
| <code java [highlight_lines_extra=" | <code java [highlight_lines_extra=" | ||
| while (!possibleNext.isEmpty()) { | while (!possibleNext.isEmpty()) { | ||
| Line 327: | Line 297: | ||
| Note again the judicious use of '' | Note again the judicious use of '' | ||
| + | This code requires yet another '' | ||
| + | <code java> | ||
| + | private boolean isComplete() { | ||
| + | return !variables.isEmpty() && next.values().stream() | ||
| + | .noneMatch(v -> v instanceof UnboundVariable); | ||
| + | } | ||
| + | </ | ||
| It's worth spending some time considering the '' | It's worth spending some time considering the '' | ||
| Line 361: | Line 338: | ||
| Actions like '' | Actions like '' | ||
| Add the following code to the '' | Add the following code to the '' | ||
| - | <code java [highlight_lines_extra=" | + | <code java [highlight_lines_extra=" |
| case IN: | case IN: | ||
| checkSetOperand(expr.operator, | checkSetOperand(expr.operator, | ||
| - | if (left instanceof | + | if (left instanceof UnboundVariable |
| - | UnboundVariable var = (UnboundVariable)left; | + | |
| Map< | Map< | ||
| for (Object element : (Set<?> | for (Object element : (Set<?> | ||
| Line 395: | Line 371: | ||
| We can now generate all nondeterministic next states by calling '' | We can now generate all nondeterministic next states by calling '' | ||
| + | |||
| + | ===== The ENABLED Operator ===== | ||
| + | |||
| + | Now that we've written '' | ||
| + | An expression '' | ||
| + | So, as simple as a quick call to '' | ||
| + | Well, not quite - the '' | ||
| + | This means we need some extra ceremony to stash the current values of '' | ||
| + | Add the highlighted code to the '' | ||
| + | <code java [highlight_lines_extra=" | ||
| + | } case ENABLED: { | ||
| + | Map< | ||
| + | Set< | ||
| + | try { | ||
| + | clearNext(); | ||
| + | return !getNextStates(expr.operator, | ||
| + | } finally { | ||
| + | next = oldNext; | ||
| + | possibleNext = oldPossibleNext; | ||
| + | } | ||
| + | } case NOT: { | ||
| + | </ | ||
| + | |||
| + | This is a big milestone: we now have fully defined // | ||
| + | We aren't at the end yet, but it's well within sight. | ||
| ===== Error Checking ===== | ===== Error Checking ===== | ||
| Line 407: | Line 408: | ||
| private void checkIsValue(Object... operands) { | private void checkIsValue(Object... operands) { | ||
| for (Object operand : operands) { | for (Object operand : operands) { | ||
| - | if (operand instanceof UnboundVariable) { | + | if (operand instanceof UnboundVariable |
| - | UnboundVariable var = (UnboundVariable)operand; | + | |
| throw new RuntimeError(var.name(), | throw new RuntimeError(var.name(), | ||
| } | } | ||
| Line 417: | Line 417: | ||
| In our '' | In our '' | ||
| Also, two values can only be compared for equality if they are both defined: | Also, two values can only be compared for equality if they are both defined: | ||
| - | <code java [highlight_lines_extra=" | + | <code java [highlight_lines_extra=" |
| case EQUAL: | case EQUAL: | ||
| - | if (left instanceof UnboundVariable) { | + | if (left instanceof UnboundVariable |
| - | UnboundVariable var = (UnboundVariable)left; | + | |
| checkIsValue(right); | checkIsValue(right); | ||
| next.put(var.name().lexeme, | next.put(var.name().lexeme, | ||
| Line 462: | Line 461: | ||
| Map<?, ?> function = (Map<?, ?> | Map<?, ?> function = (Map<?, ?> | ||
| </ | </ | ||
| + | |||
| + | ===== Hooking Up the Interpreter ===== | ||
| + | |||
| + | It takes a bit of thought to consider how we should expose our new state-stepping capabilities through the REPL. | ||
| + | One decent approach is to hijack '' | ||
| + | For this, add the following code to the bottom of the '' | ||
| + | <code java [highlight_lines_extra=" | ||
| + | // Stop if there was a syntax error. | ||
| + | if (hadError) return; | ||
| + | |||
| + | if (replMode && statements.size() == 1 | ||
| + | && statements.get(0) instanceof Stmt.Print action) { | ||
| + | tryStep(action); | ||
| + | } else { | ||
| + | interpreter.interpret(statements); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | This calls a method we'll define in '' | ||
| + | The '' | ||
| + | However, if '' | ||
| + | If this generates no new states, again treat '' | ||
| + | <code java> | ||
| + | private static void tryStep(Stmt.Print action) { | ||
| + | Object result = interpreter.executeBlock(action.expression, | ||
| + | if (!(result instanceof Boolean)) { | ||
| + | action.accept(interpreter); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | List< | ||
| + | interpreter.getNextStates(action.location, | ||
| + | if (nextStates.isEmpty()) { | ||
| + | action.accept(interpreter); | ||
| + | return; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | The '' | ||
| + | <code java [highlight_lines_extra=" | ||
| + | import java.util.List; | ||
| + | import java.util.Map; | ||
| + | |||
| + | public class TlaPlus { | ||
| + | </ | ||
| + | |||
| + | At this point all that remains is to handle the case of what happens when the '' | ||
| + | The REPL should choose one next state and make '' | ||
| + | <code java [highlight_lines_extra=" | ||
| + | List< | ||
| + | interpreter.getNextStates(action.location, | ||
| + | if (nextStates.isEmpty()) { | ||
| + | System.out.println(false); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | Map< | ||
| + | interpreter.goToState(nextState); | ||
| + | System.out.println(true); | ||
| + | System.out.println(nextState); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | We also print out '' | ||
| + | This called two methods; one, '' | ||
| + | <code java> | ||
| + | void goToState(Map< | ||
| + | current = state; | ||
| + | primed = state == null; | ||
| + | clearNext(); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | The other, in '' | ||
| + | If there are multiple possible next states, the user is prompted to choose one: | ||
| + | <code java> | ||
| + | private static Map< | ||
| + | if (nextStates.size() == 1) { | ||
| + | return nextStates.get(0); | ||
| + | } else { | ||
| + | System.out.println(" | ||
| + | for (int i = 0; i < nextStates.size(); | ||
| + | System.out.println(i + ": " + nextStates.get(i)); | ||
| + | } | ||
| + | |||
| + | System.out.print("> | ||
| + | try (java.util.Scanner in = new java.util.Scanner(System.in)) { | ||
| + | return nextStates.get(in.nextInt()); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | And that's it! | ||
| + | You can now run the REPL and step through the state space! | ||
| + | Try entering expressions like the following: | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | |||
| + | This was a pivotal chapter; excellent work making it to the end. | ||
| + | You can find the expected state of the codebase in [[https:// | ||
| + | Now we can put it all together and build a real model checker in [[creating: | ||
| + | |||
| + | [[creating: | ||