1use super::{
4 BinaryOp, Case, Decimal, Decl, Expr, FnDecl, HashMap, HashSet, LitValue, NAT, Node, Pattern,
5 Semantics, Type, binary_node, text_lit,
6};
7
8pub(super) fn reuse_successors(expr: &Expr, predecessors: &HashMap<String, String>) -> Expr {
12 match &expr.node {
13 Node::Binary {
14 op: BinaryOp::Add,
15 left,
16 right,
17 ..
18 } if expr.ty == Type::Nat
19 && left
20 .var_name()
21 .is_some_and(|name| predecessors.contains_key(name))
22 && matches!(&right.node, Node::Lit { value: LitValue::Text(one) } if one == "1") =>
23 {
24 let name = &predecessors[left.var_name().unwrap_or_default()];
25 Expr::var(name.clone(), expr.ty.clone()).with_span(expr.span)
26 }
27 Node::Let { name, value, body } => {
28 let inner = without(predecessors, &[name.as_str()]);
29 Expr {
30 node: Node::Let {
31 name: name.clone(),
32 value: Box::new(reuse_successors(value, predecessors)),
33 body: Box::new(reuse_successors(body, &inner)),
34 },
35 ..expr.clone()
36 }
37 }
38 Node::Match { scrutinee, cases } => {
39 let cases = cases
40 .iter()
41 .map(|kase| {
42 let bound: Vec<&str> = match &kase.pattern {
43 Pattern::NatSucc { name } => vec![name.as_str()],
44 Pattern::Ctor { binds, .. } => {
45 binds.iter().flatten().map(String::as_str).collect()
46 }
47 _ => Vec::new(),
48 };
49 let mut inner = without(predecessors, &bound);
50 if let (Pattern::NatSucc { name }, Some(variable)) =
51 (&kase.pattern, scrutinee.var_name())
52 && !bound.contains(&variable)
53 {
54 inner.insert(name.clone(), variable.to_owned());
55 }
56 Case {
57 pattern: kase.pattern.clone(),
58 body: reuse_successors(&kase.body, &inner),
59 }
60 })
61 .collect();
62 Expr {
63 node: Node::Match {
64 scrutinee: Box::new(reuse_successors(scrutinee, predecessors)),
65 cases,
66 },
67 ..expr.clone()
68 }
69 }
70 _ => expr.map_children(&mut |child| reuse_successors(child, predecessors)),
71 }
72}
73
74pub(super) fn without(map: &HashMap<String, String>, names: &[&str]) -> HashMap<String, String> {
75 map.iter()
76 .filter(|(key, value)| !names.contains(&key.as_str()) && !names.contains(&value.as_str()))
77 .map(|(key, value)| (key.clone(), value.clone()))
78 .collect()
79}
80
81pub(super) fn analyse_recursion(declarations: &mut [Decl]) {
85 for decl in declarations.iter_mut() {
86 let Decl::Fn(entry) = decl else { continue };
87 let mut calls = Vec::new();
88 collect_calls(&entry.body, &mut calls);
89 entry.recursive = calls.contains(&entry.full_name);
90 entry.decreasing = if entry.recursive {
91 structural_parameter(entry)
92 } else {
93 None
94 };
95 if entry.recursive && entry.decreasing.is_none() {
96 expose_natural_cases(entry);
97 }
98 }
99 let mut graph: HashMap<String, HashSet<String>> = HashMap::new();
100 for decl in declarations.iter() {
101 let Decl::Fn(entry) = decl else { continue };
102 let mut calls = Vec::new();
103 collect_calls(&entry.body, &mut calls);
104 graph.insert(
105 entry.full_name.clone(),
106 calls
107 .into_iter()
108 .filter(|call| *call != entry.full_name)
109 .collect(),
110 );
111 }
112 for decl in declarations.iter_mut() {
113 let Decl::Fn(entry) = decl else { continue };
114 let targets = &graph[&entry.full_name];
115 if targets
116 .iter()
117 .any(|target| reaches(&graph, target, &entry.full_name, &mut HashSet::new()))
118 {
119 entry.mutual = true;
120 }
121 }
122}
123
124pub(super) fn reaches(
125 graph: &HashMap<String, HashSet<String>>,
126 from: &str,
127 to: &str,
128 seen: &mut HashSet<String>,
129) -> bool {
130 if from == to {
131 return true;
132 }
133 if !seen.insert(from.to_owned()) {
134 return false;
135 }
136 graph
137 .get(from)
138 .is_some_and(|targets| targets.iter().any(|next| reaches(graph, next, to, seen)))
139}
140
141pub fn collect_calls(expr: &Expr, calls: &mut Vec<String>) {
143 if let Node::Call { func, .. } = &expr.node {
144 calls.push(func.clone());
145 }
146 for child in expr.children() {
147 collect_calls(child, calls);
148 }
149}
150
151pub(super) fn structural_parameter(entry: &FnDecl) -> Option<usize> {
152 entry.params.iter().enumerate().position(|(index, param)| {
153 decreases_on(
154 &entry.body,
155 &entry.full_name,
156 index,
157 ¶m.name,
158 &HashSet::new(),
159 )
160 })
161}
162
163pub(super) fn decreases_on(
165 expr: &Expr,
166 func: &str,
167 index: usize,
168 name: &str,
169 subterms: &HashSet<String>,
170) -> bool {
171 match &expr.node {
172 Node::Call { func: callee, args } => {
173 if callee == func {
174 let decreasing = args
175 .get(index)
176 .and_then(Expr::var_name)
177 .is_some_and(|arg| subterms.contains(arg));
178 if !decreasing {
179 return false;
180 }
181 }
182 args.iter()
183 .all(|arg| decreases_on(arg, func, index, name, subterms))
184 }
185 Node::Match { scrutinee, cases } => {
186 if !decreases_on(scrutinee, func, index, name, subterms) {
187 return false;
188 }
189 let is_param = scrutinee
190 .var_name()
191 .is_some_and(|variable| variable == name || subterms.contains(variable));
192 cases.iter().all(|kase| {
193 let mut inner = subterms.clone();
194 if is_param {
195 match &kase.pattern {
196 Pattern::NatSucc { name } => {
197 inner.insert(name.clone());
198 }
199 Pattern::Ctor { binds, .. } => {
200 inner.extend(binds.iter().flatten().cloned());
201 }
202 _ => {}
203 }
204 }
205 decreases_on(&kase.body, func, index, name, &inner)
206 })
207 }
208 Node::Let {
209 name: local,
210 value,
211 body,
212 } => {
213 if !decreases_on(value, func, index, name, subterms) {
214 return false;
215 }
216 let mut inner = subterms.clone();
217 inner.remove(local);
218 if value
220 .var_name()
221 .is_some_and(|variable| subterms.contains(variable))
222 {
223 inner.insert(local.clone());
224 }
225 decreases_on(body, func, index, name, &inner)
226 }
227 _ => expr
228 .children()
229 .into_iter()
230 .all(|child| decreases_on(child, func, index, name, subterms)),
231 }
232}
233
234pub(super) fn expose_natural_cases(entry: &mut FnDecl) {
243 for index in 0..entry.params.len() {
244 let param = &entry.params[index];
245 if !param.ty.is_natural() {
246 continue;
247 }
248 let mut fresh = 0;
249 let Some(body) = split_natural(&entry.body, ¶m.name, &mut fresh) else {
250 continue;
251 };
252 if decreases_on(&body, &entry.full_name, index, ¶m.name, &HashSet::new()) {
253 entry.body = body;
254 entry.decreasing = Some(index);
255 return;
256 }
257 }
258}
259
260pub(super) fn nat_literal(expr: &Expr) -> Option<Decimal> {
262 match &expr.node {
263 Node::Lit {
264 value: LitValue::Text(text),
265 } if !text.is_empty() && text.bytes().all(|byte| byte.is_ascii_digit()) => {
266 Decimal::parse(text)
267 }
268 _ => None,
269 }
270}
271
272pub(super) fn decrement(value: &Decimal) -> String {
274 let mut digits: Vec<u8> = value.digits.bytes().collect();
275 for digit in digits.iter_mut().rev() {
276 if *digit == b'0' {
277 *digit = b'9';
278 } else {
279 *digit -= 1;
280 break;
281 }
282 }
283 let text = String::from_utf8(digits).unwrap_or_default();
284 let trimmed = text.trim_start_matches('0');
285 if trimmed.is_empty() {
286 "0".to_owned()
287 } else {
288 trimmed.to_owned()
289 }
290}
291
292pub(super) fn zero_test(cond: &Expr, name: &str) -> Option<bool> {
294 let Node::Binary {
295 op: op @ (BinaryOp::Eq | BinaryOp::Ne),
296 left,
297 right,
298 domain: Some(domain),
299 ..
300 } = &cond.node
301 else {
302 return None;
303 };
304 if !domain.is_natural() {
305 return None;
306 }
307 let zero_on = |a: &Expr, b: &Expr| {
308 a.var_name() == Some(name) && nat_literal(b).is_some_and(|value| value.digits == "0")
309 };
310 (zero_on(left, right) || zero_on(right, left)).then_some(*op == BinaryOp::Eq)
311}
312
313pub(super) fn binds(cases: &[Case], name: &str) -> bool {
315 cases
316 .iter()
317 .any(|kase| kase.pattern.bound().contains(&name))
318}
319
320pub(super) fn split_natural(expr: &Expr, name: &str, fresh: &mut usize) -> Option<Expr> {
322 if let Node::If {
323 cond,
324 then,
325 otherwise,
326 } = &expr.node
327 && let Some(zero_then) = zero_test(cond, name)
328 {
329 let (zero, positive) = if zero_then {
330 (then, otherwise)
331 } else {
332 (otherwise, then)
333 };
334 *fresh += 1;
335 let pred = format!("ml_p{fresh}");
336 let ty = match &cond.node {
337 Node::Binary {
338 domain: Some(domain),
339 ..
340 } => domain.clone(),
341 _ => NAT,
342 };
343 let succ_body = predecessor_of(positive, name, &pred, &ty);
344 let succ_body = split_or_same(&succ_body, &pred, fresh);
345 let succ_body = split_or_same(&succ_body, name, fresh);
346 let zero = split_or_same(zero, name, fresh);
347 return Some(Expr {
348 node: Node::Match {
349 scrutinee: Box::new(Expr::var(name, ty).with_span(cond.span)),
350 cases: vec![
351 Case {
352 pattern: Pattern::NatZero,
353 body: zero,
354 },
355 Case {
356 pattern: Pattern::NatSucc { name: pred },
357 body: succ_body,
358 },
359 ],
360 },
361 ty: expr.ty.clone(),
362 span: expr.span,
363 });
364 }
365 match &expr.node {
366 Node::Let { name: local, .. } if local == name => return None,
367 Node::Match { cases, .. } if binds(cases, name) => return None,
368 _ => {}
369 }
370 let mut changed = false;
371 let copy = expr.map_children(&mut |child| {
372 split_natural(child, name, fresh).map_or_else(
373 || child.clone(),
374 |next| {
375 changed = true;
376 next
377 },
378 )
379 });
380 changed.then_some(copy)
381}
382
383pub(super) fn split_or_same(expr: &Expr, name: &str, fresh: &mut usize) -> Expr {
384 split_natural(expr, name, fresh).unwrap_or_else(|| expr.clone())
385}
386
387pub(super) fn predecessor_of(expr: &Expr, name: &str, pred: &str, ty: &Type) -> Expr {
390 let names = HashSet::from([name.to_owned()]);
391 Predecessor { pred, ty }.visit(expr, &names)
392}
393
394pub(super) struct Predecessor<'a> {
395 pred: &'a str,
396 ty: &'a Type,
397}
398
399impl Predecessor<'_> {
400 fn pred_var(&self) -> Expr {
401 Expr::var(self.pred, self.ty.clone())
402 }
403
404 fn minus(&self, k: &Decimal, like: &Expr) -> Expr {
405 if k.digits == "1" {
406 return self.pred_var();
407 }
408 let Node::Binary {
409 op,
410 semantics,
411 rounding,
412 by_zero,
413 ..
414 } = &like.node
415 else {
416 return like.clone();
417 };
418 Expr {
419 node: binary_node(
420 *op,
421 Box::new(self.pred_var()),
422 Box::new(text_lit(self.ty.clone(), decrement(k))),
423 Some(self.ty.clone()),
424 *semantics,
425 *rounding,
426 *by_zero,
427 ),
428 ty: self.ty.clone(),
429 span: like.span,
430 }
431 }
432
433 fn visit(&self, node: &Expr, names: &HashSet<String>) -> Expr {
434 let is_alias = |candidate: &Expr| {
435 candidate
436 .var_name()
437 .is_some_and(|name| names.contains(name))
438 };
439 if let Node::Binary {
440 op,
441 left,
442 right,
443 domain: Some(domain),
444 semantics,
445 rounding,
446 by_zero,
447 } = &node.node
448 && domain.is_natural()
449 && is_alias(left)
450 && let Some(k) = nat_literal(right).filter(|k| k.digits != "0")
451 {
452 if *op == BinaryOp::Sub {
453 return self.minus(&k, node);
454 }
455 if matches!(op, BinaryOp::Eq | BinaryOp::Ne) {
456 return Expr {
457 node: binary_node(
458 *op,
459 Box::new(self.pred_var()),
460 Box::new(text_lit(self.ty.clone(), decrement(&k))),
461 Some(domain.clone()),
462 *semantics,
463 *rounding,
464 *by_zero,
465 ),
466 ..node.clone()
467 };
468 }
469 }
470 if let Node::Cast {
473 arg, to: Type::Nat, ..
474 } = &node.node
475 && let Node::Binary {
476 op: BinaryOp::Sub,
477 left,
478 right,
479 ..
480 } = &arg.node
481 && let Node::Cast {
482 arg: inner,
483 from: Type::Nat,
484 ..
485 } = &left.node
486 && is_alias(inner)
487 && let Some(k) = nat_literal(right).filter(|k| k.digits != "0")
488 {
489 let like = Expr {
490 node: binary_node(
491 BinaryOp::Sub,
492 Box::new(self.pred_var()),
493 Box::new(self.pred_var()),
494 None,
495 Some(Semantics::Truncated),
496 None,
497 None,
498 ),
499 ty: self.ty.clone(),
500 span: node.span,
501 };
502 return self.minus(&k, &like);
503 }
504 match &node.node {
505 Node::Let {
506 name: local,
507 value,
508 body,
509 } => {
510 let mut inner = names.clone();
511 inner.remove(local);
512 let value_visited = self.visit(value, names);
513 if local == self.pred {
514 return Expr {
515 node: Node::Let {
516 name: local.clone(),
517 value: Box::new(value_visited),
518 body: body.clone(),
519 },
520 ..node.clone()
521 };
522 }
523 if is_alias(value) {
524 inner.insert(local.clone());
525 }
526 Expr {
527 node: Node::Let {
528 name: local.clone(),
529 value: Box::new(value_visited),
530 body: Box::new(self.visit(body, &inner)),
531 },
532 ..node.clone()
533 }
534 }
535 Node::Match { scrutinee, cases } => {
536 let scrutinee = Box::new(self.visit(scrutinee, names));
537 if binds(cases, self.pred) {
538 return Expr {
539 node: Node::Match {
540 scrutinee,
541 cases: cases.clone(),
542 },
543 ..node.clone()
544 };
545 }
546 let cases = cases
547 .iter()
548 .map(|kase| {
549 let bound = kase.pattern.bound();
550 let inner: HashSet<String> = names
551 .iter()
552 .filter(|candidate| !bound.contains(&candidate.as_str()))
553 .cloned()
554 .collect();
555 Case {
556 pattern: kase.pattern.clone(),
557 body: self.visit(&kase.body, &inner),
558 }
559 })
560 .collect();
561 Expr {
562 node: Node::Match { scrutinee, cases },
563 ..node.clone()
564 }
565 }
566 _ => node.map_children(&mut |child| self.visit(child, names)),
567 }
568 }
569}