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
288 for candidate in &self.telescope_candidates {
293 if candidate.in_modal_scope && !can_access_modal {
295 #[cfg(debug_assertions)]
297 continue;
298 }
299
300 let gender_match = candidate.gender == gender
301 || candidate.gender == Gender::Unknown || gender == Gender::Unknown;
303
304 if gender_match {
305 return Some(candidate.clone());
306 }
307 }
308
309 None
310 }
311
312 pub fn set_ownership(&mut self, noun_class: Symbol, state: OwnershipState) {
314 self.drs.set_ownership(noun_class, state);
315 }
316
317 pub fn get_ownership(&self, noun_class: Symbol) -> Option<OwnershipState> {
319 self.drs.get_ownership(noun_class)
320 }
321
322 pub fn set_ownership_by_var(&mut self, var: Symbol, state: OwnershipState) {
324 self.drs.set_ownership_by_var(var, state);
325 }
326
327 pub fn get_ownership_by_var(&self, var: Symbol) -> Option<OwnershipState> {
329 self.drs.get_ownership_by_var(var)
330 }
331
332 pub fn enter_modal_context(&mut self, is_epistemic: bool, force: f32) {
338 self.current_modal_context = Some(ModalContext {
339 active: true,
340 is_epistemic,
341 force,
342 });
343 self.drs.enter_box(BoxType::ModalScope);
345 }
346
347 pub fn exit_modal_context(&mut self) {
349 self.current_modal_context = None;
350 self.drs.exit_box();
351 }
352
353 pub fn in_modal_context(&self) -> bool {
355 self.current_modal_context.is_some()
356 }
357
358 pub fn has_prior_modal_context(&self) -> bool {
360 self.prior_modal_context.is_some()
361 }
362
363 pub fn can_subordinate(&self) -> bool {
366 self.prior_modal_context.is_some()
367 }
368
369 pub fn clear(&mut self) {
371 self.drs.clear();
372 self.event_counter = 0;
373 self.event_history.clear();
374 self.reference_time_counter = 0;
375 self.current_reference_time = None;
376 self.time_constraints.clear();
377 self.telescope_candidates.clear();
378 self.discourse_mode = false;
379 self.current_modal_context = None;
380 self.prior_modal_context = None;
381 }
382}
383
384impl Default for WorldState {
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
395pub enum ReferentSource {
396 MainClause,
398 ProperName,
400 ConditionalAntecedent,
402 UniversalRestrictor,
404 NegationScope,
406 Disjunct,
408 ModalScope,
410}
411
412impl ReferentSource {
413 pub fn gets_universal_force(&self) -> bool {
414 matches!(
415 self,
416 ReferentSource::ConditionalAntecedent | ReferentSource::UniversalRestrictor
417 )
418 }
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq)]
422pub enum BoxType {
423 Main,
425 ConditionalAntecedent,
427 ConditionalConsequent,
429 NegationScope,
431 UniversalRestrictor,
433 UniversalScope,
435 Disjunct,
437 ModalScope,
440}
441
442impl BoxType {
443 pub fn to_referent_source(&self) -> ReferentSource {
444 match self {
445 BoxType::Main => ReferentSource::MainClause,
446 BoxType::ConditionalAntecedent => ReferentSource::ConditionalAntecedent,
447 BoxType::ConditionalConsequent => ReferentSource::MainClause,
448 BoxType::NegationScope => ReferentSource::NegationScope,
449 BoxType::UniversalRestrictor => ReferentSource::UniversalRestrictor,
450 BoxType::UniversalScope => ReferentSource::MainClause,
451 BoxType::Disjunct => ReferentSource::Disjunct,
452 BoxType::ModalScope => ReferentSource::ModalScope,
453 }
454 }
455
456 pub fn can_telescope(&self) -> bool {
460 matches!(
461 self,
462 BoxType::Main
463 | BoxType::UniversalScope
464 | BoxType::UniversalRestrictor
465 | BoxType::ConditionalConsequent
466 | BoxType::ConditionalAntecedent
467 | BoxType::ModalScope )
469 }
471
472 pub fn blocks_accessibility(&self) -> bool {
474 matches!(self, BoxType::NegationScope | BoxType::Disjunct)
475 }
476}
477
478fn modifier_sets_match(a: &[Symbol], b: &[Symbol]) -> bool {
482 if a.len() != b.len() || a.is_empty() {
483 return false;
484 }
485 a.iter().all(|m| b.contains(m)) && b.iter().all(|m| a.contains(m))
486}
487
488#[derive(Debug, Clone)]
489pub struct Referent {
490 pub variable: Symbol,
491 pub noun_class: Symbol,
492 pub gender: Gender,
493 pub number: Number,
494 pub source: ReferentSource,
495 pub used_by_pronoun: bool,
496 pub ownership: OwnershipState,
497 pub modifiers: Vec<Symbol>,
504}
505
506impl Referent {
507 pub fn new(variable: Symbol, noun_class: Symbol, gender: Gender, number: Number, source: ReferentSource) -> Self {
508 Self {
509 variable,
510 noun_class,
511 gender,
512 number,
513 source,
514 used_by_pronoun: false,
515 ownership: OwnershipState::Owned,
516 modifiers: Vec::new(),
517 }
518 }
519
520 pub fn with_modifiers(
522 variable: Symbol,
523 noun_class: Symbol,
524 gender: Gender,
525 number: Number,
526 source: ReferentSource,
527 modifiers: Vec<Symbol>,
528 ) -> Self {
529 let mut referent = Self::new(variable, noun_class, gender, number, source);
530 referent.modifiers = modifiers;
531 referent
532 }
533
534 pub fn should_be_universal(&self) -> bool {
535 self.source.gets_universal_force() || self.used_by_pronoun
536 }
537}
538
539#[derive(Debug, Clone, Default)]
540pub struct DrsBox {
541 pub universe: Vec<Referent>,
542 pub box_type: Option<BoxType>,
543 pub parent: Option<usize>,
544}
545
546impl DrsBox {
547 pub fn new(box_type: BoxType, parent: Option<usize>) -> Self {
548 Self {
549 universe: Vec::new(),
550 box_type: Some(box_type),
551 parent,
552 }
553 }
554}
555
556#[derive(Debug, Clone)]
557pub struct Drs {
558 boxes: Vec<DrsBox>,
559 main_box: usize,
560 current_box: usize,
561 item_categories: std::collections::HashMap<Symbol, Symbol>,
569}
570
571impl Drs {
572 pub fn new() -> Self {
573 let main = DrsBox::new(BoxType::Main, None);
574 Self {
575 boxes: vec![main],
576 main_box: 0,
577 current_box: 0,
578 item_categories: std::collections::HashMap::new(),
579 }
580 }
581
582 pub fn enter_box(&mut self, box_type: BoxType) -> usize {
583 let parent = self.current_box;
584 let new_box = DrsBox::new(box_type, Some(parent));
585 let idx = self.boxes.len();
586 self.boxes.push(new_box);
587 self.current_box = idx;
588 idx
589 }
590
591 pub fn exit_box(&mut self) {
592 if let Some(parent) = self.boxes[self.current_box].parent {
593 self.current_box = parent;
594 }
595 }
596
597 pub fn current_box_index(&self) -> usize {
598 self.current_box
599 }
600
601 pub fn current_box_type(&self) -> Option<BoxType> {
602 self.boxes.get(self.current_box).and_then(|b| b.box_type)
603 }
604
605 pub fn introduce_referent(&mut self, variable: Symbol, noun_class: Symbol, gender: Gender, number: Number) {
606 let source = self.boxes[self.current_box]
607 .box_type
608 .map(|bt| bt.to_referent_source())
609 .unwrap_or(ReferentSource::MainClause);
610
611 let referent = Referent::new(variable, noun_class, gender, number, source);
612 self.boxes[self.current_box].universe.push(referent);
613 }
614
615 pub fn introduce_referent_with_source(&mut self, variable: Symbol, noun_class: Symbol, gender: Gender, number: Number, source: ReferentSource) {
617 let referent = Referent::new(variable, noun_class, gender, number, source);
618 self.boxes[self.current_box].universe.push(referent);
619 }
620
621 pub fn introduce_referent_with_modifiers(
626 &mut self,
627 variable: Symbol,
628 noun_class: Symbol,
629 gender: Gender,
630 number: Number,
631 source: ReferentSource,
632 modifiers: Vec<Symbol>,
633 ) {
634 let referent =
635 Referent::with_modifiers(variable, noun_class, gender, number, source, modifiers);
636 self.boxes[self.current_box].universe.push(referent);
637 }
638
639 pub fn introduce_referent_global(&mut self, variable: Symbol, noun_class: Symbol, gender: Gender, number: Number, source: ReferentSource) {
643 let referent = Referent::new(variable, noun_class, gender, number, source);
644 self.boxes[self.main_box].universe.push(referent);
645 }
646
647 pub fn introduce_proper_name(&mut self, variable: Symbol, name: Symbol, gender: Gender) {
648 let referent = Referent::new(variable, name, gender, Number::Singular, ReferentSource::ProperName);
650 self.boxes[self.current_box].universe.push(referent);
651 }
652
653 pub fn is_rigid_referent(&self, symbol: Symbol) -> bool {
657 self.boxes.iter().any(|b| {
658 b.universe
659 .iter()
660 .any(|r| r.variable == symbol && r.source == ReferentSource::ProperName)
661 })
662 }
663
664 pub fn register_item_category(&mut self, item: Symbol, category: Symbol) {
668 self.item_categories.insert(item, category);
669 }
670
671 pub fn item_category(&self, item: Symbol) -> Option<Symbol> {
673 self.item_categories.get(&item).copied()
674 }
675
676 pub fn is_accessible(&self, target_box: usize, from_box: usize) -> bool {
678 if target_box == from_box {
679 return true;
680 }
681
682 let target = &self.boxes[target_box];
683 let from = &self.boxes[from_box];
684
685 if let Some(bt) = target.box_type {
691 match bt {
692 BoxType::NegationScope | BoxType::Disjunct | BoxType::ModalScope => {
693 return false;
696 }
697 _ => {}
698 }
699 }
700
701 if let (Some(BoxType::ConditionalConsequent), Some(BoxType::ConditionalAntecedent)) =
704 (from.box_type, target.box_type)
705 {
706 if from.parent == target.parent {
708 return true;
709 }
710 }
711
712 if let (Some(BoxType::UniversalScope), Some(BoxType::UniversalRestrictor)) =
714 (from.box_type, target.box_type)
715 {
716 if from.parent == target.parent {
717 return true;
718 }
719 }
720
721 let mut current = from_box;
723 while let Some(parent) = self.boxes[current].parent {
724 if parent == target_box {
725 return true;
726 }
727 current = parent;
728 }
729
730 false
731 }
732
733 pub fn resolve_pronoun(&mut self, from_box: usize, gender: Gender, number: Number) -> Result<Symbol, ScopeError> {
735 let mut candidates = Vec::new();
741
742 for (box_idx, drs_box) in self.boxes.iter().enumerate() {
743 let box_accessible = self.is_accessible(box_idx, from_box);
744
745 for referent in &drs_box.universe {
746 if matches!(referent.source, ReferentSource::NegationScope | ReferentSource::Disjunct) {
749 continue;
750 }
751
752 let has_global_source = matches!(referent.source, ReferentSource::MainClause | ReferentSource::ProperName);
755 if !box_accessible && !has_global_source {
756 continue;
757 }
758
759 let gender_match = referent.gender == gender
765 || referent.gender == Gender::Unknown
766 || gender == Gender::Unknown;
767
768 let number_match = referent.number == number;
770
771 if gender_match && number_match {
772 candidates.push((box_idx, referent.variable));
773 }
774 }
775 }
776
777 if let Some((box_idx, var)) = candidates.last() {
779 let box_idx = *box_idx;
780 let var = *var;
781 for referent in &mut self.boxes[box_idx].universe {
782 if referent.variable == var {
783 referent.used_by_pronoun = true;
784 return Ok(var);
785 }
786 }
787 }
788
789 for (_box_idx, drs_box) in self.boxes.iter().enumerate() {
792 for referent in &drs_box.universe {
793 if matches!(referent.source, ReferentSource::MainClause | ReferentSource::ProperName) {
796 continue;
797 }
798
799 let is_inaccessible = matches!(referent.source, ReferentSource::NegationScope | ReferentSource::Disjunct)
802 || !self.is_accessible(_box_idx, from_box);
803
804 if is_inaccessible {
805 let gender_match = referent.gender == gender
807 || (gender == Gender::Unknown)
808 || (gender == Gender::Neuter && referent.gender == Gender::Unknown);
809 let number_match = referent.number == number;
810
811 if gender_match && number_match {
812 let blocking_scope = if matches!(referent.source, ReferentSource::NegationScope) {
814 BoxType::NegationScope
815 } else if matches!(referent.source, ReferentSource::Disjunct) {
816 BoxType::Disjunct
817 } else {
818 drs_box.box_type.unwrap_or(BoxType::Main)
819 };
820 let noun_class_str = format!("{:?}", referent.noun_class);
821 return Err(ScopeError::InaccessibleReferent {
822 gender,
823 blocking_scope,
824 reason: format!("'{}' is trapped in {:?} scope and cannot be accessed",
825 noun_class_str, blocking_scope),
826 });
827 }
828 }
829 }
830 }
831
832 Err(ScopeError::NoMatchingReferent {
834 gender,
835 number,
836 })
837 }
838
839 pub fn resolve_definite(&self, from_box: usize, noun_class: Symbol) -> Option<Symbol> {
841 for (box_idx, drs_box) in self.boxes.iter().enumerate() {
842 if self.is_accessible(box_idx, from_box) {
843 for referent in drs_box.universe.iter().rev() {
844 if referent.noun_class == noun_class {
845 return Some(referent.variable);
846 }
847 }
848 }
849 }
850 None
851 }
852
853 pub fn resolve_definite_by_modifier(
877 &self,
878 interner: &crate::Interner,
879 from_box: usize,
880 noun_class: Symbol,
881 modifiers: &[Symbol],
882 ) -> Option<Symbol> {
883 use crate::lexicon::lookup_sort;
884
885 if modifiers.is_empty() {
888 return None;
889 }
890
891 let new_sort = lookup_sort(interner.resolve(noun_class))?;
894 if !new_sort.is_occasion() {
895 return None;
896 }
897
898 for (box_idx, drs_box) in self.boxes.iter().enumerate() {
899 if !self.is_accessible(box_idx, from_box) {
900 continue;
901 }
902 for referent in drs_box.universe.iter().rev() {
903 if !modifier_sets_match(&referent.modifiers, modifiers) {
906 continue;
907 }
908
909 let Some(prior_sort) = lookup_sort(interner.resolve(referent.noun_class)) else {
912 continue;
913 };
914 if !prior_sort.is_occasion() {
915 continue;
916 }
917 if !new_sort.is_compatible_with(prior_sort)
918 && !prior_sort.is_compatible_with(new_sort)
919 {
920 continue;
921 }
922
923 return Some(referent.variable);
924 }
925 }
926 None
927 }
928
929 pub fn has_referent_by_variable(&self, var: Symbol) -> bool {
931 for drs_box in &self.boxes {
932 for referent in &drs_box.universe {
933 if referent.variable == var {
934 return true;
935 }
936 }
937 }
938 false
939 }
940
941 pub fn resolve_bridging(&self, interner: &crate::Interner, noun_class: Symbol) -> Option<(Symbol, &'static str)> {
944 use crate::ontology::find_bridging_wholes;
945
946 let noun_str = interner.resolve(noun_class);
947 let Some(wholes) = find_bridging_wholes(noun_str) else {
948 return None;
949 };
950
951 for whole in wholes {
953 for drs_box in &self.boxes {
954 for referent in drs_box.universe.iter().rev() {
955 let ref_class_str = interner.resolve(referent.noun_class);
956 if ref_class_str.eq_ignore_ascii_case(whole) {
957 return Some((referent.variable, *whole));
958 }
959 }
960 }
961 }
962 None
963 }
964
965 pub fn get_universal_referents(&self) -> Vec<Symbol> {
967 let mut result = Vec::new();
968 for drs_box in &self.boxes {
969 for referent in &drs_box.universe {
970 if referent.should_be_universal()
973 && !matches!(referent.source, ReferentSource::ProperName)
974 {
975 result.push(referent.variable);
976 }
977 }
978 }
979 result
980 }
981
982 pub fn get_existential_referents(&self) -> Vec<Symbol> {
984 let mut result = Vec::new();
985 for drs_box in &self.boxes {
986 for referent in &drs_box.universe {
987 if !referent.should_be_universal()
988 && !matches!(referent.source, ReferentSource::ProperName)
989 {
990 result.push(referent.variable);
991 }
992 }
993 }
994 result
995 }
996
997 pub fn get_last_event_referent(&self, interner: &crate::intern::Interner) -> Option<Symbol> {
999 for drs_box in self.boxes.iter().rev() {
1001 for referent in drs_box.universe.iter().rev() {
1002 let class_str = interner.resolve(referent.noun_class);
1003 if class_str == "Event" {
1004 return Some(referent.variable);
1005 }
1006 }
1007 }
1008 None
1009 }
1010
1011 pub fn in_conditional_antecedent(&self) -> bool {
1013 matches!(
1014 self.boxes.get(self.current_box).and_then(|b| b.box_type),
1015 Some(BoxType::ConditionalAntecedent)
1016 )
1017 }
1018
1019 pub fn in_universal_restrictor(&self) -> bool {
1021 matches!(
1022 self.boxes.get(self.current_box).and_then(|b| b.box_type),
1023 Some(BoxType::UniversalRestrictor)
1024 )
1025 }
1026
1027 pub fn get_telescope_candidates(&self) -> Vec<TelescopeCandidate> {
1031 let mut candidates = Vec::new();
1032
1033 for (box_idx, drs_box) in self.boxes.iter().enumerate() {
1034 if let Some(box_type) = drs_box.box_type {
1036 if !box_type.can_telescope() {
1037 continue; }
1039 }
1040
1041 let mut is_blocked = false;
1043 let mut check_idx = box_idx;
1044 while let Some(parent_idx) = self.boxes.get(check_idx).and_then(|b| b.parent) {
1045 if let Some(parent_type) = self.boxes.get(parent_idx).and_then(|b| b.box_type) {
1046 if parent_type.blocks_accessibility() {
1047 is_blocked = true;
1048 break;
1049 }
1050 }
1051 check_idx = parent_idx;
1052 }
1053
1054 if is_blocked {
1055 continue;
1056 }
1057
1058 let is_modal_box = drs_box.box_type == Some(BoxType::ModalScope);
1060 for referent in &drs_box.universe {
1061 if matches!(referent.source, ReferentSource::NegationScope | ReferentSource::Disjunct) {
1064 continue;
1065 }
1066
1067 candidates.push(TelescopeCandidate {
1068 variable: referent.variable,
1069 noun_class: referent.noun_class,
1070 gender: referent.gender,
1071 origin_box: box_idx,
1072 scope_path: Vec::new(), in_modal_scope: is_modal_box || referent.source == ReferentSource::ModalScope,
1074 });
1075 }
1076 }
1077
1078 candidates
1079 }
1080
1081 pub fn find_blocked_referent(&self, from_box: usize, gender: Gender) -> Option<(Symbol, BoxType)> {
1084 for (box_idx, drs_box) in self.boxes.iter().enumerate() {
1085 if self.is_accessible(box_idx, from_box) {
1087 continue;
1088 }
1089
1090 if let Some(box_type) = drs_box.box_type {
1092 if box_type.blocks_accessibility() {
1093 for referent in &drs_box.universe {
1094 let gender_match = gender == Gender::Unknown
1095 || referent.gender == Gender::Unknown
1096 || referent.gender == gender
1097 || gender == Gender::Neuter;
1098
1099 if gender_match {
1100 return Some((referent.variable, box_type));
1101 }
1102 }
1103 }
1104 }
1105 }
1106 None
1107 }
1108
1109 pub fn set_ownership(&mut self, noun_class: Symbol, state: OwnershipState) {
1111 for drs_box in &mut self.boxes {
1112 for referent in &mut drs_box.universe {
1113 if referent.noun_class == noun_class {
1114 referent.ownership = state;
1115 return;
1116 }
1117 }
1118 }
1119 }
1120
1121 pub fn set_ownership_by_var(&mut self, var: Symbol, state: OwnershipState) {
1123 for drs_box in &mut self.boxes {
1124 for referent in &mut drs_box.universe {
1125 if referent.variable == var {
1126 referent.ownership = state;
1127 return;
1128 }
1129 }
1130 }
1131 }
1132
1133 pub fn get_ownership(&self, noun_class: Symbol) -> Option<OwnershipState> {
1135 for drs_box in &self.boxes {
1136 for referent in &drs_box.universe {
1137 if referent.noun_class == noun_class {
1138 return Some(referent.ownership);
1139 }
1140 }
1141 }
1142 None
1143 }
1144
1145 pub fn get_ownership_by_var(&self, var: Symbol) -> Option<OwnershipState> {
1147 for drs_box in &self.boxes {
1148 for referent in &drs_box.universe {
1149 if referent.variable == var {
1150 return Some(referent.ownership);
1151 }
1152 }
1153 }
1154 None
1155 }
1156
1157 pub fn clear(&mut self) {
1158 self.boxes.clear();
1159 let main = DrsBox::new(BoxType::Main, None);
1160 self.boxes.push(main);
1161 self.main_box = 0;
1162 self.current_box = 0;
1163 self.item_categories.clear();
1164 }
1165}
1166
1167impl Default for Drs {
1168 fn default() -> Self {
1169 Self::new()
1170 }
1171}
1172
1173#[cfg(test)]
1174mod tests {
1175 use super::*;
1176 use logicaffeine_base::Interner;
1177
1178 #[test]
1179 fn referent_source_universal_force() {
1180 assert!(ReferentSource::ConditionalAntecedent.gets_universal_force());
1181 assert!(ReferentSource::UniversalRestrictor.gets_universal_force());
1182 assert!(!ReferentSource::MainClause.gets_universal_force());
1183 assert!(!ReferentSource::ProperName.gets_universal_force());
1184 }
1185
1186 #[test]
1187 fn drs_new_has_main_box() {
1188 let drs = Drs::new();
1189 assert_eq!(drs.boxes.len(), 1);
1190 assert_eq!(drs.current_box, 0);
1191 assert_eq!(drs.boxes[0].box_type, Some(BoxType::Main));
1192 }
1193
1194 #[test]
1195 fn drs_enter_exit_box() {
1196 let mut drs = Drs::new();
1197 assert_eq!(drs.current_box, 0);
1198
1199 let ant_idx = drs.enter_box(BoxType::ConditionalAntecedent);
1200 assert_eq!(ant_idx, 1);
1201 assert_eq!(drs.current_box, 1);
1202 assert_eq!(drs.boxes[1].parent, Some(0));
1203
1204 drs.exit_box();
1205 assert_eq!(drs.current_box, 0);
1206 }
1207
1208 #[test]
1209 fn drs_introduce_referent_tracks_source() {
1210 let mut interner = Interner::new();
1211 let mut drs = Drs::new();
1212
1213 let x = interner.intern("x");
1214 let farmer = interner.intern("Farmer");
1215
1216 drs.introduce_referent(x, farmer, Gender::Male, Number::Singular);
1218 assert_eq!(drs.boxes[0].universe[0].source, ReferentSource::MainClause);
1219
1220 drs.enter_box(BoxType::ConditionalAntecedent);
1222 let y = interner.intern("y");
1223 let donkey = interner.intern("Donkey");
1224 drs.introduce_referent(y, donkey, Gender::Neuter, Number::Singular);
1225 assert_eq!(
1226 drs.boxes[1].universe[0].source,
1227 ReferentSource::ConditionalAntecedent
1228 );
1229 }
1230
1231 #[test]
1232 fn drs_conditional_antecedent_accessible_from_consequent() {
1233 let mut interner = Interner::new();
1234 let mut drs = Drs::new();
1235
1236 let ant_idx = drs.enter_box(BoxType::ConditionalAntecedent);
1238 let y = interner.intern("y");
1239 let donkey = interner.intern("Donkey");
1240 drs.introduce_referent(y, donkey, Gender::Neuter, Number::Singular);
1241 drs.exit_box();
1242
1243 let cons_idx = drs.enter_box(BoxType::ConditionalConsequent);
1245
1246 assert!(drs.is_accessible(ant_idx, cons_idx));
1248 }
1249
1250 #[test]
1251 fn drs_negation_blocks_accessibility() {
1252 let mut drs = Drs::new();
1253
1254 let neg_idx = drs.enter_box(BoxType::NegationScope);
1256 drs.exit_box();
1257
1258 assert!(!drs.is_accessible(neg_idx, 0));
1260 }
1261
1262 #[test]
1263 fn drs_get_universal_referents() {
1264 let mut interner = Interner::new();
1265 let mut drs = Drs::new();
1266
1267 let x = interner.intern("x");
1268 let farmer = interner.intern("Farmer");
1269 drs.introduce_referent(x, farmer, Gender::Male, Number::Singular);
1270
1271 drs.enter_box(BoxType::ConditionalAntecedent);
1272 let y = interner.intern("y");
1273 let donkey = interner.intern("Donkey");
1274 drs.introduce_referent(y, donkey, Gender::Neuter, Number::Singular);
1275
1276 let universals = drs.get_universal_referents();
1277 assert_eq!(universals.len(), 1);
1278 assert_eq!(universals[0], y);
1279 }
1280
1281 #[test]
1282 fn drs_pronoun_resolution_marks_used() {
1283 let mut interner = Interner::new();
1284 let mut drs = Drs::new();
1285
1286 drs.enter_box(BoxType::UniversalRestrictor);
1287 let y = interner.intern("y");
1288 let donkey = interner.intern("Donkey");
1289 drs.introduce_referent(y, donkey, Gender::Neuter, Number::Singular);
1290
1291 let resolved = drs.resolve_pronoun(drs.current_box, Gender::Neuter, Number::Singular);
1293 assert_eq!(resolved, Ok(y));
1294
1295 assert!(drs.boxes[1].universe[0].used_by_pronoun);
1297 }
1298}