Skip to content

Step 1 of 3 · Reading · ~2 min

Loops — Repeating Execution

Control Flow & Functions

While & For Loops

Your interpreter can already evaluate expressions and run straight-line statements. This lesson adds the two loop constructs every imperative language needs: while and for. The interesting design decision isn't how to execute a loop — it's how little new machinery you need if you desugar for into while.

The while statement

Add a While node to your statement AST with two fields: condition (an expression) and body (a statement):

python

Parsing is a direct translation of the grammar rule "while" "(" expression ")" statement. Interpreting it is just as direct — it's the first place your tree-walker actually loops instead of recursing once per node:

python

Reuse whatever _truthy logic you already wrote for if: in a Lox-style language, nil and false are falsy, everything else (including 0 and "") is truthy. Getting this rule consistent across if, while, and logical operators matters — learners will test edge cases like while (0) ... expecting it to loop, because 0 is truthy here.

Desugaring for into while

A C-style for (init; condition; increment) body doesn't need its own interpreter case at all. Parse it, then rewrite it as an equivalent block using nodes you already have:

{
  init;
  while (condition) {
    body;
    increment;
  }
}

Concretely, in your parser's for_statement:

  1. Parse the optional init (a var declaration, an expression statement, or nothing).
  2. Parse the optional condition expression; if omitted, default to a literal true so the loop doesn't terminate immediately.
  3. Parse the optional increment expression.
  4. Parse body.
  5. If there's an increment, wrap body in a Block([body, ExpressionStatement(increment)]).
  6. Wrap that in a While(condition, body).
  7. If there's an init, wrap the whole While in a Block([init, while_stmt]).

This is the single biggest lesson in "keep the interpreter small": every new piece of surface syntax doesn't need a new evaluation rule if it can be expressed in terms of constructs you already support.

Edge cases to watch

  • Scoping: the init variable (e.g. var i = 0) must live in a scope that encloses the loop body but isn't visible after the loop — that's exactly what wrapping everything in an outer Block gives you for free, since blocks already create a new environment.
  • Missing clauses: for (;;) { ... } must parse (all three clauses optional) and behave as an infinite loop.
  • Increment always runs: even if the body contains a continue-like early exit (if your language has one), the desugared form still executes the increment as the last statement of the body block on every iteration.
Up nextLogical Operators (and, or)Control Flow & Functions

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…