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:
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