1use std::fmt::Write as _;
12
13use crate::node::{sym, Head, Lit, Node};
14
15pub fn to_sexpr(n: &Node) -> String {
17 let mut out = String::new();
18 write_sexpr(&mut out, n);
19 out
20}
21
22pub fn to_sexpr_pretty(n: &Node) -> String {
24 let mut out = String::new();
25 write_sexpr_pretty(&mut out, n, 0);
26 out.push('\n');
27 out
28}
29
30fn write_atom(out: &mut String, n: &Node) {
31 match &n.head {
32 Head::Sym(s) => {
33 let _ = write!(out, "{}", s.name);
34 if !s.scopes.is_empty() {
37 let _ = write!(out, "{:?}", s.scopes);
38 }
39 }
40 Head::Lit(l) => write_lit(out, l),
41 }
42}
43
44fn write_lit(out: &mut String, l: &Lit) {
45 match l {
46 Lit::Int(v) => {
47 let _ = write!(out, "{v}");
48 }
49 Lit::Float(v) => {
50 if v.fract() == 0.0 && v.is_finite() {
51 let _ = write!(out, "{v:.1}");
52 } else {
53 let _ = write!(out, "{v}");
54 }
55 }
56 Lit::Bool(v) => {
57 let _ = write!(out, "{v}");
58 }
59 Lit::Keyword(k) => {
60 let _ = write!(out, ":{k}");
61 }
62 Lit::Str(s) => write_string(out, s),
63 }
64}
65
66fn write_string(out: &mut String, s: &str) {
67 out.push('"');
68 for c in s.chars() {
69 match c {
70 '"' => out.push_str("\\\""),
71 '\\' => out.push_str("\\\\"),
72 '\n' => out.push_str("\\n"),
73 '\t' => out.push_str("\\t"),
74 '\r' => out.push_str("\\r"),
75 _ => out.push(c),
76 }
77 }
78 out.push('"');
79}
80
81fn write_sexpr(out: &mut String, n: &Node) {
82 if !n.applied {
83 write_atom(out, n);
84 return;
85 }
86 out.push('(');
87 write_atom(out, n);
88 for a in &n.args {
89 out.push(' ');
90 write_sexpr(out, a);
91 }
92 out.push(')');
93}
94
95fn write_sexpr_pretty(out: &mut String, n: &Node, indent: usize) {
96 let flat = to_sexpr(n);
97 let documented = n.args.iter().any(|a| a.meta.doc.is_some());
100 if !n.applied || (flat.len() + indent <= 96 && !documented) {
101 out.push_str(&flat);
102 return;
103 }
104 out.push('(');
105 write_atom(out, n);
106 let inner = indent + 2;
107 let pad = " ".repeat(inner);
108 for a in &n.args {
109 if let Some(doc) = &a.meta.doc {
110 for line in crate::doc::render(doc, crate::doc::SEXPR_MARKER, &pad).lines() {
111 out.push('\n');
112 out.push_str(line);
113 }
114 }
115 out.push('\n');
116 out.push_str(&pad);
117 write_sexpr_pretty(out, a, inner);
118 }
119 out.push(')');
120}
121
122pub fn to_python(n: &Node) -> String {
126 let mut p = Py {
127 out: String::new(),
128 indent: 0,
129 trailing: None,
130 };
131 if n.is_form(sym::MODULE) {
132 for (i, item) in n.args.iter().skip(1).enumerate() {
133 if i > 0 {
134 p.out.push('\n');
135 }
136 p.item(item);
137 p.comments_after(item);
138 }
139 } else {
140 p.item(n);
141 p.comments_after(n);
142 }
143 if n.meta
146 .comments
147 .as_deref()
148 .is_some_and(|c| !c.after.is_empty())
149 {
150 p.out.push('\n');
151 p.comments_after(n);
152 }
153 p.out
154}
155
156struct Py {
157 out: String,
158 indent: usize,
159 trailing: Option<String>,
164}
165
166impl Py {
167 fn line(&mut self, s: &str) {
168 for _ in 0..self.indent {
169 self.out.push_str(" ");
170 }
171 self.out.push_str(s);
172 if let Some(c) = self.trailing.take() {
173 self.out.push_str(" ");
174 self.out.push_str(&c);
175 }
176 self.out.push('\n');
177 }
178
179 fn comments_before(&mut self, n: &Node) {
191 let Some(c) = n.meta.comments.as_deref() else {
192 return;
193 };
194 for line in &c.before {
195 if line.is_empty() {
197 self.out.push('\n');
198 } else {
199 self.line(line);
200 }
201 }
202 self.trailing = c.trailing.as_deref().map(str::to_string);
203 }
204
205 fn comments_after(&mut self, n: &Node) {
207 let Some(c) = n.meta.comments.as_deref() else {
208 return;
209 };
210 for line in &c.after {
211 if line.is_empty() {
212 self.out.push('\n');
213 } else {
214 self.line(line);
215 }
216 }
217 }
218
219 fn docs(&mut self, n: &Node) {
224 let Some(doc) = n.meta.doc.clone() else {
225 return;
226 };
227 let indent = " ".repeat(self.indent);
228 self.out
229 .push_str(&crate::doc::render(&doc, crate::doc::PY_MARKER, &indent));
230 }
231
232 fn item(&mut self, n: &Node) {
233 self.comments_before(n);
234 self.docs(n);
235 self.item_body(n);
236 }
237
238 fn item_body(&mut self, n: &Node) {
245 match n.head_name() {
246 Some(sym::DECORATE) => {
247 let deco = self.expr(&n.args[0]);
248 self.line(&format!("@{deco}"));
249 self.item(&n.args[1]);
250 }
251 Some(sym::DEF) => self.def(n),
252 Some(head @ (sym::MACRO | sym::TYPED_MACRO)) => {
253 let name = self.expr(&n.args[0]);
254 let params = self.params(&n.args[1]);
255 let typed = if head == sym::TYPED_MACRO {
256 "typed "
257 } else {
258 ""
259 };
260 self.line(&format!("{typed}macro {name}({params}):"));
261 self.body(&n.args[2]);
262 }
263 Some(sym::MODEL) => {
264 let name = self.expr(&n.args[0]);
265 let typarams = self.typarams(n);
266 self.line(&format!("model {name}{typarams}:"));
267 self.indent += 1;
268 if n.args.len() == 2 {
269 self.line("pass");
270 }
271 for f in &n.args[2..] {
272 self.comments_before(f);
274 self.docs(f);
275 let fname = self.expr(&f.args[0]);
276 let fty = self.type_expr(&f.args[1]);
277 self.line(&format!("{fname}: {fty}"));
278 }
279 self.indent -= 1;
280 }
281 Some(sym::UNION) => {
282 let name = self.expr(&n.args[0]);
283 let typarams = self.typarams(n);
284 self.line(&format!("union {name}{typarams}:"));
285 self.indent += 1;
286 for v in &n.args[2..] {
287 self.comments_before(v);
289 self.docs(v);
290 let vname = self.expr(&v.args[0]);
291 if v.args.len() == 1 {
292 self.line(&vname);
293 } else {
294 let fields: Vec<String> = v.args[1..]
295 .iter()
296 .map(|f| {
297 format!("{}: {}", self.expr(&f.args[0]), self.type_expr(&f.args[1]))
298 })
299 .collect();
300 self.line(&format!("{vname}({})", fields.join(", ")));
301 }
302 }
303 self.indent -= 1;
304 }
305 Some(sym::TRAIT) => {
306 let name = self.expr(&n.args[0]);
307 self.line(&format!("trait {name}:"));
308 self.indent += 1;
309 for m in &n.args[1..] {
310 self.item(m);
311 }
312 self.indent -= 1;
313 }
314 Some(sym::IMPL) => {
315 let name = self.expr(&n.args[0]);
316 let typarams = self.typarams(n);
317 let ty = self.type_expr(&n.args[2]);
318 self.line(&format!("impl{typarams} {name} for {ty}:"));
319 self.indent += 1;
320 for m in &n.args[3..] {
321 self.item(m);
322 }
323 self.indent -= 1;
324 }
325 Some(sym::TYPE) => {
326 let name = self.expr(&n.args[0]);
327 let typarams = self.typarams(n);
328 let ty = self.type_expr(&n.args[2]);
329 self.line(&format!("type {name}{typarams} = {ty}"));
330 }
331 Some(sym::NEWTYPE) => {
332 let name = self.expr(&n.args[0]);
333 let typarams = self.typarams(n);
334 let ty = self.type_expr(&n.args[2]);
335 self.line(&format!("type {name}{typarams} = newtype[{ty}]"));
336 }
337 Some(sym::IMPORT) => {
338 let path = self.expr(&n.args[0]);
339 self.line(&format!("import {path}"));
340 }
341 Some(sym::ROW) if n.args.len() >= 2 => {
342 let name = self.expr(&n.args[0]);
343 let atoms: Vec<String> = n.args[1..].iter().map(|a| self.expr(a)).collect();
344 self.line(&format!("row {name} = {}", atoms.join(", ")));
345 }
346 Some(sym::IDENTITY) if n.args.len() == 1 => {
347 let provider = self.expr(&n.args[0]);
348 self.line(&format!("identity = {provider}"));
349 }
350 Some(sym::TEST) => {
351 let name = self.expr(&n.args[0]);
352 self.line(&format!("test {name}:"));
353 self.body(&n.args[1]);
354 }
355 Some(sym::PROPERTY) if n.args.len() == 3 => {
356 let name = self.expr(&n.args[0]);
357 let params = self.params(&n.args[1]);
358 self.line(&format!("property {name}({params}):"));
359 self.body(&n.args[2]);
360 }
361 _ => self.stmt_body(n),
362 }
363 }
364
365 fn typarams(&mut self, n: &Node) -> String {
368 let Some(t) = n.args.get(1).filter(|t| !t.args.is_empty()) else {
369 return String::new();
370 };
371 let names: Vec<String> = t
372 .args
373 .clone()
374 .iter()
375 .map(|a| {
376 if !a.is_form(sym::ANNOT) || a.args.len() < 2 {
377 return self.expr(a);
378 }
379 let bounds: Vec<String> = a.args[1..].iter().map(|b| self.expr(b)).collect();
380 format!("{}: {}", self.expr(&a.args[0]), bounds.join(" + "))
381 })
382 .collect();
383 format!("[{}]", names.join(", "))
384 }
385
386 fn def(&mut self, n: &Node) {
387 let name = self.expr(&n.args[0]);
388 let typarams = self.typarams(n);
389 let params = self.params(&n.args[2]);
390 let ret = n
391 .args
392 .get(3)
393 .filter(|r| !r.args.is_empty())
394 .map(|r| format!(" -> {}", self.type_expr(&r.args[0])))
395 .unwrap_or_default();
396 let uses = n
397 .args
398 .get(4)
399 .filter(|u| !u.args.is_empty())
400 .map(|u| {
401 let items: Vec<String> = u.args.iter().map(|e| self.expr(e)).collect();
402 format!(" uses {}", items.join(", "))
403 })
404 .unwrap_or_default();
405 match n.args.get(5) {
406 Some(body) => {
407 self.line(&format!("def {name}{typarams}({params}){ret}{uses}:"));
408 self.body(body);
409 }
410 None => self.line(&format!("def {name}{typarams}({params}){ret}{uses}")),
413 }
414 }
415
416 fn params(&self, n: &Node) -> String {
417 n.args
418 .iter()
419 .map(|p| {
420 if p.is_form(sym::ANNOT) {
421 format!("{}: {}", self.expr(&p.args[0]), self.type_expr(&p.args[1]))
422 } else {
423 self.expr(p)
424 }
425 })
426 .collect::<Vec<_>>()
427 .join(", ")
428 }
429
430 fn case_head(&self, arm: &Node) -> String {
432 let pat = self.expr(&arm.args[0]);
433 match arm.args.get(2) {
434 Some(g) => format!("{pat} if {}", self.expr(g)),
435 None => pat,
436 }
437 }
438
439 fn body(&mut self, n: &Node) {
440 self.indent += 1;
441 if n.args.is_empty() {
442 self.line("pass");
443 }
444 for s in &n.args {
445 self.stmt(s);
446 }
447 self.indent -= 1;
448 }
449
450 fn stmt(&mut self, n: &Node) {
451 self.comments_before(n);
452 self.stmt_body(n);
453 self.comments_after(n);
454 }
455
456 fn stmt_body(&mut self, n: &Node) {
457 match n.head_name() {
458 Some(sym::DO) => {
459 for s in &n.args {
460 self.stmt(s);
461 }
462 }
463 Some(sym::RETURN) => match n.args.first() {
464 Some(e) if e.is_form(sym::TRY) && e.args.len() == 1 => {
472 self.line("return try:");
473 self.body(&e.args[0]);
474 }
475 Some(e) if e.is_form(sym::PARALLEL) && e.args.len() == 1 => {
476 self.line("return parallel:");
477 self.body(&e.args[0]);
478 }
479 Some(e) if e.is_form(sym::QUOTE) && e.args.len() == 1 => {
480 self.line("return quote:");
481 let body = &e.args[0];
482 if body.is_form(sym::DO) {
483 self.body(body);
484 } else {
485 self.indent += 1;
486 self.stmt(body);
487 self.indent -= 1;
488 }
489 }
490 Some(e) => match split_block_call(e) {
491 Some((head, args, block)) => {
492 let rendered = self.call_text(head, &args);
493 self.line(&format!("return {rendered}:"));
494 self.body(&block);
495 }
496 None => {
497 let e = self.expr(e);
498 self.line(&format!("return {e}"));
499 }
500 },
501 None => self.line("return"),
502 },
503 Some(sym::LET) | Some(sym::VAR)
504 if n.args.len() == 2 && split_block_call(&n.args[1]).is_some() =>
505 {
506 let (head, args, block) =
507 split_block_call(&n.args[1]).expect("checked by the guard");
508 let target = self.expr(&n.args[0]);
509 let keyword = if n.is_form(sym::VAR) { "var " } else { "" };
510 let rendered = self.call_text(head, &args);
511 self.line(&format!("{keyword}{target} = {rendered}:"));
512 self.body(&block);
513 }
514 Some(sym::LET) | Some(sym::VAR)
520 if n.args.len() == 2
521 && (n.args[1].is_form(sym::TRY)
522 || n.args[1].is_form(sym::PARALLEL)
523 || n.args[1].is_form(sym::QUOTE))
524 && n.args[1].args.len() == 1 =>
525 {
526 let keyword = if n.is_form(sym::VAR) { "var " } else { "" };
527 let target = self.expr(&n.args[0]);
528 let head = n.args[1].head_name().unwrap_or(sym::TRY);
529 self.line(&format!("{keyword}{target} = {head}:"));
530 let body = &n.args[1].args[0];
531 if body.is_form(sym::DO) {
534 self.body(body);
535 } else {
536 self.indent += 1;
537 self.stmt(body);
538 self.indent -= 1;
539 }
540 }
541 Some(sym::LET) if n.args.len() == 2 => {
542 let t = &n.args[0];
543 let target = if t.is_form(sym::ANNOT) {
544 format!("{}: {}", self.expr(&t.args[0]), self.type_expr(&t.args[1]))
545 } else {
546 self.expr(t)
547 };
548 let v = self.expr(&n.args[1]);
549 self.line(&format!("{target} = {v}"));
550 }
551 Some(sym::VAR) if n.args.len() == 2 => {
552 let t = &n.args[0];
553 let target = if t.is_form(sym::ANNOT) {
554 format!("{}: {}", self.expr(&t.args[0]), self.type_expr(&t.args[1]))
555 } else {
556 self.expr(t)
557 };
558 let v = self.expr(&n.args[1]);
559 self.line(&format!("var {target} = {v}"));
560 }
561 Some(sym::IF) if n.args.len() >= 2 && n.args[1].is_form(sym::DO) => {
562 let c = self.expr(&n.args[0]);
563 self.line(&format!("if {c}:"));
564 self.body(&n.args[1]);
565 if let Some(alt) = n.args.get(2) {
566 if alt.args.len() == 1 && alt.args[0].is_form(sym::IF) {
568 let inner = &alt.args[0];
569 let mut s = String::new();
570 std::mem::swap(&mut self.out, &mut s);
571 self.stmt(inner);
572 std::mem::swap(&mut self.out, &mut s);
573 let pad = " ".repeat(self.indent);
574 let rewritten = s.replacen(&format!("{pad}if "), &format!("{pad}elif "), 1);
575 self.out.push_str(&rewritten);
576 } else {
577 self.line("else:");
578 self.body(alt);
579 }
580 }
581 }
582 Some(sym::FOR) if n.args.len() == 3 => {
583 let v = self.expr(&n.args[0]);
584 let seq = self.expr(&n.args[1]);
585 self.line(&format!("for {v} in {seq}:"));
586 self.body(&n.args[2]);
587 }
588 Some(sym::WHILE) if n.args.len() == 2 => {
589 let c = self.expr(&n.args[0]);
590 self.line(&format!("while {c}:"));
591 self.body(&n.args[1]);
592 }
593 Some(sym::MATCH) if !n.args.is_empty() => {
594 let s = self.expr(&n.args[0]);
595 self.line(&format!("match {s}:"));
596 self.indent += 1;
597 for arm in &n.args[1..] {
598 self.comments_before(arm);
601 self.line(&format!("case {}:", self.case_head(arm)));
602 self.body(&arm.args[1]);
603 self.comments_after(arm);
604 }
605 self.indent -= 1;
606 }
607 Some(sym::TRY) if n.args.len() == 1 => {
610 self.line("try:");
611 self.body(&n.args[0]);
612 }
613 Some(sym::PARALLEL) if n.args.len() == 1 => {
616 self.line("parallel:");
617 self.body(&n.args[0]);
618 }
619 Some(sym::ROW | sym::IDENTITY) => self.item(n),
620 Some(
621 sym::DEF | sym::MACRO | sym::TYPED_MACRO | sym::MODEL | sym::UNION | sym::TYPE,
622 )
623 | Some(sym::NEWTYPE)
624 | Some(sym::TRAIT | sym::IMPL | sym::IMPORT | sym::DECORATE | sym::TEST)
625 | Some(sym::PROPERTY) => self.item(n),
626
627 Some(sym::GIVEN) if !n.args.is_empty() => {
631 let events = self.expr(&n.args[0]);
632 match n.args.get(1) {
633 Some(actor) => {
634 let a = self.expr(actor);
635 self.line(&format!("given {events} by {a}"));
636 }
637 None => self.line(&format!("given {events}")),
638 }
639 }
640 Some(sym::WHEN) if n.args.len() >= 2 => {
641 let cmds: Vec<String> = n.args[1..].iter().map(|a| self.expr(a)).collect();
642 let cmds = cmds.join(", ");
643 match session_of(&n.args[0]) {
644 Some(session) => self.line(&format!("when {session} sends {cmds}")),
645 None => self.line(&format!("when {cmds}")),
646 }
647 }
648 Some(sym::EXPECT) if n.args.len() == 1 => {
649 let e = self.expr(&n.args[0]);
652 self.line(&format!("expect {e}"));
653 }
654 Some(sym::EXPECT_CONTAINS) if !n.args.is_empty() => {
655 let needle = self.expr(&n.args[0]);
656 match n.args.get(1).and_then(session_of) {
657 Some(session) => {
658 self.line(&format!("expect page({session}) contains {needle}"))
659 }
660 None => self.line(&format!("expect page contains {needle}")),
661 }
662 }
663 Some(sym::EXPECT_SNAPSHOT) if n.args.len() == 2 => {
664 let subject = match session_of(&n.args[1]) {
665 Some(session) => format!("page({session})"),
666 None => "page".to_string(),
667 };
668 match n.args[0].as_str_lit() {
669 Some(name) => {
670 self.line(&format!("expect {subject} matches snapshot \"{name}\""))
671 }
672 None => self.line(&format!("expect {subject} matches snapshot")),
673 }
674 }
675 Some(sym::EXPECT_FOLD) if !n.args.is_empty() => {
676 let events = self.expr(&n.args[0]);
677 match n.args.get(1).and_then(|a| a.as_str_lit()) {
678 Some(actor) => {
679 self.line(&format!("expect state == fold_of {events} by \"{actor}\""))
680 }
681 None => self.line(&format!("expect state == fold_of {events}")),
682 }
683 }
684 Some(sym::EXPECT_PLACE) if n.args.len() == 2 => {
685 let what = self.expr(&n.args[0]);
686 let tier = self.expr(&n.args[1]);
687 self.line(&format!("expect place({what}) == {tier}"));
688 }
689 Some(sym::EXPECT_FLOW) if n.args.len() == 2 => {
690 let ty = self.expr(&n.args[0]);
691 let tier = self.expr(&n.args[1]);
692 self.line(&format!("expect flow({ty}) reaches nothing on {tier}"));
693 }
694 Some(sym::EXPECT_WIRE) if n.args.len() == 1 => {
695 let path = self.expr(&n.args[0]);
696 self.line(&format!("expect wire_compatible_with {path}"));
697 }
698 Some(sym::EXPECT_EFFECT) if n.args.len() == 2 => {
699 let atom = n.args[0].as_str_lit().unwrap_or_default().to_string();
700 let how = &n.args[1];
701 match how.head_name() {
702 Some("times") if how.args.len() == 1 => {
703 match how.args[0].as_lit() {
704 Some(Lit::Int(1)) => self.line(&format!("expect {atom} once")),
705 Some(Lit::Int(k)) => self.line(&format!("expect {atom} times {k}")),
706 _ => self.line(&format!("expect {atom} once")),
707 };
708 }
709 Some("with") if how.args.len() == 1 => {
710 let v = self.expr(&how.args[0]);
711 self.line(&format!("expect {atom} with {v}"));
712 }
713 _ => self.line(&format!("expect no {atom}")),
714 }
715 }
716 Some(sym::STUB) if n.args.len() == 2 => {
717 let atom = n.args[0].as_str_lit().unwrap_or_default().to_string();
718 let body = &n.args[1];
719 if body.is_form(sym::STUB_ARMS) {
720 self.line(&format!("stub {atom}:"));
721 self.indent += 1;
722 for arm in &body.args {
723 self.comments_before(arm);
724 self.line(&format!("case {}:", self.case_head(arm)));
725 self.body(&arm.args[1]);
726 }
727 self.indent -= 1;
728 } else if body.is_form(sym::DO) {
729 self.line(&format!("stub {atom}:"));
730 self.body(body);
731 } else {
732 let v = self.expr(body);
733 self.line(&format!("stub {atom}: {v}"));
734 }
735 }
736 _ => {
737 if let Some((head, args, block)) = split_block_call(n) {
740 let rendered = self.call_text(head, &args);
741 self.line(&format!("{rendered}:"));
742 self.body(&block);
743 } else {
744 let e = self.expr(n);
745 self.line(&e);
746 }
747 }
748 }
749 }
750
751 fn call_text(&self, head: &str, args: &[Node]) -> String {
752 if args.is_empty() {
753 return head.to_string();
754 }
755 let rendered: Vec<String> = args.iter().map(|a| self.expr(a)).collect();
756 format!("{head}({})", rendered.join(", "))
757 }
758
759 fn type_expr(&self, n: &Node) -> String {
760 match n.head_name() {
761 Some("fn-type") if !n.args.is_empty() => {
766 let params: Vec<String> = n.args[..n.args.len() - 1]
767 .iter()
768 .map(|a| self.type_expr(a))
769 .collect();
770 format!(
771 "({}) -> {}",
772 params.join(", "),
773 self.type_expr(&n.args[n.args.len() - 1])
774 )
775 }
776 _ if !n.applied => {
777 let mut s = String::new();
778 write_atom(&mut s, n);
779 s
780 }
781 _ => {
782 let mut head = String::new();
783 write_atom(&mut head, n);
784 let args: Vec<String> = n.args.iter().map(|a| self.type_expr(a)).collect();
785 format!("{head}[{}]", args.join(", "))
786 }
787 }
788 }
789
790 fn block_expr(&self, n: &Node) -> String {
799 if n.is_form(sym::DO) {
800 let parts: Vec<String> = n.args.iter().map(|a| self.expr(a)).collect();
801 return parts.join("; ");
802 }
803 self.expr(n)
804 }
805
806 fn expr(&self, n: &Node) -> String {
807 if !n.applied {
808 let mut s = String::new();
809 write_atom(&mut s, n);
810 return s;
811 }
812 let head = n.head_name().unwrap_or("");
813 match head {
814 "not" if n.args.len() == 1 => format!("not {}", self.expr(&n.args[0])),
815 sym::RAISE if n.args.len() == 1 => format!("raise {}", self.expr(&n.args[0])),
816 sym::TRY | sym::PARALLEL if n.args.len() == 1 => {
823 format!("({head}: {})", self.block_expr(&n.args[0]))
824 }
825 "negate" if n.args.len() == 1 => format!("-{}", self.expr(&n.args[0])),
826 sym::UNQUOTE if n.args.len() == 1 => format!("${}", self.expr(&n.args[0])),
827 sym::SPLICE if n.args.len() == 1 => format!("$*{}", self.expr(&n.args[0])),
828 "and" | "or" | "|" | "@" | "==" | "!=" | "<" | "<=" | ">" | ">=" | "+" | "-" | "*"
829 | "/" | "%"
830 if n.args.len() == 2 =>
831 {
832 format!(
833 "({} {head} {})",
834 self.expr(&n.args[0]),
835 self.expr(&n.args[1])
836 )
837 }
838 "contains" if n.args.len() == 2 => {
839 format!("({} in {})", self.expr(&n.args[0]), self.expr(&n.args[1]))
840 }
841 "index" if n.args.len() == 2 => {
842 format!("{}[{}]", self.expr(&n.args[0]), self.expr(&n.args[1]))
843 }
844 sym::IF if n.args.len() == 3 && !n.args[1].is_form(sym::DO) => format!(
848 "({} if {} else {})",
849 self.expr(&n.args[1]),
850 self.expr(&n.args[0]),
851 self.expr(&n.args[2])
852 ),
853 sym::DOT if n.args.len() == 2 => {
854 format!("{}.{}", self.expr(&n.args[0]), self.expr(&n.args[1]))
855 }
856 sym::DOT if n.args.len() > 2 => {
857 let args: Vec<String> = n.args[2..].iter().map(|a| self.expr(a)).collect();
858 format!(
859 "{}.{}({})",
860 self.expr(&n.args[0]),
861 self.expr(&n.args[1]),
862 args.join(", ")
863 )
864 }
865 sym::KW_ARG if n.args.len() == 2 => {
866 format!("{}={}", self.expr(&n.args[0]), self.expr(&n.args[1]))
867 }
868 sym::LIST => {
869 let items: Vec<String> = n.args.iter().map(|a| self.expr(a)).collect();
870 format!("[{}]", items.join(", "))
871 }
872 sym::REST if n.args.len() == 1 => format!("*{}", self.expr(&n.args[0])),
873 sym::RECORD => {
874 let mut parts = Vec::new();
875 for pair in n.args.chunks(2) {
876 if pair.len() == 2 {
877 let k = pair[0]
878 .as_keyword()
879 .map(str::to_string)
880 .unwrap_or_else(|| self.expr(&pair[0]));
881 parts.push(format!("{k}: {}", self.expr(&pair[1])));
882 }
883 }
884 format!("{{{}}}", parts.join(", "))
885 }
886 sym::MAP => {
887 let mut parts = Vec::new();
888 for pair in n.args.chunks(2) {
889 if pair.len() == 2 {
890 parts.push(format!("{}: {}", self.expr(&pair[0]), self.expr(&pair[1])));
891 }
892 }
893 format!("{{{}}}", parts.join(", "))
894 }
895 sym::FN if n.args.len() == 2 => {
896 let params = self.params(&n.args[0]);
897 let body = &n.args[1];
898 let b = if body.args.len() == 1 {
899 self.expr(&body.args[0])
900 } else {
901 self.expr(body)
902 };
903 format!("lambda {params}: {b}")
904 }
905 sym::QUOTE if n.args.len() == 1 => {
906 format!("quote({})", self.expr(&n.args[0]))
909 }
910 sym::CALL if !n.args.is_empty() => {
911 let callee = self.expr(&n.args[0]);
912 let args: Vec<String> = n.args[1..].iter().map(|a| self.expr(a)).collect();
913 format!("{callee}({})", args.join(", "))
914 }
915 sym::DO => {
916 let items: Vec<String> = n.args.iter().map(|a| self.expr(a)).collect();
917 items.join("; ")
918 }
919 _ => {
920 let mut h = String::new();
921 write_atom(&mut h, n);
922 let args: Vec<String> = n.args.iter().map(|a| self.expr(a)).collect();
923 format!("{h}({})", args.join(", "))
924 }
925 }
926 }
927}
928
929fn session_of(n: &Node) -> Option<String> {
936 if let Some(actor) = n.as_str_lit() {
937 return Some(format!("session({})", quoted(actor)));
938 }
939 if n.is_form(sym::AT) && n.args.len() == 2 {
940 let actor = n.args[0].as_str_lit()?;
941 let route = n.args[1].as_str_lit()?;
942 return Some(format!("session({}, {})", quoted(actor), quoted(route)));
943 }
944 None
945}
946
947fn quoted(s: &str) -> String {
948 let mut out = String::with_capacity(s.len() + 2);
949 write_string(&mut out, s);
950 out
951}
952
953fn split_block_call(n: &Node) -> Option<(&str, Vec<Node>, Node)> {
954 let head = n.head_name()?;
955 let last = n.args.last()?;
956 if !last.is_form(sym::KW_ARG) || last.args.len() != 2 {
957 return None;
958 }
959 if last.args[0].as_var().map(|s| s.as_str()) != Some("do") {
960 return None;
961 }
962 let quoted = &last.args[1];
963 if !quoted.is_form(sym::QUOTE) || quoted.args.len() != 1 {
964 return None;
965 }
966 let block = quoted.args[0].clone();
967 if !block.has_head(sym::DO) {
968 return None;
969 }
970 Some((head, n.args[..n.args.len() - 1].to_vec(), block))
971}
972
973#[cfg(test)]
974mod tests {
975 use super::*;
976 use crate::{parser, sexpr};
977 use beck_diag::{Diagnostics, SourceMap};
978
979 fn roundtrip_python(src: &str) -> String {
980 let mut map = SourceMap::new();
981 let f = map.add("t.beck", src);
982 let mut d = Diagnostics::new();
983 let n = parser::parse_module(f, "t", src, &mut d);
984 assert!(!d.has_errors(), "{}", d.render(&map));
985 to_python(&n)
986 }
987
988 fn parse(src: &str) -> Node {
989 let mut map = SourceMap::new();
990 let f = map.add("t.beck", src);
991 let mut d = Diagnostics::new();
992 let n = parser::parse_module(f, "t", src, &mut d);
993 assert!(!d.has_errors(), "{}", d.render(&map));
994 n
995 }
996
997 #[test]
998 fn printing_python_is_idempotent_and_reparses_to_the_same_tree() {
999 let src = "\
1000def total(items: list[Int], base: Int) -> Int:
1001 var acc = base
1002 for i in items:
1003 acc = (acc + i)
1004 if (acc > 10):
1005 return acc
1006 elif (acc > 5):
1007 return 5
1008 else:
1009 return 0
1010";
1011 let once = roundtrip_python(src);
1012 let twice = roundtrip_python(&once);
1013 assert_eq!(once, twice, "fmt must be idempotent");
1014 assert!(parse(src).structurally_eq(&parse(&once)));
1015 }
1016
1017 #[test]
1018 fn block_calls_print_back_as_blocks() {
1019 let src = "ui:\n main:\n h1(class=\"t\"):\n \"todos\"\n";
1020 let out = roundtrip_python(src);
1021 assert!(out.contains("ui:"), "{out}");
1022 assert!(out.contains("h1(class=\"t\"):"), "{out}");
1023 assert!(parse(src).structurally_eq(&parse(&out)));
1024 }
1025
1026 #[test]
1027 fn the_two_surfaces_are_the_same_language() {
1028 let py = "def toggle(todos: Map[Id, Todo], e: Toggled) -> Map[Id, Todo]:\n\
1030 \x20 return todos.update(e.id, lambda t: t.with(done=not t.done))\n";
1031 let mut map = SourceMap::new();
1032 let f = map.add("t.beck", py);
1033 let mut d = Diagnostics::new();
1034 let from_py = parser::parse_module(f, "t", py, &mut d);
1035 assert!(!d.has_errors(), "{}", d.render(&map));
1036
1037 let sx_src = "(def toggle
1038 (params (: todos (Map Id Todo)) (: e Toggled))
1039 (returns (Map Id Todo))
1040 (uses)
1041 (do (return (. todos update (. e id)
1042 (fn (params t) (do (. t with (kw done (not (. t done)))))))))) ";
1043 let g = map.add("t.sx", sx_src);
1044 let from_sx = sexpr::read_one(g, sx_src, &mut d).unwrap();
1045 assert!(!d.has_errors(), "{}", d.render(&map));
1046
1047 assert!(
1048 from_py.args[1].structurally_eq(&from_sx),
1049 "python:\n{}\nsexpr:\n{}",
1050 to_sexpr(&from_py.args[1]),
1051 to_sexpr(&from_sx)
1052 );
1053 }
1054
1055 #[test]
1056 fn sexpr_pretty_breaks_only_long_forms() {
1057 let n = parse("def f() -> Int:\n return 1\n");
1058 let pretty = to_sexpr_pretty(&n.args[1]);
1059 assert_eq!(pretty.lines().count(), 1, "{pretty}");
1060 }
1061}
1062
1063#[cfg(test)]
1069mod roundtrip {
1070 use super::*;
1071 use crate::parser;
1072 use beck_diag::{Diagnostics, SourceMap};
1073
1074 fn parse(name: &str, src: &str) -> Node {
1075 let mut map = SourceMap::new();
1076 let f = map.add(name, src);
1077 let mut d = Diagnostics::new();
1078 let n = parser::parse_module(f, "t", src, &mut d);
1079 assert!(!d.has_errors(), "{name}:\n{}", d.render(&map));
1080 n
1081 }
1082
1083 fn corpus() -> Vec<(&'static str, &'static str)> {
1084 vec![
1085 ("example", include_str!("../../../examples/todo.beck")),
1086 (
1087 "macros",
1088 "macro unless(cond, do):\n return quote:\n if not $cond:\n $do\n",
1089 ),
1090 (
1091 "control",
1092 "def f(xs: list[Int]) -> Int:\n var acc = 0\n if (acc > 1):\n return 1\n elif (acc > 0):\n return 2\n else:\n return 3\n",
1093 ),
1094 (
1095 "types",
1096 "type Id = newtype[Str]\n\nmodel M:\n a: Int\n\nunion U:\n A(x: Int)\n B\n",
1097 ),
1098 (
1101 "tests",
1102 "test \"a\":\n given [Added(id=\"1\")] by \"ana\"\n when session(\"ana\") sends Add(id=\"1\"), Toggle(id=\"1\")\n stub net.out(payments.example.com): Declined\n stub net.out(a.example.com):\n case Charge(amount):\n return Declined\n case _:\n return Approved\n stub net.out(b.example.com):\n x = 1\n return Approved\n expect page contains \"milk\"\n expect page(session(\"bo\")) contains \"milk\"\n expect state == fold_of []\n expect state == fold_of [Added(id=\"1\")] by \"ana\"\n expect place(view) == client\n expect flow(ApiKey) reaches nothing on client\n expect wire_compatible_with \"o.becki\"\n expect no net.out\n expect net.out(h.example.com) once\n expect net.out(h.example.com) times 3\n expect net.out(h.example.com) with Charge(amount=1)\n expect Err(error=BlankText)\n\nproperty \"p\"(events: list[Event]):\n given events\n expect list_len(events) >= 0\n",
1103 ),
1104 ]
1105 }
1106
1107 #[test]
1108 fn printing_the_python_surface_round_trips() {
1109 for (name, src) in corpus() {
1110 let first = parse(name, src);
1111 let printed = to_python(&first);
1112 let leaked: &'static str = Box::leak(printed.clone().into_boxed_str());
1113 let second = parse(name, leaked);
1114 assert!(
1115 first.structurally_eq(&second),
1116 "{name} did not round-trip.\n--- printed ---\n{printed}\n--- as sexpr ---\n{}\n--- was ---\n{}",
1117 to_sexpr(&second),
1118 to_sexpr(&first)
1119 );
1120 }
1121 }
1122
1123 #[test]
1124 fn formatting_is_idempotent() {
1125 for (name, src) in corpus() {
1126 let once = to_python(&parse(name, src));
1127 let leaked: &'static str = Box::leak(once.clone().into_boxed_str());
1128 let twice = to_python(&parse(name, leaked));
1129 assert_eq!(once, twice, "{name}: fmt is not idempotent");
1130 }
1131 }
1132}