brink-syntax-native 0.0.11

Lexer and error-resilient CST for the .brink native surface (B0.5 grammar skeleton)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
//! Typed AST node wrappers for every node kind in the native CST.
//!
//! Every struct is a zero-cost newtype generated by [`ast_node!`]. A
//! representative subset has hand-written accessors below its
//! `ast_node!` line — enough to prove the pattern end-to-end for B0.6+
//! without pre-building every accessor a later lowering pass might want
//! (that's additive, not a re-architecture, when it's actually needed).

use crate::SyntaxKind::{self, DOC_COMMENT_INNER, DOC_COMMENT_OUTER, IDENT, L_PAREN};
use crate::ast::ast_node;
use crate::ast::support;
use crate::{SyntaxNode, SyntaxToken};

// ── Doc comments (B0.6b) ──────────────────────────────────────────────

ast_node!(DocComment, DOC_COMMENT);

// ── Top level & declarations ────────────────────────────────────────

ast_node!(SourceFile, SOURCE_FILE);
ast_node!(FlowDecl, FLOW_DECL);
ast_node!(FnDecl, FN_DECL);
ast_node!(ParamList, PARAM_LIST);
ast_node!(Param, PARAM);
ast_node!(VarDecl, VAR_DECL);
ast_node!(ConstDecl, CONST_DECL);
ast_node!(FlagsDecl, FLAGS_DECL);
ast_node!(FlagsMemberList, FLAGS_MEMBER_LIST);
ast_node!(FlagsMember, FLAGS_MEMBER);
ast_node!(StructDecl, STRUCT_DECL);
ast_node!(StructField, STRUCT_FIELD);
ast_node!(ExternDecl, EXTERN_DECL);
ast_node!(UseDecl, USE_DECL);
ast_node!(UseTree, USE_TREE);
ast_node!(UseTreeList, USE_TREE_LIST);
ast_node!(ImportDecl, IMPORT_DECL);
ast_node!(ModuleDecl, MODULE_DECL);

// ── Bodies & content ─────────────────────────────────────────────────

ast_node!(Block, BLOCK);
ast_node!(ContentLine, CONTENT_LINE);
ast_node!(Text, TEXT);
ast_node!(Interpolation, INTERPOLATION);
ast_node!(GlueNode, GLUE_NODE);
ast_node!(TagLine, TAG_LINE);
ast_node!(Tag, TAG);

// ── Choice points ────────────────────────────────────────────────────

ast_node!(ChoicePoint, CHOICE_POINT);
ast_node!(Choice, CHOICE);
ast_node!(ChoiceBullet, CHOICE_BULLET);
ast_node!(Label, LABEL);
ast_node!(ChoiceGuard, CHOICE_GUARD);
ast_node!(ChoiceStartContent, CHOICE_START_CONTENT);
ast_node!(ChoiceBracketContent, CHOICE_BRACKET_CONTENT);
ast_node!(ChoiceInnerContent, CHOICE_INNER_CONTENT);
ast_node!(ChoiceBody, CHOICE_BODY);
ast_node!(ElseBranch, ELSE_BRANCH);
ast_node!(Splice, SPLICE);

// ── The annotated-brace family: conditional / alternation ───────────

ast_node!(ConditionalBlock, CONDITIONAL_BLOCK);
ast_node!(IfArm, IF_ARM);
ast_node!(MatchArm, MATCH_ARM);
ast_node!(MatchPattern, MATCH_PATTERN);
ast_node!(AlternationBlock, ALTERNATION_BLOCK);
ast_node!(AlternationMarker, ALTERNATION_MARKER);
ast_node!(Entry, ENTRY);

// ── Annotations ──────────────────────────────────────────────────────

ast_node!(AnnotationLine, ANNOTATION_LINE);
ast_node!(AnnotationArgs, ANNOTATION_ARGS);
ast_node!(AnnotationArg, ANNOTATION_ARG);

// ── Diverts, tunnels, return ─────────────────────────────────────────

ast_node!(DivertStmt, DIVERT_STMT);
ast_node!(TunnelCall, TUNNEL_CALL);
ast_node!(DivertTarget, DIVERT_TARGET);
ast_node!(ReturnStmt, RETURN_STMT);
ast_node!(ReturnRedirect, RETURN_REDIRECT);

