commit 5947945ef1b00beb0744635095b6e1a6259a6a2a
parent 0fd7206cab421b0c14cad970c764ebe3514e46c9
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 1 May 2026 17:31:17 -0700
cc/parse: support brace elision in nested aggregate initializers
The aggregate-init parsers (%parse-init-array-list and
%parse-init-local-array-list) only handled nested aggregates when the
inner braces were explicit. Without them, e.g.
`struct S arr[2] = {1,2,3,4}` or `int a[2][2] = {1,2,3,4}`, the loop
fell into the scalar branch and treated each integer as the entire
aggregate slot — initializing only the first element of each
sub-aggregate and leaving the rest at whatever zero-fill happened to
land. C99 §6.7.8 ¶22 requires the contents of an aggregate without
its own braces to be drawn from the parent list, deep enough to
fill it.
Add %parse-init-{array,struct}-noBrace and their local-scope
counterparts that consume one aggregate's worth of children without
expecting an enclosing `{`/`}`. Apply them in both the global and
local array/struct list element loops when the elem type is an
aggregate and the next token is not `{`.
Test: 241-init-brace-elision-struct-array
Diffstat:
3 files changed, 218 insertions(+), 0 deletions(-)
diff --git a/cc/cc.scm b/cc/cc.scm
@@ -5692,12 +5692,107 @@
(cond ((at-punct? ps 'comma) (advance ps)))
(expect-punct ps 'rbrace)
(list p)))))
+ ;; Brace elision: when elem is itself an
+ ;; aggregate and no inner `{` was given,
+ ;; consume that aggregate's worth of
+ ;; child initializers from the parent list
+ ;; (C99 §6.7.8 ¶22). Otherwise, treat the
+ ;; next expression as a scalar piece.
+ ((eq? (ctype-kind elem) 'arr)
+ (%parse-init-array-noBrace ps elem))
+ ((or (eq? (ctype-kind elem) 'struct)
+ (eq? (ctype-kind elem) 'union))
+ (%parse-init-struct-noBrace ps elem))
(else
(list (%const-init-piece ps elem))))))
(cond ((at-punct? ps 'comma) (advance ps)))
p)))))
(lp (%init-main-prepend-reversed piece acc) (+ count 1))))))))
+;; Brace-elision helpers: consume one aggregate's children without
+;; expecting an enclosing `{` `}`. Used when an aggregate-typed element
+;; appears in an outer list without inner braces, e.g.
+;; `struct S a[2] = {1,2,3,4}`.
+(define (%parse-init-array-noBrace ps ty)
+ (let* ((elem (%init-array-elem-type ty))
+ (esize (ctype-size elem))
+ (decl (%init-array-decl-len ty))
+ (final (cond ((< decl 0) 0) (else decl))))
+ (let lp ((acc '()) (count 0))
+ (cond
+ ((>= count final)
+ ;; Pad to declared length if shorter (count==final-no-pad).
+ (reverse acc))
+ ;; Stop early if we hit `}` of an enclosing list.
+ ((at-punct? ps 'rbrace) (reverse acc))
+ (else
+ (let ((p (cond
+ ((at-punct? ps 'lbrace)
+ (advance ps)
+ (cond
+ ((eq? (ctype-kind elem) 'arr)
+ (let-values (((p _c)
+ (%parse-init-array-list ps elem))) p))
+ ((or (eq? (ctype-kind elem) 'struct)
+ (eq? (ctype-kind elem) 'union))
+ (%parse-init-struct-list ps elem))
+ (else
+ (let ((p (%const-init-piece ps elem)))
+ (cond ((at-punct? ps 'comma) (advance ps)))
+ (expect-punct ps 'rbrace)
+ (list p)))))
+ ((eq? (ctype-kind elem) 'arr)
+ (%parse-init-array-noBrace ps elem))
+ ((or (eq? (ctype-kind elem) 'struct)
+ (eq? (ctype-kind elem) 'union))
+ (%parse-init-struct-noBrace ps elem))
+ (else (list (%const-init-piece ps elem))))))
+ (cond ((at-punct? ps 'comma) (advance ps)))
+ (lp (append (reverse p) acc) (+ count 1))))))))
+
+(define (%parse-init-struct-noBrace ps ty)
+ ;; Brace-elided struct: consume positional initializers for each
+ ;; field in declaration order, returning a flat piece list (with
+ ;; offset-merge so trailing fields get padded).
+ (let* ((fields (%init-struct-fields ty))
+ (size (ctype-size ty)))
+ (let lp ((entries '()) (rest fields))
+ (cond
+ ((null? rest)
+ (%merge-init-entries (reverse entries) size))
+ ((at-punct? ps 'rbrace)
+ (%merge-init-entries (reverse entries) size))
+ (else
+ (let* ((target (car rest))
+ (fname (car target))
+ (fty (car (cdr target)))
+ (foff (car (cddr target)))
+ (piece-list
+ (cond
+ ((at-punct? ps 'lbrace)
+ (advance ps)
+ (cond
+ ((eq? (ctype-kind fty) 'arr)
+ (let-values (((p _c)
+ (%parse-init-array-list ps fty))) p))
+ ((or (eq? (ctype-kind fty) 'struct)
+ (eq? (ctype-kind fty) 'union))
+ (%parse-init-struct-list ps fty))
+ (else
+ (let ((p (%const-init-piece ps fty)))
+ (cond ((at-punct? ps 'comma) (advance ps)))
+ (expect-punct ps 'rbrace)
+ (list p)))))
+ ((eq? (ctype-kind fty) 'arr)
+ (%parse-init-array-noBrace ps fty))
+ ((or (eq? (ctype-kind fty) 'struct)
+ (eq? (ctype-kind fty) 'union))
+ (%parse-init-struct-noBrace ps fty))
+ (else
+ (list (%const-init-piece ps fty))))))
+ (cond ((at-punct? ps 'comma) (advance ps)))
+ (lp (cons (cons foff piece-list) entries) (cdr rest))))))))
+
(define (%piece-bytesize p)
;; Output width of one piece (cf. %cg-init-piece->bv): a bv emits
;; one byte per element; a (label-ref . _) emits an 8-byte slot.
@@ -5931,6 +6026,15 @@
(cg-assign (ps-cg ps)) (cg-pop (ps-cg ps))
(cond ((at-punct? ps 'comma) (advance ps)))
(expect-punct ps 'rbrace))))
+ ;; Brace elision: aggregate elem without inner `{` —
+ ;; consume that aggregate's worth of children directly
+ ;; (C99 §6.7.8 ¶22). Without this, `int a[2][2] = {1,2,3,4}`
+ ;; would treat each integer as an entire `int[2]`.
+ ((eq? (ctype-kind elem) 'arr)
+ (%parse-init-local-array-noBrace ps sm eoff elem))
+ ((or (eq? (ctype-kind elem) 'struct)
+ (eq? (ctype-kind elem) 'union))
+ (%parse-init-local-struct-noBrace ps sm eoff elem))
(else
(%push-frame-elem-lval ps eoff elem)
(parse-expr-bp ps 4) (rval! ps)
@@ -5939,6 +6043,113 @@
(cond ((at-punct? ps 'comma) (advance ps)))
(lp (+ i 1))))))))
+(define (%parse-init-local-array-noBrace ps sm base-off ty)
+ ;; Like %parse-init-local-array-list but consumes a fixed number of
+ ;; children (= declared length) without expecting an enclosing `}`.
+ ;; Pads remaining slots with zero. Caller is the parent list whose
+ ;; `}` (or further siblings) follow.
+ (let* ((elem (%init-array-elem-type ty))
+ (esize (ctype-size elem))
+ (decl (%init-array-decl-len ty))
+ (final (cond ((< decl 0) 0) (else decl))))
+ (let lp ((i 0))
+ (cond
+ ((>= i final) #t)
+ ((at-punct? ps 'rbrace)
+ ;; Hit parent `}` early; zero-fill remaining slots.
+ (let zlp ((k i))
+ (cond
+ ((>= k final) #t)
+ (else
+ (let ((off (+ base-off (* k esize))))
+ (let zb ((j 0))
+ (cond
+ ((>= j esize) #t)
+ (else
+ (%push-frame-elem-lval ps (+ off j) %t-u8)
+ (cg-push-imm (ps-cg ps) %t-u8 0)
+ (cg-assign (ps-cg ps))
+ (cg-pop (ps-cg ps))
+ (zb (+ j 1)))))
+ (zlp (+ k 1)))))))
+ (else
+ (let ((eoff (+ base-off (* i esize))))
+ (cond
+ ((at-punct? ps 'lbrace)
+ (advance ps)
+ (cond
+ ((eq? (ctype-kind elem) 'arr)
+ (%parse-init-local-array-list ps sm eoff elem))
+ ((or (eq? (ctype-kind elem) 'struct)
+ (eq? (ctype-kind elem) 'union))
+ (%parse-init-local-struct-list ps sm eoff elem))
+ (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)))
+ (expect-punct ps 'rbrace))))
+ ((eq? (ctype-kind elem) 'arr)
+ (%parse-init-local-array-noBrace ps sm eoff elem))
+ ((or (eq? (ctype-kind elem) 'struct)
+ (eq? (ctype-kind elem) 'union))
+ (%parse-init-local-struct-noBrace ps sm eoff elem))
+ (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)))
+ (lp (+ i 1))))))))
+
+(define (%parse-init-local-struct-noBrace ps sm base-off ty)
+ ;; Brace-elided struct in auto context: positional inits for each
+ ;; field; pad unmentioned tail fields with zero.
+ (let ((fields (%init-struct-fields ty)))
+ (let lp ((rest fields) (seen '()))
+ (cond
+ ((or (null? rest) (at-punct? ps 'rbrace))
+ (for-each
+ (lambda (f)
+ (cond ((not (%anon-touched? f seen))
+ (%emit-zero-field ps base-off f))))
+ fields))
+ (else
+ (let* ((target (car rest))
+ (fname (car target))
+ (fty (car (cdr target)))
+ (foff (car (cddr target)))
+ (eoff (+ base-off foff)))
+ (cond
+ ((at-punct? ps 'lbrace)
+ (advance ps)
+ (cond
+ ((eq? (ctype-kind fty) 'arr)
+ (%parse-init-local-array-list ps sm eoff fty))
+ ((or (eq? (ctype-kind fty) 'struct)
+ (eq? (ctype-kind fty) 'union))
+ (%parse-init-local-struct-list ps sm eoff fty))
+ (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)))
+ (expect-punct ps 'rbrace))))
+ ((eq? (ctype-kind fty) 'arr)
+ (%parse-init-local-array-noBrace ps sm eoff fty))
+ ((or (eq? (ctype-kind fty) 'struct)
+ (eq? (ctype-kind fty) 'union))
+ (%parse-init-local-struct-noBrace ps sm eoff fty))
+ (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 (cdr rest) (cons fname seen))))))))
+
(define (%bv-in-list? bv xs)
(cond ((null? xs) #f)
((equal? bv (car xs)) #t)
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,6 @@
+/* Brace elision in nested aggregate init */
+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; /* 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