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 };
130 if n.is_form(sym::MODULE) {
131 for (i, item) in n.args.iter().skip(1).enumerate() {
132 if i > 0 {
133 p.out.push('\n');
134 }
135 p.item(item);
136 }
137 } else {
138 p.item(n);
139 }
140 p.out
141}
142
143struct Py {
144 out: String,
145 indent: usize,
146}
147
148impl Py {
149 fn line(&mut self, s: &str) {
150 for _ in 0..self.indent {
151 self.out.push_str(" ");
152 }
153 self.out.push_str(s);
154 self.out.push('\n');
155 }
156
157 fn docs(&mut self, n: &Node) {
162 let Some(doc) = n.meta.doc.clone() else {
163 return;
164 };
165 let indent = " ".repeat(self.indent);
166 self.out
167 .push_str(&crate::doc::render(&doc, crate::doc::PY_MARKER, &indent));
168 }
169
170 fn item(&mut self, n: &Node) {
171 self.docs(n);
172 match n.head_name() {
173 Some(sym::DECORATE) => {
174 let deco = self.expr(&n.args[0]);
175 self.line(&format!("@{deco}"));
176 self.item(&n.args[1]);
177 }
178 Some(sym::DEF) => self.def(n),
179 Some(sym::MACRO) => {
180 let name = self.expr(&n.args[0]);
181 let params = self.params(&n.args[1]);
182 self.line(&format!("macro {name}({params}):"));
183 self.body(&n.args[2]);
184 }
185 Some(sym::MODEL) => {
186 let name = self.expr(&n.args[0]);
187 let typarams = self.typarams(n);
188 self.line(&format!("model {name}{typarams}:"));
189 self.indent += 1;
190 if n.args.len() == 2 {
191 self.line("pass");
192 }
193 for f in &n.args[2..] {
194 self.docs(f);
195 let fname = self.expr(&f.args[0]);
196 let fty = self.type_expr(&f.args[1]);
197 self.line(&format!("{fname}: {fty}"));
198 }
199 self.indent -= 1;
200 }
201 Some(sym::UNION) => {
202 let name = self.expr(&n.args[0]);
203 let typarams = self.typarams(n);
204 self.line(&format!("union {name}{typarams}:"));
205 self.indent += 1;
206 for v in &n.args[2..] {
207 self.docs(v);
208 let vname = self.expr(&v.args[0]);
209 if v.args.len() == 1 {
210 self.line(&vname);
211 } else {
212 let fields: Vec<String> = v.args[1..]
213 .iter()
214 .map(|f| {
215 format!("{}: {}", self.expr(&f.args[0]), self.type_expr(&f.args[1]))
216 })
217 .collect();
218 self.line(&format!("{vname}({})", fields.join(", ")));
219 }
220 }
221 self.indent -= 1;
222 }
223 Some(sym::TRAIT) => {
224 let name = self.expr(&n.args[0]);
225 self.line(&format!("trait {name}:"));
226 self.indent += 1;
227 for m in &n.args[1..] {
228 self.item(m);
229 }
230 self.indent -= 1;
231 }
232 Some(sym::IMPL) => {
233 let name = self.expr(&n.args[0]);
234 let typarams = self.typarams(n);
235 let ty = self.type_expr(&n.args[2]);
236 self.line(&format!("impl{typarams} {name} for {ty}:"));
237 self.indent += 1;
238 for m in &n.args[3..] {
239 self.item(m);
240 }
241 self.indent -= 1;
242 }
243 Some(sym::TYPE) => {
244 let name = self.expr(&n.args[0]);
245 let typarams = self.typarams(n);
246 let ty = self.type_expr(&n.args[2]);
247 self.line(&format!("type {name}{typarams} = {ty}"));
248 }
249 Some(sym::NEWTYPE) => {
250 let name = self.expr(&n.args[0]);
251 let typarams = self.typarams(n);
252 let ty = self.type_expr(&n.args[2]);
253 self.line(&format!("type {name}{typarams} = newtype[{ty}]"));
254 }
255 Some(sym::IMPORT) => {
256 let path = self.expr(&n.args[0]);
257 self.line(&format!("import {path}"));
258 }
259 Some(sym::ROW) if n.args.len() >= 2 => {
260 let name = self.expr(&n.args[0]);
261 let atoms: Vec<String> = n.args[1..].iter().map(|a| self.expr(a)).collect();
262 self.line(&format!("row {name} = {}", atoms.join(", ")));
263 }
264 Some(sym::IDENTITY) if n.args.len() == 1 => {
265 let provider = self.expr(&n.args[0]);
266 self.line(&format!("identity = {provider}"));
267 }
268 Some(sym::TEST) => {
269 let name = self.expr(&n.args[0]);
270 self.line(&format!("test {name}:"));
271 self.body(&n.args[1]);
272 }
273 Some(sym::PROPERTY) if n.args.len() == 3 => {
274 let name = self.expr(&n.args[0]);
275 let params = self.params(&n.args[1]);
276 self.line(&format!("property {name}({params}):"));
277 self.body(&n.args[2]);
278 }
279 _ => self.stmt(n),
280 }
281 }
282
283 fn typarams(&mut self, n: &Node) -> String {
286 let Some(t) = n.args.get(1).filter(|t| !t.args.is_empty()) else {
287 return String::new();
288 };
289 let names: Vec<String> = t
290 .args
291 .clone()
292 .iter()
293 .map(|a| {
294 if !a.is_form(sym::ANNOT) || a.args.len() < 2 {
295 return self.expr(a);
296 }
297 let bounds: Vec<String> = a.args[1..].iter().map(|b| self.expr(b)).collect();
298 format!("{}: {}", self.expr(&a.args[0]), bounds.join(" + "))
299 })
300 .collect();
301 format!("[{}]", names.join(", "))
302 }
303
304 fn def(&mut self, n: &Node) {
305 let name = self.expr(&n.args[0]);
306 let typarams = self.typarams(n);
307 let params = self.params(&n.args[2]);
308 let ret = n
309 .args
310 .get(3)
311 .filter(|r| !r.args.is_empty())
312 .map(|r| format!(" -> {}", self.type_expr(&r.args[0])))
313 .unwrap_or_default();
314 let uses = n
315 .args
316 .get(4)
317 .filter(|u| !u.args.is_empty())
318 .map(|u| {
319 let items: Vec<String> = u.args.iter().map(|e| self.expr(e)).collect();
320 format!(" uses {}", items.join(", "))
321 })
322 .unwrap_or_default();
323 match n.args.get(5) {
324 Some(body) => {
325 self.line(&format!("def {name}{typarams}({params}){ret}{uses}:"));
326 self.body(body);
327 }
328 None => self.line(&format!("def {name}{typarams}({params}){ret}{uses}")),
331 }
332 }
333
334 fn params(&self, n: &Node) -> String {
335 n.args
336 .iter()
337 .map(|p| {
338 if p.is_form(sym::ANNOT) {
339 format!("{}: {}", self.expr(&p.args[0]), self.type_expr(&p.args[1]))
340 } else {
341 self.expr(p)
342 }
343 })
344 .collect::<Vec<_>>()
345 .join(", ")
346 }
347
348 fn case_head(&self, arm: &Node) -> String {
350 let pat = self.expr(&arm.args[0]);
351 match arm.args.get(2) {
352 Some(g) => format!("{pat} if {}", self.expr(g)),
353 None => pat,
354 }
355 }
356
357 fn body(&mut self, n: &Node) {
358 self.indent += 1;
359 if n.args.is_empty() {
360 self.line("pass");
361 }
362 for s in &n.args {
363 self.stmt(s);
364 }
365 self.indent -= 1;
366 }
367
368 fn stmt(&mut self, n: &Node) {
369 match n.head_name() {
370 Some(sym::DO) => {
371 for s in &n.args {
372 self.stmt(s);
373 }
374 }
375 Some(sym::RETURN) => match n.args.first() {
376 Some(e) if e.is_form(sym::TRY) && e.args.len() == 1 => {
384 self.line("return try:");
385 self.body(&e.args[0]);
386 }
387 Some(e) if e.is_form(sym::PARALLEL) && e.args.len() == 1 => {
388 self.line("return parallel:");
389 self.body(&e.args[0]);
390 }
391 Some(e) if e.is_form(sym::QUOTE) && e.args.len() == 1 => {
392 self.line("return quote:");
393 let body = &e.args[0];
394 if body.is_form(sym::DO) {
395 self.body(body);
396 } else {
397 self.indent += 1;
398 self.stmt(body);
399 self.indent -= 1;
400 }
401 }
402 Some(e) => match split_block_call(e) {
403 Some((head, args, block)) => {
404 let rendered = self.call_text(head, &args);
405 self.line(&format!("return {rendered}:"));
406 self.body(&block);
407 }
408 None => {
409 let e = self.expr(e);
410 self.line(&format!("return {e}"));
411 }
412 },
413 None => self.line("return"),
414 },
415 Some(sym::LET) | Some(sym::VAR)
416 if n.args.len() == 2 && split_block_call(&n.args[1]).is_some() =>
417 {
418 let (head, args, block) =
419 split_block_call(&n.args[1]).expect("checked by the guard");
420 let target = self.expr(&n.args[0]);
421 let keyword = if n.is_form(sym::VAR) { "var " } else { "" };
422 let rendered = self.call_text(head, &args);
423 self.line(&format!("{keyword}{target} = {rendered}:"));
424 self.body(&block);
425 }
426 Some(sym::LET) | Some(sym::VAR)
430 if n.args.len() == 2
431 && (n.args[1].is_form(sym::TRY) || n.args[1].is_form(sym::PARALLEL))
432 && n.args[1].args.len() == 1 =>
433 {
434 let keyword = if n.is_form(sym::VAR) { "var " } else { "" };
435 let target = self.expr(&n.args[0]);
436 let head = n.args[1].head_name().unwrap_or(sym::TRY);
437 self.line(&format!("{keyword}{target} = {head}:"));
438 self.body(&n.args[1].args[0]);
439 }
440 Some(sym::LET) if n.args.len() == 2 => {
441 let t = &n.args[0];
442 let target = if t.is_form(sym::ANNOT) {
443 format!("{}: {}", self.expr(&t.args[0]), self.type_expr(&t.args[1]))
444 } else {
445 self.expr(t)
446 };
447 let v = self.expr(&n.args[1]);
448 self.line(&format!("{target} = {v}"));
449 }
450 Some(sym::VAR) if n.args.len() == 2 => {
451 let t = &n.args[0];
452 let target = if t.is_form(sym::ANNOT) {
453 format!("{}: {}", self.expr(&t.args[0]), self.type_expr(&t.args[1]))
454 } else {
455 self.expr(t)
456 };
457 let v = self.expr(&n.args[1]);
458 self.line(&format!("var {target} = {v}"));
459 }
460 Some(sym::IF) if n.args.len() >= 2 && n.args[1].is_form(sym::DO) => {
461 let c = self.expr(&n.args[0]);
462 self.line(&format!("if {c}:"));
463 self.body(&n.args[1]);
464 if let Some(alt) = n.args.get(2) {
465 if alt.args.len() == 1 && alt.args[0].is_form(sym::IF) {
467 let inner = &alt.args[0];
468 let mut s = String::new();
469 std::mem::swap(&mut self.out, &mut s);
470 self.stmt(inner);
471 std::mem::swap(&mut self.out, &mut s);
472 let pad = " ".repeat(self.indent);
473 let rewritten = s.replacen(&format!("{pad}if "), &format!("{pad}elif "), 1);
474 self.out.push_str(&rewritten);
475 } else {
476 self.line("else:");
477 self.body(alt);
478 }
479 }
480 }
481 Some(sym::FOR) if n.args.len() == 3 => {
482 let v = self.expr(&n.args[0]);
483 let seq = self.expr(&n.args[1]);
484 self.line(&format!("for {v} in {seq}:"));
485 self.body(&n.args[2]);
486 }
487 Some(sym::WHILE) if n.args.len() == 2 => {
488 let c = self.expr(&n.args[0]);
489 self.line(&format!("while {c}:"));
490 self.body(&n.args[1]);
491 }
492 Some(sym::MATCH) if !n.args.is_empty() => {
493 let s = self.expr(&n.args[0]);
494 self.line(&format!("match {s}:"));
495 self.indent += 1;
496 for arm in &n.args[1..] {
497 self.line(&format!("case {}:", self.case_head(arm)));
498 self.body(&arm.args[1]);
499 }
500 self.indent -= 1;
501 }
502 Some(sym::TRY) if n.args.len() == 1 => {
505 self.line("try:");
506 self.body(&n.args[0]);
507 }
508 Some(sym::PARALLEL) if n.args.len() == 1 => {
511 self.line("parallel:");
512 self.body(&n.args[0]);
513 }
514 Some(sym::ROW | sym::IDENTITY) => self.item(n),
515 Some(sym::DEF | sym::MACRO | sym::MODEL | sym::UNION | sym::TYPE | sym::NEWTYPE)
516 | Some(sym::TRAIT | sym::IMPL | sym::IMPORT | sym::DECORATE | sym::TEST)
517 | Some(sym::PROPERTY) => self.item(n),
518
519 Some(sym::GIVEN) if !n.args.is_empty() => {
523 let events = self.expr(&n.args[0]);
524 match n.args.get(1) {
525 Some(actor) => {
526 let a = self.expr(actor);
527 self.line(&format!("given {events} by {a}"));
528 }
529 None => self.line(&format!("given {events}")),
530 }
531 }
532 Some(sym::WHEN) if n.args.len() >= 2 => {
533 let cmds: Vec<String> = n.args[1..].iter().map(|a| self.expr(a)).collect();
534 let cmds = cmds.join(", ");
535 match n.args[0].as_str_lit() {
536 Some(actor) => self.line(&format!("when session(\"{actor}\") sends {cmds}")),
537 None => self.line(&format!("when {cmds}")),
538 }
539 }
540 Some(sym::EXPECT) if n.args.len() == 1 => {
541 let e = self.expr(&n.args[0]);
544 self.line(&format!("expect {e}"));
545 }
546 Some(sym::EXPECT_CONTAINS) if !n.args.is_empty() => {
547 let needle = self.expr(&n.args[0]);
548 match n.args.get(1).and_then(|a| a.as_str_lit()) {
549 Some(actor) => self.line(&format!(
550 "expect page(session(\"{actor}\")) contains {needle}"
551 )),
552 None => self.line(&format!("expect page contains {needle}")),
553 }
554 }
555 Some(sym::EXPECT_SNAPSHOT) if n.args.len() == 2 => {
556 let subject = match n.args[1].as_str_lit() {
557 Some(actor) => format!("page(session(\"{actor}\"))"),
558 None => "page".to_string(),
559 };
560 match n.args[0].as_str_lit() {
561 Some(name) => {
562 self.line(&format!("expect {subject} matches snapshot \"{name}\""))
563 }
564 None => self.line(&format!("expect {subject} matches snapshot")),
565 }
566 }
567 Some(sym::EXPECT_FOLD) if !n.args.is_empty() => {
568 let events = self.expr(&n.args[0]);
569 match n.args.get(1).and_then(|a| a.as_str_lit()) {
570 Some(actor) => {
571 self.line(&format!("expect state == fold_of {events} by \"{actor}\""))
572 }
573 None => self.line(&format!("expect state == fold_of {events}")),
574 }
575 }
576 Some(sym::EXPECT_PLACE) if n.args.len() == 2 => {
577 let what = self.expr(&n.args[0]);
578 let tier = self.expr(&n.args[1]);
579 self.line(&format!("expect place({what}) == {tier}"));
580 }
581 Some(sym::EXPECT_FLOW) if n.args.len() == 2 => {
582 let ty = self.expr(&n.args[0]);
583 let tier = self.expr(&n.args[1]);
584 self.line(&format!("expect flow({ty}) reaches nothing on {tier}"));
585 }
586 Some(sym::EXPECT_WIRE) if n.args.len() == 1 => {
587 let path = self.expr(&n.args[0]);
588 self.line(&format!("expect wire_compatible_with {path}"));
589 }
590 Some(sym::EXPECT_EFFECT) if n.args.len() == 2 => {
591 let atom = n.args[0].as_str_lit().unwrap_or_default().to_string();
592 let how = &n.args[1];
593 match how.head_name() {
594 Some("times") if how.args.len() == 1 => {
595 match how.args[0].as_lit() {
596 Some(Lit::Int(1)) => self.line(&format!("expect {atom} once")),
597 Some(Lit::Int(k)) => self.line(&format!("expect {atom} times {k}")),
598 _ => self.line(&format!("expect {atom} once")),
599 };
600 }
601 Some("with") if how.args.len() == 1 => {
602 let v = self.expr(&how.args[0]);
603 self.line(&format!("expect {atom} with {v}"));
604 }
605 _ => self.line(&format!("expect no {atom}")),
606 }
607 }
608 Some(sym::STUB) if n.args.len() == 2 => {
609 let atom = n.args[0].as_str_lit().unwrap_or_default().to_string();
610 let body = &n.args[1];
611 if body.is_form(sym::STUB_ARMS) {
612 self.line(&format!("stub {atom}:"));
613 self.indent += 1;
614 for arm in &body.args {
615 self.line(&format!("case {}:", self.case_head(arm)));
616 self.body(&arm.args[1]);
617 }
618 self.indent -= 1;
619 } else if body.is_form(sym::DO) {
620 self.line(&format!("stub {atom}:"));
621 self.body(body);
622 } else {
623 let v = self.expr(body);
624 self.line(&format!("stub {atom}: {v}"));
625 }
626 }
627 _ => {
628 if let Some((head, args, block)) = split_block_call(n) {
631 let rendered = self.call_text(head, &args);
632 self.line(&format!("{rendered}:"));
633 self.body(&block);
634 } else {
635 let e = self.expr(n);
636 self.line(&e);
637 }
638 }
639 }
640 }
641
642 fn call_text(&self, head: &str, args: &[Node]) -> String {
643 if args.is_empty() {
644 return head.to_string();
645 }
646 let rendered: Vec<String> = args.iter().map(|a| self.expr(a)).collect();
647 format!("{head}({})", rendered.join(", "))
648 }
649
650 fn type_expr(&self, n: &Node) -> String {
651 match n.head_name() {
652 Some("fn-type") if !n.args.is_empty() => {
657 let params: Vec<String> = n.args[..n.args.len() - 1]
658 .iter()
659 .map(|a| self.type_expr(a))
660 .collect();
661 format!(
662 "({}) -> {}",
663 params.join(", "),
664 self.type_expr(&n.args[n.args.len() - 1])
665 )
666 }
667 _ if !n.applied => {
668 let mut s = String::new();
669 write_atom(&mut s, n);
670 s
671 }
672 _ => {
673 let mut head = String::new();
674 write_atom(&mut head, n);
675 let args: Vec<String> = n.args.iter().map(|a| self.type_expr(a)).collect();
676 format!("{head}[{}]", args.join(", "))
677 }
678 }
679 }
680
681 fn block_expr(&self, n: &Node) -> String {
690 if n.is_form(sym::DO) {
691 let parts: Vec<String> = n.args.iter().map(|a| self.expr(a)).collect();
692 return parts.join("; ");
693 }
694 self.expr(n)
695 }
696
697 fn expr(&self, n: &Node) -> String {
698 if !n.applied {
699 let mut s = String::new();
700 write_atom(&mut s, n);
701 return s;
702 }
703 let head = n.head_name().unwrap_or("");
704 match head {
705 "not" if n.args.len() == 1 => format!("not {}", self.expr(&n.args[0])),
706 sym::RAISE if n.args.len() == 1 => format!("raise {}", self.expr(&n.args[0])),
707 sym::TRY | sym::PARALLEL if n.args.len() == 1 => {
714 format!("({head}: {})", self.block_expr(&n.args[0]))
715 }
716 "negate" if n.args.len() == 1 => format!("-{}", self.expr(&n.args[0])),
717 sym::UNQUOTE if n.args.len() == 1 => format!("${}", self.expr(&n.args[0])),
718 sym::SPLICE if n.args.len() == 1 => format!("$*{}", self.expr(&n.args[0])),
719 "and" | "or" | "|" | "@" | "==" | "!=" | "<" | "<=" | ">" | ">=" | "+" | "-" | "*"
720 | "/" | "%"
721 if n.args.len() == 2 =>
722 {
723 format!(
724 "({} {head} {})",
725 self.expr(&n.args[0]),
726 self.expr(&n.args[1])
727 )
728 }
729 "contains" if n.args.len() == 2 => {
730 format!("({} in {})", self.expr(&n.args[0]), self.expr(&n.args[1]))
731 }
732 "index" if n.args.len() == 2 => {
733 format!("{}[{}]", self.expr(&n.args[0]), self.expr(&n.args[1]))
734 }
735 sym::IF if n.args.len() == 3 && !n.args[1].is_form(sym::DO) => format!(
739 "({} if {} else {})",
740 self.expr(&n.args[1]),
741 self.expr(&n.args[0]),
742 self.expr(&n.args[2])
743 ),
744 sym::DOT if n.args.len() == 2 => {
745 format!("{}.{}", self.expr(&n.args[0]), self.expr(&n.args[1]))
746 }
747 sym::DOT if n.args.len() > 2 => {
748 let args: Vec<String> = n.args[2..].iter().map(|a| self.expr(a)).collect();
749 format!(
750 "{}.{}({})",
751 self.expr(&n.args[0]),
752 self.expr(&n.args[1]),
753 args.join(", ")
754 )
755 }
756 sym::KW_ARG if n.args.len() == 2 => {
757 format!("{}={}", self.expr(&n.args[0]), self.expr(&n.args[1]))
758 }
759 sym::LIST => {
760 let items: Vec<String> = n.args.iter().map(|a| self.expr(a)).collect();
761 format!("[{}]", items.join(", "))
762 }
763 sym::REST if n.args.len() == 1 => format!("*{}", self.expr(&n.args[0])),
764 sym::RECORD => {
765 let mut parts = Vec::new();
766 for pair in n.args.chunks(2) {
767 if pair.len() == 2 {
768 let k = pair[0]
769 .as_keyword()
770 .map(str::to_string)
771 .unwrap_or_else(|| self.expr(&pair[0]));
772 parts.push(format!("{k}: {}", self.expr(&pair[1])));
773 }
774 }
775 format!("{{{}}}", parts.join(", "))
776 }
777 sym::MAP => {
778 let mut parts = Vec::new();
779 for pair in n.args.chunks(2) {
780 if pair.len() == 2 {
781 parts.push(format!("{}: {}", self.expr(&pair[0]), self.expr(&pair[1])));
782 }
783 }
784 format!("{{{}}}", parts.join(", "))
785 }
786 sym::FN if n.args.len() == 2 => {
787 let params = self.params(&n.args[0]);
788 let body = &n.args[1];
789 let b = if body.args.len() == 1 {
790 self.expr(&body.args[0])
791 } else {
792 self.expr(body)
793 };
794 format!("lambda {params}: {b}")
795 }
796 sym::QUOTE if n.args.len() == 1 => {
797 format!("quote({})", self.expr(&n.args[0]))
800 }
801 sym::CALL if !n.args.is_empty() => {
802 let callee = self.expr(&n.args[0]);
803 let args: Vec<String> = n.args[1..].iter().map(|a| self.expr(a)).collect();
804 format!("{callee}({})", args.join(", "))
805 }
806 sym::DO => {
807 let items: Vec<String> = n.args.iter().map(|a| self.expr(a)).collect();
808 items.join("; ")
809 }
810 _ => {
811 let mut h = String::new();
812 write_atom(&mut h, n);
813 let args: Vec<String> = n.args.iter().map(|a| self.expr(a)).collect();
814 format!("{h}({})", args.join(", "))
815 }
816 }
817 }
818}
819
820fn split_block_call(n: &Node) -> Option<(&str, Vec<Node>, Node)> {
822 let head = n.head_name()?;
823 let last = n.args.last()?;
824 if !last.is_form(sym::KW_ARG) || last.args.len() != 2 {
825 return None;
826 }
827 if last.args[0].as_var().map(|s| s.as_str()) != Some("do") {
828 return None;
829 }
830 let quoted = &last.args[1];
831 if !quoted.is_form(sym::QUOTE) || quoted.args.len() != 1 {
832 return None;
833 }
834 let block = quoted.args[0].clone();
835 if !block.has_head(sym::DO) {
836 return None;
837 }
838 Some((head, n.args[..n.args.len() - 1].to_vec(), block))
839}
840
841#[cfg(test)]
842mod tests {
843 use super::*;
844 use crate::{parser, sexpr};
845 use beck_diag::{Diagnostics, SourceMap};
846
847 fn roundtrip_python(src: &str) -> String {
848 let mut map = SourceMap::new();
849 let f = map.add("t.beck", src);
850 let mut d = Diagnostics::new();
851 let n = parser::parse_module(f, "t", src, &mut d);
852 assert!(!d.has_errors(), "{}", d.render(&map));
853 to_python(&n)
854 }
855
856 fn parse(src: &str) -> Node {
857 let mut map = SourceMap::new();
858 let f = map.add("t.beck", src);
859 let mut d = Diagnostics::new();
860 let n = parser::parse_module(f, "t", src, &mut d);
861 assert!(!d.has_errors(), "{}", d.render(&map));
862 n
863 }
864
865 #[test]
866 fn printing_python_is_idempotent_and_reparses_to_the_same_tree() {
867 let src = "\
868def total(items: list[Int], base: Int) -> Int:
869 var acc = base
870 for i in items:
871 acc = (acc + i)
872 if (acc > 10):
873 return acc
874 elif (acc > 5):
875 return 5
876 else:
877 return 0
878";
879 let once = roundtrip_python(src);
880 let twice = roundtrip_python(&once);
881 assert_eq!(once, twice, "fmt must be idempotent");
882 assert!(parse(src).structurally_eq(&parse(&once)));
883 }
884
885 #[test]
886 fn block_calls_print_back_as_blocks() {
887 let src = "ui:\n main:\n h1(class=\"t\"):\n \"todos\"\n";
888 let out = roundtrip_python(src);
889 assert!(out.contains("ui:"), "{out}");
890 assert!(out.contains("h1(class=\"t\"):"), "{out}");
891 assert!(parse(src).structurally_eq(&parse(&out)));
892 }
893
894 #[test]
895 fn the_two_surfaces_are_the_same_language() {
896 let py = "def toggle(todos: Map[Id, Todo], e: Toggled) -> Map[Id, Todo]:\n\
898 \x20 return todos.update(e.id, lambda t: t.with(done=not t.done))\n";
899 let mut map = SourceMap::new();
900 let f = map.add("t.beck", py);
901 let mut d = Diagnostics::new();
902 let from_py = parser::parse_module(f, "t", py, &mut d);
903 assert!(!d.has_errors(), "{}", d.render(&map));
904
905 let sx_src = "(def toggle
906 (params (: todos (Map Id Todo)) (: e Toggled))
907 (returns (Map Id Todo))
908 (uses)
909 (do (return (. todos update (. e id)
910 (fn (params t) (do (. t with (kw done (not (. t done)))))))))) ";
911 let g = map.add("t.sx", sx_src);
912 let from_sx = sexpr::read_one(g, sx_src, &mut d).unwrap();
913 assert!(!d.has_errors(), "{}", d.render(&map));
914
915 assert!(
916 from_py.args[1].structurally_eq(&from_sx),
917 "python:\n{}\nsexpr:\n{}",
918 to_sexpr(&from_py.args[1]),
919 to_sexpr(&from_sx)
920 );
921 }
922
923 #[test]
924 fn sexpr_pretty_breaks_only_long_forms() {
925 let n = parse("def f() -> Int:\n return 1\n");
926 let pretty = to_sexpr_pretty(&n.args[1]);
927 assert_eq!(pretty.lines().count(), 1, "{pretty}");
928 }
929}
930
931#[cfg(test)]
937mod roundtrip {
938 use super::*;
939 use crate::parser;
940 use beck_diag::{Diagnostics, SourceMap};
941
942 fn parse(name: &str, src: &str) -> Node {
943 let mut map = SourceMap::new();
944 let f = map.add(name, src);
945 let mut d = Diagnostics::new();
946 let n = parser::parse_module(f, "t", src, &mut d);
947 assert!(!d.has_errors(), "{name}:\n{}", d.render(&map));
948 n
949 }
950
951 fn corpus() -> Vec<(&'static str, &'static str)> {
952 vec![
953 ("example", include_str!("../../../examples/todo.beck")),
954 (
955 "macros",
956 "macro unless(cond, do):\n return quote:\n if not $cond:\n $do\n",
957 ),
958 (
959 "control",
960 "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",
961 ),
962 (
963 "types",
964 "type Id = newtype[Str]\n\nmodel M:\n a: Int\n\nunion U:\n A(x: Int)\n B\n",
965 ),
966 (
969 "tests",
970 "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",
971 ),
972 ]
973 }
974
975 #[test]
976 fn printing_the_python_surface_round_trips() {
977 for (name, src) in corpus() {
978 let first = parse(name, src);
979 let printed = to_python(&first);
980 let leaked: &'static str = Box::leak(printed.clone().into_boxed_str());
981 let second = parse(name, leaked);
982 assert!(
983 first.structurally_eq(&second),
984 "{name} did not round-trip.\n--- printed ---\n{printed}\n--- as sexpr ---\n{}\n--- was ---\n{}",
985 to_sexpr(&second),
986 to_sexpr(&first)
987 );
988 }
989 }
990
991 #[test]
992 fn formatting_is_idempotent() {
993 for (name, src) in corpus() {
994 let once = to_python(&parse(name, src));
995 let leaked: &'static str = Box::leak(once.clone().into_boxed_str());
996 let twice = to_python(&parse(name, leaked));
997 assert_eq!(once, twice, "{name}: fmt is not idempotent");
998 }
999 }
1000}