1use super::*;
6use assura_ast::{ExprFolder, fold_arg_list, fold_joined, literal_to_string};
7
8pub(crate) const RESULT_VAR: &str = "__assura_result";
13
14pub(crate) const OLD_VAR_PREFIX: &str = "__assura_old_";
16
17pub(crate) fn is_numeric_expr(expr: &SpExpr) -> bool {
21 match &expr.node {
22 Expr::Ident(_) | Expr::Literal(Literal::Int(_)) | Expr::Literal(Literal::Float(_)) => true,
23 Expr::Field(_, _) => true,
24 Expr::BinOp { op, .. } => op.is_arithmetic(),
25 Expr::UnaryOp {
26 op: UnaryOp::Neg, ..
27 } => true,
28 Expr::Old(e) | Expr::Cast { expr: e, .. } => is_numeric_expr(e),
29 Expr::Call { .. } | Expr::MethodCall { .. } | Expr::Index { .. } => true,
30 Expr::Let { body, .. } => is_numeric_expr(body),
31 Expr::If { then_branch, .. } => is_numeric_expr(then_branch),
32 Expr::Match { arms, .. } => arms.first().is_some_and(|a| is_numeric_expr(&a.body)),
33 Expr::Literal(Literal::Str(_) | Literal::Bool(_))
35 | Expr::UnaryOp {
36 op: UnaryOp::Not, ..
37 }
38 | Expr::Forall { .. }
39 | Expr::Exists { .. }
40 | Expr::List(_)
41 | Expr::Tuple(_)
42 | Expr::Ghost(_)
43 | Expr::Apply { .. }
44 | Expr::Block(_)
45 | Expr::Raw(_) => false,
46 }
47}
48
49pub(crate) fn resolve_ordering_variant(body: &SpExpr) -> Option<&'static str> {
51 use assura_ast::MemoryOrdering;
52 let s = match &body.node {
53 Expr::Ident(s) => s.as_str(),
54 Expr::Raw(tokens) => {
55 return tokens
56 .iter()
57 .find_map(|t| MemoryOrdering::parse(t))
58 .map(|o| o.to_rust_ordering());
59 }
60 _ => return None,
61 };
62 MemoryOrdering::parse(s).map(|o| o.to_rust_ordering())
63}
64
65pub(crate) fn expr_to_rust(expr: &SpExpr) -> String {
67 RustCodegenFolder {
68 static_context: false,
69 }
70 .fold_expr(expr)
71}
72
73pub fn expr_to_rust_static(expr: &SpExpr) -> String {
83 RustCodegenFolder {
84 static_context: true,
85 }
86 .fold_expr(expr)
87}
88
89struct RustCodegenFolder {
96 static_context: bool,
97}
98
99impl ExprFolder for RustCodegenFolder {
100 type Output = String;
101
102 fn fold_literal(&mut self, lit: &Literal) -> String {
103 literal_to_string(lit)
104 }
105
106 fn fold_ident(&mut self, name: &str) -> String {
107 if !self.static_context && name == "result" {
108 RESULT_VAR.to_string()
109 } else {
110 name.to_string()
111 }
112 }
113
114 fn fold_field(&mut self, base: &SpExpr, field: &str) -> String {
115 format!("{}.{field}", self.fold_expr(base))
116 }
117
118 fn fold_method_call(&mut self, receiver: &SpExpr, method: &str, args: &[SpExpr]) -> String {
119 format!(
120 "{}.{method}({})",
121 self.fold_expr(receiver),
122 fold_arg_list(self, args)
123 )
124 }
125
126 fn fold_call(&mut self, func: &SpExpr, args: &[SpExpr]) -> String {
127 format!("{}({})", self.fold_expr(func), fold_arg_list(self, args))
128 }
129
130 fn fold_index(&mut self, base: &SpExpr, index: &SpExpr) -> String {
131 format!("{}[{}]", self.fold_expr(base), self.fold_expr(index))
132 }
133
134 fn fold_binop(&mut self, lhs: &SpExpr, op: &BinOp, rhs: &SpExpr) -> String {
135 if !self.static_context {
136 match op {
138 BinOp::Implies => {
139 return format!("(!{} || {})", self.fold_expr(lhs), self.fold_expr(rhs));
140 }
141 BinOp::In => {
142 return format!("{}.contains(&{})", self.fold_expr(rhs), self.fold_expr(lhs));
143 }
144 BinOp::NotIn => {
145 return format!(
146 "!{}.contains(&{})",
147 self.fold_expr(rhs),
148 self.fold_expr(lhs)
149 );
150 }
151 BinOp::Concat => {
152 return format!(
153 "[{}, {}].concat()",
154 self.fold_expr(lhs),
155 self.fold_expr(rhs)
156 );
157 }
158 _ => {}
159 }
160 let op_s = op.as_rust_str();
161 if op.is_ordering_comparison() && is_numeric_expr(lhs) && is_numeric_expr(rhs) {
162 return format!(
163 "(i128::from({}) {op_s} i128::from({}))",
164 self.fold_expr(lhs),
165 self.fold_expr(rhs)
166 );
167 }
168 }
169 format!(
170 "({} {} {})",
171 self.fold_expr(lhs),
172 op.as_rust_str(),
173 self.fold_expr(rhs)
174 )
175 }
176
177 fn fold_unary_op(&mut self, op: &UnaryOp, inner: &SpExpr) -> String {
178 let inner_s = self.fold_expr(inner);
179 if self.static_context {
180 format!("{}{inner_s}", op.as_rust_str())
181 } else {
182 format!("({}{})", op.as_rust_str(), inner_s)
183 }
184 }
185
186 fn fold_old(&mut self, inner: &SpExpr) -> String {
187 if self.static_context {
188 self.fold_expr(inner)
190 } else {
191 format!("{OLD_VAR_PREFIX}{}", old_var_name(inner))
192 }
193 }
194
195 fn fold_forall(&mut self, var: &str, domain: &SpExpr, body: &SpExpr) -> String {
196 if self.static_context {
197 let d = self.fold_expr(domain);
198 let b = self.fold_expr(body);
199 format!("/* forall {var} in {d}: {b} */ true")
200 } else {
201 format!(
202 "{}.iter().all(|{var}| {})",
203 self.fold_expr(domain),
204 self.fold_expr(body)
205 )
206 }
207 }
208
209 fn fold_exists(&mut self, var: &str, domain: &SpExpr, body: &SpExpr) -> String {
210 if self.static_context {
211 let d = self.fold_expr(domain);
212 let b = self.fold_expr(body);
213 format!("/* exists {var} in {d}: {b} */ true")
214 } else {
215 format!(
216 "{}.iter().any(|{var}| {})",
217 self.fold_expr(domain),
218 self.fold_expr(body)
219 )
220 }
221 }
222
223 fn fold_if(&mut self, cond: &SpExpr, then_br: &SpExpr, else_br: Option<&SpExpr>) -> String {
224 match else_br {
225 Some(eb) => format!(
226 "if {} {{ {} }} else {{ {} }}",
227 self.fold_expr(cond),
228 self.fold_expr(then_br),
229 self.fold_expr(eb)
230 ),
231 None => format!(
232 "if {} {{ {} }}",
233 self.fold_expr(cond),
234 self.fold_expr(then_br)
235 ),
236 }
237 }
238
239 fn fold_list(&mut self, items: &[SpExpr]) -> String {
240 format!("vec![{}]", fold_joined(self, items, ", "))
241 }
242
243 fn fold_cast(&mut self, inner: &SpExpr, ty: &str) -> String {
244 if self.static_context {
245 format!("({} as {ty})", self.fold_expr(inner))
246 } else {
247 format!("({} as {})", self.fold_expr(inner), map_type_token(ty))
248 }
249 }
250
251 fn fold_block(&mut self, exprs: &[SpExpr]) -> String {
252 fold_joined(self, exprs, " ")
253 }
254
255 fn fold_ghost(&mut self, inner: &SpExpr) -> String {
256 if self.static_context {
257 let s = self.fold_expr(inner);
258 format!("/* ghost: {s} */ ()")
259 } else {
260 "/* ghost erased */()".to_string()
261 }
262 }
263
264 fn fold_apply(&mut self, lemma_name: &str, args: &[SpExpr]) -> String {
265 if self.static_context {
266 format!("/* apply {lemma_name}({}) */ ()", fold_arg_list(self, args))
267 } else {
268 format!("/* lemma {lemma_name} applied */")
269 }
270 }
271
272 fn fold_let(&mut self, name: &str, value: &SpExpr, body: &SpExpr) -> String {
273 format!(
274 "{{ let {} = {}; {} }}",
275 name,
276 self.fold_expr(value),
277 self.fold_expr(body)
278 )
279 }
280
281 fn fold_match(&mut self, scrutinee: &SpExpr, arms: &[assura_ast::MatchArm]) -> String {
282 let scrut = self.fold_expr(scrutinee);
283 if self.static_context {
284 let arm_strs: Vec<String> = arms
285 .iter()
286 .map(|arm| {
287 let pat = match &arm.pattern {
288 assura_ast::Pattern::Ident(s) => s.clone(),
289 assura_ast::Pattern::Wildcard => "_".to_string(),
290 assura_ast::Pattern::Literal(lit) => match lit {
291 Literal::Int(s) | Literal::Float(s) => s.clone(),
292 Literal::Str(s) => format!("\"{s}\""),
293 Literal::Bool(b) => b.to_string(),
294 },
295 assura_ast::Pattern::Constructor { name, fields } => {
296 if fields.is_empty() {
297 name.clone()
298 } else {
299 format!("{name}(..)")
300 }
301 }
302 assura_ast::Pattern::Tuple(pats) => {
303 let ps: Vec<&str> = pats.iter().map(|_| "_").collect();
304 format!("({})", ps.join(", "))
305 }
306 };
307 let body = self.fold_expr(&arm.body);
308 format!("{pat} => {body}")
309 })
310 .collect();
311 format!("match {scrut} {{ {} }}", arm_strs.join(", "))
312 } else {
313 let arms_code: Vec<String> = arms
314 .iter()
315 .map(|arm| {
316 let pat = match &arm.pattern {
317 assura_ast::Pattern::Ident(name) => name.clone(),
318 assura_ast::Pattern::Wildcard => "_".into(),
319 assura_ast::Pattern::Literal(lit) => match lit {
320 Literal::Int(s) | Literal::Float(s) => s.clone(),
321 Literal::Str(s) => format!("\"{s}\""),
322 Literal::Bool(b) => b.to_string(),
323 },
324 assura_ast::Pattern::Constructor { name, fields } => {
325 if fields.is_empty() {
326 name.clone()
327 } else {
328 let fs: Vec<String> = fields.iter().map(pattern_to_rust).collect();
329 format!("{name}({})", fs.join(", "))
330 }
331 }
332 assura_ast::Pattern::Tuple(pats) => {
333 let ps: Vec<String> = pats.iter().map(pattern_to_rust).collect();
334 format!("({})", ps.join(", "))
335 }
336 };
337 let body = self.fold_expr(&arm.body);
338 format!(" {pat} => {body},")
339 })
340 .collect();
341 let has_wildcard = arms.iter().any(|arm| {
342 matches!(
343 &arm.pattern,
344 assura_ast::Pattern::Wildcard | assura_ast::Pattern::Ident(_)
345 )
346 });
347 if !has_wildcard {
348 let mut all_arms = arms_code;
349 all_arms.push(" _ => unreachable!(\"non-exhaustive match\"),".to_string());
350 format!("match {} {{\n{}\n}}", scrut, all_arms.join("\n"))
351 } else {
352 format!("match {} {{\n{}\n}}", scrut, arms_code.join("\n"))
353 }
354 }
355 }
356
357 fn fold_tuple(&mut self, items: &[SpExpr]) -> String {
358 format!("({})", fold_joined(self, items, ", "))
359 }
360
361 fn fold_raw(&mut self, tokens: &[String]) -> String {
362 if self.static_context {
363 let clean: Vec<&str> = tokens.iter().map(|s| s.as_str()).collect();
364 if clean.len() == 1 {
365 return clean[0].to_string();
366 }
367 if clean.len() >= 2 && clean[0] == "=" {
368 return clean[1..].join(" ");
369 }
370 clean.join(" ")
371 } else {
372 raw_tokens_to_rust(tokens)
373 }
374 }
375}
376
377pub(crate) fn raw_tokens_to_rust(tokens: &[String]) -> String {
384 if tokens.is_empty() {
385 return String::new();
386 }
387 let first = tokens[0].as_str();
389 if matches!(first, "forall" | "exists")
390 && tokens.len() >= 5
391 && let Some(in_pos) = tokens[1..].iter().position(|t| t == "in")
392 {
393 let in_pos = in_pos + 1; let var = &tokens[1..in_pos].join("_");
395 if let Some(colon_offset) = tokens[in_pos + 1..].iter().position(|t| t == ":") {
397 let colon_pos = in_pos + 1 + colon_offset;
398 let domain_tokens = &tokens[in_pos + 1..colon_pos];
399 let body_tokens = &tokens[colon_pos + 1..];
400
401 let domain = {
402 let mapped: Vec<&str> = domain_tokens.iter().map(|t| map_type_token(t)).collect();
403 smart_join_type_tokens(&mapped)
404 };
405 let body = raw_tokens_to_rust(body_tokens);
406
407 let method = if first == "forall" { "all" } else { "any" };
408 return format!("{domain}.iter().{method}(|{var}| {body})");
409 }
410 }
411
412 if let Some(at_pos) = tokens.iter().position(|t| t == "@") {
414 let before = &tokens[..at_pos];
415 let after = &tokens[at_pos + 1..];
416 let expr_s = raw_tokens_to_rust(before);
417 let state_s = after.join(" ");
418 return format!("true /* typestate: {expr_s} @ {state_s} */");
419 }
420
421 let mapped: Vec<String> = tokens
423 .iter()
424 .map(|t| {
425 if t == "result" {
426 RESULT_VAR.to_string()
427 } else {
428 map_type_token(t).to_string()
429 }
430 })
431 .collect();
432 let refs: Vec<&str> = mapped.iter().map(|s| s.as_str()).collect();
433 smart_join_type_tokens(&refs)
434}
435
436pub(crate) fn has_deep_field_access(expr: &str) -> bool {
446 let method_names = [
450 "iter",
451 "all",
452 "any",
453 "map",
454 "filter",
455 "len",
456 "is_empty",
457 "clone",
458 "count",
459 "sum",
460 "collect",
461 "flat_map",
462 "zip",
463 "enumerate",
464 "take",
465 "skip",
466 "find",
467 "fold",
468 "for_each",
469 "min",
470 "max",
471 "contains",
472 "position",
473 "into_iter",
474 "as_ref",
475 "as_mut",
476 "unwrap",
477 "unwrap_or",
478 "expect",
479 "ok",
480 "err",
481 "is_some",
482 "is_none",
483 "is_ok",
484 "is_err",
485 ];
486 for word in expr.split(|c: char| !c.is_alphanumeric() && c != '.' && c != '_') {
487 if word.contains('.') && !word.is_empty() {
488 let parts: Vec<&str> = word.split('.').collect();
489 if parts.len() >= 2
490 && parts[0]
491 .chars()
492 .next()
493 .is_some_and(|c| c.is_alphabetic() || c == '_')
494 {
495 let all_methods = parts[1..].iter().all(|p| method_names.contains(p));
497 if !all_methods {
498 return true;
499 }
500 }
501 }
502 }
503 let result_dot = format!("{RESULT_VAR}.");
505 if expr.contains(&result_dot) {
506 for chunk in expr.split(&result_dot) {
508 if chunk.is_empty() {
509 continue;
510 }
511 let after: String = chunk
512 .chars()
513 .take_while(|c| c.is_alphanumeric() || *c == '_')
514 .collect();
515 if !after.is_empty() && !method_names.contains(&after.as_str()) {
516 return true;
517 }
518 }
519 }
520 false
521}
522
523pub(crate) fn pattern_to_rust(pat: &assura_ast::Pattern) -> String {
525 match pat {
526 assura_ast::Pattern::Ident(name) => name.clone(),
527 assura_ast::Pattern::Wildcard => "_".into(),
528 assura_ast::Pattern::Literal(lit) => match lit {
529 Literal::Int(s) | Literal::Float(s) => s.clone(),
530 Literal::Str(s) => format!("\"{s}\""),
531 Literal::Bool(b) => b.to_string(),
532 },
533 assura_ast::Pattern::Constructor { name, fields } => {
534 if fields.is_empty() {
535 name.clone()
536 } else {
537 let fs: Vec<String> = fields.iter().map(pattern_to_rust).collect();
538 format!("{name}({})", fs.join(", "))
539 }
540 }
541 assura_ast::Pattern::Tuple(pats) => {
542 let ps: Vec<String> = pats.iter().map(pattern_to_rust).collect();
543 format!("({})", ps.join(", "))
544 }
545 }
546}
547
548pub(crate) fn old_var_name(expr: &SpExpr) -> String {
549 match &expr.node {
550 Expr::Ident(s) => s.clone(),
551 Expr::Field(recv, field) => format!("{}_{field}", old_var_name(recv)),
552 Expr::Call { func, .. } => old_var_name(func),
553 Expr::MethodCall {
554 receiver, method, ..
555 } => format!("{}_{method}", old_var_name(receiver)),
556 Expr::Index { expr: e, .. } => format!("{}_idx", old_var_name(e)),
557 Expr::Literal(lit) => match lit {
558 Literal::Int(s) | Literal::Float(s) => format!("lit_{s}"),
559 Literal::Str(s) => format!("lit_{}", s.trim_matches('"')),
560 Literal::Bool(b) => format!("lit_{b}"),
561 },
562 Expr::BinOp { lhs, op, rhs } => {
563 format!(
564 "{}_{}_{}",
565 old_var_name(lhs),
566 op.as_ident(),
567 old_var_name(rhs)
568 )
569 }
570 Expr::UnaryOp { op, expr: e } => {
571 let prefix = match op {
572 UnaryOp::Neg => "neg",
573 UnaryOp::Not => "not",
574 };
575 format!("{prefix}_{}", old_var_name(e))
576 }
577 Expr::Old(inner) => old_var_name(inner),
578 Expr::Cast { expr: e, .. } => old_var_name(e),
579 Expr::Ghost(inner) => format!("ghost_{}", old_var_name(inner)),
580 Expr::Forall { var, .. } => format!("forall_{var}"),
581 Expr::Exists { var, .. } => format!("exists_{var}"),
582 Expr::If { cond, .. } => format!("if_{}", old_var_name(cond)),
583 Expr::Let { name, .. } => format!("let_{name}"),
584 Expr::Match { scrutinee, .. } => format!("match_{}", old_var_name(scrutinee)),
585 Expr::Apply { lemma_name, .. } => format!("apply_{lemma_name}"),
586 Expr::List(_) => "list".to_string(),
587 Expr::Tuple(_) => "tuple".to_string(),
588 Expr::Block(exprs) => {
589 if let Some(first) = exprs.first() {
590 old_var_name(first)
591 } else {
592 "block".to_string()
593 }
594 }
595 Expr::Raw(tokens) => {
596 if let Some(first) = tokens.first() {
597 first.clone()
598 } else {
599 "raw".to_string()
600 }
601 }
602 }
603}
604
605pub(crate) fn collect_old_exprs(expr: &SpExpr) -> Vec<(String, String)> {
608 let mut result = Vec::new();
609 collect_old_exprs_inner(expr, &mut result);
610 result
611}
612
613pub(crate) fn collect_old_exprs_inner(expr: &SpExpr, out: &mut Vec<(String, String)>) {
614 match &expr.node {
615 Expr::Old(inner) => {
616 let var = old_var_name(inner);
617 let rust = expr_to_rust(inner);
618 if !out.iter().any(|(v, _)| v == &var) {
620 out.push((var, rust));
621 }
622 collect_old_exprs_inner(inner, out);
624 }
625 Expr::BinOp { lhs, rhs, .. } => {
626 collect_old_exprs_inner(lhs, out);
627 collect_old_exprs_inner(rhs, out);
628 }
629 Expr::UnaryOp { expr: e, .. } | Expr::Field(e, _) | Expr::Cast { expr: e, .. } => {
630 collect_old_exprs_inner(e, out);
631 }
632 Expr::Call { func, args } => {
633 collect_old_exprs_inner(func, out);
634 for a in args {
635 collect_old_exprs_inner(a, out);
636 }
637 }
638 Expr::MethodCall { receiver, args, .. } => {
639 collect_old_exprs_inner(receiver, out);
640 for a in args {
641 collect_old_exprs_inner(a, out);
642 }
643 }
644 Expr::Index { expr: e, index } => {
645 collect_old_exprs_inner(e, out);
646 collect_old_exprs_inner(index, out);
647 }
648 Expr::Forall { domain, body, .. } | Expr::Exists { domain, body, .. } => {
649 collect_old_exprs_inner(domain, out);
650 collect_old_exprs_inner(body, out);
651 }
652 Expr::If {
653 cond,
654 then_branch,
655 else_branch,
656 } => {
657 collect_old_exprs_inner(cond, out);
658 collect_old_exprs_inner(then_branch, out);
659 if let Some(eb) = else_branch {
660 collect_old_exprs_inner(eb, out);
661 }
662 }
663 Expr::List(items) | Expr::Block(items) => {
664 for item in items {
665 collect_old_exprs_inner(item, out);
666 }
667 }
668 Expr::Ghost(inner) => {
669 collect_old_exprs_inner(inner, out);
672 }
673 Expr::Apply { args, .. } => {
674 for a in args {
676 collect_old_exprs_inner(a, out);
677 }
678 }
679 Expr::Match { scrutinee, arms } => {
680 collect_old_exprs_inner(scrutinee, out);
681 for arm in arms {
682 collect_old_exprs_inner(&arm.body, out);
683 }
684 }
685 Expr::Let { value, body, .. } => {
686 collect_old_exprs_inner(value, out);
687 collect_old_exprs_inner(body, out);
688 }
689 Expr::Tuple(elems) => {
690 for e in elems {
691 collect_old_exprs_inner(e, out);
692 }
693 }
694 Expr::Literal(_) | Expr::Ident(_) | Expr::Raw(_) => {}
696 }
697}
698#[cfg(test)]
699#[path = "expr_tests.rs"]
700mod tests;