on master help sign in
Spell out deeper lines, nested patterns and faced inits

Syntax review S11, S12 and S15, decided by Montelot on 2026-11-03. S11: grammar.md says which line a block of deeper lines belongs to, and that a call never continues onto a deeper line, and how an open bracket's items read on the opener's line and on deeper lines. The case lex-call-open-bad-1 parses, so it is lex-call-open-ok-3. S12: Patterns say cells of patterns nest, with rows for a pair of present units and a one-item list, and how a cell's last pattern takes the rest. The prelude keeps its nested ?-s until the checker supports nested patterns. S15: the examples build their models and the cart's state with faced cells, so a reader sees the fields without the store open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U4FzmThon6Q3x1uL8yUKEY

commit44e3324ba7fe7113908a0d026201191ce84ac363 authorClaude <noreply@anthropic.com> dateTue 3 Nov 2026, 02:27 (11 hours ago) parentd43c99b
19 files changed+170−26
docs/spec/grammar.md+9−5 docs/spec/overview.md+1−1 my-app/ui/basket.jibb+1−1 my-app/ui/stores/cart.jibb+1−1 my-app/ui/todo-list.jibb+1−1 tests/golden/doc-grammar-06-cart-store/tokens.txt+7−3 tests/golden/doc-grammar-06-cart-store/tree.txt+3−1 tests/golden/doc-grammar-07-basket/tokens.txt+14−6 tests/golden/doc-grammar-07-basket/tree.txt+5−2 tests/golden/doc-overview-10-todo-list/tokens.txt+10−4 tests/golden/doc-overview-10-todo-list/tree.txt+4−1 tests/golden/inline-pattern-nested/about.txt+3 tests/golden/inline-pattern-nested/source.jibb+9 tests/golden/inline-pattern-nested/tokens.txt+71 tests/golden/inline-pattern-nested/tree.txt+31 tests/golden/lex-call-open-bad-1/about.txt → tests/golden/lex-call-open-ok-3/about.txt tests/golden/lex-call-open-bad-1/source.jibb → tests/golden/lex-call-open-ok-3/source.jibb tests/golden/lex-call-open-bad-1/tokens.txt → tests/golden/lex-call-open-ok-3/tokens.txt tests/golden/lex-call-open-bad-1/tree.txt → tests/golden/lex-call-open-ok-3/tree.txt
docs/spec/grammar.md+9−5
@@ -6,9 +6,9 @@ The syntax the compiler accepts.
66
77## Lexical rules
88
9−- **Indentation is structure**: a deeper line continues the line above; returning to an earlier indentation closes every block since. Spaces only; blank and comment-only lines don't count.
9+- **Indentation is structure**: a block of deeper lines belongs to the line above it: to the last bracket that line leaves open at its end, to the body of the last rune or `++` on it, to `?-`'s arms, to an arm's body, or to a `+$`'s cases. A deeper line that nothing on the line above takes is an error, so a call never continues onto a deeper line. Returning to an earlier indentation closes every block since. Spaces only; blank and comment-only lines don't count.
1010- **Layout tokens**: a deeper line emits INDENT, the same depth NEWLINE, a shallower one a DEDENT per closed level, then NEWLINE.
11−- **Open brackets**: a bracket open at line end takes one item per deeper line and closes at the first line that isn't deeper, with no closer. Brackets opened and closed on one line work as usual. A mold's `[` and `(` open the same way, with one field or mold per deeper line. A line may leave several brackets open: the deeper lines go to the last one, or to the body of the last rune, and the others close with it, so `(column ~ ~[` above its items needs no `)`.
11+- **Open brackets**: a bracket open at line end takes one item per deeper line and closes at the first line that isn't deeper, with no closer. Brackets opened and closed on one line work as usual. A mold's `[` and `(` open the same way, with one field or mold per deeper line. A line may leave several brackets open: the deeper lines go to the last one, or to the body of the last rune, and the others close with it, so `(column ~ ~[` above its items needs no `)`. On the opener's line, spaces separate items, so `~[text "a"` holds two items. In an expression's open bracket each deeper line is one whole item: `text "a"` there is `(text "a")`, `label=trim " x "` is `label=(trim " x ")`, and under a `(` alone the first deeper line is the head. A mold's open bracket takes exactly one field or mold per deeper line.
1212- **Tight forms**: `a+b`, `a.b`, `` `x ``, `|=%got` and the openers `~[`, `|=(`, `$-(` take no spaces. A `(` right after a wing is a change: `m(note=text)` changes, `m (note text)` calls. `%/` starts an application path, `%/blog/(slug)`.
1313- **Spacing**: one space between items; more for alignment. That is style, which the formatter keeps: the parser takes one or more spaces between items, and allows spaces after an opener, before a closer and at the end of a line. Faces `name=value`, puns `=name` and typed path segments `[id=@ud]` take no spaces, and a rune is followed by one.
1414- **Comments**: `::` to the end of the line, outside strings; inside a string, `::` is text.
@@ -181,7 +181,7 @@ Number and cell literals resolve like tags: each waits for its context and takes
181181
182182## Patterns
183183
184−Arms are tried in order. Every case needs an arm or a catch-all, `*` or a bare name. An arm that earlier arms cover, such as a second `%add` or anything after `[%move * *]`, is an error, and a case that should fail says `!!`. A name in a pattern always binds anew, so compare values with `?- (eq a b)`. Patterns take terms, numbers, strings without `{…}`, ships, loobeans and `~`, not lists or pairs.
184+Arms are tried in order. Every case needs an arm or a catch-all, `*` or a bare name. An arm that earlier arms cover, such as a second `%add` or anything after `[%move * *]`, is an error, and a case that should fail says `!!`. A name in a pattern always binds anew, so compare values with `?- (eq a b)`. Patterns take terms, numbers, strings without `{…}`, ships, loobeans and `~`, and cells of patterns, which nest; not `~[…]` lists or `a+b` pairs.
185185
186186| Pattern | Matches | Binds |
187187| --- | --- | --- |
@@ -192,11 +192,15 @@ Arms are tried in order. Every case needs an arm or a catch-all, `*` or a bare n
192192| `[%move %left n]` | Tagged `%move`, then `%left` | `n` |
193193| `~` | Null | Nothing |
194194| `[~ x]` | A unit holding a value | `x` |
195+| `[[~ a] [~ b]]` | A pair of units that both hold a value | `a`, `b` |
196+| `[x ~]` | A list of one item | `x` |
195197| `1.000`, `"milk"`, `~zod` | That literal | Nothing |
196198| `[a b]` | Any cell; `b` is the rest | `a`, `b` |
197199| `*` | Anything | Nothing |
198200| `x` | Anything | `x` |
199201
202+Cells nest, so one arm can look into several values, or a list's first cells. A cell pattern matches from the left, and its last pattern takes the rest: over `[(unit A) (unit A)]`, `[~ b]` means "the first is null", with `b` the second unit, not "a unit holding `b`". Keep every position visible, as `[* ~ b]`.
203+
200204Tags can sit anywhere in a case, and a case can carry several:
201205
202206```jibb
@@ -298,7 +302,7 @@ knot = { lower | digit | "-" | "." | "~" | "_" } ;
298302 %toggle
299303
300304++ main (store init update)
301−++ init ^- state [~ %.n]
305+++ init ^- state [items=~ open=%.n]
302306++ update |= [a=poke s=state]
303307 ?- a
304308 [%add item] s(items=[item items.s])
@@ -320,7 +324,7 @@ knot = { lower | digit | "-" | "." | "~" | "_" } ;
320324 [%acked err=(unit @t)]
321325
322326++ init |= ~
323− [[~ %.n] ""]
327+ [cart=[items=~ open=%.n] note=""]
324328
325329++ update |= [=msg m=model]
326330 ?- msg
docs/spec/overview.md+1−1
@@ -465,7 +465,7 @@ A `page`: each request scries `%blog` and renders in Nock.
465465 [%acked err=(unit @t)]
466466
467467++ init |= ts=(list item.todo)
468− [ts "" ~]
468+ [todos=ts draft="" error=~]
469469
470470++ update |= [=msg m=model]
471471 ?- msg
my-app/ui/basket.jibb+1−1
@@ -12,7 +12,7 @@
1212 [%acked err=(unit @t)]
1313
1414++ init |= ~
15− [[~ %.n] ""]
15+ [cart=[items=~ open=%.n] note=""]
1616
1717++ update |= [=msg m=model]
1818 ?- msg
my-app/ui/stores/cart.jibb+1−1
@@ -5,7 +5,7 @@
55 %toggle
66
77++ main (store init update)
8−++ init ^- state [~ %.n]
8+++ init ^- state [items=~ open=%.n]
99++ update |= [a=poke s=state]
1010 ?- a
1111 [%add item] s(items=[item items.s])
my-app/ui/todo-list.jibb+1−1
@@ -11,7 +11,7 @@
1111 [%acked err=(unit @t)]
1212
1313++ init |= ts=(list item.todo)
14− [ts "" ~]
14+ [todos=ts draft="" error=~]
1515
1616++ update |= [=msg m=model]
1717 ?- msg
tests/golden/doc-grammar-06-cart-store/tokens.txt+7−3
@@ -38,9 +38,13 @@
38388:9 glyph ^-
39398:12 name state
40408:18 glyph [
41−8:19 tight lit ~
42−8:21 lit %.n
43−8:24 tight glyph ]
41+8:19 tight name items
42+8:24 tight glyph =
43+8:25 tight lit ~
44+8:27 name open
45+8:31 tight glyph =
46+8:32 tight lit %.n
47+8:35 tight glyph ]
44489:1 NEWLINE
45499:1 glyph ++
46509:4 name update
tests/golden/doc-grammar-06-cart-store/tree.txt+3−1
@@ -16,7 +16,9 @@
1616 (value 8:1 init
1717 (cast 8:9
1818 (ref 8:12 state)
19− (tuple 8:18 (lit 8:19 ~) (lit 8:21 %.n))))
19+ (tuple 8:18
20+ (face 8:19 items (lit 8:25 ~))
21+ (face 8:27 open (lit 8:32 %.n)))))
2022 (value 9:1 update
2123 (gate 9:11
2224 (cell 9:14
tests/golden/doc-grammar-07-basket/tokens.txt+14−6
@@ -66,12 +66,20 @@
666614:12 lit ~
676715:3 INDENT
686815:3 glyph [
69−15:4 tight glyph [
70−15:5 tight lit ~
71−15:7 lit %.n
72−15:10 tight glyph ]
73−15:12 str ""
74−15:14 tight glyph ]
69+15:4 tight name cart
70+15:8 tight glyph =
71+15:9 tight glyph [
72+15:10 tight name items
73+15:15 tight glyph =
74+15:16 tight lit ~
75+15:18 name open
76+15:22 tight glyph =
77+15:23 tight lit %.n
78+15:26 tight glyph ]
79+15:28 name note
80+15:32 tight glyph =
81+15:33 tight str ""
82+15:35 tight glyph ]
758317:1 DEDENT
768417:1 NEWLINE
778517:1 glyph ++
tests/golden/doc-grammar-07-basket/tree.txt+5−2
@@ -26,8 +26,11 @@
2626 (const 14:12 ~)
2727 (body 15:3
2828 (tuple 15:3
29− (tuple 15:4 (lit 15:5 ~) (lit 15:7 %.n))
30− (str 15:12 "")))))
29+ (face 15:4 cart
30+ (tuple 15:9
31+ (face 15:10 items (lit 15:16 ~))
32+ (face 15:18 open (lit 15:23 %.n))))
33+ (face 15:28 note (str 15:33 ""))))))
3134 (value 17:1 update
3235 (gate 17:11
3336 (cell 17:14
tests/golden/doc-overview-10-todo-list/tokens.txt+10−4
@@ -88,10 +88,16 @@
888813:30 tight glyph )
898914:3 INDENT
909014:3 glyph [
91−14:4 tight name ts
92−14:7 str ""
93−14:10 lit ~
94−14:11 tight glyph ]
91+14:4 tight name todos
92+14:9 tight glyph =
93+14:10 tight name ts
94+14:13 name draft
95+14:18 tight glyph =
96+14:19 tight str ""
97+14:22 name error
98+14:27 tight glyph =
99+14:28 tight lit ~
100+14:29 tight glyph ]
9510116:1 DEDENT
9610216:1 NEWLINE
9710316:1 glyph ++
tests/golden/doc-overview-10-todo-list/tree.txt+4−1
@@ -32,7 +32,10 @@
3232 (face 13:12 ts
3333 (apply 13:15 list (ref 13:21 item.todo)))
3434 (body 14:3
35− (tuple 14:3 (wing 14:4 ts) (str 14:7 "") (lit 14:10 ~)))))
35+ (tuple 14:3
36+ (face 14:4 todos (wing 14:10 ts))
37+ (face 14:13 draft (str 14:19 ""))
38+ (face 14:22 error (lit 14:28 ~))))))
3639 (value 16:1 update
3740 (gate 16:11
3841 (cell 16:14
tests/golden/inline-pattern-nested/about.txtnew file+3
@@ -0,0 +1,3 @@
1+source: Grammar tab, Patterns: "Patterns take terms, numbers, strings without `{…}`, ships, loobeans and `~`, and cells of patterns, which nest; not `~[…]` lists or `a+b` pairs." and the rows `[[~ a] [~ b]]`, a pair of units that both hold a value, and `[x ~]`, a list of one item
2+tests: cell patterns nested inside cell patterns, and a positional cell whose last pattern takes the rest, as `[* ~ b]`
3+expect: parses: `[[~ a] [~ b]]` is a cell of two cell patterns, `[* ~ b]` is `[* [~ b]]`, and `[x ~]` is a cell of a name and null
tests/golden/inline-pattern-nested/source.jibbnew file+9
@@ -0,0 +1,9 @@
1+++ both |= args=[(unit @ud) (unit @ud)]
2+ ?- args
3+ [[~ a] [~ b]] (add a b)
4+ [* ~ b] b
5+ * 0
6+++ only |= xs=(list @ud)
7+ ?- xs
8+ [x ~] x
9+ * 0
tests/golden/inline-pattern-nested/tokens.txtnew file+71
@@ -0,0 +1,71 @@
1+1:1 glyph ++
2+1:4 name both
3+1:9 glyph |=
4+1:12 name args
5+1:16 tight glyph =
6+1:17 tight glyph [
7+1:18 tight glyph (
8+1:19 tight name unit
9+1:24 aura @ud
10+1:27 tight glyph )
11+1:29 glyph (
12+1:30 tight name unit
13+1:35 aura @ud
14+1:38 tight glyph )
15+1:39 tight glyph ]
16+2:3 INDENT
17+2:3 glyph ?-
18+2:6 name args
19+3:5 INDENT
20+3:5 glyph [
21+3:6 tight glyph [
22+3:7 tight lit ~
23+3:9 name a
24+3:10 tight glyph ]
25+3:12 glyph [
26+3:13 tight lit ~
27+3:15 name b
28+3:16 tight glyph ]
29+3:17 tight glyph ]
30+3:19 glyph (
31+3:20 tight name add
32+3:24 name a
33+3:26 name b
34+3:27 tight glyph )
35+4:5 NEWLINE
36+4:5 glyph [
37+4:6 tight glyph *
38+4:8 lit ~
39+4:10 name b
40+4:11 tight glyph ]
41+4:13 name b
42+5:5 NEWLINE
43+5:5 glyph *
44+5:7 lit 0
45+6:1 DEDENT
46+6:1 DEDENT
47+6:1 NEWLINE
48+6:1 glyph ++
49+6:4 name only
50+6:9 glyph |=
51+6:12 name xs
52+6:14 tight glyph =
53+6:15 tight glyph (
54+6:16 tight name list
55+6:21 aura @ud
56+6:24 tight glyph )
57+7:3 INDENT
58+7:3 glyph ?-
59+7:6 name xs
60+8:5 INDENT
61+8:5 glyph [
62+8:6 tight name x
63+8:8 lit ~
64+8:9 tight glyph ]
65+8:11 name x
66+9:5 NEWLINE
67+9:5 glyph *
68+9:7 lit 0
69+9:8 DEDENT
70+9:8 DEDENT
71+9:8 NEWLINE
tests/golden/inline-pattern-nested/tree.txtnew file+31
@@ -0,0 +1,31 @@
1+(module
2+ (value 1:1 both
3+ (gate 1:9
4+ (face 1:12 args
5+ (cell 1:17
6+ (apply 1:18 unit (aura 1:24 @ud))
7+ (apply 1:29 unit (aura 1:35 @ud))))
8+ (body 2:3
9+ (branch 2:3
10+ (wing 2:6 args)
11+ (arm 3:5
12+ (cell 3:5
13+ (cell 3:6 (lit 3:7 ~) (bind 3:9 a))
14+ (cell 3:12 (lit 3:13 ~) (bind 3:15 b)))
15+ (group 3:19
16+ (call 3:20 (wing 3:20 add) (wing 3:24 a) (wing 3:26 b))))
17+ (arm 4:5
18+ (cell 4:5 (any 4:6) (lit 4:8 ~) (bind 4:10 b))
19+ (wing 4:13 b))
20+ (arm 5:5 (any 5:5) (lit 5:7 0))))))
21+ (value 6:1 only
22+ (gate 6:9
23+ (face 6:12 xs
24+ (apply 6:15 list (aura 6:21 @ud)))
25+ (body 7:3
26+ (branch 7:3
27+ (wing 7:6 xs)
28+ (arm 8:5
29+ (cell 8:5 (bind 8:6 x) (lit 8:8 ~))
30+ (wing 8:11 x))
31+ (arm 9:5 (any 9:5) (lit 9:7 0)))))))
tests/golden/lex-call-open-bad-1/about.txt → tests/golden/lex-call-open-ok-3/about.txtrenamed

The content is the same.

tests/golden/lex-call-open-bad-1/source.jibb → tests/golden/lex-call-open-ok-3/source.jibbrenamed

The content is the same.

tests/golden/lex-call-open-bad-1/tokens.txt → tests/golden/lex-call-open-ok-3/tokens.txtrenamed

The content is the same.

tests/golden/lex-call-open-bad-1/tree.txt → tests/golden/lex-call-open-ok-3/tree.txtrenamed

The content is the same.