1use super::types::{BracketMethod, CombRule, CombTerm, LambdaToComb, NormalForm, ReductionStep};
4
5pub fn reduce_step(t: &CombTerm) -> Option<(CombTerm, CombRule)> {
12 match t {
13 CombTerm::App(f, x) => {
15 if let CombTerm::I = f.as_ref() {
16 return Some((*x.clone(), CombRule::IRule));
17 }
18 if let CombTerm::App(k, x_inner) = f.as_ref() {
20 if let CombTerm::K = k.as_ref() {
21 return Some((*x_inner.clone(), CombRule::KRule));
22 }
23 }
24 if let CombTerm::App(sx, y) = f.as_ref() {
26 if let CombTerm::App(s, x_inner) = sx.as_ref() {
27 if let CombTerm::S = s.as_ref() {
28 let xz = CombTerm::App(x_inner.clone(), x.clone());
29 let yz = CombTerm::App(y.clone(), x.clone());
30 let result = CombTerm::App(Box::new(xz), Box::new(yz));
31 return Some((result, CombRule::SRule));
32 }
33 }
34 }
35 if let Some((f2, rule)) = reduce_step(f) {
37 return Some((CombTerm::App(Box::new(f2), x.clone()), rule));
38 }
39 if let Some((x2, rule)) = reduce_step(x) {
41 return Some((CombTerm::App(f.clone(), Box::new(x2)), rule));
42 }
43 None
44 }
45 CombTerm::S | CombTerm::K | CombTerm::I | CombTerm::Var(_) | CombTerm::Const(_) => None,
46 }
47}
48
49pub fn reduce_to_normal(mut t: CombTerm, max_steps: usize) -> Result<NormalForm, String> {
54 for step in 0..max_steps {
55 match reduce_step(&t) {
56 None => return Ok(NormalForm(t)),
57 Some((next, _rule)) => {
58 t = next;
59 let _ = step; }
61 }
62 }
63 Err(format!(
64 "Term did not reach normal form within {} steps",
65 max_steps
66 ))
67}
68
69pub fn reduce_with_trace(
74 mut t: CombTerm,
75 max_steps: usize,
76) -> Result<(Vec<ReductionStep>, NormalForm), String> {
77 let mut steps = Vec::new();
78 for _ in 0..max_steps {
79 match reduce_step(&t) {
80 None => return Ok((steps, NormalForm(t))),
81 Some((next, rule)) => {
82 steps.push(ReductionStep {
83 from: t.clone(),
84 to: next.clone(),
85 rule_applied: rule,
86 });
87 t = next;
88 }
89 }
90 }
91 Err(format!(
92 "Term did not reach normal form within {} steps",
93 max_steps
94 ))
95}
96
97pub fn is_normal_form(t: &CombTerm) -> bool {
99 reduce_step(t).is_none()
100}
101
102pub fn beta_equal(t1: &CombTerm, t2: &CombTerm, max_steps: usize) -> bool {
105 let n1 = reduce_to_normal(t1.clone(), max_steps);
106 let n2 = reduce_to_normal(t2.clone(), max_steps);
107 match (n1, n2) {
108 (Ok(NormalForm(a)), Ok(NormalForm(b))) => a == b,
109 _ => false,
110 }
111}
112
113pub fn lambda_to_ski_naive(var: &str, body: &CombTerm) -> CombTerm {
122 match body {
123 CombTerm::Var(v) if v == var => CombTerm::I,
125 t if !free_vars(t).contains(&var.to_string()) => {
127 CombTerm::App(Box::new(CombTerm::K), Box::new(t.clone()))
128 }
129 CombTerm::App(m, n) => {
131 let sm = lambda_to_ski_naive(var, m);
132 let sn = lambda_to_ski_naive(var, n);
133 CombTerm::App(
134 Box::new(CombTerm::App(Box::new(CombTerm::S), Box::new(sm))),
135 Box::new(sn),
136 )
137 }
138 t => CombTerm::App(Box::new(CombTerm::K), Box::new(t.clone())),
140 }
141}
142
143pub fn lambda_to_ski_optimized(var: &str, body: &CombTerm) -> CombTerm {
148 match body {
149 CombTerm::Var(v) if v == var => CombTerm::I,
151 CombTerm::App(m, n) => {
153 if let CombTerm::Var(v) = n.as_ref() {
154 if v == var && !free_vars(m).contains(&var.to_string()) {
155 return *m.clone();
156 }
157 }
158 if !free_vars(body).contains(&var.to_string()) {
160 return CombTerm::App(Box::new(CombTerm::K), Box::new(body.clone()));
161 }
162 let sm = lambda_to_ski_optimized(var, m);
164 let sn = lambda_to_ski_optimized(var, n);
165 CombTerm::App(
166 Box::new(CombTerm::App(Box::new(CombTerm::S), Box::new(sm))),
167 Box::new(sn),
168 )
169 }
170 t if !free_vars(t).contains(&var.to_string()) => {
172 CombTerm::App(Box::new(CombTerm::K), Box::new(t.clone()))
173 }
174 t => CombTerm::App(Box::new(CombTerm::K), Box::new(t.clone())),
175 }
176}
177
178pub fn convert_lambda(conv: &LambdaToComb, var: &str, body: &CombTerm) -> CombTerm {
180 match conv.bracket_abstraction {
181 BracketMethod::Naive => lambda_to_ski_naive(var, body),
182 BracketMethod::Optimized => lambda_to_ski_optimized(var, body),
183 BracketMethod::TurnerOptimized => lambda_to_ski_turner(var, body),
184 }
185}
186
187pub fn lambda_to_ski_turner(var: &str, body: &CombTerm) -> CombTerm {
192 match body {
193 CombTerm::Var(v) if v == var => CombTerm::I,
194 t if !free_vars(t).contains(&var.to_string()) => {
195 CombTerm::App(Box::new(CombTerm::K), Box::new(t.clone()))
196 }
197 CombTerm::App(m, n) => {
198 let m_has_var = free_vars(m).contains(&var.to_string());
199 let n_has_var = free_vars(n).contains(&var.to_string());
200 match (m_has_var, n_has_var) {
201 (false, true) => {
203 if let CombTerm::Var(v) = n.as_ref() {
204 if v == var {
205 return *m.clone();
206 }
207 }
208 let n2 = lambda_to_ski_turner(var, n);
211 CombTerm::App(
212 Box::new(CombTerm::App(
213 Box::new(CombTerm::App(
214 Box::new(CombTerm::S),
215 Box::new(CombTerm::App(Box::new(CombTerm::K), m.clone())),
216 )),
217 Box::new(CombTerm::I),
218 )),
219 Box::new(n2),
220 )
221 }
222 (true, false) => {
223 let m2 = lambda_to_ski_turner(var, m);
226 CombTerm::App(
227 Box::new(CombTerm::App(Box::new(CombTerm::S), Box::new(m2))),
228 Box::new(CombTerm::App(Box::new(CombTerm::K), n.clone())),
229 )
230 }
231 _ => {
232 let sm = lambda_to_ski_turner(var, m);
234 let sn = lambda_to_ski_turner(var, n);
235 CombTerm::App(
236 Box::new(CombTerm::App(Box::new(CombTerm::S), Box::new(sm))),
237 Box::new(sn),
238 )
239 }
240 }
241 }
242 t => CombTerm::App(Box::new(CombTerm::K), Box::new(t.clone())),
243 }
244}
245
246pub fn church_numeral_comb(n: u64) -> CombTerm {
254 let mut body = CombTerm::Var("x".to_string());
261 for _ in 0..n {
262 body = CombTerm::App(Box::new(CombTerm::Var("f".to_string())), Box::new(body));
263 }
264 let abs_x = lambda_to_ski_optimized("x", &body);
266 lambda_to_ski_optimized("f", &abs_x)
267}
268
269pub fn comb_app_n(f: CombTerm, args: Vec<CombTerm>) -> CombTerm {
275 args.into_iter()
276 .fold(f, |acc, arg| CombTerm::App(Box::new(acc), Box::new(arg)))
277}
278
279pub fn size(t: &CombTerm) -> usize {
281 match t {
282 CombTerm::App(f, x) => 1 + size(f) + size(x),
283 CombTerm::S | CombTerm::K | CombTerm::I | CombTerm::Var(_) | CombTerm::Const(_) => 1,
284 }
285}
286
287pub fn free_vars(t: &CombTerm) -> Vec<String> {
289 let mut seen = std::collections::HashSet::new();
290 let mut result = Vec::new();
291 collect_free_vars(t, &mut seen, &mut result);
292 result
293}
294
295fn collect_free_vars(
296 t: &CombTerm,
297 seen: &mut std::collections::HashSet<String>,
298 out: &mut Vec<String>,
299) {
300 match t {
301 CombTerm::Var(v) => {
302 if seen.insert(v.clone()) {
303 out.push(v.clone());
304 }
305 }
306 CombTerm::App(f, x) => {
307 collect_free_vars(f, seen, out);
308 collect_free_vars(x, seen, out);
309 }
310 CombTerm::S | CombTerm::K | CombTerm::I | CombTerm::Const(_) => {}
311 }
312}
313
314pub fn substitute(t: &CombTerm, var: &str, val: &CombTerm) -> CombTerm {
316 match t {
317 CombTerm::Var(v) if v == var => val.clone(),
318 CombTerm::Var(_) | CombTerm::S | CombTerm::K | CombTerm::I | CombTerm::Const(_) => {
319 t.clone()
320 }
321 CombTerm::App(f, x) => CombTerm::App(
322 Box::new(substitute(f, var, val)),
323 Box::new(substitute(x, var, val)),
324 ),
325 }
326}
327
328#[cfg(test)]
331mod tests {
332 use super::super::types::*;
333 use super::*;
334
335 fn app(f: CombTerm, x: CombTerm) -> CombTerm {
336 CombTerm::App(Box::new(f), Box::new(x))
337 }
338
339 fn var(s: &str) -> CombTerm {
340 CombTerm::Var(s.to_string())
341 }
342
343 #[test]
346 fn test_normal_form_s() {
347 assert!(is_normal_form(&CombTerm::S));
348 }
349
350 #[test]
351 fn test_normal_form_k() {
352 assert!(is_normal_form(&CombTerm::K));
353 }
354
355 #[test]
356 fn test_normal_form_i() {
357 assert!(is_normal_form(&CombTerm::I));
358 }
359
360 #[test]
361 fn test_not_normal_form_i_x() {
362 let t = app(CombTerm::I, var("x"));
363 assert!(!is_normal_form(&t));
364 }
365
366 #[test]
367 fn test_not_normal_form_k_x_y() {
368 let t = app(app(CombTerm::K, var("x")), var("y"));
369 assert!(!is_normal_form(&t));
370 }
371
372 #[test]
375 fn test_reduce_step_i_rule() {
376 let t = app(CombTerm::I, var("x"));
377 let result = reduce_step(&t);
378 match result {
379 Some((CombTerm::Var(v), CombRule::IRule)) => assert_eq!(v, "x"),
380 other => panic!("expected I-rule reduction to Var(x), got {:?}", other),
381 }
382 }
383
384 #[test]
385 fn test_reduce_step_k_rule() {
386 let t = app(app(CombTerm::K, var("x")), var("y"));
387 let result = reduce_step(&t);
388 match result {
389 Some((CombTerm::Var(v), CombRule::KRule)) => assert_eq!(v, "x"),
390 other => panic!("expected K-rule reduction to Var(x), got {:?}", other),
391 }
392 }
393
394 #[test]
395 fn test_reduce_step_s_rule() {
396 let t = app(app(app(CombTerm::S, var("x")), var("y")), var("z"));
398 let (result, rule) = reduce_step(&t).expect("should reduce");
399 assert_eq!(rule, CombRule::SRule);
400 let expected = app(app(var("x"), var("z")), app(var("y"), var("z")));
402 assert_eq!(result, expected);
403 }
404
405 #[test]
406 fn test_reduce_step_none_on_normal() {
407 assert!(reduce_step(&CombTerm::S).is_none());
408 }
409
410 #[test]
413 fn test_reduce_to_normal_i_x() {
414 let t = app(CombTerm::I, var("x"));
415 let nf = reduce_to_normal(t, 100).expect("should normalize");
416 assert_eq!(nf.into_inner(), var("x"));
417 }
418
419 #[test]
420 fn test_reduce_to_normal_k_x_y() {
421 let t = app(app(CombTerm::K, var("x")), var("y"));
422 let nf = reduce_to_normal(t, 100).expect("should normalize");
423 assert_eq!(nf.into_inner(), var("x"));
424 }
425
426 #[test]
427 fn test_reduce_to_normal_already_normal() {
428 let nf = reduce_to_normal(CombTerm::S, 100).expect("should normalize");
429 assert_eq!(nf.into_inner(), CombTerm::S);
430 }
431
432 #[test]
433 fn test_reduce_to_normal_step_limit() {
434 let t = app(CombTerm::I, app(CombTerm::I, app(CombTerm::I, var("x"))));
436 let nf = reduce_to_normal(t.clone(), 20).expect("should normalize");
438 assert_eq!(nf.into_inner(), var("x"));
439 let partial = reduce_to_normal(t, 1);
441 let _ = partial;
445 }
446
447 #[test]
450 fn test_beta_equal_same_normal() {
451 assert!(beta_equal(&CombTerm::S, &CombTerm::S, 100));
452 }
453
454 #[test]
455 fn test_beta_equal_different() {
456 assert!(!beta_equal(&CombTerm::S, &CombTerm::K, 100));
457 }
458
459 #[test]
460 fn test_beta_equal_reduces_to_same() {
461 let t1 = app(CombTerm::I, var("x"));
463 let t2 = app(app(CombTerm::K, var("x")), app(CombTerm::I, var("y")));
464 assert!(beta_equal(&t1, &t2, 100));
465 }
466
467 #[test]
470 fn test_naive_identity_lambda() {
471 let result = lambda_to_ski_naive("x", &var("x"));
473 assert_eq!(result, CombTerm::I);
474 }
475
476 #[test]
477 fn test_naive_constant_lambda() {
478 let result = lambda_to_ski_naive("x", &var("y"));
480 let expected = app(CombTerm::K, var("y"));
481 assert_eq!(result, expected);
482 }
483
484 #[test]
485 fn test_naive_k_combinator() {
486 let inner = lambda_to_ski_naive("y", &var("x")); let outer = lambda_to_ski_naive("x", &inner); let expected = app(app(CombTerm::S, app(CombTerm::K, CombTerm::K)), CombTerm::I);
492 assert_eq!(outer, expected);
493 }
494
495 #[test]
498 fn test_optimized_identity() {
499 let result = lambda_to_ski_optimized("x", &var("x"));
500 assert_eq!(result, CombTerm::I);
501 }
502
503 #[test]
504 fn test_optimized_eta_reduction() {
505 let body = app(var("f"), var("x"));
507 let result = lambda_to_ski_optimized("x", &body);
508 assert_eq!(result, var("f"));
510 }
511
512 #[test]
513 fn test_optimized_constant() {
514 let result = lambda_to_ski_optimized("x", &CombTerm::K);
515 let expected = app(CombTerm::K, CombTerm::K);
516 assert_eq!(result, expected);
517 }
518
519 #[test]
522 fn test_church_zero_reduces_to_ki() {
523 let zero = church_numeral_comb(0);
525 let applied_f = app(zero.clone(), var("f"));
527 let applied_fx = app(applied_f, var("x"));
528 let nf = reduce_to_normal(applied_fx, 200).expect("should normalize");
529 assert_eq!(nf.into_inner(), var("x"));
530 }
531
532 #[test]
533 fn test_church_one_applies_once() {
534 let one = church_numeral_comb(1);
536 let applied = app(app(one, var("f")), var("x"));
537 let nf = reduce_to_normal(applied, 200).expect("should normalize");
538 assert_eq!(nf.into_inner(), app(var("f"), var("x")));
539 }
540
541 #[test]
542 fn test_church_two_applies_twice() {
543 let two = church_numeral_comb(2);
545 let applied = app(app(two, var("f")), var("x"));
546 let nf = reduce_to_normal(applied, 1000).expect("should normalize");
547 assert_eq!(nf.into_inner(), app(var("f"), app(var("f"), var("x"))));
548 }
549
550 #[test]
553 fn test_comb_app_n_empty() {
554 let result = comb_app_n(var("f"), vec![]);
555 assert_eq!(result, var("f"));
556 }
557
558 #[test]
559 fn test_comb_app_n_one() {
560 let result = comb_app_n(var("f"), vec![var("x")]);
561 assert_eq!(result, app(var("f"), var("x")));
562 }
563
564 #[test]
565 fn test_comb_app_n_three() {
566 let result = comb_app_n(var("f"), vec![var("x"), var("y"), var("z")]);
567 let expected = app(app(app(var("f"), var("x")), var("y")), var("z"));
568 assert_eq!(result, expected);
569 }
570
571 #[test]
574 fn test_size_atomic() {
575 assert_eq!(size(&CombTerm::S), 1);
576 assert_eq!(size(&CombTerm::K), 1);
577 assert_eq!(size(&var("x")), 1);
578 }
579
580 #[test]
581 fn test_size_app() {
582 let t = app(CombTerm::S, CombTerm::K);
583 assert_eq!(size(&t), 3); }
585
586 #[test]
587 fn test_size_nested() {
588 let t = app(app(CombTerm::S, CombTerm::K), CombTerm::I);
589 assert_eq!(size(&t), 5);
590 }
591
592 #[test]
595 fn test_free_vars_combinator() {
596 assert!(free_vars(&CombTerm::S).is_empty());
597 assert!(free_vars(&CombTerm::K).is_empty());
598 assert!(free_vars(&CombTerm::I).is_empty());
599 }
600
601 #[test]
602 fn test_free_vars_var() {
603 assert_eq!(free_vars(&var("x")), vec!["x".to_string()]);
604 }
605
606 #[test]
607 fn test_free_vars_app() {
608 let t = app(var("x"), var("y"));
609 assert_eq!(free_vars(&t), vec!["x".to_string(), "y".to_string()]);
610 }
611
612 #[test]
613 fn test_free_vars_deduplication() {
614 let t = app(var("x"), var("x"));
615 assert_eq!(free_vars(&t), vec!["x".to_string()]);
616 }
617
618 #[test]
621 fn test_substitute_var() {
622 let result = substitute(&var("x"), "x", &CombTerm::I);
623 assert_eq!(result, CombTerm::I);
624 }
625
626 #[test]
627 fn test_substitute_different_var() {
628 let result = substitute(&var("y"), "x", &CombTerm::I);
629 assert_eq!(result, var("y"));
630 }
631
632 #[test]
633 fn test_substitute_in_app() {
634 let t = app(var("x"), var("y"));
635 let result = substitute(&t, "x", &CombTerm::K);
636 assert_eq!(result, app(CombTerm::K, var("y")));
637 }
638
639 #[test]
640 fn test_substitute_combinator_unchanged() {
641 let result = substitute(&CombTerm::S, "x", &CombTerm::K);
642 assert_eq!(result, CombTerm::S);
643 }
644}