Skip to main content

mq_lang/ast/
code.rs

1use crate::Program;
2
3use super::node::{AccessTarget, Args, Expr, Literal, Node, Params, Pattern, StringSegment};
4use std::fmt::Write;
5
6impl Node {
7    /// Converts the AST node back to mq code code.
8    pub fn to_code(&self) -> String {
9        let mut output = String::new();
10        self.format_to_code(&mut output, 0);
11        output
12    }
13
14    fn format_to_code(&self, buf: &mut String, indent: usize) {
15        match &*self.expr {
16            Expr::Literal(lit) => {
17                format_literal(lit, buf);
18            }
19            Expr::Ident(ident) => {
20                write!(buf, "{}", ident).unwrap();
21            }
22            Expr::Self_ => {
23                buf.push_str("self");
24            }
25            Expr::Nodes => {
26                buf.push_str("nodes");
27            }
28            Expr::Selector(selector) => {
29                write!(buf, "{}", selector).unwrap();
30            }
31            Expr::SelectorChain(selectors) => {
32                for (i, sel) in selectors.iter().enumerate() {
33                    if i > 0 {
34                        buf.push_str(" | ");
35                    }
36                    write!(buf, "{}", sel).unwrap();
37                }
38            }
39            Expr::SelectorCall(selector, args) => {
40                write!(buf, "{}", selector).unwrap();
41                buf.push('(');
42                format_args(args, buf, indent);
43                buf.push(')');
44            }
45            Expr::Break(None) => {
46                buf.push_str("break");
47            }
48            Expr::Break(Some(value)) => {
49                buf.push_str("break: ");
50                value.format_to_code(buf, indent);
51            }
52            Expr::Continue => {
53                buf.push_str("continue");
54            }
55            Expr::Paren(node) => {
56                buf.push('(');
57                node.format_to_code(buf, indent);
58                buf.push(')');
59            }
60            Expr::And(operands) => {
61                for (i, op) in operands.iter().enumerate() {
62                    if i > 0 {
63                        buf.push_str(" && ");
64                    }
65                    op.format_to_code(buf, indent);
66                }
67            }
68            Expr::Or(operands) => {
69                for (i, op) in operands.iter().enumerate() {
70                    if i > 0 {
71                        buf.push_str(" || ");
72                    }
73                    op.format_to_code(buf, indent);
74                }
75            }
76            Expr::Call(func, args) => {
77                write!(buf, "{}", func).unwrap();
78                buf.push('(');
79                format_args(args, buf, indent);
80                buf.push(')');
81            }
82            Expr::CallDynamic(func, args) => {
83                func.format_to_code(buf, indent);
84                buf.push('(');
85                format_args(args, buf, indent);
86                buf.push(')');
87            }
88            Expr::As(ident, value) => {
89                value.format_to_code(buf, indent);
90                write!(buf, " as {}", ident).unwrap();
91            }
92            Expr::Let(pattern, value) => {
93                buf.push_str("let ");
94                format_pattern(pattern, buf);
95                buf.push_str(" = ");
96                value.format_to_code(buf, indent);
97            }
98            Expr::Var(pattern, value) => {
99                buf.push_str("var ");
100                format_pattern(pattern, buf);
101                buf.push_str(" = ");
102                value.format_to_code(buf, indent);
103            }
104            Expr::Assign(ident, value) => {
105                write!(buf, "{} = ", ident).unwrap();
106                value.format_to_code(buf, indent);
107            }
108            Expr::If(branches) => {
109                for (i, (cond_opt, body)) in branches.iter().enumerate() {
110                    if i == 0 {
111                        buf.push_str("if ");
112                        if let Some(cond) = cond_opt {
113                            buf.push('(');
114                            cond.format_to_code(buf, indent);
115                            buf.push(')');
116                        }
117                        buf.push_str(": ");
118                        body.format_to_code(buf, indent);
119                    } else if let Some(cond) = cond_opt {
120                        buf.push_str(" elif (");
121                        cond.format_to_code(buf, indent);
122                        buf.push_str("): ");
123                        body.format_to_code(buf, indent);
124                    } else {
125                        buf.push_str(" else: ");
126                        body.format_to_code(buf, indent);
127                    }
128                }
129            }
130            Expr::While(cond, program) => {
131                buf.push_str("while (");
132                cond.format_to_code(buf, indent);
133                buf.push(')');
134                if needs_block_syntax(program) {
135                    format_program_block(program, buf, indent);
136                } else if let Some(stmt) = program.first() {
137                    buf.push_str(": ");
138                    stmt.format_to_code(buf, indent);
139                }
140            }
141            Expr::Loop(program) => {
142                buf.push_str("loop");
143                format_program_block(program, buf, indent);
144            }
145            Expr::Foreach(item, iter, program) => {
146                write!(buf, "foreach({}, ", item).unwrap();
147                iter.format_to_code(buf, indent);
148                buf.push(')');
149                if needs_block_syntax(program) {
150                    format_program_block(program, buf, indent);
151                } else if let Some(stmt) = program.first() {
152                    buf.push_str(": ");
153                    stmt.format_to_code(buf, indent);
154                }
155            }
156            Expr::Block(program) => {
157                // Remove leading space from format_program_block output
158                let start_len = buf.len();
159                format_program_block(program, buf, indent);
160                // format_program_block adds " do", but Block doesn't need leading space
161                if buf[start_len..].starts_with(' ') {
162                    buf.replace_range(start_len..start_len + 1, "");
163                }
164            }
165            Expr::Def(name, params, program) => {
166                write!(buf, "def {}(", name).unwrap();
167                format_params(params, buf, indent);
168                buf.push(')');
169                if needs_block_syntax(program) {
170                    format_program_block(program, buf, indent);
171                } else if let Some(stmt) = program.first() {
172                    buf.push_str(": ");
173                    stmt.format_to_code(buf, indent);
174                }
175            }
176            Expr::Fn(params, program) => {
177                buf.push_str("fn(");
178                format_params(params, buf, indent);
179                buf.push(')');
180                if needs_block_syntax(program) {
181                    format_program_block(program, buf, indent);
182                } else if let Some(stmt) = program.first() {
183                    buf.push_str(": ");
184                    stmt.format_to_code(buf, indent);
185                }
186            }
187            Expr::Match(value, arms) => {
188                buf.push_str("match (");
189                value.format_to_code(buf, indent);
190                buf.push_str(") do");
191                for arm in arms.iter() {
192                    buf.push('\n');
193                    buf.push_str(&"  ".repeat(indent + 1));
194                    buf.push_str("| ");
195                    format_pattern(&arm.pattern, buf);
196                    if let Some(guard) = &arm.guard {
197                        buf.push_str(" if ");
198                        guard.format_to_code(buf, indent + 1);
199                    }
200                    buf.push_str(": ");
201                    arm.body.format_to_code(buf, indent + 1);
202                }
203                buf.push('\n');
204                buf.push_str(&"  ".repeat(indent));
205                buf.push_str("end");
206            }
207            Expr::InterpolatedString(segments) => {
208                buf.push_str("s\"");
209                for segment in segments.iter() {
210                    match segment {
211                        StringSegment::Text(text) => {
212                            buf.push_str(&escape_string(text));
213                        }
214                        StringSegment::Expr(expr) => {
215                            buf.push_str("${");
216                            expr.format_to_code(buf, indent);
217                            buf.push('}');
218                        }
219                        StringSegment::Env(name) => {
220                            write!(buf, "${{{}}}", name).unwrap();
221                        }
222                        StringSegment::Self_ => {
223                            buf.push_str("${self}");
224                        }
225                    }
226                }
227                buf.push('"');
228            }
229            Expr::Macro(name, params, body) => {
230                write!(buf, "macro {}(", name).unwrap();
231                format_params(params, buf, indent);
232                buf.push_str("): ");
233                body.format_to_code(buf, indent);
234            }
235            Expr::Quote(node) => {
236                buf.push_str("quote: ");
237                node.format_to_code(buf, indent);
238            }
239            Expr::Unquote(node) => {
240                buf.push_str("unquote(");
241                node.format_to_code(buf, indent);
242                buf.push(')');
243            }
244            Expr::Try(try_expr, catch_expr) => {
245                buf.push_str("try ");
246                try_expr.format_to_code(buf, indent);
247                buf.push_str(" catch: ");
248                catch_expr.format_to_code(buf, indent);
249            }
250            Expr::Module(name, program) => {
251                write!(buf, "module {}", name).unwrap();
252                format_program_block(program, buf, indent);
253            }
254            Expr::QualifiedAccess(path, target) => {
255                for (i, part) in path.iter().enumerate() {
256                    if i > 0 {
257                        buf.push_str("::");
258                    }
259                    write!(buf, "{}", part).unwrap();
260                }
261                match target {
262                    AccessTarget::Call(func, args) => {
263                        write!(buf, "::{}", func).unwrap();
264                        buf.push('(');
265                        format_args(args, buf, indent);
266                        buf.push(')');
267                    }
268                    AccessTarget::Ident(ident) => {
269                        write!(buf, "::{}", ident).unwrap();
270                    }
271                }
272            }
273            Expr::Include(path) => {
274                buf.push_str("include ");
275                format_literal(path, buf);
276            }
277            Expr::Import(path) => {
278                buf.push_str("import ");
279                format_literal(path, buf);
280            }
281        }
282    }
283}
284
285fn escape_string(s: &str) -> String {
286    let mut result = String::with_capacity(s.len());
287    for ch in s.chars() {
288        match ch {
289            '\\' => result.push_str("\\\\"),
290            '"' => result.push_str("\\\""),
291            '\n' => result.push_str("\\n"),
292            '\t' => result.push_str("\\t"),
293            '\r' => result.push_str("\\r"),
294            _ => result.push(ch),
295        }
296    }
297    result
298}
299
300fn format_literal(literal: &Literal, buf: &mut String) {
301    match literal {
302        Literal::String(s) => {
303            buf.push('"');
304            buf.push_str(&escape_string(s));
305            buf.push('"');
306        }
307        Literal::Bytes(b) => {
308            buf.push_str("b\"");
309            for byte in b {
310                if byte.is_ascii_graphic() && *byte != b'"' && *byte != b'\\' {
311                    buf.push(*byte as char);
312                } else {
313                    write!(buf, "\\x{:02x}", byte).unwrap();
314                }
315            }
316            buf.push('"');
317        }
318        Literal::Number(n) => {
319            write!(buf, "{}", n).unwrap();
320        }
321        Literal::Symbol(ident) => {
322            write!(buf, ":{}", ident).unwrap();
323        }
324        Literal::Bool(b) => {
325            buf.push_str(if *b { "true" } else { "false" });
326        }
327        Literal::None => {
328            buf.push_str("none");
329        }
330    }
331}
332
333fn format_args(args: &Args, buf: &mut String, indent: usize) {
334    for (i, arg) in args.iter().enumerate() {
335        if i > 0 {
336            buf.push_str(", ");
337        }
338        arg.format_to_code(buf, indent);
339    }
340}
341
342fn format_params(params: &Params, buf: &mut String, indent: usize) {
343    for (i, param) in params.iter().enumerate() {
344        if i > 0 {
345            buf.push_str(", ");
346        }
347
348        write!(buf, "{}", param.ident).unwrap();
349        if let Some(default) = &param.default {
350            buf.push_str(" = ");
351            default.format_to_code(buf, indent);
352        }
353    }
354}
355
356fn format_program_block(program: &Program, buf: &mut String, indent: usize) {
357    buf.push_str(" do\n");
358    buf.push_str(&"  ".repeat(indent + 1));
359    for (i, stmt) in program.iter().enumerate() {
360        if i > 0 {
361            buf.push_str(" | ");
362        }
363        stmt.format_to_code(buf, indent + 1);
364    }
365    buf.push('\n');
366    buf.push_str(&"  ".repeat(indent));
367    buf.push_str("end");
368}
369
370fn format_pattern(pattern: &Pattern, buf: &mut String) {
371    match pattern {
372        Pattern::Literal(lit) => {
373            format_literal(lit, buf);
374        }
375        Pattern::Ident(ident) => {
376            write!(buf, "{}", ident).unwrap();
377        }
378        Pattern::Wildcard => {
379            buf.push('_');
380        }
381        Pattern::Array(patterns) => {
382            buf.push('[');
383            for (i, p) in patterns.iter().enumerate() {
384                if i > 0 {
385                    buf.push_str(", ");
386                }
387                format_pattern(p, buf);
388            }
389            buf.push(']');
390        }
391        Pattern::ArrayRest(patterns, rest) => {
392            buf.push('[');
393            for (i, p) in patterns.iter().enumerate() {
394                if i > 0 {
395                    buf.push_str(", ");
396                }
397                format_pattern(p, buf);
398            }
399            if !patterns.is_empty() {
400                buf.push_str(", ");
401            }
402            write!(buf, "..{}", rest).unwrap();
403            buf.push(']');
404        }
405        Pattern::Dict(entries) => {
406            buf.push('{');
407            for (i, (key, value)) in entries.iter().enumerate() {
408                if i > 0 {
409                    buf.push_str(", ");
410                }
411                write!(buf, "{}: ", key).unwrap();
412                format_pattern(value, buf);
413            }
414            buf.push('}');
415        }
416        Pattern::Type(type_name) => {
417            write!(buf, ":{}", type_name).unwrap();
418        }
419        Pattern::Or(patterns) => {
420            for (i, p) in patterns.iter().enumerate() {
421                if i > 0 {
422                    buf.push_str(" || ");
423                }
424                format_pattern(p, buf);
425            }
426        }
427    }
428}
429
430fn needs_block_syntax(program: &Program) -> bool {
431    program.len() > 1
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437    use crate::{
438        Ident, IdentWithToken, Shared,
439        arena::ArenaId,
440        ast::node::{MatchArm, Param, Pattern},
441        number::Number,
442    };
443    use rstest::rstest;
444    use smallvec::smallvec;
445
446    // Helper function to create a Node from an Expr
447    fn create_node(expr: Expr) -> Node {
448        Node {
449            token_id: ArenaId::new(0),
450            expr: Shared::new(expr),
451        }
452    }
453
454    #[rstest]
455    #[case::string(Literal::String("hello".to_string()), r#""hello""#)]
456    #[case::string_with_quote(Literal::String(r#"hello"world"#.to_string()), r#""hello\"world""#)]
457    #[case::string_with_backslash(Literal::String(r"hello\world".to_string()), r#""hello\\world""#)]
458    #[case::string_with_newline(Literal::String("hello\nworld".to_string()), r#""hello\nworld""#)]
459    #[case::string_with_tab(Literal::String("hello\tworld".to_string()), r#""hello\tworld""#)]
460    #[case::number_int(Literal::Number(Number::new(42.0)), "42")]
461    #[case::number_float(Literal::Number(Number::new(42.5)), "42.5")]
462    #[case::symbol(Literal::Symbol(Ident::new("test")), ":test")]
463    #[case::bool_true(Literal::Bool(true), "true")]
464    #[case::bool_false(Literal::Bool(false), "false")]
465    #[case::none(Literal::None, "none")]
466    fn test_to_code_literals(#[case] literal: Literal, #[case] expected: &str) {
467        let node = create_node(Expr::Literal(literal));
468        assert_eq!(node.to_code(), expected);
469    }
470
471    #[rstest]
472    #[case::ident(Expr::Ident(IdentWithToken::new("foo")), "foo")]
473    #[case::self_(Expr::Self_, "self")]
474    #[case::nodes(Expr::Nodes, "nodes")]
475    #[case::break_(Expr::Break(None), "break")]
476    #[case::continue_(Expr::Continue, "continue")]
477    fn test_to_code_simple_expressions(#[case] expr: Expr, #[case] expected: &str) {
478        let node = create_node(expr);
479        assert_eq!(node.to_code(), expected);
480    }
481
482    #[rstest]
483    #[case::simple(Expr::Literal(Literal::Number(Number::new(42.0))), "(42)")]
484    #[case::ident(Expr::Ident(IdentWithToken::new("x")), "(x)")]
485    fn test_to_code_paren(#[case] inner_expr: Expr, #[case] expected: &str) {
486        let inner_node = Shared::new(create_node(inner_expr));
487        let node = create_node(Expr::Paren(inner_node));
488        assert_eq!(node.to_code(), expected);
489    }
490
491    #[rstest]
492    #[case::and(
493        Expr::And(vec![
494            Shared::new(create_node(Expr::Ident(IdentWithToken::new("x")))),
495            Shared::new(create_node(Expr::Ident(IdentWithToken::new("y")))),
496        ]),
497        "x && y"
498    )]
499    #[case::or(
500        Expr::Or(vec![
501            Shared::new(create_node(Expr::Ident(IdentWithToken::new("a")))),
502            Shared::new(create_node(Expr::Ident(IdentWithToken::new("b")))),
503        ]),
504        "a || b"
505    )]
506    #[case::and_three_operands(
507        Expr::And(vec![
508            Shared::new(create_node(Expr::Ident(IdentWithToken::new("x")))),
509            Shared::new(create_node(Expr::Ident(IdentWithToken::new("y")))),
510            Shared::new(create_node(Expr::Ident(IdentWithToken::new("z")))),
511        ]),
512        "x && y && z"
513    )]
514    #[case::or_three_operands(
515        Expr::Or(vec![
516            Shared::new(create_node(Expr::Ident(IdentWithToken::new("a")))),
517            Shared::new(create_node(Expr::Ident(IdentWithToken::new("b")))),
518            Shared::new(create_node(Expr::Ident(IdentWithToken::new("c")))),
519        ]),
520        "a || b || c"
521    )]
522    #[case::or_with_nested_and(
523        // Or([And([a, b]), c])
524        Expr::Or(vec![
525            Shared::new(create_node(Expr::And(vec![
526                Shared::new(create_node(Expr::Ident(IdentWithToken::new("a")))),
527                Shared::new(create_node(Expr::Ident(IdentWithToken::new("b")))),
528            ]))),
529            Shared::new(create_node(Expr::Ident(IdentWithToken::new("c")))),
530        ]),
531        "a && b || c"
532    )]
533    #[case::or_with_and_in_middle(
534        // Or([a, And([b, c]), d])
535        Expr::Or(vec![
536            Shared::new(create_node(Expr::Ident(IdentWithToken::new("a")))),
537            Shared::new(create_node(Expr::And(vec![
538                Shared::new(create_node(Expr::Ident(IdentWithToken::new("b")))),
539                Shared::new(create_node(Expr::Ident(IdentWithToken::new("c")))),
540            ]))),
541            Shared::new(create_node(Expr::Ident(IdentWithToken::new("d")))),
542        ]),
543        "a || b && c || d"
544    )]
545    fn test_to_code_operators(#[case] expr: Expr, #[case] expected: &str) {
546        let node = create_node(expr);
547        assert_eq!(node.to_code(), expected);
548    }
549
550    #[rstest]
551    #[case::no_args(
552        Expr::Call(IdentWithToken::new("test"), smallvec![]),
553        "test()"
554    )]
555    #[case::one_arg(
556        Expr::Call(
557            IdentWithToken::new("add"),
558            smallvec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]
559        ),
560        "add(1)"
561    )]
562    #[case::two_args(
563        Expr::Call(
564            IdentWithToken::new("add"),
565            smallvec![
566                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0))))),
567                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(2.0)))))
568            ]
569        ),
570        "add(1, 2)"
571    )]
572    fn test_to_code_call(#[case] expr: Expr, #[case] expected: &str) {
573        let node = create_node(expr);
574        assert_eq!(node.to_code(), expected);
575    }
576
577    #[rstest]
578    #[case::simple(
579        Expr::CallDynamic(
580            Shared::new(create_node(Expr::Ident(IdentWithToken::new("func")))),
581            smallvec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(42.0)))))]
582        ),
583        "func(42)"
584    )]
585    fn test_to_code_call_dynamic(#[case] expr: Expr, #[case] expected: &str) {
586        let node = create_node(expr);
587        assert_eq!(node.to_code(), expected);
588    }
589
590    #[rstest]
591    #[case::let_simple(
592        Expr::Let(
593            Pattern::Ident(IdentWithToken::new("x")),
594            Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(42.0)))))
595        ),
596        "let x = 42"
597    )]
598    #[case::let_array_pattern(
599        Expr::Let(
600            Pattern::Array(vec![
601                Pattern::Ident(IdentWithToken::new("a")),
602                Pattern::Ident(IdentWithToken::new("b")),
603            ]),
604            Shared::new(create_node(Expr::Ident(IdentWithToken::new("arr"))))
605        ),
606        "let [a, b] = arr"
607    )]
608    #[case::let_dict_pattern(
609        Expr::Let(
610            Pattern::Dict(vec![
611                (IdentWithToken::new("x"), Pattern::Ident(IdentWithToken::new("x"))),
612                (IdentWithToken::new("y"), Pattern::Ident(IdentWithToken::new("y"))),
613            ]),
614            Shared::new(create_node(Expr::Ident(IdentWithToken::new("d"))))
615        ),
616        "let {x: x, y: y} = d"
617    )]
618    #[case::var_simple(
619        Expr::Var(
620            Pattern::Ident(IdentWithToken::new("y")),
621            Shared::new(create_node(Expr::Literal(Literal::String("hello".to_string()))))
622        ),
623        r#"var y = "hello""#
624    )]
625    #[case::assign_simple(
626        Expr::Assign(
627            IdentWithToken::new("z"),
628            Shared::new(create_node(Expr::Ident(IdentWithToken::new("value"))))
629        ),
630        "z = value"
631    )]
632    fn test_to_code_variables(#[case] expr: Expr, #[case] expected: &str) {
633        let node = create_node(expr);
634        assert_eq!(node.to_code(), expected);
635    }
636
637    #[rstest]
638    #[case::if_simple(
639        Expr::If(smallvec![
640            (
641                Some(Shared::new(create_node(Expr::Ident(IdentWithToken::new("x"))))),
642                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))
643            )
644        ]),
645        "if (x): 1"
646    )]
647    #[case::if_else(
648        Expr::If(smallvec![
649            (
650                Some(Shared::new(create_node(Expr::Ident(IdentWithToken::new("x"))))),
651                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))
652            ),
653            (
654                None,
655                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(2.0)))))
656            )
657        ]),
658        "if (x): 1 else: 2"
659    )]
660    #[case::if_elif_else(
661        Expr::If(smallvec![
662            (
663                Some(Shared::new(create_node(Expr::Ident(IdentWithToken::new("x"))))),
664                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))
665            ),
666            (
667                Some(Shared::new(create_node(Expr::Ident(IdentWithToken::new("y"))))),
668                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(2.0)))))
669            ),
670            (
671                None,
672                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(3.0)))))
673            )
674        ]),
675        "if (x): 1 elif (y): 2 else: 3"
676    )]
677    fn test_to_code_if(#[case] expr: Expr, #[case] expected: &str) {
678        let node = create_node(expr);
679        assert_eq!(node.to_code(), expected);
680    }
681
682    #[rstest]
683    #[case::while_inline(
684        Expr::While(
685            Shared::new(create_node(Expr::Ident(IdentWithToken::new("x")))),
686            vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]
687        ),
688        "while (x): 1"
689    )]
690    #[case::while_block(
691        Expr::While(
692            Shared::new(create_node(Expr::Ident(IdentWithToken::new("x")))),
693            vec![
694                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0))))),
695                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(2.0)))))
696            ]
697        ),
698        "while (x) do\n  1 | 2\nend"
699    )]
700    fn test_to_code_while(#[case] expr: Expr, #[case] expected: &str) {
701        let node = create_node(expr);
702        assert_eq!(node.to_code(), expected);
703    }
704
705    #[rstest]
706    #[case::loop_single(
707        Expr::Loop(vec![Shared::new(create_node(Expr::Break(None)))]),
708        "loop do\n  break\nend"
709    )]
710    fn test_to_code_loop(#[case] expr: Expr, #[case] expected: &str) {
711        let node = create_node(expr);
712        assert_eq!(node.to_code(), expected);
713    }
714
715    #[rstest]
716    #[case::foreach_inline(
717        Expr::Foreach(
718            IdentWithToken::new("item"),
719            Shared::new(create_node(Expr::Ident(IdentWithToken::new("items")))),
720            vec![Shared::new(create_node(Expr::Ident(IdentWithToken::new("item"))))]
721        ),
722        "foreach(item, items): item"
723    )]
724    #[case::foreach_block(
725        Expr::Foreach(
726            IdentWithToken::new("x"),
727            Shared::new(create_node(Expr::Ident(IdentWithToken::new("arr")))),
728            vec![
729                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0))))),
730                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(2.0)))))
731            ]
732        ),
733        "foreach(x, arr) do\n  1 | 2\nend"
734    )]
735    fn test_to_code_foreach(#[case] expr: Expr, #[case] expected: &str) {
736        let node = create_node(expr);
737        assert_eq!(node.to_code(), expected);
738    }
739
740    #[rstest]
741    #[case::single(
742        Expr::Block(vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]),
743        "do\n  1\nend"
744    )]
745    #[case::multiple(
746        Expr::Block(vec![
747            Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0))))),
748            Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(2.0))))),
749            Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(3.0)))))
750        ]),
751        "do\n  1 | 2 | 3\nend"
752    )]
753    fn test_to_code_block(#[case] expr: Expr, #[case] expected: &str) {
754        let node = create_node(expr);
755        assert_eq!(node.to_code(), expected);
756    }
757
758    #[rstest]
759    #[case::no_params_inline(
760        Expr::Def(
761            IdentWithToken::new("test"),
762            smallvec![],
763            vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(42.0)))))]
764        ),
765        "def test(): 42"
766    )]
767    #[case::with_params_inline(
768        Expr::Def(
769            IdentWithToken::new("add"),
770            smallvec![
771                Param::new(IdentWithToken::new("x")),
772                Param::new(IdentWithToken::new("y"))
773            ],
774            vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]
775        ),
776        "def add(x, y): 1"
777    )]
778    #[case::with_default_value(
779        Expr::Def(
780            IdentWithToken::new("greet"),
781            smallvec![
782                Param::new(IdentWithToken::new("name")),
783                Param::with_default(
784                    IdentWithToken::new("greeting"),
785                    Some(Shared::new(create_node(Expr::Literal(Literal::String("Hello".to_string())))))
786                )
787            ],
788            vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]
789        ),
790        r#"def greet(name, greeting = "Hello"): 1"#
791    )]
792    #[case::with_multiple_defaults(
793        Expr::Def(
794            IdentWithToken::new("foo"),
795            smallvec![
796                Param::new(IdentWithToken::new("a")),
797                Param::with_default(
798                    IdentWithToken::new("b"),
799                    Some(Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(2.0))))))
800                ),
801                Param::with_default(
802                    IdentWithToken::new("c"),
803                    Some(Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(3.0))))))
804                )
805            ],
806            vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]
807        ),
808        "def foo(a, b = 2, c = 3): 1"
809    )]
810    #[case::block(
811        Expr::Def(
812            IdentWithToken::new("test"),
813            smallvec![Param::new(IdentWithToken::new("x"))],
814            vec![
815                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0))))),
816                Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(2.0)))))
817            ]
818        ),
819        "def test(x) do\n  1 | 2\nend"
820    )]
821    fn test_to_code_def(#[case] expr: Expr, #[case] expected: &str) {
822        let node = create_node(expr);
823        assert_eq!(node.to_code(), expected);
824    }
825
826    #[rstest]
827    #[case::no_params_inline(
828        Expr::Fn(
829            smallvec![],
830            vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(42.0)))))]
831        ),
832        "fn(): 42"
833    )]
834    #[case::with_params_inline(
835        Expr::Fn(
836            smallvec![
837                Param::new(IdentWithToken::new("x")),
838                Param::new(IdentWithToken::new("y"))
839            ],
840            vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]
841        ),
842        "fn(x, y): 1"
843    )]
844    #[case::with_default_value(
845        Expr::Fn(
846            smallvec![
847                Param::new(IdentWithToken::new("x")),
848                Param::with_default(
849                    IdentWithToken::new("y"),
850                    Some(Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(10.0))))))
851                )
852            ],
853            vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]
854        ),
855        "fn(x, y = 10): 1"
856    )]
857    fn test_to_code_fn(#[case] expr: Expr, #[case] expected: &str) {
858        let node = create_node(expr);
859        assert_eq!(node.to_code(), expected);
860    }
861
862    #[rstest]
863    #[case::simple(
864        Expr::Match(
865            Shared::new(create_node(Expr::Ident(IdentWithToken::new("x")))),
866            smallvec![
867                MatchArm {
868                    pattern: Pattern::Literal(Literal::Number(Number::new(1.0))),
869                    guard: None,
870                    body: Shared::new(create_node(Expr::Literal(Literal::String("one".to_string()))))
871                },
872                MatchArm {
873                    pattern: Pattern::Wildcard,
874                    guard: None,
875                    body: Shared::new(create_node(Expr::Literal(Literal::String("other".to_string()))))
876                }
877            ]
878        ),
879        "match (x) do\n  | 1: \"one\"\n  | _: \"other\"\nend"
880    )]
881    fn test_to_code_match(#[case] expr: Expr, #[case] expected: &str) {
882        let node = create_node(expr);
883        assert_eq!(node.to_code(), expected);
884    }
885
886    #[rstest]
887    #[case::text_only(
888        Expr::InterpolatedString(vec![StringSegment::Text("hello".to_string())]),
889        r#"s"hello""#
890    )]
891    #[case::with_expr(
892        Expr::InterpolatedString(vec![
893            StringSegment::Text("Hello ".to_string()),
894            StringSegment::Expr(Shared::new(create_node(Expr::Ident(IdentWithToken::new("name"))))),
895            StringSegment::Text("!".to_string())
896        ]),
897        r#"s"Hello ${name}!""#
898    )]
899    fn test_to_code_interpolated_string(#[case] expr: Expr, #[case] expected: &str) {
900        let node = create_node(expr);
901        assert_eq!(node.to_code(), expected);
902    }
903
904    #[rstest]
905    #[case::simple(
906        Expr::Macro(
907            IdentWithToken::new("double"),
908            smallvec![Param::new(IdentWithToken::new("x"))],
909            Shared::new(create_node(Expr::Ident(IdentWithToken::new("x"))))
910        ),
911        "macro double(x): x"
912    )]
913    #[case::with_default_value(
914        Expr::Macro(
915            IdentWithToken::new("greet_macro"),
916            smallvec![
917                Param::new(IdentWithToken::new("name")),
918                Param::with_default(
919                    IdentWithToken::new("prefix"),
920                    Some(Shared::new(create_node(Expr::Literal(Literal::String("Hi".to_string())))))
921                )
922            ],
923            Shared::new(create_node(Expr::Ident(IdentWithToken::new("name"))))
924        ),
925        r#"macro greet_macro(name, prefix = "Hi"): name"#
926    )]
927    fn test_to_code_macro(#[case] expr: Expr, #[case] expected: &str) {
928        let node = create_node(expr);
929        assert_eq!(node.to_code(), expected);
930    }
931
932    #[rstest]
933    #[case::quote(
934        Expr::Quote(Shared::new(create_node(Expr::Ident(IdentWithToken::new("x"))))),
935        "quote: x"
936    )]
937    #[case::unquote(
938        Expr::Unquote(Shared::new(create_node(Expr::Ident(IdentWithToken::new("x"))))),
939        "unquote(x)"
940    )]
941    fn test_to_code_quote_unquote(#[case] expr: Expr, #[case] expected: &str) {
942        let node = create_node(expr);
943        assert_eq!(node.to_code(), expected);
944    }
945
946    #[rstest]
947    #[case::simple(
948        Expr::Try(
949            Shared::new(create_node(Expr::Ident(IdentWithToken::new("risky")))),
950            Shared::new(create_node(Expr::Literal(Literal::String("error".to_string()))))
951        ),
952        r#"try risky catch: "error""#
953    )]
954    fn test_to_code_try(#[case] expr: Expr, #[case] expected: &str) {
955        let node = create_node(expr);
956        assert_eq!(node.to_code(), expected);
957    }
958
959    #[rstest]
960    #[case::simple(
961        Expr::Module(
962            IdentWithToken::new("math"),
963            vec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]
964        ),
965        "module math do\n  1\nend"
966    )]
967    fn test_to_code_module(#[case] expr: Expr, #[case] expected: &str) {
968        let node = create_node(expr);
969        assert_eq!(node.to_code(), expected);
970    }
971
972    #[rstest]
973    #[case::ident(
974        Expr::QualifiedAccess(
975            vec![IdentWithToken::new("module"), IdentWithToken::new("submodule")],
976            AccessTarget::Ident(IdentWithToken::new("func"))
977        ),
978        "module::submodule::func"
979    )]
980    #[case::call(
981        Expr::QualifiedAccess(
982            vec![IdentWithToken::new("module")],
983            AccessTarget::Call(
984                IdentWithToken::new("func"),
985                smallvec![Shared::new(create_node(Expr::Literal(Literal::Number(Number::new(1.0)))))]
986            )
987        ),
988        "module::func(1)"
989    )]
990    fn test_to_code_qualified_access(#[case] expr: Expr, #[case] expected: &str) {
991        let node = create_node(expr);
992        assert_eq!(node.to_code(), expected);
993    }
994
995    #[rstest]
996    #[case::include(
997        Expr::Include(Literal::String("file.mq".to_string())),
998        r#"include "file.mq""#
999    )]
1000    #[case::import(
1001        Expr::Import(Literal::String("module.mq".to_string())),
1002        r#"import "module.mq""#
1003    )]
1004    fn test_to_code_include_import(#[case] expr: Expr, #[case] expected: &str) {
1005        let node = create_node(expr);
1006        assert_eq!(node.to_code(), expected);
1007    }
1008
1009    // Complex expression tests
1010    #[rstest]
1011    #[case::nested_call(
1012        Expr::Call(
1013            IdentWithToken::new("map"),
1014            smallvec![
1015                Shared::new(create_node(Expr::Call(
1016                    IdentWithToken::new("filter"),
1017                    smallvec![Shared::new(create_node(Expr::Ident(IdentWithToken::new("items"))))]
1018                )))
1019            ]
1020        ),
1021        "map(filter(items))"
1022    )]
1023    fn test_to_code_complex(#[case] expr: Expr, #[case] expected: &str) {
1024        let node = create_node(expr);
1025        assert_eq!(node.to_code(), expected);
1026    }
1027}