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