// ── Paths ────────────────────────────────────────────────────────────

ast_node!(Path, PATH);
ast_node!(PathSegment, PATH_SEGMENT);

// ── Expressions ──────────────────────────────────────────────────────

ast_node!(IntegerLit, INTEGER_LIT);
ast_node!(FloatLit, FLOAT_LIT);
ast_node!(StringLit, STRING_LIT);
ast_node!(BooleanLit, BOOLEAN_LIT);
ast_node!(PathExpr, PATH_EXPR);
ast_node!(ParenExpr, PAREN_EXPR);
ast_node!(PrefixExpr, PREFIX_EXPR);
ast_node!(InfixExpr, INFIX_EXPR);
ast_node!(CallExpr, CALL_EXPR);
ast_node!(ArgList, ARG_LIST);
ast_node!(LambdaExpr, LAMBDA_EXPR);
ast_node!(LambdaParams, LAMBDA_PARAMS);

// ── Error recovery ───────────────────────────────────────────────────

ast_node!(Error, ERROR);

// ── Hand-written accessors ───────────────────────────────────────────

impl DocComment {
    /// `true` for the inner (`//!`) form — a run whose tokens are
    /// `DOC_COMMENT_INNER` rather than `DOC_COMMENT_OUTER`. One node shape
    /// covers both variants (`syntax_kind.rs`'s `DOC_COMMENT` doc); this is
    /// how callers tell them apart. A well-formed `DOC_COMMENT` node's
    /// comment tokens are always uniformly one kind or the other (the
    /// parser's `doc_comment::consume_doc_run` never mixes them within a
    /// single run), so checking the first one is sufficient.
    pub fn is_inner(&self) -> bool {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .any(|t| t.kind() == DOC_COMMENT_INNER)
    }

    /// Every doc-comment line in source order: the token's text with its
    /// `///`/`//!` marker stripped and a single leading space (if any)
    /// trimmed, paired with that token's source range — the same shape the
    /// OLD ink parser's `collect_doc_lines` produces, so both frontends
    /// feed the identical format-agnostic `hir::doc_block::parse_lines`
    /// tag parser.
    pub fn lines(&self) -> Vec<(String, rowan::TextRange)> {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .filter(|t| matches!(t.kind(), DOC_COMMENT_OUTER | DOC_COMMENT_INNER))
            .map(|t| {
                let text = t.text();
                let body = text
                    .strip_prefix("///")
                    .or_else(|| text.strip_prefix("//!"))
                    .unwrap_or(text);
                (body.trim_start().to_string(), t.text_range())
            })
            .collect()
    }
}

impl SourceFile {
    /// Every top-level `flow`/`fn` declaration in the file (charter §4:
    /// "no one-flow-per-file constraint — files hold many declarations").
    pub fn flows(&self) -> impl Iterator<Item = FlowDecl> {
        support::children(&self.syntax)
    }

    /// Every top-level `fn` declaration in the file.
    pub fn fns(&self) -> impl Iterator<Item = FnDecl> {
        support::children(&self.syntax)
    }

    /// Every direct child node, typed as its own `SyntaxKind` where a
    /// wrapper exists — the generic escape hatch for callers that want to
    /// walk the whole item list without matching on every variant twice.
    pub fn syntax_children(&self) -> impl Iterator<Item = SyntaxNode> {
        self.syntax.children()
    }

