Skip to content

Step 1 of 3 · Reading · ~2 min

From Expressions to Statements

Statements & State

Expressions Produce Values; Statements Produce Effects

Everything you've built so far — the scanner, the precedence ladder, the AST, the evaluator — deals with expressions: things that reduce to a value. But a real program is a sequence of statements: things that do something (print output, declare a variable, run a loop) and don't themselves have a value. This lesson introduces the statement/expression split that the rest of the language is built on.

Two Statement Forms, One New Grammar Layer

program        → statement* EOF
statement      → exprStmt | printStmt
exprStmt       → expression ";"
printStmt      → "print" expression ";"

A program is just a flat list of statements, each terminated by ;. There are two kinds so far:

  • Expression statement: an expression followed by ;, evaluated purely for its side effects (which, right now, means "none" — but this shape becomes essential once function calls and assignments exist, since foo(); is a statement that discards its return value).
  • Print statement: the keyword print followed by an expression and ; — evaluates the expression and writes the result to output.

Parsing Statements: One Level Above Expressions

The parser gets a new entry point above expression():

python

Note the shape: statement() looks at the first token to decide which statement rule applies (here, just checking for the print keyword), then delegates. This "peek at the front, dispatch, delegate" pattern is exactly how you'll add every future statement type (var, if, while, for, blocks) — each one gets a leading keyword or token that statement() checks for, with a fallback to expression_statement() when nothing matches.

Two New AST Node Types

python

These look nearly identical, but they mean different things to the interpreter: executing a Print node evaluates its expression and writes the result; executing an ExpressionStmt evaluates its expression and discards the result. The distinction matters even though both currently just call evaluate() — it's the hook where "does this produce visible output or not" gets decided.

The Top-Level Loop

The interpreter's entry point becomes: parse the whole token stream into a list of statements (not a single expression), then execute them one at a time in order:

python

Watch for the stringify detail: Lox-style languages print nil for None, drop the .0 from whole-number floats (3 not 3.0), and print booleans as true/false rather than Python's True/False. Getting this output formatting exactly right is what separates "the interpreter works" from "the interpreter's test output matches."

Up nextVariable Declaration & AssignmentStatements & State

Discussion

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

Sign in to post a comment or reply.

Loading…