Skip to main content

mq_lang/ast/
node.rs

1use super::{Program, TokenId};
2#[cfg(feature = "ast-json")]
3use crate::arena::ArenaId;
4use crate::{Ident, Shared, Token, arena::Arena, number::Number, range::Range, selector::Selector};
5#[cfg(feature = "ast-json")]
6use serde::{Deserialize, Serialize};
7use smallvec::SmallVec;
8use smol_str::SmolStr;
9use std::{
10    fmt::{self, Display, Formatter},
11    hash::{Hash, Hasher},
12};
13
14/// Represents a function parameter with an optional default value
15#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
16#[derive(PartialEq, PartialOrd, Debug, Clone)]
17pub struct Param {
18    pub ident: IdentWithToken,
19    pub default: Option<Shared<Node>>,
20    pub is_variadic: bool,
21}
22
23impl Display for Param {
24    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
25        if self.is_variadic {
26            write!(f, "*{}", self.ident)
27        } else {
28            write!(f, "{}", self.ident)
29        }
30    }
31}
32
33impl Param {
34    pub fn new(name: IdentWithToken) -> Self {
35        Self::with_default(name, None)
36    }
37
38    pub fn with_default(name: IdentWithToken, default_value: Option<Shared<Node>>) -> Self {
39        Self {
40            ident: name,
41            default: default_value,
42            is_variadic: false,
43        }
44    }
45
46    /// Creates a variadic parameter (e.g., `*args`)
47    pub fn variadic(name: IdentWithToken) -> Self {
48        Self {
49            ident: name,
50            default: None,
51            is_variadic: true,
52        }
53    }
54}
55
56pub type Params = SmallVec<[Param; 4]>;
57pub type Args = SmallVec<[Shared<Node>; 4]>;
58pub type Cond = (Option<Shared<Node>>, Shared<Node>);
59pub type Branches = SmallVec<[Cond; 4]>;
60pub type MatchArms = SmallVec<[MatchArm; 4]>;
61
62#[derive(PartialEq, PartialOrd, Debug, Clone)]
63#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
64pub struct Node {
65    #[cfg_attr(
66        feature = "ast-json",
67        serde(skip_serializing, skip_deserializing, default = "default_token_id")
68    )]
69    pub token_id: TokenId,
70    pub expr: Shared<Expr>,
71}
72
73#[cfg(feature = "ast-json")]
74fn default_token_id() -> TokenId {
75    ArenaId::new(0)
76}
77
78impl Node {
79    #[cfg(feature = "ast-json")]
80    pub fn to_json(&self) -> Result<String, serde_json::Error> {
81        serde_json::to_string_pretty(self)
82    }
83
84    #[cfg(feature = "ast-json")]
85    pub fn from_json(json_str: &str) -> Result<Self, serde_json::Error> {
86        serde_json::from_str(json_str)
87    }
88
89    pub fn range(&self, arena: Shared<Arena<Shared<Token>>>) -> Range {
90        match &*self.expr {
91            Expr::Block(program)
92            | Expr::Def(_, _, program)
93            | Expr::Fn(_, program)
94            | Expr::While(_, program)
95            | Expr::Loop(program)
96            | Expr::Module(_, program)
97            | Expr::Foreach(_, _, program) => {
98                let start = program
99                    .first()
100                    .map(|node| node.range(Shared::clone(&arena)).start)
101                    .unwrap_or_default();
102                let end = program
103                    .last()
104                    .map(|node| node.range(Shared::clone(&arena)).end)
105                    .unwrap_or_default();
106                Range { start, end }
107            }
108            Expr::Call(_, args) => {
109                let start = args
110                    .first()
111                    .map(|node| node.range(Shared::clone(&arena)).start)
112                    .unwrap_or_default();
113                let end = args
114                    .last()
115                    .map(|node| node.range(Shared::clone(&arena)).end)
116                    .unwrap_or_default();
117                Range { start, end }
118            }
119            Expr::CallDynamic(callable, args) => {
120                let start = callable.range(Shared::clone(&arena)).start;
121                let end = args
122                    .last()
123                    .map(|node| node.range(Shared::clone(&arena)).end)
124                    .unwrap_or_else(|| callable.range(Shared::clone(&arena)).end);
125                Range { start, end }
126            }
127            Expr::Macro(_, params, block) => {
128                let start = params
129                    .first()
130                    .and_then(|param| param.ident.token.as_ref().map(|t| t.range))
131                    .unwrap_or(block.range(Shared::clone(&arena)))
132                    .start;
133                let end = block.range(arena).end;
134                Range { start, end }
135            }
136            Expr::As(_, node)
137            | Expr::Let(_, node)
138            | Expr::Var(_, node)
139            | Expr::Assign(_, node)
140            | Expr::Quote(node)
141            | Expr::Unquote(node) => node.range(Shared::clone(&arena)),
142            Expr::If(nodes) => {
143                if let (Some(first), Some(last)) = (nodes.first(), nodes.last()) {
144                    let start = first.1.range(Shared::clone(&arena));
145                    let end = last.1.range(Shared::clone(&arena));
146                    Range {
147                        start: start.start,
148                        end: end.end,
149                    }
150                } else {
151                    // Fallback to token range if no branches exist
152                    arena[self.token_id].range
153                }
154            }
155            Expr::Match(value, arms) => {
156                let start = value.range(Shared::clone(&arena)).start;
157                let end = arms
158                    .last()
159                    .map(|arm| arm.body.range(Shared::clone(&arena)).end)
160                    .unwrap_or_else(|| arena[self.token_id].range.end);
161                Range { start, end }
162            }
163            Expr::Paren(node) => node.range(Shared::clone(&arena)),
164            Expr::Try(try_expr, catch_expr) => {
165                let start = try_expr.range(Shared::clone(&arena)).start;
166                let end = catch_expr.range(Shared::clone(&arena)).end;
167                Range { start, end }
168            }
169            Expr::And(exprs) | Expr::Or(exprs) => {
170                if let (Some(first), Some(last)) = (exprs.first(), exprs.last()) {
171                    Range {
172                        start: first.range(Shared::clone(&arena)).start,
173                        end: last.range(Shared::clone(&arena)).end,
174                    }
175                } else {
176                    arena[self.token_id].range
177                }
178            }
179            Expr::Break(Some(value_node)) => {
180                let start = arena[self.token_id].range.start;
181                let end = value_node.range(Shared::clone(&arena)).end;
182                Range { start, end }
183            }
184            Expr::SelectorCall(_, args) => {
185                let start = arena[self.token_id].range.start;
186                let end = args
187                    .last()
188                    .map(|node| node.range(Shared::clone(&arena)).end)
189                    .unwrap_or_else(|| arena[self.token_id].range.end);
190                Range { start, end }
191            }
192            Expr::Literal(_)
193            | Expr::Ident(_)
194            | Expr::Selector(_)
195            | Expr::SelectorChain(_)
196            | Expr::Include(_)
197            | Expr::Import(_)
198            | Expr::InterpolatedString(_)
199            | Expr::QualifiedAccess(_, _)
200            | Expr::Nodes
201            | Expr::Self_
202            | Expr::Break(None)
203            | Expr::Continue => arena[self.token_id].range,
204        }
205    }
206
207    pub fn is_nodes(&self) -> bool {
208        matches!(*self.expr, Expr::Nodes)
209    }
210}
211
212#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
213#[derive(PartialEq, Debug, Eq, Clone)]
214pub struct IdentWithToken {
215    pub name: Ident,
216    #[cfg_attr(feature = "ast-json", serde(skip_serializing_if = "Option::is_none", default))]
217    pub token: Option<Shared<Token>>,
218}
219
220impl Hash for IdentWithToken {
221    fn hash<H: Hasher>(&self, state: &mut H) {
222        self.name.hash(state);
223    }
224}
225
226impl Ord for IdentWithToken {
227    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
228        self.name.cmp(&other.name)
229    }
230}
231
232impl PartialOrd for IdentWithToken {
233    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
234        Some(self.cmp(other))
235    }
236}
237
238impl IdentWithToken {
239    pub fn new(name: &str) -> Self {
240        Self::new_with_token(name, None)
241    }
242
243    pub fn new_with_token(name: &str, token: Option<Shared<Token>>) -> Self {
244        Self {
245            name: name.into(),
246            token,
247        }
248    }
249}
250
251impl Display for IdentWithToken {
252    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
253        write!(f, "{}", self.name)
254    }
255}
256
257#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
258#[derive(Debug, Clone, PartialOrd, PartialEq)]
259pub enum StringSegment {
260    Text(String),
261    Expr(Shared<Node>),
262    Env(SmolStr),
263    Self_,
264}
265
266#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
267#[derive(PartialEq, PartialOrd, Debug, Clone)]
268pub enum Pattern {
269    Literal(Literal),
270    Ident(IdentWithToken),
271    Wildcard,
272    Array(Vec<Pattern>),
273    ArrayRest(Vec<Pattern>, IdentWithToken), // patterns before .., rest binding
274    Dict(Vec<(IdentWithToken, Pattern)>),
275    Type(Ident),      // :string, :number, etc.
276    Or(Vec<Pattern>), // p1 || p2 || p3
277}
278
279#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
280#[derive(PartialEq, PartialOrd, Debug, Clone)]
281pub struct MatchArm {
282    pub pattern: Pattern,
283    pub guard: Option<Shared<Node>>,
284    pub body: Shared<Node>,
285}
286
287#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
288#[derive(PartialEq, PartialOrd, Debug, Clone)]
289pub enum Literal {
290    String(String),
291    Bytes(Vec<u8>),
292    Number(Number),
293    Symbol(Ident),
294    Bool(bool),
295    None,
296}
297
298impl From<&str> for Literal {
299    fn from(s: &str) -> Self {
300        Literal::String(s.to_owned())
301    }
302}
303
304#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
305#[derive(PartialEq, PartialOrd, Debug, Clone)]
306pub enum AccessTarget {
307    Call(IdentWithToken, Args),
308    Ident(IdentWithToken),
309}
310
311impl Display for Literal {
312    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
313        match self {
314            Literal::String(s) => write!(f, "{}", s),
315            Literal::Bytes(b) => {
316                write!(f, "b\"")?;
317                for byte in b {
318                    if byte.is_ascii_graphic() && *byte != b'"' && *byte != b'\\' {
319                        write!(f, "{}", *byte as char)?;
320                    } else {
321                        write!(f, "\\x{:02x}", byte)?;
322                    }
323                }
324                write!(f, "\"")
325            }
326            Literal::Number(n) => write!(f, "{}", n),
327            Literal::Symbol(i) => write!(f, "{}", i),
328            Literal::Bool(b) => write!(f, "{}", b),
329            Literal::None => write!(f, "none"),
330        }
331    }
332}
333
334#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
335#[derive(PartialEq, PartialOrd, Debug, Clone)]
336pub enum Expr {
337    As(IdentWithToken, Shared<Node>),
338    Block(Program),
339    Call(IdentWithToken, Args),
340    CallDynamic(Shared<Node>, Args),
341    Def(IdentWithToken, Params, Program),
342    Macro(IdentWithToken, Params, Shared<Node>),
343    Fn(Params, Program),
344    Let(Pattern, Shared<Node>),
345    Loop(Program),
346    Var(Pattern, Shared<Node>),
347    Assign(IdentWithToken, Shared<Node>),
348    And(Vec<Shared<Node>>),
349    Or(Vec<Shared<Node>>),
350    Literal(Literal),
351    Ident(IdentWithToken),
352    InterpolatedString(Vec<StringSegment>),
353    Selector(Selector),
354    /// A sequence of selectors merged by the optimizer to reduce pipeline overhead.
355    ///
356    /// Equivalent to applying each selector in order, but evaluated in a single
357    /// `eval_expr` call instead of N separate pipeline steps.
358    SelectorChain(SmallVec<[Selector; 4]>),
359    /// A selector with runtime-evaluated arguments for filtered matching.
360    ///
361    /// Supports `.h(1..2)`, `.h(1, 2)`, `.code("rust")`, etc.
362    SelectorCall(Selector, Args),
363    While(Shared<Node>, Program),
364    Foreach(IdentWithToken, Shared<Node>, Program),
365    If(Branches),
366    Match(Shared<Node>, MatchArms),
367    Include(Literal),
368    Import(Literal),
369    Module(IdentWithToken, Program),
370    QualifiedAccess(Vec<IdentWithToken>, AccessTarget),
371    Self_,
372    Nodes,
373    Paren(Shared<Node>),
374    Quote(Shared<Node>),
375    Unquote(Shared<Node>),
376    Try(Shared<Node>, Shared<Node>),
377    Break(Option<Shared<Node>>),
378    Continue,
379}
380
381#[cfg(feature = "debugger")]
382impl Display for Expr {
383    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
384        match self {
385            Expr::Call(ident, args) => {
386                write!(f, "{}(", ident)?;
387                for (i, arg) in args.iter().enumerate() {
388                    if i > 0 {
389                        write!(f, ", ")?;
390                    }
391                    write!(f, "{}", arg.expr)?;
392                }
393                write!(f, ")")
394            }
395            Expr::CallDynamic(callable, args) => {
396                write!(f, "{}(", callable.expr)?;
397                for (i, arg) in args.iter().enumerate() {
398                    if i > 0 {
399                        write!(f, ", ")?;
400                    }
401                    write!(f, "{}", arg.expr)?;
402                }
403                write!(f, ")")
404            }
405            _ => write!(f, ""),
406        }
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413    use crate::{Position, TokenKind, arena::ArenaId};
414    use rstest::rstest;
415    use smallvec::smallvec;
416
417    fn create_token(range: Range) -> Shared<Token> {
418        Shared::new(Token {
419            range,
420            kind: TokenKind::Eof,
421            module_id: ArenaId::new(0),
422        })
423    }
424
425    #[rstest]
426    #[case(
427        Expr::CallDynamic(
428            Shared::new(Node {
429                token_id: ArenaId::new(1),
430                expr: Shared::new(Expr::Literal(Literal::String("callee".to_string()))),
431            }),
432            smallvec![
433                Shared::new(Node {
434                    token_id: ArenaId::new(0),
435                    expr: Shared::new(Expr::Literal(Literal::String("arg1".to_string()))),
436                }),
437                Shared::new(Node {
438                    token_id: ArenaId::new(1),
439                    expr: Shared::new(Expr::Literal(Literal::String("arg2".to_string()))),
440                }),
441            ]
442        ),
443        vec![
444            (0, Range { start: Position::new(1, 1), end: Position::new(1, 5) }),
445            (1, Range { start: Position::new(2, 1), end: Position::new(2, 5) }),
446        ],
447        Range { start: Position::new(2, 1), end: Position::new(2, 5) }
448    )]
449    #[case(
450        Expr::Match(
451            Shared::new(Node {
452                token_id: ArenaId::new(0),
453                expr: Shared::new(Expr::Literal(Literal::String("val".to_string()))),
454            }),
455            smallvec![
456                MatchArm {
457                    pattern: Pattern::Literal(Literal::String("a".to_string())),
458                    guard: None,
459                    body: Shared::new(Node {
460                        token_id: ArenaId::new(1),
461                        expr: Shared::new(Expr::Literal(Literal::String("body1".to_string()))),
462                    }),
463                },
464                MatchArm {
465                    pattern: Pattern::Literal(Literal::String("b".to_string())),
466                    guard: None,
467                    body: Shared::new(Node {
468                        token_id: ArenaId::new(2),
469                        expr: Shared::new(Expr::Literal(Literal::String("body2".to_string()))),
470                    }),
471                },
472            ]
473        ),
474        vec![
475            (0, Range { start: Position::new(10, 1), end: Position::new(10, 5) }),
476            (1, Range { start: Position::new(11, 1), end: Position::new(11, 5) }),
477            (2, Range { start: Position::new(12, 1), end: Position::new(12, 5) }),
478        ],
479        Range { start: Position::new(10, 1), end: Position::new(12, 5) }
480    )]
481    #[case(
482        Expr::Try(
483            Shared::new(Node {
484                token_id: ArenaId::new(0),
485                expr: Shared::new(Expr::Literal(Literal::String("try".to_string()))),
486            }),
487            Shared::new(Node {
488                token_id: ArenaId::new(1),
489                expr: Shared::new(Expr::Literal(Literal::String("catch".to_string()))),
490            })
491        ),
492        vec![
493            (0, Range { start: Position::new(20, 1), end: Position::new(20, 5) }),
494            (1, Range { start: Position::new(21, 1), end: Position::new(21, 5) }),
495        ],
496        Range { start: Position::new(20, 1), end: Position::new(21, 5) }
497    )]
498    #[case(
499        Expr::Let(
500            Pattern::Ident(IdentWithToken::new("x")),
501            Shared::new(Node {
502                token_id: ArenaId::new(0),
503                expr: Shared::new(Expr::Literal(Literal::String("letval".to_string()))),
504            })
505        ),
506        vec![
507            (0, Range { start: Position::new(30, 1), end: Position::new(30, 5) }),
508        ],
509        Range { start: Position::new(30, 1), end: Position::new(30, 5) }
510    )]
511    #[case(
512        Expr::Paren(
513            Shared::new(Node {
514                token_id: ArenaId::new(0),
515                expr: Shared::new(Expr::Literal(Literal::String("paren".to_string()))),
516            })
517        ),
518        vec![
519            (0, Range { start: Position::new(40, 1), end: Position::new(40, 5) }),
520        ],
521        Range { start: Position::new(40, 1), end: Position::new(40, 5) }
522    )]
523    #[case(
524        Expr::Block(vec![
525            Shared::new(Node {
526                token_id: ArenaId::new(0),
527                expr: Shared::new(Expr::Literal(Literal::String("block1".to_string()))),
528            }),
529            Shared::new(Node {
530                token_id: ArenaId::new(1),
531                expr: Shared::new(Expr::Literal(Literal::String("block2".to_string()))),
532            }),
533        ]),
534        vec![
535            (0, Range { start: Position::new(50, 1), end: Position::new(50, 5) }),
536            (1, Range { start: Position::new(51, 1), end: Position::new(51, 5) }),
537        ],
538        Range { start: Position::new(50, 1), end: Position::new(51, 5) }
539    )]
540    #[case(
541        Expr::Def(
542            IdentWithToken::new("f"),
543            smallvec![],
544            vec![
545                Shared::new(Node {
546                    token_id: ArenaId::new(0),
547                    expr: Shared::new(Expr::Literal(Literal::String("def1".to_string()))),
548                }),
549                Shared::new(Node {
550                    token_id: ArenaId::new(1),
551                    expr: Shared::new(Expr::Literal(Literal::String("def2".to_string()))),
552                }),
553            ]
554        ),
555        vec![
556            (0, Range { start: Position::new(60, 1), end: Position::new(60, 5) }),
557            (1, Range { start: Position::new(61, 1), end: Position::new(61, 5) }),
558        ],
559        Range { start: Position::new(60, 1), end: Position::new(61, 5) }
560    )]
561    #[case(
562        Expr::Fn(
563            smallvec![],
564            vec![
565                Shared::new(Node {
566                    token_id: ArenaId::new(0),
567                    expr: Shared::new(Expr::Literal(Literal::String("fn1".to_string()))),
568                }),
569                Shared::new(Node {
570                    token_id: ArenaId::new(1),
571                    expr: Shared::new(Expr::Literal(Literal::String("fn2".to_string()))),
572                }),
573            ]
574        ),
575        vec![
576            (0, Range { start: Position::new(70, 1), end: Position::new(70, 5) }),
577            (1, Range { start: Position::new(71, 1), end: Position::new(71, 5) }),
578        ],
579        Range { start: Position::new(70, 1), end: Position::new(71, 5) }
580    )]
581    #[case(
582        Expr::While(
583            Shared::new(Node {
584                token_id: ArenaId::new(0),
585                expr: Shared::new(Expr::Literal(Literal::String("cond".to_string()))),
586            }),
587            vec![
588                Shared::new(Node {
589                    token_id: ArenaId::new(1),
590                    expr: Shared::new(Expr::Literal(Literal::String("while1".to_string()))),
591                }),
592                Shared::new(Node {
593                    token_id: ArenaId::new(2),
594                    expr: Shared::new(Expr::Literal(Literal::String("while2".to_string()))),
595                }),
596            ]
597        ),
598        vec![
599            (0, Range { start: Position::new(81, 1), end: Position::new(81, 5) }),
600            (1, Range { start: Position::new(82, 1), end: Position::new(82, 5) }),
601            (2, Range { start: Position::new(82, 1), end: Position::new(82, 5) }),
602        ],
603        Range { start: Position::new(82, 1), end: Position::new(82, 5) }
604    )]
605    #[case(
606        Expr::Foreach(
607            IdentWithToken::new("item"),
608            Shared::new(Node {
609                token_id: ArenaId::new(0),
610                expr: Shared::new(Expr::Literal(Literal::String("iter".to_string()))),
611            }),
612            vec![
613                Shared::new(Node {
614                    token_id: ArenaId::new(1),
615                    expr: Shared::new(Expr::Literal(Literal::String("foreach1".to_string()))),
616                }),
617                Shared::new(Node {
618                    token_id: ArenaId::new(2),
619                    expr: Shared::new(Expr::Literal(Literal::String("foreach2".to_string()))),
620                }),
621            ]
622        ),
623        vec![
624            (0, Range { start: Position::new(101, 1), end: Position::new(101, 5) }),
625            (1, Range { start: Position::new(102, 1), end: Position::new(102, 5) }),
626            (2, Range { start: Position::new(102, 1), end: Position::new(102, 5) }),
627        ],
628        Range { start: Position::new(102, 1), end: Position::new(102, 5) }
629    )]
630    #[case(
631        Expr::If(smallvec![
632            (
633                Some(Shared::new(Node {
634                    token_id: ArenaId::new(0),
635                    expr: Shared::new(Expr::Literal(Literal::String("cond1".to_string()))),
636                })),
637                Shared::new(Node {
638                    token_id: ArenaId::new(1),
639                    expr: Shared::new(Expr::Literal(Literal::String("if1".to_string()))),
640                })
641            ),
642            (
643                Some(Shared::new(Node {
644                    token_id: ArenaId::new(2),
645                    expr: Shared::new(Expr::Literal(Literal::String("cond2".to_string()))),
646                })),
647                Shared::new(Node {
648                    token_id: ArenaId::new(3),
649                    expr: Shared::new(Expr::Literal(Literal::String("if2".to_string()))),
650                })
651            ),
652        ]),
653        vec![
654            (0, Range { start: Position::new(111, 1), end: Position::new(111, 5) }),
655            (1, Range { start: Position::new(113, 1), end: Position::new(113, 5) }),
656            (2, Range { start: Position::new(114, 1), end: Position::new(115, 5) }),
657            (3, Range { start: Position::new(116, 1), end: Position::new(117, 5) }),
658        ],
659        Range { start: Position::new(113, 1), end: Position::new(117, 5) }
660    )]
661    #[case(
662        Expr::Call(
663            IdentWithToken::new("func"),
664            smallvec![
665                Shared::new(Node {
666                    token_id: ArenaId::new(0),
667                    expr: Shared::new(Expr::Literal(Literal::String("arg1".to_string()))),
668                }),
669                Shared::new(Node {
670                    token_id: ArenaId::new(1),
671                    expr: Shared::new(Expr::Literal(Literal::String("arg2".to_string()))),
672                }),
673            ]
674        ),
675        vec![
676            (0, Range { start: Position::new(120, 1), end: Position::new(120, 5) }),
677            (1, Range { start: Position::new(121, 1), end: Position::new(121, 5) }),
678        ],
679        Range { start: Position::new(120, 1), end: Position::new(121, 5) }
680    )]
681    fn test_node_range_various_exprs(
682        #[case] expr: Expr,
683        #[case] token_ranges: Vec<(usize, Range)>,
684        #[case] expected: Range,
685    ) {
686        let mut arena = Arena::new(150);
687        for (_, range) in &token_ranges {
688            let token = create_token(*range);
689            let _ = arena.alloc(token);
690        }
691        let node = Node {
692            token_id: ArenaId::new(0),
693            expr: Shared::new(expr),
694        };
695        assert_eq!(node.range(Shared::new(arena)), expected);
696    }
697
698    fn make_node(token_id: u32) -> Shared<Node> {
699        Shared::new(Node {
700            token_id: ArenaId::new(token_id),
701            expr: Shared::new(Expr::Literal(Literal::None)),
702        })
703    }
704
705    fn single_token_arena(range: Range) -> Shared<Arena<Shared<Token>>> {
706        let mut arena = Arena::new(10);
707        arena.alloc(create_token(range));
708        Shared::new(arena)
709    }
710
711    #[test]
712    fn test_range_loop_uses_program() {
713        let r0 = Range {
714            start: Position::new(1, 1),
715            end: Position::new(1, 5),
716        };
717        let r1 = Range {
718            start: Position::new(2, 1),
719            end: Position::new(2, 5),
720        };
721        let mut arena = Arena::new(10);
722        arena.alloc(create_token(r0));
723        arena.alloc(create_token(r1));
724        let expr = Expr::Loop(vec![make_node(0), make_node(1)]);
725        let node = Node {
726            token_id: ArenaId::new(0),
727            expr: Shared::new(expr),
728        };
729        let got = node.range(Shared::new(arena));
730        assert_eq!(got.start, r0.start);
731        assert_eq!(got.end, r1.end);
732    }
733
734    #[test]
735    fn test_range_module_uses_program() {
736        let r0 = Range {
737            start: Position::new(10, 1),
738            end: Position::new(10, 5),
739        };
740        let r1 = Range {
741            start: Position::new(11, 1),
742            end: Position::new(11, 5),
743        };
744        let mut arena = Arena::new(10);
745        arena.alloc(create_token(r0));
746        arena.alloc(create_token(r1));
747        let expr = Expr::Module(IdentWithToken::new("m"), vec![make_node(0), make_node(1)]);
748        let node = Node {
749            token_id: ArenaId::new(0),
750            expr: Shared::new(expr),
751        };
752        let got = node.range(Shared::new(arena));
753        assert_eq!(got.start, r0.start);
754        assert_eq!(got.end, r1.end);
755    }
756
757    #[test]
758    fn test_range_empty_block_uses_default() {
759        let r0 = Range {
760            start: Position::new(5, 1),
761            end: Position::new(5, 5),
762        };
763        let arena = single_token_arena(r0);
764        let expr = Expr::Block(vec![]);
765        let node = Node {
766            token_id: ArenaId::new(0),
767            expr: Shared::new(expr),
768        };
769        let got = node.range(arena);
770        assert_eq!(got, Range::default());
771    }
772
773    #[test]
774    fn test_range_as_delegates_to_inner() {
775        let r0 = Range {
776            start: Position::new(20, 1),
777            end: Position::new(20, 8),
778        };
779        let arena = single_token_arena(r0);
780        let expr = Expr::As(IdentWithToken::new("x"), make_node(0));
781        let node = Node {
782            token_id: ArenaId::new(0),
783            expr: Shared::new(expr),
784        };
785        assert_eq!(node.range(arena), r0);
786    }
787
788    #[test]
789    fn test_range_var_delegates_to_inner() {
790        let r0 = Range {
791            start: Position::new(21, 1),
792            end: Position::new(21, 8),
793        };
794        let arena = single_token_arena(r0);
795        let expr = Expr::Var(Pattern::Wildcard, make_node(0));
796        let node = Node {
797            token_id: ArenaId::new(0),
798            expr: Shared::new(expr),
799        };
800        assert_eq!(node.range(arena), r0);
801    }
802
803    #[test]
804    fn test_range_assign_delegates_to_inner() {
805        let r0 = Range {
806            start: Position::new(22, 1),
807            end: Position::new(22, 8),
808        };
809        let arena = single_token_arena(r0);
810        let expr = Expr::Assign(IdentWithToken::new("v"), make_node(0));
811        let node = Node {
812            token_id: ArenaId::new(0),
813            expr: Shared::new(expr),
814        };
815        assert_eq!(node.range(arena), r0);
816    }
817
818    #[test]
819    fn test_range_quote_delegates_to_inner() {
820        let r0 = Range {
821            start: Position::new(23, 1),
822            end: Position::new(23, 8),
823        };
824        let arena = single_token_arena(r0);
825        let expr = Expr::Quote(make_node(0));
826        let node = Node {
827            token_id: ArenaId::new(0),
828            expr: Shared::new(expr),
829        };
830        assert_eq!(node.range(arena), r0);
831    }
832
833    #[test]
834    fn test_range_unquote_delegates_to_inner() {
835        let r0 = Range {
836            start: Position::new(24, 1),
837            end: Position::new(24, 8),
838        };
839        let arena = single_token_arena(r0);
840        let expr = Expr::Unquote(make_node(0));
841        let node = Node {
842            token_id: ArenaId::new(0),
843            expr: Shared::new(expr),
844        };
845        assert_eq!(node.range(arena), r0);
846    }
847
848    #[test]
849    fn test_range_and_empty_falls_back_to_token() {
850        let r0 = Range {
851            start: Position::new(30, 1),
852            end: Position::new(30, 5),
853        };
854        let arena = single_token_arena(r0);
855        let expr = Expr::And(vec![]);
856        let node = Node {
857            token_id: ArenaId::new(0),
858            expr: Shared::new(expr),
859        };
860        assert_eq!(node.range(arena), r0);
861    }
862
863    #[test]
864    fn test_range_or_empty_falls_back_to_token() {
865        let r0 = Range {
866            start: Position::new(31, 1),
867            end: Position::new(31, 5),
868        };
869        let arena = single_token_arena(r0);
870        let expr = Expr::Or(vec![]);
871        let node = Node {
872            token_id: ArenaId::new(0),
873            expr: Shared::new(expr),
874        };
875        assert_eq!(node.range(arena), r0);
876    }
877
878    #[test]
879    fn test_range_and_non_empty() {
880        let r0 = Range {
881            start: Position::new(40, 1),
882            end: Position::new(40, 5),
883        };
884        let r1 = Range {
885            start: Position::new(41, 1),
886            end: Position::new(41, 5),
887        };
888        let mut arena = Arena::new(10);
889        arena.alloc(create_token(r0));
890        arena.alloc(create_token(r1));
891        let expr = Expr::And(vec![make_node(0), make_node(1)]);
892        let node = Node {
893            token_id: ArenaId::new(0),
894            expr: Shared::new(expr),
895        };
896        let got = node.range(Shared::new(arena));
897        assert_eq!(got.start, r0.start);
898        assert_eq!(got.end, r1.end);
899    }
900
901    #[test]
902    fn test_range_break_with_value() {
903        let r0 = Range {
904            start: Position::new(50, 1),
905            end: Position::new(50, 3),
906        };
907        let r1 = Range {
908            start: Position::new(50, 5),
909            end: Position::new(50, 8),
910        };
911        let mut arena = Arena::new(10);
912        arena.alloc(create_token(r0));
913        arena.alloc(create_token(r1));
914        let expr = Expr::Break(Some(make_node(1)));
915        let node = Node {
916            token_id: ArenaId::new(0),
917            expr: Shared::new(expr),
918        };
919        let got = node.range(Shared::new(arena));
920        assert_eq!(got.start, r0.start);
921        assert_eq!(got.end, r1.end);
922    }
923
924    #[test]
925    fn test_range_selector_call_with_args() {
926        use crate::selector::Selector;
927        let r0 = Range {
928            start: Position::new(60, 1),
929            end: Position::new(60, 3),
930        };
931        let r1 = Range {
932            start: Position::new(60, 4),
933            end: Position::new(60, 6),
934        };
935        let mut arena = Arena::new(10);
936        arena.alloc(create_token(r0));
937        arena.alloc(create_token(r1));
938        let expr = Expr::SelectorCall(Selector::Heading(None), smallvec![make_node(1)]);
939        let node = Node {
940            token_id: ArenaId::new(0),
941            expr: Shared::new(expr),
942        };
943        let got = node.range(Shared::new(arena));
944        assert_eq!(got.start, r0.start);
945        assert_eq!(got.end, r1.end);
946    }
947
948    #[test]
949    fn test_range_terminal_exprs_use_token() {
950        let r0 = Range {
951            start: Position::new(70, 1),
952            end: Position::new(70, 5),
953        };
954
955        for expr in [
956            Expr::Literal(Literal::None),
957            Expr::Nodes,
958            Expr::Self_,
959            Expr::Break(None),
960            Expr::Continue,
961        ] {
962            let arena = single_token_arena(r0);
963            let node = Node {
964                token_id: ArenaId::new(0),
965                expr: Shared::new(expr),
966            };
967            assert_eq!(node.range(arena), r0, "terminal expr should use token range");
968        }
969    }
970
971    #[test]
972    fn test_range_macro_with_params() {
973        let r0 = Range {
974            start: Position::new(80, 1),
975            end: Position::new(80, 5),
976        };
977        let r1 = Range {
978            start: Position::new(81, 1),
979            end: Position::new(81, 5),
980        };
981        let mut arena = Arena::new(10);
982        arena.alloc(create_token(r0));
983        arena.alloc(create_token(r1));
984        let param = Param::new(IdentWithToken::new_with_token("p", Some(create_token(r0))));
985        let body = make_node(1);
986        let expr = Expr::Macro(IdentWithToken::new("m"), smallvec![param], body);
987        let node = Node {
988            token_id: ArenaId::new(0),
989            expr: Shared::new(expr),
990        };
991        let got = node.range(Shared::new(arena));
992        assert_eq!(got.start, r0.start);
993        assert_eq!(got.end, r1.end);
994    }
995
996    #[test]
997    fn test_range_macro_no_params_uses_body() {
998        let r0 = Range {
999            start: Position::new(90, 1),
1000            end: Position::new(90, 5),
1001        };
1002        let arena = single_token_arena(r0);
1003        let body = make_node(0);
1004        let expr = Expr::Macro(IdentWithToken::new("m"), smallvec![], body);
1005        let node = Node {
1006            token_id: ArenaId::new(0),
1007            expr: Shared::new(expr),
1008        };
1009        assert_eq!(node.range(arena), r0);
1010    }
1011
1012    #[test]
1013    fn test_range_call_empty_args_is_default() {
1014        let r0 = Range {
1015            start: Position::new(1, 1),
1016            end: Position::new(1, 5),
1017        };
1018        let arena = single_token_arena(r0);
1019        let expr = Expr::Call(IdentWithToken::new("f"), smallvec![]);
1020        let node = Node {
1021            token_id: ArenaId::new(0),
1022            expr: Shared::new(expr),
1023        };
1024        assert_eq!(node.range(arena), Range::default());
1025    }
1026
1027    #[test]
1028    fn test_is_nodes() {
1029        let r = Range::default();
1030        let arena = single_token_arena(r);
1031        let nodes_node = Node {
1032            token_id: ArenaId::new(0),
1033            expr: Shared::new(Expr::Nodes),
1034        };
1035        assert!(nodes_node.is_nodes());
1036        let other = Node {
1037            token_id: ArenaId::new(0),
1038            expr: Shared::new(Expr::Self_),
1039        };
1040        assert!(!other.is_nodes());
1041        let _ = arena;
1042    }
1043
1044    #[test]
1045    fn test_param_new_and_display() {
1046        let p = Param::new(IdentWithToken::new("x"));
1047        assert_eq!(p.to_string(), "x");
1048        assert!(!p.is_variadic);
1049        assert!(p.default.is_none());
1050    }
1051
1052    #[test]
1053    fn test_param_variadic_display() {
1054        let p = Param::variadic(IdentWithToken::new("args"));
1055        assert!(p.is_variadic);
1056        assert_eq!(p.to_string(), "*args");
1057    }
1058
1059    #[test]
1060    fn test_param_with_default() {
1061        let default_node = make_node(0);
1062        let p = Param::with_default(IdentWithToken::new("x"), Some(default_node));
1063        assert!(p.default.is_some());
1064    }
1065
1066    #[rstest]
1067    #[case(Literal::String("hello".to_string()), "hello")]
1068    #[case(Literal::Number(crate::number::Number::from(42.0)), "42")]
1069    #[case(Literal::Bool(true), "true")]
1070    #[case(Literal::Bool(false), "false")]
1071    #[case(Literal::None, "none")]
1072    fn test_literal_display(#[case] lit: Literal, #[case] expected: &str) {
1073        assert_eq!(lit.to_string(), expected);
1074    }
1075
1076    #[test]
1077    fn test_literal_bytes_display_ascii() {
1078        let lit = Literal::Bytes(b"hello".to_vec());
1079        assert_eq!(lit.to_string(), "b\"hello\"");
1080    }
1081
1082    #[test]
1083    fn test_literal_bytes_display_non_ascii() {
1084        let lit = Literal::Bytes(vec![0x00, 0xff]);
1085        assert_eq!(lit.to_string(), "b\"\\x00\\xff\"");
1086    }
1087
1088    #[test]
1089    fn test_ident_with_token_display() {
1090        let ident = IdentWithToken::new("foo");
1091        assert_eq!(ident.to_string(), "foo");
1092    }
1093
1094    #[test]
1095    fn test_ident_with_token_ord() {
1096        let a = IdentWithToken::new("a");
1097        let b = IdentWithToken::new("b");
1098        assert!(a < b);
1099    }
1100
1101    #[cfg(feature = "debugger")]
1102    #[test]
1103    fn test_expr_display_call() {
1104        let call = Expr::Call(IdentWithToken::new("func"), smallvec![make_node(0), make_node(1)]);
1105        let s = format!("{call}");
1106        assert!(s.starts_with("func("), "expected func(...), got: {s}");
1107    }
1108
1109    #[cfg(feature = "debugger")]
1110    #[test]
1111    fn test_expr_display_call_dynamic() {
1112        let callee = Shared::new(Node {
1113            token_id: ArenaId::new(0),
1114            expr: Shared::new(Expr::Literal(Literal::None)),
1115        });
1116        let dynamic = Expr::CallDynamic(callee, smallvec![]);
1117        let s = format!("{dynamic}");
1118        assert!(s.contains('('), "expected parens in: {s}");
1119    }
1120
1121    #[cfg(feature = "debugger")]
1122    #[test]
1123    fn test_expr_display_other_is_empty() {
1124        let lit = Expr::Literal(Literal::None);
1125        assert_eq!(format!("{lit}"), "");
1126    }
1127}