1use logicaffeine_base::Arena;
39use crate::ast::{LogicExpr, QuantifierKind, Term};
40use logicaffeine_base::{Interner, Symbol};
41use crate::lexicon;
42use crate::token::TokenType;
43
44fn clone_term<'a>(term: &Term<'a>, arena: &'a Arena<Term<'a>>) -> Term<'a> {
45 match term {
46 Term::Constant(s) => Term::Constant(*s),
47 Term::Variable(s) => Term::Variable(*s),
48 Term::Function(name, args) => {
49 let cloned_args: Vec<Term<'a>> = args.iter().map(|t| clone_term(t, arena)).collect();
50 Term::Function(*name, arena.alloc_slice(cloned_args))
51 }
52 Term::Group(members) => {
53 let cloned: Vec<Term<'a>> = members.iter().map(|t| clone_term(t, arena)).collect();
54 Term::Group(arena.alloc_slice(cloned))
55 }
56 Term::Possessed { possessor, possessed } => Term::Possessed {
57 possessor: arena.alloc(clone_term(possessor, arena)),
58 possessed: *possessed,
59 },
60 Term::Sigma(predicate) => Term::Sigma(*predicate),
61 Term::Intension(predicate) => Term::Intension(*predicate),
62 Term::Kind(kind) => Term::Kind(*kind),
63 Term::Proposition(expr) => Term::Proposition(*expr),
64 Term::Value { kind, unit, dimension } => Term::Value {
65 kind: *kind,
66 unit: *unit,
67 dimension: *dimension,
68 },
69 }
70}
71
72pub fn is_opaque_verb(verb: Symbol, interner: &Interner) -> bool {
78 let verb_str = interner.resolve(verb);
79 let lower = verb_str.to_lowercase();
80 lexicon::is_opaque_verb(&lower)
81}
82
83pub fn make_intensional<'a>(
87 operator: Symbol,
88 content: &'a LogicExpr<'a>,
89 arena: &'a Arena<LogicExpr<'a>>,
90) -> &'a LogicExpr<'a> {
91 arena.alloc(LogicExpr::Intensional { operator, content })
92}
93
94pub fn substitute_respecting_opacity<'a>(
99 expr: &'a LogicExpr<'a>,
100 var: Symbol,
101 replacement: &'a LogicExpr<'a>,
102 expr_arena: &'a Arena<LogicExpr<'a>>,
103 term_arena: &'a Arena<Term<'a>>,
104) -> &'a LogicExpr<'a> {
105 match expr {
106 LogicExpr::Intensional { operator, content } => {
107 expr_arena.alloc(LogicExpr::Intensional {
108 operator: *operator,
109 content: *content,
110 })
111 }
112
113 LogicExpr::Predicate { name, args, .. } => {
114 let new_args: Vec<Term<'a>> = args
115 .iter()
116 .map(|arg| substitute_term_for_opacity(arg, var, replacement, term_arena))
117 .collect();
118 expr_arena.alloc(LogicExpr::Predicate {
119 name: *name,
120 args: term_arena.alloc_slice(new_args),
121 world: None,
122 })
123 }
124
125 LogicExpr::BinaryOp { left, op, right } => expr_arena.alloc(LogicExpr::BinaryOp {
126 left: substitute_respecting_opacity(left, var, replacement, expr_arena, term_arena),
127 op: op.clone(),
128 right: substitute_respecting_opacity(right, var, replacement, expr_arena, term_arena),
129 }),
130
131 LogicExpr::UnaryOp { op, operand } => expr_arena.alloc(LogicExpr::UnaryOp {
132 op: op.clone(),
133 operand: substitute_respecting_opacity(operand, var, replacement, expr_arena, term_arena),
134 }),
135
136 LogicExpr::Quantifier { kind, variable, body, island_id } => {
137 if *variable == var {
138 expr
139 } else {
140 expr_arena.alloc(LogicExpr::Quantifier {
141 kind: *kind,
142 variable: *variable,
143 body: substitute_respecting_opacity(body, var, replacement, expr_arena, term_arena),
144 island_id: *island_id,
145 })
146 }
147 }
148
149 LogicExpr::Lambda { variable, body } => {
150 if *variable == var {
151 expr
152 } else {
153 expr_arena.alloc(LogicExpr::Lambda {
154 variable: *variable,
155 body: substitute_respecting_opacity(body, var, replacement, expr_arena, term_arena),
156 })
157 }
158 }
159
160 LogicExpr::App { function, argument } => expr_arena.alloc(LogicExpr::App {
161 function: substitute_respecting_opacity(function, var, replacement, expr_arena, term_arena),
162 argument: substitute_respecting_opacity(argument, var, replacement, expr_arena, term_arena),
163 }),
164
165 LogicExpr::Atom(s) => {
166 if *s == var {
167 replacement
168 } else {
169 expr
170 }
171 }
172
173 _ => expr,
174 }
175}
176
177fn substitute_term_for_opacity<'a>(
178 term: &Term<'a>,
179 var: Symbol,
180 replacement: &LogicExpr<'a>,
181 arena: &'a Arena<Term<'a>>,
182) -> Term<'a> {
183 match term {
184 Term::Constant(c) if *c == var => {
185 match replacement {
186 LogicExpr::Atom(s) => Term::Constant(*s),
187 _ => clone_term(term, arena),
188 }
189 }
190 Term::Variable(v) if *v == var => {
191 match replacement {
192 LogicExpr::Atom(s) => Term::Constant(*s),
193 _ => clone_term(term, arena),
194 }
195 }
196 _ => clone_term(term, arena),
197 }
198}
199
200pub fn to_event_semantics<'a>(
205 expr: &'a LogicExpr<'a>,
206 interner: &mut Interner,
207 expr_arena: &'a Arena<LogicExpr<'a>>,
208 term_arena: &'a Arena<Term<'a>>,
209) -> &'a LogicExpr<'a> {
210 match expr {
211 LogicExpr::Predicate { name, args, .. } => {
212 let e_sym = interner.intern("e");
213 let _event_var = term_arena.alloc(Term::Variable(e_sym));
214
215 let event_pred = expr_arena.alloc(LogicExpr::Predicate {
216 name: *name,
217 args: term_arena.alloc_slice([Term::Variable(e_sym)]),
218 world: None,
219 });
220
221 let mut body = event_pred;
222
223 if !args.is_empty() {
224 let agent_args = term_arena.alloc_slice([Term::Variable(e_sym), clone_term(&args[0], term_arena)]);
225 let agent_pred = expr_arena.alloc(LogicExpr::Predicate {
226 name: interner.intern("Agent"),
227 args: agent_args,
228 world: None,
229 });
230 body = expr_arena.alloc(LogicExpr::BinaryOp {
231 left: body,
232 op: TokenType::And,
233 right: agent_pred,
234 });
235 }
236
237 if args.len() > 1 {
238 let theme_args = term_arena.alloc_slice([Term::Variable(e_sym), clone_term(&args[1], term_arena)]);
239 let theme_pred = expr_arena.alloc(LogicExpr::Predicate {
240 name: interner.intern("Theme"),
241 args: theme_args,
242 world: None,
243 });
244 body = expr_arena.alloc(LogicExpr::BinaryOp {
245 left: body,
246 op: TokenType::And,
247 right: theme_pred,
248 });
249 }
250
251 if args.len() > 2 {
252 let goal_args = term_arena.alloc_slice([Term::Variable(e_sym), clone_term(&args[2], term_arena)]);
253 let goal_pred = expr_arena.alloc(LogicExpr::Predicate {
254 name: interner.intern("Goal"),
255 args: goal_args,
256 world: None,
257 });
258 body = expr_arena.alloc(LogicExpr::BinaryOp {
259 left: body,
260 op: TokenType::And,
261 right: goal_pred,
262 });
263 }
264
265 expr_arena.alloc(LogicExpr::Quantifier {
266 kind: QuantifierKind::Existential,
267 variable: e_sym,
268 body,
269 island_id: 0,
270 })
271 }
272 _ => expr,
273 }
274}
275
276pub fn apply_adverb<'a>(
280 expr: &'a LogicExpr<'a>,
281 adverb: Symbol,
282 interner: &mut Interner,
283 expr_arena: &'a Arena<LogicExpr<'a>>,
284 term_arena: &'a Arena<Term<'a>>,
285) -> &'a LogicExpr<'a> {
286 let e_sym = interner.intern("e");
287 match expr {
288 LogicExpr::Quantifier { kind, variable, body, island_id } if *variable == e_sym => {
289 let adverb_str = interner.resolve(adverb);
290 let capitalized = capitalize(adverb_str);
291 let adverb_pred = expr_arena.alloc(LogicExpr::Predicate {
292 name: interner.intern(&capitalized),
293 args: term_arena.alloc_slice([Term::Variable(*variable)]),
294 world: None,
295 });
296
297 let new_body = expr_arena.alloc(LogicExpr::BinaryOp {
298 left: *body,
299 op: TokenType::And,
300 right: adverb_pred,
301 });
302
303 expr_arena.alloc(LogicExpr::Quantifier {
304 kind: *kind,
305 variable: *variable,
306 body: new_body,
307 island_id: *island_id,
308 })
309 }
310 _ => expr,
311 }
312}
313
314fn capitalize(s: &str) -> String {
315 let mut chars = s.chars();
316 match chars.next() {
317 None => String::new(),
318 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
319 }
320}
321
322fn factorial(n: usize) -> u64 {
323 (1..=n as u64).product()
324}
325
326pub struct ScopeIterator<'a> {
327 expr_arena: &'a Arena<LogicExpr<'a>>,
328 islands: Vec<Vec<ScopalElement<'a>>>,
329 core: &'a LogicExpr<'a>,
330 current_index: u64,
331 total: u64,
332 single_result: Option<&'a LogicExpr<'a>>,
333 returned_single: bool,
334}
335
336impl<'a> ScopeIterator<'a> {
337 fn nth_island_aware_permutation(&self, n: u64) -> Vec<ScopalElement<'a>> {
338 let mut result = Vec::new();
339 let mut remainder = n;
340
341 for island in &self.islands {
342 let island_perms = factorial(island.len());
343 let island_index = remainder % island_perms;
344 remainder /= island_perms;
345
346 let perm = nth_permutation_of_slice(island, island_index);
347 result.extend(perm);
348 }
349
350 result
351 }
352}
353
354fn nth_permutation_of_slice<T: Clone>(items: &[T], n: u64) -> Vec<T> {
355 let len = items.len();
356 let mut available: Vec<usize> = (0..len).collect();
357 let mut result = Vec::with_capacity(len);
358 let mut remainder = n;
359
360 for i in 0..len {
361 let divisor = factorial(len - i - 1);
362 let index = (remainder / divisor) as usize;
363 remainder %= divisor;
364 result.push(items[available.remove(index)].clone());
365 }
366 result
367}
368
369impl<'a> Iterator for ScopeIterator<'a> {
370 type Item = &'a LogicExpr<'a>;
371
372 fn next(&mut self) -> Option<Self::Item> {
373 if let Some(single) = self.single_result {
374 if self.returned_single {
375 return None;
376 }
377 self.returned_single = true;
378 return Some(single);
379 }
380
381 if self.current_index >= self.total {
382 return None;
383 }
384 let ordered = self.nth_island_aware_permutation(self.current_index);
385 self.current_index += 1;
386 Some(rebuild_with_scopal_elements(&ordered, self.core, self.expr_arena))
387 }
388
389 fn size_hint(&self) -> (usize, Option<usize>) {
390 if self.single_result.is_some() {
391 let remaining = if self.returned_single { 0 } else { 1 };
392 return (remaining, Some(remaining));
393 }
394 let remaining = (self.total - self.current_index) as usize;
395 (remaining, Some(remaining))
396 }
397}
398
399impl<'a> ExactSizeIterator for ScopeIterator<'a> {}
400
401#[derive(Clone, Debug)]
402struct QuantifierInfo<'a> {
403 kind: QuantifierKind,
404 variable: Symbol,
405 restrictor: &'a LogicExpr<'a>,
406 island_id: u32,
407}
408
409#[derive(Clone, Debug)]
410enum ScopalElement<'a> {
411 Quantifier(QuantifierInfo<'a>),
412 Negation { island_id: u32 },
413}
414
415impl<'a> ScopalElement<'a> {
416 fn island_id(&self) -> u32 {
417 match self {
418 ScopalElement::Quantifier(q) => q.island_id,
419 ScopalElement::Negation { island_id } => *island_id,
420 }
421 }
422}
423
424pub fn enumerate_scopings<'a>(
436 expr: &'a LogicExpr<'a>,
437 interner: &mut Interner,
438 expr_arena: &'a Arena<LogicExpr<'a>>,
439 _term_arena: &'a Arena<Term<'a>>,
440) -> ScopeIterator<'a> {
441 let mut elements = Vec::new();
442 let core = extract_scopal_elements(expr, &mut elements, interner, expr_arena);
443
444 if elements.is_empty() || elements.len() == 1 {
445 return ScopeIterator {
446 expr_arena,
447 islands: Vec::new(),
448 core,
449 current_index: 0,
450 total: 0,
451 single_result: Some(expr),
452 returned_single: false,
453 };
454 }
455
456 let islands = group_scopal_by_island(elements);
457 let total: u64 = islands.iter().map(|island| factorial(island.len())).product();
458
459 ScopeIterator {
460 expr_arena,
461 islands,
462 core,
463 current_index: 0,
464 total,
465 single_result: None,
466 returned_single: false,
467 }
468}
469
470fn group_by_island<'a>(quantifiers: Vec<QuantifierInfo<'a>>) -> Vec<Vec<QuantifierInfo<'a>>> {
471 use std::collections::BTreeMap;
472
473 let mut by_island: BTreeMap<u32, Vec<QuantifierInfo<'a>>> = BTreeMap::new();
474 for q in quantifiers {
475 by_island.entry(q.island_id).or_default().push(q);
476 }
477
478 by_island.into_values().collect()
479}
480
481fn group_scopal_by_island<'a>(elements: Vec<ScopalElement<'a>>) -> Vec<Vec<ScopalElement<'a>>> {
482 use std::collections::BTreeMap;
483
484 let mut by_island: BTreeMap<u32, Vec<ScopalElement<'a>>> = BTreeMap::new();
485 for elem in elements {
486 by_island.entry(elem.island_id()).or_default().push(elem);
487 }
488
489 by_island.into_values().collect()
490}
491
492pub fn cumulative_reading<'a>(
501 expr: &'a LogicExpr<'a>,
502 interner: &mut Interner,
503 expr_arena: &'a Arena<LogicExpr<'a>>,
504) -> Option<&'a LogicExpr<'a>> {
505 let mut elements = Vec::new();
506 let core = extract_scopal_elements(expr, &mut elements, interner, expr_arena);
507 if elements.len() != 2 {
508 return None;
509 }
510 let (q1, q2) = match (&elements[0], &elements[1]) {
511 (ScopalElement::Quantifier(a), ScopalElement::Quantifier(b)) => (a.clone(), b.clone()),
512 _ => return None,
513 };
514 if !matches!(q1.kind, QuantifierKind::Cardinal(_))
517 || !matches!(q2.kind, QuantifierKind::Cardinal(_))
518 || q1.island_id != q2.island_id
519 {
520 return None;
521 }
522
523 let and = |l: &'a LogicExpr<'a>, r: &'a LogicExpr<'a>| -> &'a LogicExpr<'a> {
524 expr_arena.alloc(LogicExpr::BinaryOp { left: l, op: TokenType::And, right: r })
525 };
526
527 let exists_y = expr_arena.alloc(LogicExpr::Quantifier {
529 kind: QuantifierKind::Existential,
530 variable: q2.variable,
531 body: and(q2.restrictor, core),
532 island_id: q1.island_id,
533 });
534 let c1 = expr_arena.alloc(LogicExpr::Quantifier {
535 kind: q1.kind,
536 variable: q1.variable,
537 body: and(q1.restrictor, exists_y),
538 island_id: q1.island_id,
539 });
540
541 let exists_x = expr_arena.alloc(LogicExpr::Quantifier {
543 kind: QuantifierKind::Existential,
544 variable: q1.variable,
545 body: and(q1.restrictor, core),
546 island_id: q2.island_id,
547 });
548 let c2 = expr_arena.alloc(LogicExpr::Quantifier {
549 kind: q2.kind,
550 variable: q2.variable,
551 body: and(q2.restrictor, exists_x),
552 island_id: q2.island_id,
553 });
554
555 Some(and(c1, c2))
556}
557
558fn extract_scopal_elements<'a>(
559 expr: &'a LogicExpr<'a>,
560 elements: &mut Vec<ScopalElement<'a>>,
561 interner: &mut Interner,
562 expr_arena: &'a Arena<LogicExpr<'a>>,
563) -> &'a LogicExpr<'a> {
564 match expr {
565 LogicExpr::Quantifier { kind, variable, body, island_id } => {
566 if let LogicExpr::BinaryOp { left, op, right } = body {
567 if matches!(op, TokenType::If | TokenType::Implies | TokenType::And) {
568 if let LogicExpr::UnaryOp { op: TokenType::Not, operand } = right {
570 elements.push(ScopalElement::Quantifier(QuantifierInfo {
573 kind: *kind,
574 variable: *variable,
575 restrictor: *left,
576 island_id: *island_id,
577 }));
578 elements.push(ScopalElement::Negation { island_id: *island_id });
579 return extract_scopal_elements(operand, elements, interner, expr_arena);
580 }
581 elements.push(ScopalElement::Quantifier(QuantifierInfo {
583 kind: *kind,
584 variable: *variable,
585 restrictor: *left,
586 island_id: *island_id,
587 }));
588 return extract_scopal_elements(right, elements, interner, expr_arena);
589 }
590 }
591 elements.push(ScopalElement::Quantifier(QuantifierInfo {
593 kind: *kind,
594 variable: *variable,
595 restrictor: expr_arena.alloc(LogicExpr::Atom(interner.intern("T"))),
596 island_id: *island_id,
597 }));
598 extract_scopal_elements(body, elements, interner, expr_arena)
599 }
600 LogicExpr::UnaryOp { op: TokenType::Not, operand } => {
601 elements.push(ScopalElement::Negation { island_id: 0 });
603 extract_scopal_elements(operand, elements, interner, expr_arena)
604 }
605 _ => expr,
606 }
607}
608
609fn rebuild_with_scopal_elements<'a>(
610 elements: &[ScopalElement<'a>],
611 core: &'a LogicExpr<'a>,
612 arena: &'a Arena<LogicExpr<'a>>,
613) -> &'a LogicExpr<'a> {
614 let mut result = core;
615
616 for elem in elements.iter().rev() {
617 match elem {
618 ScopalElement::Quantifier(q) => {
619 let connective = match q.kind {
620 QuantifierKind::Universal => TokenType::Implies,
621 _ => TokenType::And,
622 };
623
624 let body = arena.alloc(LogicExpr::BinaryOp {
625 left: q.restrictor,
626 op: connective,
627 right: result,
628 });
629
630 result = arena.alloc(LogicExpr::Quantifier {
631 kind: q.kind,
632 variable: q.variable,
633 body,
634 island_id: q.island_id,
635 });
636 }
637 ScopalElement::Negation { .. } => {
638 result = arena.alloc(LogicExpr::UnaryOp {
639 op: TokenType::Not,
640 operand: result,
641 });
642 }
643 }
644 }
645
646 result
647}
648
649fn extract_quantifiers<'a>(
650 expr: &'a LogicExpr<'a>,
651 quantifiers: &mut Vec<QuantifierInfo<'a>>,
652 interner: &mut Interner,
653 expr_arena: &'a Arena<LogicExpr<'a>>,
654) -> &'a LogicExpr<'a> {
655 match expr {
656 LogicExpr::Quantifier { kind, variable, body, island_id } => {
657 if let LogicExpr::BinaryOp { left, op, right } = body {
658 if matches!(op, TokenType::If | TokenType::Implies | TokenType::And) {
659 quantifiers.push(QuantifierInfo {
660 kind: *kind,
661 variable: *variable,
662 restrictor: *left,
663 island_id: *island_id,
664 });
665 return extract_quantifiers(right, quantifiers, interner, expr_arena);
666 }
667 }
668 quantifiers.push(QuantifierInfo {
669 kind: *kind,
670 variable: *variable,
671 restrictor: expr_arena.alloc(LogicExpr::Atom(interner.intern("T"))),
672 island_id: *island_id,
673 });
674 extract_quantifiers(body, quantifiers, interner, expr_arena)
675 }
676 _ => expr,
677 }
678}
679
680fn rebuild_with_scope_order<'a>(
681 quantifiers: &[QuantifierInfo<'a>],
682 core: &'a LogicExpr<'a>,
683 arena: &'a Arena<LogicExpr<'a>>,
684) -> &'a LogicExpr<'a> {
685 let mut result = core;
686
687 for q in quantifiers.iter().rev() {
688 let connective = match q.kind {
689 QuantifierKind::Universal => TokenType::Implies,
690 _ => TokenType::And,
691 };
692
693 let body = arena.alloc(LogicExpr::BinaryOp {
694 left: q.restrictor,
695 op: connective,
696 right: result,
697 });
698
699 result = arena.alloc(LogicExpr::Quantifier {
700 kind: q.kind,
701 variable: q.variable,
702 body,
703 island_id: q.island_id,
704 });
705 }
706
707 result
708}
709
710pub fn lift_proper_name<'a>(
711 name: Symbol,
712 interner: &mut Interner,
713 arena: &'a Arena<LogicExpr<'a>>,
714) -> &'a LogicExpr<'a> {
715 let p_sym = interner.intern("P");
716 let inner_app = arena.alloc(LogicExpr::App {
717 function: arena.alloc(LogicExpr::Atom(p_sym)),
718 argument: arena.alloc(LogicExpr::Atom(name)),
719 });
720 arena.alloc(LogicExpr::Lambda {
721 variable: p_sym,
722 body: inner_app,
723 })
724}
725
726pub fn lift_quantifier<'a>(
727 kind: QuantifierKind,
728 restrictor: Symbol,
729 interner: &mut Interner,
730 expr_arena: &'a Arena<LogicExpr<'a>>,
731 term_arena: &'a Arena<Term<'a>>,
732) -> &'a LogicExpr<'a> {
733 let x_sym = interner.intern("x");
734 let q_sym = interner.intern("Q");
735
736 let restrictor_pred = expr_arena.alloc(LogicExpr::Predicate {
737 name: restrictor,
738 args: term_arena.alloc_slice([Term::Variable(x_sym)]),
739 world: None,
740 });
741
742 let q_of_x = expr_arena.alloc(LogicExpr::App {
743 function: expr_arena.alloc(LogicExpr::Atom(q_sym)),
744 argument: expr_arena.alloc(LogicExpr::Atom(x_sym)),
745 });
746
747 let connective = match kind {
748 QuantifierKind::Universal => TokenType::Implies,
749 _ => TokenType::And,
750 };
751
752 let body = expr_arena.alloc(LogicExpr::BinaryOp {
753 left: restrictor_pred,
754 op: connective,
755 right: q_of_x,
756 });
757
758 let quantifier = expr_arena.alloc(LogicExpr::Quantifier {
759 kind,
760 variable: x_sym,
761 body,
762 island_id: 0,
763 });
764
765 expr_arena.alloc(LogicExpr::Lambda {
766 variable: q_sym,
767 body: quantifier,
768 })
769}
770
771pub fn beta_reduce<'a>(
776 expr: &'a LogicExpr<'a>,
777 expr_arena: &'a Arena<LogicExpr<'a>>,
778 term_arena: &'a Arena<Term<'a>>,
779) -> &'a LogicExpr<'a> {
780 match expr {
781 LogicExpr::App { function, argument } => {
782 if let LogicExpr::Lambda { variable, body } = function {
783 substitute(body, *variable, argument, expr_arena, term_arena)
784 } else {
785 expr_arena.alloc(LogicExpr::App {
786 function: beta_reduce(function, expr_arena, term_arena),
787 argument: beta_reduce(argument, expr_arena, term_arena),
788 })
789 }
790 }
791 LogicExpr::Lambda { variable, body } => expr_arena.alloc(LogicExpr::Lambda {
792 variable: *variable,
793 body: beta_reduce(body, expr_arena, term_arena),
794 }),
795 _ => expr,
796 }
797}
798
799fn substitute<'a>(
800 expr: &'a LogicExpr<'a>,
801 var: Symbol,
802 replacement: &'a LogicExpr<'a>,
803 expr_arena: &'a Arena<LogicExpr<'a>>,
804 term_arena: &'a Arena<Term<'a>>,
805) -> &'a LogicExpr<'a> {
806 match expr {
807 LogicExpr::Predicate { name, args, .. } => {
808 let new_args: Vec<Term<'a>> = args
809 .iter()
810 .map(|arg| substitute_term(arg, var, replacement, term_arena))
811 .collect();
812 expr_arena.alloc(LogicExpr::Predicate {
813 name: *name,
814 args: term_arena.alloc_slice(new_args),
815 world: None,
816 })
817 }
818
819 LogicExpr::Lambda { variable, body } => {
820 if *variable == var {
821 expr
822 } else {
823 expr_arena.alloc(LogicExpr::Lambda {
824 variable: *variable,
825 body: substitute(body, var, replacement, expr_arena, term_arena),
826 })
827 }
828 }
829
830 LogicExpr::App { function, argument } => expr_arena.alloc(LogicExpr::App {
831 function: substitute(function, var, replacement, expr_arena, term_arena),
832 argument: substitute(argument, var, replacement, expr_arena, term_arena),
833 }),
834
835 LogicExpr::BinaryOp { left, op, right } => expr_arena.alloc(LogicExpr::BinaryOp {
836 left: substitute(left, var, replacement, expr_arena, term_arena),
837 op: op.clone(),
838 right: substitute(right, var, replacement, expr_arena, term_arena),
839 }),
840
841 LogicExpr::UnaryOp { op, operand } => expr_arena.alloc(LogicExpr::UnaryOp {
842 op: op.clone(),
843 operand: substitute(operand, var, replacement, expr_arena, term_arena),
844 }),
845
846 LogicExpr::Quantifier { kind, variable, body, island_id } => {
847 if *variable == var {
848 expr
849 } else {
850 expr_arena.alloc(LogicExpr::Quantifier {
851 kind: *kind,
852 variable: *variable,
853 body: substitute(body, var, replacement, expr_arena, term_arena),
854 island_id: *island_id,
855 })
856 }
857 }
858
859 LogicExpr::Atom(s) => {
860 if *s == var {
861 replacement
862 } else {
863 expr
864 }
865 }
866
867 _ => expr,
868 }
869}
870
871fn substitute_term<'a>(
872 term: &Term<'a>,
873 var: Symbol,
874 replacement: &LogicExpr<'a>,
875 term_arena: &'a Arena<Term<'a>>,
876) -> Term<'a> {
877 match term {
878 Term::Variable(v) if *v == var => {
879 match replacement {
880 LogicExpr::Atom(s) => Term::Constant(*s),
881 LogicExpr::Predicate { name, .. } => Term::Constant(*name),
882 _ => clone_term(term, term_arena),
883 }
884 }
885 _ => clone_term(term, term_arena),
886 }
887}
888
889#[derive(Debug)]
894struct IntensionalContext {
895 verb: Symbol,
896 quantifier_var: Symbol,
897 restrictor: Symbol,
898}
899
900fn find_opaque_verb_context<'a>(
901 expr: &'a LogicExpr<'a>,
902 interner: &Interner,
903) -> Option<IntensionalContext> {
904 match expr {
905 LogicExpr::Quantifier { kind: QuantifierKind::Existential, variable, body, .. } => {
906 if let LogicExpr::BinaryOp { left, op: TokenType::And, right } = body {
907 if let LogicExpr::Predicate { name: restrictor, args, .. } = left {
908 if args.len() == 1 {
909 if let Term::Variable(v) = &args[0] {
910 if *v == *variable {
911 if let Some(verb) = find_opaque_verb_in_scope(right, *variable, interner) {
912 return Some(IntensionalContext {
913 verb,
914 quantifier_var: *variable,
915 restrictor: *restrictor,
916 });
917 }
918 }
919 }
920 }
921 }
922 }
923 None
924 }
925 _ => None,
926 }
927}
928
929fn find_opaque_verb_in_scope<'a>(
930 expr: &'a LogicExpr<'a>,
931 theme_var: Symbol,
932 interner: &Interner,
933) -> Option<Symbol> {
934 match expr {
935 LogicExpr::Quantifier { body, .. } => find_opaque_verb_in_scope(body, theme_var, interner),
936 LogicExpr::BinaryOp { left, right, .. } => {
937 find_opaque_verb_in_scope(left, theme_var, interner)
938 .or_else(|| find_opaque_verb_in_scope(right, theme_var, interner))
939 }
940 LogicExpr::NeoEvent(data) => {
941 if is_opaque_verb(data.verb, interner) {
942 for (role, term) in data.roles.iter() {
943 if matches!(role, crate::ast::ThematicRole::Theme) {
944 if let Term::Variable(v) = term {
945 if *v == theme_var {
946 return Some(data.verb);
947 }
948 }
949 }
950 }
951 }
952 None
953 }
954 LogicExpr::Predicate { name, args, .. } => {
955 if is_opaque_verb(*name, interner) && args.len() >= 2 {
956 if let Term::Variable(v) = &args[1] {
957 if *v == theme_var {
958 return Some(*name);
959 }
960 }
961 }
962 None
963 }
964 _ => None,
965 }
966}
967
968fn build_de_dicto_reading<'a>(
969 expr: &'a LogicExpr<'a>,
970 ctx: &IntensionalContext,
971 expr_arena: &'a Arena<LogicExpr<'a>>,
972 term_arena: &'a Arena<Term<'a>>,
973 role_arena: &'a Arena<(crate::ast::ThematicRole, Term<'a>)>,
974) -> &'a LogicExpr<'a> {
975 match expr {
976 LogicExpr::Quantifier { kind: QuantifierKind::Existential, variable, body, .. }
977 if *variable == ctx.quantifier_var =>
978 {
979 if let LogicExpr::BinaryOp { right, .. } = body {
980 replace_theme_with_intension(right, ctx, expr_arena, term_arena, role_arena)
981 } else {
982 expr
983 }
984 }
985 _ => expr,
986 }
987}
988
989fn replace_theme_with_intension<'a>(
990 expr: &'a LogicExpr<'a>,
991 ctx: &IntensionalContext,
992 expr_arena: &'a Arena<LogicExpr<'a>>,
993 term_arena: &'a Arena<Term<'a>>,
994 role_arena: &'a Arena<(crate::ast::ThematicRole, Term<'a>)>,
995) -> &'a LogicExpr<'a> {
996 match expr {
997 LogicExpr::Quantifier { kind, variable, body, island_id } => {
998 let new_body = replace_theme_with_intension(body, ctx, expr_arena, term_arena, role_arena);
999 expr_arena.alloc(LogicExpr::Quantifier {
1000 kind: *kind,
1001 variable: *variable,
1002 body: new_body,
1003 island_id: *island_id,
1004 })
1005 }
1006 LogicExpr::BinaryOp { left, op, right } => {
1007 let new_left = replace_theme_with_intension(left, ctx, expr_arena, term_arena, role_arena);
1008 let new_right = replace_theme_with_intension(right, ctx, expr_arena, term_arena, role_arena);
1009 expr_arena.alloc(LogicExpr::BinaryOp {
1010 left: new_left,
1011 op: op.clone(),
1012 right: new_right,
1013 })
1014 }
1015 LogicExpr::NeoEvent(data) => {
1016 let new_roles: Vec<_> = data.roles.iter().map(|(role, term)| {
1017 if matches!(role, crate::ast::ThematicRole::Theme) {
1018 if let Term::Variable(v) = term {
1019 if *v == ctx.quantifier_var {
1020 return (*role, Term::Intension(ctx.restrictor));
1021 }
1022 }
1023 }
1024 (*role, clone_term(term, term_arena))
1025 }).collect();
1026
1027 expr_arena.alloc(LogicExpr::NeoEvent(Box::new(crate::ast::NeoEventData {
1028 event_var: data.event_var,
1029 verb: data.verb,
1030 roles: role_arena.alloc_slice(new_roles),
1031 modifiers: data.modifiers,
1032 suppress_existential: false,
1033 world: None,
1034 })))
1035 }
1036 LogicExpr::Predicate { name, args, .. } => {
1037 let new_args: Vec<_> = args.iter().map(|arg| {
1038 if let Term::Variable(v) = arg {
1039 if *v == ctx.quantifier_var {
1040 return Term::Intension(ctx.restrictor);
1041 }
1042 }
1043 clone_term(arg, term_arena)
1044 }).collect();
1045
1046 expr_arena.alloc(LogicExpr::Predicate {
1047 name: *name,
1048 args: term_arena.alloc_slice(new_args),
1049 world: None,
1050 })
1051 }
1052 _ => expr,
1053 }
1054}
1055
1056pub fn enumerate_intensional_readings<'a>(
1057 expr: &'a LogicExpr<'a>,
1058 interner: &mut Interner,
1059 expr_arena: &'a Arena<LogicExpr<'a>>,
1060 term_arena: &'a Arena<Term<'a>>,
1061 role_arena: &'a Arena<(crate::ast::ThematicRole, Term<'a>)>,
1062) -> Vec<&'a LogicExpr<'a>> {
1063 if let Some(de_re) = build_de_re_from_de_dicto(expr, interner, expr_arena, term_arena, role_arena) {
1065 return vec![de_re, expr];
1067 }
1068
1069 if let Some(de_re) = raise_existential_from_proposition(expr, expr_arena, term_arena) {
1073 return vec![expr, de_re];
1074 }
1075
1076 if let Some(ctx) = find_opaque_verb_context(expr, interner) {
1078 let de_dicto = build_de_dicto_reading(expr, &ctx, expr_arena, term_arena, role_arena);
1079 vec![expr, de_dicto]
1080 } else {
1081 vec![expr]
1082 }
1083}
1084
1085fn raise_existential_from_proposition<'a>(
1089 expr: &'a LogicExpr<'a>,
1090 expr_arena: &'a Arena<LogicExpr<'a>>,
1091 term_arena: &'a Arena<Term<'a>>,
1092) -> Option<&'a LogicExpr<'a>> {
1093 use crate::token::TokenType;
1094
1095 let (temporal_op, attitude) = match expr {
1096 LogicExpr::Temporal { operator, body } => (Some(*operator), *body),
1097 other => (None, other),
1098 };
1099
1100 let LogicExpr::Predicate { name, args, world } = attitude else {
1101 return None;
1102 };
1103 if args.len() != 2 {
1104 return None;
1105 }
1106 let Term::Proposition(LogicExpr::Quantifier {
1107 kind: crate::ast::QuantifierKind::Existential,
1108 variable,
1109 body,
1110 island_id,
1111 }) = &args[1]
1112 else {
1113 return None;
1114 };
1115 let LogicExpr::BinaryOp {
1116 left: restriction,
1117 op: TokenType::And,
1118 right: inner,
1119 } = body
1120 else {
1121 return None;
1122 };
1123
1124 let raised_attitude = expr_arena.alloc(LogicExpr::Predicate {
1125 name: *name,
1126 args: term_arena.alloc_slice([args[0].clone(), Term::Proposition(inner)]),
1127 world: world.clone(),
1128 });
1129 let raised_attitude = match temporal_op {
1130 Some(operator) => expr_arena.alloc(LogicExpr::Temporal {
1131 operator,
1132 body: raised_attitude,
1133 }),
1134 None => raised_attitude,
1135 };
1136 let raised_body = expr_arena.alloc(LogicExpr::BinaryOp {
1137 left: restriction,
1138 op: TokenType::And,
1139 right: raised_attitude,
1140 });
1141 Some(expr_arena.alloc(LogicExpr::Quantifier {
1142 kind: crate::ast::QuantifierKind::Existential,
1143 variable: *variable,
1144 body: raised_body,
1145 island_id: *island_id,
1146 }))
1147}
1148
1149fn build_de_re_from_de_dicto<'a>(
1150 expr: &'a LogicExpr<'a>,
1151 interner: &mut Interner,
1152 expr_arena: &'a Arena<LogicExpr<'a>>,
1153 term_arena: &'a Arena<Term<'a>>,
1154 role_arena: &'a Arena<(crate::ast::ThematicRole, Term<'a>)>,
1155) -> Option<&'a LogicExpr<'a>> {
1156 match expr {
1158 LogicExpr::NeoEvent(data) => {
1159 for (role, term) in data.roles.iter() {
1161 if matches!(role, crate::ast::ThematicRole::Theme) {
1162 if let Term::Intension(noun) = term {
1163 let var = interner.intern("x");
1165
1166 let noun_pred = expr_arena.alloc(LogicExpr::Predicate {
1168 name: *noun,
1169 args: term_arena.alloc_slice([Term::Variable(var)]),
1170 world: None,
1171 });
1172
1173 let new_roles: Vec<_> = data.roles.iter().map(|(r, t)| {
1175 if matches!(r, crate::ast::ThematicRole::Theme) {
1176 (*r, Term::Variable(var))
1177 } else {
1178 (*r, t.clone())
1179 }
1180 }).collect();
1181
1182 let new_event = expr_arena.alloc(LogicExpr::NeoEvent(Box::new(crate::ast::NeoEventData {
1183 event_var: data.event_var,
1184 verb: data.verb,
1185 roles: role_arena.alloc_slice(new_roles),
1186 modifiers: data.modifiers,
1187 suppress_existential: false,
1188 world: None,
1189 })));
1190
1191 let body = expr_arena.alloc(LogicExpr::BinaryOp {
1193 left: noun_pred,
1194 op: crate::token::TokenType::And,
1195 right: new_event,
1196 });
1197
1198 return Some(expr_arena.alloc(LogicExpr::Quantifier {
1199 kind: crate::ast::QuantifierKind::Existential,
1200 variable: var,
1201 body,
1202 island_id: 0,
1203 }));
1204 }
1205 }
1206 }
1207 None
1208 }
1209 _ => None,
1210 }
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215 use super::*;
1216 use crate::ast::{LogicExpr, Term};
1217 use logicaffeine_base::Interner;
1218 use crate::registry::SymbolRegistry;
1219 use crate::OutputFormat;
1220
1221 #[test]
1222 fn test_lambda_formatting_unicode() {
1223 let mut interner = Interner::new();
1224 let expr_arena: Arena<LogicExpr> = Arena::new();
1225 let term_arena: Arena<Term> = Arena::new();
1226
1227 let x = interner.intern("x");
1228 let sleep = interner.intern("Sleep");
1229
1230 let body = expr_arena.alloc(LogicExpr::Predicate {
1231 name: sleep,
1232 args: term_arena.alloc_slice([Term::Variable(x)]),
1233 world: None,
1234 });
1235 let lambda = expr_arena.alloc(LogicExpr::Lambda { variable: x, body });
1236
1237 let mut registry = SymbolRegistry::new();
1238 let output = lambda.transpile(&mut registry, &interner, OutputFormat::Unicode);
1239 assert!(output.contains("λx"), "Unicode should use λ: {}", output);
1240 }
1241
1242 #[test]
1243 fn test_lambda_formatting_latex() {
1244 let mut interner = Interner::new();
1245 let expr_arena: Arena<LogicExpr> = Arena::new();
1246 let term_arena: Arena<Term> = Arena::new();
1247
1248 let x = interner.intern("x");
1249 let sleep = interner.intern("Sleep");
1250
1251 let body = expr_arena.alloc(LogicExpr::Predicate {
1252 name: sleep,
1253 args: term_arena.alloc_slice([Term::Variable(x)]),
1254 world: None,
1255 });
1256 let lambda = expr_arena.alloc(LogicExpr::Lambda { variable: x, body });
1257
1258 let mut registry = SymbolRegistry::new();
1259 let output = lambda.transpile(&mut registry, &interner, OutputFormat::LaTeX);
1260 assert!(output.contains("\\lambda"), "LaTeX should use \\lambda: {}", output);
1261 }
1262
1263 #[test]
1264 fn test_application_formatting() {
1265 let mut interner = Interner::new();
1266 let expr_arena: Arena<LogicExpr> = Arena::new();
1267
1268 let p = interner.intern("P");
1269 let j = interner.intern("j");
1270
1271 let func = expr_arena.alloc(LogicExpr::Atom(p));
1272 let arg = expr_arena.alloc(LogicExpr::Atom(j));
1273 let app = expr_arena.alloc(LogicExpr::App { function: func, argument: arg });
1274
1275 let mut registry = SymbolRegistry::new();
1276 let output = app.transpile(&mut registry, &interner, OutputFormat::Unicode);
1277 assert!(output.contains("(") && output.contains(")"), "App should have parens: {}", output);
1278 }
1279
1280 #[test]
1281 fn test_nested_lambda() {
1282 let mut interner = Interner::new();
1283 let expr_arena: Arena<LogicExpr> = Arena::new();
1284
1285 let x = interner.intern("x");
1286 let y = interner.intern("y");
1287
1288 let inner_body = expr_arena.alloc(LogicExpr::Atom(x));
1289 let inner_lambda = expr_arena.alloc(LogicExpr::Lambda { variable: y, body: inner_body });
1290 let outer_lambda = expr_arena.alloc(LogicExpr::Lambda { variable: x, body: inner_lambda });
1291
1292 let mut registry = SymbolRegistry::new();
1293 let output = outer_lambda.transpile(&mut registry, &interner, OutputFormat::Unicode);
1294 assert!(output.contains("λx") && output.contains("λy"), "Nested lambdas: {}", output);
1295 }
1296
1297 #[test]
1298 fn test_lambda_app_helper_functions() {
1299 let mut interner = Interner::new();
1300 let expr_arena: Arena<LogicExpr> = Arena::new();
1301 let _term_arena: Arena<Term> = Arena::new();
1302
1303 let x = interner.intern("x");
1304 let p = interner.intern("P");
1305
1306 let body = expr_arena.alloc(LogicExpr::Atom(x));
1307 let lambda = LogicExpr::lambda(x, body, &expr_arena);
1308
1309 let arg = expr_arena.alloc(LogicExpr::Atom(p));
1310 let app = LogicExpr::app(lambda, arg, &expr_arena);
1311
1312 assert!(matches!(app, LogicExpr::App { .. }));
1313 }
1314
1315 #[test]
1316 fn lift_proper_name_returns_lambda() {
1317 let mut interner = Interner::new();
1318 let arena: Arena<LogicExpr> = Arena::new();
1319
1320 let john = interner.intern("John");
1321 let lifted = lift_proper_name(john, &mut interner, &arena);
1322
1323 assert!(matches!(lifted, LogicExpr::Lambda { .. }), "Should return Lambda");
1324 }
1325
1326 #[test]
1327 fn lift_proper_name_applies_predicate() {
1328 let mut interner = Interner::new();
1329 let arena: Arena<LogicExpr> = Arena::new();
1330
1331 let john = interner.intern("John");
1332 let lifted = lift_proper_name(john, &mut interner, &arena);
1333
1334 if let LogicExpr::Lambda { body, .. } = lifted {
1335 assert!(matches!(body, LogicExpr::App { .. }), "Body should be App");
1336 } else {
1337 panic!("Expected Lambda");
1338 }
1339 }
1340
1341 #[test]
1342 fn lift_quantifier_universal_returns_lambda() {
1343 let mut interner = Interner::new();
1344 let expr_arena: Arena<LogicExpr> = Arena::new();
1345 let term_arena: Arena<Term> = Arena::new();
1346
1347 let woman = interner.intern("woman");
1348 let lifted = lift_quantifier(QuantifierKind::Universal, woman, &mut interner, &expr_arena, &term_arena);
1349
1350 assert!(matches!(lifted, LogicExpr::Lambda { .. }), "Should return Lambda");
1351 }
1352
1353 #[test]
1354 fn lift_quantifier_universal_structure() {
1355 let mut interner = Interner::new();
1356 let expr_arena: Arena<LogicExpr> = Arena::new();
1357 let term_arena: Arena<Term> = Arena::new();
1358
1359 let woman = interner.intern("woman");
1360 let lifted = lift_quantifier(QuantifierKind::Universal, woman, &mut interner, &expr_arena, &term_arena);
1361
1362 if let LogicExpr::Lambda { body, .. } = lifted {
1363 assert!(
1364 matches!(body, LogicExpr::Quantifier { kind: QuantifierKind::Universal, .. }),
1365 "Body should contain ∀, got {:?}",
1366 body
1367 );
1368 } else {
1369 panic!("Expected Lambda, got {:?}", lifted);
1370 }
1371 }
1372
1373 #[test]
1374 fn lift_quantifier_existential_returns_lambda() {
1375 let mut interner = Interner::new();
1376 let expr_arena: Arena<LogicExpr> = Arena::new();
1377 let term_arena: Arena<Term> = Arena::new();
1378
1379 let man = interner.intern("man");
1380 let lifted = lift_quantifier(QuantifierKind::Existential, man, &mut interner, &expr_arena, &term_arena);
1381
1382 assert!(matches!(lifted, LogicExpr::Lambda { .. }), "Should return Lambda");
1383 }
1384
1385 #[test]
1386 fn lift_quantifier_existential_structure() {
1387 let mut interner = Interner::new();
1388 let expr_arena: Arena<LogicExpr> = Arena::new();
1389 let term_arena: Arena<Term> = Arena::new();
1390
1391 let man = interner.intern("man");
1392 let lifted = lift_quantifier(QuantifierKind::Existential, man, &mut interner, &expr_arena, &term_arena);
1393
1394 if let LogicExpr::Lambda { body, .. } = lifted {
1395 assert!(
1396 matches!(body, LogicExpr::Quantifier { kind: QuantifierKind::Existential, .. }),
1397 "Body should contain ∃, got {:?}",
1398 body
1399 );
1400 } else {
1401 panic!("Expected Lambda, got {:?}", lifted);
1402 }
1403 }
1404
1405 #[test]
1406 fn beta_reduce_simple_predicate() {
1407 let mut interner = Interner::new();
1408 let expr_arena: Arena<LogicExpr> = Arena::new();
1409 let term_arena: Arena<Term> = Arena::new();
1410
1411 let x = interner.intern("x");
1412 let john = interner.intern("John");
1413 let run = interner.intern("Run");
1414
1415 let body = expr_arena.alloc(LogicExpr::Predicate {
1416 name: run,
1417 args: term_arena.alloc_slice([Term::Variable(x)]),
1418 world: None,
1419 });
1420 let lambda = expr_arena.alloc(LogicExpr::Lambda { variable: x, body });
1421 let arg = expr_arena.alloc(LogicExpr::Atom(john));
1422 let app = expr_arena.alloc(LogicExpr::App { function: lambda, argument: arg });
1423
1424 let reduced = beta_reduce(app, &expr_arena, &term_arena);
1425
1426 let mut registry = SymbolRegistry::new();
1427 let output = reduced.transpile(&mut registry, &interner, OutputFormat::Unicode);
1428 assert!(output.contains("R(J)") || output.contains("Run(John)"), "Should substitute: {}", output);
1429 }
1430
1431 #[test]
1432 fn beta_reduce_with_constant() {
1433 let mut interner = Interner::new();
1434 let expr_arena: Arena<LogicExpr> = Arena::new();
1435 let term_arena: Arena<Term> = Arena::new();
1436
1437 let x = interner.intern("x");
1438 let c = interner.intern("c");
1439
1440 let body = expr_arena.alloc(LogicExpr::Atom(c));
1441 let lambda = expr_arena.alloc(LogicExpr::Lambda { variable: x, body });
1442 let arg = expr_arena.alloc(LogicExpr::Atom(interner.intern("anything")));
1443 let app = expr_arena.alloc(LogicExpr::App { function: lambda, argument: arg });
1444
1445 let reduced = beta_reduce(app, &expr_arena, &term_arena);
1446 assert!(matches!(reduced, LogicExpr::Atom(s) if *s == c), "Constant should remain");
1447 }
1448
1449 #[test]
1450 fn beta_reduce_nested_lambda() {
1451 let mut interner = Interner::new();
1452 let expr_arena: Arena<LogicExpr> = Arena::new();
1453 let term_arena: Arena<Term> = Arena::new();
1454
1455 let x = interner.intern("x");
1456 let y = interner.intern("y");
1457
1458 let inner_body = expr_arena.alloc(LogicExpr::Atom(x));
1459 let inner_lambda = expr_arena.alloc(LogicExpr::Lambda { variable: y, body: inner_body });
1460 let outer_lambda = expr_arena.alloc(LogicExpr::Lambda { variable: x, body: inner_lambda });
1461
1462 let reduced = beta_reduce(outer_lambda, &expr_arena, &term_arena);
1463 assert!(matches!(reduced, LogicExpr::Lambda { .. }), "Should still be lambda");
1464 }
1465
1466 #[test]
1467 fn beta_reduce_non_application_unchanged() {
1468 let mut interner = Interner::new();
1469 let expr_arena: Arena<LogicExpr> = Arena::new();
1470 let term_arena: Arena<Term> = Arena::new();
1471
1472 let p = interner.intern("P");
1473 let atom = expr_arena.alloc(LogicExpr::Atom(p));
1474
1475 let reduced = beta_reduce(atom, &expr_arena, &term_arena);
1476 assert!(matches!(reduced, LogicExpr::Atom(s) if *s == p), "Atom unchanged");
1477 }
1478
1479 #[test]
1480 fn beta_reduce_preserves_unbound_variables() {
1481 let mut interner = Interner::new();
1482 let expr_arena: Arena<LogicExpr> = Arena::new();
1483 let term_arena: Arena<Term> = Arena::new();
1484
1485 let x = interner.intern("x");
1486 let y = interner.intern("y");
1487 let john = interner.intern("John");
1488 let loves = interner.intern("Loves");
1489
1490 let body = expr_arena.alloc(LogicExpr::Predicate {
1491 name: loves,
1492 args: term_arena.alloc_slice([Term::Variable(x), Term::Variable(y)]),
1493 world: None,
1494 });
1495 let lambda = expr_arena.alloc(LogicExpr::Lambda { variable: x, body });
1496 let arg = expr_arena.alloc(LogicExpr::Atom(john));
1497 let app = expr_arena.alloc(LogicExpr::App { function: lambda, argument: arg });
1498
1499 let reduced = beta_reduce(app, &expr_arena, &term_arena);
1500
1501 let mut registry = SymbolRegistry::new();
1502 let output = reduced.transpile(&mut registry, &interner, OutputFormat::Unicode);
1503 assert!(output.contains("y"), "y should remain unbound: {}", output);
1504 }
1505
1506 #[test]
1507 fn enumerate_scopings_single_quantifier() {
1508 let mut interner = Interner::new();
1509 let expr_arena: Arena<LogicExpr> = Arena::new();
1510 let term_arena: Arena<Term> = Arena::new();
1511
1512 let x = interner.intern("x");
1513 let dog = interner.intern("Dog");
1514 let bark = interner.intern("Bark");
1515
1516 let left = expr_arena.alloc(LogicExpr::Predicate {
1517 name: dog,
1518 args: term_arena.alloc_slice([Term::Variable(x)]),
1519 world: None,
1520 });
1521 let right = expr_arena.alloc(LogicExpr::Predicate {
1522 name: bark,
1523 args: term_arena.alloc_slice([Term::Variable(x)]),
1524 world: None,
1525 });
1526 let body = expr_arena.alloc(LogicExpr::BinaryOp {
1527 left,
1528 op: TokenType::Implies,
1529 right,
1530 });
1531 let expr = expr_arena.alloc(LogicExpr::Quantifier {
1532 kind: QuantifierKind::Universal,
1533 variable: x,
1534 body,
1535 island_id: 0,
1536 });
1537
1538 let scopings = enumerate_scopings(expr, &mut interner, &expr_arena, &term_arena);
1539 assert_eq!(scopings.len(), 1, "Single quantifier should have 1 reading");
1540 }
1541
1542 #[test]
1543 fn enumerate_scopings_no_quantifier() {
1544 let mut interner = Interner::new();
1545 let expr_arena: Arena<LogicExpr> = Arena::new();
1546 let term_arena: Arena<Term> = Arena::new();
1547
1548 let run = interner.intern("Run");
1549 let john = interner.intern("John");
1550
1551 let expr = expr_arena.alloc(LogicExpr::Predicate {
1552 name: run,
1553 args: term_arena.alloc_slice([Term::Constant(john)]),
1554 world: None,
1555 });
1556
1557 let scopings = enumerate_scopings(expr, &mut interner, &expr_arena, &term_arena);
1558 assert_eq!(scopings.len(), 1, "No quantifiers should have 1 reading");
1559 }
1560
1561 #[test]
1562 fn is_opaque_verb_believes() {
1563 let mut interner = Interner::new();
1564 let believes = interner.intern("believes");
1565 let believes_cap = interner.intern("Believes");
1566 assert!(is_opaque_verb(believes, &interner), "believes should be opaque");
1567 assert!(is_opaque_verb(believes_cap, &interner), "Believes should be opaque");
1568 }
1569
1570 #[test]
1571 fn is_opaque_verb_seeks() {
1572 let mut interner = Interner::new();
1573 let seeks = interner.intern("seeks");
1574 let wants = interner.intern("wants");
1575 assert!(is_opaque_verb(seeks, &interner), "seeks should be opaque");
1576 assert!(is_opaque_verb(wants, &interner), "wants should be opaque");
1577 }
1578
1579 #[test]
1580 fn is_opaque_verb_normal_verbs() {
1581 let mut interner = Interner::new();
1582 let runs = interner.intern("runs");
1583 let loves = interner.intern("loves");
1584 assert!(!is_opaque_verb(runs, &interner), "runs should NOT be opaque");
1585 assert!(!is_opaque_verb(loves, &interner), "loves should NOT be opaque");
1586 }
1587
1588 #[test]
1589 fn make_intensional_creates_wrapper() {
1590 let mut interner = Interner::new();
1591 let expr_arena: Arena<LogicExpr> = Arena::new();
1592 let term_arena: Arena<Term> = Arena::new();
1593
1594 let weak = interner.intern("Weak");
1595 let clark = interner.intern("Clark");
1596 let believes = interner.intern("believes");
1597
1598 let content = expr_arena.alloc(LogicExpr::Predicate {
1599 name: weak,
1600 args: term_arena.alloc_slice([Term::Constant(clark)]),
1601 world: None,
1602 });
1603
1604 let intensional = make_intensional(believes, content, &expr_arena);
1605
1606 assert!(
1607 matches!(intensional, LogicExpr::Intensional { operator, .. } if *operator == believes),
1608 "Should create Intensional wrapper, got {:?}",
1609 intensional
1610 );
1611 }
1612
1613 #[test]
1614 fn intensional_transpiles_with_brackets() {
1615 let mut interner = Interner::new();
1616 let expr_arena: Arena<LogicExpr> = Arena::new();
1617 let term_arena: Arena<Term> = Arena::new();
1618
1619 let weak = interner.intern("Weak");
1620 let clark = interner.intern("Clark");
1621 let believes = interner.intern("Believes");
1622
1623 let content = expr_arena.alloc(LogicExpr::Predicate {
1624 name: weak,
1625 args: term_arena.alloc_slice([Term::Constant(clark)]),
1626 world: None,
1627 });
1628
1629 let intensional = expr_arena.alloc(LogicExpr::Intensional {
1630 operator: believes,
1631 content,
1632 });
1633
1634 let mut registry = SymbolRegistry::new();
1635 let output = intensional.transpile(&mut registry, &interner, OutputFormat::Unicode);
1636
1637 assert!(
1638 output.contains("[") && output.contains("]"),
1639 "Intensional should use brackets: got {}",
1640 output
1641 );
1642 }
1643
1644 #[test]
1645 fn substitute_respecting_opacity_blocks_inside_intensional() {
1646 let mut interner = Interner::new();
1647 let expr_arena: Arena<LogicExpr> = Arena::new();
1648 let term_arena: Arena<Term> = Arena::new();
1649
1650 let weak = interner.intern("Weak");
1651 let clark = interner.intern("Clark");
1652 let believes = interner.intern("Believes");
1653 let superman = interner.intern("Superman");
1654
1655 let inner = expr_arena.alloc(LogicExpr::Predicate {
1656 name: weak,
1657 args: term_arena.alloc_slice([Term::Constant(clark)]),
1658 world: None,
1659 });
1660 let expr = expr_arena.alloc(LogicExpr::Intensional {
1661 operator: believes,
1662 content: inner,
1663 });
1664
1665 let replacement = expr_arena.alloc(LogicExpr::Atom(superman));
1666 let result = substitute_respecting_opacity(expr, clark, replacement, &expr_arena, &term_arena);
1667
1668 let mut registry = SymbolRegistry::new();
1669 let output = result.transpile(&mut registry, &interner, OutputFormat::Unicode);
1670
1671 assert!(
1672 output.contains("C") && !output.contains("S"),
1673 "Should NOT substitute inside intensional context: got {}",
1674 output
1675 );
1676 }
1677
1678 #[test]
1679 fn substitute_respecting_opacity_allows_outside() {
1680 let mut interner = Interner::new();
1681 let expr_arena: Arena<LogicExpr> = Arena::new();
1682 let term_arena: Arena<Term> = Arena::new();
1683
1684 let weak = interner.intern("Weak");
1685 let clark = interner.intern("Clark");
1686 let superman = interner.intern("Superman");
1687
1688 let expr = expr_arena.alloc(LogicExpr::Predicate {
1689 name: weak,
1690 args: term_arena.alloc_slice([Term::Constant(clark)]),
1691 world: None,
1692 });
1693
1694 let replacement = expr_arena.alloc(LogicExpr::Atom(superman));
1695 let result = substitute_respecting_opacity(expr, clark, replacement, &expr_arena, &term_arena);
1696
1697 let mut registry = SymbolRegistry::new();
1698 let output = result.transpile(&mut registry, &interner, OutputFormat::Unicode);
1699
1700 assert!(
1701 output.contains("S"),
1702 "Should substitute outside intensional context: got {}",
1703 output
1704 );
1705 }
1706
1707 #[test]
1708 fn factorial_basic() {
1709 assert_eq!(factorial(0), 1);
1710 assert_eq!(factorial(1), 1);
1711 assert_eq!(factorial(2), 2);
1712 assert_eq!(factorial(3), 6);
1713 assert_eq!(factorial(4), 24);
1714 assert_eq!(factorial(5), 120);
1715 }
1716
1717 #[test]
1718 fn scope_iterator_two_quantifiers_yields_two() {
1719 let mut interner = Interner::new();
1720 let expr_arena: Arena<LogicExpr> = Arena::new();
1721 let term_arena: Arena<Term> = Arena::new();
1722
1723 let x = interner.intern("x");
1724 let y = interner.intern("y");
1725 let man = interner.intern("Man");
1726 let woman = interner.intern("Woman");
1727 let loves = interner.intern("Loves");
1728
1729 let man_x = expr_arena.alloc(LogicExpr::Predicate {
1730 name: man,
1731 args: term_arena.alloc_slice([Term::Variable(x)]),
1732 world: None,
1733 });
1734 let woman_y = expr_arena.alloc(LogicExpr::Predicate {
1735 name: woman,
1736 args: term_arena.alloc_slice([Term::Variable(y)]),
1737 world: None,
1738 });
1739 let loves_xy = expr_arena.alloc(LogicExpr::Predicate {
1740 name: loves,
1741 args: term_arena.alloc_slice([Term::Variable(x), Term::Variable(y)]),
1742 world: None,
1743 });
1744
1745 let inner = expr_arena.alloc(LogicExpr::BinaryOp {
1746 left: woman_y,
1747 op: TokenType::And,
1748 right: loves_xy,
1749 });
1750 let inner_q = expr_arena.alloc(LogicExpr::Quantifier {
1751 kind: QuantifierKind::Existential,
1752 variable: y,
1753 body: inner,
1754 island_id: 0,
1755 });
1756
1757 let outer = expr_arena.alloc(LogicExpr::BinaryOp {
1758 left: man_x,
1759 op: TokenType::Implies,
1760 right: inner_q,
1761 });
1762 let expr = expr_arena.alloc(LogicExpr::Quantifier {
1763 kind: QuantifierKind::Universal,
1764 variable: x,
1765 body: outer,
1766 island_id: 0,
1767 });
1768
1769 let scopings: Vec<_> = enumerate_scopings(expr, &mut interner, &expr_arena, &term_arena).collect();
1770 assert_eq!(scopings.len(), 2, "Two quantifiers should have 2! = 2 readings");
1771 }
1772
1773 #[test]
1774 fn scope_iterator_three_quantifiers_yields_six() {
1775 let mut interner = Interner::new();
1776 let expr_arena: Arena<LogicExpr> = Arena::new();
1777 let term_arena: Arena<Term> = Arena::new();
1778
1779 let x = interner.intern("x");
1780 let y = interner.intern("y");
1781 let z = interner.intern("z");
1782 let man = interner.intern("Man");
1783 let woman = interner.intern("Woman");
1784 let book = interner.intern("Book");
1785 let gives = interner.intern("Gives");
1786
1787 let man_x = expr_arena.alloc(LogicExpr::Predicate {
1788 name: man,
1789 args: term_arena.alloc_slice([Term::Variable(x)]),
1790 world: None,
1791 });
1792 let woman_y = expr_arena.alloc(LogicExpr::Predicate {
1793 name: woman,
1794 args: term_arena.alloc_slice([Term::Variable(y)]),
1795 world: None,
1796 });
1797 let book_z = expr_arena.alloc(LogicExpr::Predicate {
1798 name: book,
1799 args: term_arena.alloc_slice([Term::Variable(z)]),
1800 world: None,
1801 });
1802 let gives_xyz = expr_arena.alloc(LogicExpr::Predicate {
1803 name: gives,
1804 args: term_arena.alloc_slice([Term::Variable(x), Term::Variable(y), Term::Variable(z)]),
1805 world: None,
1806 });
1807
1808 let inner_z = expr_arena.alloc(LogicExpr::BinaryOp {
1809 left: book_z,
1810 op: TokenType::And,
1811 right: gives_xyz,
1812 });
1813 let q_z = expr_arena.alloc(LogicExpr::Quantifier {
1814 kind: QuantifierKind::Existential,
1815 variable: z,
1816 body: inner_z,
1817 island_id: 0,
1818 });
1819
1820 let inner_y = expr_arena.alloc(LogicExpr::BinaryOp {
1821 left: woman_y,
1822 op: TokenType::And,
1823 right: q_z,
1824 });
1825 let q_y = expr_arena.alloc(LogicExpr::Quantifier {
1826 kind: QuantifierKind::Existential,
1827 variable: y,
1828 body: inner_y,
1829 island_id: 0,
1830 });
1831
1832 let outer = expr_arena.alloc(LogicExpr::BinaryOp {
1833 left: man_x,
1834 op: TokenType::Implies,
1835 right: q_y,
1836 });
1837 let expr = expr_arena.alloc(LogicExpr::Quantifier {
1838 kind: QuantifierKind::Universal,
1839 variable: x,
1840 body: outer,
1841 island_id: 0,
1842 });
1843
1844 let scopings: Vec<_> = enumerate_scopings(expr, &mut interner, &expr_arena, &term_arena).collect();
1845 assert_eq!(scopings.len(), 6, "Three quantifiers should have 3! = 6 readings");
1846 }
1847
1848 #[test]
1849 fn scope_iterator_no_duplicates() {
1850 use std::collections::HashSet;
1851
1852 let mut interner = Interner::new();
1853 let expr_arena: Arena<LogicExpr> = Arena::new();
1854 let term_arena: Arena<Term> = Arena::new();
1855
1856 let x = interner.intern("x");
1857 let y = interner.intern("y");
1858 let man = interner.intern("Man");
1859 let woman = interner.intern("Woman");
1860 let loves = interner.intern("Loves");
1861
1862 let man_x = expr_arena.alloc(LogicExpr::Predicate {
1863 name: man,
1864 args: term_arena.alloc_slice([Term::Variable(x)]),
1865 world: None,
1866 });
1867 let woman_y = expr_arena.alloc(LogicExpr::Predicate {
1868 name: woman,
1869 args: term_arena.alloc_slice([Term::Variable(y)]),
1870 world: None,
1871 });
1872 let loves_xy = expr_arena.alloc(LogicExpr::Predicate {
1873 name: loves,
1874 args: term_arena.alloc_slice([Term::Variable(x), Term::Variable(y)]),
1875 world: None,
1876 });
1877
1878 let inner = expr_arena.alloc(LogicExpr::BinaryOp {
1879 left: woman_y,
1880 op: TokenType::And,
1881 right: loves_xy,
1882 });
1883 let inner_q = expr_arena.alloc(LogicExpr::Quantifier {
1884 kind: QuantifierKind::Existential,
1885 variable: y,
1886 body: inner,
1887 island_id: 0,
1888 });
1889
1890 let outer = expr_arena.alloc(LogicExpr::BinaryOp {
1891 left: man_x,
1892 op: TokenType::Implies,
1893 right: inner_q,
1894 });
1895 let expr = expr_arena.alloc(LogicExpr::Quantifier {
1896 kind: QuantifierKind::Universal,
1897 variable: x,
1898 body: outer,
1899 island_id: 0,
1900 });
1901
1902 let mut registry = SymbolRegistry::new();
1903 let outputs: HashSet<String> = enumerate_scopings(expr, &mut interner, &expr_arena, &term_arena)
1904 .map(|e| e.transpile(&mut registry, &interner, OutputFormat::Unicode))
1905 .collect();
1906
1907 assert_eq!(outputs.len(), 2, "All scopings should be unique");
1908 }
1909
1910 #[test]
1911 fn scope_iterator_exact_size() {
1912 let mut interner = Interner::new();
1913 let expr_arena: Arena<LogicExpr> = Arena::new();
1914 let term_arena: Arena<Term> = Arena::new();
1915
1916 let x = interner.intern("x");
1917 let y = interner.intern("y");
1918 let man = interner.intern("Man");
1919 let woman = interner.intern("Woman");
1920 let loves = interner.intern("Loves");
1921
1922 let man_x = expr_arena.alloc(LogicExpr::Predicate {
1923 name: man,
1924 args: term_arena.alloc_slice([Term::Variable(x)]),
1925 world: None,
1926 });
1927 let woman_y = expr_arena.alloc(LogicExpr::Predicate {
1928 name: woman,
1929 args: term_arena.alloc_slice([Term::Variable(y)]),
1930 world: None,
1931 });
1932 let loves_xy = expr_arena.alloc(LogicExpr::Predicate {
1933 name: loves,
1934 args: term_arena.alloc_slice([Term::Variable(x), Term::Variable(y)]),
1935 world: None,
1936 });
1937
1938 let inner = expr_arena.alloc(LogicExpr::BinaryOp {
1939 left: woman_y,
1940 op: TokenType::And,
1941 right: loves_xy,
1942 });
1943 let inner_q = expr_arena.alloc(LogicExpr::Quantifier {
1944 kind: QuantifierKind::Existential,
1945 variable: y,
1946 body: inner,
1947 island_id: 0,
1948 });
1949
1950 let outer = expr_arena.alloc(LogicExpr::BinaryOp {
1951 left: man_x,
1952 op: TokenType::Implies,
1953 right: inner_q,
1954 });
1955 let expr = expr_arena.alloc(LogicExpr::Quantifier {
1956 kind: QuantifierKind::Universal,
1957 variable: x,
1958 body: outer,
1959 island_id: 0,
1960 });
1961
1962 let mut iter = enumerate_scopings(expr, &mut interner, &expr_arena, &term_arena);
1963 assert_eq!(iter.len(), 2);
1964 iter.next();
1965 assert_eq!(iter.len(), 1);
1966 iter.next();
1967 assert_eq!(iter.len(), 0);
1968 }
1969
1970 #[test]
1971 fn island_constraints_reduce_permutations() {
1972 let mut interner = Interner::new();
1973 let expr_arena: Arena<LogicExpr> = Arena::new();
1974 let term_arena: Arena<Term> = Arena::new();
1975
1976 let x = interner.intern("x");
1977 let y = interner.intern("y");
1978 let man = interner.intern("Man");
1979 let woman = interner.intern("Woman");
1980 let loves = interner.intern("Loves");
1981
1982 let man_x = expr_arena.alloc(LogicExpr::Predicate {
1983 name: man,
1984 args: term_arena.alloc_slice([Term::Variable(x)]),
1985 world: None,
1986 });
1987 let woman_y = expr_arena.alloc(LogicExpr::Predicate {
1988 name: woman,
1989 args: term_arena.alloc_slice([Term::Variable(y)]),
1990 world: None,
1991 });
1992 let loves_xy = expr_arena.alloc(LogicExpr::Predicate {
1993 name: loves,
1994 args: term_arena.alloc_slice([Term::Variable(x), Term::Variable(y)]),
1995 world: None,
1996 });
1997
1998 let inner = expr_arena.alloc(LogicExpr::BinaryOp {
1999 left: woman_y,
2000 op: TokenType::And,
2001 right: loves_xy,
2002 });
2003 let inner_q = expr_arena.alloc(LogicExpr::Quantifier {
2004 kind: QuantifierKind::Existential,
2005 variable: y,
2006 body: inner,
2007 island_id: 1,
2008 });
2009
2010 let outer = expr_arena.alloc(LogicExpr::BinaryOp {
2011 left: man_x,
2012 op: TokenType::Implies,
2013 right: inner_q,
2014 });
2015 let expr = expr_arena.alloc(LogicExpr::Quantifier {
2016 kind: QuantifierKind::Universal,
2017 variable: x,
2018 body: outer,
2019 island_id: 0,
2020 });
2021
2022 let scopings = enumerate_scopings(expr, &mut interner, &expr_arena, &term_arena);
2023 assert_eq!(
2024 scopings.len(),
2025 1,
2026 "Two quantifiers in different islands: 1! × 1! = 1 reading (no cross-island scoping)"
2027 );
2028 }
2029
2030 #[test]
2031 fn multiple_quantifiers_per_island() {
2032 let mut interner = Interner::new();
2033 let expr_arena: Arena<LogicExpr> = Arena::new();
2034 let term_arena: Arena<Term> = Arena::new();
2035
2036 let x = interner.intern("x");
2037 let y = interner.intern("y");
2038 let z = interner.intern("z");
2039 let w = interner.intern("w");
2040 let pred = interner.intern("P");
2041
2042 let core = expr_arena.alloc(LogicExpr::Predicate {
2043 name: pred,
2044 args: term_arena.alloc_slice([
2045 Term::Variable(x),
2046 Term::Variable(y),
2047 Term::Variable(z),
2048 Term::Variable(w),
2049 ]),
2050 world: None,
2051 });
2052
2053 let true_sym = interner.intern("T");
2054 let t = expr_arena.alloc(LogicExpr::Atom(true_sym));
2055
2056 let q_w = expr_arena.alloc(LogicExpr::Quantifier {
2057 kind: QuantifierKind::Existential,
2058 variable: w,
2059 body: expr_arena.alloc(LogicExpr::BinaryOp { left: t, op: TokenType::And, right: core }),
2060 island_id: 1,
2061 });
2062 let q_z = expr_arena.alloc(LogicExpr::Quantifier {
2063 kind: QuantifierKind::Existential,
2064 variable: z,
2065 body: expr_arena.alloc(LogicExpr::BinaryOp { left: t, op: TokenType::And, right: q_w }),
2066 island_id: 1,
2067 });
2068 let q_y = expr_arena.alloc(LogicExpr::Quantifier {
2069 kind: QuantifierKind::Existential,
2070 variable: y,
2071 body: expr_arena.alloc(LogicExpr::BinaryOp { left: t, op: TokenType::And, right: q_z }),
2072 island_id: 0,
2073 });
2074 let expr = expr_arena.alloc(LogicExpr::Quantifier {
2075 kind: QuantifierKind::Universal,
2076 variable: x,
2077 body: expr_arena.alloc(LogicExpr::BinaryOp { left: t, op: TokenType::Implies, right: q_y }),
2078 island_id: 0,
2079 });
2080
2081 let scopings = enumerate_scopings(expr, &mut interner, &expr_arena, &term_arena);
2082 assert_eq!(
2083 scopings.len(),
2084 4,
2085 "4 quantifiers split 2+2 across islands: 2! × 2! = 4 (not 4! = 24)"
2086 );
2087 }
2088}