Skip to content

Commit 0748cd9

Browse files
committed
feat(bootstrap): Stage 6 — 4-slot environment, 4-deep let nesting
- Expand env from 2 slots (e0,e1) to 4 (e0..e3) supporting 4 nested lets - Recursive env_lookup searches most-recent slot first for correct shadowing - Closures still capture 1 binding (most recent) with existing 32-bit encoding - New tests: 3-deep (a+b+c=6) and 4-deep (a+b+c+d=10) let nesting - Update README to Stage 6
1 parent 272e8ae commit 0748cd9

2 files changed

Lines changed: 51 additions & 37 deletions

File tree

bootstrap/README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Nulang Self-Hosting Bootstrap
22

3-
> **Status:** Stage 5 — closures with environment capture working.
3+
> **Status:** Stage 64-slot environment, closures with 1-capture.
44
> **Target:** A Nulang→Nulang compiler written in Nulang Core (RFC 0002)
55
> that targets the `.nbc` format (RFC 0001).
66
@@ -26,7 +26,7 @@ source.nula
2626

2727
```bash
2828
nulang bootstrap/compiler_core.nula
29-
# Expected: 42, 7, 9, 43, 200, 6, 36, 11, 8, 7
29+
# Expected: 42, 7, 9, 43, 200, 6, 36, 11, 8, 7, 6, 10
3030
```
3131

3232
## What's implemented
@@ -44,6 +44,12 @@ nulang bootstrap/compiler_core.nula
4444
- **Closure encoding:** 30-bit flag `1 << 30` in the high word of the 32-bit value; low 16 bits are the source position. Packed fields: flag | (ph << 23) | (body_start << 16) | (cap_hash << 8) | cap_value.
4545
- **Out-of-band sentinel:** `left == 1 << 40` replaces `left == 0` to distinguish "no left operand" from the valid expression result 0.
4646

47+
### Stage 6 — 4-slot environment (2026-07-28)
48+
- **Environment:** expanded from 2 slots (e0, e1) to 4 slots (e0..e3), supporting up to 4 nested `let` bindings.
49+
- **Lookup:** recursive `env_lookup` searches most-recent slot first for correct shadowing.
50+
- **Closures:** still capture 1 binding (most recent), encoded as before in bits 8-15 of the closure tag.
51+
- **New tests:** `let a=1 in let b=2 in let c=3 in a+b+c` → 6, 4-deep → 10.
52+
4753
### Register spilling (2026-07-24)
4854
- Inline spilling (commit 06b03c6): no capacity limit.
4955
- Round-robin temp registers (commit db22c67): prevents clobbering in multi-operand spilled reads.
@@ -53,4 +59,4 @@ nulang bootstrap/compiler_core.nula
5359
- HM type inference
5460
- MIR lowering → `.nbc` codec
5561
- Self-compilation (`compiler_core.nula``compiler_core.nbc`)
56-
- Multi-binding environment capture (currently limited to 1 captured binding)
62+
- Multi-binding closure capture (closures still limited to 1 captured variable)

bootstrap/compiler_core.nula

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// bootstrap/compiler_core.nula — Stage 5: closures with env capture
1+
// bootstrap/compiler_core.nula — Stage 6: 4-slot env, 1-capture closures
22
// Return: (value << 16) | pos. Entry: (hash << 32) + value.
33
// Closure encoding (32-bit): flag(bit30) | (ph<<23) | (bs<<16) | (cap_h<<8) | cap_v
44
// flag = 1 << 30 = 1073741824. Position is clean source pos.
5+
// Env: e0..e3 with elen 0..4. let pushes to next slot. Closures capture most recent.
56

67
fn is_digit(c: Int) -> Bool { c >= 48 and c <= 57 }
78
fn is_alpha(c: Int) -> Bool { (c >= 65 and c <= 90) or (c >= 97 and c <= 122) }
@@ -34,23 +35,18 @@ fn read_ident(src: String, pos: Int, len: Int, h: Int) -> Int {
3435
}
3536
}
3637

