1use crate::ast::*;
9use crate::span::Span;
10
11thread_local! {
12 static WRITTEN_SOURCE: std::cell::RefCell<Option<std::rc::Rc<str>>> =
15 const { std::cell::RefCell::new(None) };
16}
17
18pub struct WrittenForms(Option<std::rc::Rc<str>>);
20
21impl WrittenForms {
22 pub fn enter(source: &str) -> Self {
23 let new = Some(std::rc::Rc::<str>::from(source));
24 Self(WRITTEN_SOURCE.with(|cell| cell.replace(new)))
25 }
26}
27
28impl Drop for WrittenForms {
29 fn drop(&mut self) {
30 let previous = self.0.take();
31 WRITTEN_SOURCE.with(|cell| *cell.borrow_mut() = previous);
32 }
33}
34
35fn written_string_form(e: &Expr, value: Option<&str>) -> Option<String> {
41 written_literal(e.span, value)
42}
43
44fn written_literal(span: Span, value: Option<&str>) -> Option<String> {
47 WRITTEN_SOURCE.with(|cell| {
48 let source = cell.borrow();
49 let text = source.as_deref()?.get(span.range())?;
50 let special = text.starts_with("r\"")
51 || text.starts_with("r#")
52 || text.starts_with("\"\"\"")
53 || text.contains("\\u{");
54 if !special {
55 return None;
56 }
57 let text = if text.starts_with("\"\"\"") || text.starts_with("r\"\"\"") {
63 dedent_multiline(text)
64 } else {
65 text.to_string()
66 };
67 match value {
68 Some(value) => {
69 let decoded = crate::lexer::decode_string_literal(&text, span).ok()?;
70 (decoded == value).then_some(text)
71 }
72 None => (text.starts_with("\"\"\"") || text.contains("\\u{")).then_some(text),
75 }
76 })
77}
78
79fn dedent_multiline(text: &str) -> String {
82 let last = text.rsplit('\n').next().unwrap_or("");
83 let indent = &last[..last.len() - last.trim_start_matches([' ', '\t']).len()];
84 let mut out = String::with_capacity(text.len());
85 for (i, line) in text.split('\n').enumerate() {
86 if i > 0 {
87 out.push('\n');
88 }
89 if i == 0 {
90 let (line, cr) = line.strip_suffix('\r').map_or((line, ""), |l| (l, "\r"));
93 out.push_str(line.trim_end_matches([' ', '\t']));
94 out.push_str(cr);
95 } else if let Some(rest) = line.strip_prefix(indent) {
96 out.push_str(rest);
97 } else if line.trim().is_empty() {
98 } else {
100 out.push_str(line);
101 }
102 }
103 out
104}
105
106pub fn call_site_actor_src(p: &CallSiteActor) -> String {
109 match (&p.identity, &p.claims) {
110 (Some(id), Some(claims)) => format!(
112 "by {}({}, {})",
113 p.actor.name,
114 expr_with_prec(id, 0),
115 expr_with_prec(claims, 0)
116 ),
117 (Some(id), None) => format!("by {}({})", p.actor.name, expr_with_prec(id, 0)),
118 (None, _) => format!("by {}", p.actor.name),
119 }
120}
121
122pub fn type_ref_to_string(t: &TypeRef) -> String {
123 match t {
124 TypeRef::Base(b, _) => b.name().to_string(),
125 TypeRef::Named(id) => id.name.clone(),
126 TypeRef::Result(a, b, _) => format!(
127 "Result[{}, {}]",
128 type_ref_to_string(a),
129 type_ref_to_string(b)
130 ),
131 TypeRef::Option(t, _) => format!("Option[{}]", type_ref_to_string(t)),
132 TypeRef::Effect(t, _) => format!("Effect[{}]", type_ref_to_string(t)),
133 TypeRef::HttpResult(t, _) => format!("HttpResult[{}]", type_ref_to_string(t)),
134 TypeRef::QueueResult(_) => "QueueResult".to_string(),
135 TypeRef::Asset(_) => "Asset".to_string(),
136 TypeRef::RequestHeaders(_) => "RequestHeaders".to_string(),
137 TypeRef::RequestQuery(_) => "RequestQuery".to_string(),
138 TypeRef::TokenClaims(_) => "TokenClaims".to_string(),
139 TypeRef::BoundaryRejection(_) => "BoundaryRejection".to_string(),
140 TypeRef::List(t, _) => format!("List[{}]", type_ref_to_string(t)),
141 TypeRef::Set(t, _) => format!("Set[{}]", type_ref_to_string(t)),
142 TypeRef::Query(t, _) => format!("Query[{}]", type_ref_to_string(t)),
143 TypeRef::Stream(t, _) => format!("Stream[{}]", type_ref_to_string(t)),
144 TypeRef::Connection(t, _) => format!("Connection[{}]", type_ref_to_string(t)),
145 TypeRef::History(t, _) => format!("History[{}]", type_ref_to_string(t)),
146 TypeRef::Map(k, v, _) => {
147 format!("Map[{}, {}]", type_ref_to_string(k), type_ref_to_string(v))
148 }
149 TypeRef::ValidationError(_) => "ValidationError".to_string(),
150 TypeRef::JsonError(_) => "JsonError".to_string(),
151 TypeRef::Unit(_) => "()".to_string(),
152 TypeRef::App { name, args, .. } => format!(
154 "{}[{}]",
155 name.name,
156 args.iter()
157 .map(type_ref_to_string)
158 .collect::<Vec<_>>()
159 .join(", ")
160 ),
161 TypeRef::Fn(params, ret, _) => {
162 let lhs = match params.len() {
163 0 => "()".to_string(),
164 1 if !matches!(params[0], TypeRef::Fn(..)) => type_ref_to_string(¶ms[0]),
165 _ => format!(
166 "({})",
167 params
168 .iter()
169 .map(type_ref_to_string)
170 .collect::<Vec<_>>()
171 .join(", ")
172 ),
173 };
174 format!("{lhs} -> {}", type_ref_to_string(ret))
175 }
176 }
177}
178
179pub fn refinement_to_string(r: &Refinement) -> String {
180 let mut s = String::new();
181 for (i, p) in r.predicates.iter().enumerate() {
182 if i > 0 {
183 s.push_str(" && ");
184 }
185 s.push_str(&pred_to_string(p));
186 }
187 s
188}
189
190pub fn literal_text(span: Span, value: &str) -> String {
192 written_literal(span, Some(value)).unwrap_or_else(|| format!("\"{}\"", escape_string(value)))
193}
194
195pub fn matches_argument_span(p: &RefinementPred) -> Span {
197 WRITTEN_SOURCE.with(|cell| {
198 let source = cell.borrow();
199 let Some(text) = source.as_deref().and_then(|s| s.get(p.span.range())) else {
200 return p.span;
201 };
202 let (Some(open), Some(close)) = (text.find('('), text.rfind(')')) else {
203 return p.span;
204 };
205 let inner = &text[open + 1..close];
206 let lead = inner.len() - inner.trim_start().len();
207 let start = p.span.start + open + 1 + lead;
208 Span::new_in(p.span.file, start, start + inner.trim().len())
209 })
210}
211
212fn pred_to_string(p: &RefinementPred) -> String {
213 let arg = |n: i64| p.arg_text.clone().unwrap_or_else(|| n.to_string());
214 match &p.kind {
215 PredKind::Matches(re) => format!("Matches({})", literal_text(matches_argument_span(p), re)),
216 PredKind::InRange(a, b) => format!("InRange({}, {})", a.lexeme, b.lexeme),
217 PredKind::InRangeF(a, b) => format!("InRange({}, {})", a.lexeme, b.lexeme),
218 PredKind::InRangeD(a, b) => format!("InRange({}, {})", a.lexeme, b.lexeme),
219 PredKind::MinLength(n) => format!("MinLength({})", arg(*n)),
220 PredKind::MaxLength(n) => format!("MaxLength({})", arg(*n)),
221 PredKind::Length(n) => format!("Length({})", arg(*n)),
222 PredKind::NonNegative => "NonNegative".to_string(),
223 PredKind::Positive => "Positive".to_string(),
224 PredKind::NonEmpty => "NonEmpty".to_string(),
225 PredKind::IsoDate => "IsoDate".to_string(),
226 }
227}
228
229pub fn escape_string(s: &str) -> String {
230 let mut out = String::with_capacity(s.len());
231 for ch in s.chars() {
232 match ch {
233 '\\' => out.push_str("\\\\"),
234 '"' => out.push_str("\\\""),
235 '\n' => out.push_str("\\n"),
236 '\t' => out.push_str("\\t"),
237 '\r' => out.push_str("\\r"),
241 '\0' => out.push_str("\\0"),
242 c if c.is_control() => {
243 out.push_str(&format!("\\u{{{:X}}}", c as u32));
244 }
245 c => out.push(c),
246 }
247 }
248 out
249}
250
251pub fn expr_to_string(e: &Expr) -> String {
252 expr_with_prec(e, 0)
253}
254
255pub fn binop_prec(op: BinOp) -> u8 {
258 match op {
259 BinOp::Implies => 0,
261 BinOp::Or => 1,
262 BinOp::And => 2,
263 BinOp::Eq | BinOp::NotEq => 3,
264 BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => 4,
265 BinOp::Add | BinOp::Sub => 5,
266 BinOp::Mul | BinOp::Div => 6,
267 }
268}
269
270pub fn expr_with_prec(e: &Expr, parent_prec: u8) -> String {
271 match &e.kind {
272 ExprKind::IntLit { lexeme, .. } => lexeme.clone(),
275 ExprKind::FloatLit { lexeme, .. } => lexeme.clone(),
277 ExprKind::DecimalLit { lexeme, .. } => lexeme.clone(),
278 ExprKind::DurationLit { value, unit, .. } => format!("{value}.{}", unit.name()),
280 ExprKind::StrLit(s) => {
281 written_string_form(e, Some(s)).unwrap_or_else(|| format!("\"{}\"", escape_string(s)))
282 }
283 ExprKind::InterpStr(parts) => {
287 if let Some(written) = written_string_form(e, None) {
288 return written;
289 }
290 let mut out = String::from("\"");
291 for part in parts {
292 match part {
293 InterpPart::Chunk(text) => out.push_str(&escape_string(text)),
294 InterpPart::Hole(hole) => {
295 out.push_str(&format!("\\({})", expr_with_prec(hole, 0)));
296 }
297 }
298 }
299 out.push('"');
300 out
301 }
302 ExprKind::BoolLit(b) => b.to_string(),
303 ExprKind::UnitLit => "()".to_string(),
304 ExprKind::Ident(id) => id.name.clone(),
305 ExprKind::ListLit(elems) => format!(
306 "[{}]",
307 elems
308 .iter()
309 .map(expr_to_string)
310 .collect::<Vec<_>>()
311 .join(", ")
312 ),
313 ExprKind::Call {
314 name,
315 type_args,
316 args,
317 } => {
318 let targs = if type_args.is_empty() {
319 String::new()
320 } else {
321 format!(
322 "[{}]",
323 type_args
324 .iter()
325 .map(type_ref_to_string)
326 .collect::<Vec<_>>()
327 .join(", ")
328 )
329 };
330 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
331 format!("{}{}({})", name.name, targs, parts.join(", "))
332 }
333 ExprKind::BinOp(op, l, r) => {
334 let prec = binop_prec(*op);
335 let inner = format!(
336 "{} {} {}",
337 expr_with_prec(l, prec),
338 op.name(),
339 expr_with_prec(r, prec + 1)
340 );
341 if prec < parent_prec {
342 format!("({inner})")
343 } else {
344 inner
345 }
346 }
347 ExprKind::UnaryOp(op, inner) => {
348 let s = format!("{}{}", op.name(), expr_with_prec(inner, 7));
350 if parent_prec > 7 { format!("({s})") } else { s }
351 }
352 ExprKind::Paren(inner) => format!("({})", expr_with_prec(inner, 0)),
353 ExprKind::Lambda(lambda) => {
355 let params: Vec<String> = lambda.params.iter().map(lambda_param_src).collect();
356 let body = match lambda_body_src(lambda) {
357 LambdaBodySrc::Block(b) => format_block_oneline(b),
358 LambdaBodySrc::Expr(e) => match &e.kind {
359 ExprKind::Block(b) => format_block_oneline(b),
360 _ => expr_with_prec(e, 0),
361 },
362 };
363 format!("({}) => {}", params.join(", "), body)
364 }
365 ExprKind::Block(b) => format_block_oneline(b),
366 ExprKind::If {
367 cond,
368 then_block,
369 else_block,
370 } => {
371 if else_block.is_synth_unit() {
374 format!(
375 "if {} {}",
376 expr_with_prec(cond, 0),
377 format_block_oneline(then_block),
378 )
379 } else if let Some(chained) = else_if_tail(else_block) {
380 format!(
381 "if {} {} else {}",
382 expr_with_prec(cond, 0),
383 format_block_oneline(then_block),
384 expr_with_prec(chained, 0),
385 )
386 } else {
387 format!(
388 "if {} {} else {}",
389 expr_with_prec(cond, 0),
390 format_block_oneline(then_block),
391 format_block_oneline(else_block),
392 )
393 }
394 }
395 ExprKind::Ok(v) => format!("Ok({})", expr_with_prec(v, 0)),
396 ExprKind::Err(v) => format!("Err({})", expr_with_prec(v, 0)),
397 ExprKind::Some(v) => format!("Some({})", expr_with_prec(v, 0)),
398 ExprKind::None => "None".to_string(),
399 ExprKind::Question(v) => format!("{}?", expr_with_prec(v, 8)),
400 ExprKind::RecordConstruction { type_name, fields } => {
401 let parts: Vec<String> = fields
402 .iter()
403 .map(|f| match &f.value {
404 Some(v) => format!("{}: {}", f.name.name, expr_with_prec(v, 0)),
405 None => f.name.name.clone(),
406 })
407 .collect();
408 if parts.is_empty() {
409 format!("{} {{}}", type_name.name)
410 } else {
411 format!("{} {{ {} }}", type_name.name, parts.join(", "))
412 }
413 }
414 ExprKind::FieldAccess { receiver, field } => {
415 format!("{}.{}", expr_with_prec(receiver, 8), field.name)
416 }
417 ExprKind::MethodCall {
418 receiver,
419 method,
420 type_args,
421 args,
422 } => {
423 let targs = if type_args.is_empty() {
424 String::new()
425 } else {
426 format!(
427 "[{}]",
428 type_args
429 .iter()
430 .map(type_ref_to_string)
431 .collect::<Vec<_>>()
432 .join(", ")
433 )
434 };
435 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
436 format!(
437 "{}.{}{targs}({})",
438 expr_with_prec(receiver, 8),
439 method.name,
440 parts.join(", ")
441 )
442 }
443 ExprKind::Match { discriminant, arms } => {
444 let mut out = String::new();
445 out.push_str("match ");
446 out.push_str(&expr_with_prec(discriminant, 0));
447 out.push_str(" {\n");
448 for arm in arms {
449 out.push('\t');
450 out.push_str(&pattern_to_string(&arm.pattern));
451 if let Some(guard) = &arm.guard {
452 out.push_str(" if ");
453 out.push_str(&expr_with_prec(guard, 0));
454 }
455 out.push_str(" => ");
456 match &arm.body {
457 MatchBody::Expr(e) => out.push_str(&expr_with_prec(e, 0)),
458 MatchBody::Block(b) => out.push_str(&format_block_oneline(b)),
459 }
460 out.push_str(",\n");
461 }
462 out.push('}');
463 out
464 }
465 ExprKind::Is { value, pattern } => {
466 format!(
467 "{} is {}",
468 expr_with_prec(value, 4),
469 is_pattern_src(pattern)
470 )
471 }
472 ExprKind::RecordSpread {
473 type_name,
474 base,
475 overrides,
476 } => {
477 let mut parts = vec![format!("...{}", expr_with_prec(base, 0))];
478 for f in overrides {
479 if let Some(v) = &f.value {
480 parts.push(format!("{}: {}", f.name.name, expr_with_prec(v, 0)));
481 } else {
482 parts.push(f.name.name.clone());
483 }
484 }
485 let body = parts.join(", ");
486 match type_name {
487 Some(tn) => format!("{} {{ {} }}", tn.name, body),
488 None => format!("{{ {} }}", body),
489 }
490 }
491 ExprKind::EffectPure(v) => format!("Effect.pure({})", expr_with_prec(v, 0)),
492 ExprKind::Expect(v) => format!("expect {}", expr_with_prec(v, 0)),
493 ExprKind::Val { type_ref, args } => {
494 let t = type_ref_to_string(type_ref);
495 if args.is_empty() {
496 format!("Val[{t}]")
497 } else {
498 let a = args
499 .iter()
500 .map(|x| expr_with_prec(x, 0))
501 .collect::<Vec<_>>()
502 .join(", ");
503 format!("Val[{t}]({a})")
504 }
505 }
506 ExprKind::Wire(inner) => format!("Wire({})", expr_with_prec(inner, 0)),
507 ExprKind::Trace { cap, op } => format!("trace({}.{})", cap.name, op.name),
508 ExprKind::Faults(f) => match &f.principal {
512 Some(p) => format!(
513 "{} {} faults",
514 expr_with_prec(&f.call, 0),
515 call_site_actor_src(p)
516 ),
517 None => format!("{} faults", expr_with_prec(&f.call, 0)),
518 },
519 ExprKind::Observation(o) => {
520 let subject = format!("{}.{}", o.cap.name, o.op.name);
521 match &o.matcher {
522 ObservationMatcher::NeverCalled => format!("{subject} never called"),
523 ObservationMatcher::Before { cap, op } => {
524 format!("{subject} before {}.{}", cap.name, op.name)
525 }
526 ObservationMatcher::Called { count, with_pred } => {
527 let mut s = format!("{subject} called");
528 if let Some(c) = count {
529 if matches!(c.kind, ExprKind::IntLit { value: 1, .. }) {
530 s.push_str(" once");
531 } else {
532 s.push_str(&format!(" {} times", expr_with_prec(c, 0)));
533 }
534 }
535 if let Some(p) = with_pred {
536 s.push_str(&format!(" with {}", expr_with_prec(p, 0)));
537 }
538 s
539 }
540 }
541 }
542 }
543}
544
545pub fn else_if_tail(b: &Block) -> Option<&Expr> {
549 (b.statements.is_empty()
550 && b.tail_leading_comments.is_empty()
551 && !b.implicit_tail
552 && matches!(b.tail.kind, ExprKind::If { .. })
553 && b.span == b.tail.span)
554 .then_some(&*b.tail)
555}
556
557pub fn pattern_to_string(p: &Pattern) -> String {
558 match p {
559 Pattern::Wildcard(_) => "_".to_string(),
560 Pattern::Binding(id) => id.name.clone(),
562 Pattern::Literal { value, text, .. } => match value {
564 LiteralValue::Int(n) => text.clone().unwrap_or_else(|| n.to_string()),
565 LiteralValue::Str(s) => format!("\"{}\"", escape_string(s)),
566 LiteralValue::Bool(b) => b.to_string(),
567 },
568 Pattern::Range(r) => r.describe_source(),
570 Pattern::Refined {
573 inner, predicate, ..
574 } => format!(
575 "{} where {}",
576 pattern_to_string(inner),
577 refinement_to_string(predicate)
578 ),
579 Pattern::Variant {
580 type_name,
581 variant,
582 bindings,
583 ..
584 } => {
585 let name_part = match type_name {
586 Some(t) => format!("{}.{}", t.name, variant.name),
587 None => variant.name.clone(),
588 };
589 if bindings.is_empty() {
590 name_part
591 } else {
592 let parts: Vec<String> = bindings
594 .iter()
595 .map(|b| match &b.kind {
596 PatternBindingKind::Positional { pattern } => pattern_to_string(pattern),
597 PatternBindingKind::Named { field, pattern } => {
598 format!("{}: {}", field.name, pattern_to_string(pattern))
599 }
600 })
601 .collect();
602 format!("{}({})", name_part, parts.join(", "))
603 }
604 }
605 Pattern::Record {
608 type_name, fields, ..
609 } => {
610 let mut parts: Vec<String> = fields
611 .iter()
612 .map(|(name, sub)| match sub {
613 Some(p) => format!("{}: {}", name.name, pattern_to_string(p)),
614 None => name.name.clone(),
615 })
616 .collect();
617 parts.push("..".to_string());
618 format!("{} {{ {} }}", type_name.name, parts.join(", "))
619 }
620 Pattern::List { elems, rest, .. } => {
623 let mut parts: Vec<String> = elems.iter().map(pattern_to_string).collect();
624 if let Some(r) = rest {
625 parts.push(match &r.binder {
626 Some(b) => format!("..{}", b.name),
627 None => "..".to_string(),
628 });
629 }
630 format!("[{}]", parts.join(", "))
631 }
632 Pattern::Or(alts, _) => alts
634 .iter()
635 .map(pattern_to_string)
636 .collect::<Vec<_>>()
637 .join(" | "),
638 }
639}
640
641pub fn is_pattern_src(p: &Pattern) -> String {
646 let src = pattern_to_string(p);
647 let has_record = match p {
648 Pattern::Record { .. } => true,
649 Pattern::Or(alts, _) => alts.iter().any(|a| matches!(a, Pattern::Record { .. })),
650 _ => false,
651 };
652 if has_record { format!("({src})") } else { src }
653}
654
655pub fn omit_unit_tail(b: &Block) -> bool {
673 matches!(b.tail.kind, ExprKind::UnitLit) && b.tail_leading_comments.is_empty()
674}
675
676fn format_block_oneline(b: &Block) -> String {
677 if b.statements.is_empty() {
678 if b.implicit_tail {
682 "{}".to_string()
683 } else {
684 format!("{{ {} }}", expr_with_prec(&b.tail, 0))
685 }
686 } else {
687 let mut out = String::from("{\n");
689 block_body_lines(b, &mut out);
690 out.push('}');
691 out
692 }
693}
694
695fn block_body_lines(b: &Block, out: &mut String) {
699 for stmt in &b.statements {
700 if let Some((l, effectful, le)) = let_else_binding(stmt)
701 && let ExprKind::Match { arms, .. } = &b.tail.kind
702 && let Some(MatchBody::Block(rest)) = arms.first().map(|a| &a.body)
703 {
704 out.push('\t');
705 out.push_str(&let_else_to_string(l, effectful, le, arms));
706 out.push('\n');
707 block_body_lines(rest, out);
708 return;
709 }
710 out.push('\t');
711 out.push_str(&stmt_to_string(stmt));
712 out.push('\n');
713 }
714 if !omit_unit_tail(b) {
715 out.push('\t');
716 out.push_str(&expr_with_prec(&b.tail, 0));
717 out.push('\n');
718 }
719}
720
721pub enum LambdaBodySrc<'a> {
723 Expr(&'a Expr),
724 Block(&'a Block),
725}
726
727pub fn lambda_body_src(lambda: &LambdaExpr) -> LambdaBodySrc<'_> {
732 let patterns = lambda.params.iter().filter(|p| p.pattern.is_some()).count();
733 if patterns == 0 {
734 return LambdaBodySrc::Expr(&lambda.body);
735 }
736 let ExprKind::Block(block) = &lambda.body.kind else {
737 return LambdaBodySrc::Expr(&lambda.body);
738 };
739 let mut block: &Block = block;
740 for _ in 0..patterns {
741 let ExprKind::Match { arms, .. } = &block.tail.kind else {
742 return LambdaBodySrc::Expr(&lambda.body);
743 };
744 let Some(MatchBody::Block(rest)) = arms.first().map(|a| &a.body) else {
745 return LambdaBodySrc::Expr(&lambda.body);
746 };
747 block = rest;
748 }
749 if lambda.block_body {
750 LambdaBodySrc::Block(block)
751 } else {
752 LambdaBodySrc::Expr(&block.tail)
753 }
754}
755
756pub fn let_else_binding(s: &Statement) -> Option<(&LetStmt, bool, &LetElse)> {
759 match s {
760 Statement::Let(l) => l.let_else.as_ref().map(|le| (l, false, le)),
761 Statement::EffectLet(l) => l.let_else.as_ref().map(|le| (l, true, le)),
762 _ => None,
763 }
764}
765
766pub fn let_pattern_src(p: &Pattern) -> String {
772 let src = pattern_to_string(p);
773 let name_len = src
774 .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
775 .unwrap_or(src.len());
776 let opens_with_name = src.starts_with(|c: char| c.is_ascii_alphabetic())
777 && !matches!(&src[..name_len], "None" | "true" | "false");
778 let then = src[name_len..].chars().next();
779 let opens_record = src[name_len..].trim_start().starts_with('{');
781 if opens_with_name && !opens_record && !matches!(then, Some('(' | '.')) {
782 format!("({src})")
783 } else {
784 src
785 }
786}
787
788fn let_else_to_string(l: &LetStmt, effectful: bool, le: &LetElse, arms: &[MatchArm]) -> String {
791 let mut out = format!(
792 "let {} {} {}",
793 let_pattern_src(&arms[0].pattern),
794 if effectful { "<-" } else { "=" },
795 expr_with_prec(&l.value, 0)
796 );
797 if let Some(p) = &l.principal {
798 out.push_str(&format!(" {}", call_site_actor_src(p)));
799 }
800 match (le.form, arms.get(1).map(|a| &a.body)) {
801 (LetElseForm::Plain, _) => {}
802 (LetElseForm::Block, Some(MatchBody::Block(otherwise))) => {
803 out.push_str(&format!(" else {}", format_block_oneline(otherwise)));
804 }
805 _ => {
806 let rendered: Vec<String> = arms[1..]
807 .iter()
808 .map(|a| {
809 let body = match &a.body {
810 MatchBody::Expr(e) => expr_with_prec(e, 0),
811 MatchBody::Block(b) => format_block_oneline(b),
812 };
813 let guard = a
814 .guard
815 .as_ref()
816 .map(|g| format!(" if {}", expr_with_prec(g, 0)))
817 .unwrap_or_default();
818 format!("{}{guard} => {body}", pattern_to_string(&a.pattern))
819 })
820 .collect();
821 out.push_str(&format!(" else match {{ {} }}", rendered.join(", ")));
822 }
823 }
824 out
825}
826
827fn stmt_to_string(s: &Statement) -> String {
828 match s {
829 Statement::Let(l) => {
830 let mut out = format!("let {}", l.name.name);
831 if let Some(t) = &l.type_annot {
832 out.push_str(&format!(": {}", type_ref_to_string(t)));
833 }
834 out.push_str(&format!(" = {}", expr_with_prec(&l.value, 0)));
835 out
836 }
837 Statement::EffectLet(l) => {
838 let mut out = format!("let {}", l.name.name);
839 if let Some(t) = &l.type_annot {
840 out.push_str(&format!(": {}", type_ref_to_string(t)));
841 }
842 out.push_str(&format!(" <- {}", expr_with_prec(&l.value, 0)));
843 if let Some(p) = &l.principal {
844 out.push_str(&format!(" {}", call_site_actor_src(p)));
845 }
846 out
847 }
848 Statement::Expect(a) => format!(
849 "expect {}{}",
850 expr_with_prec(&a.value, 0),
851 a.snapshot.as_ref().map_or(String::new(), |c| format!(
852 " matches snapshot \"{}\"",
853 escape_string(&c.name)
854 ))
855 ),
856 Statement::Send(s) => format!("~> {}", expr_with_prec(&s.value, 0)),
857 Statement::Do(d) => format!("do {}", expr_with_prec(&d.value, 0)),
858 Statement::Assign(a) => format!("{} := {}", a.target.name, expr_with_prec(&a.value, 0)),
859 Statement::Advance(a) => {
860 format!("advance {} by {}", a.cap.name, expr_with_prec(&a.amount, 0))
861 }
862 Statement::Par(p) => {
863 let body: Vec<String> = p.statements.iter().map(stmt_to_string).collect();
864 format!("par {{ {} }}", body.join(" "))
865 }
866 }
867}
868
869pub fn lambda_param_src(p: &LambdaParam) -> String {
872 let name = if p.is_wildcard() { "_" } else { &p.name.name };
873 match (&p.pattern, &p.type_ref) {
874 (Some(pattern), _) => pattern_to_string(pattern),
875 (None, Some(tr)) => format!("{name}: {}", type_ref_to_string(tr)),
876 (None, None) => name.to_string(),
877 }
878}