Skip to content
Lesson 12 of 38

Step 1 of 3 · Reading · ~3 min

Conditional Execution

Control Flow & Functions

Your First Control Flow Statement

Everything so far executes top-to-bottom, unconditionally. if/else is where the interpreter starts making decisions — and it's also your first encounter with a classic parsing ambiguity that every C-family language has to explicitly resolve: the dangling else.

Grammar

statement → exprStmt | printStmt | block | ifStmt
ifStmt    → "if" "(" expression ")" statement ( "else" statement )?

Notice condition isn't required to be a boolean-typed expression at the grammar level — the language is dynamically typed, so any expression is legal in the condition position, and truthiness rules decide how it's interpreted at runtime. Also notice the branches are statement, not specifically a block — so if (x) print "hi"; (no braces) is legal, just as it is in C or JavaScript. This is exactly why blocks matter: to execute multiple statements conditionally, you wrap them in { }, which parses as a single Block statement satisfying the statement slot.

The Dangling Else

if (a) if (b) print "both"; else print "only a?";

Does that else belong to the inner if (b) or the outer if (a)? Every C-family grammar resolves this the same way: else binds to the nearest preceding unmatched if. Your recursive descent parser gets this for free if you implement it the naive, greedy way:

python

Because then_branch = self.statement() is a single recursive call, if the "then" branch is itself an if statement, that inner call is the one that greedily checks for and consumes a following else — before control ever returns to the outer if_statement() call. The outer call never gets a chance to see that else token; it's already gone. No special-case logic is required — the recursive structure of the parser resolves the ambiguity as a natural side effect of call order.

The AST Node and Its Evaluation

python

Execution is a direct mirror of the grammar:

python

Truthiness, Reused

The rule from earlier — everything is truthy except false and nil — is what is_truthy() encodes, and it's exactly what makes if (0) take the "then" branch (since 0 is truthy in this language) while if (nil) and if (false) both take the "else" branch (or skip entirely if there is none). Keep this rule consistent everywhere truthiness is checked — if, and later while, for, and the logical and/or operators — since a divergence between them is a subtle, hard-to-spot bug.

Note also that only one of the two branches ever executes — the other is never evaluated at all. This matters once expressions can have side effects (assignment, printing, function calls): code in the untaken branch must have zero observable effect.

Up nextWhile & For LoopsControl Flow & Functions

Discussion

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

Sign in to post a comment or reply.

Loading…