    /// The file-level inner `//!` doc comment, if the file opens with one
    /// (B0.6b: "documents the enclosing ... file"). CST-only for now — no
    /// native HIR type represents whole-file identity yet (`lower_native`'s
    /// module doc, judgment call #7), so nothing consumes this today; kept
    /// for the LSP/fmt/source-map consumers the ruling names.
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl FlowDecl {
    /// The declared name (the `IDENT` immediately after `flow`).
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    pub fn param_list(&self) -> Option<ParamList> {
        support::child(&self.syntax)
    }

    pub fn body(&self) -> Option<Block> {
        support::child(&self.syntax)
    }

    /// Nested `flow` declarations directly inside this one's body — a
    /// stitch (charter §4: "stitches are nested `flow`s").
    pub fn stitches(&self) -> impl Iterator<Item = FlowDecl> {
        self.body()
            .into_iter()
            .flat_map(|b| support::children::<FlowDecl>(&b.syntax).collect::<Vec<_>>())
    }

    /// The leading `///` doc comment, if one is attached (B0.6b).
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl FnDecl {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    pub fn param_list(&self) -> Option<ParamList> {
        support::child(&self.syntax)
    }

    pub fn body(&self) -> Option<Block> {
        support::child(&self.syntax)
    }

    /// The leading `///` doc comment, if one is attached (B0.6b).
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl ParamList {
    pub fn params(&self) -> impl Iterator<Item = Param> {
        support::children(&self.syntax)
    }
}

impl Param {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    /// `true` if this parameter is `ref`-marked.
    pub fn is_ref(&self) -> bool {
        support::token(&self.syntax, SyntaxKind::KW_REF).is_some()
    }
}

impl Block {
    /// Every direct-child node in this block's body, in source order —
    /// the untyped escape hatch, since a `BLOCK`'s items span every
    /// declaration/body-line kind this crate defines. Includes a leading
    /// inner `DOC_COMMENT`, if present — callers that don't want it in
    /// their item stream (e.g. `hir::lower_native::body::lower_block`)
    /// filter it out themselves, same as they already skip other
    /// non-statement node kinds.
    pub fn items(&self) -> impl Iterator<Item = SyntaxNode> {
        self.syntax.children()
    }

    /// The inner `//!` doc comment, if this block opens with one (B0.6b):
    /// documents the enclosing knot/flow/stitch, not a following
    /// declaration. `None` for a `CHOICE_BODY`/`ELSE_BRANCH` — the parser
    /// never attaches an inner doc there (`parser::block::braced_item_list`
    /// only checks for one when building a real `BLOCK`).
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl VarDecl {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    /// The initializer expression's root node, if the `=` clause was
    /// present. `var name = expr` always parses the initializer as exactly
    /// one child node (whatever expression-grammar kind it is) after the
    /// `IDENT`, so "the first child node" is unambiguous.
    pub fn value(&self) -> Option<SyntaxNode> {
        self.syntax
            .children()
            .find(|n| n.kind() != SyntaxKind::DOC_COMMENT)
    }

    /// The leading `///` doc comment, if one is attached (B0.6b).
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl ConstDecl {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    /// See [`VarDecl::value`].
    pub fn value(&self) -> Option<SyntaxNode> {
        self.syntax
            .children()
            .find(|n| n.kind() != SyntaxKind::DOC_COMMENT)
    }

    /// The leading `///` doc comment, if one is attached (B0.6b).
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl FlagsDecl {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    pub fn member_list(&self) -> Option<FlagsMemberList> {
        support::child(&self.syntax)
    }

    /// The leading `///` doc comment, if one is attached (B0.6b).
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl FlagsMemberList {
    pub fn members(&self) -> impl Iterator<Item = FlagsMember> {
        support::children(&self.syntax)
    }
}

impl FlagsMember {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    /// `true` for a parenthesized member (`(name)`, the default-on entry).
    pub fn is_active(&self) -> bool {
        support::token(&self.syntax, L_PAREN).is_some()
    }
}

impl StructDecl {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    pub fn fields(&self) -> impl Iterator<Item = StructField> {
        support::children(&self.syntax)
    }

    /// The leading `///` doc comment, if one is attached (B0.6b).
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl StructField {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    /// The field's declared type — a bare dotted path in this skeleton
    /// grammar (no generics/fn-types, `parser/decl.rs::struct_field`).
    pub fn type_path(&self) -> Option<Path> {
        support::child(&self.syntax)
    }
}

impl ExternDecl {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    pub fn param_list(&self) -> Option<ParamList> {
        support::child(&self.syntax)
    }

    /// The leading `///` doc comment, if one is attached (B0.6b).
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl ImportDecl {
    pub fn path(&self) -> Option<Path> {
        support::child(&self.syntax)
    }

    /// The leading `///` doc comment, if one is attached (B0.6b). No native
    /// HIR field consumes this yet (`Import` carries no `doc`, matching the
    /// OLD ink frontend's own `Import` shape) — CST-only for now, same
    /// status as [`SourceFile::doc`].
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl UseDecl {
    pub fn tree(&self) -> Option<UseTree> {
        support::child(&self.syntax)
    }

    /// See [`ImportDecl::doc`].
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl UseTree {
    /// The leading dotted/`::`-separated path segments, in order —
    /// `use_tree`'s grammar lays these out as bare `IDENT` tokens
    /// interspersed with `::` directly inside `USE_TREE` (no nested `PATH`
    /// node, unlike `import`'s path), so this walks direct-child tokens up
    /// to (not including) an `as`-alias or a nested `{ … }` list.
    pub fn path_segments(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .take_while(|t| t.kind() != SyntaxKind::KW_AS)
            .filter(|t| t.kind() == IDENT)
    }

    /// The `as alias` name, if this tree ends in one.
    pub fn alias_token(&self) -> Option<SyntaxToken> {
        let mut saw_as = false;
        for el in self.syntax.children_with_tokens() {
            let Some(tok) = el.into_token() else {
                continue;
            };
            if tok.kind().is_trivia() {
                continue;
            }
            if saw_as {
                return if tok.kind() == IDENT { Some(tok) } else { None };
            }
            if tok.kind() == SyntaxKind::KW_AS {
                saw_as = true;
            }
        }
        None
    }

    /// The nested `{ a, b as c, … }` group, if this tree has one.
    pub fn nested_list(&self) -> Option<UseTreeList> {
        support::child(&self.syntax)
    }
}

impl UseTreeList {
    pub fn trees(&self) -> impl Iterator<Item = UseTree> {
        support::children(&self.syntax)
    }
}

impl ModuleDecl {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    pub fn body(&self) -> Option<Block> {
        support::child(&self.syntax)
    }

    /// See [`ImportDecl::doc`] — no native HIR "module container" node
    /// exists yet either (`lower_native`'s module doc, judgment call #4).
    pub fn doc(&self) -> Option<DocComment> {
        support::child(&self.syntax)
    }
}

impl Path {
    /// Every `PATH_SEGMENT`'s `IDENT`, in order.
    pub fn segments(&self) -> impl Iterator<Item = SyntaxToken> {
        support::children::<PathSegment>(&self.syntax)
            .filter_map(|seg| support::token(&seg.syntax, IDENT))
    }

    /// `true` if any separator is `::` (crosses a module wall, charter
    /// §13.2) rather than only `.`.
    pub fn crosses_module_wall(&self) -> bool {
        support::tokens(&self.syntax, SyntaxKind::COLON_COLON)
            .next()
            .is_some()
    }
}

impl ChoicePoint {
    pub fn choices(&self) -> impl Iterator<Item = Choice> {
        support::children(&self.syntax)
    }

    pub fn else_branch(&self) -> Option<ElseBranch> {
        support::child(&self.syntax)
    }
}

impl Choice {
    /// `true` for a sticky (`+`) choice, `false` for a once-only (`*`) one.
    ///
    /// B0.7 fix (`docs/b0-sequencing.md` §B0.7, issue #1176): the bullet
    /// token is wrapped in a nested `CHOICE_BULLET` node
    /// (`choice.rs::choice`: `p.start_node(CHOICE_BULLET); p.bump(); …`),
    /// never a direct token of `CHOICE` itself — `support::token` only
    /// looks at direct children, so the original B0.5/B0.6 implementation
    /// (`support::token(&self.syntax, PLUS)`) always returned `false`. Every
    /// choice line in the corpus was silently lowering as once-only; caught
    /// by B0.7's `choice_point_lowers_to_choice_set_with_sticky_and_once`
    /// test, the first real exercise of a sticky (`+`) choice line.
    pub fn is_sticky(&self) -> bool {
        support::child::<ChoiceBullet>(&self.syntax)
            .is_some_and(|b| support::token(&b.syntax, SyntaxKind::PLUS).is_some())
    }

    pub fn label(&self) -> Option<Label> {
        support::child(&self.syntax)
    }

    pub fn guard(&self) -> Option<ChoiceGuard> {
        support::child(&self.syntax)
    }

    pub fn body(&self) -> Option<ChoiceBody> {
        support::child(&self.syntax)
    }
}

impl AnnotationLine {
    /// The directive/annotation name (`effects`, …).
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    pub fn args(&self) -> Option<AnnotationArgs> {
        support::child(&self.syntax)
    }
}

impl AnnotationArgs {
    pub fn args(&self) -> impl Iterator<Item = AnnotationArg> {
        support::children(&self.syntax)
    }
}

impl AnnotationArg {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    /// The nested paren-clause (`reads(gold, hp)` inside `effects(…)`), if
    /// this argument has one.
    pub fn nested_args(&self) -> Option<AnnotationArgs> {
        support::child(&self.syntax)
    }
}

impl CallExpr {
    /// The callee path. Fixed for B0.6 (`docs/b0-sequencing.md` §B0.6):
    /// `expr::path_or_call` wraps a bare `PATH` node directly (not a nested
    /// `PATH_EXPR`) when it commits to `CALL_EXPR` — the previous
    /// `Option<PathExpr>` signature could never cast successfully against
    /// the real grammar shape and always returned `None` for every call
    /// expression. No test exercised it before B0.6's lowering needed it.
    pub fn callee(&self) -> Option<Path> {
        support::child(&self.syntax)
    }

    pub fn arg_list(&self) -> Option<ArgList> {
        support::child(&self.syntax)
    }
}

impl PathExpr {
    pub fn path(&self) -> Option<Path> {
        support::child(&self.syntax)
    }
}

impl IntegerLit {
    pub fn value_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, crate::SyntaxKind::INTEGER)
    }

    pub fn value(&self) -> Option<i64> {
        self.value_token()
            .and_then(|t| t.text().parse::<i64>().ok())
    }
}

impl FloatLit {
    pub fn value_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, crate::SyntaxKind::FLOAT)
    }

    pub fn value(&self) -> Option<f64> {
        self.value_token()
            .and_then(|t| t.text().parse::<f64>().ok())
    }
}

impl BooleanLit {
    pub fn value(&self) -> Option<bool> {
        let tok = self
            .syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .find(|t| matches!(t.kind(), SyntaxKind::KW_TRUE | SyntaxKind::KW_FALSE))?;
        match tok.kind() {
            SyntaxKind::KW_TRUE => Some(true),
            SyntaxKind::KW_FALSE => Some(false),
            _ => None,
        }
    }
}

impl ParenExpr {
    /// The parenthesized inner expression's root node.
    pub fn inner(&self) -> Option<SyntaxNode> {
        self.syntax.children().next()
    }
}

impl PrefixExpr {
    /// The prefix operator token (`-` or `!`).
    pub fn op_token(&self) -> Option<SyntaxToken> {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .find(|t| matches!(t.kind(), SyntaxKind::MINUS | SyntaxKind::BANG))
    }

    /// The operand's root node.
    pub fn operand(&self) -> Option<SyntaxNode> {
        self.syntax.children().next()
    }
}

impl InfixExpr {
    /// The left-hand operand's root node (the first child node).
    pub fn lhs(&self) -> Option<SyntaxNode> {
        self.syntax.children().next()
    }

    /// The right-hand operand's root node (the last child node).
    pub fn rhs(&self) -> Option<SyntaxNode> {
        self.syntax.children().last()
    }

    /// The operator token. Two adjacent `PIPE`s (`||`) are represented as
    /// two tokens (see `expr::infix_binding_power`'s doc) — this returns
    /// the *first* one; callers that need to distinguish `|` from `||`
    /// check [`Self::is_double_pipe`].
    pub fn op_token(&self) -> Option<SyntaxToken> {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .find(|t| {
                matches!(
                    t.kind(),
                    SyntaxKind::AMP_AMP
                        | SyntaxKind::EQ_EQ
                        | SyntaxKind::BANG_EQ
                        | SyntaxKind::LT
                        | SyntaxKind::GT
                        | SyntaxKind::LT_EQ
                        | SyntaxKind::GT_EQ
                        | SyntaxKind::PLUS
                        | SyntaxKind::MINUS
                        | SyntaxKind::STAR
                        | SyntaxKind::SLASH
                        | SyntaxKind::PERCENT
                        | SyntaxKind::PIPE
                )
            })
    }

    /// `true` if the operator is `||` (two adjacent `PIPE` tokens) rather
    /// than a single-token operator.
    pub fn is_double_pipe(&self) -> bool {
        let mut pipes = self
            .syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .filter(|t| t.kind() == SyntaxKind::PIPE);
        pipes.next().is_some() && pipes.next().is_some()
    }
}

impl ArgList {
    /// `true` if this arg list opens with `(` at all (always true for a
    /// well-formed parse — exposed for error-recovery callers that walk a
    /// possibly-malformed tree).
    pub fn is_open(&self) -> bool {
        support::token(&self.syntax, L_PAREN).is_some()
    }
}

impl DivertTarget {
    pub fn is_end(&self) -> bool {
        support::token(&self.syntax, SyntaxKind::KW_END).is_some()
    }

    pub fn is_done(&self) -> bool {
        support::token(&self.syntax, SyntaxKind::KW_DONE).is_some()
    }

    pub fn path(&self) -> Option<Path> {
        support::child(&self.syntax)
    }
}

// ── B0.7 additions: body-dialect accessors ──────────────────────────
//
// Everything below was added for `hir::lower_native`'s body lowering
// (`docs/b0-sequencing.md` §B0.7). B0.5/B0.6 hand-wrote a representative
// subset of accessors "enough to prove the pattern end-to-end ... not
// pre-building every accessor a later lowering pass might want" (this
// file's module doc) — B0.7 is exactly that later pass.

impl ContentLine {
    /// The leading `(name)` label, if this line opens with one (G-1).
    pub fn label(&self) -> Option<Label> {
        support::child(&self.syntax)
    }
}

impl Label {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }
}

impl DivertStmt {
    pub fn target(&self) -> Option<DivertTarget> {
        support::child(&self.syntax)
    }
}

impl TunnelCall {
    /// The one divert target between the opening and closing `->` (native's
    /// `-> place ->` shape carries exactly one target, unlike ink's chained
    /// `-> a -> b ->`).
    pub fn target(&self) -> Option<DivertTarget> {
        support::child(&self.syntax)
    }
}

impl ReturnRedirect {
    pub fn target(&self) -> Option<DivertTarget> {
        support::child(&self.syntax)
    }
}

impl Choice {
    pub fn start_content(&self) -> Option<ChoiceStartContent> {
        support::child(&self.syntax)
    }

    pub fn bracket_content(&self) -> Option<ChoiceBracketContent> {
        support::child(&self.syntax)
    }

    pub fn inner_content(&self) -> Option<ChoiceInnerContent> {
        support::child(&self.syntax)
    }
}

impl ChoiceGuard {
    /// The guard's condition expression — `CHOICE_GUARD`'s only child node
    /// (`L_BRACE KW_IF expression R_BRACE`; the braces/keyword are tokens).
    pub fn expr(&self) -> Option<SyntaxNode> {
        self.syntax.children().next()
    }
}

impl ChoiceBody {
    pub fn items(&self) -> impl Iterator<Item = SyntaxNode> {
        self.syntax.children()
    }
}

impl ElseBranch {
    /// The nested `CHOICE_BODY`, when this `else` belongs to a choice point
    /// (`choice.rs::else_branch` — always braced, no colon form).
    pub fn choice_body(&self) -> Option<ChoiceBody> {
        support::child(&self.syntax)
    }

    /// The nested `BLOCK`, when this `else` belongs to the conditional
    /// family's braced-arm form (`{if cond {…} else {…}}`).
    pub fn block(&self) -> Option<Block> {
        support::child(&self.syntax)
    }

    /// Every direct-child item, for the conditional family's colon-body
    /// form (`{if cond: … else: …}`), where body items are direct children
    /// with no wrapper node (`family.rs::colon_body`).
    pub fn items(&self) -> impl Iterator<Item = SyntaxNode> {
        self.syntax.children()
    }
}

impl Splice {
    pub fn path(&self) -> Option<Path> {
        support::child(&self.syntax)
    }

    pub fn arg_list(&self) -> Option<ArgList> {
        support::child(&self.syntax)
    }
}

impl ConditionalBlock {
    pub fn is_if(&self) -> bool {
        support::token(&self.syntax, SyntaxKind::KW_IF).is_some()
    }

    pub fn is_match(&self) -> bool {
        support::token(&self.syntax, SyntaxKind::KW_MATCH).is_some()
    }

    /// The head expression: the `if` condition or the `match` subject —
    /// `CONDITIONAL_BLOCK`'s only child node that isn't an arm/else
    /// (`family.rs::conditional_block`: the expression is parsed directly
    /// into this node before the arm(s)).
    pub fn condition(&self) -> Option<SyntaxNode> {
        self.syntax.children().find(|n| {
            !matches!(
                n.kind(),
                SyntaxKind::IF_ARM | SyntaxKind::ELSE_BRANCH | SyntaxKind::MATCH_ARM
            )
        })
    }

    pub fn if_arm(&self) -> Option<IfArm> {
        support::child(&self.syntax)
    }

    pub fn else_arm(&self) -> Option<ElseBranch> {
        support::child(&self.syntax)
    }

    /// `match`'s arms — direct children of `CONDITIONAL_BLOCK` itself
    /// (`family.rs::match_arm_list` opens no wrapper node of its own).
    pub fn match_arms(&self) -> impl Iterator<Item = MatchArm> {
        support::children(&self.syntax)
    }
}

impl IfArm {
    /// The nested `BLOCK`, for the braced-arm form.
    pub fn block(&self) -> Option<Block> {
        support::child(&self.syntax)
    }

    /// Direct-child items, for the colon-body form (see `ElseBranch::items`).
    pub fn items(&self) -> impl Iterator<Item = SyntaxNode> {
        self.syntax.children()
    }
}

impl MatchArm {
    /// The pattern's expression — `MATCH_PATTERN`'s only child node.
    pub fn pattern_expr(&self) -> Option<SyntaxNode> {
        support::child::<MatchPattern>(&self.syntax).and_then(|p| p.syntax.children().next())
    }

    /// The nested `BLOCK`, for a braced arm body (`pattern => { … }`).
    pub fn block(&self) -> Option<Block> {
        support::child(&self.syntax)
    }

    /// The bare expression, for an unbraced arm body (`pattern => expr`) —
    /// the one child node that is neither `MATCH_PATTERN` nor `BLOCK`.
    pub fn bare_expr(&self) -> Option<SyntaxNode> {
        self.syntax
            .children()
            .find(|n| !matches!(n.kind(), SyntaxKind::MATCH_PATTERN | SyntaxKind::BLOCK))
    }
}

impl AlternationBlock {
    /// The `~`/`&`/`!`/`|` marker token.
    pub fn marker_token(&self) -> Option<SyntaxToken> {
        support::child::<AlternationMarker>(&self.syntax).and_then(|m| {
            m.syntax
                .children_with_tokens()
                .filter_map(rowan::NodeOrToken::into_token)
                .find(|t| {
                    matches!(
                        t.kind(),
                        SyntaxKind::TILDE | SyntaxKind::AMP | SyntaxKind::BANG | SyntaxKind::PIPE
                    )
                })
        })
    }

    /// The multiline `-`-prefixed entries, if this block used that form
    /// (`family.rs::multiline_entries`). Empty for the single-line
    /// pipe-separated form — see [`Self::syntax`] for the raw child walk
    /// callers need for that form instead (no per-alternative wrapper node
    /// exists for it, `family.rs::inline_alternatives`).
    pub fn entries(&self) -> impl Iterator<Item = Entry> {
        support::children(&self.syntax)
    }
}

impl Entry {
    /// Every direct-child item inside this `-`-prefixed entry (the leading
    /// `MINUS` is a token, filtered out by `.children()`).
    pub fn items(&self) -> impl Iterator<Item = SyntaxNode> {
        self.syntax.children()
    }
}

impl TagLine {
    pub fn tags(&self) -> impl Iterator<Item = Tag> {
        support::children(&self.syntax)
    }
}