Repository navigation
Commit 04daa83
committed
compiler: implement function declaration hoisting
Hoist top-level function declarations to make them available before their
source position, enabling forward references and mutual recursion without
requiring specific declaration order.
The implementation uses three compilation phases within each scope:
- Phase 1 saves parser state, scans tokens to collect names of top-level
function declarations, let/const variable declarations, and import * as
locals, skipping over initializer expressions via depth tracking, and
restores parser state.
- After phase 1, a null-initialized local slot is declared for each
collected name (functions, variables, and import locals alike,
preserving constness).
- Phase 2 saves parser state, re-scans to find function declarations,
compiles each function body into the pre-declared slot, and restores
parser state. Both scanning phases share a single top-level statement
scanner (uc_compiler_scan_toplevel_stmts) that handles brace/alt-block
depth tracking and statement boundary detection, with a mode parameter
controlling whether to collect names or compile function bodies.
- Phase 3 compiles the scope body normally, skipping function
declarations that were already compiled in phase 2. Forward references
from hoisted function bodies resolve correctly because all local slots
exist as upvalue targets during phase 2 function compilation.
Pre-declaring variable and import names alongside function names follows
ECMAScript semantics: all bindings are created at scope entry before any
code runs. Without this, a hoisted function referencing a let/const
variable or import * as local declared earlier in source order would fail
to resolve it as an upvalue during phase 2.
Hoisting applies only to declarations that are direct statements of the
scope body. Functions inside control flow or braced blocks are not
hoisted. Export declarations reuse compile_declexpr with an export flag
to register each variable immediately after declaration.
For import * as declarations with pre-declared slots, the import code
skips initialize_local and instead stores the module object into the
existing slot via I_SLOC.
Parser state save/restore preserves the error buffer alongside other
state, preventing duplicate error messages when phase 1/2 scanning
encounters syntax errors that phase 3 reports again. The last lineinfo
byte is also preserved to prevent corruption from uc_source_line_update()
modifying the tail entry during scanning.
Signed-off-by: Felix Fietkau <nbd@nbd.name>1 parent d810e57 commit 04daa83
5 files changed
Lines changed: 909 additions & 19 deletions
0 commit comments