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#[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 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 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), Dict(Vec<(IdentWithToken, Pattern)>),
275 Type(Ident), Or(Vec<Pattern>), }
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 SelectorChain(SmallVec<[Selector; 4]>),
359 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}