1use bynk_syntax::ast::*;
54use bynk_syntax::error::CompileError;
55use bynk_syntax::lexer::{Token, TokenKind, tokenize};
56use bynk_syntax::parser::{parse_units, parse_units_with_drain_check};
57use bynk_syntax::span::Span;
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
62pub enum IndentStyle {
63 #[default]
64 Tab,
65 Spaces(u8),
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct FormatOptions {
74 pub indent: IndentStyle,
75 pub max_line_width: u32,
76 pub trailing_comma: bool,
77}
78
79impl Default for FormatOptions {
80 fn default() -> Self {
81 Self {
82 indent: IndentStyle::Tab,
83 max_line_width: 100,
84 trailing_comma: true,
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
92pub struct FormatError {
93 pub errors: Vec<CompileError>,
94}
95
96pub fn format_source(source: &str, opts: &FormatOptions) -> Result<String, FormatError> {
101 let source = &*normalize_line_endings(source);
106 let tokens = tokenize(source).map_err(|e| FormatError { errors: vec![e] })?;
107 let (units, _warnings, _) =
113 parse_units_with_drain_check(&tokens, source).map_err(|errors| FormatError { errors })?;
114 let output = render_units(&units, opts);
115 if let Some(error) = comment_loss(source, &tokens, &output) {
127 return Err(FormatError {
128 errors: vec![error],
129 });
130 }
131 if let Some(error) = doc_block_loss(source, &tokens, &output) {
138 return Err(FormatError {
139 errors: vec![error],
140 });
141 }
142 if let Some(error) = roundtrip_divergence(&tokens, source, &output, opts) {
151 return Err(FormatError {
152 errors: vec![error],
153 });
154 }
155 Ok(output)
156}
157
158pub fn normalize_line_endings(source: &str) -> std::borrow::Cow<'_, str> {
170 if !source.contains("\r\n") {
171 return std::borrow::Cow::Borrowed(source);
172 }
173 let mut out = String::with_capacity(source.len());
174 let mut pending_crs = 0usize;
175 for c in source.chars() {
176 match c {
177 '\r' => pending_crs += 1,
178 '\n' => {
179 pending_crs = 0;
180 out.push('\n');
181 }
182 _ => {
183 out.extend(std::iter::repeat_n('\r', pending_crs));
184 pending_crs = 0;
185 out.push(c);
186 }
187 }
188 }
189 out.extend(std::iter::repeat_n('\r', pending_crs));
190 std::borrow::Cow::Owned(out)
191}
192
193fn render_units(units: &[SourceUnit], opts: &FormatOptions) -> String {
199 let parts: Vec<String> = units
200 .iter()
201 .map(|unit| {
202 let mut f = Formatter::new(opts);
203 f.format_unit(unit);
204 f.finish()
205 })
206 .collect();
207 parts.join("\n")
208}
209
210fn code_only_canonical(source: &str, opts: &FormatOptions) -> Result<String, Vec<CompileError>> {
217 let tokens = tokenize(source).map_err(|e| vec![e])?;
218 code_only_canonical_from_tokens(&tokens, source, opts)
219}
220
221fn code_only_canonical_from_tokens(
226 tokens: &[Token],
227 source: &str,
228 opts: &FormatOptions,
229) -> Result<String, Vec<CompileError>> {
230 let code: Vec<Token> = tokens
231 .iter()
232 .filter(|t| t.kind != TokenKind::Comment)
233 .cloned()
234 .collect();
235 let units = parse_units(&code, source)?;
236 Ok(render_units(&units, opts))
237}
238
239fn roundtrip_divergence(
254 tokens: &[Token],
255 source: &str,
256 output: &str,
257 opts: &FormatOptions,
258) -> Option<CompileError> {
259 let canon_out = match code_only_canonical(output, opts) {
262 Ok(canon) => canon,
263 Err(_) => {
264 return Some(roundtrip_error(
265 "the formatter produced output that no longer parses",
266 ));
267 }
268 };
269 let canon_in = code_only_canonical_from_tokens(tokens, source, opts).ok()?;
275 (canon_in != canon_out)
276 .then(|| roundtrip_error("the formatter's output does not round-trip to the same code"))
277}
278
279fn roundtrip_error(what: &str) -> CompileError {
290 CompileError {
291 category: "bynk.fmt.roundtrip",
292 span: Span::default(),
293 message: format!("{what} — the file was left unchanged"),
294 labels: Vec::new(),
295 notes: vec![
296 "this is a formatter bug, not a problem with your source; please report it \
297 with the file that triggered it"
298 .to_string(),
299 ],
300 suggestions: Vec::new(),
301 }
302}
303
304fn comment_loss(source: &str, tokens: &[Token], output: &str) -> Option<CompileError> {
311 use bynk_syntax::lexer::comment_body;
312 let in_comments: Vec<Span> = tokens
313 .iter()
314 .filter(|t| t.kind == TokenKind::Comment)
315 .map(|t| t.span)
316 .collect();
317 if in_comments.is_empty() {
318 return None;
319 }
320 let mut out_bodies: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
323 if let Ok(out_tokens) = tokenize(output) {
324 for t in &out_tokens {
325 if t.kind == TokenKind::Comment {
326 *out_bodies
327 .entry(comment_body(output, t.span).trim().to_string())
328 .or_insert(0) += 1;
329 }
330 }
331 }
332 let mut lost = 0usize;
333 let mut first_lost: Option<Span> = None;
334 for span in &in_comments {
335 let body = comment_body(source, *span).trim().to_string();
336 match out_bodies.get_mut(&body) {
337 Some(n) if *n > 0 => *n -= 1,
338 _ => {
339 lost += 1;
340 first_lost.get_or_insert(*span);
341 }
342 }
343 }
344 let span = first_lost?;
345 Some(CompileError {
346 category: "bynk.fmt.comment_loss",
347 span,
348 message: format!(
349 "formatting would lose {lost} comment{} — the file was left unchanged",
350 if lost == 1 { "" } else { "s" }
351 ),
352 labels: vec![(
353 span,
354 "this comment sits where the formatter cannot yet re-attach it".to_string(),
355 )],
356 notes: vec![
357 "comments inside expression subtrees are not yet preserved; move the comment onto \
358 its own line before the enclosing statement to format this file"
359 .to_string(),
360 ],
361 suggestions: Vec::new(),
362 })
363}
364
365fn doc_block_loss(source: &str, tokens: &[Token], output: &str) -> Option<CompileError> {
375 use bynk_syntax::lexer::doc_block_content;
376 let in_docs: Vec<Span> = tokens
377 .iter()
378 .filter(|t| t.kind == TokenKind::DocBlock)
379 .map(|t| t.span)
380 .collect();
381 if in_docs.is_empty() {
382 return None;
383 }
384 let normalise = |content: String| {
387 content
388 .lines()
389 .map(str::trim)
390 .collect::<Vec<_>>()
391 .join("\n")
392 };
393 let out_tokens = tokenize(output).ok()?;
394 let mut out_docs: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
395 for t in &out_tokens {
396 if t.kind == TokenKind::DocBlock {
397 *out_docs
398 .entry(normalise(doc_block_content(output, t.span)))
399 .or_insert(0) += 1;
400 }
401 }
402 let mut lost = 0usize;
403 let mut first_lost: Option<Span> = None;
404 for span in &in_docs {
405 match out_docs.get_mut(&normalise(doc_block_content(source, *span))) {
406 Some(n) if *n > 0 => *n -= 1,
407 _ => {
408 lost += 1;
409 first_lost.get_or_insert(*span);
410 }
411 }
412 }
413 let span = first_lost?;
414 Some(CompileError {
415 category: "bynk.fmt.comment_loss",
416 span,
417 message: format!(
418 "formatting would lose {lost} documentation block{} — the file was left unchanged",
419 if lost == 1 { "" } else { "s" }
420 ),
421 labels: vec![(
422 span,
423 "this documentation block has no counterpart in the formatted output".to_string(),
424 )],
425 notes: vec![
426 "a `---` block attaches to the declaration directly below it; one separated from \
427 it by a blank line, or with no declaration after it, attaches to nothing. Remove \
428 the blank line to attach it, or make it a `--` comment if it documents nothing"
429 .to_string(),
430 "if the block is already directly above a declaration, this is a formatter bug; \
431 please report it with the file that triggered it"
432 .to_string(),
433 ],
434 suggestions: Vec::new(),
435 })
436}
437
438struct Formatter<'a> {
441 opts: &'a FormatOptions,
442 out: String,
443 indent_level: u32,
444 at_line_start: bool,
447}
448
449impl<'a> Formatter<'a> {
450 fn new(opts: &'a FormatOptions) -> Self {
451 Self {
452 opts,
453 out: String::new(),
454 indent_level: 0,
455 at_line_start: true,
456 }
457 }
458
459 fn finish(mut self) -> String {
460 while self.out.ends_with("\n\n") {
462 self.out.pop();
463 }
464 if !self.out.ends_with('\n') {
465 self.out.push('\n');
466 }
467 self.out
468 }
469
470 fn indent_unit(&self) -> String {
471 match self.opts.indent {
472 IndentStyle::Tab => "\t".to_string(),
473 IndentStyle::Spaces(n) => " ".repeat(n as usize),
474 }
475 }
476
477 fn emit_indent(&mut self) {
478 let unit = self.indent_unit();
479 for _ in 0..self.indent_level {
480 self.out.push_str(&unit);
481 }
482 }
483
484 fn push(&mut self, s: &str) {
485 if self.at_line_start && !s.starts_with('\n') {
486 self.emit_indent();
487 self.at_line_start = false;
488 }
489 if s.contains('\n') {
490 self.push_reindented(s);
491 } else {
492 self.out.push_str(s);
493 }
494 }
495
496 fn push_reindented(&mut self, s: &str) {
506 let prefix = self.indent_unit().repeat(self.indent_level as usize);
507 for (i, line) in s.split('\n').enumerate() {
508 if i > 0 {
509 self.out.push('\n');
510 if !line.is_empty() {
511 self.out.push_str(&prefix);
512 }
513 }
514 self.out.push_str(line);
515 }
516 }
517
518 fn newline(&mut self) {
519 self.out.push('\n');
520 self.at_line_start = true;
521 }
522
523 #[allow(dead_code)]
524 fn blank_line(&mut self) {
525 if !self.out.ends_with('\n') {
526 self.out.push('\n');
527 }
528 if !self.out.ends_with("\n\n") {
529 self.out.push('\n');
530 }
531 self.at_line_start = true;
532 }
533
534 fn indented<F: FnOnce(&mut Self)>(&mut self, f: F) {
535 self.indent_level += 1;
536 f(self);
537 self.indent_level -= 1;
538 }
539
540 fn try_layout<F: FnOnce(&mut Self)>(&mut self, reserve: usize, body: F) -> bool {
550 self.try_layout_if(reserve, body, || true)
551 }
552
553 fn try_layout_if<F: FnOnce(&mut Self), A: FnOnce() -> bool>(
557 &mut self,
558 reserve: usize,
559 body: F,
560 accept: A,
561 ) -> bool {
562 let line_start = self.out.rfind('\n').map_or(0, |i| i + 1);
563 let mut sub = Formatter {
564 opts: self.opts,
565 out: self.out[line_start..].to_string(),
566 indent_level: self.indent_level,
567 at_line_start: self.at_line_start,
568 };
569 let produced_from = sub.out.len();
570 body(&mut sub);
571 if !sub.every_line_within_budget(reserve) || !accept() {
572 return false;
573 }
574 self.out.push_str(&sub.out[produced_from..]);
577 self.at_line_start = sub.at_line_start;
578 true
579 }
580
581 fn every_line_within_budget(&self, reserve: usize) -> bool {
584 let tab = self.indent_width();
585 let mut lines = self.out.split('\n').peekable();
586 let mut last = "";
587 while let Some(line) = lines.next() {
588 if lines.peek().is_none() {
589 last = line;
590 break;
591 }
592 if display_width(line, tab) > self.opts.max_line_width as usize {
593 return false;
594 }
595 }
596 let last_width = if self.at_line_start {
597 self.indent_level as usize * tab
599 } else {
600 display_width(last, tab)
601 };
602 last_width + reserve <= self.opts.max_line_width as usize
603 }
604
605 fn emit_doc(&mut self, doc: &str) {
611 self.push("---");
612 self.newline();
613 for line in doc.lines() {
614 if line.is_empty() {
615 self.newline();
616 } else {
617 self.push(line);
618 self.newline();
619 }
620 }
621 self.push("---");
622 self.newline();
623 }
624
625 fn emit_leading_comments(&mut self, comments: &[Comment]) {
632 self.emit_comments(comments, true);
633 }
634
635 fn emit_trailing_comments(&mut self, comments: &[Comment]) {
639 self.emit_comments(comments, false);
640 }
641
642 fn emit_comments(&mut self, comments: &[Comment], blank_after_last_orphan: bool) {
643 for (i, comment) in comments.iter().enumerate() {
644 match comment {
645 Comment::Line(body) => {
646 self.push("--");
647 self.push(body);
648 self.newline();
649 }
650 Comment::OrphanDoc(doc) => {
651 self.emit_doc(doc);
652 if i + 1 < comments.len() || blank_after_last_orphan {
653 self.newline();
654 }
655 }
656 }
657 }
658 }
659
660 fn emit_trailing_comment(&mut self, body: Option<&str>) {
663 if let Some(body) = body {
664 while self.out.ends_with('\n') {
667 self.out.pop();
668 }
669 self.out.push_str(" --");
670 self.out.push_str(body);
671 self.newline();
672 }
673 }
674
675 fn emit_trailing_comment_or_newline(&mut self, trailing: Option<&str>) {
680 if trailing.is_some() {
681 self.emit_trailing_comment(trailing);
682 } else {
683 self.newline();
684 }
685 }
686
687 fn format_unit(&mut self, unit: &SourceUnit) {
690 match unit {
691 SourceUnit::Commons(c) => self.format_commons(c),
692 SourceUnit::Context(c) => self.format_context(c),
693 SourceUnit::Suite(t) => self.format_test(t),
694 SourceUnit::Adapter(a) => self.format_adapter(a),
695 }
696 }
697
698 fn format_adapter(&mut self, a: &AdapterDecl) {
699 self.emit_leading_comments(&a.trivia.leading);
700 if let Some(doc) = &a.documentation {
701 self.emit_doc(doc);
702 }
703 let header = format!("adapter {}", a.name.joined());
704 match a.form {
705 CommonsForm::Brace => {
706 self.push(&header);
707 self.push(" {");
708 self.newline();
709 self.indented(|f| {
710 f.format_adapter_body(a);
711 });
712 self.push("}");
713 self.newline();
714 }
715 CommonsForm::Fragment => {
716 self.push(&header);
717 self.newline();
718 self.newline();
719 self.format_adapter_body(a);
720 }
721 }
722 }
723
724 fn format_adapter_body(&mut self, a: &AdapterDecl) {
725 let mut any_header = false;
726 if let Some(b) = &a.binding {
727 self.emit_leading_comments(&b.trivia.leading);
728 self.push(&format!("binding {:?}", b.module));
729 if !b.requires.is_empty() {
730 let entries: Vec<String> = b
731 .requires
732 .iter()
733 .map(|r| format!("{:?}: {:?}", r.package, r.range))
734 .collect();
735 self.push(&format!(" requires {{ {} }}", entries.join(", ")));
736 }
737 self.emit_trailing_comment_or_newline(b.trivia.trailing.as_deref());
738 any_header = true;
739 }
740 for u in &a.uses {
741 self.emit_leading_comments(&u.trivia.leading);
742 self.push(&format!("uses {}", u.target.joined()));
743 self.emit_trailing_comment_or_newline(u.trivia.trailing.as_deref());
744 any_header = true;
745 }
746 for c in &a.consumes {
747 self.format_consumes(c);
748 any_header = true;
749 }
750 for e in &a.exports {
751 self.emit_leading_comments(&e.trivia.leading);
752 self.format_exports(e);
753 if e.trivia.trailing.is_some() {
754 self.emit_trailing_comment(e.trivia.trailing.as_deref());
755 }
756 any_header = true;
757 }
758 if any_header && !a.items.is_empty() {
759 self.newline();
760 }
761 let mut first = true;
762 for item in &a.items {
763 if !first {
764 self.newline();
765 }
766 self.format_item(item);
767 first = false;
768 }
769 if !a.trailing_comments.is_empty() {
770 if !a.items.is_empty() || any_header {
771 self.newline();
772 }
773 self.emit_trailing_comments(&a.trailing_comments);
774 }
775 }
776
777 fn format_test(&mut self, t: &SuiteDecl) {
778 self.emit_leading_comments(&t.trivia.leading);
779 if let Some(doc) = &t.documentation {
780 self.emit_doc(doc);
781 }
782 let mut header = format!("suite {}", t.target.joined());
783 if let Some(tier) = t.tier {
784 header.push_str(&format!(" as {}", tier.as_str()));
785 }
786 match t.form {
787 CommonsForm::Brace => {
788 self.push(&header);
789 self.push(" {");
790 self.newline();
791 self.indented(|f| {
792 f.format_test_body(
793 &t.uses,
794 &t.stubs,
795 &t.cases,
796 &t.properties,
797 &t.trailing_comments,
798 );
799 });
800 self.push("}");
801 self.newline();
802 }
803 CommonsForm::Fragment => {
804 self.push(&header);
805 self.newline();
806 self.format_test_body(
807 &t.uses,
808 &t.stubs,
809 &t.cases,
810 &t.properties,
811 &t.trailing_comments,
812 );
813 }
814 }
815 }
816
817 fn format_test_body(
818 &mut self,
819 uses: &[UsesDecl],
820 stubs: &[StubClause],
821 cases: &[Case],
822 properties: &[PropertyDecl],
823 trailing_comments: &[Comment],
824 ) {
825 let mut first = true;
826 for u in uses {
827 if !first {
828 self.newline();
829 }
830 self.emit_leading_comments(&u.trivia.leading);
831 self.push(&format!("uses {}", u.target.joined()));
832 self.emit_trailing_comment(u.trivia.trailing.as_deref());
833 self.newline();
834 first = false;
835 }
836 for pv in stubs {
837 if !first {
838 self.newline();
839 }
840 self.format_stub_clause(pv);
841 first = false;
842 }
843 for c in cases {
844 if !first {
845 self.newline();
846 }
847 self.emit_leading_comments(&c.trivia.leading);
848 if let Some(doc) = &c.documentation {
849 self.emit_doc(doc);
850 }
851 let mut ch = format!("case \"{}\"", escape_string(&c.name));
852 if let Some(tier) = c.tier {
853 ch.push_str(&format!(" as {}", tier.as_str()));
854 }
855 ch.push(' ');
856 self.push(&ch);
857 self.format_case_block(&c.body, &c.stubs);
858 self.newline();
859 first = false;
860 }
861 for p in properties {
862 if !first {
863 self.newline();
864 }
865 self.emit_leading_comments(&p.trivia.leading);
866 if let Some(doc) = &p.documentation {
867 self.emit_doc(doc);
868 }
869 self.push(&format!("property \"{}\" {{", escape_string(&p.name)));
870 self.newline();
871 self.indented(|f| f.format_for_all(&p.forall));
872 self.push("}");
873 self.newline();
874 first = false;
875 }
876 self.emit_trailing_comments(trailing_comments);
877 }
878
879 fn format_stub_clause(&mut self, pv: &StubClause) {
883 self.emit_leading_comments(&pv.trivia.leading);
884 if let Some(doc) = &pv.documentation {
885 self.emit_doc(doc);
886 }
887 self.push(&stub_clause_to_string(pv));
888 self.emit_trailing_comment_or_newline(pv.trivia.trailing.as_deref());
889 }
890
891 fn format_case_block(&mut self, b: &Block, stubs: &[StubClause]) {
895 if stubs.is_empty() {
896 self.format_block(b);
897 return;
898 }
899 self.push("{");
900 self.newline();
901 self.indented(|f| {
902 for pv in stubs {
903 f.format_stub_clause(pv);
904 }
905 for stmt in &b.statements {
906 let trivia = statement_trivia(stmt);
907 f.emit_leading_comments(&trivia.leading);
908 f.format_statement(stmt);
909 f.emit_trailing_comment_or_newline(trivia.trailing.as_deref());
910 }
911 f.emit_leading_comments(&b.tail_leading_comments);
912 if !omit_unit_tail(b) {
915 f.format_expr(&b.tail);
916 f.newline();
917 }
918 });
919 self.push("}");
920 }
921
922 fn format_for_all(&mut self, fa: &ForAll) {
925 let bindings = fa
926 .bindings
927 .iter()
928 .map(|b| format!("{}: {}", b.name.name, type_ref_to_string(&b.type_ref)))
929 .collect::<Vec<_>>()
930 .join(", ");
931 let mut header = format!("for all {bindings}");
932 if let Some(w) = &fa.where_pred {
933 header.push_str(&format!(" where {}", expr_to_string(w)));
934 }
935 self.push(&format!("{header} "));
936 self.format_block(&fa.body);
937 self.newline();
938 }
939
940 fn format_commons(&mut self, c: &Commons) {
941 self.emit_leading_comments(&c.trivia.leading);
942 if let Some(doc) = &c.documentation {
943 self.emit_doc(doc);
944 }
945 let header = format!("commons {}", c.name.joined());
946 match c.form {
947 CommonsForm::Brace => {
948 self.push(&header);
949 self.push(" {");
950 self.newline();
951 self.indented(|f| {
952 f.format_commons_body(&c.uses, &c.items, &c.trailing_comments);
953 });
954 self.push("}");
955 self.newline();
956 }
957 CommonsForm::Fragment => {
958 self.push(&header);
959 self.newline();
960 self.newline();
961 self.format_commons_body(&c.uses, &c.items, &c.trailing_comments);
962 }
963 }
964 }
965
966 fn format_commons_body(
967 &mut self,
968 uses: &[UsesDecl],
969 items: &[CommonsItem],
970 trailing_comments: &[Comment],
971 ) {
972 let mut any_uses = false;
973 for u in uses {
974 self.emit_leading_comments(&u.trivia.leading);
975 self.push(&format!("uses {}", u.target.joined()));
976 self.emit_trailing_comment_or_newline(u.trivia.trailing.as_deref());
977 any_uses = true;
978 }
979 if any_uses && !items.is_empty() {
980 self.newline();
981 }
982 let mut first = true;
983 for item in items {
984 if !first {
985 self.newline();
986 }
987 self.format_item(item);
988 first = false;
989 }
990 if !trailing_comments.is_empty() {
991 if !items.is_empty() || any_uses {
994 self.newline();
995 }
996 self.emit_trailing_comments(trailing_comments);
997 }
998 }
999
1000 fn format_context(&mut self, c: &Context) {
1001 self.emit_leading_comments(&c.trivia.leading);
1002 if let Some(doc) = &c.documentation {
1003 self.emit_doc(doc);
1004 }
1005 let header = format!("context {}", c.name.joined());
1006 match c.form {
1007 CommonsForm::Brace => {
1008 self.push(&header);
1009 self.push(" {");
1010 self.newline();
1011 self.indented(|f| {
1012 f.format_context_body(
1013 &c.uses,
1014 &c.consumes,
1015 &c.exports,
1016 &c.items,
1017 &c.trailing_comments,
1018 );
1019 });
1020 self.push("}");
1021 self.newline();
1022 }
1023 CommonsForm::Fragment => {
1024 self.push(&header);
1025 self.newline();
1026 self.newline();
1027 self.format_context_body(
1028 &c.uses,
1029 &c.consumes,
1030 &c.exports,
1031 &c.items,
1032 &c.trailing_comments,
1033 );
1034 }
1035 }
1036 }
1037
1038 fn format_consumes(&mut self, c: &ConsumesDecl) {
1042 self.emit_leading_comments(&c.trivia.leading);
1043 match (&c.alias, &c.selected) {
1044 (Some(alias), _) => {
1045 self.push(&format!("consumes {} as {}", c.target.joined(), alias.name))
1046 }
1047 (None, Some(selected)) if selected.is_empty() => {
1048 self.push(&format!("consumes {} {{ }}", c.target.joined()));
1049 }
1050 (None, Some(selected)) => {
1051 let names: Vec<&str> = selected.iter().map(|i| i.name.as_str()).collect();
1052 self.push(&format!(
1053 "consumes {} {{ {} }}",
1054 c.target.joined(),
1055 names.join(", ")
1056 ));
1057 }
1058 (None, None) => self.push(&format!("consumes {}", c.target.joined())),
1059 }
1060 self.emit_trailing_comment_or_newline(c.trivia.trailing.as_deref());
1061 }
1062
1063 fn format_context_body(
1064 &mut self,
1065 uses: &[UsesDecl],
1066 consumes: &[ConsumesDecl],
1067 exports: &[ExportsDecl],
1068 items: &[CommonsItem],
1069 trailing_comments: &[Comment],
1070 ) {
1071 let mut any_header = false;
1072 for u in uses {
1073 self.emit_leading_comments(&u.trivia.leading);
1074 self.push(&format!("uses {}", u.target.joined()));
1075 self.emit_trailing_comment_or_newline(u.trivia.trailing.as_deref());
1076 any_header = true;
1077 }
1078 for c in consumes {
1079 self.format_consumes(c);
1080 any_header = true;
1081 }
1082 for e in exports {
1083 self.emit_leading_comments(&e.trivia.leading);
1084 self.format_exports(e);
1085 if e.trivia.trailing.is_some() {
1089 self.emit_trailing_comment(e.trivia.trailing.as_deref());
1090 }
1091 any_header = true;
1092 }
1093 if any_header && !items.is_empty() {
1094 self.newline();
1095 }
1096 let mut first = true;
1097 for item in items {
1098 if !first {
1099 self.newline();
1100 }
1101 self.format_item(item);
1102 first = false;
1103 }
1104 if !trailing_comments.is_empty() {
1105 if !items.is_empty() || any_header {
1106 self.newline();
1107 }
1108 self.emit_trailing_comments(trailing_comments);
1109 }
1110 }
1111
1112 fn format_exports(&mut self, e: &ExportsDecl) {
1113 let vis = match e.kind {
1114 ExportKind::Type(Visibility::Opaque) => "opaque",
1115 ExportKind::Type(Visibility::Transparent) => "transparent",
1116 ExportKind::Capability => "capability",
1117 };
1118 let has_comments = !e.trailing_comments.is_empty()
1120 || e.names
1121 .iter()
1122 .any(|n| !n.trivia.leading.is_empty() || n.trivia.trailing.is_some());
1123 if e.names.is_empty() && !has_comments {
1124 self.push(&format!("exports {} {{}}", vis));
1125 self.newline();
1126 return;
1127 }
1128 if !has_comments {
1130 let oneline = format!(
1131 "exports {} {{ {} }}",
1132 vis,
1133 e.names
1134 .iter()
1135 .map(|n| n.name.name.as_str())
1136 .collect::<Vec<_>>()
1137 .join(", ")
1138 );
1139 if self.fits(&oneline, 0) {
1140 self.push(&oneline);
1141 self.newline();
1142 return;
1143 }
1144 }
1145 self.push(&format!("exports {} {{", vis));
1147 self.newline();
1148 self.indented(|f| {
1149 for (i, n) in e.names.iter().enumerate() {
1150 f.emit_leading_comments(&n.trivia.leading);
1151 f.push(&n.name.name);
1152 if i + 1 < e.names.len() || f.opts.trailing_comma {
1153 f.push(",");
1154 }
1155 f.emit_trailing_comment(n.trivia.trailing.as_deref());
1156 if n.trivia.trailing.is_none() {
1157 f.newline();
1158 }
1159 }
1160 f.emit_trailing_comments(&e.trailing_comments);
1161 });
1162 self.push("}");
1163 self.newline();
1164 }
1165
1166 fn indent_width(&self) -> usize {
1170 match self.opts.indent {
1171 IndentStyle::Tab => 4,
1172 IndentStyle::Spaces(n) => n as usize,
1173 }
1174 }
1175
1176 fn current_column(&self) -> usize {
1182 if self.at_line_start {
1183 return self.indent_level as usize * self.indent_width();
1185 }
1186 let line = match self.out.rfind('\n') {
1187 Some(i) => &self.out[i + 1..],
1188 None => self.out.as_str(),
1189 };
1190 display_width(line, self.indent_width())
1191 }
1192
1193 fn fits(&self, candidate: &str, reserve: usize) -> bool {
1199 if candidate.contains('\n') {
1200 return false;
1201 }
1202 let column =
1203 self.current_column() + display_width(candidate, self.indent_width()) + reserve;
1204 column <= self.opts.max_line_width as usize
1205 }
1206
1207 fn format_item(&mut self, item: &CommonsItem) {
1208 match item {
1209 CommonsItem::Type(t) => self.format_type_decl(t),
1210 CommonsItem::Fn(f) => self.format_fn_decl(f),
1211 CommonsItem::Capability(c) => self.format_capability(c),
1212 CommonsItem::Provider(p) => self.format_provider(p),
1213 CommonsItem::Service(s) => self.format_service(s),
1214 CommonsItem::Agent(a) => self.format_agent(a),
1215 CommonsItem::Actor(a) => self.format_actor(a),
1216 CommonsItem::Messages(m) => self.format_messages(m),
1217 CommonsItem::Event(e) => self.format_event_decl(e),
1218 }
1219 }
1220
1221 fn format_event_decl(&mut self, e: &EventDecl) {
1222 self.emit_leading_comments(&e.trivia.leading);
1223 if let Some(doc) = &e.documentation {
1224 self.emit_doc(doc);
1225 }
1226 self.push(&format!("event {}", e.name.name));
1227 for ann in &e.annotations {
1230 self.push(" ");
1231 self.push(&annotation_to_string(ann));
1232 }
1233 self.push(" = ");
1234 self.format_record_body(&e.body);
1235 self.emit_trailing_comment_or_newline(e.trivia.trailing.as_deref());
1236 }
1237
1238 fn format_messages(&mut self, m: &MessagesDecl) {
1239 self.emit_leading_comments(&m.trivia.leading);
1240 if let Some(doc) = &m.documentation {
1241 self.emit_doc(doc);
1242 }
1243 self.push(&format!("messages \"{}\"", escape_string(&m.tag)));
1244 for ann in &m.annotations {
1245 self.push(" ");
1246 self.push(&annotation_to_string(ann));
1247 }
1248 self.push(" {");
1249 self.newline();
1250 self.indented(|f| {
1251 for entry in &m.entries {
1252 f.push(&format!(
1253 "\"{}\" => \"{}\"",
1254 escape_string(&entry.code),
1255 escape_string(&entry.template)
1256 ));
1257 f.newline();
1258 }
1259 });
1260 self.push("}");
1261 self.emit_trailing_comment_or_newline(m.trivia.trailing.as_deref());
1262 }
1263
1264 fn format_type_decl(&mut self, t: &TypeDecl) {
1267 self.emit_leading_comments(&t.trivia.leading);
1268 if let Some(doc) = &t.documentation {
1269 self.emit_doc(doc);
1270 }
1271 let params = if t.type_params.is_empty() {
1273 String::new()
1274 } else {
1275 let names: Vec<&str> = t
1276 .type_params
1277 .iter()
1278 .map(|tp| tp.name.name.as_str())
1279 .collect();
1280 format!("[{}]", names.join(", "))
1281 };
1282 self.push(&format!("type {}{} = ", t.name.name, params));
1283 self.format_type_body(&t.body);
1284 self.emit_trailing_comment_or_newline(t.trivia.trailing.as_deref());
1285 }
1286
1287 fn format_type_body(&mut self, body: &TypeBody) {
1288 match body {
1289 TypeBody::Refined {
1290 base, refinement, ..
1291 } => {
1292 self.push(base.name());
1293 if let Some(r) = refinement {
1294 self.push(" where ");
1295 self.format_refinement(r);
1296 }
1297 }
1298 TypeBody::Opaque {
1299 base, refinement, ..
1300 } => {
1301 self.push("opaque ");
1302 self.push(base.name());
1303 if let Some(r) = refinement {
1304 self.push(" where ");
1305 self.format_refinement(r);
1306 }
1307 }
1308 TypeBody::Record(r) => self.format_record_body(r),
1309 TypeBody::Sum(s) => self.format_sum_body(s),
1310 }
1311 }
1312
1313 fn format_refinement(&mut self, r: &Refinement) {
1314 for (i, p) in r.predicates.iter().enumerate() {
1315 if i > 0 {
1316 self.push(" && ");
1317 }
1318 self.format_pred(p);
1319 }
1320 }
1321
1322 fn format_pred(&mut self, p: &RefinementPred) {
1323 match &p.kind {
1324 PredKind::Matches(re) => self.push(&format!("Matches(\"{}\")", escape_string(re))),
1325 PredKind::InRange(a, b) => self.push(&format!("InRange({}, {})", a.value, b.value)),
1326 PredKind::InRangeF(a, b) => self.push(&format!("InRange({}, {})", a.lexeme, b.lexeme)),
1327 PredKind::MinLength(n) => self.push(&format!("MinLength({n})")),
1328 PredKind::MaxLength(n) => self.push(&format!("MaxLength({n})")),
1329 PredKind::Length(n) => self.push(&format!("Length({n})")),
1330 PredKind::NonNegative => self.push("NonNegative"),
1331 PredKind::Positive => self.push("Positive"),
1332 PredKind::NonEmpty => self.push("NonEmpty"),
1333 }
1334 }
1335
1336 fn format_record_body(&mut self, r: &RecordBody) {
1337 let has_comments = !r.trailing_comments.is_empty()
1338 || r.fields
1339 .iter()
1340 .any(|f| !f.trivia.leading.is_empty() || f.trivia.trailing.is_some());
1341 if r.fields.is_empty() && !has_comments {
1342 self.push("{}");
1343 return;
1344 }
1345 if !has_comments {
1347 let oneline_fields: Vec<String> = r
1348 .fields
1349 .iter()
1350 .map(|f| self.format_record_field_oneline(f))
1351 .collect();
1352 let oneline = format!("{{ {} }}", oneline_fields.join(", "));
1353 if self.fits(&oneline, 0) {
1354 self.push(&oneline);
1355 return;
1356 }
1357 }
1358 self.push("{");
1360 self.newline();
1361 self.indented(|f| {
1362 for (i, field) in r.fields.iter().enumerate() {
1363 f.emit_leading_comments(&field.trivia.leading);
1364 f.format_record_field(field);
1365 if i + 1 < r.fields.len() || f.opts.trailing_comma {
1366 f.push(",");
1367 }
1368 f.emit_trailing_comment_or_newline(field.trivia.trailing.as_deref());
1369 }
1370 f.emit_trailing_comments(&r.trailing_comments);
1371 });
1372 self.push("}");
1373 }
1374
1375 fn format_record_field(&mut self, field: &RecordField) {
1376 self.push(&format!("{}: ", field.name.name));
1377 self.format_type_ref(&field.type_ref);
1378 if let Some(r) = &field.refinement {
1379 self.push(" where ");
1380 self.format_refinement(r);
1381 }
1382 if let Some(init) = &field.init {
1383 self.push(" = ");
1384 self.format_expr(init);
1385 }
1386 }
1387
1388 fn format_record_field_oneline(&self, field: &RecordField) -> String {
1389 let mut out = format!("{}: ", field.name.name);
1390 out.push_str(&type_ref_to_string(&field.type_ref));
1391 if let Some(r) = &field.refinement {
1392 out.push_str(" where ");
1393 out.push_str(&refinement_to_string(r));
1394 }
1395 if let Some(init) = &field.init {
1396 out.push_str(" = ");
1397 out.push_str(&expr_to_string(init));
1398 }
1399 out
1400 }
1401
1402 fn format_sum_body(&mut self, s: &SumBody) {
1403 let any_payload = s.variants.iter().any(|v| !v.payload.is_empty());
1407 if !any_payload {
1408 let has_comments = !s.trailing_comments.is_empty()
1411 || s.variants
1412 .iter()
1413 .any(|v| !v.trivia.leading.is_empty() || v.trivia.trailing.is_some());
1414 if !has_comments {
1415 let names: Vec<&str> = s.variants.iter().map(|v| v.name.name.as_str()).collect();
1416 let oneline = format!("enum {{ {} }}", names.join(", "));
1417 if self.fits(&oneline, 0) {
1418 self.push(&oneline);
1419 return;
1420 }
1421 }
1422 self.push("enum {");
1423 self.newline();
1424 self.indented(|f| {
1425 for (i, v) in s.variants.iter().enumerate() {
1426 f.emit_leading_comments(&v.trivia.leading);
1427 f.push(&v.name.name);
1428 if i + 1 < s.variants.len() || f.opts.trailing_comma {
1429 f.push(",");
1430 }
1431 f.emit_trailing_comment(v.trivia.trailing.as_deref());
1432 if v.trivia.trailing.is_none() {
1433 f.newline();
1434 }
1435 }
1436 f.emit_trailing_comments(&s.trailing_comments);
1437 });
1438 self.push("}");
1439 return;
1440 }
1441 for (i, v) in s.variants.iter().enumerate() {
1444 if i == 0 && !v.trivia.leading.is_empty() {
1445 while self.out.ends_with(' ') {
1448 self.out.pop();
1449 }
1450 self.newline();
1451 } else if i > 0 && s.variants[i - 1].trivia.trailing.is_none() {
1452 self.newline();
1453 }
1454 self.emit_leading_comments(&v.trivia.leading);
1455 self.push("| ");
1456 self.push(&v.name.name);
1457 if !v.payload.is_empty() {
1458 self.push("(");
1459 let parts: Vec<String> = v
1460 .payload
1461 .iter()
1462 .map(|p| format!("{}: {}", p.name.name, type_ref_to_string(&p.type_ref)))
1463 .collect();
1464 self.push(&parts.join(", "));
1465 self.push(")");
1466 }
1467 self.emit_trailing_comment(v.trivia.trailing.as_deref());
1468 }
1469 if !s.embeds.is_empty() {
1473 if s.variants
1474 .last()
1475 .is_none_or(|v| v.trivia.trailing.is_none())
1476 {
1477 self.newline();
1478 }
1479 self.emit_trailing_comments(&s.trailing_comments);
1480 let parts: Vec<String> = s
1481 .embeds
1482 .iter()
1483 .map(|e| {
1484 format!(
1485 "{} as {}",
1486 type_ref_to_string(&e.source_type),
1487 e.variant.name
1488 )
1489 })
1490 .collect();
1491 self.push(&format!("embeds {}", parts.join(", ")));
1492 }
1493 }
1494
1495 fn format_type_ref(&mut self, t: &TypeRef) {
1496 self.push(&type_ref_to_string(t));
1497 }
1498
1499 fn format_fn_decl(&mut self, f: &FnDecl) {
1502 self.emit_leading_comments(&f.trivia.leading);
1503 if let Some(doc) = &f.documentation {
1504 self.emit_doc(doc);
1505 }
1506 self.push("fn ");
1507 self.push(&f.name.display());
1508 if !f.type_params.is_empty() {
1510 let names: Vec<&str> = f
1511 .type_params
1512 .iter()
1513 .map(|tp| tp.name.name.as_str())
1514 .collect();
1515 self.push(&format!("[{}]", names.join(", ")));
1516 }
1517 let tail = format!(" -> {}", type_ref_to_string(&f.return_type));
1521 let reserve = if f.requires.is_empty() && f.ensures.is_empty() {
1522 tail.chars().count() + " {".len()
1523 } else {
1524 tail.chars().count()
1525 };
1526 self.format_params(&f.params, f.has_self, reserve);
1527 self.push(" -> ");
1528 self.format_type_ref(&f.return_type);
1529 if f.requires.is_empty() && f.ensures.is_empty() {
1532 self.push(" ");
1533 } else {
1534 self.newline();
1535 self.indented(|f2| {
1536 for c in &f.requires {
1537 f2.push(&format!(
1538 "requires {}: {}",
1539 c.name.name,
1540 expr_to_string(&c.predicate)
1541 ));
1542 f2.newline();
1543 }
1544 for c in &f.ensures {
1545 f2.push(&format!(
1546 "ensures {}: {}",
1547 c.name.name,
1548 expr_to_string(&c.predicate)
1549 ));
1550 f2.newline();
1551 }
1552 });
1553 }
1554 self.format_block(&f.body);
1555 self.emit_trailing_comment_or_newline(f.trivia.trailing.as_deref());
1556 }
1557
1558 fn format_params(&mut self, params: &[Param], has_self: bool, reserve: usize) {
1563 let mut rendered: Vec<String> = Vec::new();
1564 if has_self {
1565 rendered.push("self".to_string());
1566 }
1567 for p in params {
1570 rendered.push(format!(
1571 "{}: {}",
1572 p.name.name,
1573 type_ref_to_string(&p.type_ref)
1574 ));
1575 }
1576 let oneline = format!("({})", rendered.join(", "));
1577 if rendered.is_empty() || self.fits(&oneline, reserve) {
1579 self.push(&oneline);
1580 return;
1581 }
1582 let closing_line = self.indent_level as usize * self.indent_width() + 1 + reserve;
1586 if closing_line > self.opts.max_line_width as usize {
1587 self.push(&oneline);
1588 return;
1589 }
1590 self.push("(");
1592 self.newline();
1593 self.indented(|f| {
1594 for (i, r) in rendered.iter().enumerate() {
1595 f.push(r);
1596 if i + 1 < rendered.len() {
1602 f.push(",");
1603 }
1604 f.newline();
1605 }
1606 });
1607 self.push(")");
1608 }
1609
1610 fn format_capability(&mut self, c: &CapabilityDecl) {
1613 self.emit_leading_comments(&c.trivia.leading);
1614 if let Some(doc) = &c.documentation {
1615 self.emit_doc(doc);
1616 }
1617 self.push(&format!("capability {} {{", c.name.name));
1618 self.newline();
1619 self.indented(|f| {
1620 for op in &c.ops {
1621 f.emit_leading_comments(&op.trivia.leading);
1622 if let Some(doc) = &op.documentation {
1623 f.emit_doc(doc);
1624 }
1625 f.push("fn ");
1626 f.push(&op.name.name);
1627 if !op.type_params.is_empty() {
1629 let names: Vec<&str> = op
1630 .type_params
1631 .iter()
1632 .map(|tp| tp.name.name.as_str())
1633 .collect();
1634 f.push(&format!("[{}]", names.join(", ")));
1635 }
1636 let reserve = 4 + type_ref_to_string(&op.return_type).chars().count();
1637 f.format_params(&op.params, false, reserve);
1638 f.push(" -> ");
1639 f.format_type_ref(&op.return_type);
1640 f.emit_trailing_comment_or_newline(op.trivia.trailing.as_deref());
1641 }
1642 if !c.trailing_comments.is_empty() {
1644 if !c.ops.is_empty() {
1645 f.newline();
1646 }
1647 f.emit_trailing_comments(&c.trailing_comments);
1648 }
1649 });
1650 self.push("}");
1651 self.emit_trailing_comment_or_newline(c.trivia.trailing.as_deref());
1652 }
1653
1654 fn format_provider(&mut self, p: &ProviderDecl) {
1655 self.emit_leading_comments(&p.trivia.leading);
1656 if let Some(doc) = &p.documentation {
1657 self.emit_doc(doc);
1658 }
1659 self.push(&format!(
1660 "provides {} = {}",
1661 p.capability.name, p.provider_name.name
1662 ));
1663 if !p.given.is_empty() {
1664 self.push(" given ");
1665 let names: Vec<String> = p.given.iter().map(cap_ref_src).collect();
1666 self.push(&names.join(", "));
1667 }
1668 if p.external {
1670 self.emit_trailing_comment_or_newline(p.trivia.trailing.as_deref());
1671 return;
1672 }
1673 self.push(" {");
1674 self.newline();
1675 self.indented(|f| {
1676 for (i, op) in p.ops.iter().enumerate() {
1677 if i > 0 {
1678 f.newline();
1679 }
1680 f.emit_leading_comments(&op.trivia.leading);
1681 f.push("fn ");
1682 f.push(&op.name.name);
1683 let reserve = 4 + type_ref_to_string(&op.return_type).chars().count() + 2;
1684 f.format_params(&op.params, false, reserve);
1685 f.push(" -> ");
1686 f.format_type_ref(&op.return_type);
1687 f.push(" ");
1688 f.format_block(&op.body);
1689 f.emit_trailing_comment_or_newline(op.trivia.trailing.as_deref());
1690 }
1691 });
1692 self.push("}");
1693 self.emit_trailing_comment_or_newline(p.trivia.trailing.as_deref());
1694 }
1695
1696 fn format_service(&mut self, s: &ServiceDecl) {
1697 self.emit_leading_comments(&s.trivia.leading);
1698 if let Some(doc) = &s.documentation {
1699 self.emit_doc(doc);
1700 }
1701 let from = match &s.protocol {
1702 ServiceProtocol::Call => String::new(),
1703 ServiceProtocol::Http => " from http".to_string(),
1704 ServiceProtocol::Cron => " from cron".to_string(),
1705 ServiceProtocol::Queue { name } => {
1706 format!(" from queue(\"{}\")", escape_string(name))
1707 }
1708 ServiceProtocol::WebSocket { in_type, out_type } => {
1709 format!(
1710 " from websocket(in: {}, out: {})",
1711 type_ref_to_string(in_type),
1712 type_ref_to_string(out_type)
1713 )
1714 }
1715 ServiceProtocol::Events {
1716 event_type,
1717 pattern,
1718 schema_dispatch,
1719 } => {
1720 let header = match pattern {
1721 Some(p) => format!(
1722 " from Events({} {})",
1723 type_ref_to_string(event_type),
1724 event_pattern_src(p)
1725 ),
1726 None => format!(" from Events({})", type_ref_to_string(event_type)),
1727 };
1728 match schema_dispatch {
1729 Some(d) => format!("{header} {}", schema_dispatch_src(d)),
1730 None => header,
1731 }
1732 }
1733 };
1734 let mut header = format!("service {}{}", s.name.name, from);
1738 if let Some(by) = &s.default_by {
1739 header.push_str(&format!(" {}", by_clause_src(by)));
1740 }
1741 if !s.default_given.is_empty() {
1742 let names: Vec<String> = s.default_given.iter().map(cap_ref_src).collect();
1743 header.push_str(&format!(" given {}", names.join(", ")));
1744 }
1745 self.push(&format!("{header} {{"));
1746 self.newline();
1747 self.indented(|f| {
1748 if let Some(cors) = &s.cors {
1753 f.format_cors_policy(cors);
1754 if s.security.is_some() || s.limits.is_some() || !s.handlers.is_empty() {
1755 f.newline();
1756 }
1757 }
1758 if let Some(security) = &s.security {
1759 f.format_security_policy(security);
1760 if s.limits.is_some() || !s.handlers.is_empty() {
1761 f.newline();
1762 }
1763 }
1764 if let Some(limits) = &s.limits {
1765 f.format_limits_policy(limits);
1766 if !s.handlers.is_empty() {
1767 f.newline();
1768 }
1769 }
1770 for (i, h) in s.handlers.iter().enumerate() {
1771 if i > 0 {
1772 f.newline();
1773 }
1774 f.format_handler(h);
1775 }
1776 if !s.trailing_comments.is_empty() {
1778 if !s.handlers.is_empty()
1779 || s.cors.is_some()
1780 || s.security.is_some()
1781 || s.limits.is_some()
1782 {
1783 f.newline();
1784 }
1785 f.emit_trailing_comments(&s.trailing_comments);
1786 }
1787 });
1788 self.push("}");
1789 self.emit_trailing_comment_or_newline(s.trivia.trailing.as_deref());
1790 }
1791
1792 fn format_cors_policy(&mut self, cors: &CorsPolicy) {
1795 let fields: Vec<_> = cors
1796 .fields
1797 .iter()
1798 .map(|f| (&f.name, &f.value, &f.trivia))
1799 .collect();
1800 self.format_policy("cors", &cors.trivia, &fields, &cors.trailing_comments);
1801 }
1802
1803 fn format_security_policy(&mut self, security: &SecurityPolicy) {
1805 let fields: Vec<_> = security
1806 .fields
1807 .iter()
1808 .map(|f| (&f.name, &f.value, &f.trivia))
1809 .collect();
1810 self.format_policy(
1811 "security",
1812 &security.trivia,
1813 &fields,
1814 &security.trailing_comments,
1815 );
1816 }
1817
1818 fn format_limits_policy(&mut self, limits: &LimitsPolicy) {
1822 let fields: Vec<_> = limits
1823 .fields
1824 .iter()
1825 .map(|f| (&f.name, &f.value, &f.trivia))
1826 .collect();
1827 self.format_policy("limits", &limits.trivia, &fields, &limits.trailing_comments);
1828 }
1829
1830 fn format_policy(
1834 &mut self,
1835 keyword: &str,
1836 trivia: &Trivia,
1837 fields: &[(&Ident, &Expr, &Trivia)],
1838 trailing_comments: &[Comment],
1839 ) {
1840 self.emit_leading_comments(&trivia.leading);
1841 self.push(&format!("{keyword} {{"));
1842 self.newline();
1843 self.indented(|f| {
1844 for (name, value, field_trivia) in fields {
1845 f.emit_leading_comments(&field_trivia.leading);
1846 f.push(&format!("{}: ", name.name));
1847 f.format_expr_at(value, 0, 1);
1848 f.push(",");
1849 f.emit_trailing_comment_or_newline(field_trivia.trailing.as_deref());
1850 }
1851 f.emit_trailing_comments(trailing_comments);
1852 });
1853 self.push("}");
1854 self.emit_trailing_comment_or_newline(trivia.trailing.as_deref());
1855 }
1856
1857 fn format_agent(&mut self, a: &AgentDecl) {
1858 self.emit_leading_comments(&a.trivia.leading);
1859 if let Some(doc) = &a.documentation {
1860 self.emit_doc(doc);
1861 }
1862 self.push(&format!("agent {} {{", a.name.name));
1863 self.newline();
1864 self.indented(|f| {
1865 f.emit_leading_comments(&a.key_trivia.leading);
1867 f.push(&format!(
1868 "key {}: {}",
1869 a.key_name.name,
1870 type_ref_to_string(&a.key_type)
1871 ));
1872 f.emit_trailing_comment_or_newline(a.key_trivia.trailing.as_deref());
1873 f.newline();
1874 for sf in &a.store_fields {
1876 f.format_store_field(sf);
1877 f.newline();
1878 }
1879 for inv in &a.invariants {
1882 f.newline();
1883 f.format_invariant(inv);
1884 }
1885 for tr in &a.transitions {
1888 f.newline();
1889 f.format_transition(tr);
1890 }
1891 for h in &a.handlers {
1893 f.newline();
1894 f.format_handler(h);
1895 }
1896 if !a.trailing_comments.is_empty() {
1898 f.newline();
1899 f.emit_trailing_comments(&a.trailing_comments);
1900 }
1901 });
1902 self.push("}");
1903 self.emit_trailing_comment_or_newline(a.trivia.trailing.as_deref());
1904 }
1905
1906 fn format_store_field(&mut self, sf: &StoreField) {
1910 self.emit_leading_comments(&sf.trivia.leading);
1911 if let Some(doc) = &sf.documentation {
1912 self.emit_doc(doc);
1913 }
1914 self.push(&format!(
1915 "store {}: {}",
1916 sf.name.name,
1917 store_kind_to_string(&sf.kind)
1918 ));
1919 for ann in &sf.annotations {
1921 self.push(&format!(" {}", annotation_to_string(ann)));
1922 }
1923 if let Some(init) = &sf.init {
1924 self.push(" = ");
1925 self.format_expr(init);
1926 }
1927 self.emit_trailing_comment(sf.trivia.trailing.as_deref());
1928 }
1929
1930 fn format_invariant(&mut self, inv: &Invariant) {
1933 self.emit_leading_comments(&inv.trivia.leading);
1934 if let Some(doc) = &inv.documentation {
1935 self.emit_doc(doc);
1936 }
1937 self.push(&format!("invariant {}:", inv.name.name));
1938 self.newline();
1939 self.indented(|f| {
1940 f.format_expr(&inv.predicate);
1941 });
1942 self.emit_trailing_comment_or_newline(inv.trivia.trailing.as_deref());
1943 }
1944
1945 fn format_transition(&mut self, tr: &Transition) {
1948 self.emit_leading_comments(&tr.trivia.leading);
1949 if let Some(doc) = &tr.documentation {
1950 self.emit_doc(doc);
1951 }
1952 self.push(&format!("transition {}:", tr.name.name));
1953 self.newline();
1954 self.indented(|f| {
1955 f.format_expr(&tr.predicate);
1956 });
1957 self.emit_trailing_comment_or_newline(tr.trivia.trailing.as_deref());
1958 }
1959
1960 fn format_actor(&mut self, a: &ActorDecl) {
1961 self.emit_leading_comments(&a.trivia.leading);
1962 if let Some(doc) = &a.documentation {
1963 self.emit_doc(doc);
1964 }
1965 if let Some(r) = &a.refinement {
1966 self.push(&format!(
1968 "actor {} = {} where {}",
1969 a.name.name,
1970 r.base.name,
1971 expr_to_string(&r.predicate)
1972 ));
1973 } else {
1974 let auth = a.auth.as_ref().map(|i| i.name.as_str()).unwrap_or("None");
1976 let args: Vec<String> = a
1977 .auth_config
1978 .iter()
1979 .map(|arg| match &arg.value {
1980 bynk_syntax::ast::SchemeArgValue::Str(s) => {
1981 format!("{} = \"{}\"", arg.key.name, escape_string(s))
1982 }
1983 bynk_syntax::ast::SchemeArgValue::Int(n) => {
1984 format!("{} = {n}", arg.key.name)
1985 }
1986 })
1987 .collect();
1988 let config = if args.is_empty() {
1989 String::new()
1990 } else {
1991 format!("({})", args.join(", "))
1992 };
1993 let identity = a.identity.as_ref().map(type_ref_to_string);
1994 let oneline = format!(
1995 "actor {} {{ auth = {auth}{config}{} }}",
1996 a.name.name,
1997 identity
1998 .as_ref()
1999 .map(|id| format!(", identity = {id}"))
2000 .unwrap_or_default()
2001 );
2002 let break_args = !args.is_empty() && !self.fits(&oneline, 0);
2007 let has_comments = !a.trailing_comments.is_empty()
2009 || [&a.auth_trivia, &a.identity_trivia]
2010 .iter()
2011 .any(|t| !t.leading.is_empty() || t.trailing.is_some());
2012 if !break_args && !has_comments {
2013 self.push(&oneline);
2014 } else {
2015 self.push(&format!("actor {} {{", a.name.name));
2016 self.newline();
2017 self.indented(|f| {
2018 f.emit_leading_comments(&a.auth_trivia.leading);
2019 if break_args {
2020 f.push(&format!("auth = {auth}("));
2021 f.newline();
2022 f.indented(|f2| {
2023 for (i, arg) in args.iter().enumerate() {
2024 f2.push(arg);
2025 if i + 1 < args.len() {
2026 f2.push(",");
2027 }
2028 f2.newline();
2029 }
2030 });
2031 f.push(")");
2032 } else {
2033 f.push(&format!("auth = {auth}{config}"));
2034 }
2035 if let Some(id) = &identity {
2036 f.push(",");
2039 f.emit_trailing_comment(a.auth_trivia.trailing.as_deref());
2040 if a.auth_trivia.trailing.is_none() {
2041 f.newline();
2042 }
2043 f.emit_leading_comments(&a.identity_trivia.leading);
2044 f.push(&format!("identity = {id}"));
2045 f.emit_trailing_comment(a.identity_trivia.trailing.as_deref());
2046 if a.identity_trivia.trailing.is_none() {
2047 f.newline();
2048 }
2049 } else {
2050 f.emit_trailing_comment(a.auth_trivia.trailing.as_deref());
2051 if a.auth_trivia.trailing.is_none() {
2052 f.newline();
2053 }
2054 }
2055 f.emit_trailing_comments(&a.trailing_comments);
2056 });
2057 self.push("}");
2058 }
2059 }
2060 self.emit_trailing_comment_or_newline(a.trivia.trailing.as_deref());
2061 }
2062
2063 fn format_handler(&mut self, h: &Handler) {
2064 self.emit_leading_comments(&h.trivia.leading);
2065 if let Some(doc) = &h.documentation {
2066 self.emit_doc(doc);
2067 }
2068 for ann in &h.annotations {
2072 self.push(&annotation_to_string(ann));
2073 self.newline();
2074 }
2075 match &h.kind {
2078 HandlerKind::Call => {
2079 self.push("on call");
2080 if let Some(m) = &h.method_name {
2081 self.push(&format!(" {}", m.name));
2082 }
2083 }
2084 HandlerKind::Http { method, path } => {
2085 self.push(&format!(
2088 "on {}(\"{}\") ",
2089 method.as_str(),
2090 escape_string(path)
2091 ));
2092 }
2093 HandlerKind::Cron { expr } => {
2094 self.push(&format!("on schedule(\"{}\") ", escape_string(expr)));
2095 }
2096 HandlerKind::Message => {
2097 self.push("on message");
2098 }
2099 HandlerKind::Open => {
2100 self.push("on open");
2101 }
2102 HandlerKind::Close => {
2103 self.push("on close");
2104 }
2105 HandlerKind::Event => {
2106 self.push("on event");
2107 }
2108 }
2109 let mut tail = format!(" -> {}", type_ref_to_string(&h.return_type));
2118 if let Some(by) = &h.by_clause {
2119 tail.push_str(&format!(" {}", by_clause_src(by)));
2120 }
2121 if !h.given.is_empty() {
2122 let names: Vec<String> = h.given.iter().map(cap_ref_src).collect();
2123 tail.push_str(&format!(" given {}", names.join(", ")));
2124 }
2125 self.format_params(&h.params, false, tail.chars().count() + " {".len());
2126 self.push(&tail);
2127 self.push(" ");
2128 self.format_block(&h.body);
2129 self.emit_trailing_comment_or_newline(h.trivia.trailing.as_deref());
2130 }
2131
2132 fn format_block(&mut self, b: &Block) {
2135 self.format_block_with_reserve(b, 0);
2136 }
2137
2138 fn format_block_with_reserve(&mut self, b: &Block, reserve: usize) {
2142 let tail_oneline = expr_to_string(&b.tail);
2145 let any_stmt_trivia = b.statements.iter().any(|s| !statement_trivia(s).is_empty());
2146 if b.statements.is_empty()
2147 && b.tail_leading_comments.is_empty()
2148 && !any_stmt_trivia
2149 && self.fits(&format!("{{ {tail_oneline} }}"), reserve)
2150 {
2151 self.push("{ ");
2152 self.push(&tail_oneline);
2153 self.push(" }");
2154 return;
2155 }
2156 self.format_block_multiline(b);
2157 }
2158
2159 fn format_block_multiline(&mut self, b: &Block) {
2164 self.push("{");
2165 self.newline();
2166 self.indented(|f| {
2167 for stmt in &b.statements {
2168 let trivia = statement_trivia(stmt);
2169 f.emit_leading_comments(&trivia.leading);
2170 f.format_statement(stmt);
2171 f.emit_trailing_comment_or_newline(trivia.trailing.as_deref());
2172 }
2173 f.emit_leading_comments(&b.tail_leading_comments);
2174 if !omit_unit_tail(b) {
2175 f.format_expr(&b.tail);
2176 f.newline();
2177 }
2178 });
2179 self.push("}");
2180 }
2181
2182 fn format_statement(&mut self, s: &Statement) {
2183 match s {
2184 Statement::Let(l) => {
2185 self.push("let ");
2186 self.push(&l.name.name);
2187 if let Some(t) = &l.type_annot {
2188 self.push(": ");
2189 self.format_type_ref(t);
2190 }
2191 self.push(" = ");
2192 self.format_expr(&l.value);
2193 }
2194 Statement::EffectLet(l) => {
2195 self.push("let ");
2196 self.push(&l.name.name);
2197 if let Some(t) = &l.type_annot {
2198 self.push(": ");
2199 self.format_type_ref(t);
2200 }
2201 self.push(" <- ");
2202 let principal = l
2204 .principal
2205 .as_ref()
2206 .map(|p| format!(" {}", call_site_actor_src(p)));
2207 let reserve = principal.as_deref().map_or(0, |p| p.chars().count());
2208 self.format_expr_at(&l.value, 0, reserve);
2209 if let Some(principal) = principal {
2210 self.push(&principal);
2211 }
2212 }
2213 Statement::Expect(a) => {
2214 self.push("expect ");
2215 self.format_expr(&a.value);
2216 }
2217 Statement::Send(s) => {
2218 self.push("~> ");
2219 self.format_expr(&s.value);
2220 }
2221 Statement::Do(d) => {
2222 self.push("do ");
2223 self.format_expr(&d.value);
2224 }
2225 Statement::Assign(a) => {
2226 self.push(&a.target.name);
2227 self.push(" := ");
2228 self.format_expr(&a.value);
2229 }
2230 }
2231 }
2232
2233 fn format_expr(&mut self, e: &Expr) {
2234 self.format_expr_at(e, 0, 0);
2235 }
2236
2237 fn format_expr_at(&mut self, e: &Expr, parent_prec: u8, reserve: usize) {
2250 if let ExprKind::Match { discriminant, arms } = &e.kind {
2256 self.format_match(discriminant, arms);
2257 return;
2258 }
2259 let flat = expr_with_prec(e, parent_prec);
2260 if self.fits(&flat, reserve) {
2261 self.push(&flat);
2262 return;
2263 }
2264 if needs_parens(e, parent_prec) {
2267 self.push("(");
2268 self.format_expr_broken(e, reserve + 1);
2269 self.push(")");
2270 } else {
2271 self.format_expr_broken(e, reserve);
2272 }
2273 }
2274
2275 fn format_expr_broken(&mut self, e: &Expr, reserve: usize) {
2280 match &e.kind {
2281 ExprKind::RecordConstruction { type_name, fields } if !fields.is_empty() => {
2283 self.push(&format!("{} {{", type_name.name));
2284 self.format_field_inits(fields.iter(), None);
2285 }
2286 ExprKind::RecordSpread {
2289 type_name,
2290 base,
2291 overrides,
2292 } => {
2293 match type_name {
2294 Some(tn) => self.push(&format!("{} {{", tn.name)),
2295 None => self.push("{"),
2296 }
2297 let spread = format!("...{}", expr_with_prec(base, 0));
2298 self.format_field_inits(overrides.iter(), Some(&spread));
2299 }
2300 ExprKind::Call {
2304 name,
2305 type_args,
2306 args,
2307 } if !args.is_empty() => {
2308 self.push(&format!("{}{}(", name.name, type_args_src(type_args)));
2309 self.format_arg_list(args, reserve);
2310 }
2311 ExprKind::ConstructorCall {
2312 type_name,
2313 method,
2314 args,
2315 } if !args.is_empty() => {
2316 self.push(&format!("{}.{}(", type_name.name, method.name));
2317 self.format_arg_list(args, reserve);
2318 }
2319 ExprKind::Val { type_ref, args } if !args.is_empty() => {
2320 self.push(&format!("Val[{}](", type_ref_to_string(type_ref)));
2321 self.format_arg_list(args, reserve);
2322 }
2323 ExprKind::MethodCall { .. } | ExprKind::FieldAccess { .. } => {
2327 self.format_chain(e, reserve);
2328 }
2329 ExprKind::ListLit(elems) if !elems.is_empty() => {
2331 self.push("[");
2332 self.newline();
2333 self.indented(|f| {
2334 for (i, elem) in elems.iter().enumerate() {
2335 let last = i + 1 == elems.len();
2336 f.format_expr_at(elem, 0, if last { 0 } else { 1 });
2337 if !last || f.opts.trailing_comma {
2338 f.push(",");
2339 }
2340 f.newline();
2341 }
2342 });
2343 self.push("]");
2344 }
2345 ExprKind::BinOp(op, ..) if is_logical(*op) => {
2350 let prec = binop_prec(*op);
2351 let mut operands = Vec::new();
2352 flatten_binop(e, *op, &mut operands);
2353 self.format_expr_at(operands[0], prec, 0);
2354 self.indented(|f| {
2355 for (i, operand) in operands.iter().enumerate().skip(1) {
2356 f.newline();
2357 f.push(&format!("{} ", op.name()));
2358 let last = i + 1 == operands.len();
2359 f.format_expr_at(operand, prec + 1, if last { reserve } else { 0 });
2360 }
2361 });
2362 }
2363 ExprKind::BinOp(op, lhs, rhs) => {
2366 let prec = binop_prec(*op);
2367 let tail = format!(" {} {}", op.name(), expr_with_prec(rhs, prec + 1));
2368 let lhs_reserve = if tail.contains('\n') {
2371 0
2372 } else {
2373 tail.chars().count() + reserve
2374 };
2375 self.format_expr_at(lhs, prec, lhs_reserve);
2376 self.push(&format!(" {} ", op.name()));
2377 self.format_expr_at(rhs, prec + 1, reserve);
2378 }
2379 ExprKind::Is { value, pattern } => {
2380 let pat = format!(" is {}", pattern_to_string(pattern));
2381 self.format_expr_at(value, 4, pat.chars().count() + reserve);
2382 self.push(&pat);
2383 }
2384 ExprKind::If {
2388 cond,
2389 then_block,
2390 else_block,
2391 } => {
2392 self.push("if ");
2393 self.format_expr_at(cond, 0, 2);
2394 self.push(" ");
2395 self.format_block_multiline(then_block);
2396 if !else_block.is_synth_unit() {
2399 self.push(" else ");
2400 self.format_block_multiline(else_block);
2401 }
2402 }
2403 ExprKind::Block(b) => self.format_block_multiline(b),
2404 ExprKind::Lambda(lambda) => {
2405 let params: Vec<String> = lambda
2406 .params
2407 .iter()
2408 .map(|p| match &p.type_ref {
2409 Some(tr) => format!("{}: {}", p.name.name, type_ref_to_string(tr)),
2410 None => p.name.name.clone(),
2411 })
2412 .collect();
2413 self.push(&format!("({}) => ", params.join(", ")));
2414 self.format_expr_at(&lambda.body, 0, reserve);
2415 }
2416 ExprKind::Ok(v) => self.wrap_call("Ok(", v, reserve),
2419 ExprKind::Err(v) => self.wrap_call("Err(", v, reserve),
2420 ExprKind::Some(v) => self.wrap_call("Some(", v, reserve),
2421 ExprKind::EffectPure(v) => self.wrap_call("Effect.pure(", v, reserve),
2422 ExprKind::Wire(v) => self.wrap_call("Wire(", v, reserve),
2423 ExprKind::Paren(v) => self.wrap_call("(", v, reserve),
2424 ExprKind::Question(v) => {
2425 self.format_expr_at(v, 8, reserve + 1);
2426 self.push("?");
2427 }
2428 ExprKind::Expect(v) => {
2429 self.push("expect ");
2430 self.format_expr_at(v, 0, reserve);
2431 }
2432 _ => self.push(&expr_with_prec(e, 0)),
2435 }
2436 }
2437
2438 fn wrap_call(&mut self, head: &str, inner: &Expr, reserve: usize) {
2440 self.push(head);
2441 self.format_expr_at(inner, 0, reserve + 1);
2442 self.push(")");
2443 }
2444
2445 fn format_field_inits<'f, I>(&mut self, fields: I, spread: Option<&str>)
2450 where
2451 I: ExactSizeIterator<Item = &'f FieldInit>,
2452 {
2453 let total = fields.len() + usize::from(spread.is_some());
2454 self.newline();
2455 self.indented(|f| {
2456 let mut emitted = 0usize;
2457 if let Some(spread) = spread {
2458 f.push(spread);
2459 emitted += 1;
2460 if emitted < total || f.opts.trailing_comma {
2461 f.push(",");
2462 }
2463 f.newline();
2464 }
2465 for field in fields {
2466 f.push(&field.name.name);
2467 if let Some(v) = &field.value {
2468 f.push(": ");
2469 f.format_expr_at(v, 0, 1);
2470 }
2471 emitted += 1;
2472 if emitted < total || f.opts.trailing_comma {
2473 f.push(",");
2474 }
2475 f.newline();
2476 }
2477 });
2478 self.push("}");
2479 }
2480
2481 fn format_arg_list(&mut self, args: &[Expr], reserve: usize) -> bool {
2496 if let Some((last, leading)) = args.split_last()
2497 && (leading.is_empty() || is_block_like(last))
2498 && self.try_layout(reserve, |f| {
2499 for arg in leading {
2500 f.push(&expr_with_prec(arg, 0));
2501 f.push(", ");
2502 }
2503 f.format_expr_at(last, 0, 1);
2504 f.push(")");
2505 })
2506 {
2507 return false;
2508 }
2509 self.newline();
2510 self.indented(|f| {
2511 for (i, arg) in args.iter().enumerate() {
2512 let last = i + 1 == args.len();
2513 f.format_expr_at(arg, 0, if last { 0 } else { 1 });
2514 if !last {
2515 f.push(",");
2516 }
2517 f.newline();
2518 }
2519 });
2520 self.push(")");
2521 true
2522 }
2523
2524 fn format_chain(&mut self, e: &Expr, reserve: usize) {
2531 let (base, links) = flatten_chain(e);
2532 let calls = links
2533 .iter()
2534 .filter(|l| matches!(l, ChainLink::Method { .. }))
2535 .count();
2536 if calls < 2 {
2537 self.format_chain_inline(base, &links, reserve);
2538 return;
2539 }
2540 let exploded = std::cell::Cell::new(false);
2548 if self.try_layout_if(
2549 reserve,
2550 |f| exploded.set(f.format_chain_inline(base, &links, reserve)),
2551 || !exploded.get(),
2552 ) {
2553 return;
2554 }
2555 let first_call = links
2560 .iter()
2561 .position(|l| matches!(l, ChainLink::Method { .. }))
2562 .expect("a chain with two calls has one");
2563 self.format_expr_at(base, 8, 0);
2564 for link in &links[..first_call] {
2565 self.format_chain_link(link, 0);
2566 }
2567 self.indented(|f| {
2568 let mut i = first_call;
2569 while i < links.len() {
2570 let mut end = i;
2571 while end < links.len() && matches!(links[end], ChainLink::Field(_)) {
2572 end += 1;
2573 }
2574 if end < links.len() {
2576 end += 1;
2577 }
2578 f.newline();
2579 for (offset, link) in links[i..end].iter().enumerate() {
2580 let is_last = end == links.len() && i + offset + 1 == links.len();
2581 f.format_chain_link(link, if is_last { reserve } else { 0 });
2582 }
2583 i = end;
2584 }
2585 });
2586 }
2587
2588 fn format_chain_inline(
2594 &mut self,
2595 base: &Expr,
2596 links: &[ChainLink<'_>],
2597 reserve: usize,
2598 ) -> bool {
2599 self.format_expr_at(base, 8, 0);
2600 let mut exploded = false;
2601 for (i, link) in links.iter().enumerate() {
2602 exploded |=
2603 self.format_chain_link(link, if i + 1 == links.len() { reserve } else { 0 });
2604 }
2605 exploded
2606 }
2607
2608 fn format_chain_link(&mut self, link: &ChainLink<'_>, reserve: usize) -> bool {
2612 match link {
2613 ChainLink::Field(name) => {
2614 self.push(&format!(".{name}"));
2615 false
2616 }
2617 ChainLink::Method {
2618 method,
2619 type_args,
2620 args,
2621 } => {
2622 let head = format!(".{}{}", method, type_args_src(type_args));
2623 let flat = format!(
2624 "{head}({})",
2625 args.iter()
2626 .map(|a| expr_with_prec(a, 0))
2627 .collect::<Vec<_>>()
2628 .join(", ")
2629 );
2630 if args.is_empty() || self.fits(&flat, reserve) {
2631 self.push(&flat);
2632 return false;
2633 }
2634 self.push(&head);
2635 self.push("(");
2636 self.format_arg_list(args, reserve)
2637 }
2638 }
2639 }
2640
2641 fn format_match(&mut self, discriminant: &Expr, arms: &[MatchArm]) {
2645 self.push("match ");
2646 self.format_expr_at(discriminant, 0, " {".len());
2647 self.push(" {");
2648 self.newline();
2649 self.indented(|f| {
2650 for arm in arms {
2651 f.push(&pattern_to_string(&arm.pattern));
2652 if let Some(guard) = &arm.guard {
2654 f.push(" if ");
2655 f.format_expr_at(guard, 0, " => ".len());
2656 }
2657 f.push(" => ");
2658 match &arm.body {
2660 MatchBody::Expr(e) => f.format_expr_at(e, 0, 1),
2661 MatchBody::Block(b) => f.format_block_with_reserve(b, 1),
2662 }
2663 f.push(",");
2664 f.newline();
2665 }
2666 });
2667 self.push("}");
2668 }
2669}
2670
2671enum ChainLink<'e> {
2673 Field(&'e str),
2674 Method {
2675 method: &'e str,
2676 type_args: &'e [TypeRef],
2677 args: &'e [Expr],
2678 },
2679}
2680
2681fn flatten_chain(e: &Expr) -> (&Expr, Vec<ChainLink<'_>>) {
2685 let mut links = Vec::new();
2686 let mut cur = e;
2687 loop {
2688 match &cur.kind {
2689 ExprKind::FieldAccess { receiver, field } => {
2690 links.push(ChainLink::Field(field.name.as_str()));
2691 cur = receiver;
2692 }
2693 ExprKind::MethodCall {
2694 receiver,
2695 method,
2696 type_args,
2697 args,
2698 } => {
2699 links.push(ChainLink::Method {
2700 method: method.name.as_str(),
2701 type_args,
2702 args,
2703 });
2704 cur = receiver;
2705 }
2706 _ => break,
2707 }
2708 }
2709 links.reverse();
2710 (cur, links)
2711}
2712
2713fn type_args_src(type_args: &[TypeRef]) -> String {
2715 if type_args.is_empty() {
2716 return String::new();
2717 }
2718 format!(
2719 "[{}]",
2720 type_args
2721 .iter()
2722 .map(type_ref_to_string)
2723 .collect::<Vec<_>>()
2724 .join(", ")
2725 )
2726}
2727
2728fn is_logical(op: BinOp) -> bool {
2732 matches!(op, BinOp::And | BinOp::Or | BinOp::Implies)
2733}
2734
2735fn flatten_binop<'e>(e: &'e Expr, op: BinOp, out: &mut Vec<&'e Expr>) {
2738 if let ExprKind::BinOp(inner_op, lhs, rhs) = &e.kind
2739 && *inner_op == op
2740 {
2741 flatten_binop(lhs, op, out);
2742 out.push(rhs);
2743 return;
2744 }
2745 out.push(e);
2746}
2747
2748fn is_block_like(e: &Expr) -> bool {
2754 matches!(
2755 e.kind,
2756 ExprKind::Lambda(_)
2757 | ExprKind::Block(_)
2758 | ExprKind::RecordConstruction { .. }
2759 | ExprKind::RecordSpread { .. }
2760 | ExprKind::Match { .. }
2761 | ExprKind::If { .. }
2762 )
2763}
2764
2765fn needs_parens(e: &Expr, parent_prec: u8) -> bool {
2769 match &e.kind {
2770 ExprKind::BinOp(op, ..) => binop_prec(*op) < parent_prec,
2771 ExprKind::UnaryOp(..) => parent_prec > 7,
2772 _ => false,
2773 }
2774}
2775
2776fn cap_ref_src(c: &CapRef) -> String {
2780 match &c.context {
2781 Some(prefix) => format!("{}.{}", prefix.joined(), c.name.name),
2782 None => c.name.name.clone(),
2783 }
2784}
2785
2786fn by_clause_src(by: &ByClause) -> String {
2790 let actors = by
2791 .actors
2792 .iter()
2793 .map(|a| a.name.as_str())
2794 .collect::<Vec<_>>()
2795 .join(" | ");
2796 match &by.binder {
2797 Some(b) => format!("by {}: {actors}", b.name),
2798 None => format!("by {actors}"),
2799 }
2800}
2801
2802fn event_pattern_src(p: &EventPattern) -> String {
2808 let fields: Vec<String> = p
2809 .fields
2810 .iter()
2811 .map(|f| format!("{}: {}", f.name.name, event_pattern_value_src(&f.value)))
2812 .collect();
2813 format!("{{ {}, .. }}", fields.join(", "))
2814}
2815
2816fn event_pattern_value_src(v: &EventPatternValue) -> String {
2817 match v {
2818 EventPatternValue::Literal { value, .. } => match value {
2819 LiteralValue::Int(n) => n.to_string(),
2820 LiteralValue::Str(s) => format!("\"{}\"", escape_string(s)),
2821 LiteralValue::Bool(b) => b.to_string(),
2822 },
2823 EventPatternValue::Variant {
2824 type_name, variant, ..
2825 } => match type_name {
2826 Some(t) => format!("{}.{}", t.name, variant.name),
2827 None => variant.name.clone(),
2828 },
2829 }
2830}
2831
2832fn schema_dispatch_src(d: &SchemaDispatch) -> String {
2835 match &d.pattern {
2836 SchemaVersionPattern::Literal(n) => format!("via schema({n})"),
2837 }
2838}
2839
2840fn call_site_actor_src(p: &CallSiteActor) -> String {
2843 match &p.identity {
2844 Some(id) => format!("by {}({})", p.actor.name, expr_with_prec(id, 0)),
2845 None => format!("by {}", p.actor.name),
2846 }
2847}
2848
2849fn store_kind_to_string(k: &StoreKind) -> String {
2851 if k.args.is_empty() {
2852 k.head.name.clone()
2853 } else {
2854 format!(
2855 "{}[{}]",
2856 k.head.name,
2857 k.args
2858 .iter()
2859 .map(type_ref_to_string)
2860 .collect::<Vec<_>>()
2861 .join(", ")
2862 )
2863 }
2864}
2865
2866pub fn annotation_to_string(ann: &Annotation) -> String {
2873 if ann.args.is_empty() {
2874 return format!("@{}", ann.name.name);
2875 }
2876 let args = ann
2877 .args
2878 .iter()
2879 .map(|a| match &a.label {
2880 Some(l) => format!("{}: {}", l.name, expr_with_prec(&a.value, 0)),
2881 None => expr_with_prec(&a.value, 0),
2882 })
2883 .collect::<Vec<_>>()
2884 .join(", ");
2885 format!("@{}({})", ann.name.name, args)
2886}
2887
2888fn stub_clause_to_string(pv: &StubClause) -> String {
2891 let args = pv
2892 .args
2893 .iter()
2894 .map(|a| match a {
2895 ArgPattern::Any(_) => "_".to_string(),
2896 ArgPattern::Value(e) => expr_to_string(e),
2897 })
2898 .collect::<Vec<_>>()
2899 .join(", ");
2900 let rhs = match &pv.rhs {
2901 StubRhs::Returns(e) => format!("returns {}", expr_to_string(e)),
2902 StubRhs::Fails(_) => "fails".to_string(),
2903 StubRhs::ReturnsEach(outcomes, _) => {
2904 let items = outcomes
2905 .iter()
2906 .map(|o| match o {
2907 SeqOutcome::Value(e) => expr_to_string(e),
2908 SeqOutcome::Fails(_) => "fails".to_string(),
2909 })
2910 .collect::<Vec<_>>()
2911 .join(", ");
2912 format!("returns each [{items}]")
2913 }
2914 };
2915 format!(
2916 "stub {}.{}({}) {}",
2917 pv.capability.name, pv.method.name, args, rhs
2918 )
2919}
2920
2921fn statement_trivia(s: &Statement) -> &Trivia {
2922 match s {
2923 Statement::Let(l) | Statement::EffectLet(l) => &l.trivia,
2924 Statement::Expect(a) => &a.trivia,
2925 Statement::Send(s) => &s.trivia,
2926 Statement::Do(d) => &d.trivia,
2927 Statement::Assign(a) => &a.trivia,
2928 }
2929}
2930
2931fn display_width(line: &str, tab: usize) -> usize {
2938 let mut col = 0usize;
2939 for ch in line.chars() {
2940 if ch == '\t' {
2941 col += tab - (col % tab);
2942 } else {
2943 col += 1;
2944 }
2945 }
2946 col
2947}
2948
2949fn type_ref_to_string(t: &TypeRef) -> String {
2950 match t {
2951 TypeRef::Base(b, _) => b.name().to_string(),
2952 TypeRef::Named(id) => id.name.clone(),
2953 TypeRef::Result(a, b, _) => format!(
2954 "Result[{}, {}]",
2955 type_ref_to_string(a),
2956 type_ref_to_string(b)
2957 ),
2958 TypeRef::Option(t, _) => format!("Option[{}]", type_ref_to_string(t)),
2959 TypeRef::Effect(t, _) => format!("Effect[{}]", type_ref_to_string(t)),
2960 TypeRef::HttpResult(t, _) => format!("HttpResult[{}]", type_ref_to_string(t)),
2961 TypeRef::QueueResult(_) => "QueueResult".to_string(),
2962 TypeRef::List(t, _) => format!("List[{}]", type_ref_to_string(t)),
2963 TypeRef::Query(t, _) => format!("Query[{}]", type_ref_to_string(t)),
2964 TypeRef::Stream(t, _) => format!("Stream[{}]", type_ref_to_string(t)),
2965 TypeRef::Connection(t, _) => format!("Connection[{}]", type_ref_to_string(t)),
2966 TypeRef::History(t, _) => format!("History[{}]", type_ref_to_string(t)),
2967 TypeRef::Map(k, v, _) => {
2968 format!("Map[{}, {}]", type_ref_to_string(k), type_ref_to_string(v))
2969 }
2970 TypeRef::ValidationError(_) => "ValidationError".to_string(),
2971 TypeRef::JsonError(_) => "JsonError".to_string(),
2972 TypeRef::Unit(_) => "()".to_string(),
2973 TypeRef::App { name, args, .. } => format!(
2975 "{}[{}]",
2976 name.name,
2977 args.iter()
2978 .map(type_ref_to_string)
2979 .collect::<Vec<_>>()
2980 .join(", ")
2981 ),
2982 TypeRef::Fn(params, ret, _) => {
2983 let lhs = match params.len() {
2984 0 => "()".to_string(),
2985 1 if !matches!(params[0], TypeRef::Fn(..)) => type_ref_to_string(¶ms[0]),
2986 _ => format!(
2987 "({})",
2988 params
2989 .iter()
2990 .map(type_ref_to_string)
2991 .collect::<Vec<_>>()
2992 .join(", ")
2993 ),
2994 };
2995 format!("{lhs} -> {}", type_ref_to_string(ret))
2996 }
2997 }
2998}
2999
3000pub fn refinement_to_string(r: &Refinement) -> String {
3001 let mut s = String::new();
3002 for (i, p) in r.predicates.iter().enumerate() {
3003 if i > 0 {
3004 s.push_str(" && ");
3005 }
3006 s.push_str(&pred_to_string(p));
3007 }
3008 s
3009}
3010
3011fn pred_to_string(p: &RefinementPred) -> String {
3012 match &p.kind {
3013 PredKind::Matches(re) => format!("Matches(\"{}\")", escape_string(re)),
3014 PredKind::InRange(a, b) => format!("InRange({}, {})", a.value, b.value),
3015 PredKind::InRangeF(a, b) => format!("InRange({}, {})", a.lexeme, b.lexeme),
3016 PredKind::MinLength(n) => format!("MinLength({n})"),
3017 PredKind::MaxLength(n) => format!("MaxLength({n})"),
3018 PredKind::Length(n) => format!("Length({n})"),
3019 PredKind::NonNegative => "NonNegative".to_string(),
3020 PredKind::Positive => "Positive".to_string(),
3021 PredKind::NonEmpty => "NonEmpty".to_string(),
3022 }
3023}
3024
3025pub fn escape_string(s: &str) -> String {
3026 let mut out = String::with_capacity(s.len());
3027 for ch in s.chars() {
3028 match ch {
3029 '\\' => out.push_str("\\\\"),
3030 '"' => out.push_str("\\\""),
3031 '\n' => out.push_str("\\n"),
3032 '\t' => out.push_str("\\t"),
3033 c => out.push(c),
3034 }
3035 }
3036 out
3037}
3038
3039pub fn expr_to_string(e: &Expr) -> String {
3040 expr_with_prec(e, 0)
3041}
3042
3043fn binop_prec(op: BinOp) -> u8 {
3046 match op {
3047 BinOp::Implies => 0,
3049 BinOp::Or => 1,
3050 BinOp::And => 2,
3051 BinOp::Eq | BinOp::NotEq => 3,
3052 BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => 4,
3053 BinOp::Add | BinOp::Sub => 5,
3054 BinOp::Mul | BinOp::Div => 6,
3055 }
3056}
3057
3058fn expr_with_prec(e: &Expr, parent_prec: u8) -> String {
3059 match &e.kind {
3060 ExprKind::IntLit { lexeme, .. } => lexeme.clone(),
3063 ExprKind::FloatLit { lexeme, .. } => lexeme.clone(),
3065 ExprKind::DurationLit { value, unit, .. } => format!("{value}.{}", unit.name()),
3067 ExprKind::StrLit(s) => format!("\"{}\"", escape_string(s)),
3068 ExprKind::InterpStr(parts) => {
3072 let mut out = String::from("\"");
3073 for part in parts {
3074 match part {
3075 InterpPart::Chunk(text) => out.push_str(&escape_string(text)),
3076 InterpPart::Hole(hole) => {
3077 out.push_str(&format!("\\({})", expr_with_prec(hole, 0)));
3078 }
3079 }
3080 }
3081 out.push('"');
3082 out
3083 }
3084 ExprKind::BoolLit(b) => b.to_string(),
3085 ExprKind::UnitLit => "()".to_string(),
3086 ExprKind::Ident(id) => id.name.clone(),
3087 ExprKind::ListLit(elems) => format!(
3088 "[{}]",
3089 elems
3090 .iter()
3091 .map(expr_to_string)
3092 .collect::<Vec<_>>()
3093 .join(", ")
3094 ),
3095 ExprKind::Call {
3096 name,
3097 type_args,
3098 args,
3099 } => {
3100 let targs = if type_args.is_empty() {
3101 String::new()
3102 } else {
3103 format!(
3104 "[{}]",
3105 type_args
3106 .iter()
3107 .map(type_ref_to_string)
3108 .collect::<Vec<_>>()
3109 .join(", ")
3110 )
3111 };
3112 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
3113 format!("{}{}({})", name.name, targs, parts.join(", "))
3114 }
3115 ExprKind::BinOp(op, l, r) => {
3116 let prec = binop_prec(*op);
3117 let inner = format!(
3118 "{} {} {}",
3119 expr_with_prec(l, prec),
3120 op.name(),
3121 expr_with_prec(r, prec + 1)
3122 );
3123 if prec < parent_prec {
3124 format!("({inner})")
3125 } else {
3126 inner
3127 }
3128 }
3129 ExprKind::UnaryOp(op, inner) => {
3130 let s = format!("{}{}", op.name(), expr_with_prec(inner, 7));
3132 if parent_prec > 7 { format!("({s})") } else { s }
3133 }
3134 ExprKind::Paren(inner) => format!("({})", expr_with_prec(inner, 0)),
3135 ExprKind::Lambda(lambda) => {
3137 let params: Vec<String> = lambda
3138 .params
3139 .iter()
3140 .map(|p| match &p.type_ref {
3141 Some(tr) => format!("{}: {}", p.name.name, type_ref_to_string(tr)),
3142 None => p.name.name.clone(),
3143 })
3144 .collect();
3145 let body = match &lambda.body.kind {
3146 ExprKind::Block(b) => format_block_oneline(b),
3147 _ => expr_with_prec(&lambda.body, 0),
3148 };
3149 format!("({}) => {}", params.join(", "), body)
3150 }
3151 ExprKind::Block(b) => format_block_oneline(b),
3152 ExprKind::If {
3153 cond,
3154 then_block,
3155 else_block,
3156 } => {
3157 if else_block.is_synth_unit() {
3160 format!(
3161 "if {} {}",
3162 expr_with_prec(cond, 0),
3163 format_block_oneline(then_block),
3164 )
3165 } else {
3166 format!(
3167 "if {} {} else {}",
3168 expr_with_prec(cond, 0),
3169 format_block_oneline(then_block),
3170 format_block_oneline(else_block),
3171 )
3172 }
3173 }
3174 ExprKind::Ok(v) => format!("Ok({})", expr_with_prec(v, 0)),
3175 ExprKind::Err(v) => format!("Err({})", expr_with_prec(v, 0)),
3176 ExprKind::Some(v) => format!("Some({})", expr_with_prec(v, 0)),
3177 ExprKind::None => "None".to_string(),
3178 ExprKind::Question(v) => format!("{}?", expr_with_prec(v, 8)),
3179 ExprKind::ConstructorCall {
3180 type_name,
3181 method,
3182 args,
3183 } => {
3184 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
3185 format!("{}.{}({})", type_name.name, method.name, parts.join(", "))
3186 }
3187 ExprKind::RecordConstruction { type_name, fields } => {
3188 let parts: Vec<String> = fields
3189 .iter()
3190 .map(|f| match &f.value {
3191 Some(v) => format!("{}: {}", f.name.name, expr_with_prec(v, 0)),
3192 None => f.name.name.clone(),
3193 })
3194 .collect();
3195 if parts.is_empty() {
3196 format!("{} {{}}", type_name.name)
3197 } else {
3198 format!("{} {{ {} }}", type_name.name, parts.join(", "))
3199 }
3200 }
3201 ExprKind::FieldAccess { receiver, field } => {
3202 format!("{}.{}", expr_with_prec(receiver, 8), field.name)
3203 }
3204 ExprKind::MethodCall {
3205 receiver,
3206 method,
3207 type_args,
3208 args,
3209 } => {
3210 let targs = if type_args.is_empty() {
3211 String::new()
3212 } else {
3213 format!(
3214 "[{}]",
3215 type_args
3216 .iter()
3217 .map(type_ref_to_string)
3218 .collect::<Vec<_>>()
3219 .join(", ")
3220 )
3221 };
3222 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
3223 format!(
3224 "{}.{}{targs}({})",
3225 expr_with_prec(receiver, 8),
3226 method.name,
3227 parts.join(", ")
3228 )
3229 }
3230 ExprKind::Match { discriminant, arms } => {
3231 let mut out = String::new();
3232 out.push_str("match ");
3233 out.push_str(&expr_with_prec(discriminant, 0));
3234 out.push_str(" {\n");
3235 for arm in arms {
3236 out.push('\t');
3237 out.push_str(&pattern_to_string(&arm.pattern));
3238 if let Some(guard) = &arm.guard {
3239 out.push_str(" if ");
3240 out.push_str(&expr_with_prec(guard, 0));
3241 }
3242 out.push_str(" => ");
3243 match &arm.body {
3244 MatchBody::Expr(e) => out.push_str(&expr_with_prec(e, 0)),
3245 MatchBody::Block(b) => out.push_str(&format_block_oneline(b)),
3246 }
3247 out.push_str(",\n");
3248 }
3249 out.push('}');
3250 out
3251 }
3252 ExprKind::Is { value, pattern } => {
3253 format!(
3254 "{} is {}",
3255 expr_with_prec(value, 4),
3256 pattern_to_string(pattern)
3257 )
3258 }
3259 ExprKind::RecordSpread {
3260 type_name,
3261 base,
3262 overrides,
3263 } => {
3264 let mut parts = vec![format!("...{}", expr_with_prec(base, 0))];
3265 for f in overrides {
3266 if let Some(v) = &f.value {
3267 parts.push(format!("{}: {}", f.name.name, expr_with_prec(v, 0)));
3268 } else {
3269 parts.push(f.name.name.clone());
3270 }
3271 }
3272 let body = parts.join(", ");
3273 match type_name {
3274 Some(tn) => format!("{} {{ {} }}", tn.name, body),
3275 None => format!("{{ {} }}", body),
3276 }
3277 }
3278 ExprKind::EffectPure(v) => format!("Effect.pure({})", expr_with_prec(v, 0)),
3279 ExprKind::Expect(v) => format!("expect {}", expr_with_prec(v, 0)),
3280 ExprKind::Val { type_ref, args } => {
3281 let t = type_ref_to_string(type_ref);
3282 if args.is_empty() {
3283 format!("Val[{t}]")
3284 } else {
3285 let a = args
3286 .iter()
3287 .map(|x| expr_with_prec(x, 0))
3288 .collect::<Vec<_>>()
3289 .join(", ");
3290 format!("Val[{t}]({a})")
3291 }
3292 }
3293 ExprKind::Wire(inner) => format!("Wire({})", expr_with_prec(inner, 0)),
3294 ExprKind::Trace { cap, op } => format!("trace({}.{})", cap.name, op.name),
3295 ExprKind::Faults(call) => format!("{} faults", expr_with_prec(call, 0)),
3297 ExprKind::Observation(o) => {
3298 let subject = format!("{}.{}", o.cap.name, o.op.name);
3299 match &o.matcher {
3300 ObservationMatcher::NeverCalled => format!("{subject} never called"),
3301 ObservationMatcher::Before { cap, op } => {
3302 format!("{subject} before {}.{}", cap.name, op.name)
3303 }
3304 ObservationMatcher::Called { count, with_pred } => {
3305 let mut s = format!("{subject} called");
3306 if let Some(c) = count {
3307 if matches!(c.kind, ExprKind::IntLit { value: 1, .. }) {
3308 s.push_str(" once");
3309 } else {
3310 s.push_str(&format!(" {} times", expr_with_prec(c, 0)));
3311 }
3312 }
3313 if let Some(p) = with_pred {
3314 s.push_str(&format!(" with {}", expr_with_prec(p, 0)));
3315 }
3316 s
3317 }
3318 }
3319 }
3320 }
3321}
3322
3323fn pattern_to_string(p: &Pattern) -> String {
3324 match p {
3325 Pattern::Wildcard(_) => "_".to_string(),
3326 Pattern::Binding(id) => id.name.clone(),
3328 Pattern::Literal { value, .. } => match value {
3330 LiteralValue::Int(n) => n.to_string(),
3331 LiteralValue::Str(s) => format!("\"{}\"", escape_string(s)),
3332 LiteralValue::Bool(b) => b.to_string(),
3333 },
3334 Pattern::Refined {
3337 inner, predicate, ..
3338 } => format!(
3339 "{} where {}",
3340 pattern_to_string(inner),
3341 refinement_to_string(predicate)
3342 ),
3343 Pattern::Variant {
3344 type_name,
3345 variant,
3346 bindings,
3347 ..
3348 } => {
3349 let name_part = match type_name {
3350 Some(t) => format!("{}.{}", t.name, variant.name),
3351 None => variant.name.clone(),
3352 };
3353 if bindings.is_empty() {
3354 name_part
3355 } else {
3356 let parts: Vec<String> = bindings
3358 .iter()
3359 .map(|b| match &b.kind {
3360 PatternBindingKind::Positional { pattern } => pattern_to_string(pattern),
3361 PatternBindingKind::Named { field, pattern } => {
3362 format!("{}: {}", field.name, pattern_to_string(pattern))
3363 }
3364 })
3365 .collect();
3366 format!("{}({})", name_part, parts.join(", "))
3367 }
3368 }
3369 Pattern::Or(alts, _) => alts
3371 .iter()
3372 .map(pattern_to_string)
3373 .collect::<Vec<_>>()
3374 .join(" | "),
3375 }
3376}
3377
3378fn omit_unit_tail(b: &Block) -> bool {
3396 matches!(b.tail.kind, ExprKind::UnitLit) && b.tail_leading_comments.is_empty()
3397}
3398
3399fn format_block_oneline(b: &Block) -> String {
3400 if b.statements.is_empty() {
3401 if b.implicit_tail {
3405 "{}".to_string()
3406 } else {
3407 format!("{{ {} }}", expr_with_prec(&b.tail, 0))
3408 }
3409 } else {
3410 let mut out = String::from("{\n");
3412 for stmt in &b.statements {
3413 out.push('\t');
3414 out.push_str(&stmt_to_string(stmt));
3415 out.push('\n');
3416 }
3417 if !omit_unit_tail(b) {
3418 out.push('\t');
3419 out.push_str(&expr_with_prec(&b.tail, 0));
3420 out.push('\n');
3421 }
3422 out.push('}');
3423 out
3424 }
3425}
3426
3427fn stmt_to_string(s: &Statement) -> String {
3428 match s {
3429 Statement::Let(l) => {
3430 let mut out = format!("let {}", l.name.name);
3431 if let Some(t) = &l.type_annot {
3432 out.push_str(&format!(": {}", type_ref_to_string(t)));
3433 }
3434 out.push_str(&format!(" = {}", expr_with_prec(&l.value, 0)));
3435 out
3436 }
3437 Statement::EffectLet(l) => {
3438 let mut out = format!("let {}", l.name.name);
3439 if let Some(t) = &l.type_annot {
3440 out.push_str(&format!(": {}", type_ref_to_string(t)));
3441 }
3442 out.push_str(&format!(" <- {}", expr_with_prec(&l.value, 0)));
3443 if let Some(p) = &l.principal {
3444 out.push_str(&format!(" {}", call_site_actor_src(p)));
3445 }
3446 out
3447 }
3448 Statement::Expect(a) => format!("expect {}", expr_with_prec(&a.value, 0)),
3449 Statement::Send(s) => format!("~> {}", expr_with_prec(&s.value, 0)),
3450 Statement::Do(d) => format!("do {}", expr_with_prec(&d.value, 0)),
3451 Statement::Assign(a) => format!("{} := {}", a.target.name, expr_with_prec(&a.value, 0)),
3452 }
3453}
3454
3455#[cfg(test)]
3456mod tests {
3457 use super::*;
3458
3459 #[test]
3462 fn crlf_formats_to_the_lf_canonical_form() {
3463 let lf = "context greeting\n\n---\nA greeting for a name.\n---\nfn greet(name: String) -> String { name }\n";
3464 assert_eq!(format_source(lf, &FormatOptions::default()).unwrap(), lf);
3465 let crlf = lf.replace('\n', "\r\n");
3466 let formatted = format_source(&crlf, &FormatOptions::default()).unwrap();
3467 assert_eq!(formatted, lf);
3468 assert!(!formatted.contains('\r'));
3469 }
3470
3471 #[test]
3472 fn normalize_line_endings_borrows_when_there_is_nothing_to_do() {
3473 assert!(matches!(
3474 normalize_line_endings("a\nb"),
3475 std::borrow::Cow::Borrowed(_)
3476 ));
3477 assert_eq!(normalize_line_endings("a\r\nb\r\n"), "a\nb\n");
3478 }
3479
3480 #[test]
3483 fn normalize_line_endings_is_a_fixed_point() {
3484 for input in ["a\r\r\nb", "a\r\nb\rc\r\n", "\r\r\r\n", "x\r", "a\r\n\rb"] {
3485 let once = normalize_line_endings(input).into_owned();
3486 assert_eq!(normalize_line_endings(&once), once, "{input:?}");
3487 assert!(!once.contains("\r\n"), "{input:?} -> {once:?}");
3488 }
3489 assert_eq!(normalize_line_endings("a\r\r\nb"), "a\nb");
3490 assert_eq!(normalize_line_endings("a\r\nb\rc"), "a\nb\rc");
3491 }
3492
3493 #[test]
3497 fn doc_block_loss_leaves_an_untokenizable_output_to_the_roundtrip_guard() {
3498 let source = "commons d\n\n---\nattached\n---\nfn f() -> Int { 1 }\n";
3499 let tokens = tokenize(source).unwrap();
3500 assert!(tokenize("commons d\n\"unterminated").is_err());
3501 assert!(
3502 doc_block_loss(source, &tokens, "commons d\n\"unterminated").is_none(),
3503 "an untokenizable output was reported as doc-block loss"
3504 );
3505 }
3506
3507 fn fmt(src: &str) -> String {
3508 format_source(src, &FormatOptions::default()).expect("format failed")
3509 }
3510
3511 #[test]
3512 fn formats_minimal_commons() {
3513 let src = "commons fitness.units {}";
3514 let out = fmt(src);
3515 assert!(out.starts_with("commons fitness.units"));
3516 let out2 = fmt(&out);
3518 assert_eq!(out, out2);
3519 }
3520
3521 #[test]
3522 fn formats_refined_type() {
3523 let src = "commons x { type Metres = Int where NonNegative }";
3524 let out = fmt(src);
3525 assert!(out.contains("type Metres = Int where NonNegative"));
3526 let out2 = fmt(&out);
3527 assert_eq!(out, out2);
3528 }
3529
3530 #[test]
3531 fn formats_function_decl() {
3532 let src = "commons x { fn add(a: Int, b: Int) -> Int { a + b } }";
3533 let out = fmt(src);
3534 assert!(out.contains("fn add(a: Int, b: Int) -> Int"));
3535 let out2 = fmt(&out);
3536 assert_eq!(out, out2);
3537 }
3538
3539 #[test]
3540 fn formats_record() {
3541 let src = "commons x { type Pt = { x: Int, y: Int } }";
3542 let out = fmt(src);
3543 let out2 = fmt(&out);
3544 assert_eq!(out, out2, "formatter not idempotent: {out}");
3545 }
3546
3547 #[test]
3548 fn formats_doc_block() {
3549 let src = "commons x {\n---\nA descriptive doc.\n---\ntype T = Int where Positive\n}";
3550 let out = fmt(src);
3551 assert!(out.contains("A descriptive doc."));
3552 let out2 = fmt(&out);
3553 assert_eq!(out, out2);
3554 }
3555
3556 #[test]
3559 fn preserves_leading_line_comment_on_decl() {
3560 let src = "commons x {\n-- explain T\ntype T = Int where NonNegative\n}";
3561 let out = fmt(src);
3562 assert!(out.contains("-- explain T"), "comment dropped: {out}");
3563 assert_eq!(out, fmt(&out));
3565 }
3566
3567 #[test]
3568 fn preserves_trailing_line_comment_on_decl() {
3569 let src = "commons x {\ntype T = Int where NonNegative -- short\n}";
3570 let out = fmt(src);
3571 assert!(out.contains("-- short"));
3572 assert!(
3574 out.lines()
3575 .any(|l| l.contains("type T") && l.contains("-- short")),
3576 "trailing comment not on same line: {out}"
3577 );
3578 assert_eq!(out, fmt(&out));
3579 }
3580
3581 #[test]
3582 fn preserves_grouped_leading_comments() {
3583 let src = "commons x {\n-- one\n-- two\ntype T = Int where Positive\n}";
3584 let out = fmt(src);
3585 assert!(out.contains("-- one"));
3586 assert!(out.contains("-- two"));
3587 let i1 = out.find("-- one").unwrap();
3589 let i2 = out.find("-- two").unwrap();
3590 let between = &out[i1..i2];
3591 assert_eq!(
3592 between.matches('\n').count(),
3593 1,
3594 "blank line inserted: {out}"
3595 );
3596 assert_eq!(out, fmt(&out));
3597 }
3598
3599 #[test]
3600 fn preserves_comment_before_block_tail() {
3601 let src = "commons x {\nfn f(n: Int) -> Int {\nlet y = n + 1\n-- result\ny\n}\n}";
3602 let out = fmt(src);
3603 assert!(out.contains("-- result"), "tail comment dropped: {out}");
3604 assert_eq!(out, fmt(&out));
3605 }
3606
3607 #[test]
3608 fn preserves_comment_with_doc_block_above_decl() {
3609 let src = "commons x {\n-- TODO: rename\n---\nThe canonical T.\n---\ntype T = Int where Positive\n}";
3610 let out = fmt(src);
3611 assert!(out.contains("-- TODO: rename"));
3612 assert!(out.contains("The canonical T."));
3613 let ic = out.find("-- TODO: rename").unwrap();
3615 let id = out.find("The canonical T.").unwrap();
3616 let it = out.find("type T").unwrap();
3617 assert!(ic < id && id < it, "ordering wrong: {out}");
3618 assert_eq!(out, fmt(&out));
3619 }
3620
3621 #[test]
3622 fn preserves_trailing_file_comment() {
3623 let src = "commons x.y\n\ntype T = Int where Positive\n-- TODO\n";
3624 let out = fmt(src);
3625 assert!(out.contains("-- TODO"));
3626 assert_eq!(out, fmt(&out));
3627 }
3628
3629 #[test]
3638 fn expression_interior_comment_is_not_fully_drained_and_still_refused() {
3639 let src = "commons x {\n fn f() -> Int {\n 1 + -- note\n 2\n }\n}\n";
3640 let tokens = tokenize(src).unwrap();
3641 let (_, _, fully_drained) = parse_units_with_drain_check(&tokens, src)
3642 .expect("a comment inside an expression is still a valid parse");
3643 assert!(
3644 !fully_drained,
3645 "an expression-interior comment must leave the trivia table undrained"
3646 );
3647 let err = format_source(src, &FormatOptions::default())
3648 .expect_err("formatting must still refuse rather than lose the comment");
3649 assert_eq!(err.errors[0].category, "bynk.fmt.comment_loss");
3650 }
3651
3652 #[test]
3657 fn ordinary_comment_is_fully_drained_and_formats_normally() {
3658 let src = "commons x {\n-- note\ntype T = Int where Positive\n}\n";
3659 let tokens = tokenize(src).unwrap();
3660 let (_, _, fully_drained) =
3661 parse_units_with_drain_check(&tokens, src).expect("should parse");
3662 assert!(
3663 fully_drained,
3664 "a declaration-leading comment must be fully drained"
3665 );
3666 let out = fmt(src);
3667 assert!(out.contains("-- note"));
3668 }
3669
3670 #[test]
3674 fn match_arm_block_tail_unit_after_assign_does_not_reattach_as_a_call() {
3675 let src = "commons x { fn f(status: T) -> T {\n match status {\n Draft => {\n status := Paid\n ()\n }\n Paid => (),\n }\n} }";
3681 let out = fmt(src);
3682 assert!(
3683 !out.contains("Paid()"),
3684 "the `()` tail must not re-attach to `Paid` as a call:\n{out}"
3685 );
3686 assert!(
3687 out.contains("status := Paid"),
3688 "the assignment must survive unmangled:\n{out}"
3689 );
3690 assert_eq!(out, fmt(&out), "must be idempotent");
3691 }
3692
3693 #[test]
3694 fn explicit_unit_tail_after_a_statement_is_omitted_like_an_implicit_one() {
3695 let src = "commons x { fn f() -> Effect[()] {\n let a = 1\n ()\n} }";
3699 let out = fmt(src);
3700 let expected = "commons x {\n\tfn f() -> Effect[()] {\n\t\tlet a = 1\n\t}\n}\n";
3701 assert_eq!(
3702 out, expected,
3703 "an explicit unit tail after a statement must be omitted"
3704 );
3705 assert_eq!(out, fmt(&out), "must be idempotent");
3706 }
3707
3708 #[test]
3711 fn code_only_canonical_ignores_comments() {
3712 let opts = FormatOptions::default();
3716 let bare = "commons x { type T = Int where Positive }";
3717 let commented = "commons x {\n-- a note\ntype T = Int where Positive -- trailing\n}";
3718 assert_eq!(
3719 code_only_canonical(bare, &opts).unwrap(),
3720 code_only_canonical(commented, &opts).unwrap(),
3721 );
3722 }
3723
3724 #[test]
3725 fn roundtrip_guard_accepts_faithful_output() {
3726 let opts = FormatOptions::default();
3728 let src = "commons x { fn add(a: Int, b: Int) -> Int { a + b } }";
3729 let out = format_source(src, &opts).unwrap();
3730 let tokens = tokenize(src).unwrap();
3731 assert!(roundtrip_divergence(&tokens, src, &out, &opts).is_none());
3732 }
3733
3734 #[test]
3735 fn roundtrip_guard_rejects_non_parsing_output() {
3736 let opts = FormatOptions::default();
3744 let src = "commons x { type T = Int where Positive }";
3745 let corrupt = "commons x { type T = Int where Positive } fn f(a: Int) -> Int { a +";
3746 assert!(
3747 corrupt.len() > src.len(),
3748 "output must be the longer buffer"
3749 );
3750 let tokens = tokenize(src).unwrap();
3751 let err = roundtrip_divergence(&tokens, src, corrupt, &opts)
3752 .expect("must reject non-parsing output");
3753 assert_eq!(err.category, "bynk.fmt.roundtrip");
3754 assert!(
3755 err.span.end <= src.len(),
3756 "roundtrip error span {:?} escapes the source it is rendered against (len {})",
3757 err.span,
3758 src.len(),
3759 );
3760 }
3761
3762 #[test]
3763 fn roundtrip_guard_rejects_structural_divergence() {
3764 let opts = FormatOptions::default();
3767 let src = "commons x { type T = Int where Positive }";
3768 let wrong = "commons x { type T = Bool }";
3769 let tokens = tokenize(src).unwrap();
3770 let err = roundtrip_divergence(&tokens, src, wrong, &opts)
3771 .expect("must reject structural divergence");
3772 assert_eq!(err.category, "bynk.fmt.roundtrip");
3773 assert!(err.span.end <= src.len(), "span escapes the source buffer");
3774 }
3775
3776 #[test]
3777 fn roundtrip_error_renders_against_source_without_panicking() {
3778 let err = roundtrip_error("the formatter produced output that no longer parses");
3785 let source = "commons x {}";
3786 let rendered = bynk_render::render_errors(std::slice::from_ref(&err), source, "<test>");
3787 assert!(
3788 rendered.contains("bynk.fmt.roundtrip"),
3789 "diagnostic did not render: {rendered}"
3790 );
3791 }
3792
3793 #[test]
3794 fn unchanged_files_without_comments_format_identically() {
3795 let src = "commons x { type T = Int where NonNegative }";
3796 let out = fmt(src);
3797 assert!(!out.contains("--"), "unexpected comment in output: {out}");
3800 }
3801
3802 #[test]
3805 fn formats_store_field_and_cell_write() {
3806 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}";
3807 let out = fmt(src);
3808 assert!(
3809 out.contains("store count: Cell[Int] = 0"),
3810 "store field not formatted: {out}"
3811 );
3812 assert!(
3813 out.contains("count := count + 1"),
3814 "cell write not formatted: {out}"
3815 );
3816 assert_eq!(out, fmt(&out), "formatter not idempotent: {out}");
3817 }
3818
3819 #[test]
3820 fn formats_store_only_agent_without_state_block() {
3821 let src = "context shop {\nagent Counter {\nkey id: String\nstore count: Cell[Int] = 0\non call get() -> Effect[Int] {\ncount\n}\n}\n}";
3822 let out = fmt(src);
3823 assert!(!out.contains("state {"), "spurious state block: {out}");
3825 assert!(out.contains("store count: Cell[Int] = 0"), "{out}");
3826 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3827 }
3828
3829 fn widths(out: &str) -> Vec<usize> {
3833 out.lines().map(|l| display_width(l, 4)).collect()
3834 }
3835
3836 fn assert_within_budget(out: &str) {
3837 let over: Vec<&str> = out.lines().filter(|l| display_width(l, 4) > 100).collect();
3838 assert!(over.is_empty(), "lines over 100 columns: {over:?}\n{out}");
3839 }
3840
3841 #[test]
3842 fn fit_test_counts_the_prefix_already_on_the_line() {
3843 let src = "commons x {\nfn authorise(amount: Int, ceiling: Int, floor: Int) -> Result[Int, Error] { if amount > ceiling { Err(Declined) } else { Ok(amount) } }\n}";
3846 let out = fmt(src);
3847 assert_within_budget(&out);
3848 assert!(
3849 out.contains("-> Result[Int, Error] {\n"),
3850 "body not broken out: {out}"
3851 );
3852 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3853 }
3854
3855 #[test]
3856 fn wraps_a_long_record_construction_one_field_per_line() {
3857 let src = "commons x {\nfn make() -> R { R { alpha: \"first value here\", beta: \"second value here\", gamma: \"third value here\", delta: \"fourth value\" } }\n}";
3858 let out = fmt(src);
3859 assert_within_budget(&out);
3860 assert!(
3861 out.contains("\t\talpha: \"first value here\",\n"),
3862 "fields not one per line: {out}"
3863 );
3864 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3865 }
3866
3867 #[test]
3868 fn wraps_a_long_argument_list_without_a_trailing_comma() {
3869 let src = "commons x {\nfn go() -> Int { combine(firstOperandValue, secondOperandValue, thirdOperandValue, fourthOperandValue) }\n}";
3873 let out = fmt(src);
3874 assert_within_budget(&out);
3875 assert!(
3876 !out.contains(",\n\t\t)"),
3877 "trailing comma in an argument list: {out}"
3878 );
3879 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3880 }
3881
3882 #[test]
3883 fn wraps_long_parameter_lists_behind_the_return_type() {
3884 let src = "context x {\nservice api from http {\non POST(\"/reservations/confirm\") (identifier: String, body: Reservation) -> Effect[HttpResult[Reservation]] by Visitor { Ok(body) }\n}\n}";
3885 let out = fmt(src);
3886 assert_within_budget(&out);
3887 assert!(
3888 out.contains("\t\t\tidentifier: String,\n"),
3889 "params not wrapped: {out}"
3890 );
3891 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3892 }
3893
3894 #[test]
3895 fn breaks_a_long_chain_at_its_dots() {
3896 let src = "commons x {\nfn go(rows: List[Row]) -> List[Int] { rows.filterOnlyTheInteresting((r) => r.nights > 0).mapEachOntoItsValue((r) => r.nights * r.rate).collect() }\n}";
3897 let out = fmt(src);
3898 assert_within_budget(&out);
3899 assert!(
3900 out.contains("\n\t\t\t.collect()"),
3901 "chain not broken at the dots: {out}"
3902 );
3903 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3904 }
3905
3906 #[test]
3907 fn keeps_a_chain_intact_when_only_an_argument_needs_wrapping() {
3908 let src = "commons x {\nfn join(parts: List[String]) -> String {\nlet init: Option[String] = None\nparts.fold(init, (acc, p) => match acc {\nSome(s) => Some(s.concat(p)),\nNone => Some(p),\n}).getOrElse(\"\")\n}\n}";
3911 let out = fmt(src);
3912 assert_within_budget(&out);
3913 assert!(
3914 out.contains("parts.fold(init, (acc, p) => match acc {"),
3915 "chain broken needlessly: {out}"
3916 );
3917 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3918 }
3919
3920 #[test]
3921 fn breaks_a_long_conjunction_before_each_operator() {
3922 let src = "commons x {\nfn ok(a: Int, b: Int, c: Int, d: Int) -> Bool { aSufficientlyLongPredicateName(a) && anotherRatherLongPredicate(b) && yetAnotherLongishPredicate(c) && theFinalPredicateHere(d) }\n}";
3923 let out = fmt(src);
3924 assert_within_budget(&out);
3925 assert!(
3926 out.lines().any(|l| l.trim_start().starts_with("&& ")),
3927 "conjunction not broken at the operators: {out}"
3928 );
3929 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3930 }
3931
3932 #[test]
3933 fn never_breaks_before_an_arithmetic_operator() {
3934 let src = "commons x {\nfn total(a: Int, b: Int, c: Int, d: Int) -> Int { someLongFunctionName(a) + anotherLongFunction(b) + aThirdLongFunction(c) + lastOne(d) }\n}";
3937 let out = fmt(src);
3938 assert!(
3939 !out.lines().any(|l| l.trim_start().starts_with("+ ")),
3940 "broke before `+`, which does not re-parse: {out}"
3941 );
3942 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3943 }
3944
3945 #[test]
3946 fn wraps_an_over_long_actor_auth_config() {
3947 let src = "context x {\nactor Partner { auth = Oidc(issuer = \"https://issuer.example.test\", audience = \"reservations-api\", jwks = \"https://issuer.example.test/jwks.json\"), identity = PartnerId }\n}";
3948 let out = fmt(src);
3949 assert_within_budget(&out);
3950 assert!(
3951 out.contains("\t\tissuer = "),
3952 "scheme args not wrapped: {out}"
3953 );
3954 assert!(out.contains("identity = PartnerId"), "identity lost: {out}");
3955 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3956 }
3957
3958 #[test]
3959 fn a_lone_block_like_argument_hugs_its_call() {
3960 let src = "commons x {\nfn go(items: List[Item]) -> Effect[()] { items.forEachInTurnAndOrder((item: Item) => { let _ <- store.put(item.identifier, item) }) }\n}";
3961 let out = fmt(src);
3962 assert_within_budget(&out);
3963 assert!(
3964 out.contains("((item: Item) => {"),
3965 "sole lambda argument did not hug its call: {out}"
3966 );
3967 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3968 }
3969
3970 #[test]
3971 fn an_unbreakable_line_is_left_long_rather_than_mangled() {
3972 let long = "x".repeat(140);
3975 let src = format!("commons x {{\nfn go() -> String {{ \"{long}\" }}\n}}");
3976 let out = fmt(&src);
3977 assert!(
3978 widths(&out).iter().any(|w| *w > 100),
3979 "expected an over-long line: {out}"
3980 );
3981 assert_eq!(out, fmt(&out), "not idempotent: {out}");
3982 }
3983
3984 #[test]
3985 fn wrapping_is_idempotent_and_structure_preserving_across_widths() {
3986 let src = "context x {\ntype R = { id: String, name: String, size: Int }\nfn build(id: String, name: String, size: Int) -> R { R { id: id, name: name, size: size } }\nfn pick(rows: List[R]) -> List[String] { rows.filter((r) => r.size > 0).map((r) => r.name).collect() }\n}";
3990 for width in [40u32, 60, 80, 100, 120] {
3991 let opts = FormatOptions {
3992 max_line_width: width,
3993 ..FormatOptions::default()
3994 };
3995 let out = format_source(src, &opts).unwrap_or_else(|e| {
3996 panic!("width {width}: format refused ({} errors)", e.errors.len())
3997 });
3998 let again = format_source(&out, &opts).unwrap_or_else(|e| {
3999 panic!(
4000 "width {width}: reformat refused ({} errors)",
4001 e.errors.len()
4002 )
4003 });
4004 assert_eq!(out, again, "width {width}: not idempotent:\n{out}");
4005 }
4006 }
4007
4008 #[test]
4011 fn event_schema_annotation_formats_and_is_idempotent() {
4012 let src = "context commerce.order {\nevent PaymentConfirmed @schema(2) = {\norderId: String,\n}\n}";
4013 let out = fmt(src);
4014 assert!(
4015 out.contains("event PaymentConfirmed @schema(2) = {"),
4016 "{out}"
4017 );
4018 assert_eq!(out, fmt(&out), "not idempotent: {out}");
4019 }
4020
4021 #[test]
4024 fn event_with_no_annotation_formats_unchanged() {
4025 let src = "context commerce.order {\nevent PaymentConfirmed = {\norderId: String,\n}\n}";
4026 let out = fmt(src);
4027 assert!(out.contains("event PaymentConfirmed = {"), "{out}");
4028 assert_eq!(out, fmt(&out), "not idempotent: {out}");
4029 }
4030
4031 #[test]
4034 fn via_schema_dispatch_formats_and_is_idempotent() {
4035 let src = "context commerce.order {\nservice OnPayment from Events(PaymentConfirmed) via schema(2) {\non event(e: PaymentConfirmed) -> Effect[()] {\nEffect.pure(())\n}\n}\n}";
4036 let out = fmt(src);
4037 assert!(
4038 out.contains("from Events(PaymentConfirmed) via schema(2)"),
4039 "{out}"
4040 );
4041 assert_eq!(out, fmt(&out), "not idempotent: {out}");
4042 }
4043
4044 #[test]
4048 fn via_schema_dispatch_combines_with_a_payload_pattern() {
4049 let src = "context commerce.order {\nservice OnPayment from Events(PaymentConfirmed { region: Domestic, .. }) via schema(2) {\non event(e: PaymentConfirmed) -> Effect[()] {\nEffect.pure(())\n}\n}\n}";
4050 let out = fmt(src);
4051 assert!(
4052 out.contains("from Events(PaymentConfirmed { region: Domestic, .. }) via schema(2)"),
4053 "{out}"
4054 );
4055 assert_eq!(out, fmt(&out), "not idempotent: {out}");
4056 }
4057}