Skip to main content

camxes_rs/
parse_m.rs

1//! Parse state monad for semantic analysis
2//!
3//! Ported from: ParseM.hs
4//!
5//! This module provides the monadic context for parsing and semantic analysis.
6//! It manages:
7//! - Fresh variable/constant generation
8//! - Variable binding context
9//! - Sumbasti/bribasti bindings (anaphora resolution)
10//! - Question handling
11//! - Lambda variable tracking
12//! - Argument list management
13
14use crate::jbo_prop::{JboRel, JboTerm, JboQuantifier, JboTag, JboModalOp, JboVPred, JboNPred, JboPred, SideType};
15use std::sync::Arc;
16use crate::jbo_syntax::{SumtiAtom, SumtiQualifier, TanruUnit, Lerfu, Selbri};
17use crate::logic::Prop;
18use std::collections::{HashMap, HashSet};
19use std::hash::Hash;
20
21pub type JboProp = Prop<JboRel, JboTerm, String, crate::jbo_prop::JboModalOp, JboQuantifier>;
22
23// Ported from: ParseM.hs :: type Bridi = Args -> JboProp
24/// A bridi is a function from arguments to a proposition
25pub type Bridi = Box<dyn Fn(&Args) -> JboProp + Send + Sync>;
26
27// Ported from: ParseM.hs :: ParseState
28/// ParseState holds all the state which doesn't respect the tree structure
29/// of the logical parse; it is threaded through as we parse the text left-to-right.
30#[derive(Clone)]
31pub struct ParseState {
32    pub sumbasti_bindings: SumbastiBindings,
33    pub bribasti_bindings: BribastiBindings,
34    pub backcount_stack: Vec<i32>,
35    pub next_fresh_var: i32,
36    pub next_fresh_rvar: i32,
37    pub variable_domains: HashMap<i32, VariableDomain>,
38    pub next_fresh_constant: i32,
39    pub referenced_vars: HashSet<i32>,
40    pub questions: Vec<Question>,
41    pub lambdas: HashMap<LambdaPos, i32>,
42    pub side_texticules: Vec<Texticule>,
43    pub pending_prop_transforms: Vec<PropTransform>,
44    pub non_veridical_props: Vec<JboProp>,
45    pub var_bindings: VarBindings,
46    pub rvar_bindings: RVarBindings,
47    pub indicator_texticules: bool,
48}
49
50impl std::fmt::Debug for ParseState {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        f.debug_struct("ParseState")
53            .field("sumbasti_bindings", &self.sumbasti_bindings)
54            .field("bribasti_bindings_len", &self.bribasti_bindings.len())
55            .field("backcount_stack", &self.backcount_stack)
56            .field("next_fresh_var", &self.next_fresh_var)
57            .field("next_fresh_rvar", &self.next_fresh_rvar)
58            .field("variable_domains", &self.variable_domains)
59            .field("next_fresh_constant", &self.next_fresh_constant)
60            .field("referenced_vars", &self.referenced_vars)
61            .field("questions", &self.questions)
62            .field("lambdas", &self.lambdas)
63            .field("side_texticules", &self.side_texticules)
64            .field("pending_prop_transforms", &self.pending_prop_transforms)
65            .field("non_veridical_props", &self.non_veridical_props)
66            .field("var_bindings", &self.var_bindings)
67            .field("rvar_bindings", &self.rvar_bindings)
68            .field("indicator_texticules", &self.indicator_texticules)
69            .finish()
70    }
71}
72
73/// Represents a transformation to apply to a proposition (like quantification, negation, modals)
74#[derive(Clone)]
75#[allow(clippy::large_enum_variant)]
76pub enum PropTransform {
77    Quantify(JboTerm, JboQuantifier, Option<Arc<dyn Fn(i32) -> JboProp + Send + Sync>>),
78    ConnectQuantify {
79        conn: crate::jbo_syntax::LogJboConnective,
80        fresh: JboTerm,
81        left_quant: JboQuantifier,
82        right_quant: JboQuantifier,
83        restriction: Option<Arc<dyn Fn(i32) -> JboProp + Send + Sync>>,
84    },
85    ConnectQuantifyTagged {
86        conn: crate::jbo_syntax::LogJboConnective,
87        fresh: JboTerm,
88        left_quant: JboQuantifier,
89        right_quant: JboQuantifier,
90        restriction: Option<Arc<dyn Fn(i32) -> JboProp + Send + Sync>>,
91        event_fresh: Option<JboTerm>,
92        left_modal: Option<JboModalOp>,
93        right_modal: Option<JboModalOp>,
94    },
95    RQuantify(JboRel, JboQuantifier),
96    SubstituteTerm(JboTerm, JboTerm),
97    LogicalConnectBranches {
98        conn: crate::jbo_syntax::LogJboConnective,
99        fresh: JboTerm,
100        left_term: JboTerm,
101        left_transforms: Vec<PropTransform>,
102        right_term: JboTerm,
103        right_transforms: Vec<PropTransform>,
104    },
105    ConnectBranches {
106        conn: crate::jbo_syntax::Connective,
107        left_args: Args,
108        right_args: Args,
109        continuation_position: i32,
110        left_transforms: Vec<PropTransform>,
111        right_transforms: Vec<PropTransform>,
112        outer_transforms: Vec<PropTransform>,
113    },
114    ConnectTagBranches {
115        conn: crate::jbo_prop::JboConnective,
116        left_transforms: Vec<PropTransform>,
117        right_transforms: Vec<PropTransform>,
118    },
119    Negate,
120    ApplyModal(JboModalOp),
121}
122
123impl Default for ParseState {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl ParseState {
130    pub fn new() -> Self {
131        ParseState {
132            sumbasti_bindings: HashMap::new(),
133            bribasti_bindings: HashMap::new(),
134            backcount_stack: Vec::new(),
135            next_fresh_var: 0,
136            next_fresh_rvar: 0,
137            variable_domains: HashMap::new(),
138            next_fresh_constant: 0,
139            referenced_vars: HashSet::new(),
140            questions: Vec::new(),
141            lambdas: HashMap::new(),
142            side_texticules: Vec::new(),
143            pending_prop_transforms: Vec::new(),
144            non_veridical_props: Vec::new(),
145            var_bindings: HashMap::new(),
146            rvar_bindings: HashMap::new(),
147            indicator_texticules: false,
148        }
149    }
150
151    /// Add a pending transformation to be applied to the next proposition
152    pub fn map_prop(&mut self, transform: PropTransform) {
153        self.pending_prop_transforms.push(transform);
154    }
155
156    /// Apply all pending transformations to a proposition and clear the list
157    pub fn apply_transforms(&mut self, mut prop: JboProp) -> JboProp {
158        for transform in self.pending_prop_transforms.drain(..).rev() {
159            prop = apply_transform(transform, prop);
160        }
161        prop
162    }
163
164    // Ported from: ParseM.hs :: getFreshVar
165    pub fn get_fresh_var(&mut self, dom: VariableDomain) -> JboTerm {
166        let n = self.next_fresh_var;
167        self.next_fresh_var += 1;
168        self.variable_domains.insert(n, dom);
169        JboTerm::Var(n)
170    }
171
172    // Ported from: ParseM.hs :: getFreshConstant
173    pub fn get_fresh_constant(&mut self) -> JboTerm {
174        let n = self.next_fresh_constant;
175        self.next_fresh_constant += 1;
176        JboTerm::Constant(n, Vec::new())
177    }
178
179    // Ported from: ParseM.hs :: getFreshRVar
180    pub fn get_fresh_rvar(&mut self) -> JboRel {
181        let n = self.next_fresh_rvar;
182        self.next_fresh_rvar += 1;
183        JboRel::RVar(n)
184    }
185
186    // Ported from: ParseM.hs :: getSumbasti
187    /// Get sumbasti binding for a sumti atom
188    pub fn get_sumbasti(&self, atom: &SumtiAtom) -> JboTerm {
189        get_sumbasti_binding(&self.sumbasti_bindings, atom)
190    }
191
192    // Ported from: ParseM.hs :: setSumbasti
193    /// Set sumbasti binding
194    pub fn set_sumbasti(&mut self, sumbasti: Sumbasti, term: JboTerm) {
195        self.sumbasti_bindings.insert(sumbasti, term);
196    }
197
198    pub fn update_ri(&mut self, o: &JboTerm) {
199        let mut new_bindings = HashMap::new();
200        for (key, val) in self.sumbasti_bindings.iter() {
201            if let SumtiAtom::Ri(n) = &key.atom {
202                let new_key = Sumbasti {
203                    explicitly_assigned: key.explicitly_assigned,
204                    atom: SumtiAtom::Ri(n + 1),
205                };
206                new_bindings.insert(new_key, val.clone());
207            } else {
208                new_bindings.insert(key.clone(), val.clone());
209            }
210        }
211        self.sumbasti_bindings = new_bindings;
212        let count = self.backcount_stack.first().copied().unwrap_or(0);
213        let sumbasti = Sumbasti {
214            explicitly_assigned: false,
215            atom: SumtiAtom::Ri(count),
216        };
217        self.set_sumbasti(sumbasti, o.clone());
218    }
219
220    // Ported from: ParseM.hs :: updateSumbastiWithSumtiAtom
221    /// Update sumbasti bindings based on sumti atom
222    pub fn update_sumbasti_with_sumti_atom(&mut self, sa: &SumtiAtom, o: &JboTerm) {
223        if crate::jbo_parse::gets_ri(sa) {
224            let count = self.pop_backcount();
225            self.sumbasti_bindings = set_shunting(
226                &|n| Sumbasti {
227                    explicitly_assigned: false,
228                    atom: SumtiAtom::Ri(count + n as i32),
229                },
230                o.clone(),
231                &self.sumbasti_bindings,
232            );
233        }
234
235        // Handle name first-letter binding
236        if let SumtiAtom::Name(_, _, name) = sa {
237            if let Some(first_char) = name.chars().next() {
238                let lerfu = SumtiAtom::LerfuString(vec![Lerfu::LerfuChar(first_char)]);
239                let sumbasti = Sumbasti {
240                    explicitly_assigned: false,
241                    atom: lerfu,
242                };
243                self.set_sumbasti(sumbasti, o.clone());
244            }
245        }
246
247        if let SumtiAtom::Description(_, _, _, crate::jbo_syntax::EitherSelbriSumti::Selbri(sb), _, _) = sa {
248            for lerfu in crate::jbo_syntax::lerfu_strings_of_selbri(sb) {
249                let sumbasti = Sumbasti {
250                    explicitly_assigned: false,
251                    atom: SumtiAtom::LerfuString(lerfu),
252                };
253                self.set_sumbasti(sumbasti, o.clone());
254            }
255        }
256    }
257
258    // Ported from: ParseM.hs :: pushBackcount
259    /// Push a new backcount level for ri/ra/ru
260    pub fn push_backcount(&mut self) {
261        let new_stack: Vec<i32> = std::iter::once(0)
262            .chain(self.backcount_stack.iter().map(|n| n + 1))
263            .collect();
264        self.backcount_stack = new_stack;
265    }
266
267    // Ported from: ParseM.hs :: popBackcount
268    /// Pop backcount and return the count
269    pub fn pop_backcount(&mut self) -> i32 {
270        if self.backcount_stack.is_empty() {
271            return 0;
272        }
273        self.backcount_stack.remove(0)
274    }
275
276    // Ported from: ParseM.hs :: setReferenced
277    /// Mark a variable as referenced
278    pub fn set_referenced(&mut self, n: i32) {
279        self.referenced_vars.insert(n);
280    }
281
282    // Ported from: ParseM.hs :: referenced
283    /// Check if a variable is referenced
284    pub fn referenced(&self, n: i32) -> bool {
285        self.referenced_vars.contains(&n)
286    }
287
288    // Ported from: ParseM.hs :: putLambda
289    /// Put a lambda variable and return the term
290    pub fn put_lambda(&mut self, mlev: Option<i32>, mnum: Option<i32>) -> JboTerm {
291        let lev = mlev.unwrap_or(1);
292        let next = self.lambdas.keys().filter_map(|pos| if pos.level == lev { Some(pos.num) } else { None })
293            .max()
294            .unwrap_or(0) + 1;
295        let num = mnum.unwrap_or(next);
296        let pos = LambdaPos { level: lev, num };
297
298        if let Some(&n) = self.lambdas.get(&pos) {
299            return JboTerm::Var(n);
300        }
301
302        let fresh = self.get_fresh_var(VariableDomain::UnrestrictedDomain);
303        if let JboTerm::Var(n) = fresh {
304            self.lambdas.insert(pos, n);
305            JboTerm::Var(n)
306        } else {
307            fresh
308        }
309    }
310}
311
312// Ported from: ParseM.hs :: BridiParseState
313/// BridiParseState holds state which respects the logical structure
314#[derive(Debug, Clone)]
315pub struct BridiParseState {
316    pub arglist: Arglist,
317    pub var_bindings: VarBindings,
318    pub rvar_bindings: RVarBindings,
319    pub is_sub_bridi: bool,
320}
321
322impl Default for BridiParseState {
323    fn default() -> Self {
324        Self::new()
325    }
326}
327
328impl BridiParseState {
329    pub fn new() -> Self {
330        BridiParseState {
331            arglist: Arglist::new(),
332            var_bindings: HashMap::new(),
333            rvar_bindings: HashMap::new(),
334            is_sub_bridi: false,
335        }
336    }
337}
338
339// Ported from: ParseM.hs :: Sumbasti
340#[derive(Debug, Clone, PartialEq, Eq, Hash)]
341pub struct Sumbasti {
342    pub explicitly_assigned: bool,
343    pub atom: SumtiAtom,
344}
345
346pub type SumbastiBindings = HashMap<Sumbasti, JboTerm>;
347pub type StoredBridi = Arc<dyn Fn(&Args) -> JboProp + Send + Sync>;
348pub type BribastiBindings = HashMap<TanruUnit, StoredBridi>;
349
350// Ported from: ParseM.hs :: Question
351#[derive(Debug, Clone)]
352pub struct Question {
353    pub kau_depth: Option<i32>,
354    pub info: QInfo,
355}
356
357// Ported from: ParseM.hs :: QInfo
358pub enum QInfo {
359    QTruth,
360    QSumti(JboTerm, Option<JboPred>),
361    QBridi(JboRel),
362}
363
364impl Clone for QInfo {
365    fn clone(&self) -> Self {
366        match self {
367            QInfo::QTruth => QInfo::QTruth,
368            QInfo::QSumti(term, dom) => QInfo::QSumti(term.clone(), dom.clone()),
369            QInfo::QBridi(rel) => QInfo::QBridi(rel.clone()),
370        }
371    }
372}
373
374impl std::fmt::Debug for QInfo {
375    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376        match self {
377            QInfo::QTruth => write!(f, "QTruth"),
378            QInfo::QSumti(term, _) => write!(f, "QSumti({:?})", term),
379            QInfo::QBridi(rel) => write!(f, "QBridi({:?})", rel),
380        }
381    }
382}
383
384// Ported from: ParseM.hs :: LambdaPos
385#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
386pub struct LambdaPos {
387    pub level: i32,
388    pub num: i32,
389}
390
391// Ported from: ParseM.hs :: VariableDomain
392pub enum VariableDomain {
393    UnrestrictedDomain,
394    PredDomain(JboPred),
395    FiniteDomain(Vec<JboTerm>),
396}
397
398impl Clone for VariableDomain {
399    fn clone(&self) -> Self {
400        match self {
401            VariableDomain::UnrestrictedDomain => VariableDomain::UnrestrictedDomain,
402            VariableDomain::PredDomain(pred) => VariableDomain::PredDomain(pred.clone()),
403            VariableDomain::FiniteDomain(os) => VariableDomain::FiniteDomain(os.clone()),
404        }
405    }
406}
407
408impl std::fmt::Debug for VariableDomain {
409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410        match self {
411            VariableDomain::UnrestrictedDomain => write!(f, "UnrestrictedDomain"),
412            VariableDomain::PredDomain(_) => write!(f, "PredDomain(<fn>)"),
413            VariableDomain::FiniteDomain(os) => f.debug_tuple("FiniteDomain").field(os).finish(),
414        }
415    }
416}
417
418// Ported from: ParseM.hs :: Arglist
419#[derive(Debug, Clone)]
420pub struct Arglist {
421    pub args: Args,
422    pub position: i32,
423}
424
425impl Default for Arglist {
426    fn default() -> Self {
427        Self::new()
428    }
429}
430
431impl Arglist {
432    pub fn new() -> Self {
433        Arglist {
434            args: HashMap::new(),
435            position: 1,
436        }
437    }
438}
439
440pub type Args = HashMap<ArgPos, JboTerm>;
441
442// Ported from: ParseM.hs :: ArgPos
443#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
444pub enum ArgPos {
445    NPos(i32),
446    JaiPos,
447    UnfilledPos(i32),
448}
449
450pub type VarBindings = HashMap<SumtiAtom, JboTerm>;
451pub type RVarBindings = HashMap<TanruUnit, JboRel>;
452
453// Ported from: ParseM.hs :: Texticule
454#[derive(Debug, Clone)]
455#[allow(clippy::large_enum_variant)]
456pub enum Texticule {
457    TexticuleProp(JboProp),
458    TexticuleSide(SideType, Box<Texticule>),
459}
460
461// Ported from: ParseM.hs :: argsToJboterms
462pub fn args_to_jboterms(args: &Args) -> Vec<JboTerm> {
463    let max_n = args.keys()
464        .filter_map(|k| match k {
465            ArgPos::NPos(n) => Some(*n),
466            _ => None,
467        })
468        .max()
469        .unwrap_or(0);
470
471    (1..=max_n)
472        .map(|n| args.get(&ArgPos::NPos(n)).cloned().unwrap_or(JboTerm::Unfilled))
473        .collect()
474}
475
476// ============================================================================
477// Additional functions from ParseM.hs
478// ============================================================================
479
480// Ported from: ParseM.hs :: jboRelToBridi
481/// Convert a JboRel to a Bridi function
482pub fn jbo_rel_to_bridi(rel: JboRel) -> Bridi {
483    Box::new(move |args: &Args| {
484        let terms = args_to_jboterms(args);
485        Prop::Rel(rel.clone(), terms)
486    })
487}
488
489// Apply pending transforms to a bridi, returning a new bridi that applies transforms when called
490pub fn apply_transforms_to_bridi(bridi: Bridi, state: &mut ParseState) -> Bridi {
491    let transforms = state.pending_prop_transforms.drain(..).collect::<Vec<_>>();
492    apply_transform_list_to_bridi(bridi, transforms)
493}
494
495pub fn apply_transform_list_to_bridi(bridi: Bridi, transforms: Vec<PropTransform>) -> Bridi {
496    if transforms.is_empty() {
497        return bridi;
498    }
499
500    Box::new(move |args: &Args| {
501        let mut prop = bridi(args);
502        for transform in transforms.iter().rev() {
503            prop = apply_transform(transform.clone(), prop);
504        }
505        prop
506    })
507}
508
509// Ported from: ParseM.hs :: withJaiAsTag
510/// Wrap a bridi with JAI as a modal tag
511pub fn with_jai_as_tag(jtag: crate::jbo_prop::JboTag, bridi: Bridi) -> Bridi {
512    Box::new(move |args: &Args| {
513        let jai_term = args.get(&ArgPos::JaiPos).cloned();
514        let mut new_args = args.clone();
515        new_args.remove(&ArgPos::JaiPos);
516
517        let inner_prop = bridi(&new_args);
518        Prop::Modal(
519            crate::jbo_prop::JboModalOp::Tagged(jtag.clone(), jai_term),
520            Box::new(inner_prop)
521        )
522    })
523}
524
525// Ported from: ParseM.hs :: withJaiAsRaising
526/// Wrap a bridi with JAI as raising (tu'a)
527pub fn with_jai_as_raising(bridi: Bridi) -> Bridi {
528    Box::new(move |args: &Args| {
529        if let Some(jai_term) = args.get(&ArgPos::JaiPos) {
530            let mut new_args = args.clone();
531            new_args.remove(&ArgPos::JaiPos);
532
533            let qualified = JboTerm::QualifiedTerm(
534                SumtiQualifier::LAhE("tu'a".to_string()),
535                Box::new(jai_term.clone()),
536            );
537            new_args.insert(ArgPos::NPos(1), qualified);
538
539            bridi(&new_args)
540        } else {
541            bridi(args)
542        }
543    })
544}
545
546// Ported from: ParseM.hs :: partiallyResolveBridi
547/// Partially resolve a bridi by capturing its arglist
548/// Returns a new bridi that joins extra args with the captured arglist
549pub fn partially_resolve_bridi(bridi: Bridi, arglist: &Args) -> Bridi {
550    let captured_args = arglist.clone();
551    Box::new(move |extra_args: &Args| {
552        let joined = join_args(extra_args, &captured_args);
553        bridi(&joined)
554    })
555}
556
557// Ported from: ParseM.hs :: joinArgs
558/// Join two argument maps, handling implicit arguments
559pub fn join_args(new: &Args, old: &Args) -> Args {
560    let mut result = old.clone();
561
562    let mut old_positions: Vec<i32> = old.keys()
563        .filter_map(|k| match k {
564            ArgPos::NPos(n) => Some(*n),
565            _ => None,
566        })
567        .collect();
568    old_positions.sort();
569
570    let mut implicit_args: Vec<(i32, JboTerm)> = new.iter()
571        .filter_map(|(k, v)| match k {
572            ArgPos::UnfilledPos(n) if !matches!(v, JboTerm::Unfilled) => Some((*n, v.clone())),
573            _ => None,
574        })
575        .collect();
576    implicit_args.sort_by_key(|(n, _)| *n);
577
578    for (n, value) in implicit_args.into_iter().rev() {
579        let mut gap = 1;
580        let mut seen = 0;
581        loop {
582            if !old_positions.contains(&gap) {
583                if seen == n {
584                    result.insert(ArgPos::NPos(gap), value);
585                    break;
586                }
587                seen += 1;
588            }
589            gap += 1;
590        }
591    }
592
593    for (k, v) in new.iter() {
594        if !matches!(k, ArgPos::UnfilledPos(_)) {
595            result.insert(k.clone(), v.clone());
596        }
597    }
598
599    result
600}
601
602// Ported from: ParseM.hs :: swapArgs
603/// Swap two argument positions
604pub fn swap_args(p1: &ArgPos, p2: &ArgPos, args: &Args) -> Args {
605    args.iter()
606        .map(|(k, v)| {
607            let new_k = if k == p1 {
608                p2.clone()
609            } else if k == p2 {
610                p1.clone()
611            } else {
612                k.clone()
613            };
614            (new_k, v.clone())
615        })
616        .collect()
617}
618
619// Ported from: ParseM.hs :: setArg
620/// Set an argument at a specific position
621pub fn set_arg(pos: ArgPos, term: JboTerm, args: &Args) -> Args {
622    let mut result = args.clone();
623    result.insert(pos, term);
624    result
625}
626
627// Ported from: ParseM.hs :: addFaiToArglist
628/// Add FAI term to arglist
629pub fn add_fai_to_arglist(term: JboTerm, arglist: &Arglist) -> Arglist {
630    let mut new_args = arglist.args.clone();
631    new_args.insert(ArgPos::JaiPos, term);
632    Arglist {
633        args: new_args,
634        position: arglist.position,
635    }
636}
637
638// Ported from: ParseM.hs :: closeBridi
639/// Close a bridi by converting filled places to unfilled positions
640pub fn close_bridi(bridi: Bridi) -> Bridi {
641    Box::new(move |args: &Args| {
642        let terms = args_to_jboterms(args);
643        let mut new_args: Args = terms.iter()
644            .enumerate()
645            .map(|(i, t)| (ArgPos::UnfilledPos(i as i32), t.clone()))
646            .collect();
647        
648        // Preserve JAI position if present
649        if let Some(jai_term) = args.get(&ArgPos::JaiPos) {
650            new_args.insert(ArgPos::JaiPos, jai_term.clone());
651        }
652        
653        bridi(&new_args)
654    })
655}
656
657// Ported from: ParseM.hs :: swapTerms
658/// Swap two terms in a list by position (1-indexed)
659pub fn swap_terms(terms: &[JboTerm], n: i32, m: i32) -> Vec<JboTerm> {
660    crate::util::swap_finite_with_default(JboTerm::Unfilled, terms, (n - 1) as usize, (m - 1) as usize)
661}
662
663impl ParseState {
664    // Ported from: ParseM.hs :: addSideTexticule
665    /// Add a side texticule to the parse state
666    pub fn add_side_texticule(&mut self, texticule: Texticule) {
667        self.side_texticules.insert(0, texticule);
668    }
669    
670    // Ported from: ParseM.hs :: takeSideTexticules
671    /// Take all side texticules and clear the list
672    pub fn take_side_texticules(&mut self) -> Vec<Texticule> {
673        std::mem::take(&mut self.side_texticules)
674    }
675    
676    // Ported from: ParseM.hs :: addSumtiQuestion
677    /// Add a sumti question (ma) to the parse state
678    pub fn add_sumti_question(&mut self, kau_depth: Option<i32>) -> JboTerm {
679        let var = self.get_fresh_var(VariableDomain::UnrestrictedDomain);
680        self.questions.insert(0, Question {
681            kau_depth,
682            info: QInfo::QSumti(var.clone(), None),
683        });
684        var
685    }
686    
687    // Ported from: ParseM.hs :: addBridiQuestion
688    /// Add a bridi question (mo) to the parse state
689    pub fn add_bridi_question(&mut self, kau_depth: Option<i32>) -> JboRel {
690        let rvar = self.get_fresh_rvar();
691        self.questions.insert(0, Question {
692            kau_depth,
693            info: QInfo::QBridi(rvar.clone()),
694        });
695        rvar
696    }
697    
698    // Ported from: ParseM.hs :: addQuestion
699    /// Add a question to the parse state
700    pub fn add_question(&mut self, question: Question) {
701        self.questions.insert(0, question);
702    }
703    
704    // Ported from: ParseM.hs :: modifyQuestions
705    /// Modify the questions list
706    pub fn modify_questions<F>(&mut self, f: F)
707    where
708        F: FnOnce(Vec<Question>) -> Vec<Question>,
709    {
710        let questions = std::mem::take(&mut self.questions);
711        self.questions = f(questions);
712    }
713}
714
715impl BridiParseState {
716    // Ported from: ParseM.hs :: advanceArgPosToSelbri
717    /// Advance argument position to selbri (position 2 if no args yet)
718    pub fn advance_arg_pos_to_selbri(&mut self) {
719        if self.arglist.args.is_empty() {
720            self.arglist.position = 2;
721        }
722    }
723    
724    // Ported from: ParseM.hs :: setArgInArglist
725    /// Set an argument in the arglist
726    pub fn set_arg_in_arglist(&mut self, pos: ArgPos, term: JboTerm) {
727        self.arglist.args.insert(pos, term);
728    }
729    
730    // Ported from: ParseM.hs :: setArgPos
731    /// Set the current argument position
732    pub fn set_arg_pos(&mut self, pos: i32) {
733        self.arglist.position = pos;
734    }
735}
736
737// ============================================================================
738// Lambda handling
739// ============================================================================
740
741
742pub fn shunt_lambdas(parse_state: &mut ParseState, delta: i32) {
743    let mut new_lambdas = HashMap::new();
744    for (pos, v) in parse_state.lambdas.iter() {
745        if pos.level > 0 {
746            let new_pos = LambdaPos {
747                level: pos.level + delta,
748                num: pos.num,
749            };
750            new_lambdas.insert(new_pos, *v);
751        }
752    }
753    parse_state.lambdas = new_lambdas;
754}
755
756// ============================================================================
757// Bridi conversion functions
758// ============================================================================
759
760pub fn bridi_to_jbo_vpred(bridi: Bridi) -> JboVPred {
761    Arc::new(move |os: &[JboTerm]| {
762        let args: Args = os.iter()
763            .enumerate()
764            .map(|(n, o)| (ArgPos::UnfilledPos(n as i32), o.clone()))
765            .collect();
766        bridi(&args)
767    })
768}
769
770pub fn bridi_to_jbo_pred(bridi: Bridi) -> JboPred {
771    v_pred_to_pred(bridi_to_jbo_vpred(bridi))
772}
773
774// ============================================================================
775// Question handling
776// ============================================================================
777
778pub fn with_questions<P: PreProp>(top: bool, parse_state: &mut ParseState, p: P) -> P {
779    let saved_questions = parse_state.questions.clone();
780    parse_state.questions.clear();
781    let result = do_questions(top, parse_state, p);
782    parse_state.questions.extend(saved_questions);
783    result
784}
785
786pub fn do_questions<P: PreProp>(top: bool, parse_state: &mut ParseState, p: P) -> P {
787    let qinfos = de_kau(top, parse_state);
788    qinfos.into_iter().fold(p, |acc, qinfo| do_q_info(qinfo, acc))
789}
790
791pub fn do_q_info<P: PreProp>(qinfo: QInfo, p: P) -> P {
792    match qinfo {
793        QInfo::QSumti(qv, dom) => {
794            let qv_clone = qv.clone();
795            p.lift_to_prop(Box::new(move |prop: JboProp| {
796                let qv_inner = qv_clone.clone();
797                let restriction = dom.clone().map(|pred| {
798                    Arc::new(move |v: i32| pred(&JboTerm::BoundVar(v))) as Arc<dyn Fn(i32) -> JboProp + Send + Sync>
799                });
800                Prop::Quantified(
801                    JboQuantifier::QuestionQuantifier,
802                    restriction,
803                    Arc::new(move |v: i32| sub_term(&qv_inner, &JboTerm::BoundVar(v), &prop)),
804                )
805            }))
806        }
807        QInfo::QBridi(qv) => {
808            let qv_clone = qv.clone();
809            p.lift_to_prop(Box::new(move |prop: JboProp| {
810                let qv_inner = qv_clone.clone();
811                Prop::Quantified(
812                    JboQuantifier::RelQuantifier(Box::new(JboQuantifier::QuestionQuantifier)),
813                    None,
814                    Arc::new(move |v: i32| sub_rel(&qv_inner, &JboRel::BoundRVar(v), &prop)),
815                )
816            }))
817        }
818        QInfo::QTruth => {
819            p.lift_to_prop(Box::new(|prop: JboProp| {
820                Prop::Modal(JboModalOp::QTruthModal, Box::new(prop))
821            }))
822        }
823    }
824}
825
826pub fn de_kau(top: bool, parse_state: &mut ParseState) -> Vec<QInfo> {
827    let mut out_qs = Vec::new();
828    let mut remaining_qs = Vec::new();
829    
830    for q in parse_state.questions.iter() {
831        if top || q.kau_depth == Some(1) {
832            out_qs.push(q.info.clone());
833        } else {
834            let mut new_q = q.clone();
835            if let Some(depth) = new_q.kau_depth {
836                new_q.kau_depth = Some(depth - 1);
837            }
838            remaining_qs.push(new_q);
839        }
840    }
841    
842    parse_state.questions = remaining_qs;
843    out_qs
844}
845
846// ============================================================================
847// Argument handling
848// ============================================================================
849
850pub fn add_arg(arg: Arg, bridi_state: &mut BridiParseState, parse_state: &mut ParseState) {
851    match arg {
852        Arg::FAITagged(o) => {
853            bridi_state.arglist.args.insert(ArgPos::JaiPos, o);
854        }
855        Arg::Untagged(o) => {
856            let n = bridi_state.arglist.position;
857            set_arg_in_arglist(ArgPos::NPos(n), o.clone(), bridi_state);
858            if !bridi_state.is_sub_bridi {
859                parse_state.map_prop(PropTransform::SubstituteTerm(
860                    JboTerm::UnboundSumbasti(crate::jbo_syntax::SumtiAtom::MainBridiSumbasti(n)),
861                    o,
862                ));
863            }
864            set_arg_pos(n + 1, bridi_state);
865        }
866        Arg::FATagged(n, o) => {
867            let n = n as i32;
868            set_arg_in_arglist(ArgPos::NPos(n), o.clone(), bridi_state);
869            if !bridi_state.is_sub_bridi {
870                parse_state.map_prop(PropTransform::SubstituteTerm(
871                    JboTerm::UnboundSumbasti(crate::jbo_syntax::SumtiAtom::MainBridiSumbasti(n)),
872                    o,
873                ));
874            }
875            set_arg_pos(n + 1, bridi_state);
876        }
877    }
878}
879
880pub fn set_arg_in_arglist(pos: ArgPos, term: JboTerm, bridi_state: &mut BridiParseState) {
881    bridi_state.arglist.args.insert(pos, term);
882}
883
884pub fn set_arg_pos(n: i32, bridi_state: &mut BridiParseState) {
885    bridi_state.arglist.position = n;
886}
887
888// ============================================================================
889// Variable binding utilities
890// ============================================================================
891
892pub fn shunt_vars<K: Eq + Clone + Hash, V: Clone, F: Fn(usize) -> K>(
893    var_fn: &F,
894    bindings: &HashMap<K, V>,
895) -> HashMap<K, V> {
896    let mut result = bindings.clone();
897
898    // Find the first unbound position
899    let max_bound = (1..)
900        .find(|&n| !bindings.contains_key(&var_fn(n)))
901        .unwrap_or(1);
902
903    // Shunt all variables up by 1
904    for n in 1..max_bound {
905        if let Some(v) = bindings.get(&var_fn(n)) {
906            result.insert(var_fn(n + 1), v.clone());
907        }
908    }
909
910    result
911}
912
913pub fn set_shunting<K: Eq + Clone + Hash, V: Clone, F: Fn(usize) -> K>(
914    var_fn: &F,
915    value: V,
916    bindings: &HashMap<K, V>,
917) -> HashMap<K, V> {
918    let mut result = shunt_vars(var_fn, bindings);
919    result.insert(var_fn(1), value);
920    result
921}
922
923// ============================================================================
924// PreProp trait and implementations
925// ============================================================================
926
927pub trait PreProp: Clone {
928    fn lift_to_prop(self, f: Box<dyn Fn(JboProp) -> JboProp + Send + Sync>) -> Self;
929    fn lift_to_prop2(self, other: Self, f: Box<dyn Fn(JboProp, JboProp) -> JboProp + Send + Sync>) -> Self;
930    fn dummy_pre_prop() -> Self;
931}
932
933impl PreProp for JboProp {
934    fn lift_to_prop(self, f: Box<dyn Fn(JboProp) -> JboProp + Send + Sync>) -> Self {
935        f(self)
936    }
937
938    fn lift_to_prop2(self, other: Self, f: Box<dyn Fn(JboProp, JboProp) -> JboProp + Send + Sync>) -> Self {
939        f(self, other)
940    }
941
942    fn dummy_pre_prop() -> Self {
943        Prop::Eet
944    }
945}
946
947impl PreProp for JboPred {
948    fn lift_to_prop(self, f: Box<dyn Fn(JboProp) -> JboProp + Send + Sync>) -> Self {
949        Arc::new(move |o: &JboTerm| f(self(o)))
950    }
951
952    fn lift_to_prop2(self, other: Self, f: Box<dyn Fn(JboProp, JboProp) -> JboProp + Send + Sync>) -> Self {
953        Arc::new(move |o: &JboTerm| f(self(o), other(o)))
954    }
955
956    fn dummy_pre_prop() -> Self {
957        Arc::new(|_| Prop::Eet)
958    }
959}
960
961impl PreProp for JboNPred {
962    fn lift_to_prop(self, f: Box<dyn Fn(JboProp) -> JboProp + Send + Sync>) -> Self {
963        let pred = self.pred;
964        JboNPred {
965            arity: self.arity,
966            pred: Arc::new(move |os: &[JboTerm]| f(pred(os))),
967        }
968    }
969
970    fn lift_to_prop2(self, other: Self, f: Box<dyn Fn(JboProp, JboProp) -> JboProp + Send + Sync>) -> Self {
971        if self.arity != other.arity {
972            panic!("mismatched JboNPred arities in lift_to_prop2");
973        }
974        let pred1 = self.pred;
975        let pred2 = other.pred;
976        JboNPred {
977            arity: self.arity,
978            pred: Arc::new(move |os: &[JboTerm]| f(pred1(os), pred2(os))),
979        }
980    }
981
982    fn dummy_pre_prop() -> Self {
983        JboNPred {
984            arity: 0,
985            pred: Arc::new(|_| Prop::Eet),
986        }
987    }
988}
989
990// ============================================================================
991// Helper functions for substitution
992// ============================================================================
993
994fn sub_term(old: &JboTerm, new: &JboTerm, prop: &JboProp) -> JboProp {
995    crate::jbo_prop::sub_term(old, new, prop.clone())
996}
997
998fn sub_rel(old: &JboRel, new: &JboRel, prop: &JboProp) -> JboProp {
999    crate::jbo_prop::sub_rel(old, new, prop.clone())
1000}
1001
1002fn jbo_pred_to_loj_pred(pred: &JboPred) -> Arc<dyn Fn(i32) -> JboProp + Send + Sync> {
1003    crate::jbo_prop::jbo_pred_to_loj_pred(pred)
1004}
1005
1006fn v_pred_to_pred(vpred: JboVPred) -> JboPred {
1007    crate::jbo_prop::v_pred_to_pred(vpred)
1008}
1009
1010
1011// ============================================================================
1012// Arg type definition
1013// ============================================================================
1014
1015#[derive(Debug, Clone)]
1016pub enum Arg {
1017    Untagged(JboTerm),
1018    FATagged(usize, JboTerm),
1019    FAITagged(JboTerm),
1020}
1021
1022// ============================================================================
1023// QInfo type definition
1024// ============================================================================
1025
1026
1027pub fn do_lambdas(vpred: JboVPred, parse_state: &mut ParseState) -> JboNPred {
1028    let ls = parse_state.lambdas.clone();
1029    let arity = ls.keys()
1030        .filter_map(|pos| if pos.level == 1 { Some(pos.num) } else { None })
1031        .max()
1032        .unwrap_or(0)
1033        .max(1);
1034
1035    shunt_lambdas(parse_state, -1);
1036
1037    JboNPred {
1038        arity: arity as usize,
1039        pred: Arc::new(move |os: &[JboTerm]| {
1040            let mut current: JboVPred = vpred.clone();
1041            for (idx, o) in os.iter().enumerate() {
1042                let num = (idx + 1) as i32;
1043                if let Some(v) = ls.get(&LambdaPos { level: 1, num }) {
1044                    let previous = current.clone();
1045                    let old = JboTerm::Var(*v);
1046                    let new = o.clone();
1047                    current = Arc::new(move |rest: &[JboTerm]| {
1048                        crate::jbo_prop::sub_term(&old, &new, previous(rest))
1049                    });
1050                } else {
1051                    let previous = current.clone();
1052                    let head = o.clone();
1053                    current = Arc::new(move |rest: &[JboTerm]| {
1054                        let mut args = vec![head.clone()];
1055                        args.extend_from_slice(rest);
1056                        previous(&args)
1057                    });
1058                }
1059            }
1060            current(&[])
1061        }),
1062    }
1063}
1064
1065// ============================================================================
1066// Additional ParseM.hs functions
1067// ============================================================================
1068
1069// Ported from: ParseM.hs :: mPredToVDom
1070/// Convert Maybe JboPred to VariableDomain
1071pub fn m_pred_to_v_dom(pred: Option<JboPred>) -> VariableDomain {
1072    match pred {
1073        Some(p) => VariableDomain::PredDomain(p),
1074        None => VariableDomain::UnrestrictedDomain,
1075    }
1076}
1077
1078// Ported from: ParseM.hs :: getSumbastiBinding
1079/// Get sumbasti binding, checking both referenced and unreferenced
1080pub fn get_sumbasti_binding(bs: &SumbastiBindings, atom: &SumtiAtom) -> JboTerm {
1081    let key_ref = Sumbasti { explicitly_assigned: true, atom: atom.clone() };
1082    if let Some(term) = bs.get(&key_ref) {
1083        return term.clone();
1084    }
1085    let key_unref = Sumbasti { explicitly_assigned: false, atom: atom.clone() };
1086    if let Some(term) = bs.get(&key_unref) {
1087        return term.clone();
1088    }
1089    JboTerm::UnboundSumbasti(atom.clone())
1090}
1091
1092// Ported from: ParseM.hs :: getBribastiBinding
1093/// Get bribasti binding, defaulting to jboRelToBridi if not found
1094pub fn get_bribasti_binding(bs: &BribastiBindings, tu: &TanruUnit) -> Bridi {
1095    if let Some(stored) = bs.get(tu) {
1096        let stored = stored.clone();
1097        return Box::new(move |args: &Args| stored(args));
1098    }
1099    let rel = match tu {
1100        TanruUnit::TUBrivla(s) => JboRel::Brivla(s.clone()),
1101        _ => JboRel::UnboundBribasti(tu.clone()),
1102    };
1103    jbo_rel_to_bridi(rel)
1104}
1105
1106// Ported from: ParseM.hs :: addSideTexticule
1107/// Add a side texticule to parse state
1108pub fn add_side_texticule(side: Texticule, parse_state: &mut ParseState) {
1109    parse_state.side_texticules.insert(0, side);
1110}
1111
1112// Ported from: ParseM.hs :: takeSideTexticules
1113/// Take all side texticules and clear the list
1114pub fn take_side_texticules(parse_state: &mut ParseState) -> Vec<Texticule> {
1115    let result = parse_state.side_texticules.clone();
1116    parse_state.side_texticules.clear();
1117    result
1118}
1119
1120// Ported from: ParseM.hs :: addSumtiQuestion
1121/// Add a sumti question and return the variable
1122pub fn add_sumti_question(
1123    kau: Option<i32>,
1124    dom: Option<&JboPred>,
1125    parse_state: &mut ParseState,
1126) -> JboTerm {
1127    let v_dom = match dom {
1128        Some(pred) => VariableDomain::PredDomain(pred.clone()),
1129        None => VariableDomain::UnrestrictedDomain,
1130    };
1131    let var = get_fresh_var(&v_dom, parse_state);
1132    let q_info = QInfo::QSumti(var.clone(), dom.cloned());
1133    let question = Question {
1134        kau_depth: kau,
1135        info: q_info,
1136    };
1137    parse_state.questions.insert(0, question);
1138    var
1139}
1140
1141// Ported from: ParseM.hs :: addBridiQuestion
1142/// Add a bridi question and return the relation variable
1143pub fn add_bridi_question(kau: Option<i32>, parse_state: &mut ParseState) -> JboRel {
1144    let rvar = get_fresh_rvar(parse_state);
1145    let q_info = QInfo::QBridi(rvar.clone());
1146    let question = Question {
1147        kau_depth: kau,
1148        info: q_info,
1149    };
1150    parse_state.questions.push(question);
1151    rvar
1152}
1153
1154// Helper to get fresh var
1155fn get_fresh_var(dom: &VariableDomain, parse_state: &mut ParseState) -> JboTerm {
1156    let v = parse_state.next_fresh_var;
1157    parse_state.next_fresh_var += 1;
1158    parse_state.variable_domains.insert(v, dom.clone());
1159    JboTerm::Var(v)
1160}
1161
1162// Helper to get fresh rvar
1163fn get_fresh_rvar(parse_state: &mut ParseState) -> JboRel {
1164    let v = parse_state.next_fresh_rvar;
1165    parse_state.next_fresh_rvar += 1;
1166    JboRel::RVar(v)
1167}
1168
1169// ============================================================================
1170// Additional ParseM.hs functions (continued)
1171// ============================================================================
1172
1173// Ported from: ParseM.hs :: advanceArgPosToSelbri
1174/// Advance argument position to selbri (position 2) if no args yet
1175pub fn advance_arg_pos_to_selbri(bridi_state: &mut BridiParseState) {
1176    if bridi_state.arglist.args.is_empty() {
1177        bridi_state.arglist.position = 2;
1178    }
1179}
1180
1181// Ported from: ParseM.hs :: ignoringArgs
1182/// Execute action while preserving arglist state
1183pub fn ignoring_args<F, R>(bridi_state: &mut BridiParseState, f: F) -> R
1184where
1185    F: FnOnce(&mut BridiParseState) -> R,
1186{
1187    let saved_arglist = bridi_state.arglist.clone();
1188    let result = f(bridi_state);
1189    bridi_state.arglist = saved_arglist;
1190    result
1191}
1192
1193// Ported from: ParseM.hs :: updateReferenced
1194/// Mark a variable as referenced
1195pub fn update_referenced(term: &JboTerm, parse_state: &mut ParseState) {
1196    if let JboTerm::Var(n) = term {
1197        parse_state.referenced_vars.insert(*n);
1198    }
1199}
1200
1201// Ported from: ParseM.hs :: doAssigns
1202/// Process a list of assignments
1203pub fn do_assigns(
1204    term: JboTerm,
1205    assigns: &[Either<SumtiAtom, JboTerm>],
1206    parse_state: &mut ParseState,
1207) -> JboTerm {
1208    let mut result = term;
1209    for assign in assigns {
1210        result = do_assign(result, assign, parse_state);
1211    }
1212    result
1213}
1214
1215// Ported from: ParseM.hs :: doAssign
1216/// Process a single assignment
1217pub fn do_assign(
1218    term: JboTerm,
1219    assign: &Either<SumtiAtom, JboTerm>,
1220    parse_state: &mut ParseState,
1221) -> JboTerm {
1222    match (&term, assign) {
1223        (JboTerm::UnboundSumbasti(a), Either::Right(assto)) if is_assignable(a) => {
1224            let key = Sumbasti {
1225                explicitly_assigned: true,
1226                atom: a.clone(),
1227            };
1228            parse_state.sumbasti_bindings.insert(key, assto.clone());
1229            assto.clone()
1230        }
1231        (_, Either::Left(ass)) => {
1232            let key = Sumbasti {
1233                explicitly_assigned: true,
1234                atom: ass.clone(),
1235            };
1236            parse_state.sumbasti_bindings.insert(key, term.clone());
1237            term
1238        }
1239        _ => term,
1240    }
1241}
1242
1243// Helper for doAssign
1244// Ported from: JboSyntax.hs :: isAssignable
1245fn is_assignable(atom: &SumtiAtom) -> bool {
1246    matches!(
1247        atom,
1248        SumtiAtom::Assignable(_) | SumtiAtom::LerfuString(_) | SumtiAtom::Name(_, _, _)
1249    )
1250}
1251
1252// Helper type for Either
1253#[derive(Debug, Clone)]
1254pub enum Either<L, R> {
1255    Left(L),
1256    Right(R),
1257}
1258
1259// Ported from: ParseM.hs :: doIncidentals
1260/// Process incidental clauses - adds side texticules for non-veridical propositions
1261pub fn do_incidentals(
1262    term: JboTerm,
1263    incidentals: &[JboPred],
1264    parse_state: &mut ParseState,
1265) -> JboTerm {
1266    let Some(pred) = crate::jbo_prop::and_m_pred(incidentals.to_vec()) else {
1267        return term;
1268    };
1269
1270    let (pred, term) = quantify_out_frees(true, pred, term, parse_state);
1271    let prop = pred(&term);
1272    extract_non_veridical(&prop, &term, parse_state);
1273    parse_state.add_side_texticule(Texticule::TexticuleProp(prop));
1274    term
1275}
1276
1277fn quantify_out_frees(
1278    add_params: bool,
1279    pred: JboPred,
1280    term: JboTerm,
1281    parse_state: &mut ParseState,
1282) -> (JboPred, JboTerm) {
1283    let frees = crate::jbo_prop::free_vars(&pred(&term));
1284    if frees.is_empty() {
1285        return (pred, term);
1286    }
1287
1288    let term = if add_params {
1289        match term {
1290            JboTerm::Constant(n, params) => {
1291                let mut new_params = frees.iter().rev().cloned().collect::<Vec<_>>();
1292                new_params.extend(params);
1293                JboTerm::Constant(n, new_params)
1294            }
1295            other => other,
1296        }
1297    } else {
1298        term
1299    };
1300
1301    let mut pred = pred;
1302    for free in frees {
1303        let domain = match free {
1304            JboTerm::Var(n) => parse_state.variable_domains.get(&n).cloned().unwrap_or(VariableDomain::UnrestrictedDomain),
1305            _ => VariableDomain::UnrestrictedDomain,
1306        };
1307        pred = quantify_out_free(pred, free, domain);
1308    }
1309
1310    quantify_out_frees(false, pred, term, parse_state)
1311}
1312
1313fn quantify_out_free(pred: JboPred, free: JboTerm, domain: VariableDomain) -> JboPred {
1314    Arc::new(move |x: &JboTerm| {
1315        let body = pred(x);
1316        let free_for_body = free.clone();
1317        let restriction = restr_of_domain(domain.clone());
1318        Prop::Quantified(
1319            JboQuantifier::LojQuantifier(crate::logic::LojQuantifier::Forall),
1320            restriction.map(|r| {
1321                Arc::new(move |v: i32| r(&JboTerm::BoundVar(v))) as Arc<dyn Fn(i32) -> JboProp + Send + Sync>
1322            }),
1323            Arc::new(move |v: i32| crate::jbo_prop::sub_term(&free_for_body, &JboTerm::BoundVar(v), body.clone())),
1324        )
1325    })
1326}
1327
1328fn restr_of_domain(domain: VariableDomain) -> Option<JboPred> {
1329    match domain {
1330        VariableDomain::UnrestrictedDomain => None,
1331        VariableDomain::PredDomain(pred) => Some(pred),
1332        VariableDomain::FiniteDomain(os) => {
1333            let first = os.first()?.clone();
1334            let rest = os[1..].to_vec();
1335            Some(Arc::new(move |x: &JboTerm| {
1336                rest.iter().rev().fold(
1337                    Prop::Rel(JboRel::Equal, vec![x.clone(), first.clone()]),
1338                    |acc, o| Prop::Connected(
1339                        crate::logic::Connective::Or,
1340                        Box::new(Prop::Rel(JboRel::Equal, vec![x.clone(), o.clone()])),
1341                        Box::new(acc),
1342                    ),
1343                )
1344            }))
1345        }
1346    }
1347}
1348
1349/// Extract non-veridical propositions from a proposition and add to parse state
1350fn extract_non_veridical(prop: &JboProp, _term: &JboTerm, parse_state: &mut ParseState) {
1351    if let Prop::Modal(JboModalOp::NonVeridical, inner) = prop {
1352        parse_state.non_veridical_props.push(inner.as_ref().clone());
1353    }
1354}
1355
1356pub fn apply_transform_list(transforms: Vec<PropTransform>, mut prop: JboProp) -> JboProp {
1357    for transform in transforms.into_iter().rev() {
1358        prop = apply_transform(transform, prop);
1359    }
1360    prop
1361}
1362
1363fn continuation_args_from_prop(prop: &JboProp, start_position: i32) -> Args {
1364    let Prop::Rel(_, terms) = prop else {
1365        return Args::new();
1366    };
1367    terms
1368        .iter()
1369        .enumerate()
1370        .filter_map(|(idx, term)| {
1371            let position = idx as i32 + 1;
1372            if position >= start_position {
1373                Some((ArgPos::UnfilledPos(position - start_position), term.clone()))
1374            } else {
1375                None
1376            }
1377        })
1378        .collect()
1379}
1380
1381fn fill_args_in_prop(prop: JboProp, args: &Args) -> JboProp {
1382    let terms = args_to_jboterms(args);
1383    match prop {
1384        Prop::Rel(rel, _) => Prop::Rel(rel, terms),
1385        Prop::Not(p) => Prop::Not(Box::new(fill_args_in_prop(*p, args))),
1386        Prop::Connected(c, p1, p2) => Prop::Connected(
1387            c,
1388            Box::new(fill_args_in_prop(*p1, args)),
1389            Box::new(fill_args_in_prop(*p2, args)),
1390        ),
1391        Prop::NonLogConnected(j, p1, p2) => Prop::NonLogConnected(
1392            j,
1393            Box::new(fill_args_in_prop(*p1, args)),
1394            Box::new(fill_args_in_prop(*p2, args)),
1395        ),
1396        Prop::Modal(m, p) => Prop::Modal(m, Box::new(fill_args_in_prop(*p, args))),
1397        Prop::Quantified(q, pred, body) => {
1398            let args = args.clone();
1399            Prop::Quantified(q, pred, Arc::new(move |v| fill_args_in_prop(body(v), &args)))
1400        }
1401        Prop::Eet => Prop::Eet,
1402    }
1403}
1404
1405// Apply a single transformation to a proposition
1406fn apply_transform(transform: PropTransform, prop: JboProp) -> JboProp {
1407    match transform {
1408        PropTransform::Quantify(fresh_var, quantifier, restriction) => {
1409            if let JboTerm::Var(n) = fresh_var {
1410                let pred = restriction.map(|f| {
1411                    Arc::new(move |v: i32| f(v)) as Arc<dyn Fn(i32) -> JboProp + Send + Sync>
1412                });
1413                Prop::Quantified(
1414                    quantifier,
1415                    pred,
1416                    Arc::new(move |v: i32| crate::jbo_prop::sub_term(&JboTerm::Var(n), &JboTerm::BoundVar(v), prop.clone()))
1417                )
1418            } else {
1419                prop
1420            }
1421        }
1422        PropTransform::ConnectQuantify { conn, fresh, left_quant, right_quant, restriction } => {
1423            let left_fresh = fresh.clone();
1424            let left_prop = prop.clone();
1425            let left_restriction = restriction.clone();
1426            let left = Prop::Quantified(
1427                left_quant,
1428                left_restriction,
1429                Arc::new(move |v: i32| crate::jbo_prop::sub_term(&left_fresh, &JboTerm::BoundVar(v), left_prop.clone())),
1430            );
1431            let right = Prop::Quantified(
1432                right_quant,
1433                restriction,
1434                Arc::new(move |v: i32| crate::jbo_prop::sub_term(&fresh, &JboTerm::BoundVar(v), prop.clone())),
1435            );
1436            crate::jbo_prop::conn_to_fol(&conn, left, right)
1437        }
1438        PropTransform::ConnectQuantifyTagged { conn, fresh, left_quant, right_quant, restriction, event_fresh, left_modal, right_modal } => {
1439            let left_fresh = fresh.clone();
1440            let left_prop = prop.clone();
1441            let left_restriction = restriction.clone();
1442            let left = Prop::Quantified(
1443                left_quant,
1444                left_restriction,
1445                Arc::new(move |v: i32| crate::jbo_prop::sub_term(&left_fresh, &JboTerm::BoundVar(v), left_prop.clone())),
1446            );
1447            let left = if let Some(modal) = left_modal {
1448                Prop::Modal(modal, Box::new(left))
1449            } else {
1450                left
1451            };
1452            let right = Prop::Quantified(
1453                right_quant,
1454                restriction,
1455                Arc::new(move |v: i32| crate::jbo_prop::sub_term(&fresh, &JboTerm::BoundVar(v), prop.clone())),
1456            );
1457            let right = if let Some(modal) = right_modal {
1458                Prop::Modal(modal, Box::new(right))
1459            } else {
1460                right
1461            };
1462            let connected = crate::jbo_prop::conn_to_fol(&conn, left, right);
1463            if let Some(JboTerm::Var(n)) = event_fresh {
1464                Prop::Quantified(
1465                    JboQuantifier::LojQuantifier(crate::logic::LojQuantifier::Exists),
1466                    None,
1467                    Arc::new(move |v: i32| crate::jbo_prop::sub_term(&JboTerm::Var(n), &JboTerm::BoundVar(v), connected.clone()))
1468                )
1469            } else {
1470                connected
1471            }
1472        }
1473        PropTransform::RQuantify(fresh_rel, quantifier) => {
1474            Prop::Quantified(
1475                quantifier,
1476                None,
1477                Arc::new(move |v: i32| crate::jbo_prop::sub_rel(&fresh_rel, &JboRel::BoundRVar(v), prop.clone()))
1478            )
1479        }
1480        PropTransform::SubstituteTerm(old_term, new_term) => crate::jbo_prop::sub_term(&old_term, &new_term, prop),
1481        PropTransform::LogicalConnectBranches { conn, fresh, left_term, left_transforms, right_term, right_transforms } => {
1482            let left_base = crate::jbo_prop::sub_term(&fresh, &left_term, prop.clone());
1483            let right_base = crate::jbo_prop::sub_term(&fresh, &right_term, prop);
1484            let left = apply_transform_list(left_transforms, left_base);
1485            let right = apply_transform_list(right_transforms, right_base);
1486            crate::jbo_prop::conn_to_fol(&conn, left, right)
1487        }
1488        PropTransform::ConnectBranches { conn, left_args, right_args, continuation_position, left_transforms, right_transforms, outer_transforms } => {
1489            let continuation_args = continuation_args_from_prop(&prop, continuation_position);
1490            let left_args = join_args(&continuation_args, &left_args);
1491            let right_args = join_args(&continuation_args, &right_args);
1492            let left = apply_transform_list(left_transforms, fill_args_in_prop(prop.clone(), &left_args));
1493            let right = apply_transform_list(right_transforms, fill_args_in_prop(prop, &right_args));
1494            let connected = match conn {
1495                crate::jbo_syntax::Connective::JboConnLog(_, lcon) => crate::jbo_prop::conn_to_fol(&lcon, left, right),
1496                crate::jbo_syntax::Connective::JboConnJoik(_, joik) => crate::jbo_prop::joik_to_fol(joik, left, right),
1497            };
1498            apply_transform_list(outer_transforms, connected)
1499        }
1500        PropTransform::ConnectTagBranches { conn, left_transforms, right_transforms } => {
1501            let left = apply_transform_list(left_transforms, prop.clone());
1502            let right = apply_transform_list(right_transforms, prop);
1503            match conn {
1504                crate::jbo_prop::JboConnective::JboConnLog(_, lcon) => crate::jbo_prop::conn_to_fol(&lcon, left, right),
1505                crate::jbo_prop::JboConnective::JboConnJoik(_, joik) => crate::jbo_prop::joik_to_fol(joik, left, right),
1506            }
1507        }
1508        PropTransform::Negate => Prop::Not(Box::new(prop)),
1509        PropTransform::ApplyModal(modal_op) => Prop::Modal(modal_op, Box::new(prop)),
1510    }
1511}
1512
1513impl std::fmt::Debug for PropTransform {
1514    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1515        match self {
1516            PropTransform::Quantify(term, quant, _) => write!(f, "Quantify({:?}, {:?}, <fn>)", term, quant),
1517            PropTransform::ConnectQuantify { conn, fresh, .. } => write!(f, "ConnectQuantify({:?}, {:?})", conn, fresh),
1518            PropTransform::ConnectQuantifyTagged { conn, fresh, .. } => write!(f, "ConnectQuantifyTagged({:?}, {:?})", conn, fresh),
1519            PropTransform::RQuantify(rel, quant) => write!(f, "RQuantify({:?}, {:?})", rel, quant),
1520            PropTransform::SubstituteTerm(old_term, new_term) => write!(f, "SubstituteTerm({:?}, {:?})", old_term, new_term),
1521            PropTransform::LogicalConnectBranches { conn, fresh, left_term, right_term, .. } => write!(f, "LogicalConnectBranches({:?}, {:?}, {:?}, {:?})", conn, fresh, left_term, right_term),
1522            PropTransform::ConnectBranches { conn, .. } => write!(f, "ConnectBranches({:?})", conn),
1523            PropTransform::ConnectTagBranches { conn, .. } => write!(f, "ConnectTagBranches({:?})", conn),
1524            PropTransform::Negate => write!(f, "Negate"),
1525            PropTransform::ApplyModal(op) => write!(f, "ApplyModal({:?})", op),
1526        }
1527    }
1528}