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