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 } => {
319 let targs = if type_args.is_empty() {
320 String::new()
321 } else {
322 format!(
323 "[{}]",
324 type_args
325 .iter()
326 .map(type_ref_to_string)
327 .collect::<Vec<_>>()
328 .join(", ")
329 )
330 };
331 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
332 format!("{}{}({})", name.name, targs, parts.join(", "))
333 }
334 ExprKind::BinOp(op, l, r) => {
335 let prec = binop_prec(*op);
336 let inner = format!(
337 "{} {} {}",
338 expr_with_prec(l, prec),
339 op.name(),
340 expr_with_prec(r, prec + 1)
341 );
342 if prec < parent_prec {
343 format!("({inner})")
344 } else {
345 inner
346 }
347 }
348 ExprKind::UnaryOp(op, inner) => {
349 let s = format!("{}{}", op.name(), expr_with_prec(inner, 7));
351 if parent_prec > 7 { format!("({s})") } else { s }
352 }
353 ExprKind::Paren(inner) => format!("({})", expr_with_prec(inner, 0)),
354 ExprKind::Lambda(lambda) => {
356 let params: Vec<String> = lambda.params.iter().map(lambda_param_src).collect();
357 let body = match lambda_body_src(lambda) {
358 LambdaBodySrc::Block(b) => format_block_oneline(b),
359 LambdaBodySrc::Expr(e) => match &e.kind {
360 ExprKind::Block(b) => format_block_oneline(b),
361 _ => expr_with_prec(e, 0),
362 },
363 };
364 format!("({}) => {}", params.join(", "), body)
365 }
366 ExprKind::Block(b) => format_block_oneline(b),
367 ExprKind::If {
368 cond,
369 then_block,
370 else_block,
371 } => {
372 if else_block.is_synth_unit() {
375 format!(
376 "if {} {}",
377 expr_with_prec(cond, 0),
378 format_block_oneline(then_block),
379 )
380 } else if let Some(chained) = else_if_tail(else_block) {
381 format!(
382 "if {} {} else {}",
383 expr_with_prec(cond, 0),
384 format_block_oneline(then_block),
385 expr_with_prec(chained, 0),
386 )
387 } else {
388 format!(
389 "if {} {} else {}",
390 expr_with_prec(cond, 0),
391 format_block_oneline(then_block),
392 format_block_oneline(else_block),
393 )
394 }
395 }
396 ExprKind::Ok(v) => format!("Ok({})", expr_with_prec(v, 0)),
397 ExprKind::Err(v) => format!("Err({})", expr_with_prec(v, 0)),
398 ExprKind::Some(v) => format!("Some({})", expr_with_prec(v, 0)),
399 ExprKind::None => "None".to_string(),
400 ExprKind::Question(v) => format!("{}?", expr_with_prec(v, 8)),
401 ExprKind::RecordConstruction {
402 type_name, fields, ..
403 } => {
404 let parts: Vec<String> = fields
405 .iter()
406 .map(|f| match &f.value {
407 Some(v) => format!("{}: {}", f.name.name, expr_with_prec(v, 0)),
408 None => f.name.name.clone(),
409 })
410 .collect();
411 if parts.is_empty() {
412 format!("{} {{}}", type_name.name)
413 } else {
414 format!("{} {{ {} }}", type_name.name, parts.join(", "))
415 }
416 }
417 ExprKind::FieldAccess { receiver, field } => {
418 format!("{}.{}", expr_with_prec(receiver, 8), field.name)
419 }
420 ExprKind::MethodCall {
421 receiver,
422 method,
423 type_args,
424 args,
425 ..
426 } => {
427 let targs = if type_args.is_empty() {
428 String::new()
429 } else {
430 format!(
431 "[{}]",
432 type_args
433 .iter()
434 .map(type_ref_to_string)
435 .collect::<Vec<_>>()
436 .join(", ")
437 )
438 };
439 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
440 format!(
441 "{}.{}{targs}({})",
442 expr_with_prec(receiver, 8),
443 method.name,
444 parts.join(", ")
445 )
446 }
447 ExprKind::Match { discriminant, arms } => {
448 let mut out = String::new();
449 out.push_str("match ");
450 out.push_str(&expr_with_prec(discriminant, 0));
451 out.push_str(" {\n");
452 for arm in arms {
453 out.push('\t');
454 out.push_str(&pattern_to_string(&arm.pattern));
455 if let Some(guard) = &arm.guard {
456 out.push_str(" if ");
457 out.push_str(&expr_with_prec(guard, 0));
458 }
459 out.push_str(" => ");
460 match &arm.body {
461 MatchBody::Expr(e) => out.push_str(&expr_with_prec(e, 0)),
462 MatchBody::Block(b) => out.push_str(&format_block_oneline(b)),
463 }
464 out.push_str(",\n");
465 }
466 out.push('}');
467 out
468 }
469 ExprKind::Is { value, pattern } => {
470 format!(
471 "{} is {}",
472 expr_with_prec(value, 4),
473 is_pattern_src(pattern)
474 )
475 }
476 ExprKind::RecordSpread {
477 type_name,
478 base,
479 overrides,
480 ..
481 } => {
482 let mut parts = vec![format!("...{}", expr_with_prec(base, 0))];
483 for f in overrides {
484 if let Some(v) = &f.value {
485 parts.push(format!("{}: {}", f.name.name, expr_with_prec(v, 0)));
486 } else {
487 parts.push(f.name.name.clone());
488 }
489 }
490 let body = parts.join(", ");
491 match type_name {
492 Some(tn) => format!("{} {{ {} }}", tn.name, body),
493 None => format!("{{ {} }}", body),
494 }
495 }
496 ExprKind::EffectPure(v) => format!("Effect.pure({})", expr_with_prec(v, 0)),
497 ExprKind::Expect(v) => format!("expect {}", expr_with_prec(v, 0)),
498 ExprKind::Val { type_ref, args, .. } => {
499 let t = type_ref_to_string(type_ref);
500 if args.is_empty() {
501 format!("Val[{t}]")
502 } else {
503 let a = args
504 .iter()
505 .map(|x| expr_with_prec(x, 0))
506 .collect::<Vec<_>>()
507 .join(", ");
508 format!("Val[{t}]({a})")
509 }
510 }
511 ExprKind::Wire(inner) => format!("Wire({})", expr_with_prec(inner, 0)),
512 ExprKind::Trace { cap, op } => format!("trace({}.{})", cap.name, op.name),
513 ExprKind::Faults(f) => match &f.principal {
517 Some(p) => format!(
518 "{} {} faults",
519 expr_with_prec(&f.call, 0),
520 call_site_actor_src(p)
521 ),
522 None => format!("{} faults", expr_with_prec(&f.call, 0)),
523 },
524 ExprKind::Observation(o) => {
525 let subject = format!("{}.{}", o.cap.name, o.op.name);
526 match &o.matcher {
527 ObservationMatcher::NeverCalled => format!("{subject} never called"),
528 ObservationMatcher::Before { cap, op } => {
529 format!("{subject} before {}.{}", cap.name, op.name)
530 }
531 ObservationMatcher::Called { count, with_pred } => {
532 let mut s = format!("{subject} called");
533 if let Some(c) = count {
534 if matches!(c.kind, ExprKind::IntLit { value: 1, .. }) {
535 s.push_str(" once");
536 } else {
537 s.push_str(&format!(" {} times", expr_with_prec(c, 0)));
538 }
539 }
540 if let Some(p) = with_pred {
541 s.push_str(&format!(" with {}", expr_with_prec(p, 0)));
542 }
543 s
544 }
545 }
546 }
547 }
548}
549
550pub fn else_if_tail(b: &Block) -> Option<&Expr> {
554 (b.statements.is_empty()
555 && b.tail_leading_comments.is_empty()
556 && !b.implicit_tail
557 && matches!(b.tail.kind, ExprKind::If { .. })
558 && b.span == b.tail.span)
559 .then_some(&*b.tail)
560}
561
562pub fn pattern_to_string(p: &Pattern) -> String {
563 match p {
564 Pattern::Wildcard(_) => "_".to_string(),
565 Pattern::Binding(id) => id.name.clone(),
567 Pattern::Literal { value, text, .. } => match value {
569 LiteralValue::Int(n) => text.clone().unwrap_or_else(|| n.to_string()),
570 LiteralValue::Str(s) => format!("\"{}\"", escape_string(s)),
571 LiteralValue::Bool(b) => b.to_string(),
572 },
573 Pattern::Range(r) => r.describe_source(),
575 Pattern::Refined {
578 inner, predicate, ..
579 } => format!(
580 "{} where {}",
581 pattern_to_string(inner),
582 refinement_to_string(predicate)
583 ),
584 Pattern::Variant {
585 type_name,
586 variant,
587 bindings,
588 ..
589 } => {
590 let name_part = match type_name {
591 Some(t) => format!("{}.{}", t.name, variant.name),
592 None => variant.name.clone(),
593 };
594 if bindings.is_empty() {
595 name_part
596 } else {
597 let parts: Vec<String> = bindings
599 .iter()
600 .map(|b| match &b.kind {
601 PatternBindingKind::Positional { pattern } => pattern_to_string(pattern),
602 PatternBindingKind::Named { field, pattern } => {
603 format!("{}: {}", field.name, pattern_to_string(pattern))
604 }
605 })
606 .collect();
607 format!("{}({})", name_part, parts.join(", "))
608 }
609 }
610 Pattern::Record {
613 type_name, fields, ..
614 } => {
615 let mut parts: Vec<String> = fields
616 .iter()
617 .map(|(name, sub)| match sub {
618 Some(p) => format!("{}: {}", name.name, pattern_to_string(p)),
619 None => name.name.clone(),
620 })
621 .collect();
622 parts.push("..".to_string());
623 format!("{} {{ {} }}", type_name.name, parts.join(", "))
624 }
625 Pattern::List { elems, rest, .. } => {
628 let mut parts: Vec<String> = elems.iter().map(pattern_to_string).collect();
629 if let Some(r) = rest {
630 parts.push(match &r.binder {
631 Some(b) => format!("..{}", b.name),
632 None => "..".to_string(),
633 });
634 }
635 format!("[{}]", parts.join(", "))
636 }
637 Pattern::Or(alts, _) => alts
639 .iter()
640 .map(pattern_to_string)
641 .collect::<Vec<_>>()
642 .join(" | "),
643 }
644}
645
646pub fn is_pattern_src(p: &Pattern) -> String {
651 let src = pattern_to_string(p);
652 let has_record = match p {
653 Pattern::Record { .. } => true,
654 Pattern::Or(alts, _) => alts.iter().any(|a| matches!(a, Pattern::Record { .. })),
655 _ => false,
656 };
657 if has_record { format!("({src})") } else { src }
658}
659
660pub fn omit_unit_tail(b: &Block) -> bool {
678 matches!(b.tail.kind, ExprKind::UnitLit) && b.tail_leading_comments.is_empty()
679}
680
681fn format_block_oneline(b: &Block) -> String {
682 if b.statements.is_empty() {
683 if b.implicit_tail {
687 "{}".to_string()
688 } else {
689 format!("{{ {} }}", expr_with_prec(&b.tail, 0))
690 }
691 } else {
692 let mut out = String::from("{\n");
694 block_body_lines(b, &mut out);
695 out.push('}');
696 out
697 }
698}
699
700fn block_body_lines(b: &Block, out: &mut String) {
704 for stmt in &b.statements {
705 if let Some((l, effectful, le)) = let_else_binding(stmt)
706 && let ExprKind::Match { arms, .. } = &b.tail.kind
707 && let Some(MatchBody::Block(rest)) = arms.first().map(|a| &a.body)
708 {
709 out.push('\t');
710 out.push_str(&let_else_to_string(l, effectful, le, arms));
711 out.push('\n');
712 block_body_lines(rest, out);
713 return;
714 }
715 out.push('\t');
716 out.push_str(&stmt_to_string(stmt));
717 out.push('\n');
718 }
719 if !omit_unit_tail(b) {
720 out.push('\t');
721 out.push_str(&expr_with_prec(&b.tail, 0));
722 out.push('\n');
723 }
724}
725
726pub enum LambdaBodySrc<'a> {
728 Expr(&'a Expr),
729 Block(&'a Block),
730}
731
732pub fn lambda_body_src(lambda: &LambdaExpr) -> LambdaBodySrc<'_> {
737 let patterns = lambda.params.iter().filter(|p| p.pattern.is_some()).count();
738 if patterns == 0 {
739 return LambdaBodySrc::Expr(&lambda.body);
740 }
741 let ExprKind::Block(block) = &lambda.body.kind else {
742 return LambdaBodySrc::Expr(&lambda.body);
743 };
744 let mut block: &Block = block;
745 for _ in 0..patterns {
746 let ExprKind::Match { arms, .. } = &block.tail.kind else {
747 return LambdaBodySrc::Expr(&lambda.body);
748 };
749 let Some(MatchBody::Block(rest)) = arms.first().map(|a| &a.body) else {
750 return LambdaBodySrc::Expr(&lambda.body);
751 };
752 block = rest;
753 }
754 if lambda.block_body {
755 LambdaBodySrc::Block(block)
756 } else {
757 LambdaBodySrc::Expr(&block.tail)
758 }
759}
760
761pub fn let_else_binding(s: &Statement) -> Option<(&LetStmt, bool, &LetElse)> {
764 match s {
765 Statement::Let(l) => l.let_else.as_ref().map(|le| (l, false, le)),
766 Statement::EffectLet(l) => l.let_else.as_ref().map(|le| (l, true, le)),
767 _ => None,
768 }
769}
770
771pub fn let_pattern_src(p: &Pattern) -> String {
777 let src = pattern_to_string(p);
778 let name_len = src
779 .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
780 .unwrap_or(src.len());
781 let opens_with_name = src.starts_with(|c: char| c.is_ascii_alphabetic())
782 && !matches!(&src[..name_len], "None" | "true" | "false");
783 let then = src[name_len..].chars().next();
784 let opens_record = src[name_len..].trim_start().starts_with('{');
786 if opens_with_name && !opens_record && !matches!(then, Some('(' | '.')) {
787 format!("({src})")
788 } else {
789 src
790 }
791}
792
793fn let_else_to_string(l: &LetStmt, effectful: bool, le: &LetElse, arms: &[MatchArm]) -> String {
796 let mut out = format!(
797 "let {} {} {}",
798 let_pattern_src(&arms[0].pattern),
799 if effectful { "<-" } else { "=" },
800 expr_with_prec(&l.value, 0)
801 );
802 if let Some(p) = &l.principal {
803 out.push_str(&format!(" {}", call_site_actor_src(p)));
804 }
805 match (le.form, arms.get(1).map(|a| &a.body)) {
806 (LetElseForm::Plain, _) => {}
807 (LetElseForm::Block, Some(MatchBody::Block(otherwise))) => {
808 out.push_str(&format!(" else {}", format_block_oneline(otherwise)));
809 }
810 _ => {
811 let rendered: Vec<String> = arms[1..]
812 .iter()
813 .map(|a| {
814 let body = match &a.body {
815 MatchBody::Expr(e) => expr_with_prec(e, 0),
816 MatchBody::Block(b) => format_block_oneline(b),
817 };
818 let guard = a
819 .guard
820 .as_ref()
821 .map(|g| format!(" if {}", expr_with_prec(g, 0)))
822 .unwrap_or_default();
823 format!("{}{guard} => {body}", pattern_to_string(&a.pattern))
824 })
825 .collect();
826 out.push_str(&format!(" else match {{ {} }}", rendered.join(", ")));
827 }
828 }
829 out
830}
831
832fn stmt_to_string(s: &Statement) -> String {
833 match s {
834 Statement::Let(l) => {
835 let mut out = format!("let {}", l.name.name);
836 if let Some(t) = &l.type_annot {
837 out.push_str(&format!(": {}", type_ref_to_string(t)));
838 }
839 out.push_str(&format!(" = {}", expr_with_prec(&l.value, 0)));
840 out
841 }
842 Statement::EffectLet(l) => {
843 let mut out = format!("let {}", l.name.name);
844 if let Some(t) = &l.type_annot {
845 out.push_str(&format!(": {}", type_ref_to_string(t)));
846 }
847 out.push_str(&format!(" <- {}", expr_with_prec(&l.value, 0)));
848 if let Some(p) = &l.principal {
849 out.push_str(&format!(" {}", call_site_actor_src(p)));
850 }
851 out
852 }
853 Statement::Expect(a) => format!(
854 "expect {}{}",
855 expr_with_prec(&a.value, 0),
856 a.snapshot.as_ref().map_or(String::new(), |c| format!(
857 " matches snapshot \"{}\"",
858 escape_string(&c.name)
859 ))
860 ),
861 Statement::Send(s) => format!("~> {}", expr_with_prec(&s.value, 0)),
862 Statement::Do(d) => format!("do {}", expr_with_prec(&d.value, 0)),
863 Statement::Assign(a) => format!("{} := {}", a.target.name, expr_with_prec(&a.value, 0)),
864 Statement::Advance(a) => {
865 format!("advance {} by {}", a.cap.name, expr_with_prec(&a.amount, 0))
866 }
867 Statement::Par(p) => {
868 let body: Vec<String> = p.statements.iter().map(stmt_to_string).collect();
869 format!("par {{ {} }}", body.join(" "))
870 }
871 }
872}
873
874pub fn lambda_param_src(p: &LambdaParam) -> String {
877 let name = if p.is_wildcard() { "_" } else { &p.name.name };
878 match (&p.pattern, &p.type_ref) {
879 (Some(pattern), _) => pattern_to_string(pattern),
880 (None, Some(tr)) => format!("{name}: {}", type_ref_to_string(tr)),
881 (None, None) => name.to_string(),
882 }
883}