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 ¶ms
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) = ¶m.declared_type {
587 out.push_str(declared_type);
588 out.push(' ');
589 }
590 out.push_str(¶m.name);
591 if param.optional {
592 out.push('?');
593 }
594 if let Some(default_value) = ¶m.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("e_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("e_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}