Skip to main content

zuzu_rust/
codegen.rs

1use crate::ast::{
2    BlockStatement, CallArgument, CatchBinding, CatchClause, ClassDeclaration, ClassMember,
3    DeclarationBindingEntry, DictEntry, DictKey, Expression, FieldDeclaration, FunctionDeclaration,
4    ImportDeclaration, MethodDeclaration, Parameter, Program, Statement, SwitchCase, TemplatePart,
5    TraitDeclaration,
6};
7
8const PREC_ASSIGNMENT: u8 = 1;
9const PREC_CHAIN: u8 = 2;
10const PREC_TERNARY: u8 = 3;
11const PREC_OR: u8 = 4;
12const PREC_ONLYIF: u8 = 5;
13const PREC_XOR: u8 = 6;
14const PREC_AND: u8 = 7;
15const PREC_EQUALITY: u8 = 8;
16const PREC_COMPARISON: u8 = 9;
17const PREC_BITWISE_OR: u8 = 10;
18const PREC_BITWISE_XOR: u8 = 11;
19const PREC_BITWISE_AND: u8 = 12;
20const PREC_SHIFT: u8 = 13;
21const PREC_SET: u8 = 14;
22const PREC_CONCAT: u8 = 15;
23const PREC_ADDITIVE: u8 = 16;
24const PREC_MULTIPLICATIVE: u8 = 17;
25const PREC_EXPONENT: u8 = 18;
26const PREC_PREFIX: u8 = 19;
27const PREC_POSTFIX: u8 = 20;
28const PREC_ATOM: u8 = 21;
29
30pub fn render_program(program: &Program) -> String {
31    let mut out = String::new();
32    for statement in &program.statements {
33        render_statement(statement, 0, &mut out);
34        out.push('\n');
35    }
36    out
37}
38
39pub fn render_function_declaration(node: &FunctionDeclaration) -> String {
40    let mut out = String::new();
41    render_function_declaration_into(node, 0, &mut out);
42    out
43}
44
45pub fn render_class_declaration(node: &ClassDeclaration) -> String {
46    let mut out = String::new();
47    render_class_declaration_into(node, 0, &mut out);
48    out
49}
50
51pub fn render_trait_declaration(node: &TraitDeclaration) -> String {
52    let mut out = String::new();
53    render_trait_declaration_into(node, 0, &mut out);
54    out
55}
56
57pub fn render_block(block: &BlockStatement) -> String {
58    let mut out = String::new();
59    render_block_into(block, 0, &mut out);
60    out
61}
62
63pub fn render_expression(expression: &Expression) -> String {
64    render_expr(expression, 0)
65}
66
67fn is_synthetic_here_lambda(params: &[Parameter]) -> bool {
68    params.len() == 1
69        && params[0].name == "^^"
70        && params[0].optional
71        && !params[0].variadic
72        && params[0].declared_type.is_none()
73        && params[0].default_value.is_none()
74}
75
76pub fn render_function_literal(
77    params: &[Parameter],
78    return_type: Option<&str>,
79    body: &BlockStatement,
80    is_async: bool,
81) -> String {
82    let mut out = String::new();
83    if is_async {
84        out.push_str("async ");
85    }
86    out.push_str("function ");
87    render_parameter_list(params, &mut out);
88    render_return_type(return_type, &mut out);
89    out.push(' ');
90    render_block_into(body, 0, &mut out);
91    out
92}
93
94fn render_statement(statement: &Statement, indent: usize, out: &mut String) {
95    match statement {
96        Statement::Block(block) => {
97            push_indent(out, indent);
98            render_block_into(block, indent, out);
99        }
100        Statement::VariableDeclaration(node) => {
101            push_indent(out, indent);
102            out.push_str(&render_variable_head(
103                &node.kind,
104                node.declared_type.as_deref(),
105                &node.name,
106                node.init.as_ref(),
107                node.is_weak_storage,
108            ));
109            out.push(';');
110        }
111        Statement::VariableUnpackDeclaration(node) => {
112            push_indent(out, indent);
113            out.push_str(&node.kind);
114            out.push(' ');
115            out.push_str(&render_binding_pattern(node.pattern.entries()));
116            out.push_str(" := ");
117            out.push_str(&render_expression(&node.init));
118            out.push(';');
119        }
120        Statement::FunctionDeclaration(node) => render_function_declaration_into(node, indent, out),
121        Statement::ClassDeclaration(node) => render_class_declaration_into(node, indent, out),
122        Statement::TraitDeclaration(node) => render_trait_declaration_into(node, indent, out),
123        Statement::ImportDeclaration(node) => render_import_declaration(node, indent, out),
124        Statement::IfStatement(node) => {
125            push_indent(out, indent);
126            out.push_str("if ( ");
127            out.push_str(&render_expression(&node.test));
128            out.push_str(" ) ");
129            render_block_into(&node.consequent, indent, out);
130            if let Some(alternate) = &node.alternate {
131                out.push('\n');
132                push_indent(out, indent);
133                out.push_str("else");
134                match alternate.as_ref() {
135                    Statement::IfStatement(_) => {
136                        out.push(' ');
137                        render_statement_without_indent(alternate, indent, out);
138                    }
139                    Statement::Block(block) => {
140                        out.push(' ');
141                        render_block_into(block, indent, out);
142                    }
143                    other => {
144                        out.push('\n');
145                        render_statement(other, indent + 1, out);
146                    }
147                }
148            }
149        }
150        Statement::WhileStatement(node) => {
151            push_indent(out, indent);
152            out.push_str("while ( ");
153            out.push_str(&render_expression(&node.test));
154            out.push_str(" ) ");
155            render_block_into(&node.body, indent, out);
156        }
157        Statement::ForStatement(node) => {
158            push_indent(out, indent);
159            out.push_str("for ( ");
160            if let Some(kind) = &node.binding_kind {
161                out.push_str(kind);
162                out.push(' ');
163            }
164            out.push_str(&node.variable);
165            out.push_str(" in ");
166            out.push_str(&render_expression(&node.iterable));
167            out.push_str(" ) ");
168            render_block_into(&node.body, indent, out);
169            if let Some(else_block) = &node.else_block {
170                out.push('\n');
171                push_indent(out, indent);
172                out.push_str("else ");
173                render_block_into(else_block, indent, out);
174            }
175        }
176        Statement::SwitchStatement(node) => {
177            push_indent(out, indent);
178            out.push_str("switch ( ");
179            out.push_str(&render_expression(&node.discriminant));
180            if let Some(comparator) = &node.comparator {
181                out.push_str(" : ");
182                out.push_str(comparator);
183            }
184            out.push_str(" ) {\n");
185            for case in &node.cases {
186                render_switch_case(case, indent + 1, out);
187            }
188            if let Some(default) = &node.default {
189                push_indent(out, indent + 1);
190                out.push_str("default:\n");
191                render_statement_list(default, indent + 2, out);
192            }
193            push_indent(out, indent);
194            out.push('}');
195        }
196        Statement::TryStatement(node) => {
197            push_indent(out, indent);
198            out.push_str("try ");
199            render_block_into(&node.body, indent, out);
200            for handler in &node.handlers {
201                out.push('\n');
202                push_indent(out, indent);
203                render_catch_clause(handler, indent, out);
204            }
205        }
206        Statement::ReturnStatement(node) => {
207            push_indent(out, indent);
208            out.push_str("return");
209            if let Some(argument) = &node.argument {
210                out.push(' ');
211                out.push_str(&render_expression(argument));
212            }
213            out.push(';');
214        }
215        Statement::LoopControlStatement(node) => {
216            push_indent(out, indent);
217            out.push_str(&node.keyword);
218            out.push(';');
219        }
220        Statement::ThrowStatement(node) => {
221            push_indent(out, indent);
222            out.push_str("throw ");
223            out.push_str(&render_expression(&node.argument));
224            out.push(';');
225        }
226        Statement::DieStatement(node) => {
227            push_indent(out, indent);
228            out.push_str("die ");
229            out.push_str(&render_expression(&node.argument));
230            out.push(';');
231        }
232        Statement::PostfixConditionalStatement(node) => {
233            push_indent(out, indent);
234            out.push_str(&render_inline_statement(&node.statement));
235            out.push(' ');
236            out.push_str(&node.keyword);
237            out.push(' ');
238            out.push_str(&render_expression(&node.test));
239            out.push(';');
240        }
241        Statement::KeywordStatement(node) => {
242            push_indent(out, indent);
243            out.push_str(&node.keyword);
244            if !node.arguments.is_empty() {
245                out.push(' ');
246                out.push_str(&render_expression_list(&node.arguments));
247            }
248            out.push(';');
249        }
250        Statement::ExpressionStatement(node) => {
251            push_indent(out, indent);
252            out.push_str(&render_expression(&node.expression));
253            out.push(';');
254        }
255    }
256}
257
258fn render_statement_without_indent(statement: &Statement, indent: usize, out: &mut String) {
259    let mut rendered = String::new();
260    render_statement(statement, indent, &mut rendered);
261    out.push_str(rendered.trim_start_matches('\t'));
262}
263
264fn render_statement_list(statements: &[Statement], indent: usize, out: &mut String) {
265    for statement in statements {
266        render_statement(statement, indent, out);
267        out.push('\n');
268    }
269}
270
271fn render_block_into(block: &BlockStatement, indent: usize, out: &mut String) {
272    out.push_str("{\n");
273    render_statement_list(&block.statements, indent + 1, out);
274    push_indent(out, indent);
275    out.push('}');
276}
277
278fn render_function_declaration_into(node: &FunctionDeclaration, indent: usize, out: &mut String) {
279    push_indent(out, indent);
280    if node.is_async {
281        out.push_str("async ");
282    }
283    out.push_str("function ");
284    out.push_str(&node.name);
285    if node.is_predeclared {
286        out.push(';');
287        return;
288    }
289    out.push(' ');
290    render_parameter_list(&node.params, out);
291    render_return_type(node.return_type.as_deref(), out);
292    out.push(' ');
293    render_block_into(&node.body, indent, out);
294}
295
296fn render_class_declaration_into(node: &ClassDeclaration, indent: usize, out: &mut String) {
297    push_indent(out, indent);
298    out.push_str("class ");
299    out.push_str(&node.name);
300    if let Some(base) = &node.base {
301        out.push_str(" extends ");
302        out.push_str(base);
303    }
304    if !node.traits.is_empty() {
305        out.push_str(" with ");
306        out.push_str(&node.traits.join(", "));
307    }
308    if node.shorthand {
309        out.push(';');
310        return;
311    }
312    out.push_str(" {\n");
313    for member in &node.body {
314        render_class_member(member, indent + 1, out);
315        out.push('\n');
316    }
317    push_indent(out, indent);
318    out.push('}');
319}
320
321fn render_trait_declaration_into(node: &TraitDeclaration, indent: usize, out: &mut String) {
322    push_indent(out, indent);
323    out.push_str("trait ");
324    out.push_str(&node.name);
325    if node.shorthand {
326        out.push(';');
327        return;
328    }
329    out.push_str(" {\n");
330    for member in &node.body {
331        render_class_member(member, indent + 1, out);
332        out.push('\n');
333    }
334    push_indent(out, indent);
335    out.push('}');
336}
337
338fn render_class_member(member: &ClassMember, indent: usize, out: &mut String) {
339    match member {
340        ClassMember::Field(field) => render_field_declaration(field, indent, out),
341        ClassMember::Method(method) => render_method_declaration(method, indent, out),
342        ClassMember::Class(class) => render_class_declaration_into(class, indent, out),
343        ClassMember::Trait(trait_node) => render_trait_declaration_into(trait_node, indent, out),
344    }
345}
346
347fn render_field_declaration(field: &FieldDeclaration, indent: usize, out: &mut String) {
348    push_indent(out, indent);
349    out.push_str(&field.kind);
350    out.push(' ');
351    if let Some(declared_type) = &field.declared_type {
352        out.push_str(declared_type);
353        out.push(' ');
354    }
355    out.push_str(&field.name);
356    if !field.accessors.is_empty() {
357        out.push_str(" with ");
358        out.push_str(&field.accessors.join(", "));
359    }
360    if let Some(default_value) = &field.default_value {
361        out.push_str(" := ");
362        out.push_str(&render_expression(default_value));
363    }
364    if field.is_weak_storage {
365        out.push_str(" but weak");
366    }
367    out.push(';');
368}
369
370fn render_method_declaration(method: &MethodDeclaration, indent: usize, out: &mut String) {
371    push_indent(out, indent);
372    if method.is_static {
373        out.push_str("static ");
374    }
375    if method.is_async {
376        out.push_str("async ");
377    }
378    out.push_str("method ");
379    out.push_str(&method.name);
380    if method.is_predeclared {
381        out.push(';');
382        return;
383    }
384    out.push(' ');
385    render_parameter_list(&method.params, out);
386    render_return_type(method.return_type.as_deref(), out);
387    out.push(' ');
388    render_block_into(&method.body, indent, out);
389}
390
391fn render_variable_head(
392    kind: &str,
393    declared_type: Option<&str>,
394    name: &str,
395    init: Option<&Expression>,
396    is_weak_storage: bool,
397) -> String {
398    let mut out = String::new();
399    out.push_str(kind);
400    out.push(' ');
401    if let Some(declared_type) = declared_type {
402        out.push_str(declared_type);
403        out.push(' ');
404    }
405    out.push_str(name);
406    if let Some(init) = init {
407        out.push_str(" := ");
408        out.push_str(&render_expression(init));
409    }
410    if is_weak_storage {
411        out.push_str(" but weak");
412    }
413    out
414}
415
416fn render_binding_pattern(entries: &[DeclarationBindingEntry]) -> String {
417    format!(
418        "{{ {} }}",
419        entries
420            .iter()
421            .map(render_binding_entry)
422            .collect::<Vec<_>>()
423            .join(", ")
424    )
425}
426
427fn render_binding_entry(entry: &DeclarationBindingEntry) -> String {
428    let key = render_dict_literal_key(&entry.key);
429    let shorthand = matches!(&entry.key, DictKey::Identifier { name, .. } if name == &entry.name);
430    let mut out = String::new();
431    if shorthand {
432        if let Some(declared_type) = &entry.declared_type {
433            out.push_str(declared_type);
434            out.push(' ');
435        }
436        out.push_str(&entry.name);
437    } else {
438        out.push_str(&key);
439        out.push_str(": ");
440        if let Some(declared_type) = &entry.declared_type {
441            out.push_str(declared_type);
442            out.push(' ');
443        }
444        out.push_str(&entry.name);
445    }
446    if let Some(default_value) = &entry.default_value {
447        out.push_str(" := ");
448        out.push_str(&render_expression(default_value));
449    }
450    if entry.is_weak_storage {
451        out.push_str(" but weak");
452    }
453    out
454}
455
456fn render_import_declaration(node: &ImportDeclaration, indent: usize, out: &mut String) {
457    push_indent(out, indent);
458    out.push_str("from ");
459    out.push_str(&node.source);
460    out.push(' ');
461    if node.try_mode {
462        out.push_str("try ");
463    }
464    out.push_str("import ");
465    if node.import_all {
466        out.push('*');
467    } else {
468        out.push_str(
469            &node
470                .specifiers
471                .iter()
472                .map(|specifier| {
473                    if specifier.imported == specifier.local {
474                        specifier.imported.clone()
475                    } else {
476                        format!("{} as {}", specifier.imported, specifier.local)
477                    }
478                })
479                .collect::<Vec<_>>()
480                .join(", "),
481        );
482    }
483    if let Some(condition) = &node.condition {
484        out.push(' ');
485        out.push_str(&condition.keyword);
486        out.push(' ');
487        out.push_str(&render_expression(&condition.test));
488    }
489    out.push(';');
490}
491
492fn render_switch_case(case: &SwitchCase, indent: usize, out: &mut String) {
493    push_indent(out, indent);
494    out.push_str("case ");
495    for (index, value) in case.values.iter().enumerate() {
496        if index > 0 {
497            out.push_str(", ");
498        }
499        if let Some(operator) = case.operators.get(index).and_then(|value| value.as_deref()) {
500            out.push_str(operator);
501            out.push(' ');
502        }
503        out.push_str(&render_expression(value));
504    }
505    out.push_str(":\n");
506    render_statement_list(&case.consequent, indent + 1, out);
507}
508
509fn render_catch_clause(handler: &CatchClause, indent: usize, out: &mut String) {
510    out.push_str("catch");
511    if let Some(binding) = &handler.binding {
512        out.push_str(" ( ");
513        out.push_str(&render_catch_binding(binding));
514        out.push_str(" )");
515    }
516    out.push(' ');
517    render_block_into(&handler.body, indent, out);
518}
519
520fn render_catch_binding(binding: &CatchBinding) -> String {
521    match (binding.declared_type.as_deref(), binding.name.as_deref()) {
522        (Some(declared_type), Some(name)) => format!("{declared_type} {name}"),
523        (None, Some(name)) => name.to_owned(),
524        (Some(declared_type), None) => declared_type.to_owned(),
525        (None, None) => String::new(),
526    }
527}
528
529fn render_inline_statement(statement: &Statement) -> String {
530    match statement {
531        Statement::ExpressionStatement(node) => render_expression(&node.expression),
532        Statement::ReturnStatement(node) => match &node.argument {
533            Some(argument) => format!("return {}", render_expression(argument)),
534            None => "return".to_owned(),
535        },
536        Statement::LoopControlStatement(node) => node.keyword.clone(),
537        Statement::ThrowStatement(node) => format!("throw {}", render_expression(&node.argument)),
538        Statement::DieStatement(node) => format!("die {}", render_expression(&node.argument)),
539        Statement::KeywordStatement(node) => {
540            if node.arguments.is_empty() {
541                node.keyword.clone()
542            } else {
543                format!(
544                    "{} {}",
545                    node.keyword,
546                    render_expression_list(&node.arguments)
547                )
548            }
549        }
550        other => {
551            let mut out = String::new();
552            render_statement(other, 0, &mut out);
553            out.trim_end_matches(';').to_owned()
554        }
555    }
556}
557
558fn render_parameter_list(params: &[Parameter], out: &mut String) {
559    out.push('(');
560    if !params.is_empty() {
561        out.push(' ');
562        out.push_str(
563            &params
564                .iter()
565                .map(render_parameter)
566                .collect::<Vec<_>>()
567                .join(", "),
568        );
569        out.push(' ');
570    }
571    out.push(')');
572}
573
574fn render_return_type(return_type: Option<&str>, out: &mut String) {
575    if let Some(return_type) = return_type {
576        out.push_str(" -> ");
577        out.push_str(return_type);
578    }
579}
580
581fn render_parameter(param: &Parameter) -> String {
582    let mut out = String::new();
583    if param.variadic {
584        out.push_str("...");
585    }
586    if let Some(declared_type) = &param.declared_type {
587        out.push_str(declared_type);
588        out.push(' ');
589    }
590    out.push_str(&param.name);
591    if param.optional {
592        out.push('?');
593    }
594    if let Some(default_value) = &param.default_value {
595        out.push_str(" := ");
596        out.push_str(&render_expression(default_value));
597    }
598    out
599}
600
601fn render_expr(expression: &Expression, parent_prec: u8) -> String {
602    let (text, prec) = match expression {
603        Expression::Identifier { name, .. } => (name.clone(), PREC_ATOM),
604        Expression::NumberLiteral { value, .. } => (value.clone(), PREC_ATOM),
605        Expression::StringLiteral { value, .. } => (quote_string(value), PREC_ATOM),
606        Expression::BinaryStringLiteral { bytes, .. } => (quote_binary_string(bytes), PREC_ATOM),
607        Expression::RegexLiteral {
608            pattern,
609            parts,
610            flags,
611            ..
612        } => (render_regex_literal(pattern, parts, flags), PREC_ATOM),
613        Expression::BooleanLiteral { value, .. } => {
614            (if *value { "true" } else { "false" }.to_owned(), PREC_ATOM)
615        }
616        Expression::NullLiteral { .. } => ("null".to_owned(), PREC_ATOM),
617        Expression::ArrayLiteral { elements, .. } => (
618            format!("[ {} ]", render_expression_list(elements)),
619            PREC_ATOM,
620        ),
621        Expression::SetLiteral { elements, .. } => (
622            format!("<< {} >>", render_expression_list(elements)),
623            PREC_ATOM,
624        ),
625        Expression::BagLiteral { elements, .. } => (
626            format!("<<< {} >>>", render_expression_list(elements)),
627            PREC_ATOM,
628        ),
629        Expression::DictLiteral { entries, .. } => {
630            (format!("{{ {} }}", render_dict_entries(entries)), PREC_ATOM)
631        }
632        Expression::PairListLiteral { entries, .. } => (
633            format!("{{{{ {} }}}}", render_dict_entries(entries)),
634            PREC_ATOM,
635        ),
636        Expression::TemplateLiteral { parts, .. } => (render_template_literal(parts), PREC_ATOM),
637        Expression::Unary {
638            operator,
639            argument,
640            traits,
641            ..
642        } => {
643            if operator == "new" && !traits.is_empty() {
644                if let Expression::Call {
645                    callee, arguments, ..
646                } = argument.as_ref()
647                {
648                    (
649                        format!(
650                            "new {} with {}{}",
651                            render_expr(callee, PREC_POSTFIX),
652                            traits.join(", "),
653                            render_call_arguments(arguments)
654                        ),
655                        PREC_PREFIX,
656                    )
657                } else {
658                    let argument = render_expr(argument, PREC_PREFIX);
659                    (format!("{operator} {argument}"), PREC_PREFIX)
660                }
661            } else {
662                let argument = render_expr(argument, PREC_PREFIX);
663                let text = if is_word_operator(operator) {
664                    format!("{operator} {argument}")
665                } else {
666                    format!("{operator}{argument}")
667                };
668                (text, PREC_PREFIX)
669            }
670        }
671        Expression::Binary {
672            operator,
673            left,
674            right,
675            ..
676        } => {
677            let prec = infix_precedence(operator);
678            let right_parent = if is_right_associative(operator) {
679                prec
680            } else {
681                prec + 1
682            };
683            (
684                format!(
685                    "{} {} {}",
686                    render_expr(left, prec),
687                    preferred_render_operator(operator),
688                    render_expr(right, right_parent)
689                ),
690                prec,
691            )
692        }
693        Expression::Ternary {
694            test,
695            consequent,
696            alternate,
697            ..
698        } => (
699            format!(
700                "{} ? {} : {}",
701                render_expr(test, PREC_TERNARY),
702                render_expression(consequent),
703                render_expr(alternate, PREC_TERNARY)
704            ),
705            PREC_TERNARY,
706        ),
707        Expression::DefinedOr { left, right, .. } => (
708            format!(
709                "{} ?: {}",
710                render_expr(left, PREC_TERNARY),
711                render_expr(right, PREC_TERNARY)
712            ),
713            PREC_TERNARY,
714        ),
715        Expression::Assignment {
716            operator,
717            left,
718            right,
719            is_weak_write,
720            ..
721        } => (
722            {
723                let mut out = format!(
724                    "{} {} {}",
725                    render_expr(left, PREC_ASSIGNMENT),
726                    operator,
727                    render_expr(right, PREC_ASSIGNMENT)
728                );
729                if *is_weak_write {
730                    out.push_str(" but weak");
731                }
732                out
733            },
734            PREC_ASSIGNMENT,
735        ),
736        Expression::Call {
737            callee, arguments, ..
738        } => (
739            format!(
740                "{}{}",
741                render_expr(callee, PREC_POSTFIX),
742                render_call_arguments(arguments)
743            ),
744            PREC_POSTFIX,
745        ),
746        Expression::MemberAccess { object, member, .. } => (
747            format!("{}.{}", render_expr(object, PREC_POSTFIX), member),
748            PREC_POSTFIX,
749        ),
750        Expression::DynamicMemberCall {
751            object,
752            member,
753            arguments,
754            ..
755        } => (
756            format!(
757                "{}.( {} ){}",
758                render_expr(object, PREC_POSTFIX),
759                render_expression(member),
760                render_call_arguments(arguments)
761            ),
762            PREC_POSTFIX,
763        ),
764        Expression::Index { object, index, .. } => (
765            format!(
766                "{}[{}]",
767                render_expr(object, PREC_POSTFIX),
768                render_expression(index)
769            ),
770            PREC_POSTFIX,
771        ),
772        Expression::Slice {
773            object, start, end, ..
774        } => {
775            let start = start
776                .as_ref()
777                .map(|expr| render_expression(expr))
778                .unwrap_or_default();
779            let end = end
780                .as_ref()
781                .map(|expr| render_expression(expr))
782                .unwrap_or_default();
783            (
784                format!("{}[{}:{}]", render_expr(object, PREC_POSTFIX), start, end),
785                PREC_POSTFIX,
786            )
787        }
788        Expression::DictAccess { object, key, .. } => (
789            format!(
790                "{}{{{}}}",
791                render_expr(object, PREC_POSTFIX),
792                render_dict_access_key(key)
793            ),
794            PREC_POSTFIX,
795        ),
796        Expression::PostfixUpdate {
797            operator, argument, ..
798        } => (
799            format!("{}{}", render_expr(argument, PREC_POSTFIX), operator),
800            PREC_POSTFIX,
801        ),
802        Expression::Lambda {
803            params,
804            body,
805            is_async,
806            ..
807        } => {
808            if !*is_async && is_synthetic_here_lambda(params) {
809                (format!("-> {}", render_expression(body)), PREC_ATOM)
810            } else {
811                let mut out = String::new();
812                if *is_async {
813                    out.push_str("async ");
814                }
815                out.push_str("fn ");
816                render_parameter_list(params, &mut out);
817                out.push_str(" -> ");
818                out.push_str(&render_expression(body));
819                (out, PREC_ATOM)
820            }
821        }
822        Expression::FunctionExpression {
823            params,
824            return_type,
825            body,
826            is_async,
827            ..
828        } => (
829            render_function_literal(params, return_type.as_deref(), body, *is_async),
830            PREC_ATOM,
831        ),
832        Expression::LetExpression {
833            kind,
834            declared_type,
835            name,
836            init,
837            is_weak_storage,
838            ..
839        } => {
840            let mut out = String::new();
841            out.push_str(kind);
842            out.push(' ');
843            if let Some(declared_type) = declared_type {
844                out.push_str(declared_type);
845                out.push(' ');
846            }
847            out.push_str(name);
848            if let Some(init) = init {
849                out.push_str(" := ");
850                out.push_str(&render_expression(init));
851            }
852            if *is_weak_storage {
853                out.push_str(" but weak");
854            }
855            (out, PREC_PREFIX)
856        }
857        Expression::TryExpression { body, handlers, .. } => {
858            let mut out = String::from("try ");
859            render_block_into(body, 0, &mut out);
860            for handler in handlers {
861                out.push(' ');
862                render_catch_clause(handler, 0, &mut out);
863            }
864            (out, PREC_ATOM)
865        }
866        Expression::DoExpression { body, .. } => {
867            let mut out = String::from("do ");
868            render_block_into(body, 0, &mut out);
869            (out, PREC_ATOM)
870        }
871        Expression::AwaitExpression { body, .. } => {
872            let mut out = String::from("await ");
873            render_block_into(body, 0, &mut out);
874            (out, PREC_ATOM)
875        }
876        Expression::SpawnExpression { body, .. } => {
877            let mut out = String::from("spawn ");
878            render_block_into(body, 0, &mut out);
879            (out, PREC_ATOM)
880        }
881        Expression::SuperCall { arguments, .. } => (
882            format!("super{}", render_call_arguments(arguments)),
883            PREC_ATOM,
884        ),
885    };
886    if prec < parent_prec {
887        format!("({text})")
888    } else {
889        text
890    }
891}
892
893fn render_expression_list(expressions: &[Expression]) -> String {
894    expressions
895        .iter()
896        .map(render_expression)
897        .collect::<Vec<_>>()
898        .join(", ")
899}
900
901fn render_dict_entries(entries: &[DictEntry]) -> String {
902    entries
903        .iter()
904        .map(|entry| {
905            format!(
906                "{}: {}",
907                render_dict_literal_key(&entry.key),
908                render_expression(&entry.value)
909            )
910        })
911        .collect::<Vec<_>>()
912        .join(", ")
913}
914
915fn render_call_arguments(arguments: &[CallArgument]) -> String {
916    if arguments.is_empty() {
917        return "()".to_owned();
918    }
919    format!(
920        "( {} )",
921        arguments
922            .iter()
923            .map(|argument| match argument {
924                CallArgument::Positional { value, .. } => render_expression(value),
925                CallArgument::Spread { value, .. } => format!("...{}", render_expression(value)),
926                CallArgument::Named { name, value, .. } => {
927                    format!("{}: {}", render_call_key(name), render_expression(value))
928                }
929            })
930            .collect::<Vec<_>>()
931            .join(", ")
932    )
933}
934
935fn render_dict_literal_key(key: &DictKey) -> String {
936    match key {
937        DictKey::Identifier { name, .. } => name.clone(),
938        DictKey::StringLiteral { value, .. } => quote_string(value),
939        DictKey::Expression { expression, .. } => format!("({})", render_expression(expression)),
940    }
941}
942
943fn render_dict_access_key(key: &DictKey) -> String {
944    match key {
945        DictKey::Identifier { name, .. } => name.clone(),
946        DictKey::StringLiteral { value, .. } => quote_string(value),
947        DictKey::Expression { expression, .. } => render_expression(expression),
948    }
949}
950
951fn render_call_key(key: &DictKey) -> String {
952    match key {
953        DictKey::Identifier { name, .. } => name.clone(),
954        DictKey::StringLiteral { value, .. } => quote_string(value),
955        DictKey::Expression { expression, .. } => format!("({})", render_expression(expression)),
956    }
957}
958
959fn render_template_literal(parts: &[TemplatePart]) -> String {
960    let mut out = String::from("`");
961    for part in parts {
962        match part {
963            TemplatePart::Text { value, .. } => out.push_str(&quote_template_text(value)),
964            TemplatePart::Expression { expression, .. } => {
965                out.push_str("${ ");
966                out.push_str(&render_expression(expression));
967                out.push_str(" }");
968            }
969        }
970    }
971    out.push('`');
972    out
973}
974
975fn render_regex_literal(pattern: &str, parts: &[TemplatePart], flags: &str) -> String {
976    if parts.is_empty() {
977        return format!("/{}/{}", quote_regex_pattern(pattern), flags);
978    }
979
980    let mut out = String::from("/");
981    for part in parts {
982        match part {
983            TemplatePart::Text { value, .. } => out.push_str(&quote_regex_pattern(value)),
984            TemplatePart::Expression { expression, .. } => {
985                out.push_str("${ ");
986                out.push_str(&render_expression(expression));
987                out.push_str(" }");
988            }
989        }
990    }
991    out.push('/');
992    out.push_str(flags);
993    out
994}
995
996fn infix_precedence(operator: &str) -> u8 {
997    match operator {
998        "or" | "⋁" | "or?" | "⋁?" => PREC_OR,
999        "▷" | "|>" | "◁" | "<|" => PREC_CHAIN,
1000        "onlyif" | "⊨" | "onlyif?" | "⊨?" => PREC_ONLYIF,
1001        "xor" | "⊻" | "xor?" | "⊻?" | "nor" | "⊽" | "nor?" | "⊽?" | "xnor" | "↔" | "xnor?"
1002        | "↔?" => PREC_XOR,
1003        "and" | "⋀" | "and?" | "⋀?" | "nand" | "⊼" | "nand?" | "⊼?" | "butnot" | "⊭"
1004        | "butnot?" | "⊭?" => PREC_AND,
1005        "==" | "≡" | "!=" | "≢" | "default" => PREC_EQUALITY,
1006        "=" | "≠" | "<" | ">" | "<=" | "≤" | ">=" | "≥" | "<=>" | "≶" | "≷" | "eq" | "ne"
1007        | "gt" | "ge" | "lt" | "le" | "cmp" | "eqi" | "nei" | "gti" | "gei" | "lti" | "lei"
1008        | "cmpi" | "in" | "∈" | "∉" | "subsetof" | "⊂" | "supersetof" | "⊃" | "equivalentof"
1009        | "⊂⊃" | "instanceof" | "does" | "can" | "~" | "->" | "@" | "@?" | "@@" | "∣"
1010        | "divides" | "∤" => PREC_COMPARISON,
1011        "|" => PREC_BITWISE_OR,
1012        "^" => PREC_BITWISE_XOR,
1013        "&" => PREC_BITWISE_AND,
1014        "<<" | "«" | ">>" | "»" => PREC_SHIFT,
1015        "union" | "⋃" | "intersection" | "⋂" | "\\" | "∖" | "..." => PREC_SET,
1016        "_" => PREC_CONCAT,
1017        "+" | "-" => PREC_ADDITIVE,
1018        "*" | "/" | "×" | "÷" | "mod" => PREC_MULTIPLICATIVE,
1019        "**" => PREC_EXPONENT,
1020        _ => PREC_COMPARISON,
1021    }
1022}
1023
1024fn is_right_associative(operator: &str) -> bool {
1025    matches!(
1026        operator,
1027        "**" | "◁" | "<|" | "onlyif" | "⊨" | "onlyif?" | "⊨?"
1028    )
1029}
1030
1031fn preferred_render_operator(operator: &str) -> &str {
1032    match operator {
1033        "|>" => "▷",
1034        "<|" => "◁",
1035        other => other,
1036    }
1037}
1038
1039fn is_word_operator(operator: &str) -> bool {
1040    operator
1041        .chars()
1042        .all(|ch| ch == '_' || ch.is_ascii_alphabetic())
1043}
1044
1045fn quote_string(value: &str) -> String {
1046    let escaped = value
1047        .replace('\\', "\\\\")
1048        .replace('"', "\\\"")
1049        .replace('\n', "\\n")
1050        .replace('\r', "\\r")
1051        .replace('\t', "\\t");
1052    format!("\"{escaped}\"")
1053}
1054
1055fn quote_binary_string(bytes: &[u8]) -> String {
1056    let mut out = String::from("'");
1057    for byte in bytes {
1058        match *byte {
1059            b'\n' => out.push_str("\\n"),
1060            b'\r' => out.push_str("\\r"),
1061            b'\t' => out.push_str("\\t"),
1062            b'\\' => out.push_str("\\\\"),
1063            b'\'' => out.push_str("\\'"),
1064            b'"' => out.push_str("\\\""),
1065            b'`' => out.push_str("\\`"),
1066            b'/' => out.push_str("\\/"),
1067            b'$' => out.push_str("\\$"),
1068            0x20..=0x7e => out.push(*byte as char),
1069            _ => out.push_str(&format!("\\x{byte:02X}")),
1070        }
1071    }
1072    out.push('\'');
1073    out
1074}
1075
1076fn quote_regex_pattern(pattern: &str) -> String {
1077    pattern.replace('\\', "\\\\").replace('/', "\\/")
1078}
1079
1080fn quote_template_text(value: &str) -> String {
1081    value
1082        .replace('\\', "\\\\")
1083        .replace('`', "\\`")
1084        .replace("${", "\\${")
1085}
1086
1087fn push_indent(out: &mut String, indent: usize) {
1088    for _ in 0..indent {
1089        out.push('\t');
1090    }
1091}