1use bynk_syntax::ast::*;
28use bynk_syntax::error::CompileError;
29use bynk_syntax::lexer::tokenize;
30use bynk_syntax::parser::parse_units;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
35pub enum IndentStyle {
36 #[default]
37 Tab,
38 Spaces(u8),
39}
40
41#[derive(Debug, Clone)]
43pub struct FormatOptions {
44 pub indent: IndentStyle,
45 pub max_line_width: u32,
46 pub trailing_comma: bool,
47}
48
49impl Default for FormatOptions {
50 fn default() -> Self {
51 Self {
52 indent: IndentStyle::Tab,
53 max_line_width: 100,
54 trailing_comma: true,
55 }
56 }
57}
58
59#[derive(Debug, Clone)]
62pub struct FormatError {
63 pub errors: Vec<CompileError>,
64}
65
66pub fn format_source(source: &str, opts: &FormatOptions) -> Result<String, FormatError> {
71 let tokens = tokenize(source).map_err(|e| FormatError { errors: vec![e] })?;
72 let units = parse_units(&tokens, source).map_err(|errors| FormatError { errors })?;
78 let parts: Vec<String> = units
79 .iter()
80 .map(|unit| {
81 let mut f = Formatter::new(opts);
82 f.format_unit(unit);
83 f.finish()
84 })
85 .collect();
86 Ok(parts.join("\n"))
87}
88
89struct Formatter<'a> {
92 opts: &'a FormatOptions,
93 out: String,
94 indent_level: u32,
95 at_line_start: bool,
98}
99
100impl<'a> Formatter<'a> {
101 fn new(opts: &'a FormatOptions) -> Self {
102 Self {
103 opts,
104 out: String::new(),
105 indent_level: 0,
106 at_line_start: true,
107 }
108 }
109
110 fn finish(mut self) -> String {
111 while self.out.ends_with("\n\n") {
113 self.out.pop();
114 }
115 if !self.out.ends_with('\n') {
116 self.out.push('\n');
117 }
118 self.out
119 }
120
121 fn indent_unit(&self) -> String {
122 match self.opts.indent {
123 IndentStyle::Tab => "\t".to_string(),
124 IndentStyle::Spaces(n) => " ".repeat(n as usize),
125 }
126 }
127
128 fn emit_indent(&mut self) {
129 let unit = self.indent_unit();
130 for _ in 0..self.indent_level {
131 self.out.push_str(&unit);
132 }
133 }
134
135 fn push(&mut self, s: &str) {
136 if self.at_line_start && !s.starts_with('\n') {
137 self.emit_indent();
138 self.at_line_start = false;
139 }
140 if s.contains('\n') {
141 self.push_reindented(s);
142 } else {
143 self.out.push_str(s);
144 }
145 }
146
147 fn push_reindented(&mut self, s: &str) {
157 let prefix = self.indent_unit().repeat(self.indent_level as usize);
158 for (i, line) in s.split('\n').enumerate() {
159 if i > 0 {
160 self.out.push('\n');
161 if !line.is_empty() {
162 self.out.push_str(&prefix);
163 }
164 }
165 self.out.push_str(line);
166 }
167 }
168
169 fn newline(&mut self) {
170 self.out.push('\n');
171 self.at_line_start = true;
172 }
173
174 #[allow(dead_code)]
175 fn blank_line(&mut self) {
176 if !self.out.ends_with('\n') {
177 self.out.push('\n');
178 }
179 if !self.out.ends_with("\n\n") {
180 self.out.push('\n');
181 }
182 self.at_line_start = true;
183 }
184
185 fn indented<F: FnOnce(&mut Self)>(&mut self, f: F) {
186 self.indent_level += 1;
187 f(self);
188 self.indent_level -= 1;
189 }
190
191 fn emit_doc(&mut self, doc: &str) {
197 self.push("---");
198 self.newline();
199 for line in doc.lines() {
200 if line.is_empty() {
201 self.newline();
202 } else {
203 self.push(line);
204 self.newline();
205 }
206 }
207 self.push("---");
208 self.newline();
209 }
210
211 fn emit_leading_comments(&mut self, comments: &[String]) {
216 for body in comments {
217 self.push("--");
218 self.push(body);
219 self.newline();
220 }
221 }
222
223 fn emit_trailing_comment(&mut self, body: Option<&str>) {
226 if let Some(body) = body {
227 while self.out.ends_with('\n') {
230 self.out.pop();
231 }
232 self.out.push_str(" --");
233 self.out.push_str(body);
234 self.newline();
235 }
236 }
237
238 fn format_unit(&mut self, unit: &SourceUnit) {
241 match unit {
242 SourceUnit::Commons(c) => self.format_commons(c),
243 SourceUnit::Context(c) => self.format_context(c),
244 SourceUnit::Suite(t) => self.format_test(t),
245 SourceUnit::Integration(i) => self.format_integration(i),
246 SourceUnit::Adapter(a) => self.format_adapter(a),
247 }
248 }
249
250 fn format_adapter(&mut self, a: &AdapterDecl) {
251 self.emit_leading_comments(&a.trivia.leading);
252 if let Some(doc) = &a.documentation {
253 self.emit_doc(doc);
254 }
255 let header = format!("adapter {}", a.name.joined());
256 match a.form {
257 CommonsForm::Brace => {
258 self.push(&header);
259 self.push(" {");
260 self.newline();
261 self.indented(|f| {
262 f.format_adapter_body(a);
263 });
264 self.push("}");
265 self.newline();
266 }
267 CommonsForm::Fragment => {
268 self.push(&header);
269 self.newline();
270 self.newline();
271 self.format_adapter_body(a);
272 }
273 }
274 }
275
276 fn format_adapter_body(&mut self, a: &AdapterDecl) {
277 let mut any_header = false;
278 if let Some(b) = &a.binding {
279 self.emit_leading_comments(&b.trivia.leading);
280 self.push(&format!("binding {:?}", b.module));
281 if !b.requires.is_empty() {
282 let entries: Vec<String> = b
283 .requires
284 .iter()
285 .map(|r| format!("{:?}: {:?}", r.package, r.range))
286 .collect();
287 self.push(&format!(" requires {{ {} }}", entries.join(", ")));
288 }
289 self.emit_trailing_comment(b.trivia.trailing.as_deref());
290 if b.trivia.trailing.is_none() {
291 self.newline();
292 }
293 any_header = true;
294 }
295 for u in &a.uses {
296 self.emit_leading_comments(&u.trivia.leading);
297 self.push(&format!("uses {}", u.target.joined()));
298 self.emit_trailing_comment(u.trivia.trailing.as_deref());
299 if u.trivia.trailing.is_none() {
300 self.newline();
301 }
302 any_header = true;
303 }
304 for c in &a.consumes {
305 self.format_consumes(c);
306 any_header = true;
307 }
308 for e in &a.exports {
309 self.emit_leading_comments(&e.trivia.leading);
310 self.format_exports(e);
311 if e.trivia.trailing.is_some() {
312 self.emit_trailing_comment(e.trivia.trailing.as_deref());
313 }
314 any_header = true;
315 }
316 if any_header && !a.items.is_empty() {
317 self.newline();
318 }
319 let mut first = true;
320 for item in &a.items {
321 if !first {
322 self.newline();
323 }
324 self.format_item(item);
325 first = false;
326 }
327 if !a.trailing_comments.is_empty() {
328 if !a.items.is_empty() || any_header {
329 self.newline();
330 }
331 self.emit_leading_comments(&a.trailing_comments);
332 }
333 }
334
335 fn format_integration(&mut self, i: &IntegrationDecl) {
336 self.emit_leading_comments(&i.trivia.leading);
337 if let Some(doc) = &i.documentation {
338 self.emit_doc(doc);
339 }
340 let header = format!("suite integration \"{}\"", escape_string(&i.suite));
341 match i.form {
342 CommonsForm::Brace => {
343 self.push(&header);
344 self.push(" {");
345 self.newline();
346 self.indented(|f| {
347 f.format_integration_body(i);
348 });
349 self.push("}");
350 self.newline();
351 }
352 CommonsForm::Fragment => {
353 self.push(&header);
354 self.newline();
355 self.newline();
356 self.format_integration_body(i);
357 }
358 }
359 }
360
361 fn format_integration_body(&mut self, i: &IntegrationDecl) {
362 let wires = i
363 .participants
364 .iter()
365 .map(|p| p.joined())
366 .collect::<Vec<_>>()
367 .join(", ");
368 self.push(&format!("wires {wires}"));
369 self.newline();
370 for u in &i.uses {
371 self.newline();
372 self.emit_leading_comments(&u.trivia.leading);
373 self.push(&format!("uses {}", u.target.joined()));
374 self.emit_trailing_comment(u.trivia.trailing.as_deref());
375 self.newline();
376 }
377 for c in &i.cases {
378 self.newline();
379 self.emit_leading_comments(&c.trivia.leading);
380 if let Some(doc) = &c.documentation {
381 self.emit_doc(doc);
382 }
383 self.push(&format!("case \"{}\" ", escape_string(&c.name)));
384 self.format_block(&c.body);
385 self.newline();
386 }
387 for comment in &i.trailing_comments {
388 self.push(&format!("--{comment}"));
389 self.newline();
390 }
391 }
392
393 fn format_test(&mut self, t: &SuiteDecl) {
394 self.emit_leading_comments(&t.trivia.leading);
395 if let Some(doc) = &t.documentation {
396 self.emit_doc(doc);
397 }
398 let header = format!("suite {}", t.target.joined());
399 match t.form {
400 CommonsForm::Brace => {
401 self.push(&header);
402 self.push(" {");
403 self.newline();
404 self.indented(|f| {
405 f.format_test_body(
406 &t.uses,
407 &t.mocks,
408 &t.cases,
409 &t.properties,
410 &t.trailing_comments,
411 );
412 });
413 self.push("}");
414 self.newline();
415 }
416 CommonsForm::Fragment => {
417 self.push(&header);
418 self.newline();
419 self.format_test_body(
420 &t.uses,
421 &t.mocks,
422 &t.cases,
423 &t.properties,
424 &t.trailing_comments,
425 );
426 }
427 }
428 }
429
430 fn format_test_body(
431 &mut self,
432 uses: &[UsesDecl],
433 mocks: &[MockDecl],
434 cases: &[Case],
435 properties: &[PropertyDecl],
436 trailing_comments: &[String],
437 ) {
438 let mut first = true;
439 for u in uses {
440 if !first {
441 self.newline();
442 }
443 self.emit_leading_comments(&u.trivia.leading);
444 self.push(&format!("uses {}", u.target.joined()));
445 self.emit_trailing_comment(u.trivia.trailing.as_deref());
446 self.newline();
447 first = false;
448 }
449 for m in mocks {
450 if !first {
451 self.newline();
452 }
453 self.emit_leading_comments(&m.trivia.leading);
454 if let Some(doc) = &m.documentation {
455 self.emit_doc(doc);
456 }
457 self.push(&format!(
458 "mocks {} = {} {{",
459 m.target_name.name, m.impl_name.name
460 ));
461 self.newline();
462 self.indented(|f| {
463 let mut first_op = true;
464 for op in &m.ops {
465 if !first_op {
466 f.newline();
467 }
468 let params = op
469 .params
470 .iter()
471 .map(|p| format!("{}: {}", p.name.name, type_ref_to_string(&p.type_ref)))
472 .collect::<Vec<_>>()
473 .join(", ");
474 f.push(&format!(
475 "fn {}({params}) -> {} ",
476 op.name.name,
477 type_ref_to_string(&op.return_type)
478 ));
479 f.format_block(&op.body);
480 f.newline();
481 first_op = false;
482 }
483 });
484 self.push("}");
485 self.newline();
486 first = false;
487 }
488 for c in cases {
489 if !first {
490 self.newline();
491 }
492 self.emit_leading_comments(&c.trivia.leading);
493 if let Some(doc) = &c.documentation {
494 self.emit_doc(doc);
495 }
496 self.push(&format!("case \"{}\" ", escape_string(&c.name)));
497 self.format_block(&c.body);
498 self.newline();
499 first = false;
500 }
501 for p in properties {
502 if !first {
503 self.newline();
504 }
505 self.emit_leading_comments(&p.trivia.leading);
506 if let Some(doc) = &p.documentation {
507 self.emit_doc(doc);
508 }
509 self.push(&format!("property \"{}\" {{", escape_string(&p.name)));
510 self.newline();
511 self.indented(|f| f.format_for_all(&p.forall));
512 self.push("}");
513 self.newline();
514 first = false;
515 }
516 for comment in trailing_comments {
517 self.push(&format!("--{comment}"));
518 self.newline();
519 }
520 }
521
522 fn format_for_all(&mut self, fa: &ForAll) {
525 let bindings = fa
526 .bindings
527 .iter()
528 .map(|b| format!("{}: {}", b.name.name, type_ref_to_string(&b.type_ref)))
529 .collect::<Vec<_>>()
530 .join(", ");
531 let mut header = format!("for all {bindings}");
532 if let Some(w) = &fa.where_pred {
533 header.push_str(&format!(" where {}", expr_to_string(w)));
534 }
535 self.push(&format!("{header} "));
536 self.format_block(&fa.body);
537 self.newline();
538 }
539
540 fn format_commons(&mut self, c: &Commons) {
541 self.emit_leading_comments(&c.trivia.leading);
542 if let Some(doc) = &c.documentation {
543 self.emit_doc(doc);
544 }
545 let header = format!("commons {}", c.name.joined());
546 match c.form {
547 CommonsForm::Brace => {
548 self.push(&header);
549 self.push(" {");
550 self.newline();
551 self.indented(|f| {
552 f.format_commons_body(&c.uses, &c.items, &c.trailing_comments);
553 });
554 self.push("}");
555 self.newline();
556 }
557 CommonsForm::Fragment => {
558 self.push(&header);
559 self.newline();
560 self.newline();
561 self.format_commons_body(&c.uses, &c.items, &c.trailing_comments);
562 }
563 }
564 }
565
566 fn format_commons_body(
567 &mut self,
568 uses: &[UsesDecl],
569 items: &[CommonsItem],
570 trailing_comments: &[String],
571 ) {
572 let mut any_uses = false;
573 for u in uses {
574 self.emit_leading_comments(&u.trivia.leading);
575 self.push(&format!("uses {}", u.target.joined()));
576 self.emit_trailing_comment(u.trivia.trailing.as_deref());
577 if u.trivia.trailing.is_none() {
578 self.newline();
579 }
580 any_uses = true;
581 }
582 if any_uses && !items.is_empty() {
583 self.newline();
584 }
585 let mut first = true;
586 for item in items {
587 if !first {
588 self.newline();
589 }
590 self.format_item(item);
591 first = false;
592 }
593 if !trailing_comments.is_empty() {
594 if !items.is_empty() || any_uses {
597 self.newline();
598 }
599 self.emit_leading_comments(trailing_comments);
600 }
601 }
602
603 fn format_context(&mut self, c: &Context) {
604 self.emit_leading_comments(&c.trivia.leading);
605 if let Some(doc) = &c.documentation {
606 self.emit_doc(doc);
607 }
608 let header = format!("context {}", c.name.joined());
609 match c.form {
610 CommonsForm::Brace => {
611 self.push(&header);
612 self.push(" {");
613 self.newline();
614 self.indented(|f| {
615 f.format_context_body(
616 &c.uses,
617 &c.consumes,
618 &c.exports,
619 &c.items,
620 &c.trailing_comments,
621 );
622 });
623 self.push("}");
624 self.newline();
625 }
626 CommonsForm::Fragment => {
627 self.push(&header);
628 self.newline();
629 self.newline();
630 self.format_context_body(
631 &c.uses,
632 &c.consumes,
633 &c.exports,
634 &c.items,
635 &c.trailing_comments,
636 );
637 }
638 }
639 }
640
641 fn format_consumes(&mut self, c: &ConsumesDecl) {
645 self.emit_leading_comments(&c.trivia.leading);
646 match (&c.alias, &c.selected) {
647 (Some(alias), _) => {
648 self.push(&format!("consumes {} as {}", c.target.joined(), alias.name))
649 }
650 (None, Some(selected)) if selected.is_empty() => {
651 self.push(&format!("consumes {} {{ }}", c.target.joined()));
652 }
653 (None, Some(selected)) => {
654 let names: Vec<&str> = selected.iter().map(|i| i.name.as_str()).collect();
655 self.push(&format!(
656 "consumes {} {{ {} }}",
657 c.target.joined(),
658 names.join(", ")
659 ));
660 }
661 (None, None) => self.push(&format!("consumes {}", c.target.joined())),
662 }
663 self.emit_trailing_comment(c.trivia.trailing.as_deref());
664 if c.trivia.trailing.is_none() {
665 self.newline();
666 }
667 }
668
669 fn format_context_body(
670 &mut self,
671 uses: &[UsesDecl],
672 consumes: &[ConsumesDecl],
673 exports: &[ExportsDecl],
674 items: &[CommonsItem],
675 trailing_comments: &[String],
676 ) {
677 let mut any_header = false;
678 for u in uses {
679 self.emit_leading_comments(&u.trivia.leading);
680 self.push(&format!("uses {}", u.target.joined()));
681 self.emit_trailing_comment(u.trivia.trailing.as_deref());
682 if u.trivia.trailing.is_none() {
683 self.newline();
684 }
685 any_header = true;
686 }
687 for c in consumes {
688 self.format_consumes(c);
689 any_header = true;
690 }
691 for e in exports {
692 self.emit_leading_comments(&e.trivia.leading);
693 self.format_exports(e);
694 if e.trivia.trailing.is_some() {
698 self.emit_trailing_comment(e.trivia.trailing.as_deref());
699 }
700 any_header = true;
701 }
702 if any_header && !items.is_empty() {
703 self.newline();
704 }
705 let mut first = true;
706 for item in items {
707 if !first {
708 self.newline();
709 }
710 self.format_item(item);
711 first = false;
712 }
713 if !trailing_comments.is_empty() {
714 if !items.is_empty() || any_header {
715 self.newline();
716 }
717 self.emit_leading_comments(trailing_comments);
718 }
719 }
720
721 fn format_exports(&mut self, e: &ExportsDecl) {
722 let vis = match e.kind {
723 ExportKind::Type(Visibility::Opaque) => "opaque",
724 ExportKind::Type(Visibility::Transparent) => "transparent",
725 ExportKind::Capability => "capability",
726 };
727 if e.names.is_empty() {
728 self.push(&format!("exports {} {{}}", vis));
729 self.newline();
730 return;
731 }
732 let oneline = format!(
734 "exports {} {{ {} }}",
735 vis,
736 e.names
737 .iter()
738 .map(|n| n.name.as_str())
739 .collect::<Vec<_>>()
740 .join(", ")
741 );
742 if self.line_fits(&oneline) {
743 self.push(&oneline);
744 self.newline();
745 return;
746 }
747 self.push(&format!("exports {} {{", vis));
749 self.newline();
750 self.indented(|f| {
751 for (i, n) in e.names.iter().enumerate() {
752 f.push(&n.name);
753 if i + 1 < e.names.len() || f.opts.trailing_comma {
754 f.push(",");
755 }
756 f.newline();
757 }
758 });
759 self.push("}");
760 self.newline();
761 }
762
763 fn line_fits(&self, candidate: &str) -> bool {
764 let unit_len = match self.opts.indent {
765 IndentStyle::Tab => 4, IndentStyle::Spaces(n) => n as usize,
767 };
768 let column = self.indent_level as usize * unit_len + candidate.len();
769 column as u32 <= self.opts.max_line_width
770 }
771
772 fn format_item(&mut self, item: &CommonsItem) {
773 match item {
774 CommonsItem::Type(t) => self.format_type_decl(t),
775 CommonsItem::Fn(f) => self.format_fn_decl(f),
776 CommonsItem::Capability(c) => self.format_capability(c),
777 CommonsItem::Provider(p) => self.format_provider(p),
778 CommonsItem::Service(s) => self.format_service(s),
779 CommonsItem::Agent(a) => self.format_agent(a),
780 CommonsItem::Actor(a) => self.format_actor(a),
781 }
782 }
783
784 fn format_type_decl(&mut self, t: &TypeDecl) {
787 self.emit_leading_comments(&t.trivia.leading);
788 if let Some(doc) = &t.documentation {
789 self.emit_doc(doc);
790 }
791 self.push(&format!("type {} = ", t.name.name));
792 self.format_type_body(&t.body);
793 self.emit_trailing_comment(t.trivia.trailing.as_deref());
794 if t.trivia.trailing.is_none() {
795 self.newline();
796 }
797 }
798
799 fn format_type_body(&mut self, body: &TypeBody) {
800 match body {
801 TypeBody::Refined {
802 base, refinement, ..
803 } => {
804 self.push(base.name());
805 if let Some(r) = refinement {
806 self.push(" where ");
807 self.format_refinement(r);
808 }
809 }
810 TypeBody::Opaque {
811 base, refinement, ..
812 } => {
813 self.push("opaque ");
814 self.push(base.name());
815 if let Some(r) = refinement {
816 self.push(" where ");
817 self.format_refinement(r);
818 }
819 }
820 TypeBody::Record(r) => self.format_record_body(r),
821 TypeBody::Sum(s) => self.format_sum_body(s),
822 }
823 }
824
825 fn format_refinement(&mut self, r: &Refinement) {
826 for (i, p) in r.predicates.iter().enumerate() {
827 if i > 0 {
828 self.push(" and ");
829 }
830 self.format_pred(p);
831 }
832 }
833
834 fn format_pred(&mut self, p: &RefinementPred) {
835 match &p.kind {
836 PredKind::Matches(re) => self.push(&format!("Matches(\"{}\")", escape_string(re))),
837 PredKind::InRange(a, b) => self.push(&format!("InRange({}, {})", a.value, b.value)),
838 PredKind::InRangeF(a, b) => self.push(&format!("InRange({}, {})", a.lexeme, b.lexeme)),
839 PredKind::MinLength(n) => self.push(&format!("MinLength({n})")),
840 PredKind::MaxLength(n) => self.push(&format!("MaxLength({n})")),
841 PredKind::Length(n) => self.push(&format!("Length({n})")),
842 PredKind::NonNegative => self.push("NonNegative"),
843 PredKind::Positive => self.push("Positive"),
844 PredKind::NonEmpty => self.push("NonEmpty"),
845 }
846 }
847
848 fn format_record_body(&mut self, r: &RecordBody) {
849 if r.fields.is_empty() {
850 self.push("{}");
851 return;
852 }
853 let oneline_fields: Vec<String> = r
855 .fields
856 .iter()
857 .map(|f| self.format_record_field_oneline(f))
858 .collect();
859 let oneline = format!("{{ {} }}", oneline_fields.join(", "));
860 if self.line_fits(&oneline) && !oneline.contains('\n') {
861 self.push(&oneline);
862 return;
863 }
864 self.push("{");
866 self.newline();
867 self.indented(|f| {
868 for (i, field) in r.fields.iter().enumerate() {
869 f.format_record_field(field);
870 if i + 1 < r.fields.len() || f.opts.trailing_comma {
871 f.push(",");
872 }
873 f.newline();
874 }
875 });
876 self.push("}");
877 }
878
879 fn format_record_field(&mut self, field: &RecordField) {
880 self.push(&format!("{}: ", field.name.name));
881 self.format_type_ref(&field.type_ref);
882 if let Some(r) = &field.refinement {
883 self.push(" where ");
884 self.format_refinement(r);
885 }
886 if let Some(init) = &field.init {
887 self.push(" = ");
888 self.format_expr(init);
889 }
890 }
891
892 fn format_record_field_oneline(&self, field: &RecordField) -> String {
893 let mut out = format!("{}: ", field.name.name);
894 out.push_str(&type_ref_to_string(&field.type_ref));
895 if let Some(r) = &field.refinement {
896 out.push_str(" where ");
897 out.push_str(&refinement_to_string(r));
898 }
899 if let Some(init) = &field.init {
900 out.push_str(" = ");
901 out.push_str(&expr_to_string(init));
902 }
903 out
904 }
905
906 fn format_sum_body(&mut self, s: &SumBody) {
907 let any_payload = s.variants.iter().any(|v| !v.payload.is_empty());
911 if !any_payload {
912 let names: Vec<&str> = s.variants.iter().map(|v| v.name.name.as_str()).collect();
914 let oneline = format!("enum {{ {} }}", names.join(", "));
915 if self.line_fits(&oneline) {
916 self.push(&oneline);
917 return;
918 }
919 self.push("enum {");
920 self.newline();
921 self.indented(|f| {
922 for (i, v) in s.variants.iter().enumerate() {
923 f.push(&v.name.name);
924 if i + 1 < s.variants.len() || f.opts.trailing_comma {
925 f.push(",");
926 }
927 f.newline();
928 }
929 });
930 self.push("}");
931 return;
932 }
933 for (i, v) in s.variants.iter().enumerate() {
935 if i > 0 {
936 self.newline();
937 }
938 self.push("| ");
939 self.push(&v.name.name);
940 if !v.payload.is_empty() {
941 self.push("(");
942 let parts: Vec<String> = v
943 .payload
944 .iter()
945 .map(|p| format!("{}: {}", p.name.name, type_ref_to_string(&p.type_ref)))
946 .collect();
947 self.push(&parts.join(", "));
948 self.push(")");
949 }
950 }
951 }
952
953 fn format_type_ref(&mut self, t: &TypeRef) {
954 self.push(&type_ref_to_string(t));
955 }
956
957 fn format_fn_decl(&mut self, f: &FnDecl) {
960 self.emit_leading_comments(&f.trivia.leading);
961 if let Some(doc) = &f.documentation {
962 self.emit_doc(doc);
963 }
964 self.push("fn ");
965 self.push(&f.name.display());
966 if !f.type_params.is_empty() {
968 let names: Vec<&str> = f
969 .type_params
970 .iter()
971 .map(|tp| tp.name.name.as_str())
972 .collect();
973 self.push(&format!("[{}]", names.join(", ")));
974 }
975 self.format_params(&f.params, f.has_self);
976 self.push(" -> ");
977 self.format_type_ref(&f.return_type);
978 self.push(" ");
979 self.format_block(&f.body);
980 self.emit_trailing_comment(f.trivia.trailing.as_deref());
981 if f.trivia.trailing.is_none() {
982 self.newline();
983 }
984 }
985
986 fn format_params(&mut self, params: &[Param], has_self: bool) {
987 let mut rendered: Vec<String> = Vec::new();
988 if has_self {
989 rendered.push("self".to_string());
990 }
991 for p in params {
994 rendered.push(format!(
995 "{}: {}",
996 p.name.name,
997 type_ref_to_string(&p.type_ref)
998 ));
999 }
1000 let oneline = format!("({})", rendered.join(", "));
1001 if self.line_fits(&oneline) || rendered.len() <= 1 {
1002 self.push(&oneline);
1003 return;
1004 }
1005 self.push("(");
1007 self.newline();
1008 self.indented(|f| {
1009 for (i, r) in rendered.iter().enumerate() {
1010 f.push(r);
1011 if i + 1 < rendered.len() {
1017 f.push(",");
1018 }
1019 f.newline();
1020 }
1021 });
1022 self.push(")");
1023 }
1024
1025 fn format_capability(&mut self, c: &CapabilityDecl) {
1028 self.emit_leading_comments(&c.trivia.leading);
1029 if let Some(doc) = &c.documentation {
1030 self.emit_doc(doc);
1031 }
1032 self.push(&format!("capability {} {{", c.name.name));
1033 self.newline();
1034 self.indented(|f| {
1035 for op in &c.ops {
1036 f.emit_leading_comments(&op.trivia.leading);
1037 if let Some(doc) = &op.documentation {
1038 f.emit_doc(doc);
1039 }
1040 f.push("fn ");
1041 f.push(&op.name.name);
1042 f.format_params(&op.params, false);
1043 f.push(" -> ");
1044 f.format_type_ref(&op.return_type);
1045 f.emit_trailing_comment(op.trivia.trailing.as_deref());
1046 if op.trivia.trailing.is_none() {
1047 f.newline();
1048 }
1049 }
1050 });
1051 self.push("}");
1052 self.emit_trailing_comment(c.trivia.trailing.as_deref());
1053 if c.trivia.trailing.is_none() {
1054 self.newline();
1055 }
1056 }
1057
1058 fn format_provider(&mut self, p: &ProviderDecl) {
1059 self.emit_leading_comments(&p.trivia.leading);
1060 if let Some(doc) = &p.documentation {
1061 self.emit_doc(doc);
1062 }
1063 self.push(&format!(
1064 "provides {} = {}",
1065 p.capability.name, p.provider_name.name
1066 ));
1067 if !p.given.is_empty() {
1068 self.push(" given ");
1069 let names: Vec<String> = p.given.iter().map(cap_ref_src).collect();
1070 self.push(&names.join(", "));
1071 }
1072 if p.external {
1074 self.emit_trailing_comment(p.trivia.trailing.as_deref());
1075 if p.trivia.trailing.is_none() {
1076 self.newline();
1077 }
1078 return;
1079 }
1080 self.push(" {");
1081 self.newline();
1082 self.indented(|f| {
1083 for (i, op) in p.ops.iter().enumerate() {
1084 if i > 0 {
1085 f.newline();
1086 }
1087 f.emit_leading_comments(&op.trivia.leading);
1088 f.push("fn ");
1089 f.push(&op.name.name);
1090 f.format_params(&op.params, false);
1091 f.push(" -> ");
1092 f.format_type_ref(&op.return_type);
1093 f.push(" ");
1094 f.format_block(&op.body);
1095 f.emit_trailing_comment(op.trivia.trailing.as_deref());
1096 if op.trivia.trailing.is_none() {
1097 f.newline();
1098 }
1099 }
1100 });
1101 self.push("}");
1102 self.emit_trailing_comment(p.trivia.trailing.as_deref());
1103 if p.trivia.trailing.is_none() {
1104 self.newline();
1105 }
1106 }
1107
1108 fn format_service(&mut self, s: &ServiceDecl) {
1109 self.emit_leading_comments(&s.trivia.leading);
1110 if let Some(doc) = &s.documentation {
1111 self.emit_doc(doc);
1112 }
1113 let from = match &s.protocol {
1114 ServiceProtocol::Call => String::new(),
1115 ServiceProtocol::Http => " from http".to_string(),
1116 ServiceProtocol::Cron => " from cron".to_string(),
1117 ServiceProtocol::Queue { name } => {
1118 format!(" from queue(\"{}\")", escape_string(name))
1119 }
1120 ServiceProtocol::WebSocket { in_type, out_type } => {
1121 format!(
1122 " from WebSocket(in: {}, out: {})",
1123 type_ref_to_string(in_type),
1124 type_ref_to_string(out_type)
1125 )
1126 }
1127 };
1128 self.push(&format!("service {}{} {{", s.name.name, from));
1129 self.newline();
1130 self.indented(|f| {
1131 for (i, h) in s.handlers.iter().enumerate() {
1132 if i > 0 {
1133 f.newline();
1134 }
1135 f.format_handler(h);
1136 }
1137 });
1138 self.push("}");
1139 self.emit_trailing_comment(s.trivia.trailing.as_deref());
1140 if s.trivia.trailing.is_none() {
1141 self.newline();
1142 }
1143 }
1144
1145 fn format_agent(&mut self, a: &AgentDecl) {
1146 self.emit_leading_comments(&a.trivia.leading);
1147 if let Some(doc) = &a.documentation {
1148 self.emit_doc(doc);
1149 }
1150 self.push(&format!("agent {} {{", a.name.name));
1151 self.newline();
1152 self.indented(|f| {
1153 f.push(&format!(
1155 "key {}: {}",
1156 a.key_name.name,
1157 type_ref_to_string(&a.key_type)
1158 ));
1159 f.newline();
1160 f.newline();
1161 for sf in &a.store_fields {
1163 f.format_store_field(sf);
1164 f.newline();
1165 }
1166 for inv in &a.invariants {
1169 f.newline();
1170 f.format_invariant(inv);
1171 }
1172 for h in &a.handlers {
1174 f.newline();
1175 f.format_handler(h);
1176 }
1177 });
1178 self.push("}");
1179 self.emit_trailing_comment(a.trivia.trailing.as_deref());
1180 if a.trivia.trailing.is_none() {
1181 self.newline();
1182 }
1183 }
1184
1185 fn format_store_field(&mut self, sf: &StoreField) {
1189 self.emit_leading_comments(&sf.trivia.leading);
1190 if let Some(doc) = &sf.documentation {
1191 self.emit_doc(doc);
1192 }
1193 self.push(&format!(
1194 "store {}: {}",
1195 sf.name.name,
1196 store_kind_to_string(&sf.kind)
1197 ));
1198 for ann in &sf.annotations {
1200 self.push(&format!(" {}", annotation_to_string(ann)));
1201 }
1202 if let Some(init) = &sf.init {
1203 self.push(&format!(" = {}", expr_with_prec(init, 0)));
1204 }
1205 self.emit_trailing_comment(sf.trivia.trailing.as_deref());
1206 }
1207
1208 fn format_invariant(&mut self, inv: &Invariant) {
1211 self.emit_leading_comments(&inv.trivia.leading);
1212 if let Some(doc) = &inv.documentation {
1213 self.emit_doc(doc);
1214 }
1215 self.push(&format!("invariant {}:", inv.name.name));
1216 self.newline();
1217 self.indented(|f| {
1218 f.push(&expr_to_string(&inv.predicate));
1219 });
1220 self.emit_trailing_comment(inv.trivia.trailing.as_deref());
1221 if inv.trivia.trailing.is_none() {
1222 self.newline();
1223 }
1224 }
1225
1226 fn format_actor(&mut self, a: &ActorDecl) {
1227 self.emit_leading_comments(&a.trivia.leading);
1228 if let Some(doc) = &a.documentation {
1229 self.emit_doc(doc);
1230 }
1231 if let Some(r) = &a.refinement {
1232 self.push(&format!(
1234 "actor {} = {} where {}",
1235 a.name.name,
1236 r.base.name,
1237 expr_to_string(&r.predicate)
1238 ));
1239 } else {
1240 let auth = a.auth.as_ref().map(|i| i.name.as_str()).unwrap_or("None");
1242 self.push(&format!("actor {} {{ auth = {auth}", a.name.name));
1243 if !a.auth_config.is_empty() {
1244 let args: Vec<String> = a
1245 .auth_config
1246 .iter()
1247 .map(|arg| match &arg.value {
1248 bynk_syntax::ast::SchemeArgValue::Str(s) => {
1249 format!("{} = \"{}\"", arg.key.name, escape_string(s))
1250 }
1251 bynk_syntax::ast::SchemeArgValue::Int(n) => {
1252 format!("{} = {n}", arg.key.name)
1253 }
1254 })
1255 .collect();
1256 self.push(&format!("({})", args.join(", ")));
1257 }
1258 if let Some(id) = &a.identity {
1259 self.push(&format!(", identity = {}", type_ref_to_string(id)));
1260 }
1261 self.push(" }");
1262 }
1263 self.emit_trailing_comment(a.trivia.trailing.as_deref());
1264 if a.trivia.trailing.is_none() {
1265 self.newline();
1266 }
1267 }
1268
1269 fn format_handler(&mut self, h: &Handler) {
1270 self.emit_leading_comments(&h.trivia.leading);
1271 if let Some(doc) = &h.documentation {
1272 self.emit_doc(doc);
1273 }
1274 match &h.kind {
1277 HandlerKind::Call => {
1278 self.push("on call");
1279 if let Some(m) = &h.method_name {
1280 self.push(&format!(" {}", m.name));
1281 }
1282 }
1283 HandlerKind::Http { method, path } => {
1284 self.push(&format!(
1287 "on {}(\"{}\") ",
1288 method.as_str(),
1289 escape_string(path)
1290 ));
1291 }
1292 HandlerKind::Cron { expr } => {
1293 self.push(&format!("on schedule(\"{}\") ", escape_string(expr)));
1294 }
1295 HandlerKind::Message => {
1296 self.push("on message");
1297 }
1298 HandlerKind::Open => {
1299 self.push("on open");
1300 }
1301 HandlerKind::Close => {
1302 self.push("on close");
1303 }
1304 }
1305 if let Some(by) = &h.by_clause {
1309 if matches!(
1310 h.kind,
1311 HandlerKind::Call | HandlerKind::Message | HandlerKind::Open | HandlerKind::Close
1312 ) {
1313 self.push(" ");
1314 }
1315 let actors = by
1316 .actors
1317 .iter()
1318 .map(|a| a.name.as_str())
1319 .collect::<Vec<_>>()
1320 .join(" | ");
1321 match &by.binder {
1322 Some(b) => self.push(&format!("by {}: {actors} ", b.name)),
1323 None => self.push(&format!("by {actors} ")),
1324 }
1325 }
1326 self.format_params(&h.params, false);
1327 self.push(" -> ");
1328 self.format_type_ref(&h.return_type);
1329 if !h.given.is_empty() {
1330 self.push(" given ");
1331 let names: Vec<String> = h.given.iter().map(cap_ref_src).collect();
1332 self.push(&names.join(", "));
1333 }
1334 self.push(" ");
1335 self.format_block(&h.body);
1336 self.emit_trailing_comment(h.trivia.trailing.as_deref());
1337 if h.trivia.trailing.is_none() {
1338 self.newline();
1339 }
1340 }
1341
1342 fn format_block(&mut self, b: &Block) {
1345 let tail_oneline = expr_to_string(&b.tail);
1348 let any_stmt_trivia = b.statements.iter().any(|s| !statement_trivia(s).is_empty());
1349 if b.statements.is_empty()
1350 && b.tail_leading_comments.is_empty()
1351 && !any_stmt_trivia
1352 && self.line_fits(&format!("{{ {tail_oneline} }}"))
1353 && !tail_oneline.contains('\n')
1354 {
1355 self.push("{ ");
1356 self.format_expr(&b.tail);
1357 self.push(" }");
1358 return;
1359 }
1360 self.push("{");
1361 self.newline();
1362 self.indented(|f| {
1363 for stmt in &b.statements {
1364 let trivia = statement_trivia(stmt);
1365 f.emit_leading_comments(&trivia.leading);
1366 f.format_statement(stmt);
1367 f.emit_trailing_comment(trivia.trailing.as_deref());
1368 if trivia.trailing.is_none() {
1369 f.newline();
1370 }
1371 }
1372 f.emit_leading_comments(&b.tail_leading_comments);
1373 let implicit_unit_after_assert = matches!(b.tail.kind, ExprKind::UnitLit)
1380 && matches!(b.statements.last(), Some(Statement::Expect(_)))
1381 && b.tail_leading_comments.is_empty();
1382 if !implicit_unit_after_assert {
1383 f.format_expr(&b.tail);
1384 f.newline();
1385 }
1386 });
1387 self.push("}");
1388 }
1389
1390 fn format_statement(&mut self, s: &Statement) {
1391 match s {
1392 Statement::Let(l) => {
1393 self.push("let ");
1394 self.push(&l.name.name);
1395 if let Some(t) = &l.type_annot {
1396 self.push(": ");
1397 self.format_type_ref(t);
1398 }
1399 self.push(" = ");
1400 self.format_expr(&l.value);
1401 }
1402 Statement::EffectLet(l) => {
1403 self.push("let ");
1404 self.push(&l.name.name);
1405 if let Some(t) = &l.type_annot {
1406 self.push(": ");
1407 self.format_type_ref(t);
1408 }
1409 self.push(" <- ");
1410 self.format_expr(&l.value);
1411 }
1412 Statement::Expect(a) => {
1413 self.push("expect ");
1414 self.format_expr(&a.value);
1415 }
1416 Statement::Send(s) => {
1417 self.push("~> ");
1418 self.format_expr(&s.value);
1419 }
1420 Statement::Assign(a) => {
1421 self.push(&a.target.name);
1422 self.push(" := ");
1423 self.format_expr(&a.value);
1424 }
1425 }
1426 }
1427
1428 fn format_expr(&mut self, e: &Expr) {
1429 match &e.kind {
1436 ExprKind::Match { discriminant, arms } => self.format_match(discriminant, arms),
1437 _ => self.push(&expr_to_string(e)),
1438 }
1439 }
1440
1441 fn format_match(&mut self, discriminant: &Expr, arms: &[MatchArm]) {
1445 self.push("match ");
1446 self.format_expr(discriminant);
1447 self.push(" {");
1448 self.newline();
1449 self.indented(|f| {
1450 for arm in arms {
1451 f.push(&pattern_to_string(&arm.pattern));
1452 f.push(" => ");
1453 match &arm.body {
1454 MatchBody::Expr(e) => f.format_expr(e),
1455 MatchBody::Block(b) => f.format_block(b),
1456 }
1457 f.push(",");
1458 f.newline();
1459 }
1460 });
1461 self.push("}");
1462 }
1463}
1464
1465fn cap_ref_src(c: &CapRef) -> String {
1469 match &c.context {
1470 Some(prefix) => format!("{}.{}", prefix.joined(), c.name.name),
1471 None => c.name.name.clone(),
1472 }
1473}
1474
1475fn store_kind_to_string(k: &StoreKind) -> String {
1477 if k.args.is_empty() {
1478 k.head.name.clone()
1479 } else {
1480 format!(
1481 "{}[{}]",
1482 k.head.name,
1483 k.args
1484 .iter()
1485 .map(type_ref_to_string)
1486 .collect::<Vec<_>>()
1487 .join(", ")
1488 )
1489 }
1490}
1491
1492fn annotation_to_string(ann: &Annotation) -> String {
1495 if ann.args.is_empty() {
1496 return format!("@{}", ann.name.name);
1497 }
1498 let args = ann
1499 .args
1500 .iter()
1501 .map(|a| match &a.label {
1502 Some(l) => format!("{}: {}", l.name, expr_with_prec(&a.value, 0)),
1503 None => expr_with_prec(&a.value, 0),
1504 })
1505 .collect::<Vec<_>>()
1506 .join(", ");
1507 format!("@{}({})", ann.name.name, args)
1508}
1509
1510fn statement_trivia(s: &Statement) -> &Trivia {
1511 match s {
1512 Statement::Let(l) | Statement::EffectLet(l) => &l.trivia,
1513 Statement::Expect(a) => &a.trivia,
1514 Statement::Send(s) => &s.trivia,
1515 Statement::Assign(a) => &a.trivia,
1516 }
1517}
1518
1519fn type_ref_to_string(t: &TypeRef) -> String {
1522 match t {
1523 TypeRef::Base(b, _) => b.name().to_string(),
1524 TypeRef::Named(id) => id.name.clone(),
1525 TypeRef::Result(a, b, _) => format!(
1526 "Result[{}, {}]",
1527 type_ref_to_string(a),
1528 type_ref_to_string(b)
1529 ),
1530 TypeRef::Option(t, _) => format!("Option[{}]", type_ref_to_string(t)),
1531 TypeRef::Effect(t, _) => format!("Effect[{}]", type_ref_to_string(t)),
1532 TypeRef::HttpResult(t, _) => format!("HttpResult[{}]", type_ref_to_string(t)),
1533 TypeRef::QueueResult(_) => "QueueResult".to_string(),
1534 TypeRef::List(t, _) => format!("List[{}]", type_ref_to_string(t)),
1535 TypeRef::Query(t, _) => format!("Query[{}]", type_ref_to_string(t)),
1536 TypeRef::Stream(t, _) => format!("Stream[{}]", type_ref_to_string(t)),
1537 TypeRef::Connection(t, _) => format!("Connection[{}]", type_ref_to_string(t)),
1538 TypeRef::Map(k, v, _) => {
1539 format!("Map[{}, {}]", type_ref_to_string(k), type_ref_to_string(v))
1540 }
1541 TypeRef::ValidationError(_) => "ValidationError".to_string(),
1542 TypeRef::JsonError(_) => "JsonError".to_string(),
1543 TypeRef::Unit(_) => "()".to_string(),
1544 TypeRef::Fn(params, ret, _) => {
1545 let lhs = match params.len() {
1546 0 => "()".to_string(),
1547 1 if !matches!(params[0], TypeRef::Fn(..)) => type_ref_to_string(¶ms[0]),
1548 _ => format!(
1549 "({})",
1550 params
1551 .iter()
1552 .map(type_ref_to_string)
1553 .collect::<Vec<_>>()
1554 .join(", ")
1555 ),
1556 };
1557 format!("{lhs} -> {}", type_ref_to_string(ret))
1558 }
1559 }
1560}
1561
1562fn refinement_to_string(r: &Refinement) -> String {
1563 let mut s = String::new();
1564 for (i, p) in r.predicates.iter().enumerate() {
1565 if i > 0 {
1566 s.push_str(" and ");
1567 }
1568 s.push_str(&pred_to_string(p));
1569 }
1570 s
1571}
1572
1573fn pred_to_string(p: &RefinementPred) -> String {
1574 match &p.kind {
1575 PredKind::Matches(re) => format!("Matches(\"{}\")", escape_string(re)),
1576 PredKind::InRange(a, b) => format!("InRange({}, {})", a.value, b.value),
1577 PredKind::InRangeF(a, b) => format!("InRange({}, {})", a.lexeme, b.lexeme),
1578 PredKind::MinLength(n) => format!("MinLength({n})"),
1579 PredKind::MaxLength(n) => format!("MaxLength({n})"),
1580 PredKind::Length(n) => format!("Length({n})"),
1581 PredKind::NonNegative => "NonNegative".to_string(),
1582 PredKind::Positive => "Positive".to_string(),
1583 PredKind::NonEmpty => "NonEmpty".to_string(),
1584 }
1585}
1586
1587fn escape_string(s: &str) -> String {
1588 let mut out = String::with_capacity(s.len());
1589 for ch in s.chars() {
1590 match ch {
1591 '\\' => out.push_str("\\\\"),
1592 '"' => out.push_str("\\\""),
1593 '\n' => out.push_str("\\n"),
1594 '\t' => out.push_str("\\t"),
1595 c => out.push(c),
1596 }
1597 }
1598 out
1599}
1600
1601fn expr_to_string(e: &Expr) -> String {
1602 expr_with_prec(e, 0)
1603}
1604
1605fn binop_prec(op: BinOp) -> u8 {
1608 match op {
1609 BinOp::Implies => 0,
1611 BinOp::Or => 1,
1612 BinOp::And => 2,
1613 BinOp::Eq | BinOp::NotEq => 3,
1614 BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => 4,
1615 BinOp::Add | BinOp::Sub => 5,
1616 BinOp::Mul | BinOp::Div => 6,
1617 }
1618}
1619
1620fn expr_with_prec(e: &Expr, parent_prec: u8) -> String {
1621 match &e.kind {
1622 ExprKind::IntLit(n) => n.to_string(),
1623 ExprKind::FloatLit { lexeme, .. } => lexeme.clone(),
1625 ExprKind::DurationLit { value, unit, .. } => format!("{value}.{}", unit.name()),
1627 ExprKind::StrLit(s) => format!("\"{}\"", escape_string(s)),
1628 ExprKind::InterpStr(parts) => {
1632 let mut out = String::from("\"");
1633 for part in parts {
1634 match part {
1635 InterpPart::Chunk(text) => out.push_str(&escape_string(text)),
1636 InterpPart::Hole(hole) => {
1637 out.push_str(&format!("\\({})", expr_with_prec(hole, 0)));
1638 }
1639 }
1640 }
1641 out.push('"');
1642 out
1643 }
1644 ExprKind::BoolLit(b) => b.to_string(),
1645 ExprKind::UnitLit => "()".to_string(),
1646 ExprKind::Ident(id) => id.name.clone(),
1647 ExprKind::ListLit(elems) => format!(
1648 "[{}]",
1649 elems
1650 .iter()
1651 .map(expr_to_string)
1652 .collect::<Vec<_>>()
1653 .join(", ")
1654 ),
1655 ExprKind::Call {
1656 name,
1657 type_args,
1658 args,
1659 } => {
1660 let targs = if type_args.is_empty() {
1661 String::new()
1662 } else {
1663 format!(
1664 "[{}]",
1665 type_args
1666 .iter()
1667 .map(type_ref_to_string)
1668 .collect::<Vec<_>>()
1669 .join(", ")
1670 )
1671 };
1672 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
1673 format!("{}{}({})", name.name, targs, parts.join(", "))
1674 }
1675 ExprKind::BinOp(op, l, r) => {
1676 let prec = binop_prec(*op);
1677 let inner = format!(
1678 "{} {} {}",
1679 expr_with_prec(l, prec),
1680 op.name(),
1681 expr_with_prec(r, prec + 1)
1682 );
1683 if prec < parent_prec {
1684 format!("({inner})")
1685 } else {
1686 inner
1687 }
1688 }
1689 ExprKind::UnaryOp(op, inner) => {
1690 let s = format!("{}{}", op.name(), expr_with_prec(inner, 7));
1692 if parent_prec > 7 { format!("({s})") } else { s }
1693 }
1694 ExprKind::Paren(inner) => format!("({})", expr_with_prec(inner, 0)),
1695 ExprKind::Lambda(lambda) => {
1697 let params: Vec<String> = lambda
1698 .params
1699 .iter()
1700 .map(|p| match &p.type_ref {
1701 Some(tr) => format!("{}: {}", p.name.name, type_ref_to_string(tr)),
1702 None => p.name.name.clone(),
1703 })
1704 .collect();
1705 let body = match &lambda.body.kind {
1706 ExprKind::Block(b) => format_block_oneline(b),
1707 _ => expr_with_prec(&lambda.body, 0),
1708 };
1709 format!("({}) => {}", params.join(", "), body)
1710 }
1711 ExprKind::Block(b) => format_block_oneline(b),
1712 ExprKind::If {
1713 cond,
1714 then_block,
1715 else_block,
1716 } => {
1717 format!(
1718 "if {} {} else {}",
1719 expr_with_prec(cond, 0),
1720 format_block_oneline(then_block),
1721 format_block_oneline(else_block),
1722 )
1723 }
1724 ExprKind::Ok(v) => format!("Ok({})", expr_with_prec(v, 0)),
1725 ExprKind::Err(v) => format!("Err({})", expr_with_prec(v, 0)),
1726 ExprKind::Some(v) => format!("Some({})", expr_with_prec(v, 0)),
1727 ExprKind::None => "None".to_string(),
1728 ExprKind::Question(v) => format!("{}?", expr_with_prec(v, 8)),
1729 ExprKind::ConstructorCall {
1730 type_name,
1731 method,
1732 args,
1733 } => {
1734 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
1735 format!("{}.{}({})", type_name.name, method.name, parts.join(", "))
1736 }
1737 ExprKind::RecordConstruction { type_name, fields } => {
1738 let parts: Vec<String> = fields
1739 .iter()
1740 .map(|f| match &f.value {
1741 Some(v) => format!("{}: {}", f.name.name, expr_with_prec(v, 0)),
1742 None => f.name.name.clone(),
1743 })
1744 .collect();
1745 if parts.is_empty() {
1746 format!("{} {{}}", type_name.name)
1747 } else {
1748 format!("{} {{ {} }}", type_name.name, parts.join(", "))
1749 }
1750 }
1751 ExprKind::FieldAccess { receiver, field } => {
1752 format!("{}.{}", expr_with_prec(receiver, 8), field.name)
1753 }
1754 ExprKind::MethodCall {
1755 receiver,
1756 method,
1757 type_args,
1758 args,
1759 } => {
1760 let targs = if type_args.is_empty() {
1761 String::new()
1762 } else {
1763 format!(
1764 "[{}]",
1765 type_args
1766 .iter()
1767 .map(type_ref_to_string)
1768 .collect::<Vec<_>>()
1769 .join(", ")
1770 )
1771 };
1772 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
1773 format!(
1774 "{}.{}{targs}({})",
1775 expr_with_prec(receiver, 8),
1776 method.name,
1777 parts.join(", ")
1778 )
1779 }
1780 ExprKind::Match { discriminant, arms } => {
1781 let mut out = String::new();
1782 out.push_str("match ");
1783 out.push_str(&expr_with_prec(discriminant, 0));
1784 out.push_str(" {\n");
1785 for arm in arms {
1786 out.push('\t');
1787 out.push_str(&pattern_to_string(&arm.pattern));
1788 out.push_str(" => ");
1789 match &arm.body {
1790 MatchBody::Expr(e) => out.push_str(&expr_with_prec(e, 0)),
1791 MatchBody::Block(b) => out.push_str(&format_block_oneline(b)),
1792 }
1793 out.push_str(",\n");
1794 }
1795 out.push('}');
1796 out
1797 }
1798 ExprKind::Is { value, pattern } => {
1799 format!(
1800 "{} is {}",
1801 expr_with_prec(value, 4),
1802 pattern_to_string(pattern)
1803 )
1804 }
1805 ExprKind::RecordSpread {
1806 type_name,
1807 base,
1808 overrides,
1809 } => {
1810 let mut parts = vec![format!("...{}", expr_with_prec(base, 0))];
1811 for f in overrides {
1812 if let Some(v) = &f.value {
1813 parts.push(format!("{}: {}", f.name.name, expr_with_prec(v, 0)));
1814 } else {
1815 parts.push(f.name.name.clone());
1816 }
1817 }
1818 let body = parts.join(", ");
1819 match type_name {
1820 Some(tn) => format!("{} {{ {} }}", tn.name, body),
1821 None => format!("{{ {} }}", body),
1822 }
1823 }
1824 ExprKind::EffectPure(v) => format!("Effect.pure({})", expr_with_prec(v, 0)),
1825 ExprKind::Expect(v) => format!("expect {}", expr_with_prec(v, 0)),
1826 ExprKind::Val { type_ref, args } => {
1827 let t = type_ref_to_string(type_ref);
1828 if args.is_empty() {
1829 format!("Val[{t}]")
1830 } else {
1831 let a = args
1832 .iter()
1833 .map(|x| expr_with_prec(x, 0))
1834 .collect::<Vec<_>>()
1835 .join(", ");
1836 format!("Val[{t}]({a})")
1837 }
1838 }
1839 }
1840}
1841
1842fn pattern_to_string(p: &Pattern) -> String {
1843 match p {
1844 Pattern::Wildcard(_) => "_".to_string(),
1845 Pattern::Variant {
1846 type_name,
1847 variant,
1848 bindings,
1849 ..
1850 } => {
1851 let name_part = match type_name {
1852 Some(t) => format!("{}.{}", t.name, variant.name),
1853 None => variant.name.clone(),
1854 };
1855 if bindings.is_empty() {
1856 name_part
1857 } else {
1858 let parts: Vec<String> = bindings
1859 .iter()
1860 .map(|b| match &b.kind {
1861 PatternBindingKind::Positional { name } => name.name.clone(),
1862 PatternBindingKind::Named { field, name } => {
1863 format!("{}: {}", field.name, name.name)
1864 }
1865 })
1866 .collect();
1867 format!("{}({})", name_part, parts.join(", "))
1868 }
1869 }
1870 }
1871}
1872
1873fn format_block_oneline(b: &Block) -> String {
1874 if b.statements.is_empty() {
1875 format!("{{ {} }}", expr_with_prec(&b.tail, 0))
1876 } else {
1877 let mut out = String::from("{\n");
1879 for stmt in &b.statements {
1880 out.push('\t');
1881 out.push_str(&stmt_to_string(stmt));
1882 out.push('\n');
1883 }
1884 let implicit_unit_after_assert = matches!(b.tail.kind, ExprKind::UnitLit)
1887 && matches!(b.statements.last(), Some(Statement::Expect(_)));
1888 if !implicit_unit_after_assert {
1889 out.push('\t');
1890 out.push_str(&expr_with_prec(&b.tail, 0));
1891 out.push('\n');
1892 }
1893 out.push('}');
1894 out
1895 }
1896}
1897
1898fn stmt_to_string(s: &Statement) -> String {
1899 match s {
1900 Statement::Let(l) => {
1901 let mut out = format!("let {}", l.name.name);
1902 if let Some(t) = &l.type_annot {
1903 out.push_str(&format!(": {}", type_ref_to_string(t)));
1904 }
1905 out.push_str(&format!(" = {}", expr_with_prec(&l.value, 0)));
1906 out
1907 }
1908 Statement::EffectLet(l) => {
1909 let mut out = format!("let {}", l.name.name);
1910 if let Some(t) = &l.type_annot {
1911 out.push_str(&format!(": {}", type_ref_to_string(t)));
1912 }
1913 out.push_str(&format!(" <- {}", expr_with_prec(&l.value, 0)));
1914 out
1915 }
1916 Statement::Expect(a) => format!("expect {}", expr_with_prec(&a.value, 0)),
1917 Statement::Send(s) => format!("~> {}", expr_with_prec(&s.value, 0)),
1918 Statement::Assign(a) => format!("{} := {}", a.target.name, expr_with_prec(&a.value, 0)),
1919 }
1920}
1921
1922#[cfg(test)]
1923mod tests {
1924 use super::*;
1925
1926 fn fmt(src: &str) -> String {
1927 format_source(src, &FormatOptions::default()).expect("format failed")
1928 }
1929
1930 #[test]
1931 fn formats_minimal_commons() {
1932 let src = "commons fitness.units {}";
1933 let out = fmt(src);
1934 assert!(out.starts_with("commons fitness.units"));
1935 let out2 = fmt(&out);
1937 assert_eq!(out, out2);
1938 }
1939
1940 #[test]
1941 fn formats_refined_type() {
1942 let src = "commons x { type Metres = Int where NonNegative }";
1943 let out = fmt(src);
1944 assert!(out.contains("type Metres = Int where NonNegative"));
1945 let out2 = fmt(&out);
1946 assert_eq!(out, out2);
1947 }
1948
1949 #[test]
1950 fn formats_function_decl() {
1951 let src = "commons x { fn add(a: Int, b: Int) -> Int { a + b } }";
1952 let out = fmt(src);
1953 assert!(out.contains("fn add(a: Int, b: Int) -> Int"));
1954 let out2 = fmt(&out);
1955 assert_eq!(out, out2);
1956 }
1957
1958 #[test]
1959 fn formats_record() {
1960 let src = "commons x { type Pt = { x: Int, y: Int } }";
1961 let out = fmt(src);
1962 let out2 = fmt(&out);
1963 assert_eq!(out, out2, "formatter not idempotent: {out}");
1964 }
1965
1966 #[test]
1967 fn formats_doc_block() {
1968 let src = "commons x {\n---\nA descriptive doc.\n---\ntype T = Int where Positive\n}";
1969 let out = fmt(src);
1970 assert!(out.contains("A descriptive doc."));
1971 let out2 = fmt(&out);
1972 assert_eq!(out, out2);
1973 }
1974
1975 #[test]
1978 fn preserves_leading_line_comment_on_decl() {
1979 let src = "commons x {\n-- explain T\ntype T = Int where NonNegative\n}";
1980 let out = fmt(src);
1981 assert!(out.contains("-- explain T"), "comment dropped: {out}");
1982 assert_eq!(out, fmt(&out));
1984 }
1985
1986 #[test]
1987 fn preserves_trailing_line_comment_on_decl() {
1988 let src = "commons x {\ntype T = Int where NonNegative -- short\n}";
1989 let out = fmt(src);
1990 assert!(out.contains("-- short"));
1991 assert!(
1993 out.lines()
1994 .any(|l| l.contains("type T") && l.contains("-- short")),
1995 "trailing comment not on same line: {out}"
1996 );
1997 assert_eq!(out, fmt(&out));
1998 }
1999
2000 #[test]
2001 fn preserves_grouped_leading_comments() {
2002 let src = "commons x {\n-- one\n-- two\ntype T = Int where Positive\n}";
2003 let out = fmt(src);
2004 assert!(out.contains("-- one"));
2005 assert!(out.contains("-- two"));
2006 let i1 = out.find("-- one").unwrap();
2008 let i2 = out.find("-- two").unwrap();
2009 let between = &out[i1..i2];
2010 assert_eq!(
2011 between.matches('\n').count(),
2012 1,
2013 "blank line inserted: {out}"
2014 );
2015 assert_eq!(out, fmt(&out));
2016 }
2017
2018 #[test]
2019 fn preserves_comment_before_block_tail() {
2020 let src = "commons x {\nfn f(n: Int) -> Int {\nlet y = n + 1\n-- result\ny\n}\n}";
2021 let out = fmt(src);
2022 assert!(out.contains("-- result"), "tail comment dropped: {out}");
2023 assert_eq!(out, fmt(&out));
2024 }
2025
2026 #[test]
2027 fn preserves_comment_with_doc_block_above_decl() {
2028 let src = "commons x {\n-- TODO: rename\n---\nThe canonical T.\n---\ntype T = Int where Positive\n}";
2029 let out = fmt(src);
2030 assert!(out.contains("-- TODO: rename"));
2031 assert!(out.contains("The canonical T."));
2032 let ic = out.find("-- TODO: rename").unwrap();
2034 let id = out.find("The canonical T.").unwrap();
2035 let it = out.find("type T").unwrap();
2036 assert!(ic < id && id < it, "ordering wrong: {out}");
2037 assert_eq!(out, fmt(&out));
2038 }
2039
2040 #[test]
2041 fn preserves_trailing_file_comment() {
2042 let src = "commons x.y\n\ntype T = Int where Positive\n-- TODO\n";
2043 let out = fmt(src);
2044 assert!(out.contains("-- TODO"));
2045 assert_eq!(out, fmt(&out));
2046 }
2047
2048 #[test]
2049 fn unchanged_files_without_comments_format_identically() {
2050 let src = "commons x { type T = Int where NonNegative }";
2051 let out = fmt(src);
2052 assert!(!out.contains("--"), "unexpected comment in output: {out}");
2055 }
2056
2057 #[test]
2060 fn formats_store_field_and_cell_write() {
2061 let src = "context shop {\nagent Counter {\nkey id: String\nstore count: Cell[Int] = 0\non call bump() -> Effect[()] {\ncount := count + 1\n()\n}\n}\n}";
2062 let out = fmt(src);
2063 assert!(
2064 out.contains("store count: Cell[Int] = 0"),
2065 "store field not formatted: {out}"
2066 );
2067 assert!(
2068 out.contains("count := count + 1"),
2069 "cell write not formatted: {out}"
2070 );
2071 assert_eq!(out, fmt(&out), "formatter not idempotent: {out}");
2072 }
2073
2074 #[test]
2075 fn formats_store_only_agent_without_state_block() {
2076 let src = "context shop {\nagent Counter {\nkey id: String\nstore count: Cell[Int] = 0\non call get() -> Effect[Int] {\ncount\n}\n}\n}";
2077 let out = fmt(src);
2078 assert!(!out.contains("state {"), "spurious state block: {out}");
2080 assert!(out.contains("store count: Cell[Int] = 0"), "{out}");
2081 assert_eq!(out, fmt(&out), "not idempotent: {out}");
2082 }
2083}