37-
fn env_lookup(e0: Int, e1: Int, elen: Int, h: Int) -> Int {
38+
// 4-slot environment lookup: search e3, e2, e1, e0 (most-recent first).
39+
fn env_lookup(e0: Int, e1: Int, e2: Int, e3: Int, elen: Int, h: Int) -> Int {
3840
if elen <= 0 then 0
39-
else if elen >= 2 then {
40-
let s1 = e1;
41-
let h1 = s1 >> 32;
42-
if h1 == h then s1 & 0xFFFFFFFF
43-
else {
44-
let s0 = e0;
45-
let h0 = s0 >> 32;
46-
if h0 == h then s0 & 0xFFFFFFFF
47-
else 0
48-
}
49-
} else {
50-
let s0 = e0;
51-
let h0 = s0 >> 32;
52-
if h0 == h then s0 & 0xFFFFFFFF
53-
else 0
41+
else {
42+
// Check most recent slot first
43+
let slot = if elen >= 4 then e3
44+
else if elen >= 3 then e2
45+
else if elen >= 2 then e1
46+
else e0;
47+
let sh = slot >> 32;
48+
if sh == h then slot & 0xFFFFFFFF
49+
else env_lookup(e0, e1, e2, 0, elen - 1, h)
5450
}
5551
}
5652

@@ -60,7 +56,7 @@ fn prec_of(c: Int) -> Int {
6056
else 0
6157
}
6258

63-
fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: Int, e1: Int, elen: Int) -> Int {
59+
fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: Int, e1: Int, e2: Int, e3: Int, elen: Int) -> Int {
6460
let no_left = 1 << 40;
6561
let pair = if left == no_left then {
6662
let p = skip_ws(src, pos, len);
@@ -69,7 +65,7 @@ fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: In
6965
let c = perform String.charAt(src, p);
7066
if is_digit(c) then read_int(src, p, len, 0)
7167
else if c == 40 then {
72-
let inner = parse_pratt(src, p + 1, len, no_left, 0, e0, e1, elen);
68+
let inner = parse_pratt(src, p + 1, len, no_left, 0, e0, e1, e2, e3, elen);
7369
let v = inner >> 16;
7470
let q = inner & 0xFFFF;
7571
let q2 = skip_ws(src, q, len);
@@ -83,6 +79,7 @@ fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: In
8379
let h = ident >> 16;
8480
let q = ident & 0xFFFF;
8581
if h == 3321 then {
82+
// "let" keyword
8683
let q2 = skip_ws(src, q, len);
8784
let var_id = read_ident(src, q2, len, 0);
8885
let vh = var_id >> 16;
@@ -91,7 +88,7 @@ fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: In
9188
if q4 < len then {
9289
let c3 = perform String.charAt(src, q4);
9390
if c3 == 61 then {
94-
let bound = parse_pratt(src, q4 + 1, len, no_left, 0, e0, e1, elen);
91+
let bound = parse_pratt(src, q4 + 1, len, no_left, 0, e0, e1, e2, e3, elen);
9592
let bv = bound >> 16;
9693
let bp = bound & 0xFFFF;
9794
let bp2 = skip_ws(src, bp, len);
@@ -101,21 +98,26 @@ fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: In
10198
if ci == 105 and cn == 110 then {
10299
let entry = (vh << 32) + bv;
103100
if elen == 0 then {
104-
parse_pratt(src, bp2 + 2, len, no_left, 0, entry, e1, 1)
101+
parse_pratt(src, bp2 + 2, len, no_left, 0, entry, 0, 0, 0, 1)
102+
} else if elen == 1 then {
103+
parse_pratt(src, bp2 + 2, len, no_left, 0, e0, entry, 0, 0, 2)
104+
} else if elen == 2 then {
105+
parse_pratt(src, bp2 + 2, len, no_left, 0, e0, e1, entry, 0, 3)
105106
} else {
106-
parse_pratt(src, bp2 + 2, len, no_left, 0, e0, entry, 2)
107+
parse_pratt(src, bp2 + 2, len, no_left, 0, e0, e1, e2, entry, 4)
107108
}
108109
} else (bv << 16) + bp2
109110
} else (bv << 16) + bp2
110111
} else {
111-
let v = env_lookup(e0, e1, elen, h);
112+
let v = env_lookup(e0, e1, e2, e3, elen, h);
112113
(v << 16) + q
113114
}
114115
} else {
115-
let v = env_lookup(e0, e1, elen, h);
116+
let v = env_lookup(e0, e1, e2, e3, elen, h);
116117
(v << 16) + q
117118
}
118119
} else if h == 620 then {
120+
// "fn" keyword
119121
let q2 = skip_ws(src, q, len);
120122
if q2 < len then {
121123
let c1 = perform String.charAt(src, q2);
@@ -134,12 +136,15 @@ fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: In
134136
let c4 = perform String.charAt(src, q6 + 1);
135137
if c3 == 61 and c4 == 62 then {
136138
let body_start = q6 + 2;
137-
let body_parsed = parse_pratt(src, body_start, len, no_left, 0, e0, e1, elen);
139+
let body_parsed = parse_pratt(src, body_start, len, no_left, 0, e0, e1, e2, e3, elen);
138140
let body_end = body_parsed & 0xFFFF;
139141
let flag = 1 << 30;
140142
let enc = flag + (ph << 23) + (((body_start & 0x7F) << 16));
141143
let cap_data = if elen > 0 then {
142-
let cap_e = if elen >= 2 then e1 else e0;
144+
let cap_e = if elen >= 4 then e3
145+
else if elen >= 3 then e2
146+
else if elen >= 2 then e1
147+
else e0;
143148
let cap_h = (cap_e >> 32) & 0xFF;
144149
let cap_v = cap_e & 0xFFFF;
145150
(cap_h << 16) + cap_v
@@ -155,7 +160,7 @@ fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: In
155160
} else (0 << 16) + p
156161
} else (0 << 16) + p
157162
} else {
158-
let v = env_lookup(e0, e1, elen, h);
163+
let v = env_lookup(e0, e1, e2, e3, elen, h);
159164
(v << 16) + q
160165
}
161166
} else (0 << 16) + p
@@ -171,7 +176,7 @@ fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: In
171176
else {
172177
let c = perform String.charAt(src, p);
173178
if c == 40 then {
174-
let arg = parse_pratt(src, p + 1, len, no_left, 0, e0, e1, elen);
179+
let arg = parse_pratt(src, p + 1, len, no_left, 0, e0, e1, e2, e3, elen);
175180
let av = arg >> 16;
176181
let ap = arg & 0xFFFF;
177182
let ap2 = skip_ws(src, ap, len);
@@ -185,23 +190,23 @@ fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: In
185190
let cap_v = lv & 0xFF;
186191
let entry_param = (ph << 32) + av;
187192
if cap_h == 0 then {
188-
parse_pratt(src, bs, len, no_left, 0, entry_param, 0, 1)
193+
parse_pratt(src, bs, len, no_left, 0, entry_param, 0, 0, 0, 1)
189194
} else {
190195
let entry_cap = (cap_h << 32) + cap_v;
191-
parse_pratt(src, bs, len, no_left, 0, entry_cap, entry_param, 2)
196+
parse_pratt(src, bs, len, no_left, 0, entry_cap, entry_param, 0, 0, 2)
192197
}
193198
} else {
194199
(lv << 16) + ap2
195200
};
196201
let rv = result >> 16;
197-
parse_pratt(src, ap2 + 1, len, rv, min_prec, e0, e1, elen)
202+
parse_pratt(src, ap2 + 1, len, rv, min_prec, e0, e1, e2, e3, elen)
198203
} else pair
199204
} else pair
200205
} else {
201206
let prec = prec_of(c);
202207
if prec == 0 or prec < min_prec then pair
203208
else {
204-
let rhs = parse_pratt(src, p + 1, len, no_left, prec, e0, e1, elen);
209+
let rhs = parse_pratt(src, p + 1, len, no_left, prec, e0, e1, e2, e3, elen);
205210
let rv = rhs >> 16;
206211
let rp = rhs & 0xFFFF;
207212
let result = if c == 43 then lv + rv
@@ -210,15 +215,15 @@ fn parse_pratt(src: String, pos: Int, len: Int, left: Int, min_prec: Int, e0: In
210215
else if c == 47 then {
211216
if rv == 0 then lv else lv / rv
212217
} else lv;
213-
parse_pratt(src, rp, len, result, min_prec, e0, e1, elen)
218+
parse_pratt(src, rp, len, result, min_prec, e0, e1, e2, e3, elen)
214219
}
215220
}
216221
}
217222
}
218223

219224
fn parse(src: String) -> Int {
220225
let len = perform String.length(src);
221-
let result = parse_pratt(src, 0, len, 1 << 40, 0, 0, 0, 0);
226+
let result = parse_pratt(src, 0, len, 1 << 40, 0, 0, 0, 0, 0, 0);
222227
result >> 16
223228
}
224229

@@ -233,4 +238,7 @@ fn main() {
233238
perform IO.print(parse("let f = fn(x) => x + 1 in f(10)"))
234239
perform IO.print(parse("let a = 3 in (fn(x) => a + x)(5)"))
235240
perform IO.print(parse("let add = fn(a) => fn(b) => a + b in add(3)(4)"))
241+
// New tests: 3- and 4-deep let bindings
242+
perform IO.print(parse("let a = 1 in let b = 2 in let c = 3 in a + b + c"))
243+
perform IO.print(parse("let a = 1 in let b = 2 in let c = 3 in let d = 4 in a + b + c + d"))
236244
}

0 commit comments

Comments
 (0)