v4 Control Flow: for, break/continue, && / ||
Lesson, slides, and applied problem sets.
View SlidesLesson
v4 Control Flow: for, break/continue, && / ||
Why v4 control flow matters
Before v4, you mostly lowered expressions. Now you lower execution shape:
- repetitive structure
- conditional jumps
- non-local branch targets (
break,continue) - short-circuit boolean edges
This is where control-flow correctness determines correctness of otherwise "valid" programs.
1) Canonical loop execution model
for (init; cond; post) { body } lowers with deterministic phases:
- optional
initexecutes once - jump to
cond - evaluate
cond(or implicit true when omitted) - if falsey -> exit loop
- execute
body - optional
post - jump to
cond
A loop has two special edge types:
break: exit loop earlycontinue: go to continuation point
2) Loop scoping rules in v4
In this pack:
- initializer may be declaration form (
let i = 0) - initializer scope includes
cond,body, andpost - scope unwinds after loop end
That scope rule ensures:
for (let i = 0; ; ) { ... }hasivisible in body/postidoes not leak outside loop
3) Compiler lowering skeleton
Lowering typically stores jump placeholders first:
ENTER_SCOPE
[init]
loop_start:
[cond]
JUMP_IF_FALSE loop_end
[body]
post_label:
[post]
JUMP loop_start
loop_end:
EXIT_SCOPE
For omitted parts:
- no-init: skip init emission
- no-cond: emit constant-true branch behavior
- no-post: jump from post-label directly to loop_start
4) Patch lists for break/continue
You usually need a loop stack entry:
breakJumps []intto patch to loop_endcontinueJumps []intto patch to post/next-iteration point
At loop exit, patch every recorded jump offset.
This avoids recursive backtracking and keeps nesting explicit.
Important:
continuein nested loops must target the innermost loop continuationbreaktargets current loop-end label
5) Short-circuit lowering (&& and ||)
For a && b:
- evaluate
a - if falsey: jump to false branch, push
false, skipb - else evaluate
b, result becomes final boolean
For a || b:
- evaluate
a - if truthy: jump to true branch, push
true, skipb - else evaluate
b
This module treats result as boolean in control-flow lowering, but runtime truthiness remains package policy (false or nil.
6) Precedence and parser support
Grammar in v4 parser is explicit:
or -> and ("||" and)*and -> equality ("&&" equality)*
That gives left-associative behavior inside each precedence level.
Parsing contracts must preserve:
a || b && casa || (b && c)a && b || cas(a && b) || c
7) Correctness checks that catch 90% of bugs
Before finalizing v4 control flow:
- every emitted jump offset is patched
- each unresolved
break/continuelist should be empty after exit - no jump points to wrong loop level
- boolean result left on stack exactly once for logical expressions
A small diff in one jump target can change program meaning without crashing.
8) Practice checkpoints (almost solved)
1) Loop state ordering + for (let i = 0; i < 2; i = i + 1) { } + Expected order: init once, repeated cond -> body -> post -> cond.
2) continue target in with-post loops + for (i = 0; i < 5; i = i + 1) { continue; f(i); } + Expected: f(i) is skipped; jump goes to post update.
3) break behavior + for (...) { break; } + Expected: exits to immediate loop end and does not execute post/body after break point.
4) Infinite loop rule + for (;;) { if (false) break; } + Expected: legal and semantically infinite unless control exits.
5) Short-circuit correctness + false && sideEffect() should not execute sideEffect(). true || sideEffect() should not execute sideEffect().
6) Nesting + Inner break/continue do not affect outer loop targets.
7) Omitted expression semantics + for (; ; i = i + 1) keeps loop running until explicit break/return from body.
This module is where deterministic jump graphs make your compiler production-real.
Module Items
Program Lexer v4