Skip to main content

xlog_logic/resolver/
extraction.rs

1#![allow(
2    missing_docs,
3    reason = "portable extraction DTOs are described by their serialized field names"
4)]
5
6use std::collections::{BTreeMap, HashSet};
7use std::fmt;
8use std::path::{Path, PathBuf};
9
10use serde::Serialize;
11use xlog_core::ScalarType;
12
13use crate::ast::{
14    AggOp, ArithExpr, Atom, BodyLiteral, CompOp, EpistemicOp, FuncBody, NeuralLabel, Program,
15    ProgramMergeReport, Term, TypeRef,
16};
17use crate::stratify::analyze_stratification;
18
19use super::{
20    ModuleResolver, ResolvedProgramManifest, ResolvedProgramManifestError, ResolvedSourceObject,
21    ResolvedSourceObjectKind,
22};
23
24const EXTRACTION_SCHEMA_VERSION: &str = "xlog.resolved-program-extraction.v1";
25
26/// Source inventory and executable dependency structure for one resolved program.
27#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
28pub struct ResolvedProgramExtraction {
29    pub schema_version: String,
30    pub source_manifest: ResolvedProgramManifest,
31    pub executable_program: ExecutableProgram,
32}
33
34/// Executable rule graph after module visibility and selection are applied.
35#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
36pub struct ExecutableProgram {
37    pub domains: Vec<ExecutableDomain>,
38    pub functions: Vec<ExecutableFunction>,
39    pub relations: Vec<ExecutableRelation>,
40    pub rules: Vec<ExecutableRule>,
41    pub constraints: Vec<ExecutableConstraint>,
42    pub queries: Vec<ExecutableQuery>,
43    pub probabilistic_facts: Vec<ExecutableProbabilisticFact>,
44    pub annotated_disjunctions: Vec<ExecutableAnnotatedDisjunction>,
45    pub evidence: Vec<ExecutableEvidence>,
46    pub probabilistic_queries: Vec<ExecutableProbabilisticQuery>,
47    pub neural_predicates: Vec<ExecutableNeuralPredicate>,
48    pub learnable_rules: Vec<ExecutableLearnableRule>,
49    pub dependencies: Vec<RelationDependency>,
50    pub sccs: Vec<ExecutableScc>,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
54#[serde(rename_all = "snake_case")]
55pub enum ExecutableScalarType {
56    U32,
57    U64,
58    I32,
59    I64,
60    F32,
61    F64,
62    Bool,
63    Symbol,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
67#[serde(tag = "kind", rename_all = "snake_case")]
68pub enum ExecutableTypeReference {
69    Scalar { scalar_type: ExecutableScalarType },
70    Domain { name: String },
71    List { element: Box<Self> },
72    Term,
73    Compound,
74    PredicateReference,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
78pub struct ExecutablePredicateColumn {
79    pub name: Option<String>,
80    pub type_reference: ExecutableTypeReference,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
84pub struct ExecutableDomain {
85    pub domain_id: String,
86    pub module_id: String,
87    pub source_object_id: String,
88    pub name: String,
89    pub scalar_type: ExecutableScalarType,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
93pub struct ExecutableFunctionParameter {
94    pub name: String,
95    pub scalar_type: Option<ExecutableScalarType>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
99#[serde(tag = "kind", rename_all = "snake_case")]
100pub enum ExecutableFunctionBody {
101    Arithmetic {
102        expression: ExecutableArithmeticExpression,
103    },
104    Conditional {
105        condition_left: ExecutableArithmeticExpression,
106        condition_operator: ComparisonOperator,
107        condition_right: ExecutableArithmeticExpression,
108        then_body: Box<Self>,
109        else_body: Box<Self>,
110    },
111    Predicate {
112        result: String,
113        body: Vec<ExecutableBodyLiteral>,
114    },
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
118pub struct ExecutableFunction {
119    pub function_id: String,
120    pub module_id: String,
121    pub source_object_id: String,
122    pub name: String,
123    pub parameters: Vec<ExecutableFunctionParameter>,
124    pub return_type: Option<ExecutableScalarType>,
125    pub body: ExecutableFunctionBody,
126    pub is_private: bool,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
130pub struct ExecutableProbability {
131    pub ieee754_bits: u64,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
135pub struct ExecutableProbabilisticFact {
136    pub probabilistic_fact_id: String,
137    pub module_id: String,
138    pub source_object_id: String,
139    pub probability: ExecutableProbability,
140    pub atom: ExecutableAtom,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
144pub struct ExecutableWeightedAtom {
145    pub probability: ExecutableProbability,
146    pub atom: ExecutableAtom,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
150pub struct ExecutableAnnotatedDisjunction {
151    pub annotated_disjunction_id: String,
152    pub module_id: String,
153    pub source_object_id: String,
154    pub choices: Vec<ExecutableWeightedAtom>,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
158pub struct ExecutableEvidence {
159    pub evidence_id: String,
160    pub module_id: String,
161    pub source_object_id: String,
162    pub atom: ExecutableAtom,
163    pub value: bool,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
167pub struct ExecutableProbabilisticQuery {
168    pub probabilistic_query_id: String,
169    pub module_id: String,
170    pub source_object_id: String,
171    pub atom: ExecutableAtom,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
175pub enum ExecutableNeuralLabel {
176    Integer { value: i64 },
177    Symbol { value: String },
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
181pub struct ExecutableNeuralPredicate {
182    pub neural_predicate_id: String,
183    pub module_id: String,
184    pub source_object_id: String,
185    pub network: String,
186    pub inputs: Vec<String>,
187    pub output: String,
188    pub labels: Option<Vec<ExecutableNeuralLabel>>,
189    pub predicate: ExecutableAtom,
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
193pub struct ExecutableLearnableRule {
194    pub learnable_rule_id: String,
195    pub module_id: String,
196    pub source_object_id: String,
197    pub mask_name: String,
198    pub head: ExecutableAtom,
199    pub body: Vec<ExecutableBodyLiteral>,
200}
201
202/// One predicate signature participating in the executable program.
203#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
204pub struct ExecutableRelation {
205    pub relation_id: String,
206    pub name: String,
207    pub arity: usize,
208    pub schema: Option<Vec<ExecutablePredicateColumn>>,
209    pub definitions: Vec<ExecutableRelationDefinition>,
210    pub declaration_source_object_ids: Vec<String>,
211    pub scc_id: Option<String>,
212    pub stratum: Option<usize>,
213    pub non_monotone: bool,
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
217pub struct ExecutableRelationDefinition {
218    pub source_object_id: String,
219    pub kind: ExecutableRelationDefinitionKind,
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
223#[serde(rename_all = "snake_case")]
224pub enum ExecutableRelationDefinitionKind {
225    Rule,
226    ProbabilisticFact,
227    AnnotatedDisjunction,
228    NeuralPredicate,
229    LearnableRule,
230}
231
232/// One source-authored executable fact or rule.
233#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
234pub struct ExecutableRule {
235    pub rule_id: String,
236    pub module_id: String,
237    pub source_object_id: String,
238    pub head: ExecutableAtom,
239    pub body: Vec<ExecutableBodyLiteral>,
240    pub scc_id: Option<String>,
241    pub stratum: Option<usize>,
242}
243
244/// One source-authored integrity constraint.
245#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
246pub struct ExecutableConstraint {
247    pub constraint_id: String,
248    pub module_id: String,
249    pub source_object_id: String,
250    pub body: Vec<ExecutableBodyLiteral>,
251}
252
253/// One source-authored deterministic query.
254#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
255pub struct ExecutableQuery {
256    pub query_id: String,
257    pub module_id: String,
258    pub source_object_id: String,
259    pub goal: ExecutableAtom,
260}
261
262/// One relation dependency contributed by a rule body literal.
263#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
264pub struct RelationDependency {
265    pub producer_id: String,
266    pub producer_kind: RelationDependencyProducerKind,
267    pub dependent_relation_id: String,
268    pub dependency_relation_id: String,
269    pub body_ordinal: usize,
270    pub kind: RelationDependencyKind,
271    pub epistemic_operator: Option<EpistemicOperator>,
272    pub epistemic_negated: bool,
273}
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
276#[serde(rename_all = "snake_case")]
277pub enum RelationDependencyProducerKind {
278    Rule,
279    LearnableRule,
280}
281
282/// Semantic kind of a relation dependency.
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
284#[serde(rename_all = "snake_case")]
285pub enum RelationDependencyKind {
286    Positive,
287    Negative,
288    Aggregate,
289    Epistemic,
290}
291
292/// One strongly connected predicate component from the production stratifier.
293#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
294pub struct ExecutableScc {
295    pub scc_id: String,
296    pub relation_ids: Vec<String>,
297    pub non_monotone: bool,
298    pub stratum: Option<usize>,
299}
300
301/// A predicate application with a stable relation identity.
302#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
303pub struct ExecutableAtom {
304    pub relation_id: String,
305    pub name: String,
306    pub terms: Vec<ExecutableTerm>,
307}
308
309/// Complete source term representation used by executable atoms.
310#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
311#[serde(tag = "kind", rename_all = "snake_case")]
312pub enum ExecutableTerm {
313    Variable {
314        name: String,
315    },
316    Anonymous,
317    Integer {
318        value: i64,
319    },
320    Float {
321        ieee754_bits: u64,
322    },
323    String {
324        value: String,
325    },
326    Symbol {
327        value: String,
328    },
329    List {
330        items: Vec<ExecutableTerm>,
331    },
332    Cons {
333        head: Box<ExecutableTerm>,
334        tail: Box<ExecutableTerm>,
335    },
336    Compound {
337        functor: String,
338        arguments: Vec<ExecutableTerm>,
339    },
340    PredicateReference {
341        name: String,
342    },
343    Aggregate {
344        operator: AggregateOperator,
345        variable: String,
346    },
347}
348
349/// Aggregate operator preserved from a rule head.
350#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
351#[serde(rename_all = "snake_case")]
352pub enum AggregateOperator {
353    Count,
354    Sum,
355    Min,
356    Max,
357    LogSumExp,
358}
359
360/// Complete executable rule-body literal.
361#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
362#[serde(tag = "kind", rename_all = "snake_case")]
363pub enum ExecutableBodyLiteral {
364    Positive {
365        atom: ExecutableAtom,
366    },
367    Negative {
368        atom: ExecutableAtom,
369    },
370    Epistemic {
371        operator: EpistemicOperator,
372        negated: bool,
373        atom: ExecutableAtom,
374    },
375    Comparison {
376        left: ExecutableTerm,
377        operator: ComparisonOperator,
378        right: ExecutableTerm,
379    },
380    IsExpression {
381        target: String,
382        expression: ExecutableArithmeticExpression,
383    },
384    Univ {
385        term: ExecutableTerm,
386        parts: ExecutableTerm,
387    },
388}
389
390/// Epistemic operator preserved from the source program.
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
392#[serde(rename_all = "snake_case")]
393pub enum EpistemicOperator {
394    Know,
395    Possible,
396}
397
398/// Comparison operator preserved from a body literal.
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
400#[serde(rename_all = "snake_case")]
401pub enum ComparisonOperator {
402    Equal,
403    NotEqual,
404    LessThan,
405    LessThanOrEqual,
406    GreaterThan,
407    GreaterThanOrEqual,
408}
409
410/// Arithmetic expression preserved without string rendering.
411#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
412#[serde(tag = "kind", rename_all = "snake_case")]
413pub enum ExecutableArithmeticExpression {
414    Variable {
415        name: String,
416    },
417    Integer {
418        value: i64,
419    },
420    Float {
421        ieee754_bits: u64,
422    },
423    Add {
424        left: Box<Self>,
425        right: Box<Self>,
426    },
427    Subtract {
428        left: Box<Self>,
429        right: Box<Self>,
430    },
431    Multiply {
432        left: Box<Self>,
433        right: Box<Self>,
434    },
435    Divide {
436        left: Box<Self>,
437        right: Box<Self>,
438    },
439    Modulo {
440        left: Box<Self>,
441        right: Box<Self>,
442    },
443    AbsoluteValue {
444        value: Box<Self>,
445    },
446    Minimum {
447        left: Box<Self>,
448        right: Box<Self>,
449    },
450    Maximum {
451        left: Box<Self>,
452        right: Box<Self>,
453    },
454    Power {
455        base: Box<Self>,
456        exponent: Box<Self>,
457    },
458    Cast {
459        value: Box<Self>,
460        scalar_type: ExecutableScalarType,
461    },
462    FunctionCall {
463        name: String,
464        arguments: Vec<Self>,
465    },
466    Conditional {
467        condition_left: Box<Self>,
468        condition_operator: ComparisonOperator,
469        condition_right: Box<Self>,
470        then_expression: Box<Self>,
471        else_expression: Box<Self>,
472    },
473}
474
475/// Failure while constructing the structured executable program.
476#[derive(Debug)]
477pub enum ResolvedProgramExtractionError {
478    Manifest(ResolvedProgramManifestError),
479    ModuleValidation {
480        message: String,
481    },
482    MissingEntry,
483    MissingModule {
484        path: PathBuf,
485    },
486    MissingSourceObject {
487        module_id: String,
488        kind: ResolvedSourceObjectKind,
489        ordinal: usize,
490    },
491    RelationArityConflict {
492        name: String,
493        first: usize,
494        second: usize,
495    },
496    MissingRelationArity {
497        name: String,
498    },
499}
500
501impl fmt::Display for ResolvedProgramExtractionError {
502    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
503        match self {
504            Self::Manifest(error) => write!(formatter, "{error}"),
505            Self::ModuleValidation { message } => {
506                write!(formatter, "resolved module validation failed: {message}")
507            }
508            Self::MissingEntry => write!(formatter, "resolver has no loaded entry program"),
509            Self::MissingModule { path } => write!(
510                formatter,
511                "resolved source {} is absent from the source manifest",
512                path.display()
513            ),
514            Self::MissingSourceObject {
515                module_id,
516                kind,
517                ordinal,
518            } => write!(
519                formatter,
520                "module {module_id} has no {kind:?} source object at ordinal {ordinal}"
521            ),
522            Self::RelationArityConflict {
523                name,
524                first,
525                second,
526            } => write!(
527                formatter,
528                "relation {name} appears with incompatible arities {first} and {second}"
529            ),
530            Self::MissingRelationArity { name } => {
531                write!(formatter, "relation {name} has no recoverable arity")
532            }
533        }
534    }
535}
536
537impl std::error::Error for ResolvedProgramExtractionError {}
538
539impl From<ResolvedProgramManifestError> for ResolvedProgramExtractionError {
540    fn from(error: ResolvedProgramManifestError) -> Self {
541        Self::Manifest(error)
542    }
543}
544
545#[derive(Debug, Clone)]
546struct AstOrigin {
547    module_id: String,
548    source_object_id: String,
549}
550
551struct MergedProgram {
552    program: Program,
553    domain_origins: Vec<AstOrigin>,
554    function_origins: Vec<AstOrigin>,
555    predicate_origins: Vec<AstOrigin>,
556    rule_origins: Vec<AstOrigin>,
557    constraint_origins: Vec<AstOrigin>,
558    query_origins: Vec<AstOrigin>,
559    probabilistic_fact_origins: Vec<AstOrigin>,
560    annotated_disjunction_origins: Vec<AstOrigin>,
561    evidence_origins: Vec<AstOrigin>,
562    probabilistic_query_origins: Vec<AstOrigin>,
563    neural_predicate_origins: Vec<AstOrigin>,
564    learnable_rule_origins: Vec<AstOrigin>,
565}
566
567struct OriginCatalog {
568    modules: BTreeMap<PathBuf, OriginModule>,
569}
570
571struct OriginModule {
572    module_id: String,
573    objects: BTreeMap<ResolvedSourceObjectKind, Vec<ResolvedSourceObject>>,
574}
575
576impl OriginCatalog {
577    fn new(
578        source_root: &Path,
579        manifest: &ResolvedProgramManifest,
580    ) -> Result<Self, ResolvedProgramExtractionError> {
581        let source_root = std::fs::canonicalize(source_root).map_err(|error| {
582            ResolvedProgramExtractionError::ModuleValidation {
583                message: format!("cannot canonicalize source root: {error}"),
584            }
585        })?;
586        let mut modules = BTreeMap::new();
587        for module in &manifest.modules {
588            let source =
589                std::fs::canonicalize(source_root.join(&module.source_path)).map_err(|error| {
590                    ResolvedProgramExtractionError::ModuleValidation {
591                        message: format!(
592                            "cannot canonicalize resolved source {}: {error}",
593                            module.source_path
594                        ),
595                    }
596                })?;
597            let mut objects = BTreeMap::<ResolvedSourceObjectKind, Vec<_>>::new();
598            for object in &module.source_objects {
599                objects.entry(object.kind).or_default().push(object.clone());
600            }
601            modules.insert(
602                source,
603                OriginModule {
604                    module_id: module.module_id.clone(),
605                    objects,
606                },
607            );
608        }
609        Ok(Self { modules })
610    }
611
612    fn origin(
613        &self,
614        source: &Path,
615        kind: ResolvedSourceObjectKind,
616        ordinal: usize,
617    ) -> Result<AstOrigin, ResolvedProgramExtractionError> {
618        let module = self.modules.get(source).ok_or_else(|| {
619            ResolvedProgramExtractionError::MissingModule {
620                path: source.to_path_buf(),
621            }
622        })?;
623        let object = module
624            .objects
625            .get(&kind)
626            .and_then(|objects| objects.get(ordinal))
627            .ok_or_else(|| ResolvedProgramExtractionError::MissingSourceObject {
628                module_id: module.module_id.clone(),
629                kind,
630                ordinal,
631            })?;
632        Ok(AstOrigin {
633            module_id: module.module_id.clone(),
634            source_object_id: object.object_id.clone(),
635        })
636    }
637}
638
639impl ModuleResolver {
640    /// Extract the exact executable program selected by this resolved entry closure.
641    pub fn resolved_program_extraction(
642        &self,
643        source_root: &Path,
644    ) -> Result<ResolvedProgramExtraction, ResolvedProgramExtractionError> {
645        let source_manifest = self.resolved_program_manifest(source_root)?;
646        let catalog = OriginCatalog::new(source_root, &source_manifest)?;
647        let merged = self.merge_program_with_origins(&catalog)?;
648        let executable_program = build_executable_program(merged)?;
649        Ok(ResolvedProgramExtraction {
650            schema_version: EXTRACTION_SCHEMA_VERSION.to_string(),
651            source_manifest,
652            executable_program,
653        })
654    }
655
656    fn merge_program_with_origins(
657        &self,
658        catalog: &OriginCatalog,
659    ) -> Result<MergedProgram, ResolvedProgramExtractionError> {
660        let entry = self
661            .entry
662            .as_ref()
663            .ok_or(ResolvedProgramExtractionError::MissingEntry)?;
664        let entry_source = self
665            .entry_source
666            .as_ref()
667            .ok_or(ResolvedProgramExtractionError::MissingEntry)?;
668        let mut program = entry.program.clone();
669        let imports = self
670            .resolved_imports_for_program(&program)
671            .map_err(|error| ResolvedProgramExtractionError::ModuleValidation {
672                message: error.to_string(),
673            })?;
674        self.validate_resolved_imports(&imports).map_err(|error| {
675            ResolvedProgramExtractionError::ModuleValidation {
676                message: error.to_string(),
677            }
678        })?;
679        self.validate_program_against_imports(&program, &imports)
680            .map_err(|error| ResolvedProgramExtractionError::ModuleValidation {
681                message: error.to_string(),
682            })?;
683
684        let entry_rules = std::mem::take(&mut program.rules);
685        let mut domain_origins = origins_for_count(
686            catalog,
687            entry_source,
688            ResolvedSourceObjectKind::Domain,
689            program.domains.len(),
690        )?;
691        let mut function_origins = origins_for_count(
692            catalog,
693            entry_source,
694            ResolvedSourceObjectKind::Function,
695            program.functions.len(),
696        )?;
697        let mut predicate_origins = origins_for_count(
698            catalog,
699            entry_source,
700            ResolvedSourceObjectKind::Predicate,
701            program.predicates.len(),
702        )?;
703        let constraint_origins = origins_for_count(
704            catalog,
705            entry_source,
706            ResolvedSourceObjectKind::Constraint,
707            program.constraints.len(),
708        )?;
709        let query_origins = origins_for_count(
710            catalog,
711            entry_source,
712            ResolvedSourceObjectKind::Query,
713            program.queries.len(),
714        )?;
715        let probabilistic_fact_origins = origins_for_count(
716            catalog,
717            entry_source,
718            ResolvedSourceObjectKind::ProbabilisticFact,
719            program.prob_facts.len(),
720        )?;
721        let annotated_disjunction_origins = origins_for_count(
722            catalog,
723            entry_source,
724            ResolvedSourceObjectKind::AnnotatedDisjunction,
725            program.annotated_disjunctions.len(),
726        )?;
727        let evidence_origins = origins_for_count(
728            catalog,
729            entry_source,
730            ResolvedSourceObjectKind::Evidence,
731            program.evidence.len(),
732        )?;
733        let probabilistic_query_origins = origins_for_count(
734            catalog,
735            entry_source,
736            ResolvedSourceObjectKind::ProbabilisticQuery,
737            program.prob_queries.len(),
738        )?;
739        let neural_predicate_origins = origins_for_count(
740            catalog,
741            entry_source,
742            ResolvedSourceObjectKind::NeuralPredicate,
743            program.neural_predicates.len(),
744        )?;
745        let learnable_rule_origins = origins_for_count(
746            catalog,
747            entry_source,
748            ResolvedSourceObjectKind::LearnableRule,
749            program.learnable_rules.len(),
750        )?;
751
752        let mut merge_reports = Vec::<(PathBuf, ProgramMergeReport)>::new();
753        let mut merged_imports = HashSet::new();
754        self.merge_import_group_with_report(
755            &mut program,
756            &imports,
757            &mut merged_imports,
758            &mut |source, report| merge_reports.push((source.to_path_buf(), report.clone())),
759        )
760        .map_err(|error| ResolvedProgramExtractionError::ModuleValidation {
761            message: error.to_string(),
762        })?;
763
764        let mut rule_origins = Vec::new();
765        for (source, report) in merge_reports {
766            for ordinal in report.domains {
767                domain_origins.push(catalog.origin(
768                    &source,
769                    ResolvedSourceObjectKind::Domain,
770                    ordinal,
771                )?);
772            }
773            for ordinal in report.functions {
774                function_origins.push(catalog.origin(
775                    &source,
776                    ResolvedSourceObjectKind::Function,
777                    ordinal,
778                )?);
779            }
780            for ordinal in report.predicates {
781                predicate_origins.push(catalog.origin(
782                    &source,
783                    ResolvedSourceObjectKind::Predicate,
784                    ordinal,
785                )?);
786            }
787            for ordinal in report.rules {
788                rule_origins.push(catalog.origin(
789                    &source,
790                    ResolvedSourceObjectKind::Rule,
791                    ordinal,
792                )?);
793            }
794        }
795        program.rules.extend(entry_rules);
796        rule_origins.extend(origins_for_count(
797            catalog,
798            entry_source,
799            ResolvedSourceObjectKind::Rule,
800            entry.program.rules.len(),
801        )?);
802
803        Ok(MergedProgram {
804            program,
805            domain_origins,
806            function_origins,
807            predicate_origins,
808            rule_origins,
809            constraint_origins,
810            query_origins,
811            probabilistic_fact_origins,
812            annotated_disjunction_origins,
813            evidence_origins,
814            probabilistic_query_origins,
815            neural_predicate_origins,
816            learnable_rule_origins,
817        })
818    }
819}
820
821fn origins_for_count(
822    catalog: &OriginCatalog,
823    source: &Path,
824    kind: ResolvedSourceObjectKind,
825    count: usize,
826) -> Result<Vec<AstOrigin>, ResolvedProgramExtractionError> {
827    (0..count)
828        .map(|ordinal| catalog.origin(source, kind, ordinal))
829        .collect()
830}
831
832fn build_executable_program(
833    merged: MergedProgram,
834) -> Result<ExecutableProgram, ResolvedProgramExtractionError> {
835    let analysis = analyze_stratification(&merged.program);
836    let mut scc_by_predicate = BTreeMap::new();
837    let mut sccs = Vec::with_capacity(analysis.sccs.len());
838    for (index, predicates) in analysis.sccs.iter().enumerate() {
839        let scc_id = format!("scc:{index}");
840        let non_monotone = analysis.non_monotone_sccs.contains(&index);
841        let stratum = predicates
842            .iter()
843            .filter_map(|predicate| analysis.strata.get(predicate).copied())
844            .next();
845        let mut relation_ids = predicates
846            .iter()
847            .map(|predicate| {
848                relation_arity(&merged.program, predicate)
849                    .map(|arity| relation_id(predicate, arity))
850            })
851            .collect::<Result<Vec<_>, _>>()?;
852        relation_ids.sort();
853        for predicate in predicates {
854            scc_by_predicate.insert(predicate.clone(), (scc_id.clone(), stratum, non_monotone));
855        }
856        sccs.push(ExecutableScc {
857            scc_id,
858            relation_ids,
859            non_monotone,
860            stratum,
861        });
862    }
863
864    let mut rules = Vec::with_capacity(merged.program.rules.len());
865    let mut dependencies = Vec::new();
866    for (rule, origin) in merged.program.rules.iter().zip(&merged.rule_origins) {
867        let head = executable_atom(&rule.head);
868        let (scc_id, stratum, _) = scc_by_predicate
869            .get(&rule.head.predicate)
870            .cloned()
871            .map_or((None, None, false), |(scc_id, stratum, non_monotone)| {
872                (Some(scc_id), stratum, non_monotone)
873            });
874        let rule_id = origin.source_object_id.clone();
875        for (body_ordinal, literal) in rule.body.iter().enumerate() {
876            if let Some(dependency) = relation_dependency(
877                &rule_id,
878                RelationDependencyProducerKind::Rule,
879                &head.relation_id,
880                body_ordinal,
881                rule.has_aggregation(),
882                literal,
883            ) {
884                dependencies.push(dependency);
885            }
886        }
887        rules.push(ExecutableRule {
888            rule_id,
889            module_id: origin.module_id.clone(),
890            source_object_id: origin.source_object_id.clone(),
891            head,
892            body: rule.body.iter().map(executable_literal).collect(),
893            scc_id,
894            stratum,
895        });
896    }
897
898    let constraints = merged
899        .program
900        .constraints
901        .iter()
902        .zip(&merged.constraint_origins)
903        .map(|(constraint, origin)| ExecutableConstraint {
904            constraint_id: origin.source_object_id.clone(),
905            module_id: origin.module_id.clone(),
906            source_object_id: origin.source_object_id.clone(),
907            body: constraint.body.iter().map(executable_literal).collect(),
908        })
909        .collect();
910    let queries = merged
911        .program
912        .queries
913        .iter()
914        .zip(&merged.query_origins)
915        .map(|(query, origin)| ExecutableQuery {
916            query_id: origin.source_object_id.clone(),
917            module_id: origin.module_id.clone(),
918            source_object_id: origin.source_object_id.clone(),
919            goal: executable_atom(&query.atom),
920        })
921        .collect();
922    let domains = merged
923        .program
924        .domains
925        .iter()
926        .zip(&merged.domain_origins)
927        .map(|(domain, origin)| ExecutableDomain {
928            domain_id: origin.source_object_id.clone(),
929            module_id: origin.module_id.clone(),
930            source_object_id: origin.source_object_id.clone(),
931            name: domain.name.clone(),
932            scalar_type: executable_scalar_type(domain.typ),
933        })
934        .collect();
935    let functions = merged
936        .program
937        .functions
938        .iter()
939        .zip(&merged.function_origins)
940        .map(|(function, origin)| ExecutableFunction {
941            function_id: origin.source_object_id.clone(),
942            module_id: origin.module_id.clone(),
943            source_object_id: origin.source_object_id.clone(),
944            name: function.name.clone(),
945            parameters: function
946                .params
947                .iter()
948                .map(|parameter| ExecutableFunctionParameter {
949                    name: parameter.name.clone(),
950                    scalar_type: parameter.typ.map(executable_scalar_type),
951                })
952                .collect(),
953            return_type: function.return_type.map(executable_scalar_type),
954            body: executable_function_body(&function.body),
955            is_private: function.is_private,
956        })
957        .collect();
958    let probabilistic_facts = merged
959        .program
960        .prob_facts
961        .iter()
962        .zip(&merged.probabilistic_fact_origins)
963        .map(|(fact, origin)| ExecutableProbabilisticFact {
964            probabilistic_fact_id: origin.source_object_id.clone(),
965            module_id: origin.module_id.clone(),
966            source_object_id: origin.source_object_id.clone(),
967            probability: executable_probability(fact.prob),
968            atom: executable_atom(&fact.atom),
969        })
970        .collect();
971    let annotated_disjunctions = merged
972        .program
973        .annotated_disjunctions
974        .iter()
975        .zip(&merged.annotated_disjunction_origins)
976        .map(|(disjunction, origin)| ExecutableAnnotatedDisjunction {
977            annotated_disjunction_id: origin.source_object_id.clone(),
978            module_id: origin.module_id.clone(),
979            source_object_id: origin.source_object_id.clone(),
980            choices: disjunction
981                .choices
982                .iter()
983                .map(|choice| ExecutableWeightedAtom {
984                    probability: executable_probability(choice.prob),
985                    atom: executable_atom(&choice.atom),
986                })
987                .collect(),
988        })
989        .collect();
990    let evidence = merged
991        .program
992        .evidence
993        .iter()
994        .zip(&merged.evidence_origins)
995        .map(|(evidence, origin)| ExecutableEvidence {
996            evidence_id: origin.source_object_id.clone(),
997            module_id: origin.module_id.clone(),
998            source_object_id: origin.source_object_id.clone(),
999            atom: executable_atom(&evidence.atom),
1000            value: evidence.value,
1001        })
1002        .collect();
1003    let probabilistic_queries = merged
1004        .program
1005        .prob_queries
1006        .iter()
1007        .zip(&merged.probabilistic_query_origins)
1008        .map(|(query, origin)| ExecutableProbabilisticQuery {
1009            probabilistic_query_id: origin.source_object_id.clone(),
1010            module_id: origin.module_id.clone(),
1011            source_object_id: origin.source_object_id.clone(),
1012            atom: executable_atom(&query.atom),
1013        })
1014        .collect();
1015    let neural_predicates = merged
1016        .program
1017        .neural_predicates
1018        .iter()
1019        .zip(&merged.neural_predicate_origins)
1020        .map(|(declaration, origin)| ExecutableNeuralPredicate {
1021            neural_predicate_id: origin.source_object_id.clone(),
1022            module_id: origin.module_id.clone(),
1023            source_object_id: origin.source_object_id.clone(),
1024            network: declaration.network.clone(),
1025            inputs: declaration.inputs.clone(),
1026            output: declaration.output.clone(),
1027            labels: declaration
1028                .labels
1029                .as_ref()
1030                .map(|labels| labels.iter().map(executable_neural_label).collect()),
1031            predicate: executable_atom(&declaration.predicate),
1032        })
1033        .collect();
1034    let mut learnable_rules = Vec::with_capacity(merged.program.learnable_rules.len());
1035    for (rule, origin) in merged
1036        .program
1037        .learnable_rules
1038        .iter()
1039        .zip(&merged.learnable_rule_origins)
1040    {
1041        let rule_id = origin.source_object_id.clone();
1042        let head = executable_atom(&rule.head);
1043        let has_aggregation = rule
1044            .head
1045            .terms
1046            .iter()
1047            .any(|term| matches!(term, Term::Aggregate { .. }));
1048        for (body_ordinal, literal) in rule.body.iter().enumerate() {
1049            if let Some(dependency) = relation_dependency(
1050                &rule_id,
1051                RelationDependencyProducerKind::LearnableRule,
1052                &head.relation_id,
1053                body_ordinal,
1054                has_aggregation,
1055                literal,
1056            ) {
1057                dependencies.push(dependency);
1058            }
1059        }
1060        learnable_rules.push(ExecutableLearnableRule {
1061            learnable_rule_id: rule_id,
1062            module_id: origin.module_id.clone(),
1063            source_object_id: origin.source_object_id.clone(),
1064            mask_name: rule.mask_name.clone(),
1065            head,
1066            body: rule.body.iter().map(executable_literal).collect(),
1067        });
1068    }
1069    let relations = build_relations(&merged, &rules, &scc_by_predicate)?;
1070
1071    Ok(ExecutableProgram {
1072        domains,
1073        functions,
1074        relations,
1075        rules,
1076        constraints,
1077        queries,
1078        probabilistic_facts,
1079        annotated_disjunctions,
1080        evidence,
1081        probabilistic_queries,
1082        neural_predicates,
1083        learnable_rules,
1084        dependencies,
1085        sccs,
1086    })
1087}
1088
1089fn build_relations(
1090    merged: &MergedProgram,
1091    rules: &[ExecutableRule],
1092    scc_by_predicate: &BTreeMap<String, (String, Option<usize>, bool)>,
1093) -> Result<Vec<ExecutableRelation>, ResolvedProgramExtractionError> {
1094    let mut arities = BTreeMap::<String, usize>::new();
1095    for declaration in &merged.program.predicates {
1096        register_arity(
1097            &mut arities,
1098            &declaration.name,
1099            declaration.schema_columns().len(),
1100        )?;
1101    }
1102    for rule in &merged.program.rules {
1103        register_atom_arities(&mut arities, &rule.head)?;
1104        for literal in &rule.body {
1105            if let Some(atom) = literal.atom() {
1106                register_atom_arities(&mut arities, atom)?;
1107            } else if let BodyLiteral::Epistemic(literal) = literal {
1108                register_atom_arities(&mut arities, &literal.atom)?;
1109            }
1110        }
1111    }
1112    for constraint in &merged.program.constraints {
1113        for literal in &constraint.body {
1114            if let Some(atom) = literal.atom() {
1115                register_atom_arities(&mut arities, atom)?;
1116            } else if let BodyLiteral::Epistemic(literal) = literal {
1117                register_atom_arities(&mut arities, &literal.atom)?;
1118            }
1119        }
1120    }
1121    for query in &merged.program.queries {
1122        register_atom_arities(&mut arities, &query.atom)?;
1123    }
1124    for fact in &merged.program.prob_facts {
1125        register_atom_arities(&mut arities, &fact.atom)?;
1126    }
1127    for disjunction in &merged.program.annotated_disjunctions {
1128        for choice in &disjunction.choices {
1129            register_atom_arities(&mut arities, &choice.atom)?;
1130        }
1131    }
1132    for evidence in &merged.program.evidence {
1133        register_atom_arities(&mut arities, &evidence.atom)?;
1134    }
1135    for query in &merged.program.prob_queries {
1136        register_atom_arities(&mut arities, &query.atom)?;
1137    }
1138    for declaration in &merged.program.neural_predicates {
1139        register_atom_arities(&mut arities, &declaration.predicate)?;
1140    }
1141    for rule in &merged.program.learnable_rules {
1142        register_atom_arities(&mut arities, &rule.head)?;
1143        for literal in &rule.body {
1144            if let Some(atom) = literal.atom() {
1145                register_atom_arities(&mut arities, atom)?;
1146            } else if let BodyLiteral::Epistemic(literal) = literal {
1147                register_atom_arities(&mut arities, &literal.atom)?;
1148            }
1149        }
1150    }
1151
1152    let mut definitions = BTreeMap::<String, Vec<ExecutableRelationDefinition>>::new();
1153    for rule in rules {
1154        definitions
1155            .entry(rule.head.name.clone())
1156            .or_default()
1157            .push(ExecutableRelationDefinition {
1158                source_object_id: rule.rule_id.clone(),
1159                kind: ExecutableRelationDefinitionKind::Rule,
1160            });
1161    }
1162    for (fact, origin) in merged
1163        .program
1164        .prob_facts
1165        .iter()
1166        .zip(&merged.probabilistic_fact_origins)
1167    {
1168        definitions
1169            .entry(fact.atom.predicate.clone())
1170            .or_default()
1171            .push(ExecutableRelationDefinition {
1172                source_object_id: origin.source_object_id.clone(),
1173                kind: ExecutableRelationDefinitionKind::ProbabilisticFact,
1174            });
1175    }
1176    for (disjunction, origin) in merged
1177        .program
1178        .annotated_disjunctions
1179        .iter()
1180        .zip(&merged.annotated_disjunction_origins)
1181    {
1182        for choice in &disjunction.choices {
1183            definitions
1184                .entry(choice.atom.predicate.clone())
1185                .or_default()
1186                .push(ExecutableRelationDefinition {
1187                    source_object_id: origin.source_object_id.clone(),
1188                    kind: ExecutableRelationDefinitionKind::AnnotatedDisjunction,
1189                });
1190        }
1191    }
1192    for (declaration, origin) in merged
1193        .program
1194        .neural_predicates
1195        .iter()
1196        .zip(&merged.neural_predicate_origins)
1197    {
1198        definitions
1199            .entry(declaration.predicate.predicate.clone())
1200            .or_default()
1201            .push(ExecutableRelationDefinition {
1202                source_object_id: origin.source_object_id.clone(),
1203                kind: ExecutableRelationDefinitionKind::NeuralPredicate,
1204            });
1205    }
1206    for (rule, origin) in merged
1207        .program
1208        .learnable_rules
1209        .iter()
1210        .zip(&merged.learnable_rule_origins)
1211    {
1212        definitions
1213            .entry(rule.head.predicate.clone())
1214            .or_default()
1215            .push(ExecutableRelationDefinition {
1216                source_object_id: origin.source_object_id.clone(),
1217                kind: ExecutableRelationDefinitionKind::LearnableRule,
1218            });
1219    }
1220    let mut declarations = BTreeMap::<String, Vec<String>>::new();
1221    let mut schemas = BTreeMap::<String, Vec<ExecutablePredicateColumn>>::new();
1222    for (declaration, origin) in merged
1223        .program
1224        .predicates
1225        .iter()
1226        .zip(&merged.predicate_origins)
1227    {
1228        declarations
1229            .entry(declaration.name.clone())
1230            .or_default()
1231            .push(origin.source_object_id.clone());
1232        schemas.entry(declaration.name.clone()).or_insert_with(|| {
1233            declaration
1234                .schema_columns()
1235                .iter()
1236                .map(|column| ExecutablePredicateColumn {
1237                    name: column.name.clone(),
1238                    type_reference: executable_type_reference(&column.typ),
1239                })
1240                .collect()
1241        });
1242    }
1243
1244    Ok(arities
1245        .into_iter()
1246        .map(|(name, arity)| {
1247            let relation_id = relation_id(&name, arity);
1248            let (scc_id, stratum, non_monotone) = scc_by_predicate
1249                .get(&name)
1250                .cloned()
1251                .map_or((None, None, false), |(id, stratum, non_monotone)| {
1252                    (Some(id), stratum, non_monotone)
1253                });
1254            ExecutableRelation {
1255                relation_id,
1256                name: name.clone(),
1257                arity,
1258                schema: schemas.remove(&name),
1259                definitions: definitions.remove(&name).unwrap_or_default(),
1260                declaration_source_object_ids: declarations.remove(&name).unwrap_or_default(),
1261                scc_id,
1262                stratum,
1263                non_monotone,
1264            }
1265        })
1266        .collect())
1267}
1268
1269fn register_atom_arities(
1270    arities: &mut BTreeMap<String, usize>,
1271    atom: &Atom,
1272) -> Result<(), ResolvedProgramExtractionError> {
1273    register_arity(arities, &atom.predicate, atom.terms.len())
1274}
1275
1276fn register_arity(
1277    arities: &mut BTreeMap<String, usize>,
1278    name: &str,
1279    arity: usize,
1280) -> Result<(), ResolvedProgramExtractionError> {
1281    if let Some(existing) = arities.insert(name.to_string(), arity) {
1282        if existing != arity {
1283            return Err(ResolvedProgramExtractionError::RelationArityConflict {
1284                name: name.to_string(),
1285                first: existing,
1286                second: arity,
1287            });
1288        }
1289    }
1290    Ok(())
1291}
1292
1293fn relation_arity(
1294    program: &Program,
1295    predicate: &str,
1296) -> Result<usize, ResolvedProgramExtractionError> {
1297    let defined_arity = program
1298        .rules
1299        .iter()
1300        .map(|rule| &rule.head)
1301        .chain(program.prob_facts.iter().map(|fact| &fact.atom))
1302        .chain(
1303            program
1304                .annotated_disjunctions
1305                .iter()
1306                .flat_map(|disjunction| disjunction.choices.iter().map(|choice| &choice.atom)),
1307        )
1308        .chain(
1309            program
1310                .neural_predicates
1311                .iter()
1312                .map(|declaration| &declaration.predicate),
1313        )
1314        .chain(program.learnable_rules.iter().map(|rule| &rule.head))
1315        .find(|atom| atom.predicate == predicate)
1316        .map(|atom| atom.terms.len());
1317    let declared_arity = program
1318        .predicates
1319        .iter()
1320        .find(|declaration| declaration.name == predicate)
1321        .map(|declaration| declaration.schema_columns().len());
1322    let referenced_arity = program
1323        .rules
1324        .iter()
1325        .flat_map(|rule| &rule.body)
1326        .chain(
1327            program
1328                .constraints
1329                .iter()
1330                .flat_map(|constraint| &constraint.body),
1331        )
1332        .chain(program.learnable_rules.iter().flat_map(|rule| &rule.body))
1333        .find_map(|literal| literal_atom_arity(literal, predicate));
1334    let observed_arity = program
1335        .queries
1336        .iter()
1337        .map(|query| &query.atom)
1338        .chain(program.evidence.iter().map(|evidence| &evidence.atom))
1339        .chain(program.prob_queries.iter().map(|query| &query.atom))
1340        .find(|atom| atom.predicate == predicate)
1341        .map(|atom| atom.terms.len());
1342
1343    defined_arity
1344        .or(declared_arity)
1345        .or(referenced_arity)
1346        .or(observed_arity)
1347        .ok_or_else(|| ResolvedProgramExtractionError::MissingRelationArity {
1348            name: predicate.to_string(),
1349        })
1350}
1351
1352fn literal_atom_arity(literal: &BodyLiteral, predicate: &str) -> Option<usize> {
1353    match literal {
1354        BodyLiteral::Positive(atom) | BodyLiteral::Negated(atom) if atom.predicate == predicate => {
1355            Some(atom.terms.len())
1356        }
1357        BodyLiteral::Epistemic(literal) if literal.atom.predicate == predicate => {
1358            Some(literal.atom.terms.len())
1359        }
1360        _ => None,
1361    }
1362}
1363
1364fn relation_dependency(
1365    producer_id: &str,
1366    producer_kind: RelationDependencyProducerKind,
1367    dependent_relation_id: &str,
1368    body_ordinal: usize,
1369    aggregate_head: bool,
1370    literal: &BodyLiteral,
1371) -> Option<RelationDependency> {
1372    let (atom, kind, epistemic_operator, epistemic_negated) = match literal {
1373        BodyLiteral::Positive(atom) => (
1374            atom,
1375            if aggregate_head {
1376                RelationDependencyKind::Aggregate
1377            } else {
1378                RelationDependencyKind::Positive
1379            },
1380            None,
1381            false,
1382        ),
1383        BodyLiteral::Negated(atom) => (atom, RelationDependencyKind::Negative, None, false),
1384        BodyLiteral::Epistemic(literal) => (
1385            &literal.atom,
1386            RelationDependencyKind::Epistemic,
1387            Some(epistemic_operator(literal.op)),
1388            literal.negated,
1389        ),
1390        BodyLiteral::Comparison(_) | BodyLiteral::IsExpr(_) | BodyLiteral::Univ(_) => {
1391            return None;
1392        }
1393    };
1394    Some(RelationDependency {
1395        producer_id: producer_id.to_string(),
1396        producer_kind,
1397        dependent_relation_id: dependent_relation_id.to_string(),
1398        dependency_relation_id: relation_id(&atom.predicate, atom.terms.len()),
1399        body_ordinal,
1400        kind,
1401        epistemic_operator,
1402        epistemic_negated,
1403    })
1404}
1405
1406fn executable_literal(literal: &BodyLiteral) -> ExecutableBodyLiteral {
1407    match literal {
1408        BodyLiteral::Positive(atom) => ExecutableBodyLiteral::Positive {
1409            atom: executable_atom(atom),
1410        },
1411        BodyLiteral::Negated(atom) => ExecutableBodyLiteral::Negative {
1412            atom: executable_atom(atom),
1413        },
1414        BodyLiteral::Epistemic(literal) => ExecutableBodyLiteral::Epistemic {
1415            operator: epistemic_operator(literal.op),
1416            negated: literal.negated,
1417            atom: executable_atom(&literal.atom),
1418        },
1419        BodyLiteral::Comparison(comparison) => ExecutableBodyLiteral::Comparison {
1420            left: executable_term(&comparison.left),
1421            operator: comparison_operator(comparison.op),
1422            right: executable_term(&comparison.right),
1423        },
1424        BodyLiteral::IsExpr(expression) => ExecutableBodyLiteral::IsExpression {
1425            target: expression.target.clone(),
1426            expression: executable_arithmetic_expression(&expression.expr),
1427        },
1428        BodyLiteral::Univ(univ) => ExecutableBodyLiteral::Univ {
1429            term: executable_term(&univ.term),
1430            parts: executable_term(&univ.parts),
1431        },
1432    }
1433}
1434
1435fn executable_scalar_type(scalar_type: ScalarType) -> ExecutableScalarType {
1436    match scalar_type {
1437        ScalarType::U32 => ExecutableScalarType::U32,
1438        ScalarType::U64 => ExecutableScalarType::U64,
1439        ScalarType::I32 => ExecutableScalarType::I32,
1440        ScalarType::I64 => ExecutableScalarType::I64,
1441        ScalarType::F32 => ExecutableScalarType::F32,
1442        ScalarType::F64 => ExecutableScalarType::F64,
1443        ScalarType::Bool => ExecutableScalarType::Bool,
1444        ScalarType::Symbol => ExecutableScalarType::Symbol,
1445    }
1446}
1447
1448fn executable_type_reference(type_reference: &TypeRef) -> ExecutableTypeReference {
1449    match type_reference {
1450        TypeRef::Scalar(scalar_type) => ExecutableTypeReference::Scalar {
1451            scalar_type: executable_scalar_type(*scalar_type),
1452        },
1453        TypeRef::Domain(name) => ExecutableTypeReference::Domain { name: name.clone() },
1454        TypeRef::List(element) => ExecutableTypeReference::List {
1455            element: Box::new(executable_type_reference(element)),
1456        },
1457        TypeRef::Term => ExecutableTypeReference::Term,
1458        TypeRef::Compound => ExecutableTypeReference::Compound,
1459        TypeRef::PredRef => ExecutableTypeReference::PredicateReference,
1460    }
1461}
1462
1463fn executable_function_body(body: &FuncBody) -> ExecutableFunctionBody {
1464    match body {
1465        FuncBody::Arithmetic(expression) => ExecutableFunctionBody::Arithmetic {
1466            expression: executable_arithmetic_expression(expression),
1467        },
1468        FuncBody::Conditional(conditional) => ExecutableFunctionBody::Conditional {
1469            condition_left: executable_arithmetic_expression(&conditional.cond_left),
1470            condition_operator: comparison_operator(conditional.cond_op),
1471            condition_right: executable_arithmetic_expression(&conditional.cond_right),
1472            then_body: Box::new(executable_function_body(&conditional.then_branch)),
1473            else_body: Box::new(executable_function_body(&conditional.else_branch)),
1474        },
1475        FuncBody::Predicate { result, body } => ExecutableFunctionBody::Predicate {
1476            result: result.clone(),
1477            body: body.iter().map(executable_literal).collect(),
1478        },
1479    }
1480}
1481
1482fn executable_atom(atom: &Atom) -> ExecutableAtom {
1483    ExecutableAtom {
1484        relation_id: relation_id(&atom.predicate, atom.terms.len()),
1485        name: atom.predicate.clone(),
1486        terms: atom.terms.iter().map(executable_term).collect(),
1487    }
1488}
1489
1490fn executable_probability(probability: f64) -> ExecutableProbability {
1491    ExecutableProbability {
1492        ieee754_bits: probability.to_bits(),
1493    }
1494}
1495
1496fn executable_neural_label(label: &NeuralLabel) -> ExecutableNeuralLabel {
1497    match label {
1498        NeuralLabel::Integer(value) => ExecutableNeuralLabel::Integer { value: *value },
1499        NeuralLabel::Symbol(value) => ExecutableNeuralLabel::Symbol {
1500            value: value.clone(),
1501        },
1502    }
1503}
1504
1505fn executable_term(term: &Term) -> ExecutableTerm {
1506    match term {
1507        Term::Variable(name) => ExecutableTerm::Variable { name: name.clone() },
1508        Term::Anonymous => ExecutableTerm::Anonymous,
1509        Term::Integer(value) => ExecutableTerm::Integer { value: *value },
1510        Term::Float(value) => ExecutableTerm::Float {
1511            ieee754_bits: value.to_bits(),
1512        },
1513        Term::String(value) => ExecutableTerm::String {
1514            value: value.clone(),
1515        },
1516        Term::Symbol(id) => ExecutableTerm::Symbol {
1517            value: xlog_core::symbol::resolve(*id),
1518        },
1519        Term::List(items) => ExecutableTerm::List {
1520            items: items.iter().map(executable_term).collect(),
1521        },
1522        Term::Cons { head, tail } => ExecutableTerm::Cons {
1523            head: Box::new(executable_term(head)),
1524            tail: Box::new(executable_term(tail)),
1525        },
1526        Term::Compound { functor, args } => ExecutableTerm::Compound {
1527            functor: functor.clone(),
1528            arguments: args.iter().map(executable_term).collect(),
1529        },
1530        Term::PredRef(name) => ExecutableTerm::PredicateReference { name: name.clone() },
1531        Term::Aggregate(aggregate) => ExecutableTerm::Aggregate {
1532            operator: aggregate_operator(aggregate.op),
1533            variable: aggregate.variable.clone(),
1534        },
1535    }
1536}
1537
1538fn executable_arithmetic_expression(expression: &ArithExpr) -> ExecutableArithmeticExpression {
1539    match expression {
1540        ArithExpr::Variable(name) => {
1541            ExecutableArithmeticExpression::Variable { name: name.clone() }
1542        }
1543        ArithExpr::Integer(value) => ExecutableArithmeticExpression::Integer { value: *value },
1544        ArithExpr::Float(value) => ExecutableArithmeticExpression::Float {
1545            ieee754_bits: value.to_bits(),
1546        },
1547        ArithExpr::Add(left, right) => ExecutableArithmeticExpression::Add {
1548            left: Box::new(executable_arithmetic_expression(left)),
1549            right: Box::new(executable_arithmetic_expression(right)),
1550        },
1551        ArithExpr::Sub(left, right) => ExecutableArithmeticExpression::Subtract {
1552            left: Box::new(executable_arithmetic_expression(left)),
1553            right: Box::new(executable_arithmetic_expression(right)),
1554        },
1555        ArithExpr::Mul(left, right) => ExecutableArithmeticExpression::Multiply {
1556            left: Box::new(executable_arithmetic_expression(left)),
1557            right: Box::new(executable_arithmetic_expression(right)),
1558        },
1559        ArithExpr::Div(left, right) => ExecutableArithmeticExpression::Divide {
1560            left: Box::new(executable_arithmetic_expression(left)),
1561            right: Box::new(executable_arithmetic_expression(right)),
1562        },
1563        ArithExpr::Mod(left, right) => ExecutableArithmeticExpression::Modulo {
1564            left: Box::new(executable_arithmetic_expression(left)),
1565            right: Box::new(executable_arithmetic_expression(right)),
1566        },
1567        ArithExpr::Abs(value) => ExecutableArithmeticExpression::AbsoluteValue {
1568            value: Box::new(executable_arithmetic_expression(value)),
1569        },
1570        ArithExpr::Min(left, right) => ExecutableArithmeticExpression::Minimum {
1571            left: Box::new(executable_arithmetic_expression(left)),
1572            right: Box::new(executable_arithmetic_expression(right)),
1573        },
1574        ArithExpr::Max(left, right) => ExecutableArithmeticExpression::Maximum {
1575            left: Box::new(executable_arithmetic_expression(left)),
1576            right: Box::new(executable_arithmetic_expression(right)),
1577        },
1578        ArithExpr::Pow(base, exponent) => ExecutableArithmeticExpression::Power {
1579            base: Box::new(executable_arithmetic_expression(base)),
1580            exponent: Box::new(executable_arithmetic_expression(exponent)),
1581        },
1582        ArithExpr::Cast(value, scalar_type) => ExecutableArithmeticExpression::Cast {
1583            value: Box::new(executable_arithmetic_expression(value)),
1584            scalar_type: executable_scalar_type(*scalar_type),
1585        },
1586        ArithExpr::FuncCall { name, args } => ExecutableArithmeticExpression::FunctionCall {
1587            name: name.clone(),
1588            arguments: args.iter().map(executable_arithmetic_expression).collect(),
1589        },
1590        ArithExpr::Conditional {
1591            cond_left,
1592            cond_op,
1593            cond_right,
1594            then_expr,
1595            else_expr,
1596        } => ExecutableArithmeticExpression::Conditional {
1597            condition_left: Box::new(executable_arithmetic_expression(cond_left)),
1598            condition_operator: comparison_operator(*cond_op),
1599            condition_right: Box::new(executable_arithmetic_expression(cond_right)),
1600            then_expression: Box::new(executable_arithmetic_expression(then_expr)),
1601            else_expression: Box::new(executable_arithmetic_expression(else_expr)),
1602        },
1603    }
1604}
1605
1606fn relation_id(name: &str, arity: usize) -> String {
1607    format!("relation:{name}/{arity}")
1608}
1609
1610fn aggregate_operator(operator: AggOp) -> AggregateOperator {
1611    match operator {
1612        AggOp::Count => AggregateOperator::Count,
1613        AggOp::Sum => AggregateOperator::Sum,
1614        AggOp::Min => AggregateOperator::Min,
1615        AggOp::Max => AggregateOperator::Max,
1616        AggOp::LogSumExp => AggregateOperator::LogSumExp,
1617    }
1618}
1619
1620fn epistemic_operator(operator: EpistemicOp) -> EpistemicOperator {
1621    match operator {
1622        EpistemicOp::Know => EpistemicOperator::Know,
1623        EpistemicOp::Possible => EpistemicOperator::Possible,
1624    }
1625}
1626
1627fn comparison_operator(operator: CompOp) -> ComparisonOperator {
1628    match operator {
1629        CompOp::Eq => ComparisonOperator::Equal,
1630        CompOp::Ne => ComparisonOperator::NotEqual,
1631        CompOp::Lt => ComparisonOperator::LessThan,
1632        CompOp::Le => ComparisonOperator::LessThanOrEqual,
1633        CompOp::Gt => ComparisonOperator::GreaterThan,
1634        CompOp::Ge => ComparisonOperator::GreaterThanOrEqual,
1635    }
1636}