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 match &p.kind {
214 PredKind::Matches(re) => format!("Matches({})", literal_text(matches_argument_span(p), re)),
215 PredKind::InRange(a, b) => format!("InRange({}, {})", a.value, b.value),
216 PredKind::InRangeF(a, b) => format!("InRange({}, {})", a.lexeme, b.lexeme),
217 PredKind::InRangeD(a, b) => format!("InRange({}, {})", a.lexeme, b.lexeme),
218 PredKind::MinLength(n) => format!("MinLength({n})"),
219 PredKind::MaxLength(n) => format!("MaxLength({n})"),
220 PredKind::Length(n) => format!("Length({n})"),
221 PredKind::NonNegative => "NonNegative".to_string(),
222 PredKind::Positive => "Positive".to_string(),
223 PredKind::NonEmpty => "NonEmpty".to_string(),
224 PredKind::IsoDate => "IsoDate".to_string(),
225 }
226}
227
228pub fn escape_string(s: &str) -> String {
229 let mut out = String::with_capacity(s.len());
230 for ch in s.chars() {
231 match ch {
232 '\\' => out.push_str("\\\\"),
233 '"' => out.push_str("\\\""),
234 '\n' => out.push_str("\\n"),
235 '\t' => out.push_str("\\t"),
236 '\r' => out.push_str("\\r"),
240 '\0' => out.push_str("\\0"),
241 c if c.is_control() => {
242 out.push_str(&format!("\\u{{{:X}}}", c as u32));
243 }
244 c => out.push(c),
245 }
246 }
247 out
248}
249
250pub fn expr_to_string(e: &Expr) -> String {
251 expr_with_prec(e, 0)
252}
253
254pub fn binop_prec(op: BinOp) -> u8 {
257 match op {
258 BinOp::Implies => 0,
260 BinOp::Or => 1,
261 BinOp::And => 2,
262 BinOp::Eq | BinOp::NotEq => 3,
263 BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => 4,
264 BinOp::Add | BinOp::Sub => 5,
265 BinOp::Mul | BinOp::Div => 6,
266 }
267}
268
269pub fn expr_with_prec(e: &Expr, parent_prec: u8) -> String {
270 match &e.kind {
271 ExprKind::IntLit { lexeme, .. } => lexeme.clone(),
274 ExprKind::FloatLit { lexeme, .. } => lexeme.clone(),
276 ExprKind::DecimalLit { lexeme, .. } => lexeme.clone(),
277 ExprKind::DurationLit { value, unit, .. } => format!("{value}.{}", unit.name()),
279 ExprKind::StrLit(s) => {
280 written_string_form(e, Some(s)).unwrap_or_else(|| format!("\"{}\"", escape_string(s)))
281 }
282 ExprKind::InterpStr(parts) => {
286 if let Some(written) = written_string_form(e, None) {
287 return written;
288 }
289 let mut out = String::from("\"");
290 for part in parts {
291 match part {
292 InterpPart::Chunk(text) => out.push_str(&escape_string(text)),
293 InterpPart::Hole(hole) => {
294 out.push_str(&format!("\\({})", expr_with_prec(hole, 0)));
295 }
296 }
297 }
298 out.push('"');
299 out
300 }
301 ExprKind::BoolLit(b) => b.to_string(),
302 ExprKind::UnitLit => "()".to_string(),
303 ExprKind::Ident(id) => id.name.clone(),
304 ExprKind::ListLit(elems) => format!(
305 "[{}]",
306 elems
307 .iter()
308 .map(expr_to_string)
309 .collect::<Vec<_>>()
310 .join(", ")
311 ),
312 ExprKind::Call {
313 name,
314 type_args,
315 args,
316 } => {
317 let targs = if type_args.is_empty() {
318 String::new()
319 } else {
320 format!(
321 "[{}]",
322 type_args
323 .iter()
324 .map(type_ref_to_string)
325 .collect::<Vec<_>>()
326 .join(", ")
327 )
328 };
329 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
330 format!("{}{}({})", name.name, targs, parts.join(", "))
331 }
332 ExprKind::BinOp(op, l, r) => {
333 let prec = binop_prec(*op);
334 let inner = format!(
335 "{} {} {}",
336 expr_with_prec(l, prec),
337 op.name(),
338 expr_with_prec(r, prec + 1)
339 );
340 if prec < parent_prec {
341 format!("({inner})")
342 } else {
343 inner
344 }
345 }
346 ExprKind::UnaryOp(op, inner) => {
347 let s = format!("{}{}", op.name(), expr_with_prec(inner, 7));
349 if parent_prec > 7 { format!("({s})") } else { s }
350 }
351 ExprKind::Paren(inner) => format!("({})", expr_with_prec(inner, 0)),
352 ExprKind::Lambda(lambda) => {
354 let params: Vec<String> = lambda.params.iter().map(lambda_param_src).collect();
355 let body = match lambda_body_src(lambda) {
356 LambdaBodySrc::Block(b) => format_block_oneline(b),
357 LambdaBodySrc::Expr(e) => match &e.kind {
358 ExprKind::Block(b) => format_block_oneline(b),
359 _ => expr_with_prec(e, 0),
360 },
361 };
362 format!("({}) => {}", params.join(", "), body)
363 }
364 ExprKind::Block(b) => format_block_oneline(b),
365 ExprKind::If {
366 cond,
367 then_block,
368 else_block,
369 } => {
370 if else_block.is_synth_unit() {
373 format!(
374 "if {} {}",
375 expr_with_prec(cond, 0),
376 format_block_oneline(then_block),
377 )
378 } else if let Some(chained) = else_if_tail(else_block) {
379 format!(
380 "if {} {} else {}",
381 expr_with_prec(cond, 0),
382 format_block_oneline(then_block),
383 expr_with_prec(chained, 0),
384 )
385 } else {
386 format!(
387 "if {} {} else {}",
388 expr_with_prec(cond, 0),
389 format_block_oneline(then_block),
390 format_block_oneline(else_block),
391 )
392 }
393 }
394 ExprKind::Ok(v) => format!("Ok({})", expr_with_prec(v, 0)),
395 ExprKind::Err(v) => format!("Err({})", expr_with_prec(v, 0)),
396 ExprKind::Some(v) => format!("Some({})", expr_with_prec(v, 0)),
397 ExprKind::None => "None".to_string(),
398 ExprKind::Question(v) => format!("{}?", expr_with_prec(v, 8)),
399 ExprKind::RecordConstruction { type_name, fields } => {
400 let parts: Vec<String> = fields
401 .iter()
402 .map(|f| match &f.value {
403 Some(v) => format!("{}: {}", f.name.name, expr_with_prec(v, 0)),
404 None => f.name.name.clone(),
405 })
406 .collect();
407 if parts.is_empty() {
408 format!("{} {{}}", type_name.name)
409 } else {
410 format!("{} {{ {} }}", type_name.name, parts.join(", "))
411 }
412 }
413 ExprKind::FieldAccess { receiver, field } => {
414 format!("{}.{}", expr_with_prec(receiver, 8), field.name)
415 }
416 ExprKind::MethodCall {
417 receiver,
418 method,
419 type_args,
420 args,
421 } => {
422 let targs = if type_args.is_empty() {
423 String::new()
424 } else {
425 format!(
426 "[{}]",
427 type_args
428 .iter()
429 .map(type_ref_to_string)
430 .collect::<Vec<_>>()
431 .join(", ")
432 )
433 };
434 let parts: Vec<String> = args.iter().map(|a| expr_with_prec(a, 0)).collect();
435 format!(
436 "{}.{}{targs}({})",
437 expr_with_prec(receiver, 8),
438 method.name,
439 parts.join(", ")
440 )
441 }
442 ExprKind::Match { discriminant, arms } => {
443 let mut out = String::new();
444 out.push_str("match ");
445 out.push_str(&expr_with_prec(discriminant, 0));
446 out.push_str(" {\n");
447 for arm in arms {
448 out.push('\t');
449 out.push_str(&pattern_to_string(&arm.pattern));
450 if let Some(guard) = &arm.guard {
451 out.push_str(" if ");
452 out.push_str(&expr_with_prec(guard, 0));
453 }
454 out.push_str(" => ");
455 match &arm.body {
456 MatchBody::Expr(e) => out.push_str(&expr_with_prec(e, 0)),
457 MatchBody::Block(b) => out.push_str(&format_block_oneline(b)),
458 }
459 out.push_str(",\n");
460 }
461 out.push('}');
462 out
463 }
464 ExprKind::Is { value, pattern } => {
465 format!(
466 "{} is {}",
467 expr_with_prec(value, 4),
468 is_pattern_src(pattern)
469 )
470 }
471 ExprKind::RecordSpread {
472 type_name,
473 base,
474 overrides,
475 } => {
476 let mut parts = vec![format!("...{}", expr_with_prec(base, 0))];
477 for f in overrides {
478 if let Some(v) = &f.value {
479 parts.push(format!("{}: {}", f.name.name, expr_with_prec(v, 0)));
480 } else {
481 parts.push(f.name.name.clone());
482 }
483 }
484 let body = parts.join(", ");
485 match type_name {
486 Some(tn) => format!("{} {{ {} }}", tn.name, body),
487 None => format!("{{ {} }}", body),
488 }
489 }
490 ExprKind::EffectPure(v) => format!("Effect.pure({})", expr_with_prec(v, 0)),
491 ExprKind::Expect(v) => format!("expect {}", expr_with_prec(v, 0)),
492 ExprKind::Val { type_ref, args } => {
493 let t = type_ref_to_string(type_ref);
494 if args.is_empty() {
495 format!("Val[{t}]")
496 } else {
497 let a = args
498 .iter()
499 .map(|x| expr_with_prec(x, 0))
500 .collect::<Vec<_>>()
501 .join(", ");
502 format!("Val[{t}]({a})")
503 }
504 }
505 ExprKind::Wire(inner) => format!("Wire({})", expr_with_prec(inner, 0)),
506 ExprKind::Trace { cap, op } => format!("trace({}.{})", cap.name, op.name),
507 ExprKind::Faults(f) => match &f.principal {
511 Some(p) => format!(
512 "{} {} faults",
513 expr_with_prec(&f.call, 0),
514 call_site_actor_src(p)
515 ),
516 None => format!("{} faults", expr_with_prec(&f.call, 0)),
517 },
518 ExprKind::Observation(o) => {
519 let subject = format!("{}.{}", o.cap.name, o.op.name);
520 match &o.matcher {
521 ObservationMatcher::NeverCalled => format!("{subject} never called"),
522 ObservationMatcher::Before { cap, op } => {
523 format!("{subject} before {}.{}", cap.name, op.name)
524 }
525 ObservationMatcher::Called { count, with_pred } => {
526 let mut s = format!("{subject} called");
527 if let Some(c) = count {
528 if matches!(c.kind, ExprKind::IntLit { value: 1, .. }) {
529 s.push_str(" once");
530 } else {
531 s.push_str(&format!(" {} times", expr_with_prec(c, 0)));
532 }
533 }
534 if let Some(p) = with_pred {
535 s.push_str(&format!(" with {}", expr_with_prec(p, 0)));
536 }
537 s
538 }
539 }
540 }
541 }
542}
543
544pub fn else_if_tail(b: &Block) -> Option<&Expr> {
548 (b.statements.is_empty()
549 && b.tail_leading_comments.is_empty()
550 && !b.implicit_tail
551 && matches!(b.tail.kind, ExprKind::If { .. })
552 && b.span == b.tail.span)
553 .then_some(&*b.tail)
554}
555
556pub fn pattern_to_string(p: &Pattern) -> String {
557 match p {
558 Pattern::Wildcard(_) => "_".to_string(),
559 Pattern::Binding(id) => id.name.clone(),
561 Pattern::Literal { value, text, .. } => match value {
563 LiteralValue::Int(n) => text.clone().unwrap_or_else(|| n.to_string()),
564 LiteralValue::Str(s) => format!("\"{}\"", escape_string(s)),
565 LiteralValue::Bool(b) => b.to_string(),
566 },
567 Pattern::Range(r) => r.describe_source(),
569 Pattern::Refined {
572 inner, predicate, ..
573 } => format!(
574 "{} where {}",
575 pattern_to_string(inner),
576 refinement_to_string(predicate)
577 ),
578 Pattern::Variant {
579 type_name,
580 variant,
581 bindings,
582 ..
583 } => {
584 let name_part = match type_name {
585 Some(t) => format!("{}.{}", t.name, variant.name),
586 None => variant.name.clone(),
587 };
588 if bindings.is_empty() {
589 name_part
590 } else {
591 let parts: Vec<String> = bindings
593 .iter()
594 .map(|b| match &b.kind {
595 PatternBindingKind::Positional { pattern } => pattern_to_string(pattern),
596 PatternBindingKind::Named { field, pattern } => {
597 format!("{}: {}", field.name, pattern_to_string(pattern))
598 }
599 })
600 .collect();
601 format!("{}({})", name_part, parts.join(", "))
602 }
603 }
604 Pattern::Record {
607 type_name, fields, ..
608 } => {
609 let mut parts: Vec<String> = fields
610 .iter()
611 .map(|(name, sub)| match sub {
612 Some(p) => format!("{}: {}", name.name, pattern_to_string(p)),
613 None => name.name.clone(),
614 })
615 .collect();
616 parts.push("..".to_string());
617 format!("{} {{ {} }}", type_name.name, parts.join(", "))
618 }
619 Pattern::List { elems, rest, .. } => {
622 let mut parts: Vec<String> = elems.iter().map(pattern_to_string).collect();
623 if let Some(r) = rest {
624 parts.push(match &r.binder {
625 Some(b) => format!("..{}", b.name),
626 None => "..".to_string(),
627 });
628 }
629 format!("[{}]", parts.join(", "))
630 }
631 Pattern::Or(alts, _) => alts
633 .iter()
634 .map(pattern_to_string)
635 .collect::<Vec<_>>()
636 .join(" | "),
637 }
638}
639
640pub fn is_pattern_src(p: &Pattern) -> String {
645 let src = pattern_to_string(p);
646 let has_record = match p {
647 Pattern::Record { .. } => true,
648 Pattern::Or(alts, _) => alts.iter().any(|a| matches!(a, Pattern::Record { .. })),
649 _ => false,
650 };
651 if has_record { format!("({src})") } else { src }
652}
653
654pub fn omit_unit_tail(b: &Block) -> bool {
672 matches!(b.tail.kind, ExprKind::UnitLit) && b.tail_leading_comments.is_empty()
673}
674
675fn format_block_oneline(b: &Block) -> String {
676 if b.statements.is_empty() {
677 if b.implicit_tail {
681 "{}".to_string()
682 } else {
683 format!("{{ {} }}", expr_with_prec(&b.tail, 0))
684 }
685 } else {
686 let mut out = String::from("{\n");
688 block_body_lines(b, &mut out);
689 out.push('}');
690 out
691 }
692}
693
694fn block_body_lines(b: &Block, out: &mut String) {
698 for stmt in &b.statements {
699 if let Some((l, effectful, le)) = let_else_binding(stmt)
700 && let ExprKind::Match { arms, .. } = &b.tail.kind
701 && let Some(MatchBody::Block(rest)) = arms.first().map(|a| &a.body)
702 {
703 out.push('\t');
704 out.push_str(&let_else_to_string(l, effectful, le, arms));
705 out.push('\n');
706 block_body_lines(rest, out);
707 return;
708 }
709 out.push('\t');
710 out.push_str(&stmt_to_string(stmt));
711 out.push('\n');
712 }
713 if !omit_unit_tail(b) {
714 out.push('\t');
715 out.push_str(&expr_with_prec(&b.tail, 0));
716 out.push('\n');
717 }
718}
719
720pub enum LambdaBodySrc<'a> {
722 Expr(&'a Expr),
723 Block(&'a Block),
724}
725
726pub fn lambda_body_src(lambda: &LambdaExpr) -> LambdaBodySrc<'_> {
731 let patterns = lambda.params.iter().filter(|p| p.pattern.is_some()).count();
732 if patterns == 0 {
733 return LambdaBodySrc::Expr(&lambda.body);
734 }
735 let ExprKind::Block(block) = &lambda.body.kind else {
736 return LambdaBodySrc::Expr(&lambda.body);
737 };
738 let mut block: &Block = block;
739 for _ in 0..patterns {
740 let ExprKind::Match { arms, .. } = &block.tail.kind else {
741 return LambdaBodySrc::Expr(&lambda.body);
742 };
743 let Some(MatchBody::Block(rest)) = arms.first().map(|a| &a.body) else {
744 return LambdaBodySrc::Expr(&lambda.body);
745 };
746 block = rest;
747 }
748 if lambda.block_body {
749 LambdaBodySrc::Block(block)
750 } else {
751 LambdaBodySrc::Expr(&block.tail)
752 }
753}
754
755pub fn let_else_binding(s: &Statement) -> Option<(&LetStmt, bool, &LetElse)> {
758 match s {
759 Statement::Let(l) => l.let_else.as_ref().map(|le| (l, false, le)),
760 Statement::EffectLet(l) => l.let_else.as_ref().map(|le| (l, true, le)),
761 _ => None,
762 }
763}
764
765pub fn let_pattern_src(p: &Pattern) -> String {
771 let src = pattern_to_string(p);
772 let name_len = src
773 .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
774 .unwrap_or(src.len());
775 let opens_with_name = src.starts_with(|c: char| c.is_ascii_alphabetic())
776 && !matches!(&src[..name_len], "None" | "true" | "false");
777 let then = src[name_len..].chars().next();
778 let opens_record = src[name_len..].trim_start().starts_with('{');
780 if opens_with_name && !opens_record && !matches!(then, Some('(' | '.')) {
781 format!("({src})")
782 } else {
783 src
784 }
785}
786
787fn let_else_to_string(l: &LetStmt, effectful: bool, le: &LetElse, arms: &[MatchArm]) -> String {
790 let mut out = format!(
791 "let {} {} {}",
792 let_pattern_src(&arms[0].pattern),
793 if effectful { "<-" } else { "=" },
794 expr_with_prec(&l.value, 0)
795 );
796 if let Some(p) = &l.principal {
797 out.push_str(&format!(" {}", call_site_actor_src(p)));
798 }
799 match (le.form, arms.get(1).map(|a| &a.body)) {
800 (LetElseForm::Plain, _) => {}
801 (LetElseForm::Block, Some(MatchBody::Block(otherwise))) => {
802 out.push_str(&format!(" else {}", format_block_oneline(otherwise)));
803 }
804 _ => {
805 let rendered: Vec<String> = arms[1..]
806 .iter()
807 .map(|a| {
808 let body = match &a.body {
809 MatchBody::Expr(e) => expr_with_prec(e, 0),
810 MatchBody::Block(b) => format_block_oneline(b),
811 };
812 let guard = a
813 .guard
814 .as_ref()
815 .map(|g| format!(" if {}", expr_with_prec(g, 0)))
816 .unwrap_or_default();
817 format!("{}{guard} => {body}", pattern_to_string(&a.pattern))
818 })
819 .collect();
820 out.push_str(&format!(" else match {{ {} }}", rendered.join(", ")));
821 }
822 }
823 out
824}
825
826fn stmt_to_string(s: &Statement) -> String {
827 match s {
828 Statement::Let(l) => {
829 let mut out = format!("let {}", l.name.name);
830 if let Some(t) = &l.type_annot {
831 out.push_str(&format!(": {}", type_ref_to_string(t)));
832 }
833 out.push_str(&format!(" = {}", expr_with_prec(&l.value, 0)));
834 out
835 }
836 Statement::EffectLet(l) => {
837 let mut out = format!("let {}", l.name.name);
838 if let Some(t) = &l.type_annot {
839 out.push_str(&format!(": {}", type_ref_to_string(t)));
840 }
841 out.push_str(&format!(" <- {}", expr_with_prec(&l.value, 0)));
842 if let Some(p) = &l.principal {
843 out.push_str(&format!(" {}", call_site_actor_src(p)));
844 }
845 out
846 }
847 Statement::Expect(a) => format!(
848 "expect {}{}",
849 expr_with_prec(&a.value, 0),
850 a.snapshot.as_ref().map_or(String::new(), |c| format!(
851 " matches snapshot \"{}\"",
852 escape_string(&c.name)
853 ))
854 ),
855 Statement::Send(s) => format!("~> {}", expr_with_prec(&s.value, 0)),
856 Statement::Do(d) => format!("do {}", expr_with_prec(&d.value, 0)),
857 Statement::Assign(a) => format!("{} := {}", a.target.name, expr_with_prec(&a.value, 0)),
858 Statement::Advance(a) => {
859 format!("advance {} by {}", a.cap.name, expr_with_prec(&a.amount, 0))
860 }
861 Statement::Par(p) => {
862 let body: Vec<String> = p.statements.iter().map(stmt_to_string).collect();
863 format!("par {{ {} }}", body.join(" "))
864 }
865 }
866}
867
868pub fn lambda_param_src(p: &LambdaParam) -> String {
871 let name = if p.is_wildcard() { "_" } else { &p.name.name };
872 match (&p.pattern, &p.type_ref) {
873 (Some(pattern), _) => pattern_to_string(pattern),
874 (None, Some(tr)) => format!("{name}: {}", type_ref_to_string(tr)),
875 (None, None) => name.to_string(),
876 }
877}