Skip to main content

brink_syntax/ast/
nodes.rs

1//! Typed AST node wrappers for every node kind in the ink CST.
2//!
3//! Each struct is a zero-cost newtype around [`SyntaxNode`] generated by
4//! [`ast_node!`]. Structs with hand-written accessors have `impl` blocks
5//! below their definition.
6
7use crate::SyntaxKind::{
8    self, AMP, AMP_AMP, BANG, BANG_EQ, BANG_QUESTION, CARET, COLON, DIVERT, DOLLAR, EQ, EQ_EQ,
9    FLOAT, GT, GT_EQ, HASH, IDENT, INTEGER, KW_AND, KW_CYCLE, KW_DONE, KW_ELSE, KW_END, KW_FALSE,
10    KW_FUNCTION, KW_HAS, KW_HASNT, KW_MOD, KW_NOT, KW_ONCE, KW_OR, KW_REF, KW_SHUFFLE, KW_STOPPING,
11    KW_TODO, KW_TRUE, L_PAREN, LT, LT_EQ, MINUS, MINUS_EQ, NEWLINE, PERCENT, PIPE, PLUS, PLUS_EQ,
12    QUESTION, R_PAREN, SLASH, STAR, TILDE,
13};
14use crate::ast::AstNode as _;
15use crate::ast::ast_node;
16use crate::ast::support;
17use crate::{SyntaxNode, SyntaxToken};
18
19// ── Top-level ────────────────────────────────────────────────────────
20
21ast_node!(SourceFile, SOURCE_FILE);
22ast_node!(IncludeStmt, INCLUDE_STMT);
23ast_node!(ImportStmt, IMPORT_STMT);
24ast_node!(ImportList, IMPORT_LIST);
25ast_node!(ImportItem, IMPORT_ITEM);
26ast_node!(ImportModule, IMPORT_MODULE);
27ast_node!(FilePath, FILE_PATH);
28ast_node!(ExternalDecl, EXTERNAL_DECL);
29
30// ── Knots & stitches ─────────────────────────────────────────────────
31
32ast_node!(KnotDef, KNOT_DEF);
33ast_node!(KnotHeader, KNOT_HEADER);
34ast_node!(KnotBody, KNOT_BODY);
35ast_node!(KnotParams, KNOT_PARAMS);
36ast_node!(KnotParamDecl, KNOT_PARAM_DECL);
37ast_node!(StitchDef, STITCH_DEF);
38ast_node!(StitchHeader, STITCH_HEADER);
39ast_node!(StitchBody, STITCH_BODY);
40
41// ── Lines ────────────────────────────────────────────────────────────
42
43ast_node!(EmptyLine, EMPTY_LINE);
44ast_node!(AuthorWarning, AUTHOR_WARNING);
45ast_node!(LogicLine, LOGIC_LINE);
46ast_node!(ContentLine, CONTENT_LINE);
47ast_node!(TagLine, TAG_LINE);
48ast_node!(AnnotationLine, ANNOTATION_LINE);
49ast_node!(StrayClosingBrace, STRAY_CLOSING_BRACE);
50
51// ── Logic ────────────────────────────────────────────────────────────
52
53ast_node!(ReturnStmt, RETURN_STMT);
54ast_node!(TempDecl, TEMP_DECL);
55ast_node!(Assignment, ASSIGNMENT);
56ast_node!(AwaitStmt, AWAIT_STMT);
57
58// ── Content ──────────────────────────────────────────────────────────
59
60ast_node!(MixedContent, MIXED_CONTENT);
61ast_node!(Text, TEXT);
62ast_node!(Escape, ESCAPE);
63ast_node!(GlueNode, GLUE_NODE);
64
65// ── Choices ──────────────────────────────────────────────────────────
66
67ast_node!(Choice, CHOICE);
68ast_node!(ChoiceBullets, CHOICE_BULLETS);
69ast_node!(Label, LABEL);
70ast_node!(ChoiceCondition, CHOICE_CONDITION);
71ast_node!(ChoiceStartContent, CHOICE_START_CONTENT);
72ast_node!(ChoiceBracketContent, CHOICE_BRACKET_CONTENT);
73ast_node!(ChoiceInnerContent, CHOICE_INNER_CONTENT);
74
75// ── Gathers ──────────────────────────────────────────────────────────
76
77ast_node!(Gather, GATHER);
78ast_node!(GatherDashes, GATHER_DASHES);
79
80// ── Tags ─────────────────────────────────────────────────────────────
81
82ast_node!(Tags, TAGS);
83ast_node!(Tag, TAG);
84
85// ── Inline logic ─────────────────────────────────────────────────────
86
87ast_node!(InlineLogic, INLINE_LOGIC);
88ast_node!(MultilineBlock, MULTILINE_BLOCK);
89ast_node!(SequenceWithAnnotation, SEQUENCE_WITH_ANNOTATION);
90ast_node!(SequenceSymbolAnnotation, SEQUENCE_SYMBOL_ANNOTATION);
91ast_node!(SequenceWordAnnotation, SEQUENCE_WORD_ANNOTATION);
92ast_node!(InlineBranchesSeq, INLINE_BRANCHES_SEQ);
93ast_node!(MultilineBranchesSeq, MULTILINE_BRANCHES_SEQ);
94ast_node!(MultilineBranchSeq, MULTILINE_BRANCH_SEQ);
95ast_node!(BranchContent, BRANCH_CONTENT);
96
97// ── Conditionals ─────────────────────────────────────────────────────
98
99ast_node!(ConditionalWithExpr, CONDITIONAL_WITH_EXPR);
100ast_node!(BranchlessCondBody, BRANCHLESS_COND_BODY);
101ast_node!(ElseBranch, ELSE_BRANCH);
102ast_node!(InlineBranchesCond, INLINE_BRANCHES_COND);
103ast_node!(MultilineBranchesCond, MULTILINE_BRANCHES_COND);
104ast_node!(MultilineConditional, MULTILINE_CONDITIONAL);
105ast_node!(MultilineBranchCond, MULTILINE_BRANCH_COND);
106ast_node!(MultilineBranchBody, MULTILINE_BRANCH_BODY);
107ast_node!(ImplicitSequence, IMPLICIT_SEQUENCE);
108
109// ── Expressions ──────────────────────────────────────────────────────
110
111ast_node!(InnerExpression, INNER_EXPRESSION);
112ast_node!(PrefixExpr, PREFIX_EXPR);
113ast_node!(PostfixExpr, POSTFIX_EXPR);
114ast_node!(InfixExpr, INFIX_EXPR);
115ast_node!(ParenExpr, PAREN_EXPR);
116ast_node!(FunctionCall, FUNCTION_CALL);
117ast_node!(ArgList, ARG_LIST);
118ast_node!(DivertTargetExpr, DIVERT_TARGET_EXPR);
119ast_node!(ListExpr, LIST_EXPR);
120
121// ── T1b superset: sigil literals + indexing (docs/t1b-surface-spec.md §3-4) ──
122
123ast_node!(ArrayLiteral, ARRAY_LITERAL);
124ast_node!(MapLiteral, MAP_LITERAL);
125ast_node!(MapEntry, MAP_ENTRY);
126ast_node!(IndexExpr, INDEX_EXPR);
127ast_node!(RangeExpr, RANGE_EXPR);
128
129// ── T1b superset: multi-line `~ { … }` blocks (docs/t1b-surface-spec.md §2) ──
130
131ast_node!(StmtBlock, STMT_BLOCK);
132ast_node!(IfStmt, IF_STMT);
133ast_node!(ElseClause, ELSE_CLAUSE);
134ast_node!(WhileStmt, WHILE_STMT);
135ast_node!(ForStmt, FOR_STMT);
136ast_node!(BreakStmt, BREAK_STMT);
137ast_node!(ContinueStmt, CONTINUE_STMT);
138ast_node!(ExprStmt, EXPR_STMT);
139
140// ── TM-2 inline type annotations (docs/typed-mode-spec.md §3) ────────
141
142ast_node!(TypeAnnotation, TYPE_ANNOTATION);
143ast_node!(TypeExpr, TYPE_EXPR);
144ast_node!(TypeName, TYPE_NAME);
145ast_node!(TypeGeneric, TYPE_GENERIC);
146ast_node!(TypeFn, TYPE_FN);
147
148// ── TM-4b structs (docs/typed-mode-spec.md §6) ────────────────────────
149
150ast_node!(StructDecl, STRUCT_DECL);
151ast_node!(StructFieldDecl, STRUCT_FIELD_DECL);
152ast_node!(StructLiteral, STRUCT_LITERAL);
153ast_node!(StructFieldInit, STRUCT_FIELD_INIT);
154ast_node!(FieldAccessExpr, FIELD_ACCESS_EXPR);
155
156// ── T1c function values (docs/t1c-spec.md §2) ─────────────────────────
157
158ast_node!(FnLiteral, FN_LITERAL);
159
160// ── T1e path projections (docs/t1e-spec.md §2) ────────────────────────
161
162ast_node!(RefExpr, REF_EXPR);
163
164// ── Computed-callee call attempt (docs/t1c-spec.md §3/§10, issue #869) ──
165
166ast_node!(CallExpr, CALL_EXPR);
167
168// ── Diverts ──────────────────────────────────────────────────────────
169
170ast_node!(DivertNode, DIVERT_NODE);
171ast_node!(SimpleDivert, SIMPLE_DIVERT);
172ast_node!(DivertTargetWithArgs, DIVERT_TARGET_WITH_ARGS);
173ast_node!(ThreadStart, THREAD_START);
174ast_node!(TunnelOnwardsNode, TUNNEL_ONWARDS_NODE);
175ast_node!(TunnelCallNode, TUNNEL_CALL_NODE);
176
177// ── TM-2 inline type annotations (docs/typed-mode-spec.md §3) ────────
178
179impl TypeAnnotation {
180    /// The annotated type expression after `:`.
181    pub fn type_expr(&self) -> Option<TypeExpr> {
182        support::child(&self.syntax)
183    }
184}
185
186/// What a [`TypeExpr`] wraps — exactly one of these per node.
187pub enum TypeExprKind {
188    Name(TypeName),
189    Generic(TypeGeneric),
190    Fn(TypeFn),
191}
192
193impl TypeExpr {
194    /// The single child this type expression wraps.
195    ///
196    /// `None` only for a malformed/error-recovered `TYPE_EXPR` (e.g. an
197    /// empty annotation at the parser's nesting depth limit) — every
198    /// well-formed one always has exactly one of these.
199    pub fn kind(&self) -> Option<TypeExprKind> {
200        if let Some(n) = support::child::<TypeName>(&self.syntax) {
201            Some(TypeExprKind::Name(n))
202        } else if let Some(g) = support::child::<TypeGeneric>(&self.syntax) {
203            Some(TypeExprKind::Generic(g))
204        } else {
205            support::child::<TypeFn>(&self.syntax).map(TypeExprKind::Fn)
206        }
207    }
208}
209
210impl TypeName {
211    pub fn identifier(&self) -> Option<Identifier> {
212        support::child(&self.syntax)
213    }
214
215    /// The bare type name text (e.g. `"int"`, `"void"`, or an unrecognized
216    /// name — grammar accepts any identifier; validity is a semantic check).
217    pub fn name(&self) -> Option<String> {
218        self.identifier().and_then(|id| id.name())
219    }
220}
221
222impl TypeGeneric {
223    pub fn identifier(&self) -> Option<Identifier> {
224        support::child(&self.syntax)
225    }
226
227    /// The generic head name (e.g. `"list"`, `"array"`, `"map"`).
228    pub fn name(&self) -> Option<String> {
229        self.identifier().and_then(|id| id.name())
230    }
231
232    /// The type arguments in source order (e.g. `[K, V]` for `Map<K, V>`).
233    pub fn args(&self) -> impl Iterator<Item = TypeExpr> {
234        support::children(&self.syntax)
235    }
236}
237
238impl TypeFn {
239    /// Every `TYPE_EXPR` child in source order: the last is the return type,
240    /// every earlier one is a parameter type.
241    fn type_exprs(&self) -> Vec<TypeExpr> {
242        support::children(&self.syntax).collect()
243    }
244
245    /// Parameter types, in declaration order.
246    pub fn params(&self) -> Vec<TypeExpr> {
247        let mut exprs = self.type_exprs();
248        if exprs.is_empty() {
249            return exprs;
250        }
251        exprs.pop(); // drop the return type
252        exprs
253    }
254
255    /// The return type after `:`.
256    pub fn return_type(&self) -> Option<TypeExpr> {
257        self.type_exprs().pop()
258    }
259}
260
261// ── Identifiers ──────────────────────────────────────────────────────
262
263ast_node!(Identifier, IDENTIFIER);
264ast_node!(Path, PATH);
265
266// ── Declarations ─────────────────────────────────────────────────────
267
268ast_node!(VarDecl, VAR_DECL);
269ast_node!(ConstDecl, CONST_DECL);
270ast_node!(ListDecl, LIST_DECL);
271ast_node!(ListDef, LIST_DEF);
272ast_node!(ListMember, LIST_MEMBER);
273ast_node!(ListMemberOn, LIST_MEMBER_ON);
274ast_node!(ListMemberOff, LIST_MEMBER_OFF);
275ast_node!(FunctionParamList, FUNCTION_PARAM_LIST);
276
277// ── Literals ─────────────────────────────────────────────────────────
278
279ast_node!(IntegerLit, INTEGER_LIT);
280ast_node!(FloatLit, FLOAT_LIT);
281ast_node!(StringLit, STRING_LIT);
282ast_node!(BooleanLit, BOOLEAN_LIT);
283
284// ── Error recovery ───────────────────────────────────────────────────
285
286ast_node!(Error, ERROR);
287
288// ── Expression enum ──────────────────────────────────────────────────
289
290/// A typed expression node.
291///
292/// Covers every node kind the Pratt expression parser can produce.
293#[derive(Clone, PartialEq, Eq, Hash)]
294pub enum Expr {
295    Prefix(PrefixExpr),
296    Postfix(PostfixExpr),
297    Infix(InfixExpr),
298    Paren(ParenExpr),
299    FunctionCall(FunctionCall),
300    IntegerLit(IntegerLit),
301    FloatLit(FloatLit),
302    StringLit(StringLit),
303    BooleanLit(BooleanLit),
304    Path(Path),
305    ListExpr(ListExpr),
306    DivertTarget(DivertTargetExpr),
307    /// `#[expr, …]` — array sigil literal (T1b §3, brink extension).
308    ArrayLiteral(ArrayLiteral),
309    /// `#{key: expr, …}` — map sigil literal (T1b §3, brink extension).
310    MapLiteral(MapLiteral),
311    /// `base[index]` — postfix indexing (T1b §4, brink extension).
312    Index(IndexExpr),
313    /// `Name#{field: expr, …}` — struct construction literal (TM-4b,
314    /// docs/typed-mode-spec.md §6, brink extension).
315    StructLiteral(StructLiteral),
316    /// `base.field` — postfix field access (TM-4b, docs/typed-mode-spec.md
317    /// §6, brink extension). Only produced where the dotted-`PATH` grammar
318    /// doesn't already cover the shape — see `FIELD_ACCESS_EXPR`'s doc.
319    FieldAccess(FieldAccessExpr),
320    /// `#fn(target, args…)` — function-value creation (T1c,
321    /// docs/t1c-spec.md §2, brink extension).
322    FnLiteral(FnLiteral),
323    /// `ref lvalue-path` — path-projection creation (T1e,
324    /// docs/t1e-spec.md §2, brink extension). Legal only in ref-argument
325    /// position (calls, `#fn(…)`, `bind(…)`) — a `brink-analyzer` concern,
326    /// not a grammar one.
327    RefExpr(RefExpr),
328    /// `expr(args…)` where `expr` isn't a bare identifier immediately
329    /// followed by `(` (that shape is `FunctionCall`) — a computed callee
330    /// (indexed, field access, call-result, parenthesized, …). Parses so
331    /// the call syntax and its args aren't silently reinterpreted as
332    /// trailing prose text; always rejected at HIR lowering (E104,
333    /// docs/t1c-spec.md §3/§10, issue #869) since Direct-call syntax is
334    /// RULED to a bare variable/temp/param callee only.
335    ComputedCall(CallExpr),
336    /// `a..b` / `a..=b` — range literal (NS-A5, docs/stdlib-spec.md §7,
337    /// brink extension).
338    Range(RangeExpr),
339}
340
341impl std::fmt::Debug for Expr {
342    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343        std::fmt::Debug::fmt(self.syntax(), f)
344    }
345}
346
347impl std::fmt::Display for Expr {
348    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349        std::fmt::Display::fmt(&self.syntax().text(), f)
350    }
351}
352
353impl crate::ast::AstNode for Expr {
354    fn can_cast(kind: SyntaxKind) -> bool {
355        matches!(
356            kind,
357            SyntaxKind::PREFIX_EXPR
358                | SyntaxKind::POSTFIX_EXPR
359                | SyntaxKind::INFIX_EXPR
360                | SyntaxKind::PAREN_EXPR
361                | SyntaxKind::FUNCTION_CALL
362                | SyntaxKind::INTEGER_LIT
363                | SyntaxKind::FLOAT_LIT
364                | SyntaxKind::STRING_LIT
365                | SyntaxKind::BOOLEAN_LIT
366                | SyntaxKind::PATH
367                | SyntaxKind::LIST_EXPR
368                | SyntaxKind::DIVERT_TARGET_EXPR
369                | SyntaxKind::ARRAY_LITERAL
370                | SyntaxKind::MAP_LITERAL
371                | SyntaxKind::INDEX_EXPR
372                | SyntaxKind::STRUCT_LITERAL
373                | SyntaxKind::FIELD_ACCESS_EXPR
374                | SyntaxKind::FN_LITERAL
375                | SyntaxKind::REF_EXPR
376                | SyntaxKind::CALL_EXPR
377                | SyntaxKind::RANGE_EXPR
378        )
379    }
380
381    fn cast(node: SyntaxNode) -> Option<Self> {
382        match node.kind() {
383            SyntaxKind::PREFIX_EXPR => PrefixExpr::cast(node).map(Expr::Prefix),
384            SyntaxKind::POSTFIX_EXPR => PostfixExpr::cast(node).map(Expr::Postfix),
385            SyntaxKind::INFIX_EXPR => InfixExpr::cast(node).map(Expr::Infix),
386            SyntaxKind::PAREN_EXPR => ParenExpr::cast(node).map(Expr::Paren),
387            SyntaxKind::FUNCTION_CALL => FunctionCall::cast(node).map(Expr::FunctionCall),
388            SyntaxKind::INTEGER_LIT => IntegerLit::cast(node).map(Expr::IntegerLit),
389            SyntaxKind::FLOAT_LIT => FloatLit::cast(node).map(Expr::FloatLit),
390            SyntaxKind::STRING_LIT => StringLit::cast(node).map(Expr::StringLit),
391            SyntaxKind::BOOLEAN_LIT => BooleanLit::cast(node).map(Expr::BooleanLit),
392            SyntaxKind::PATH => Path::cast(node).map(Expr::Path),
393            SyntaxKind::LIST_EXPR => ListExpr::cast(node).map(Expr::ListExpr),
394            SyntaxKind::DIVERT_TARGET_EXPR => DivertTargetExpr::cast(node).map(Expr::DivertTarget),
395            SyntaxKind::ARRAY_LITERAL => ArrayLiteral::cast(node).map(Expr::ArrayLiteral),
396            SyntaxKind::MAP_LITERAL => MapLiteral::cast(node).map(Expr::MapLiteral),
397            SyntaxKind::INDEX_EXPR => IndexExpr::cast(node).map(Expr::Index),
398            SyntaxKind::STRUCT_LITERAL => StructLiteral::cast(node).map(Expr::StructLiteral),
399            SyntaxKind::FIELD_ACCESS_EXPR => FieldAccessExpr::cast(node).map(Expr::FieldAccess),
400            SyntaxKind::FN_LITERAL => FnLiteral::cast(node).map(Expr::FnLiteral),
401            SyntaxKind::REF_EXPR => RefExpr::cast(node).map(Expr::RefExpr),
402            SyntaxKind::CALL_EXPR => CallExpr::cast(node).map(Expr::ComputedCall),
403            SyntaxKind::RANGE_EXPR => RangeExpr::cast(node).map(Expr::Range),
404            _ => None,
405        }
406    }
407
408    fn syntax(&self) -> &SyntaxNode {
409        match self {
410            Expr::Prefix(n) => n.syntax(),
411            Expr::Postfix(n) => n.syntax(),
412            Expr::Infix(n) => n.syntax(),
413            Expr::Paren(n) => n.syntax(),
414            Expr::FunctionCall(n) => n.syntax(),
415            Expr::IntegerLit(n) => n.syntax(),
416            Expr::FloatLit(n) => n.syntax(),
417            Expr::StringLit(n) => n.syntax(),
418            Expr::BooleanLit(n) => n.syntax(),
419            Expr::Path(n) => n.syntax(),
420            Expr::ListExpr(n) => n.syntax(),
421            Expr::DivertTarget(n) => n.syntax(),
422            Expr::ArrayLiteral(n) => n.syntax(),
423            Expr::MapLiteral(n) => n.syntax(),
424            Expr::Index(n) => n.syntax(),
425            Expr::StructLiteral(n) => n.syntax(),
426            Expr::FieldAccess(n) => n.syntax(),
427            Expr::FnLiteral(n) => n.syntax(),
428            Expr::RefExpr(n) => n.syntax(),
429            Expr::ComputedCall(n) => n.syntax(),
430            Expr::Range(n) => n.syntax(),
431        }
432    }
433}
434
435// ── Content node accessor macro ─────────────────────────────────────
436
437/// Generates shared content-element accessors for nodes that contain
438/// mixed inline content (`TEXT`, `INLINE_LOGIC`, `GLUE_NODE`, `ESCAPE`).
439macro_rules! content_node_accessors {
440    ($name:ident) => {
441        impl $name {
442            pub fn texts(&self) -> impl Iterator<Item = Text> {
443                support::children(&self.syntax)
444            }
445
446            pub fn inline_logics(&self) -> impl Iterator<Item = InlineLogic> {
447                support::children(&self.syntax)
448            }
449
450            pub fn glue_nodes(&self) -> impl Iterator<Item = GlueNode> {
451                support::children(&self.syntax)
452            }
453
454            pub fn escapes(&self) -> impl Iterator<Item = Escape> {
455                support::children(&self.syntax)
456            }
457        }
458    };
459}
460
461content_node_accessors!(ChoiceStartContent);
462content_node_accessors!(ChoiceBracketContent);
463content_node_accessors!(ChoiceInnerContent);
464content_node_accessors!(BranchContent);
465
466// ═══════════════════════════════════════════════════════════════════════
467// Accessors
468// ═══════════════════════════════════════════════════════════════════════
469
470// ── SourceFile ───────────────────────────────────────────────────────
471
472impl SourceFile {
473    pub fn knots(&self) -> impl Iterator<Item = KnotDef> {
474        support::children(&self.syntax)
475    }
476
477    pub fn includes(&self) -> impl Iterator<Item = IncludeStmt> {
478        support::children(&self.syntax)
479    }
480
481    /// `IMPORT` statements (M-2, docs/modules-spec.md §2). Top-level only.
482    pub fn imports(&self) -> impl Iterator<Item = ImportStmt> {
483        support::children(&self.syntax)
484    }
485
486    pub fn externals(&self) -> impl Iterator<Item = ExternalDecl> {
487        support::children(&self.syntax)
488    }
489
490    pub fn stitches(&self) -> impl Iterator<Item = StitchDef> {
491        support::children(&self.syntax)
492    }
493
494    pub fn var_decls(&self) -> impl Iterator<Item = VarDecl> {
495        support::children(&self.syntax)
496    }
497
498    pub fn const_decls(&self) -> impl Iterator<Item = ConstDecl> {
499        support::children(&self.syntax)
500    }
501
502    pub fn list_decls(&self) -> impl Iterator<Item = ListDecl> {
503        support::children(&self.syntax)
504    }
505
506    /// `STRUCT` declarations (TM-4b, docs/typed-mode-spec.md §6) — unlike
507    /// `VAR`/`CONST`/`LIST` (which C# allows at any statement level, so
508    /// callers walk `.descendants()` for those), a struct shape is
509    /// top-level only, so a direct-children scan is exact.
510    pub fn struct_decls(&self) -> impl Iterator<Item = StructDecl> {
511        support::children(&self.syntax)
512    }
513
514    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
515        support::children(&self.syntax)
516    }
517
518    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
519        support::children(&self.syntax)
520    }
521
522    pub fn choices(&self) -> impl Iterator<Item = Choice> {
523        support::children(&self.syntax)
524    }
525
526    pub fn gathers(&self) -> impl Iterator<Item = Gather> {
527        support::children(&self.syntax)
528    }
529}
530
531// ── IncludeStmt ──────────────────────────────────────────────────────
532
533impl IncludeStmt {
534    pub fn file_path(&self) -> Option<FilePath> {
535        support::child(&self.syntax)
536    }
537}
538
539// ── ImportStmt (M-2, docs/modules-spec.md §2) ────────────────────────
540
541impl ImportStmt {
542    /// The `{ … }` name list, present only for the bare form
543    /// (`IMPORT { a } FROM mod`). Absent for the qualified form
544    /// (`IMPORT mod`).
545    pub fn list(&self) -> Option<ImportList> {
546        support::child(&self.syntax)
547    }
548
549    /// The imported module name node (present in both forms).
550    pub fn module(&self) -> Option<ImportModule> {
551        support::child(&self.syntax)
552    }
553}
554
555impl ImportList {
556    pub fn items(&self) -> impl Iterator<Item = ImportItem> {
557        support::children(&self.syntax)
558    }
559}
560
561impl ImportItem {
562    /// The imported name (first identifier) and its optional alias (the
563    /// identifier after `AS`). The list has one entry with no alias, or two
564    /// with `[name, alias]`.
565    fn identifiers(&self) -> impl Iterator<Item = Identifier> {
566        support::children(&self.syntax)
567    }
568
569    /// The imported definition's own name.
570    pub fn name(&self) -> Option<String> {
571        self.identifiers().next().and_then(|id| id.name())
572    }
573
574    /// The local alias (`AS gt`), if any.
575    pub fn alias(&self) -> Option<String> {
576        self.identifiers().nth(1).and_then(|id| id.name())
577    }
578}
579
580impl ImportModule {
581    pub fn name(&self) -> Option<String> {
582        support::child::<Identifier>(&self.syntax).and_then(|id| id.name())
583    }
584}
585
586// ── FilePath ─────────────────────────────────────────────────────────
587
588impl FilePath {
589    /// Returns the raw text of the file path (concatenation of all child tokens).
590    pub fn text(&self) -> String {
591        self.syntax.text().to_string()
592    }
593}
594
595// ── ExternalDecl ─────────────────────────────────────────────────────
596
597impl ExternalDecl {
598    pub fn identifier(&self) -> Option<Identifier> {
599        support::child(&self.syntax)
600    }
601
602    pub fn name(&self) -> Option<String> {
603        self.identifier().and_then(|id| id.name())
604    }
605
606    pub fn param_list(&self) -> Option<FunctionParamList> {
607        support::child(&self.syntax)
608    }
609}
610
611// ── KnotDef ──────────────────────────────────────────────────────────
612
613impl KnotDef {
614    pub fn header(&self) -> Option<KnotHeader> {
615        support::child(&self.syntax)
616    }
617
618    pub fn body(&self) -> Option<KnotBody> {
619        support::child(&self.syntax)
620    }
621}
622
623// ── KnotHeader ───────────────────────────────────────────────────────
624
625impl KnotHeader {
626    pub fn function_kw(&self) -> Option<SyntaxToken> {
627        support::token(&self.syntax, KW_FUNCTION)
628    }
629
630    pub fn is_function(&self) -> bool {
631        self.function_kw().is_some()
632    }
633
634    pub fn identifier(&self) -> Option<Identifier> {
635        support::child(&self.syntax)
636    }
637
638    pub fn name(&self) -> Option<String> {
639        self.identifier().and_then(|id| id.name())
640    }
641
642    pub fn params(&self) -> Option<KnotParams> {
643        support::child(&self.syntax)
644    }
645
646    /// The return type annotation after the params (TM-2, docs/typed-mode-spec.md
647    /// §3: `): type ===`), if present.
648    pub fn return_type(&self) -> Option<TypeAnnotation> {
649        support::child(&self.syntax)
650    }
651}
652
653// ── KnotBody ─────────────────────────────────────────────────────────
654
655impl KnotBody {
656    pub fn stitches(&self) -> impl Iterator<Item = StitchDef> {
657        support::children(&self.syntax)
658    }
659
660    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
661        support::children(&self.syntax)
662    }
663
664    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
665        support::children(&self.syntax)
666    }
667
668    pub fn choices(&self) -> impl Iterator<Item = Choice> {
669        support::children(&self.syntax)
670    }
671
672    pub fn gathers(&self) -> impl Iterator<Item = Gather> {
673        support::children(&self.syntax)
674    }
675}
676
677// ── KnotParams ───────────────────────────────────────────────────────
678
679impl KnotParams {
680    pub fn params(&self) -> impl Iterator<Item = KnotParamDecl> {
681        support::children(&self.syntax)
682    }
683}
684
685// ── KnotParamDecl ────────────────────────────────────────────────────
686
687impl KnotParamDecl {
688    pub fn divert_token(&self) -> Option<SyntaxToken> {
689        support::token(&self.syntax, DIVERT)
690    }
691
692    pub fn is_divert(&self) -> bool {
693        self.divert_token().is_some()
694    }
695
696    pub fn ref_kw(&self) -> Option<SyntaxToken> {
697        support::token(&self.syntax, KW_REF)
698    }
699
700    pub fn is_ref(&self) -> bool {
701        self.ref_kw().is_some()
702    }
703
704    pub fn identifier(&self) -> Option<Identifier> {
705        support::child(&self.syntax)
706    }
707
708    pub fn name(&self) -> Option<String> {
709        self.identifier().and_then(|id| id.name())
710    }
711
712    /// The parameter's type annotation (TM-2, docs/typed-mode-spec.md §3:
713    /// `name: type`), if present.
714    pub fn type_annotation(&self) -> Option<TypeAnnotation> {
715        support::child(&self.syntax)
716    }
717}
718
719// ── StitchDef ────────────────────────────────────────────────────────
720
721impl StitchDef {
722    pub fn header(&self) -> Option<StitchHeader> {
723        support::child(&self.syntax)
724    }
725
726    pub fn body(&self) -> Option<StitchBody> {
727        support::child(&self.syntax)
728    }
729}
730
731// ── StitchHeader ─────────────────────────────────────────────────────
732
733impl StitchHeader {
734    pub fn identifier(&self) -> Option<Identifier> {
735        support::child(&self.syntax)
736    }
737
738    pub fn name(&self) -> Option<String> {
739        self.identifier().and_then(|id| id.name())
740    }
741
742    pub fn params(&self) -> Option<KnotParams> {
743        support::child(&self.syntax)
744    }
745
746    /// The return type annotation after the params (NG-C, issue #1489,
747    /// widened to stitches by #1509: `= name(params): type`), if present —
748    /// the same TM-2 grammar position `KnotHeader::return_type` parses,
749    /// minus the trailing `===` a stitch header never has.
750    pub fn return_type(&self) -> Option<TypeAnnotation> {
751        support::child(&self.syntax)
752    }
753}
754
755// ── StitchBody ───────────────────────────────────────────────────────
756
757impl StitchBody {
758    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
759        support::children(&self.syntax)
760    }
761
762    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
763        support::children(&self.syntax)
764    }
765
766    pub fn choices(&self) -> impl Iterator<Item = Choice> {
767        support::children(&self.syntax)
768    }
769
770    pub fn gathers(&self) -> impl Iterator<Item = Gather> {
771        support::children(&self.syntax)
772    }
773}
774
775// ── ContentLine ──────────────────────────────────────────────────────
776
777impl ContentLine {
778    pub fn mixed_content(&self) -> Option<MixedContent> {
779        support::child(&self.syntax)
780    }
781
782    pub fn divert(&self) -> Option<DivertNode> {
783        support::child(&self.syntax)
784    }
785
786    pub fn tags(&self) -> Option<Tags> {
787        support::child(&self.syntax)
788    }
789}
790
791// ── LogicLine ────────────────────────────────────────────────────────
792
793impl LogicLine {
794    pub fn return_stmt(&self) -> Option<ReturnStmt> {
795        support::child(&self.syntax)
796    }
797
798    pub fn temp_decl(&self) -> Option<TempDecl> {
799        support::child(&self.syntax)
800    }
801
802    pub fn assignment(&self) -> Option<Assignment> {
803        support::child(&self.syntax)
804    }
805
806    /// The `~ await <cond>` `FlowFrame` suspension point, if this logic line is
807    /// one (docs/flow-suspension-spec.md §3).
808    pub fn await_stmt(&self) -> Option<AwaitStmt> {
809        support::child(&self.syntax)
810    }
811
812    /// The T1b `~ { … }` multi-line block body, if this logic line opens one
813    /// (docs/t1b-surface-spec.md §2) rather than a single statement.
814    pub fn stmt_block(&self) -> Option<StmtBlock> {
815        support::child(&self.syntax)
816    }
817}
818
819// ── TagLine ──────────────────────────────────────────────────────────
820
821impl TagLine {
822    pub fn tags(&self) -> Option<Tags> {
823        support::child(&self.syntax)
824    }
825}
826
827// ── AnnotationLine ───────────────────────────────────────────────────
828
829impl AnnotationLine {
830    /// The annotation's name token — the `IDENT` after `@[` (e.g. `effects`
831    /// in `@[effects(pure)]`).
832    pub fn name_token(&self) -> Option<SyntaxToken> {
833        self.syntax
834            .children_with_tokens()
835            .filter_map(rowan::NodeOrToken::into_token)
836            .find(|t| t.kind() == IDENT)
837    }
838
839    /// The raw text between the annotation's balanced `( … )` argument
840    /// parens, if present — `None` for a bare `@[name]`. Mirrors the
841    /// directive channel's raw-string argument contract
842    /// (`brink-ir`'s `ParsedDirective::arg`): the argument mini-grammar is
843    /// parsed downstream, not here.
844    pub fn arg_text(&self) -> Option<String> {
845        let mut depth = 0usize;
846        let mut collecting = false;
847        let mut out = String::new();
848        for el in self.syntax.children_with_tokens() {
849            let rowan::NodeOrToken::Token(tok) = el else {
850                continue;
851            };
852            match tok.kind() {
853                L_PAREN => {
854                    if collecting {
855                        out.push_str(tok.text());
856                    }
857                    depth += 1;
858                    collecting = true;
859                }
860                R_PAREN => {
861                    depth = depth.saturating_sub(1);
862                    if depth == 0 {
863                        return Some(out);
864                    }
865                    out.push_str(tok.text());
866                }
867                _ if collecting => out.push_str(tok.text()),
868                _ => {}
869            }
870        }
871        collecting.then_some(out)
872    }
873}
874
875// ── ReturnStmt ───────────────────────────────────────────────────────
876
877impl ReturnStmt {
878    /// Returns the value expression, if any.
879    ///
880    /// A bare `return` has no child expression node; `return expr` always
881    /// wraps the expression in a typed node (the parser calls `expression()`).
882    pub fn value(&self) -> Option<Expr> {
883        support::child(&self.syntax)
884    }
885
886    /// Returns `true` if the return has a value expression.
887    pub fn has_value(&self) -> bool {
888        self.value().is_some()
889    }
890}
891
892// ── AwaitStmt ────────────────────────────────────────────────────────
893
894impl AwaitStmt {
895    /// The condition expression — the `<cond>` in `await <cond>`
896    /// (docs/flow-suspension-spec.md §3). Absent only for a malformed bare
897    /// `await` with no expression (the parser emits a diagnostic there).
898    pub fn condition(&self) -> Option<Expr> {
899        self.syntax.children().find_map(Expr::cast)
900    }
901}
902
903// ── TempDecl ─────────────────────────────────────────────────────────
904
905impl TempDecl {
906    pub fn identifier(&self) -> Option<Identifier> {
907        support::child(&self.syntax)
908    }
909
910    pub fn name(&self) -> Option<String> {
911        self.identifier().and_then(|id| id.name())
912    }
913
914    /// The ascription's type annotation (TM-2, docs/typed-mode-spec.md §3:
915    /// `~ temp name: type = expr`), if present.
916    pub fn type_annotation(&self) -> Option<TypeAnnotation> {
917        support::child(&self.syntax)
918    }
919
920    pub fn eq_token(&self) -> Option<SyntaxToken> {
921        support::token(&self.syntax, EQ)
922    }
923
924    /// Returns the initializer expression after `=`.
925    pub fn value(&self) -> Option<Expr> {
926        support::child(&self.syntax)
927    }
928}
929
930// ── Assignment ───────────────────────────────────────────────────────
931
932impl Assignment {
933    pub fn target(&self) -> Option<Expr> {
934        self.syntax.children().find_map(Expr::cast)
935    }
936
937    /// The assignment operator token (`=`, `+=`, or `-=`).
938    pub fn op_token(&self) -> Option<SyntaxToken> {
939        self.syntax
940            .children_with_tokens()
941            .filter_map(rowan::NodeOrToken::into_token)
942            .find(|tok| matches!(tok.kind(), EQ | PLUS_EQ | MINUS_EQ))
943    }
944
945    /// Returns the right-hand side value expression (the second `Expr` child).
946    pub fn value(&self) -> Option<Expr> {
947        self.syntax.children().filter_map(Expr::cast).nth(1)
948    }
949}
950
951// ═══════════════════════════════════════════════════════════════════════
952// T1b superset: multi-line `~ { … }` blocks (docs/t1b-surface-spec.md §2)
953// ═══════════════════════════════════════════════════════════════════════
954
955/// A single statement inside a `~ { … }` block body.
956///
957/// Deliberately excludes every weave concept (content, choices, diverts,
958/// gathers, threads) — the seam rule from docs/t1b-surface-spec.md §2:
959/// blocks compute, weave flows.
960#[derive(Clone, PartialEq, Eq, Hash)]
961pub enum BlockStmt {
962    TempDecl(TempDecl),
963    Assignment(Assignment),
964    Return(ReturnStmt),
965    If(IfStmt),
966    While(WhileStmt),
967    For(ForStmt),
968    Break(BreakStmt),
969    Continue(ContinueStmt),
970    ExprStmt(ExprStmt),
971    /// `await <cond>` — a `FlowFrame` suspension point inside a `~ { … }` block
972    /// (docs/flow-suspension-spec.md §3).
973    Await(AwaitStmt),
974}
975
976impl std::fmt::Debug for BlockStmt {
977    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
978        std::fmt::Debug::fmt(self.syntax(), f)
979    }
980}
981
982impl crate::ast::AstNode for BlockStmt {
983    fn can_cast(kind: SyntaxKind) -> bool {
984        matches!(
985            kind,
986            SyntaxKind::TEMP_DECL
987                | SyntaxKind::ASSIGNMENT
988                | SyntaxKind::RETURN_STMT
989                | SyntaxKind::IF_STMT
990                | SyntaxKind::WHILE_STMT
991                | SyntaxKind::FOR_STMT
992                | SyntaxKind::BREAK_STMT
993                | SyntaxKind::CONTINUE_STMT
994                | SyntaxKind::EXPR_STMT
995                | SyntaxKind::AWAIT_STMT
996        )
997    }
998
999    fn cast(node: SyntaxNode) -> Option<Self> {
1000        match node.kind() {
1001            SyntaxKind::TEMP_DECL => TempDecl::cast(node).map(BlockStmt::TempDecl),
1002            SyntaxKind::ASSIGNMENT => Assignment::cast(node).map(BlockStmt::Assignment),
1003            SyntaxKind::RETURN_STMT => ReturnStmt::cast(node).map(BlockStmt::Return),
1004            SyntaxKind::IF_STMT => IfStmt::cast(node).map(BlockStmt::If),
1005            SyntaxKind::WHILE_STMT => WhileStmt::cast(node).map(BlockStmt::While),
1006            SyntaxKind::FOR_STMT => ForStmt::cast(node).map(BlockStmt::For),
1007            SyntaxKind::BREAK_STMT => BreakStmt::cast(node).map(BlockStmt::Break),
1008            SyntaxKind::CONTINUE_STMT => ContinueStmt::cast(node).map(BlockStmt::Continue),
1009            SyntaxKind::EXPR_STMT => ExprStmt::cast(node).map(BlockStmt::ExprStmt),
1010            SyntaxKind::AWAIT_STMT => AwaitStmt::cast(node).map(BlockStmt::Await),
1011            _ => None,
1012        }
1013    }
1014
1015    fn syntax(&self) -> &SyntaxNode {
1016        match self {
1017            BlockStmt::TempDecl(n) => n.syntax(),
1018            BlockStmt::Assignment(n) => n.syntax(),
1019            BlockStmt::Return(n) => n.syntax(),
1020            BlockStmt::If(n) => n.syntax(),
1021            BlockStmt::While(n) => n.syntax(),
1022            BlockStmt::For(n) => n.syntax(),
1023            BlockStmt::Break(n) => n.syntax(),
1024            BlockStmt::Continue(n) => n.syntax(),
1025            BlockStmt::ExprStmt(n) => n.syntax(),
1026            BlockStmt::Await(n) => n.syntax(),
1027        }
1028    }
1029}
1030
1031// ── StmtBlock ────────────────────────────────────────────────────────
1032
1033impl StmtBlock {
1034    /// The statements in this block, in source order.
1035    pub fn stmts(&self) -> impl Iterator<Item = BlockStmt> {
1036        support::children(&self.syntax)
1037    }
1038}
1039
1040// ── IfStmt ───────────────────────────────────────────────────────────
1041
1042impl IfStmt {
1043    /// The condition expression (the first `Expr` child).
1044    pub fn condition(&self) -> Option<Expr> {
1045        self.syntax.children().find_map(Expr::cast)
1046    }
1047
1048    /// The `{ … }` body executed when the condition holds.
1049    pub fn body(&self) -> Option<StmtBlock> {
1050        support::child(&self.syntax)
1051    }
1052
1053    /// The `else` arm, if present.
1054    pub fn else_clause(&self) -> Option<ElseClause> {
1055        support::child(&self.syntax)
1056    }
1057}
1058
1059// ── ElseClause ───────────────────────────────────────────────────────
1060
1061impl ElseClause {
1062    /// The nested `if` for an `else if` chain, if this is one.
1063    pub fn if_stmt(&self) -> Option<IfStmt> {
1064        support::child(&self.syntax)
1065    }
1066
1067    /// The `{ … }` body for a bare `else`, if this is one (mutually
1068    /// exclusive with [`ElseClause::if_stmt`]).
1069    pub fn body(&self) -> Option<StmtBlock> {
1070        support::child(&self.syntax)
1071    }
1072}
1073
1074// ── WhileStmt ────────────────────────────────────────────────────────
1075
1076impl WhileStmt {
1077    pub fn condition(&self) -> Option<Expr> {
1078        self.syntax.children().find_map(Expr::cast)
1079    }
1080
1081    pub fn body(&self) -> Option<StmtBlock> {
1082        support::child(&self.syntax)
1083    }
1084
1085    /// Whether this is the persistent-await form `while await cond { … }`
1086    /// (docs/flow-suspension-spec.md §3) rather than a plain `while` loop. The
1087    /// parser bumps the marker `await` as a direct `IDENT` token child (the
1088    /// `while` keyword is the only other direct `IDENT` token; the condition
1089    /// lives inside an `Expr` node, never a bare token), so the presence of an
1090    /// `IDENT` token spelled `await` here is the unambiguous marker.
1091    pub fn is_await(&self) -> bool {
1092        self.syntax
1093            .children_with_tokens()
1094            .filter_map(rowan::NodeOrToken::into_token)
1095            .any(|tok| tok.kind() == SyntaxKind::IDENT && tok.text() == "await")
1096    }
1097}
1098
1099// ── ForStmt ──────────────────────────────────────────────────────────
1100
1101impl ForStmt {
1102    /// The loop variable name.
1103    pub fn identifier(&self) -> Option<Identifier> {
1104        support::child(&self.syntax)
1105    }
1106
1107    pub fn name(&self) -> Option<String> {
1108        self.identifier().and_then(|id| id.name())
1109    }
1110
1111    /// The iterable expression (after `in`).
1112    pub fn iterable(&self) -> Option<Expr> {
1113        self.syntax.children().find_map(Expr::cast)
1114    }
1115
1116    pub fn body(&self) -> Option<StmtBlock> {
1117        support::child(&self.syntax)
1118    }
1119}
1120
1121// ── ExprStmt ─────────────────────────────────────────────────────────
1122
1123impl ExprStmt {
1124    pub fn expr(&self) -> Option<Expr> {
1125        support::child(&self.syntax)
1126    }
1127}
1128
1129// ═══════════════════════════════════════════════════════════════════════
1130// T1b superset: sigil literals + indexing (docs/t1b-surface-spec.md §3-4)
1131// ═══════════════════════════════════════════════════════════════════════
1132
1133// ── ArrayLiteral ─────────────────────────────────────────────────────
1134
1135impl ArrayLiteral {
1136    pub fn elements(&self) -> impl Iterator<Item = Expr> {
1137        support::children(&self.syntax)
1138    }
1139}
1140
1141// ── MapLiteral ───────────────────────────────────────────────────────
1142
1143impl MapLiteral {
1144    pub fn entries(&self) -> impl Iterator<Item = MapEntry> {
1145        support::children(&self.syntax)
1146    }
1147}
1148
1149// ── MapEntry ─────────────────────────────────────────────────────────
1150
1151impl MapEntry {
1152    /// The key expression (the first `Expr` child).
1153    pub fn key(&self) -> Option<Expr> {
1154        self.syntax.children().find_map(Expr::cast)
1155    }
1156
1157    /// The value expression (the second `Expr` child).
1158    pub fn value(&self) -> Option<Expr> {
1159        self.syntax.children().filter_map(Expr::cast).nth(1)
1160    }
1161}
1162
1163// ── IndexExpr ────────────────────────────────────────────────────────
1164
1165impl IndexExpr {
1166    /// The base being indexed (the first `Expr` child) — `a` in `a[i]`.
1167    pub fn base(&self) -> Option<Expr> {
1168        self.syntax.children().find_map(Expr::cast)
1169    }
1170
1171    /// The index expression (the second `Expr` child) — `i` in `a[i]`.
1172    pub fn index(&self) -> Option<Expr> {
1173        self.syntax.children().filter_map(Expr::cast).nth(1)
1174    }
1175}
1176
1177// ── RangeExpr (NS-A5, docs/stdlib-spec.md §7) ────────────────────────
1178
1179impl RangeExpr {
1180    /// The start bound (the first `Expr` child) — `a` in `a..b`.
1181    pub fn start(&self) -> Option<Expr> {
1182        self.syntax.children().find_map(Expr::cast)
1183    }
1184
1185    /// The end bound (the second `Expr` child) — `b` in `a..b`.
1186    pub fn end(&self) -> Option<Expr> {
1187        self.syntax.children().filter_map(Expr::cast).nth(1)
1188    }
1189
1190    /// `true` for the inclusive `..=` form — detected by the `EQ` token the
1191    /// parser bumped between the dots and the end bound.
1192    pub fn is_inclusive(&self) -> bool {
1193        self.syntax
1194            .children_with_tokens()
1195            .filter_map(rowan::NodeOrToken::into_token)
1196            .any(|t| t.kind() == SyntaxKind::EQ)
1197    }
1198}
1199
1200// ═══════════════════════════════════════════════════════════════════════
1201// TM-4b structs (docs/typed-mode-spec.md §6)
1202// ═══════════════════════════════════════════════════════════════════════
1203
1204// ── StructDecl ───────────────────────────────────────────────────────
1205
1206impl StructDecl {
1207    pub fn identifier(&self) -> Option<Identifier> {
1208        support::child(&self.syntax)
1209    }
1210
1211    pub fn name(&self) -> Option<String> {
1212        self.identifier().and_then(|id| id.name())
1213    }
1214
1215    /// The declared fields, in source order.
1216    pub fn fields(&self) -> impl Iterator<Item = StructFieldDecl> {
1217        support::children(&self.syntax)
1218    }
1219}
1220
1221// ── StructFieldDecl ──────────────────────────────────────────────────
1222
1223impl StructFieldDecl {
1224    pub fn identifier(&self) -> Option<Identifier> {
1225        support::child(&self.syntax)
1226    }
1227
1228    pub fn name(&self) -> Option<String> {
1229        self.identifier().and_then(|id| id.name())
1230    }
1231
1232    /// The field's declared type (value position — mirrors the
1233    /// construction literal's field-init value position, §6).
1234    pub fn type_expr(&self) -> Option<TypeExpr> {
1235        support::child(&self.syntax)
1236    }
1237}
1238
1239// ── StructLiteral ────────────────────────────────────────────────────
1240
1241impl StructLiteral {
1242    /// The leading shape-name identifier (e.g. `Point` in `Point#{…}`).
1243    pub fn identifier(&self) -> Option<Identifier> {
1244        support::child(&self.syntax)
1245    }
1246
1247    pub fn shape_name(&self) -> Option<String> {
1248        self.identifier().and_then(|id| id.name())
1249    }
1250
1251    /// The field initializers, in source order.
1252    pub fn fields(&self) -> impl Iterator<Item = StructFieldInit> {
1253        support::children(&self.syntax)
1254    }
1255}
1256
1257// ── StructFieldInit ──────────────────────────────────────────────────
1258
1259impl StructFieldInit {
1260    pub fn identifier(&self) -> Option<Identifier> {
1261        support::child(&self.syntax)
1262    }
1263
1264    pub fn name(&self) -> Option<String> {
1265        self.identifier().and_then(|id| id.name())
1266    }
1267
1268    /// The initializer expression after `:`.
1269    pub fn value(&self) -> Option<Expr> {
1270        support::child(&self.syntax)
1271    }
1272}
1273
1274// ── FieldAccessExpr ──────────────────────────────────────────────────
1275
1276impl FieldAccessExpr {
1277    /// The expression being accessed (the first, and only, `Expr` child) —
1278    /// `base` in `base.field`.
1279    pub fn base(&self) -> Option<Expr> {
1280        self.syntax.children().find_map(Expr::cast)
1281    }
1282
1283    /// The field name identifier after `.`.
1284    pub fn field(&self) -> Option<Identifier> {
1285        support::child(&self.syntax)
1286    }
1287
1288    pub fn field_name(&self) -> Option<String> {
1289        self.field().and_then(|id| id.name())
1290    }
1291}
1292
1293// ═══════════════════════════════════════════════════════════════════════
1294// T1c function values (docs/t1c-spec.md §2)
1295// ═══════════════════════════════════════════════════════════════════════
1296
1297// ── FnLiteral ────────────────────────────────────────────────────────
1298
1299impl FnLiteral {
1300    /// The static target path (the first `PATH` child) — `heal` in
1301    /// `#fn(heal, hp)`. `None` on malformed input (`#fn()`, `#fn(1)`).
1302    pub fn target(&self) -> Option<Path> {
1303        support::child(&self.syntax)
1304    }
1305
1306    /// The bound-argument expressions after the target, in source order.
1307    /// The target `PATH` is itself castable to `Expr::Path`, so this skips
1308    /// the first `Expr` child iff it is that target node.
1309    pub fn args(&self) -> impl Iterator<Item = Expr> {
1310        let target_node = self.target().map(|t| t.syntax().clone());
1311        self.syntax
1312            .children()
1313            .filter_map(Expr::cast)
1314            .filter(move |e| Some(e.syntax()) != target_node.as_ref())
1315    }
1316}
1317
1318// ═══════════════════════════════════════════════════════════════════════
1319// T1e path projections (docs/t1e-spec.md §2)
1320// ═══════════════════════════════════════════════════════════════════════
1321
1322// ── RefExpr ──────────────────────────────────────────────────────────
1323
1324impl RefExpr {
1325    pub fn ref_kw(&self) -> Option<SyntaxToken> {
1326        support::token(&self.syntax, KW_REF)
1327    }
1328
1329    /// The lvalue-shaped operand after `ref` — a plain path, a dotted field
1330    /// chain, `[…]` indexing, or a mix. `None` on malformed input (`ref` at
1331    /// end of input, or followed by a token that starts no expression).
1332    pub fn operand(&self) -> Option<Expr> {
1333        support::child(&self.syntax)
1334    }
1335}
1336
1337// ── MixedContent ─────────────────────────────────────────────────────
1338
1339impl MixedContent {
1340    pub fn texts(&self) -> impl Iterator<Item = Text> {
1341        support::children(&self.syntax)
1342    }
1343
1344    pub fn glue_nodes(&self) -> impl Iterator<Item = GlueNode> {
1345        support::children(&self.syntax)
1346    }
1347
1348    pub fn inline_logics(&self) -> impl Iterator<Item = InlineLogic> {
1349        support::children(&self.syntax)
1350    }
1351
1352    pub fn escapes(&self) -> impl Iterator<Item = Escape> {
1353        support::children(&self.syntax)
1354    }
1355}
1356
1357// ── Choice ───────────────────────────────────────────────────────────
1358
1359impl Choice {
1360    pub fn bullets(&self) -> Option<ChoiceBullets> {
1361        support::child(&self.syntax)
1362    }
1363
1364    pub fn label(&self) -> Option<Label> {
1365        support::child(&self.syntax)
1366    }
1367
1368    pub fn conditions(&self) -> impl Iterator<Item = ChoiceCondition> {
1369        support::children(&self.syntax)
1370    }
1371
1372    pub fn start_content(&self) -> Option<ChoiceStartContent> {
1373        support::child(&self.syntax)
1374    }
1375
1376    pub fn bracket_content(&self) -> Option<ChoiceBracketContent> {
1377        support::child(&self.syntax)
1378    }
1379
1380    pub fn inner_content(&self) -> Option<ChoiceInnerContent> {
1381        support::child(&self.syntax)
1382    }
1383
1384    pub fn divert(&self) -> Option<DivertNode> {
1385        support::child(&self.syntax)
1386    }
1387
1388    pub fn tags(&self) -> Option<Tags> {
1389        support::child(&self.syntax)
1390    }
1391
1392    /// Returns an iterator over all TAGS children (tags can appear on
1393    /// each content region within a choice line).
1394    pub fn all_tags(&self) -> impl Iterator<Item = Tags> {
1395        support::children(&self.syntax)
1396    }
1397}
1398
1399// ── ChoiceBullets ────────────────────────────────────────────────────
1400
1401impl ChoiceBullets {
1402    /// Number of bullet characters (nesting depth).
1403    pub fn depth(&self) -> usize {
1404        self.syntax
1405            .children_with_tokens()
1406            .filter_map(rowan::NodeOrToken::into_token)
1407            .filter(|tok| matches!(tok.kind(), STAR | PLUS))
1408            .count()
1409    }
1410
1411    /// Returns `true` if using `+` (sticky), `false` if using `*`.
1412    ///
1413    /// Determined by the first bullet token, matching the reference ink
1414    /// compiler's behavior for degenerate mixed-bullet cases.
1415    pub fn is_sticky(&self) -> bool {
1416        self.syntax
1417            .children_with_tokens()
1418            .filter_map(rowan::NodeOrToken::into_token)
1419            .find(|tok| matches!(tok.kind(), STAR | PLUS))
1420            .is_some_and(|tok| tok.kind() == PLUS)
1421    }
1422
1423    /// Returns `true` if bullets mix `*` and `+` (e.g. `*+`, `+*`).
1424    ///
1425    /// Mixed bullets are degenerate input — a diagnostic pass should flag them.
1426    pub fn is_mixed(&self) -> bool {
1427        let mut has_star = false;
1428        let mut has_plus = false;
1429        for tok in self
1430            .syntax
1431            .children_with_tokens()
1432            .filter_map(rowan::NodeOrToken::into_token)
1433        {
1434            match tok.kind() {
1435                STAR => has_star = true,
1436                PLUS => has_plus = true,
1437                _ => {}
1438            }
1439        }
1440        has_star && has_plus
1441    }
1442}
1443
1444// ── Label ────────────────────────────────────────────────────────────
1445
1446impl Label {
1447    pub fn identifier(&self) -> Option<Identifier> {
1448        support::child(&self.syntax)
1449    }
1450
1451    pub fn name(&self) -> Option<String> {
1452        self.identifier().and_then(|id| id.name())
1453    }
1454}
1455
1456// ── Gather ───────────────────────────────────────────────────────────
1457
1458impl Gather {
1459    pub fn dashes(&self) -> Option<GatherDashes> {
1460        support::child(&self.syntax)
1461    }
1462
1463    pub fn label(&self) -> Option<Label> {
1464        support::child(&self.syntax)
1465    }
1466
1467    pub fn mixed_content(&self) -> Option<MixedContent> {
1468        support::child(&self.syntax)
1469    }
1470
1471    /// Inline choice on the same line as the gather (e.g. `- * hello`).
1472    pub fn choice(&self) -> Option<Choice> {
1473        support::child(&self.syntax)
1474    }
1475
1476    pub fn divert(&self) -> Option<DivertNode> {
1477        support::child(&self.syntax)
1478    }
1479
1480    pub fn tags(&self) -> Option<Tags> {
1481        support::child(&self.syntax)
1482    }
1483}
1484
1485// ── GatherDashes ─────────────────────────────────────────────────────
1486
1487impl GatherDashes {
1488    /// Number of dashes (nesting depth).
1489    pub fn depth(&self) -> usize {
1490        support::tokens(&self.syntax, MINUS).count()
1491    }
1492}
1493
1494// ── Tags ─────────────────────────────────────────────────────────────
1495
1496impl Tags {
1497    pub fn tags(&self) -> impl Iterator<Item = Tag> {
1498        support::children(&self.syntax)
1499    }
1500}
1501
1502// ── Tag ──────────────────────────────────────────────────────────────
1503
1504impl Tag {
1505    /// Returns the tag value with the leading `#` stripped.
1506    ///
1507    /// Walks tokens directly rather than string-manipulating the full node text.
1508    /// The parser guarantees a `HASH` token is always present.
1509    pub fn text(&self) -> String {
1510        self.syntax
1511            .children_with_tokens()
1512            .filter_map(rowan::NodeOrToken::into_token)
1513            .filter(|tok| tok.kind() != HASH)
1514            .map(|tok| tok.text().to_string())
1515            .collect::<String>()
1516            .trim()
1517            .to_string()
1518    }
1519}
1520
1521// ── InlineLogic ──────────────────────────────────────────────────────
1522
1523impl InlineLogic {
1524    pub fn inner_expression(&self) -> Option<InnerExpression> {
1525        support::child(&self.syntax)
1526    }
1527
1528    pub fn conditional(&self) -> Option<ConditionalWithExpr> {
1529        support::child(&self.syntax)
1530    }
1531
1532    pub fn sequence(&self) -> Option<SequenceWithAnnotation> {
1533        support::child(&self.syntax)
1534    }
1535
1536    pub fn implicit_sequence(&self) -> Option<ImplicitSequence> {
1537        support::child(&self.syntax)
1538    }
1539
1540    pub fn multiline_conditional(&self) -> Option<MultilineConditional> {
1541        support::child(&self.syntax)
1542    }
1543}
1544
1545// ── MultilineBlock ───────────────────────────────────────────────────
1546
1547impl MultilineBlock {
1548    pub fn conditional(&self) -> Option<ConditionalWithExpr> {
1549        support::child(&self.syntax)
1550    }
1551
1552    pub fn sequence(&self) -> Option<SequenceWithAnnotation> {
1553        support::child(&self.syntax)
1554    }
1555
1556    pub fn branches_cond(&self) -> Option<MultilineBranchesCond> {
1557        support::child(&self.syntax)
1558    }
1559}
1560
1561// ── SequenceWithAnnotation ───────────────────────────────────────────
1562
1563impl SequenceWithAnnotation {
1564    pub fn symbol_annotation(&self) -> Option<SequenceSymbolAnnotation> {
1565        support::child(&self.syntax)
1566    }
1567
1568    pub fn word_annotation(&self) -> Option<SequenceWordAnnotation> {
1569        support::child(&self.syntax)
1570    }
1571
1572    pub fn inline_branches(&self) -> Option<InlineBranchesSeq> {
1573        support::child(&self.syntax)
1574    }
1575
1576    pub fn multiline_branches(&self) -> Option<MultilineBranchesSeq> {
1577        support::child(&self.syntax)
1578    }
1579}
1580
1581// ── SequenceSymbolAnnotation ──────────────────────────────────────────
1582
1583impl SequenceSymbolAnnotation {
1584    pub fn amp_token(&self) -> Option<SyntaxToken> {
1585        support::token(&self.syntax, AMP)
1586    }
1587
1588    pub fn bang_token(&self) -> Option<SyntaxToken> {
1589        support::token(&self.syntax, BANG)
1590    }
1591
1592    pub fn tilde_token(&self) -> Option<SyntaxToken> {
1593        support::token(&self.syntax, TILDE)
1594    }
1595
1596    pub fn dollar_token(&self) -> Option<SyntaxToken> {
1597        support::token(&self.syntax, DOLLAR)
1598    }
1599}
1600
1601// ── SequenceWordAnnotation ───────────────────────────────────────────
1602
1603impl SequenceWordAnnotation {
1604    pub fn stopping_kw(&self) -> Option<SyntaxToken> {
1605        support::token(&self.syntax, KW_STOPPING)
1606    }
1607
1608    pub fn cycle_kw(&self) -> Option<SyntaxToken> {
1609        support::token(&self.syntax, KW_CYCLE)
1610    }
1611
1612    pub fn shuffle_kw(&self) -> Option<SyntaxToken> {
1613        support::token(&self.syntax, KW_SHUFFLE)
1614    }
1615
1616    pub fn once_kw(&self) -> Option<SyntaxToken> {
1617        support::token(&self.syntax, KW_ONCE)
1618    }
1619}
1620
1621// ── InlineBranchesSeq ────────────────────────────────────────────────
1622
1623impl InlineBranchesSeq {
1624    pub fn branches(&self) -> impl Iterator<Item = BranchContent> {
1625        support::children(&self.syntax)
1626    }
1627}
1628
1629// ── InlineBranchesCond ───────────────────────────────────────────────
1630
1631impl InlineBranchesCond {
1632    pub fn branches(&self) -> impl Iterator<Item = BranchContent> {
1633        support::children(&self.syntax)
1634    }
1635}
1636
1637// ── MultilineBranchesSeq ─────────────────────────────────────────────
1638
1639impl MultilineBranchesSeq {
1640    pub fn branches(&self) -> impl Iterator<Item = MultilineBranchSeq> {
1641        support::children(&self.syntax)
1642    }
1643}
1644
1645// ── MultilineBranchesCond ────────────────────────────────────────────
1646
1647impl MultilineBranchesCond {
1648    pub fn branches(&self) -> impl Iterator<Item = MultilineBranchCond> {
1649        support::children(&self.syntax)
1650    }
1651}
1652
1653// ── MultilineBranchSeq ───────────────────────────────────────────────
1654
1655impl MultilineBranchSeq {
1656    pub fn body(&self) -> Option<MultilineBranchBody> {
1657        support::child(&self.syntax)
1658    }
1659}
1660
1661// ── MultilineBranchCond ──────────────────────────────────────────────
1662
1663impl MultilineBranchCond {
1664    /// Returns the branch condition expression (if not an else branch).
1665    pub fn condition(&self) -> Option<Expr> {
1666        support::child(&self.syntax)
1667    }
1668
1669    pub fn body(&self) -> Option<MultilineBranchBody> {
1670        support::child(&self.syntax)
1671    }
1672
1673    pub fn else_kw(&self) -> Option<SyntaxToken> {
1674        support::token(&self.syntax, KW_ELSE)
1675    }
1676
1677    pub fn is_else(&self) -> bool {
1678        self.else_kw().is_some()
1679    }
1680}
1681
1682// ── ConditionalWithExpr ──────────────────────────────────────────────
1683
1684impl ConditionalWithExpr {
1685    /// Returns the condition expression.
1686    pub fn condition(&self) -> Option<Expr> {
1687        support::child(&self.syntax)
1688    }
1689
1690    pub fn inline_branches(&self) -> Option<InlineBranchesCond> {
1691        support::child(&self.syntax)
1692    }
1693
1694    pub fn multiline_branches(&self) -> Option<MultilineBranchesCond> {
1695        support::child(&self.syntax)
1696    }
1697
1698    pub fn branchless_body(&self) -> Option<BranchlessCondBody> {
1699        support::child(&self.syntax)
1700    }
1701}
1702
1703// ── BranchlessCondBody ───────────────────────────────────────────────
1704
1705impl BranchlessCondBody {
1706    pub fn texts(&self) -> impl Iterator<Item = Text> {
1707        support::children(&self.syntax)
1708    }
1709
1710    pub fn inline_logics(&self) -> impl Iterator<Item = InlineLogic> {
1711        support::children(&self.syntax)
1712    }
1713
1714    pub fn glue_nodes(&self) -> impl Iterator<Item = GlueNode> {
1715        support::children(&self.syntax)
1716    }
1717
1718    pub fn escapes(&self) -> impl Iterator<Item = Escape> {
1719        support::children(&self.syntax)
1720    }
1721
1722    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
1723        support::children(&self.syntax)
1724    }
1725
1726    pub fn divert(&self) -> Option<DivertNode> {
1727        support::child(&self.syntax)
1728    }
1729
1730    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
1731        support::children(&self.syntax)
1732    }
1733
1734    pub fn else_branch(&self) -> Option<ElseBranch> {
1735        support::child(&self.syntax)
1736    }
1737}
1738
1739// ── ElseBranch ───────────────────────────────────────────────────────
1740
1741impl ElseBranch {
1742    pub fn branch(&self) -> Option<MultilineBranchCond> {
1743        support::child(&self.syntax)
1744    }
1745}
1746
1747// ── MultilineConditional ─────────────────────────────────────────────
1748
1749impl MultilineConditional {
1750    pub fn branches(&self) -> impl Iterator<Item = MultilineBranchCond> {
1751        support::children(&self.syntax)
1752    }
1753}
1754
1755// ── ImplicitSequence ─────────────────────────────────────────────────
1756
1757impl ImplicitSequence {
1758    pub fn branches(&self) -> impl Iterator<Item = BranchContent> {
1759        support::children(&self.syntax)
1760    }
1761}
1762
1763// ── PrefixExpr ───────────────────────────────────────────────────────
1764
1765impl PrefixExpr {
1766    pub fn op_token(&self) -> Option<SyntaxToken> {
1767        self.syntax
1768            .children_with_tokens()
1769            .filter_map(rowan::NodeOrToken::into_token)
1770            .find(|tok| matches!(tok.kind(), MINUS | BANG | KW_NOT))
1771    }
1772
1773    /// Returns the operand expression.
1774    pub fn operand(&self) -> Option<Expr> {
1775        support::child(&self.syntax)
1776    }
1777}
1778
1779// ── PostfixExpr ──────────────────────────────────────────────────────
1780
1781impl PostfixExpr {
1782    /// Returns the first operator token (`PLUS` for `++`, `MINUS` for `--`).
1783    /// Both operators are two adjacent tokens inside this node.
1784    pub fn op_token(&self) -> Option<SyntaxToken> {
1785        self.syntax
1786            .children_with_tokens()
1787            .filter_map(rowan::NodeOrToken::into_token)
1788            .find(|tok| matches!(tok.kind(), PLUS | MINUS))
1789    }
1790
1791    /// Returns the operand expression.
1792    pub fn operand(&self) -> Option<Expr> {
1793        support::child(&self.syntax)
1794    }
1795}
1796
1797// ── InfixExpr ────────────────────────────────────────────────────────
1798
1799impl InfixExpr {
1800    pub fn op_token(&self) -> Option<SyntaxToken> {
1801        self.syntax
1802            .children_with_tokens()
1803            .filter_map(rowan::NodeOrToken::into_token)
1804            .find(|tok| {
1805                matches!(
1806                    tok.kind(),
1807                    PLUS | MINUS
1808                        | STAR
1809                        | SLASH
1810                        | PERCENT
1811                        | CARET
1812                        | EQ_EQ
1813                        | BANG_EQ
1814                        | LT
1815                        | GT
1816                        | LT_EQ
1817                        | GT_EQ
1818                        | KW_AND
1819                        | AMP_AMP
1820                        | KW_OR
1821                        | PIPE
1822                        | KW_MOD
1823                        | KW_HAS
1824                        | KW_HASNT
1825                        | QUESTION
1826                        | BANG_QUESTION
1827                        | PLUS_EQ
1828                        | MINUS_EQ
1829                )
1830            })
1831    }
1832
1833    pub fn lhs(&self) -> Option<Expr> {
1834        self.syntax.children().find_map(Expr::cast)
1835    }
1836
1837    pub fn rhs(&self) -> Option<Expr> {
1838        self.syntax.children().filter_map(Expr::cast).nth(1)
1839    }
1840}
1841
1842// ── FunctionCall ─────────────────────────────────────────────────────
1843
1844impl FunctionCall {
1845    pub fn identifier(&self) -> Option<Identifier> {
1846        support::child(&self.syntax)
1847    }
1848
1849    pub fn name(&self) -> Option<String> {
1850        self.identifier().and_then(|id| id.name())
1851    }
1852
1853    pub fn arg_list(&self) -> Option<ArgList> {
1854        support::child(&self.syntax)
1855    }
1856}
1857
1858// ── CallExpr (computed-callee call attempt, issue #869) ──────────────
1859
1860impl CallExpr {
1861    /// The callee expression — the first (and only non-`ARG_LIST`) child.
1862    /// Always some non-identifier-immediately-followed-by-`(` shape
1863    /// (`FUNCTION_CALL`'s bare-name fast path never reaches this node).
1864    pub fn callee(&self) -> Option<Expr> {
1865        self.syntax.children().find_map(Expr::cast)
1866    }
1867
1868    pub fn arg_list(&self) -> Option<ArgList> {
1869        support::child(&self.syntax)
1870    }
1871}
1872
1873// ── ArgList ──────────────────────────────────────────────────────────
1874
1875impl ArgList {
1876    /// Number of arguments (child expression nodes).
1877    pub fn arg_count(&self) -> usize {
1878        self.syntax
1879            .children()
1880            .filter(|child| child.kind() != SyntaxKind::ERROR)
1881            .count()
1882    }
1883
1884    /// Iterator over the argument expressions.
1885    pub fn args(&self) -> impl Iterator<Item = Expr> {
1886        support::children(&self.syntax)
1887    }
1888}
1889
1890// ── DivertTargetExpr ─────────────────────────────────────────────────
1891
1892impl DivertTargetExpr {
1893    pub fn target(&self) -> Option<Path> {
1894        support::child(&self.syntax)
1895    }
1896}
1897
1898// ── ListExpr ─────────────────────────────────────────────────────────
1899
1900impl ListExpr {
1901    pub fn items(&self) -> impl Iterator<Item = Path> {
1902        support::children(&self.syntax)
1903    }
1904}
1905
1906// ── DivertNode ───────────────────────────────────────────────────────
1907
1908impl DivertNode {
1909    pub fn thread_start(&self) -> Option<ThreadStart> {
1910        support::child(&self.syntax)
1911    }
1912
1913    pub fn tunnel_onwards(&self) -> Option<TunnelOnwardsNode> {
1914        support::child(&self.syntax)
1915    }
1916
1917    pub fn tunnel_call(&self) -> Option<TunnelCallNode> {
1918        support::child(&self.syntax)
1919    }
1920
1921    pub fn simple_divert(&self) -> Option<SimpleDivert> {
1922        support::child(&self.syntax)
1923    }
1924}
1925
1926// ── SimpleDivert ─────────────────────────────────────────────────────
1927
1928impl SimpleDivert {
1929    pub fn targets(&self) -> impl Iterator<Item = DivertTargetWithArgs> {
1930        support::children(&self.syntax)
1931    }
1932}
1933
1934// ── DivertTargetWithArgs ─────────────────────────────────────────────
1935
1936impl DivertTargetWithArgs {
1937    pub fn path(&self) -> Option<Path> {
1938        support::child(&self.syntax)
1939    }
1940
1941    pub fn done_kw(&self) -> Option<SyntaxToken> {
1942        support::token(&self.syntax, KW_DONE)
1943    }
1944
1945    pub fn end_kw(&self) -> Option<SyntaxToken> {
1946        support::token(&self.syntax, KW_END)
1947    }
1948
1949    pub fn arg_list(&self) -> Option<ArgList> {
1950        support::child(&self.syntax)
1951    }
1952}
1953
1954// ── ThreadStart ──────────────────────────────────────────────────────
1955
1956impl ThreadStart {
1957    /// Returns the target path.
1958    ///
1959    /// The parser produces a `PATH` child directly (not wrapped in
1960    /// `DivertTargetWithArgs`), so this returns `Option<Path>`.
1961    pub fn target(&self) -> Option<Path> {
1962        support::child(&self.syntax)
1963    }
1964
1965    pub fn arg_list(&self) -> Option<ArgList> {
1966        support::child(&self.syntax)
1967    }
1968}
1969
1970// ── TunnelOnwardsNode ────────────────────────────────────────────────
1971
1972impl TunnelOnwardsNode {
1973    pub fn targets(&self) -> impl Iterator<Item = DivertTargetWithArgs> {
1974        support::children(&self.syntax)
1975    }
1976
1977    pub fn tunnel_call(&self) -> Option<TunnelCallNode> {
1978        support::child(&self.syntax)
1979    }
1980}
1981
1982// ── TunnelCallNode ──────────────────────────────────────────────────
1983
1984impl TunnelCallNode {
1985    pub fn targets(&self) -> impl Iterator<Item = DivertTargetWithArgs> {
1986        support::children(&self.syntax)
1987    }
1988}
1989
1990// ── Identifier ───────────────────────────────────────────────────────
1991
1992impl Identifier {
1993    pub fn ident_token(&self) -> Option<SyntaxToken> {
1994        support::token(&self.syntax, IDENT)
1995    }
1996
1997    /// Returns the name text, accepting either `IDENT` or keyword tokens
1998    /// (ink keywords are contextual and may appear as identifiers).
1999    pub fn name(&self) -> Option<String> {
2000        self.ident_token()
2001            .or_else(|| {
2002                self.syntax
2003                    .children_with_tokens()
2004                    .filter_map(rowan::NodeOrToken::into_token)
2005                    .find(|t| t.kind().is_keyword())
2006            })
2007            .map(|t| t.text().to_string())
2008    }
2009}
2010
2011// ── Path ─────────────────────────────────────────────────────────────
2012
2013impl Path {
2014    /// Iterator over the segment tokens (`IDENT` or keyword tokens between dots).
2015    pub fn segments(&self) -> impl Iterator<Item = SyntaxToken> {
2016        self.syntax
2017            .children_with_tokens()
2018            .filter_map(rowan::NodeOrToken::into_token)
2019            .filter(|t| t.kind() == IDENT || t.kind().is_keyword())
2020    }
2021
2022    /// Full dotted name (e.g. `"knot.stitch"`).
2023    pub fn full_name(&self) -> String {
2024        self.segments()
2025            .map(|t| t.text().to_string())
2026            .collect::<Vec<_>>()
2027            .join(".")
2028    }
2029}
2030
2031// ── VarDecl ──────────────────────────────────────────────────────────
2032
2033impl VarDecl {
2034    pub fn identifier(&self) -> Option<Identifier> {
2035        support::child(&self.syntax)
2036    }
2037
2038    pub fn name(&self) -> Option<String> {
2039        self.identifier().and_then(|id| id.name())
2040    }
2041
2042    /// The declared type annotation (TM-2, docs/typed-mode-spec.md §3:
2043    /// `VAR name: type = expr`), if present.
2044    pub fn type_annotation(&self) -> Option<TypeAnnotation> {
2045        support::child(&self.syntax)
2046    }
2047
2048    /// Returns the initializer expression after `=`.
2049    pub fn value(&self) -> Option<Expr> {
2050        support::child(&self.syntax)
2051    }
2052}
2053
2054// ── ConstDecl ────────────────────────────────────────────────────────
2055
2056impl ConstDecl {
2057    pub fn identifier(&self) -> Option<Identifier> {
2058        support::child(&self.syntax)
2059    }
2060
2061    pub fn name(&self) -> Option<String> {
2062        self.identifier().and_then(|id| id.name())
2063    }
2064
2065    /// The declared type annotation (TM-2, docs/typed-mode-spec.md §3:
2066    /// `CONST name: type = expr`), if present.
2067    pub fn type_annotation(&self) -> Option<TypeAnnotation> {
2068        support::child(&self.syntax)
2069    }
2070
2071    /// Returns the initializer expression after `=`.
2072    pub fn value(&self) -> Option<Expr> {
2073        support::child(&self.syntax)
2074    }
2075}
2076
2077// ── ListDecl ─────────────────────────────────────────────────────────
2078
2079impl ListDecl {
2080    pub fn identifier(&self) -> Option<Identifier> {
2081        support::child(&self.syntax)
2082    }
2083
2084    pub fn name(&self) -> Option<String> {
2085        self.identifier().and_then(|id| id.name())
2086    }
2087
2088    pub fn definition(&self) -> Option<ListDef> {
2089        support::child(&self.syntax)
2090    }
2091}
2092
2093// ── ListDef ──────────────────────────────────────────────────────────
2094
2095impl ListDef {
2096    pub fn members(&self) -> impl Iterator<Item = ListMember> {
2097        support::children(&self.syntax)
2098    }
2099}
2100
2101// ── ListMember ───────────────────────────────────────────────────────
2102
2103impl ListMember {
2104    pub fn on_member(&self) -> Option<ListMemberOn> {
2105        support::child(&self.syntax)
2106    }
2107
2108    pub fn off_member(&self) -> Option<ListMemberOff> {
2109        support::child(&self.syntax)
2110    }
2111}
2112
2113// ── ListMemberOn ─────────────────────────────────────────────────────
2114
2115impl ListMemberOn {
2116    pub fn name_token(&self) -> Option<SyntaxToken> {
2117        // Ink keywords are contextual — accept IDENT or keywords as member names.
2118        support::ident_or_keyword_token(&self.syntax)
2119    }
2120
2121    pub fn name(&self) -> Option<String> {
2122        self.name_token().map(|t| t.text().to_string())
2123    }
2124
2125    pub fn value_token(&self) -> Option<SyntaxToken> {
2126        support::token(&self.syntax, INTEGER)
2127    }
2128
2129    /// Returns the explicit integer value assigned to this member, if any.
2130    pub fn value(&self) -> Option<i64> {
2131        self.value_token()
2132            .and_then(|t| t.text().parse::<i64>().ok())
2133    }
2134}
2135
2136// ── ListMemberOff ────────────────────────────────────────────────────
2137
2138impl ListMemberOff {
2139    pub fn name_token(&self) -> Option<SyntaxToken> {
2140        // Ink keywords are contextual — accept IDENT or keywords as member names.
2141        support::ident_or_keyword_token(&self.syntax)
2142    }
2143
2144    pub fn name(&self) -> Option<String> {
2145        self.name_token().map(|t| t.text().to_string())
2146    }
2147
2148    pub fn value_token(&self) -> Option<SyntaxToken> {
2149        support::token(&self.syntax, INTEGER)
2150    }
2151
2152    /// Returns the explicit integer value assigned to this member, if any.
2153    pub fn value(&self) -> Option<i64> {
2154        self.value_token()
2155            .and_then(|t| t.text().parse::<i64>().ok())
2156    }
2157}
2158
2159// ── FunctionParamList ────────────────────────────────────────────────
2160
2161impl FunctionParamList {
2162    /// Iterator over the `Identifier` nodes in the param list.
2163    pub fn params(&self) -> impl Iterator<Item = Identifier> {
2164        support::children(&self.syntax)
2165    }
2166}
2167
2168// ── IntegerLit ───────────────────────────────────────────────────────
2169
2170impl IntegerLit {
2171    pub fn value_token(&self) -> Option<SyntaxToken> {
2172        support::token(&self.syntax, INTEGER)
2173    }
2174
2175    pub fn value(&self) -> Option<i64> {
2176        self.value_token()
2177            .and_then(|t| t.text().parse::<i64>().ok())
2178    }
2179}
2180
2181// ── FloatLit ─────────────────────────────────────────────────────────
2182
2183impl FloatLit {
2184    pub fn value_token(&self) -> Option<SyntaxToken> {
2185        support::token(&self.syntax, FLOAT)
2186    }
2187
2188    pub fn value(&self) -> Option<f64> {
2189        self.value_token()
2190            .and_then(|t| t.text().parse::<f64>().ok())
2191    }
2192}
2193
2194// ── StringLit ────────────────────────────────────────────────────────
2195
2196impl StringLit {
2197    /// Returns the raw content between the quotes (excluding the quotes themselves).
2198    ///
2199    /// The opening quote is always present (the parser enters `string_literal`
2200    /// only on a `QUOTE` token). The closing quote may be absent if the string
2201    /// is unterminated — the parser emits an error and closes the node without
2202    /// consuming a trailing `QUOTE`. The `strip_suffix` fallback handles that
2203    /// error-recovery case.
2204    pub fn raw_text(&self) -> String {
2205        let full = self.syntax.text().to_string();
2206        let trimmed = full.strip_prefix('"').unwrap_or(&full);
2207        trimmed.strip_suffix('"').unwrap_or(trimmed).to_string()
2208    }
2209}
2210
2211// ── BooleanLit ───────────────────────────────────────────────────────
2212
2213impl BooleanLit {
2214    pub fn value(&self) -> Option<bool> {
2215        let tok = self
2216            .syntax
2217            .children_with_tokens()
2218            .filter_map(rowan::NodeOrToken::into_token)
2219            .find(|tok| matches!(tok.kind(), KW_TRUE | KW_FALSE))?;
2220        match tok.kind() {
2221            KW_TRUE => Some(true),
2222            KW_FALSE => Some(false),
2223            _ => None,
2224        }
2225    }
2226}
2227
2228// ── AuthorWarning ────────────────────────────────────────────────────
2229
2230impl AuthorWarning {
2231    /// Returns the warning text with the `TODO:` prefix stripped.
2232    ///
2233    /// Walks tokens directly — skips the `KW_TODO` token and the optional
2234    /// `COLON`, then collects remaining content until `NEWLINE`.
2235    pub fn text(&self) -> String {
2236        self.syntax
2237            .children_with_tokens()
2238            .filter_map(rowan::NodeOrToken::into_token)
2239            .skip_while(|tok| matches!(tok.kind(), KW_TODO | COLON) || tok.kind().is_trivia())
2240            .take_while(|tok| tok.kind() != NEWLINE)
2241            .map(|tok| tok.text().to_string())
2242            .collect::<String>()
2243            .trim()
2244            .to_string()
2245    }
2246}
2247
2248// ── ChoiceCondition ──────────────────────────────────────────────────
2249
2250impl ChoiceCondition {
2251    /// Returns the condition expression inside `{ expr }`.
2252    pub fn expr(&self) -> Option<Expr> {
2253        support::child(&self.syntax)
2254    }
2255}
2256
2257// ── InnerExpression ──────────────────────────────────────────────────
2258
2259impl InnerExpression {
2260    /// Returns the wrapped expression.
2261    pub fn expr(&self) -> Option<Expr> {
2262        support::child(&self.syntax)
2263    }
2264}
2265
2266// ── ParenExpr ────────────────────────────────────────────────────────
2267
2268impl ParenExpr {
2269    /// Returns the inner expression inside `( expr )`.
2270    pub fn inner(&self) -> Option<Expr> {
2271        support::child(&self.syntax)
2272    }
2273}
2274
2275// ── BranchContent (extra) ────────────────────────────────────────────
2276
2277impl BranchContent {
2278    pub fn divert(&self) -> Option<DivertNode> {
2279        support::child(&self.syntax)
2280    }
2281}
2282
2283// ── MultilineBranchBody ──────────────────────────────────────────────
2284
2285impl MultilineBranchBody {
2286    pub fn texts(&self) -> impl Iterator<Item = Text> {
2287        support::children(&self.syntax)
2288    }
2289
2290    pub fn inline_logics(&self) -> impl Iterator<Item = InlineLogic> {
2291        support::children(&self.syntax)
2292    }
2293
2294    pub fn glue_nodes(&self) -> impl Iterator<Item = GlueNode> {
2295        support::children(&self.syntax)
2296    }
2297
2298    pub fn escapes(&self) -> impl Iterator<Item = Escape> {
2299        support::children(&self.syntax)
2300    }
2301
2302    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
2303        support::children(&self.syntax)
2304    }
2305
2306    pub fn divert(&self) -> Option<DivertNode> {
2307        support::child(&self.syntax)
2308    }
2309
2310    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
2311        support::children(&self.syntax)
2312    }
2313
2314    pub fn choices(&self) -> impl Iterator<Item = Choice> {
2315        support::children(&self.syntax)
2316    }
2317}