boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

commit a27e22c08bc27e3140f0cdc334e852447ab5c92e
parent bc2bb2738400cc80abd4b9a6c883679a0562e0a2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri,  1 May 2026 19:49:13 -0700

merge: brace elision in nested aggregate initializers (rebased on current main)

Diffstat:
Mcc/cc.scm | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Atests/cc/241-init-brace-elision-struct-array.c | 9+++++++++
Atests/cc/241-init-brace-elision-struct-array.expected-exit | 1+
Atests/cc/242-init-brace-elision-array-struct.c | 11+++++++++++
Atests/cc/242-init-brace-elision-array-struct.expected-exit | 1+
Atests/cc/243-init-brace-elision-global.c | 7+++++++
Atests/cc/243-init-brace-elision-global.expected-exit | 1+
7 files changed, 179 insertions(+), 37 deletions(-)

diff --git a/cc/cc.scm b/cc/cc.scm @@ -5690,15 +5690,30 @@ ;; initializers actually consumed (used by parse-init-global to resolve ;; an inferred top-level length). C99 forbids inferred length in ;; nested array elements, so recursive callers ignore `count`. +;; +;; `brace?` controls termination: when #t (the normal case), the loop +;; consumes elements until `}` is seen. When #f (brace-elision recursion +;; from C99 §6.7.8 ¶22), the loop consumes exactly `decl` elements from +;; the parent stream and returns without expecting `}`. In no-brace +;; mode, a leading `.` or `[` designator targets the enclosing aggregate +;; — the recursion terminates immediately, padding the unfilled tail. (define (%parse-init-array-list ps ty) - ;; Element-list array initializer; assumes `{` already consumed. + (%parse-init-array-list/mode ps ty #t)) + +(define (%parse-init-array-list/mode ps ty brace?) + ;; Element-list array initializer; assumes `{` already consumed when + ;; brace? is #t. (let* ((elem (%init-array-elem-type ty)) (esize (ctype-size elem)) (decl (%init-array-decl-len ty))) (let lp ((acc '()) (count 0)) (cond - ((at-punct? ps 'rbrace) - (advance ps) + ((cond (brace? (at-punct? ps 'rbrace)) + (else (or (>= count (cond ((< decl 0) 0) (else decl))) + (at-punct? ps 'rbrace) + (at-punct? ps 'dot) + (at-punct? ps 'lbrack)))) + (cond (brace? (advance ps))) ;; Pad to declared length if longer than count. (let* ((final (cond ((< decl 0) count) (else decl))) (pad (- final count))) @@ -5737,9 +5752,32 @@ (cond ((at-punct? ps 'comma) (advance ps))) (expect-punct ps 'rbrace) (list p))))) + ;; Brace elision: aggregate element with no `{`. + ;; Draw items from the SAME parent stream deep + ;; enough to fill `elem`. C99 §6.7.8 ¶22. + ((eq? (ctype-kind elem) 'arr) + (let-values (((p _c) + (%parse-init-array-list/mode ps elem #f))) + p)) + ((or (eq? (ctype-kind elem) 'struct) + (eq? (ctype-kind elem) 'union)) + (%parse-init-struct-list/mode ps elem #f)) (else (list (%const-init-piece ps elem)))))) - (cond ((at-punct? ps 'comma) (advance ps))) + ;; Inter-item comma: consume except for the comma + ;; following our LAST item in no-brace mode — that + ;; one belongs to the enclosing parent. + (cond + (brace? + (cond ((at-punct? ps 'comma) (advance ps)))) + (else + ;; no-brace: consume comma only if more items + ;; remain in our quota. + (cond + ((and (< (+ count 1) + (cond ((< decl 0) 0) (else decl))) + (at-punct? ps 'comma)) + (advance ps))))) p))))) (lp (%init-main-prepend-reversed piece acc) (+ count 1)))))))) @@ -5803,16 +5841,24 @@ (ins (cdr ys) (cons (car ys) head))))))))))) (define (%parse-init-struct-list ps ty) - ;; Struct/union initializer; assumes `{` already consumed. Supports - ;; positional and `.field = expr` forms — including out-of-order - ;; designators ({.y=5, .x=7}). Each entry records its absolute - ;; offset; %merge-init-entries sorts and pads at the closing brace. + (%parse-init-struct-list/mode ps ty #t)) + +(define (%parse-init-struct-list/mode ps ty brace?) + ;; Struct/union initializer; assumes `{` already consumed when brace?. + ;; In no-brace mode (brace elision, C99 §6.7.8 ¶22), terminate when + ;; positional fields are exhausted, when a `}` is seen (belongs to + ;; the enclosing aggregate), or when a designator (`.`) appears (it + ;; targets the enclosing aggregate). Doesn't consume the trailing + ;; comma after the last field — that belongs to the parent list. (let* ((fields (%init-struct-fields ty)) (size (ctype-size ty))) (let lp ((entries '()) (rest fields)) (cond - ((at-punct? ps 'rbrace) - (advance ps) + ((cond (brace? (at-punct? ps 'rbrace)) + (else (or (null? rest) + (at-punct? ps 'rbrace) + (at-punct? ps 'dot)))) + (cond (brace? (advance ps))) (%merge-init-entries (reverse entries) size)) (else (let* ((designated? (at-punct? ps 'dot)) @@ -5852,19 +5898,36 @@ (cond ((at-punct? ps 'comma) (advance ps))) (expect-punct ps 'rbrace) (list p))))) + ;; Brace elision for aggregate field with no `{`. + ((eq? (ctype-kind fty) 'arr) + (let-values (((p _c) + (%parse-init-array-list/mode ps fty #f))) + p)) + ((or (eq? (ctype-kind fty) 'struct) + (eq? (ctype-kind fty) 'union)) + (%parse-init-struct-list/mode ps fty #f)) (else - (list (%const-init-piece ps fty)))))) - (cond ((at-punct? ps 'comma) (advance ps))) - (lp (cons (cons foff piece-list) entries) - (cond - (designated? - ;; designated init: drop fields up to and including target - (let drop ((xs fields)) - (cond - ((null? xs) '()) - ((equal? (car (car xs)) fname) (cdr xs)) - (else (drop (cdr xs)))))) - (else (cdr rest)))))))))) + (list (%const-init-piece ps fty))))) + (rest1 + (cond + (designated? + ;; designated init: drop fields up to and including target + (let drop ((xs fields)) + (cond + ((null? xs) '()) + ((equal? (car (car xs)) fname) (cdr xs)) + (else (drop (cdr xs)))))) + (else (cdr rest))))) + ;; Inter-item comma: consume except for the comma after our + ;; LAST field in no-brace mode (belongs to enclosing list). + (cond + (brace? + (cond ((at-punct? ps 'comma) (advance ps)))) + (else + (cond ((and (not (null? rest1)) + (at-punct? ps 'comma)) + (advance ps))))) + (lp (cons (cons foff piece-list) entries) rest1))))))) ;; ----- Local aggregate initializers ------------------------------------ ;; Emits per-element store sequences via cg ops into the slot of `sm` @@ -5922,13 +5985,20 @@ (cg-push (ps-cg ps) (%opnd 'frame ty base-off #t))) (define (%parse-init-local-array-list ps sm base-off ty) + (%parse-init-local-array-list/mode ps sm base-off ty #t)) + +(define (%parse-init-local-array-list/mode ps sm base-off ty brace?) (let* ((elem (%init-array-elem-type ty)) (esize (ctype-size elem)) (decl (%init-array-decl-len ty))) (let lp ((i 0)) (cond - ((at-punct? ps 'rbrace) - (advance ps) + ((cond (brace? (at-punct? ps 'rbrace)) + (else (or (>= i (cond ((< decl 0) 0) (else decl))) + (at-punct? ps 'rbrace) + (at-punct? ps 'dot) + (at-punct? ps 'lbrack)))) + (cond (brace? (advance ps))) ;; Inferred-length auto path is pre-existing broken (slot ;; allocated off size=-1, sm-type unfixed). See note in ;; parse-init-local-aggregate STR branch. @@ -5977,12 +6047,29 @@ (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps)) (cond ((at-punct? ps 'comma) (advance ps))) (expect-punct ps 'rbrace)))) + ;; Brace elision: aggregate element with no `{`. Draw items + ;; from the SAME parent stream deep enough to fill `elem`. + ;; C99 §6.7.8 ¶22. + ((eq? (ctype-kind elem) 'arr) + (%parse-init-local-array-list/mode ps sm eoff elem #f)) + ((or (eq? (ctype-kind elem) 'struct) + (eq? (ctype-kind elem) 'union)) + (%parse-init-local-struct-list/mode ps sm eoff elem #f)) (else (%push-frame-elem-lval ps eoff elem) (parse-expr-bp ps 4) (rval! ps) (cg-cast (ps-cg ps) elem) (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps)))) - (cond ((at-punct? ps 'comma) (advance ps))) + ;; Inter-item comma: in no-brace mode, don't eat the comma + ;; that follows our LAST item (it belongs to the parent). + (cond + (brace? + (cond ((at-punct? ps 'comma) (advance ps)))) + (else + (cond ((and (< (+ i 1) + (cond ((< decl 0) 0) (else decl))) + (at-punct? ps 'comma)) + (advance ps))))) (lp (+ i 1)))))))) (define (%bv-in-list? bv xs) @@ -6028,17 +6115,27 @@ (zb (+ j 1))))))) (define (%parse-init-local-struct-list ps sm base-off ty) + (%parse-init-local-struct-list/mode ps sm base-off ty #t)) + +(define (%parse-init-local-struct-list/mode ps sm base-off ty brace?) ;; Track each initialized field by name in `seen`; at the closing brace ;; zero every field NOT in `seen`. Tracking by name (rather than ;; positional "remaining" fields) handles a designator jumping ;; backwards correctly — e.g. `{.y = 5}` must still zero `x`. ;; C requires every unmentioned member of an aggregate with at least ;; one designator/initializer to be zeroed (C11 §6.7.9 ¶21). + ;; + ;; In no-brace mode (brace elision, C99 §6.7.8 ¶22): terminate when + ;; positional fields exhausted, on `}` (parent's), or on `.` designator + ;; (targets parent). Don't consume trailing comma after our last field. (let ((fields (%init-struct-fields ty))) (let lp ((rest fields) (seen '())) (cond - ((at-punct? ps 'rbrace) - (advance ps) + ((cond (brace? (at-punct? ps 'rbrace)) + (else (or (null? rest) + (at-punct? ps 'rbrace) + (at-punct? ps 'dot)))) + (cond (brace? (advance ps))) (for-each (lambda (f) (cond ((not (%anon-touched? f seen)) @@ -6080,21 +6177,36 @@ (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps)) (cond ((at-punct? ps 'comma) (advance ps))) (expect-punct ps 'rbrace)))) + ;; Brace elision for aggregate field with no `{`. + ((eq? (ctype-kind fty) 'arr) + (%parse-init-local-array-list/mode ps sm eoff fty #f)) + ((or (eq? (ctype-kind fty) 'struct) + (eq? (ctype-kind fty) 'union)) + (%parse-init-local-struct-list/mode ps sm eoff fty #f)) (else (%push-frame-elem-lval ps eoff fty) (parse-expr-bp ps 4) (rval! ps) (cg-cast (ps-cg ps) fty) (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps)))) - (cond ((at-punct? ps 'comma) (advance ps))) - (lp (cond - (designated? - (let drop ((xs fields)) - (cond - ((null? xs) '()) - ((equal? (car (car xs)) fname) (cdr xs)) - (else (drop (cdr xs)))))) - (else (cdr rest))) - (cons fname seen)))))))) + (let ((rest1 + (cond + (designated? + (let drop ((xs fields)) + (cond + ((null? xs) '()) + ((equal? (car (car xs)) fname) (cdr xs)) + (else (drop (cdr xs)))))) + (else (cdr rest))))) + ;; Inter-item comma: in no-brace mode, don't eat the comma + ;; that follows our LAST field (belongs to enclosing list). + (cond + (brace? + (cond ((at-punct? ps 'comma) (advance ps)))) + (else + (cond ((and (not (null? rest1)) + (at-punct? ps 'comma)) + (advance ps))))) + (lp rest1 (cons fname seen))))))))) ;; parse-fn-body: bind the fn-sym for recursive lookup, then parse the diff --git a/tests/cc/241-init-brace-elision-struct-array.c b/tests/cc/241-init-brace-elision-struct-array.c @@ -0,0 +1,9 @@ +// tests/cc/241-init-brace-elision-struct-array.c — brace elision for +// nested aggregate initializers. C99 §6.7.8 ¶22: when an inner +// aggregate has no enclosing brace, items are drawn from the parent +// list deep enough to fill it. +struct S { int a; int b; }; +int main(void) { + struct S arr[2] = { 1, 2, 3, 4 }; /* no inner braces */ + return arr[0].a + arr[0].b + arr[1].a + arr[1].b; /* expect 10 */ +} diff --git a/tests/cc/241-init-brace-elision-struct-array.expected-exit b/tests/cc/241-init-brace-elision-struct-array.expected-exit @@ -0,0 +1 @@ +10 diff --git a/tests/cc/242-init-brace-elision-array-struct.c b/tests/cc/242-init-brace-elision-array-struct.c @@ -0,0 +1,11 @@ +// tests/cc/242-init-brace-elision-array-struct.c — brace elision in +// nested arrays of arrays. C99 §6.7.8 ¶22. +// +// int m[2][2] = {1,2,3,4} should fill m[0][0]=1, m[0][1]=2, +// m[1][0]=3, m[1][1]=4. Sum proves all four cells received the right +// value (1+2+3+4 = 10). +int main(void) { + int m[2][2] = { 1, 2, 3, 4 }; + int *p = (int *)m; + return *p + *(p + 1) + *(p + 2) + *(p + 3); +} diff --git a/tests/cc/242-init-brace-elision-array-struct.expected-exit b/tests/cc/242-init-brace-elision-array-struct.expected-exit @@ -0,0 +1 @@ +10 diff --git a/tests/cc/243-init-brace-elision-global.c b/tests/cc/243-init-brace-elision-global.c @@ -0,0 +1,7 @@ +// tests/cc/243-init-brace-elision-global.c — brace elision in +// file-scope (global) initializer. C99 §6.7.8 ¶22. +struct S { int a; int b; }; +struct S arr[2] = { 1, 2, 3, 4 }; /* no inner braces */ +int main(void) { + return arr[0].a + arr[0].b + arr[1].a + arr[1].b; /* expect 10 */ +} diff --git a/tests/cc/243-init-brace-elision-global.expected-exit b/tests/cc/243-init-brace-elision-global.expected-exit @@ -0,0 +1 @@ +10