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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
392#[serde(rename_all = "snake_case")]
393pub enum EpistemicOperator {
394 Know,
395 Possible,
396}
397
398#[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#[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#[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 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}