1use logicaffeine_base::Symbol;
46use std::fmt;
47
48pub use logicaffeine_lexicon::types::{Gender, Number, Case};
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum TimeRelation {
57 Precedes,
58 Equals,
59}
60
61#[derive(Debug, Clone)]
62pub struct TimeConstraint {
63 pub left: String,
64 pub relation: TimeRelation,
65 pub right: String,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
69pub enum OwnershipState {
70 #[default]
71 Owned,
72 Moved,
73 Borrowed,
74}
75
76#[derive(Debug, Clone, PartialEq)]
82pub enum ScopeError {
83 InaccessibleReferent {
85 gender: Gender,
86 blocking_scope: BoxType,
87 reason: String,
88 },
89 NoMatchingReferent {
91 gender: Gender,
92 number: Number,
93 },
94}
95
96impl fmt::Display for ScopeError {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 match self {
99 ScopeError::InaccessibleReferent { gender, blocking_scope, reason } => {
100 write!(f, "Cannot resolve {:?} pronoun: referent is trapped in {:?} scope. {}",
101 gender, blocking_scope, reason)
102 }
103 ScopeError::NoMatchingReferent { gender, number } => {
104 write!(f, "Cannot resolve {:?} {:?} pronoun: no matching referent in accessible scope",
105 gender, number)
106 }
107 }
108 }
109}
110
111impl std::error::Error for ScopeError {}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum ScopePath {
120 QuantifierBody,
122 ImplicationRight,
124 ConjunctionRight,
126}
127
128#[derive(Debug, Clone)]
130pub struct TelescopeCandidate {
131 pub variable: Symbol,
132 pub noun_class: Symbol,
133 pub gender: Gender,
134 pub origin_box: usize,
136 pub scope_path: Vec<ScopePath>,
138 pub in_modal_scope: bool,
140}
141
142#[derive(Debug, Clone)]
149pub struct ModalContext {
150 pub active: bool,
152 pub is_epistemic: bool,
154 pub force: f32,
156}
157
158#[derive(Debug, Clone)]
164pub struct WorldState {
165 pub drs: Drs,
167 event_counter: usize,
169 event_history: Vec<String>,
171 reference_time_counter: usize,
173 current_reference_time: Option<String>,
175 time_constraints: Vec<TimeConstraint>,
177 telescope_candidates: Vec<TelescopeCandidate>,
179 discourse_mode: bool,
182 current_modal_context: Option<ModalContext>,
184 prior_modal_context: Option<ModalContext>,
186}
187
188impl WorldState {
189 pub fn new() -> Self {
190 Self {
191 drs: Drs::new(),
192 event_counter: 0,
193 event_history: Vec::new(),
194 reference_time_counter: 0,
195 current_reference_time: None,
196 time_constraints: Vec::new(),
197 telescope_candidates: Vec::new(),
198 discourse_mode: false,
199 current_modal_context: None,
200 prior_modal_context: None,
201 }
202 }
203
204 pub fn next_event_var(&mut self) -> String {
206 self.event_counter += 1;
207 let var = format!("e{}", self.event_counter);
208 self.event_history.push(var.clone());
209 var
210 }
211
212 pub fn event_history(&self) -> &[String] {
214 &self.event_history
215 }
216
217 pub fn next_reference_time(&mut self) -> String {
219 self.reference_time_counter += 1;
220 let var = format!("r{}", self.reference_time_counter);
221 self.current_reference_time = Some(var.clone());
222 var
223 }
224
225 pub fn current_reference_time(&self) -> String {
227 self.current_reference_time.clone().unwrap_or_else(|| "S".to_string())
228 }
229
230 pub fn add_time_constraint(&mut self, left: String, relation: TimeRelation, right: String) {
232 self.time_constraints.push(TimeConstraint { left, relation, right });
233 }
234
235 pub fn time_constraints(&self) -> &[TimeConstraint] {
237 &self.time_constraints
238 }
239
240 pub fn clear_time_constraints(&mut self) {
242 self.time_constraints.clear();
243 self.reference_time_counter = 0;
244 self.current_reference_time = None;
245 }
246
247 pub fn end_sentence(&mut self) {
249 let mut candidates = self.drs.get_telescope_candidates();
251
252 if self.current_modal_context.is_some() {
256 for candidate in &mut candidates {
257 candidate.in_modal_scope = true;
258 }
259 }
260
261 self.telescope_candidates = candidates;
262 self.prior_modal_context = self.current_modal_context.take();
264 self.discourse_mode = true;
266 }
267
268 pub fn in_discourse_mode(&self) -> bool {
271 self.discourse_mode
272 }
273
274 pub fn telescope_candidates(&self) -> &[TelescopeCandidate] {
276 &self.telescope_candidates
277 }
278
279 pub fn resolve_via_telescope(&mut self, gender: Gender) -> Option<TelescopeCandidate> {
281 let can_access_modal = self.in_modal_context();
285
286 #[cfg(debug_assertions)]
287 eprintln!("[TELESCOPE DEBUG] can_access_modal={}, candidates={:?}",
288 can_access_modal,
289 self.telescope_candidates.iter()
290 .map(|c| (c.in_modal_scope, c.gender))
291 .collect::<Vec<_>>()
292 );
293
294 for candidate in &self.telescope_candidates {
299 if candidate.in_modal_scope && !can_access_modal {
301 #[cfg(debug_assertions)]
303 eprintln!("[TELESCOPE DEBUG] BLOCKED modal candidate: {:?}", candidate.gender);
304 continue;
305 }
306
307 let gender_match = candidate.gender == gender
308 || candidate.gender == Gender::Unknown || gender == Gender::Unknown;
310
311 if gender_match {
312 return Some(candidate.clone());
313 }
314 }
315
316 None
317 }
318
319 pub fn set_ownership(&mut self, noun_class: Symbol, state: OwnershipState) {
321 self.drs.set_ownership(noun_class, state);
322 }
323
324 pub fn get_ownership(&self, noun_class: Symbol) -> Option<OwnershipState> {
326 self.drs.get_ownership(noun_class)
327 }
328
329 pub fn set_ownership_by_var(&mut self, var: Symbol, state: OwnershipState) {
331 self.drs.set_ownership_by_var(var, state);
332 }
333
334 pub fn get_ownership_by_var(&self, var: Symbol) -> Option<OwnershipState> {
336 self.drs.get_ownership_by_var(var)
337 }
338
339 pub fn enter_modal_context(&mut self, is_epistemic: bool, force: f32) {
345 self.current_modal_context = Some(ModalContext {
346 active: true,
347 is_epistemic,
348 force,
349 });
350 self.drs.enter_box(BoxType::ModalScope);
352 }
353
354 pub fn exit_modal_context(&mut self) {
356 self.current_modal_context = None;
357 self.drs.exit_box();
358 }
359
360 pub fn in_modal_context(&self) -> bool {
362 self.current_modal_context.is_some()
363 }
364
365 pub fn has_prior_modal_context(&self) -> bool {
367 self.prior_modal_context.is_some()
368 }
369
370 pub fn can_subordinate(&self) -> bool {
373 self.prior_modal_context.is_some()
374 }
375
376 pub fn clear(&mut self) {
378 self.drs.clear();
379 self.event_counter = 0;
380 self.event_history.clear();
381 self.reference_time_counter = 0;
382 self.current_reference_time = None;
383 self.time_constraints.clear();
384 self.telescope_candidates.clear();
385 self.discourse_mode = false;
386 self.current_modal_context = None;
387 self.prior_modal_context = None;
388 }
389}
390
391impl Default for WorldState {
392 fn default() -> Self {
393 Self::new()
394 }
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq)]
402pub enum ReferentSource {
403 MainClause,
405 ProperName,
407 ConditionalAntecedent,
409 UniversalRestrictor,
411 NegationScope,
413 Disjunct,
415 ModalScope,
417}
418
419impl ReferentSource {
420 pub fn gets_universal_force(&self) -> bool {
421 matches!(
422 self,
423 ReferentSource::ConditionalAntecedent | ReferentSource::UniversalRestrictor
424 )
425 }
426}
427
428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
429pub enum BoxType {
430 Main,
432 ConditionalAntecedent,
434 ConditionalConsequent,
436 NegationScope,
438 UniversalRestrictor,
440 UniversalScope,
442 Disjunct,
444 ModalScope,
447}
448
449impl BoxType {
450 pub fn to_referent_source(&self) -> ReferentSource {
451 match self {
452 BoxType::Main => ReferentSource::MainClause,
453 BoxType::ConditionalAntecedent => ReferentSource::ConditionalAntecedent,
454 BoxType::ConditionalConsequent => ReferentSource::MainClause,
455 BoxType::NegationScope => ReferentSource::NegationScope,
456 BoxType::UniversalRestrictor => ReferentSource::UniversalRestrictor,
457 BoxType::UniversalScope => ReferentSource::MainClause,
458 BoxType::Disjunct => ReferentSource::Disjunct,
459 BoxType::ModalScope => ReferentSource::ModalScope,
460 }
461 }
462
463 pub fn can_telescope(&self) -> bool {
467 matches!(
468 self,
469 BoxType::Main
470 | BoxType::UniversalScope
471 | BoxType::UniversalRestrictor
472 | BoxType::ConditionalConsequent
473 | BoxType::ConditionalAntecedent
474 | BoxType::ModalScope )
476 }
478
479 pub fn blocks_accessibility(&self) -> bool {
481 matches!(self, BoxType::NegationScope | BoxType::Disjunct)
482 }
483}
484
485#[derive(Debug, Clone)]
486pub struct Referent {
487 pub variable: Symbol,
488 pub noun_class: Symbol,
489 pub gender: Gender,
490 pub number: Number,
491 pub source: ReferentSource,
492 pub used_by_pronoun: bool,
493 pub ownership: OwnershipState,
494}
495
496impl Referent {
497 pub fn new(variable: Symbol, noun_class: Symbol, gender: Gender, number: Number, source: ReferentSource) -> Self {
498 Self {
499 variable,
500 noun_class,
501 gender,
502 number,
503 source,
504 used_by_pronoun: false,
505 ownership: OwnershipState::Owned,
506 }
507 }
508
509 pub fn should_be_universal(&self) -> bool {
510 self.source.gets_universal_force() || self.used_by_pronoun
511 }
512}
513
514#[derive(Debug, Clone, Default)]
515pub struct DrsBox {
516 pub universe: Vec<Referent>,
517 pub box_type: Option<BoxType>,
518 pub parent: Option<usize>,
519}
520
521impl DrsBox {
522 pub fn new(box_type: BoxType, parent: Option<usize>) -> Self {
523 Self {
524 universe: Vec::new(),
525 box_type: Some(box_type),
526 parent,
527 }
528 }
529}
530
531#[derive(Debug, Clone)]
532pub struct Drs {
533 boxes: Vec<DrsBox>,
534 main_box: usize,
535 current_box: usize,
536}
537
538impl Drs {
539 pub fn new() -> Self {
540 let main = DrsBox::new(BoxType::Main, None);
541 Self {
542 boxes: vec![main],
543 main_box: 0,
544 current_box: 0,
545 }
546 }
547
548 pub fn enter_box(&mut self, box_type: BoxType) -> usize {
549 let parent = self.current_box;
550 let new_box = DrsBox::new(box_type, Some(parent));
551 let idx = self.boxes.len();
552 self.boxes.push(new_box);
553 self.current_box = idx;
554 idx
555 }
556
557 pub fn exit_box(&mut self) {
558 if let Some(parent) = self.boxes[self.current_box].parent {
559 self.current_box = parent;
560 }
561 }
562
563 pub fn current_box_index(&self) -> usize {
564 self.current_box
565 }
566
567 pub fn current_box_type(&self) -> Option<BoxType> {
568 self.boxes.get(self.current_box).and_then(|b| b.box_type)
569 }
570
571 pub fn introduce_referent(&mut self, variable: Symbol, noun_class: Symbol, gender: Gender, number: Number) {
572 let source = self.boxes[self.current_box]
573 .box_type
574 .map(|bt| bt.to_referent_source())
575 .unwrap_or(ReferentSource::MainClause);
576
577 let referent = Referent::new(variable, noun_class, gender, number, source);
578 self.boxes[self.current_box].universe.push(referent);
579 }
580
581 pub fn introduce_referent_with_source(&mut self, variable: Symbol, noun_class: Symbol, gender: Gender, number: Number, source: ReferentSource) {
583 let referent = Referent::new(variable, noun_class, gender, number, source);
584 self.boxes[self.current_box].universe.push(referent);
585 }
586
587 pub fn introduce_proper_name(&mut self, variable: Symbol, name: Symbol, gender: Gender) {
588 let referent = Referent::new(variable, name, gender, Number::Singular, ReferentSource::ProperName);
590 self.boxes[self.current_box].universe.push(referent);
591 }
592
593 pub fn is_accessible(&self, target_box: usize, from_box: usize) -> bool {
595 if target_box == from_box {
596 return true;
597 }
598
599 let target = &self.boxes[target_box];
600 let from = &self.boxes[from_box];
601
602 if let Some(bt) = target.box_type {
608 match bt {
609 BoxType::NegationScope | BoxType::Disjunct | BoxType::ModalScope => {
610 return false;
613 }
614 _ => {}
615 }
616 }
617
618 if let (Some(BoxType::ConditionalConsequent), Some(BoxType::ConditionalAntecedent)) =
621 (from.box_type, target.box_type)
622 {
623 if from.parent == target.parent {
625 return true;
626 }
627 }
628
629 if let (Some(BoxType::UniversalScope), Some(BoxType::UniversalRestrictor)) =
631 (from.box_type, target.box_type)
632 {
633 if from.parent == target.parent {
634 return true;
635 }
636 }
637
638 let mut current = from_box;
640 while let Some(parent) = self.boxes[current].parent {
641 if parent == target_box {
642 return true;
643 }
644 current = parent;
645 }
646
647 false
648 }
649
650 pub fn resolve_pronoun(&mut self, from_box: usize, gender: Gender, number: Number) -> Result<Symbol, ScopeError> {
652 let mut candidates = Vec::new();
658
659 for (box_idx, drs_box) in self.boxes.iter().enumerate() {
660 let box_accessible = self.is_accessible(box_idx, from_box);
661
662 for referent in &drs_box.universe {
663 if matches!(referent.source, ReferentSource::NegationScope | ReferentSource::Disjunct) {
666 continue;
667 }
668
669 let has_global_source = matches!(referent.source, ReferentSource::MainClause | ReferentSource::ProperName);
672 if !box_accessible && !has_global_source {
673 continue;
674 }
675
676 let gender_match = referent.gender == gender
682 || referent.gender == Gender::Unknown
683 || gender == Gender::Unknown;
684
685 let number_match = referent.number == number;
687
688 if gender_match && number_match {
689 candidates.push((box_idx, referent.variable));
690 }
691 }
692 }
693
694 if let Some((box_idx, var)) = candidates.last() {
696 let box_idx = *box_idx;
697 let var = *var;
698 for referent in &mut self.boxes[box_idx].universe {
699 if referent.variable == var {
700 referent.used_by_pronoun = true;
701 return Ok(var);
702 }
703 }
704 }
705
706 for (_box_idx, drs_box) in self.boxes.iter().enumerate() {
709 for referent in &drs_box.universe {
710 if matches!(referent.source, ReferentSource::MainClause | ReferentSource::ProperName) {
713 continue;
714 }
715
716 let is_inaccessible = matches!(referent.source, ReferentSource::NegationScope | ReferentSource::Disjunct)
719 || !self.is_accessible(_box_idx, from_box);
720
721 if is_inaccessible {
722 let gender_match = referent.gender == gender
724 || (gender == Gender::Unknown)
725 || (gender == Gender::Neuter && referent.gender == Gender::Unknown);
726 let number_match = referent.number == number;
727
728 if gender_match && number_match {
729 let blocking_scope = if matches!(referent.source, ReferentSource::NegationScope) {
731 BoxType::NegationScope
732 } else if matches!(referent.source, ReferentSource::Disjunct) {
733 BoxType::Disjunct
734 } else {
735 drs_box.box_type.unwrap_or(BoxType::Main)
736 };
737 let noun_class_str = format!("{:?}", referent.noun_class);
738 return Err(ScopeError::InaccessibleReferent {
739 gender,
740 blocking_scope,
741 reason: format!("'{}' is trapped in {:?} scope and cannot be accessed",
742 noun_class_str, blocking_scope),
743 });
744 }
745 }
746 }
747 }
748
749 Err(ScopeError::NoMatchingReferent {
751 gender,
752 number,
753 })
754 }
755
756 pub fn resolve_definite(&self, from_box: usize, noun_class: Symbol) -> Option<Symbol> {
758 for (box_idx, drs_box) in self.boxes.iter().enumerate() {
759 if self.is_accessible(box_idx, from_box) {
760 for referent in drs_box.universe.iter().rev() {
761 if referent.noun_class == noun_class {
762 return Some(referent.variable);
763 }
764 }
765 }
766 }
767 None
768 }
769
770 pub fn has_referent_by_variable(&self, var: Symbol) -> bool {
772 for drs_box in &self.boxes {
773 for referent in &drs_box.universe {
774 if referent.variable == var {
775 return true;
776 }
777 }
778 }
779 false
780 }
781
782 pub fn resolve_bridging(&self, interner: &crate::Interner, noun_class: Symbol) -> Option<(Symbol, &'static str)> {
785 use crate::ontology::find_bridging_wholes;
786
787 let noun_str = interner.resolve(noun_class);
788 let Some(wholes) = find_bridging_wholes(noun_str) else {
789 return None;
790 };
791
792 for whole in wholes {
794 for drs_box in &self.boxes {
795 for referent in drs_box.universe.iter().rev() {
796 let ref_class_str = interner.resolve(referent.noun_class);
797 if ref_class_str.eq_ignore_ascii_case(whole) {
798 return Some((referent.variable, *whole));
799 }
800 }
801 }
802 }
803 None
804 }
805
806 pub fn get_universal_referents(&self) -> Vec<Symbol> {
808 let mut result = Vec::new();
809 for drs_box in &self.boxes {
810 for referent in &drs_box.universe {
811 if referent.should_be_universal() {
812 result.push(referent.variable);
813 }
814 }
815 }
816 result
817 }
818
819 pub fn get_existential_referents(&self) -> Vec<Symbol> {
821 let mut result = Vec::new();
822 for drs_box in &self.boxes {
823 for referent in &drs_box.universe {
824 if !referent.should_be_universal()
825 && !matches!(referent.source, ReferentSource::ProperName)
826 {
827 result.push(referent.variable);
828 }
829 }
830 }
831 result
832 }
833
834 pub fn get_last_event_referent(&self, interner: &crate::intern::Interner) -> Option<Symbol> {
836 for drs_box in self.boxes.iter().rev() {
838 for referent in drs_box.universe.iter().rev() {
839 let class_str = interner.resolve(referent.noun_class);
840 if class_str == "Event" {
841 return Some(referent.variable);
842 }
843 }
844 }
845 None
846 }
847
848 pub fn in_conditional_antecedent(&self) -> bool {
850 matches!(
851 self.boxes.get(self.current_box).and_then(|b| b.box_type),
852 Some(BoxType::ConditionalAntecedent)
853 )
854 }
855
856 pub fn in_universal_restrictor(&self) -> bool {
858 matches!(
859 self.boxes.get(self.current_box).and_then(|b| b.box_type),
860 Some(BoxType::UniversalRestrictor)
861 )
862 }
863
864 pub fn get_telescope_candidates(&self) -> Vec<TelescopeCandidate> {
868 let mut candidates = Vec::new();
869
870 for (box_idx, drs_box) in self.boxes.iter().enumerate() {
871 if let Some(box_type) = drs_box.box_type {
873 if !box_type.can_telescope() {
874 continue; }
876 }
877
878 let mut is_blocked = false;
880 let mut check_idx = box_idx;
881 while let Some(parent_idx) = self.boxes.get(check_idx).and_then(|b| b.parent) {
882 if let Some(parent_type) = self.boxes.get(parent_idx).and_then(|b| b.box_type) {
883 if parent_type.blocks_accessibility() {
884 is_blocked = true;
885 break;
886 }
887 }
888 check_idx = parent_idx;
889 }
890
891 if is_blocked {
892 continue;
893 }
894
895 let is_modal_box = drs_box.box_type == Some(BoxType::ModalScope);
897 for referent in &drs_box.universe {
898 if matches!(referent.source, ReferentSource::NegationScope | ReferentSource::Disjunct) {
901 continue;
902 }
903
904 candidates.push(TelescopeCandidate {
905 variable: referent.variable,
906 noun_class: referent.noun_class,
907 gender: referent.gender,
908 origin_box: box_idx,
909 scope_path: Vec::new(), in_modal_scope: is_modal_box || referent.source == ReferentSource::ModalScope,
911 });
912 }
913 }
914
915 candidates
916 }
917
918 pub fn find_blocked_referent(&self, from_box: usize, gender: Gender) -> Option<(Symbol, BoxType)> {
921 for (box_idx, drs_box) in self.boxes.iter().enumerate() {
922 if self.is_accessible(box_idx, from_box) {
924 continue;
925 }
926
927 if let Some(box_type) = drs_box.box_type {
929 if box_type.blocks_accessibility() {
930 for referent in &drs_box.universe {
931 let gender_match = gender == Gender::Unknown
932 || referent.gender == Gender::Unknown
933 || referent.gender == gender
934 || gender == Gender::Neuter;
935
936 if gender_match {
937 return Some((referent.variable, box_type));
938 }
939 }
940 }
941 }
942 }
943 None
944 }
945
946 pub fn set_ownership(&mut self, noun_class: Symbol, state: OwnershipState) {
948 for drs_box in &mut self.boxes {
949 for referent in &mut drs_box.universe {
950 if referent.noun_class == noun_class {
951 referent.ownership = state;
952 return;
953 }
954 }
955 }
956 }
957
958 pub fn set_ownership_by_var(&mut self, var: Symbol, state: OwnershipState) {
960 for drs_box in &mut self.boxes {
961 for referent in &mut drs_box.universe {
962 if referent.variable == var {
963 referent.ownership = state;
964 return;
965 }
966 }
967 }
968 }
969
970 pub fn get_ownership(&self, noun_class: Symbol) -> Option<OwnershipState> {
972 for drs_box in &self.boxes {
973 for referent in &drs_box.universe {
974 if referent.noun_class == noun_class {
975 return Some(referent.ownership);
976 }
977 }
978 }
979 None
980 }
981
982 pub fn get_ownership_by_var(&self, var: Symbol) -> Option<OwnershipState> {
984 for drs_box in &self.boxes {
985 for referent in &drs_box.universe {
986 if referent.variable == var {
987 return Some(referent.ownership);
988 }
989 }
990 }
991 None
992 }
993
994 pub fn clear(&mut self) {
995 self.boxes.clear();
996 let main = DrsBox::new(BoxType::Main, None);
997 self.boxes.push(main);
998 self.main_box = 0;
999 self.current_box = 0;
1000 }
1001}
1002
1003impl Default for Drs {
1004 fn default() -> Self {
1005 Self::new()
1006 }
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011 use super::*;
1012 use logicaffeine_base::Interner;
1013
1014 #[test]
1015 fn referent_source_universal_force() {
1016 assert!(ReferentSource::ConditionalAntecedent.gets_universal_force());
1017 assert!(ReferentSource::UniversalRestrictor.gets_universal_force());
1018 assert!(!ReferentSource::MainClause.gets_universal_force());
1019 assert!(!ReferentSource::ProperName.gets_universal_force());
1020 }
1021
1022 #[test]
1023 fn drs_new_has_main_box() {
1024 let drs = Drs::new();
1025 assert_eq!(drs.boxes.len(), 1);
1026 assert_eq!(drs.current_box, 0);
1027 assert_eq!(drs.boxes[0].box_type, Some(BoxType::Main));
1028 }
1029
1030 #[test]
1031 fn drs_enter_exit_box() {
1032 let mut drs = Drs::new();
1033 assert_eq!(drs.current_box, 0);
1034
1035 let ant_idx = drs.enter_box(BoxType::ConditionalAntecedent);
1036 assert_eq!(ant_idx, 1);
1037 assert_eq!(drs.current_box, 1);
1038 assert_eq!(drs.boxes[1].parent, Some(0));
1039
1040 drs.exit_box();
1041 assert_eq!(drs.current_box, 0);
1042 }
1043
1044 #[test]
1045 fn drs_introduce_referent_tracks_source() {
1046 let mut interner = Interner::new();
1047 let mut drs = Drs::new();
1048
1049 let x = interner.intern("x");
1050 let farmer = interner.intern("Farmer");
1051
1052 drs.introduce_referent(x, farmer, Gender::Male, Number::Singular);
1054 assert_eq!(drs.boxes[0].universe[0].source, ReferentSource::MainClause);
1055
1056 drs.enter_box(BoxType::ConditionalAntecedent);
1058 let y = interner.intern("y");
1059 let donkey = interner.intern("Donkey");
1060 drs.introduce_referent(y, donkey, Gender::Neuter, Number::Singular);
1061 assert_eq!(
1062 drs.boxes[1].universe[0].source,
1063 ReferentSource::ConditionalAntecedent
1064 );
1065 }
1066
1067 #[test]
1068 fn drs_conditional_antecedent_accessible_from_consequent() {
1069 let mut interner = Interner::new();
1070 let mut drs = Drs::new();
1071
1072 let ant_idx = drs.enter_box(BoxType::ConditionalAntecedent);
1074 let y = interner.intern("y");
1075 let donkey = interner.intern("Donkey");
1076 drs.introduce_referent(y, donkey, Gender::Neuter, Number::Singular);
1077 drs.exit_box();
1078
1079 let cons_idx = drs.enter_box(BoxType::ConditionalConsequent);
1081
1082 assert!(drs.is_accessible(ant_idx, cons_idx));
1084 }
1085
1086 #[test]
1087 fn drs_negation_blocks_accessibility() {
1088 let mut drs = Drs::new();
1089
1090 let neg_idx = drs.enter_box(BoxType::NegationScope);
1092 drs.exit_box();
1093
1094 assert!(!drs.is_accessible(neg_idx, 0));
1096 }
1097
1098 #[test]
1099 fn drs_get_universal_referents() {
1100 let mut interner = Interner::new();
1101 let mut drs = Drs::new();
1102
1103 let x = interner.intern("x");
1104 let farmer = interner.intern("Farmer");
1105 drs.introduce_referent(x, farmer, Gender::Male, Number::Singular);
1106
1107 drs.enter_box(BoxType::ConditionalAntecedent);
1108 let y = interner.intern("y");
1109 let donkey = interner.intern("Donkey");
1110 drs.introduce_referent(y, donkey, Gender::Neuter, Number::Singular);
1111
1112 let universals = drs.get_universal_referents();
1113 assert_eq!(universals.len(), 1);
1114 assert_eq!(universals[0], y);
1115 }
1116
1117 #[test]
1118 fn drs_pronoun_resolution_marks_used() {
1119 let mut interner = Interner::new();
1120 let mut drs = Drs::new();
1121
1122 drs.enter_box(BoxType::UniversalRestrictor);
1123 let y = interner.intern("y");
1124 let donkey = interner.intern("Donkey");
1125 drs.introduce_referent(y, donkey, Gender::Neuter, Number::Singular);
1126
1127 let resolved = drs.resolve_pronoun(drs.current_box, Gender::Neuter, Number::Singular);
1129 assert_eq!(resolved, Ok(y));
1130
1131 assert!(drs.boxes[1].universe[0].used_by_pronoun);
1133 }
1134}