1use crate::ast::{
4 ArithExpr, Atom, BodyLiteral, Comparison, Constraint, FuncBody, FuncDef, IsExpr, Term, Univ,
5};
6use crate::function::{is_builtin, FunctionError, FunctionRegistry};
7use std::collections::{HashMap, HashSet};
8
9const GENERATED_FUNCTION_VARIABLE_PREFIX: &str = "__XLOG_FUNCTION_";
10
11pub(crate) fn generated_function_variable_name(
12 function_name: &str,
13 source_name: &str,
14 counter: usize,
15) -> String {
16 format!(
17 "{GENERATED_FUNCTION_VARIABLE_PREFIX}{}_{}_{}",
18 function_name.to_ascii_uppercase(),
19 source_name,
20 counter
21 )
22}
23
24pub(crate) fn generated_function_variable_source<'a>(
25 generated_name: &'a str,
26 function_name: &str,
27) -> Option<&'a str> {
28 let prefix = format!(
29 "{GENERATED_FUNCTION_VARIABLE_PREFIX}{}_",
30 function_name.to_ascii_uppercase()
31 );
32 let (source_name, counter) = generated_name.strip_prefix(&prefix)?.rsplit_once('_')?;
33 (!source_name.is_empty()
34 && !counter.is_empty()
35 && counter.chars().all(|character| character.is_ascii_digit()))
36 .then_some(source_name)
37}
38
39#[derive(Debug)]
40struct ExpandedExpression {
41 generated_literals: Vec<BodyLiteral>,
42 expression: ArithExpr,
43}
44
45fn inline_trailing_predicate_result_binding(
46 mut literals: Vec<BodyLiteral>,
47 result: ArithExpr,
48) -> (Vec<BodyLiteral>, ArithExpr) {
49 let ArithExpr::Variable(result_name) = &result else {
50 return (literals, result);
51 };
52 let Some(BodyLiteral::IsExpr(binding)) = literals.last() else {
53 return (literals, result);
54 };
55 let ArithExpr::Variable(source_name) = &binding.expr else {
56 return (literals, result);
57 };
58 if !predicate_prefix_binds_variable(&literals[..literals.len() - 1], source_name)
59 || binding.target != *result_name
60 || binding
61 .expr
62 .variables()
63 .into_iter()
64 .any(|name| name == result_name)
65 || literals[..literals.len() - 1].iter().any(|literal| {
66 literal
67 .variables()
68 .into_iter()
69 .any(|name| name == result_name)
70 })
71 {
72 return (literals, result);
73 }
74
75 let Some(BodyLiteral::IsExpr(binding)) = literals.pop() else {
76 unreachable!("trailing predicate result binding was checked above")
77 };
78 (literals, binding.expr)
79}
80
81fn predicate_prefix_binds_variable(literals: &[BodyLiteral], wanted: &str) -> bool {
82 let mut bound = HashSet::new();
83 for literal in literals {
84 match literal {
85 BodyLiteral::Positive(atom) => {
86 bound.extend(atom.variables().into_iter().map(ToOwned::to_owned));
87 }
88 BodyLiteral::IsExpr(binding)
89 if binding
90 .expr
91 .variables()
92 .into_iter()
93 .all(|name| bound.contains(name)) =>
94 {
95 bound.insert(binding.target.clone());
96 }
97 BodyLiteral::Negated(_)
98 | BodyLiteral::Epistemic(_)
99 | BodyLiteral::Comparison(_)
100 | BodyLiteral::IsExpr(_)
101 | BodyLiteral::Univ(_) => {}
102 }
103 }
104 bound.contains(wanted)
105}
106
107impl ExpandedExpression {
108 fn value(expression: ArithExpr) -> Self {
109 Self {
110 generated_literals: Vec::new(),
111 expression,
112 }
113 }
114}
115
116type BinaryExpressionConstructor = fn(Box<ArithExpr>, Box<ArithExpr>) -> ArithExpr;
117
118enum ExpansionTask {
119 Expression {
120 expression: ArithExpr,
121 subst: HashMap<String, ArithExpr>,
122 in_conditional_branch: bool,
123 },
124 FinishCall {
125 name: String,
126 argument_count: usize,
127 in_conditional_branch: bool,
128 },
129 EnterFunction {
130 name: String,
131 args: Vec<ArithExpr>,
132 in_conditional_branch: bool,
133 },
134 FunctionBody {
135 function_name: String,
136 body: FuncBody,
137 subst: HashMap<String, ArithExpr>,
138 in_conditional_branch: bool,
139 },
140 LeaveFunction,
141 PrependGenerated {
142 literals: Vec<BodyLiteral>,
143 },
144 FinishBinary {
145 constructor: BinaryExpressionConstructor,
146 },
147 FinishAbs,
148 FinishCast(xlog_core::ScalarType),
149 FinishConditional(crate::ast::CompOp),
150 PredicateLiteral(PreparedPredicateLiteral),
151 FinishPredicateBinding {
152 target: String,
153 },
154 FinishPredicateBody {
155 literal_count: usize,
156 result: ArithExpr,
157 },
158}
159
160enum PreparedPredicateLiteral {
161 Literal(BodyLiteral),
162 Binding {
163 target: String,
164 expression: ArithExpr,
165 subst: HashMap<String, ArithExpr>,
166 },
167}
168
169enum TermSubstitutionTask<'a> {
170 Term(&'a Term),
171 FinishList(usize),
172 FinishCons,
173 FinishCompound {
174 functor: String,
175 argument_count: usize,
176 },
177}
178
179pub struct ExpansionContext<'a> {
181 registry: &'a FunctionRegistry,
182 depth: u32,
183 max_depth: u32,
184 fresh_counter: usize,
185}
186
187impl<'a> ExpansionContext<'a> {
188 pub fn new(registry: &'a FunctionRegistry, max_depth: u32) -> Self {
190 Self {
191 registry,
192 depth: 0,
193 max_depth,
194 fresh_counter: 0,
195 }
196 }
197
198 pub fn expand_call(
203 &mut self,
204 name: &str,
205 args: &[ArithExpr],
206 ) -> Result<ArithExpr, FunctionError> {
207 if !self.registry.contains(name) {
208 return Err(FunctionError::UndefinedFunction {
209 name: name.to_string(),
210 });
211 }
212
213 let call = ArithExpr::FuncCall {
214 name: name.to_string(),
215 args: args.to_vec(),
216 };
217 let mut used_variables = call
218 .variables()
219 .into_iter()
220 .map(ToOwned::to_owned)
221 .collect();
222 let expanded =
223 self.expand_expr_for_rule(&call, &HashMap::new(), &mut used_variables, false)?;
224 if expanded.generated_literals.is_empty() {
225 Ok(expanded.expression)
226 } else {
227 Err(FunctionError::PredicateBodyRequiresRuleContext {
228 name: name.to_string(),
229 })
230 }
231 }
232
233 fn check_arity(func: &FuncDef, args: &[ArithExpr]) -> Result<(), FunctionError> {
234 if func.params.len() == args.len() {
235 return Ok(());
236 }
237 Err(FunctionError::ArityMismatch {
238 name: func.name.clone(),
239 expected: func.params.len(),
240 received: args.len(),
241 })
242 }
243
244 fn fresh_variable(
245 &mut self,
246 function_name: &str,
247 source_name: &str,
248 used_variables: &mut HashSet<String>,
249 ) -> String {
250 loop {
251 let candidate =
252 generated_function_variable_name(function_name, source_name, self.fresh_counter);
253 self.fresh_counter += 1;
254 if used_variables.insert(candidate.clone()) {
255 return candidate;
256 }
257 }
258 }
259
260 fn prepare_predicate_func(
262 &mut self,
263 function_name: &str,
264 result: String,
265 body: Vec<BodyLiteral>,
266 mut subst: HashMap<String, ArithExpr>,
267 used_variables: &mut HashSet<String>,
268 ) -> Result<(Vec<PreparedPredicateLiteral>, ArithExpr), FunctionError> {
269 let parameter_names: HashSet<String> = subst.keys().cloned().collect();
270 let mut local_names = Vec::new();
271 let mut seen_locals = HashSet::new();
272
273 if !parameter_names.contains(&result) && seen_locals.insert(result.clone()) {
274 local_names.push(result.clone());
275 }
276 for literal in &body {
277 for variable in literal.variables() {
278 if !parameter_names.contains(variable) && seen_locals.insert(variable.to_string()) {
279 local_names.push(variable.to_string());
280 }
281 }
282 }
283
284 for local in local_names {
285 let fresh = self.fresh_variable(function_name, &local, used_variables);
286 subst.insert(local, ArithExpr::Variable(fresh));
287 }
288
289 let substituted_body = body
290 .into_iter()
291 .map(|literal| {
292 if let BodyLiteral::IsExpr(binding) = literal {
293 Ok(PreparedPredicateLiteral::Binding {
294 target: self.substitute_binding_target(
295 function_name,
296 &binding.target,
297 &subst,
298 )?,
299 expression: binding.expr,
300 subst: subst.clone(),
301 })
302 } else {
303 self.substitute_literal(function_name, &literal, &subst)
304 .map(PreparedPredicateLiteral::Literal)
305 }
306 })
307 .collect::<Result<Vec<_>, _>>()?;
308 let expression = subst
309 .get(&result)
310 .cloned()
311 .unwrap_or(ArithExpr::Variable(result));
312 Ok((substituted_body, expression))
313 }
314
315 fn substitute_literal(
316 &self,
317 function_name: &str,
318 lit: &BodyLiteral,
319 subst: &HashMap<String, ArithExpr>,
320 ) -> Result<BodyLiteral, FunctionError> {
321 Ok(match lit {
322 BodyLiteral::Positive(atom) => {
323 BodyLiteral::Positive(self.substitute_atom(function_name, atom, subst)?)
324 }
325 BodyLiteral::Negated(atom) => {
326 BodyLiteral::Negated(self.substitute_atom(function_name, atom, subst)?)
327 }
328 BodyLiteral::Epistemic(lit) => BodyLiteral::Epistemic(crate::ast::EpistemicLiteral {
329 op: lit.op,
330 negated: lit.negated,
331 atom: self.substitute_atom(function_name, &lit.atom, subst)?,
332 }),
333 BodyLiteral::Comparison(cmp) => BodyLiteral::Comparison(Comparison {
334 left: self.substitute_term(function_name, &cmp.left, subst)?,
335 op: cmp.op,
336 right: self.substitute_term(function_name, &cmp.right, subst)?,
337 }),
338 BodyLiteral::IsExpr(_) => unreachable!("predicate bindings are prepared separately"),
339 BodyLiteral::Univ(univ) => BodyLiteral::Univ(Univ {
340 term: self.substitute_term(function_name, &univ.term, subst)?,
341 parts: self.substitute_term(function_name, &univ.parts, subst)?,
342 }),
343 })
344 }
345
346 fn substitute_atom(
347 &self,
348 function_name: &str,
349 atom: &Atom,
350 subst: &HashMap<String, ArithExpr>,
351 ) -> Result<Atom, FunctionError> {
352 Ok(Atom {
353 predicate: atom.predicate.clone(),
354 terms: atom
355 .terms
356 .iter()
357 .map(|term| self.substitute_term(function_name, term, subst))
358 .collect::<Result<Vec<_>, _>>()?,
359 })
360 }
361
362 fn substitute_term(
363 &self,
364 function_name: &str,
365 term: &Term,
366 subst: &HashMap<String, ArithExpr>,
367 ) -> Result<Term, FunctionError> {
368 let mut tasks = vec![TermSubstitutionTask::Term(term)];
369 let mut values = Vec::new();
370
371 while let Some(task) = tasks.pop() {
372 match task {
373 TermSubstitutionTask::Term(term) => match term {
374 Term::Variable(name) => values.push(match subst.get(name) {
375 Some(ArithExpr::Variable(new_name)) => Term::Variable(new_name.clone()),
376 Some(ArithExpr::Integer(value)) => Term::Integer(*value),
377 Some(ArithExpr::Float(value)) => Term::Float(*value),
378 Some(_) => {
379 return Err(FunctionError::UnsupportedPredicateTermArgument {
380 name: function_name.to_string(),
381 parameter: name.clone(),
382 });
383 }
384 None => Term::Variable(name.clone()),
385 }),
386 Term::List(items) => {
387 tasks.push(TermSubstitutionTask::FinishList(items.len()));
388 for item in items.iter().rev() {
389 tasks.push(TermSubstitutionTask::Term(item));
390 }
391 }
392 Term::Cons { head, tail } => {
393 tasks.push(TermSubstitutionTask::FinishCons);
394 tasks.push(TermSubstitutionTask::Term(tail));
395 tasks.push(TermSubstitutionTask::Term(head));
396 }
397 Term::Compound { functor, args } => {
398 tasks.push(TermSubstitutionTask::FinishCompound {
399 functor: functor.clone(),
400 argument_count: args.len(),
401 });
402 for argument in args.iter().rev() {
403 tasks.push(TermSubstitutionTask::Term(argument));
404 }
405 }
406 Term::Aggregate(aggregate) => {
407 values.push(Term::Aggregate(crate::ast::AggExpr {
408 op: aggregate.op,
409 variable: self.substitute_binding_target(
410 function_name,
411 &aggregate.variable,
412 subst,
413 )?,
414 }));
415 }
416 Term::Anonymous => values.push(Term::Anonymous),
417 Term::Integer(value) => values.push(Term::Integer(*value)),
418 Term::Float(value) => values.push(Term::Float(*value)),
419 Term::String(value) => values.push(Term::String(value.clone())),
420 Term::Symbol(value) => values.push(Term::Symbol(*value)),
421 Term::PredRef(value) => values.push(Term::PredRef(value.clone())),
422 },
423 TermSubstitutionTask::FinishList(item_count) => {
424 let start = values
425 .len()
426 .checked_sub(item_count)
427 .expect("list items produce substituted terms");
428 let items = values.split_off(start);
429 values.push(Term::List(items));
430 }
431 TermSubstitutionTask::FinishCons => {
432 let tail = values.pop().expect("cons tail produces a substituted term");
433 let head = values.pop().expect("cons head produces a substituted term");
434 values.push(Term::Cons {
435 head: Box::new(head),
436 tail: Box::new(tail),
437 });
438 }
439 TermSubstitutionTask::FinishCompound {
440 functor,
441 argument_count,
442 } => {
443 let start = values
444 .len()
445 .checked_sub(argument_count)
446 .expect("compound arguments produce substituted terms");
447 let args = values.split_off(start);
448 values.push(Term::Compound { functor, args });
449 }
450 }
451 }
452
453 let substituted = values.pop().expect("term substitution produces one term");
454 debug_assert!(values.is_empty());
455 Ok(substituted)
456 }
457
458 fn substitute_binding_target(
459 &self,
460 function_name: &str,
461 variable: &str,
462 subst: &HashMap<String, ArithExpr>,
463 ) -> Result<String, FunctionError> {
464 match subst.get(variable) {
465 Some(ArithExpr::Variable(new_name)) => Ok(new_name.clone()),
466 Some(_) => Err(FunctionError::InvalidPredicateBindingTarget {
467 name: function_name.to_string(),
468 parameter: variable.to_string(),
469 }),
470 None => Ok(variable.to_string()),
471 }
472 }
473
474 fn expand_literal_for_rule(
475 &mut self,
476 literal: &BodyLiteral,
477 used_variables: &mut HashSet<String>,
478 ) -> Result<Vec<BodyLiteral>, FunctionError> {
479 let BodyLiteral::IsExpr(binding) = literal else {
480 return Ok(vec![literal.clone()]);
481 };
482
483 let expanded =
484 self.expand_expr_for_rule(&binding.expr, &HashMap::new(), used_variables, false)?;
485 let mut literals = expanded.generated_literals;
486 literals.push(BodyLiteral::IsExpr(IsExpr {
487 target: binding.target.clone(),
488 expr: expanded.expression,
489 }));
490 Ok(literals)
491 }
492
493 fn expand_expr_for_rule(
494 &mut self,
495 expression: &ArithExpr,
496 subst: &HashMap<String, ArithExpr>,
497 used_variables: &mut HashSet<String>,
498 in_conditional_branch: bool,
499 ) -> Result<ExpandedExpression, FunctionError> {
500 let saved_depth = self.depth;
501 let saved_fresh_counter = self.fresh_counter;
502 let saved_used_variables = used_variables.clone();
503 let result =
504 self.run_expansion_machine(expression, subst, used_variables, in_conditional_branch);
505 self.depth = saved_depth;
506 if result.is_err() {
507 self.fresh_counter = saved_fresh_counter;
508 *used_variables = saved_used_variables;
509 }
510 result
511 }
512
513 fn run_expansion_machine(
514 &mut self,
515 expression: &ArithExpr,
516 subst: &HashMap<String, ArithExpr>,
517 used_variables: &mut HashSet<String>,
518 in_conditional_branch: bool,
519 ) -> Result<ExpandedExpression, FunctionError> {
520 let mut tasks = vec![ExpansionTask::Expression {
521 expression: expression.clone(),
522 subst: subst.clone(),
523 in_conditional_branch,
524 }];
525 let mut values = Vec::new();
526 let mut predicate_literal_values: Vec<Vec<BodyLiteral>> = Vec::new();
527
528 while let Some(task) = tasks.pop() {
529 match task {
530 ExpansionTask::Expression {
531 expression,
532 subst,
533 in_conditional_branch,
534 } => match expression {
535 ArithExpr::Variable(name) => values.push(ExpandedExpression::value(
536 subst
537 .get(&name)
538 .cloned()
539 .unwrap_or(ArithExpr::Variable(name)),
540 )),
541 ArithExpr::Integer(_) | ArithExpr::Float(_) => {
542 values.push(ExpandedExpression::value(expression));
543 }
544 ArithExpr::FuncCall { name, args } => {
545 if let Some(func) = self.registry.get(&name) {
546 Self::check_arity(func, &args)?;
547 }
548 tasks.push(ExpansionTask::FinishCall {
549 name,
550 argument_count: args.len(),
551 in_conditional_branch,
552 });
553 for argument in args.into_iter().rev() {
554 tasks.push(ExpansionTask::Expression {
555 expression: argument,
556 subst: subst.clone(),
557 in_conditional_branch,
558 });
559 }
560 }
561 ArithExpr::Add(left, right) => {
562 Self::schedule_binary(
563 &mut tasks,
564 *left,
565 *right,
566 subst,
567 in_conditional_branch,
568 ArithExpr::Add,
569 );
570 }
571 ArithExpr::Sub(left, right) => {
572 Self::schedule_binary(
573 &mut tasks,
574 *left,
575 *right,
576 subst,
577 in_conditional_branch,
578 ArithExpr::Sub,
579 );
580 }
581 ArithExpr::Mul(left, right) => {
582 Self::schedule_binary(
583 &mut tasks,
584 *left,
585 *right,
586 subst,
587 in_conditional_branch,
588 ArithExpr::Mul,
589 );
590 }
591 ArithExpr::Div(left, right) => {
592 Self::schedule_binary(
593 &mut tasks,
594 *left,
595 *right,
596 subst,
597 in_conditional_branch,
598 ArithExpr::Div,
599 );
600 }
601 ArithExpr::Mod(left, right) => {
602 Self::schedule_binary(
603 &mut tasks,
604 *left,
605 *right,
606 subst,
607 in_conditional_branch,
608 ArithExpr::Mod,
609 );
610 }
611 ArithExpr::Min(left, right) => {
612 Self::schedule_binary(
613 &mut tasks,
614 *left,
615 *right,
616 subst,
617 in_conditional_branch,
618 ArithExpr::Min,
619 );
620 }
621 ArithExpr::Max(left, right) => {
622 Self::schedule_binary(
623 &mut tasks,
624 *left,
625 *right,
626 subst,
627 in_conditional_branch,
628 ArithExpr::Max,
629 );
630 }
631 ArithExpr::Pow(left, right) => {
632 Self::schedule_binary(
633 &mut tasks,
634 *left,
635 *right,
636 subst,
637 in_conditional_branch,
638 ArithExpr::Pow,
639 );
640 }
641 ArithExpr::Abs(inner) => {
642 tasks.push(ExpansionTask::FinishAbs);
643 tasks.push(ExpansionTask::Expression {
644 expression: *inner,
645 subst,
646 in_conditional_branch,
647 });
648 }
649 ArithExpr::Cast(inner, target) => {
650 tasks.push(ExpansionTask::FinishCast(target));
651 tasks.push(ExpansionTask::Expression {
652 expression: *inner,
653 subst,
654 in_conditional_branch,
655 });
656 }
657 ArithExpr::Conditional {
658 cond_left,
659 cond_op,
660 cond_right,
661 then_expr,
662 else_expr,
663 } => {
664 tasks.push(ExpansionTask::FinishConditional(cond_op));
665 tasks.push(ExpansionTask::Expression {
666 expression: *else_expr,
667 subst: subst.clone(),
668 in_conditional_branch: true,
669 });
670 tasks.push(ExpansionTask::Expression {
671 expression: *then_expr,
672 subst: subst.clone(),
673 in_conditional_branch: true,
674 });
675 tasks.push(ExpansionTask::Expression {
676 expression: *cond_right,
677 subst: subst.clone(),
678 in_conditional_branch,
679 });
680 tasks.push(ExpansionTask::Expression {
681 expression: *cond_left,
682 subst,
683 in_conditional_branch,
684 });
685 }
686 },
687 ExpansionTask::FinishCall {
688 name,
689 argument_count,
690 in_conditional_branch,
691 } => {
692 let start = values
693 .len()
694 .checked_sub(argument_count)
695 .expect("function arguments produce expansion values");
696 let argument_values = values.split_off(start);
697 let mut generated_literals = Vec::new();
698 let mut args = Vec::with_capacity(argument_count);
699 for argument in argument_values {
700 generated_literals.extend(argument.generated_literals);
701 args.push(argument.expression);
702 }
703 if self.registry.contains(&name) {
704 tasks.push(ExpansionTask::PrependGenerated {
705 literals: generated_literals,
706 });
707 tasks.push(ExpansionTask::EnterFunction {
708 name,
709 args,
710 in_conditional_branch,
711 });
712 } else if is_builtin(&name) {
713 values.push(ExpandedExpression {
714 generated_literals,
715 expression: ArithExpr::FuncCall { name, args },
716 });
717 } else {
718 return Err(FunctionError::UndefinedFunction { name });
719 }
720 }
721 ExpansionTask::EnterFunction {
722 name,
723 args,
724 in_conditional_branch,
725 } => {
726 let func =
727 self.registry.get(&name).cloned().ok_or_else(|| {
728 FunctionError::UndefinedFunction { name: name.clone() }
729 })?;
730 Self::check_arity(&func, &args)?;
731 if self.depth >= self.max_depth {
732 return Err(FunctionError::MaxRecursionDepth {
733 name,
734 depth: self.max_depth,
735 });
736 }
737 if in_conditional_branch && matches!(func.body, FuncBody::Predicate { .. }) {
738 return Err(FunctionError::PredicateCallInConditionalBranch { name });
739 }
740
741 self.depth += 1;
742 let subst = func
743 .params
744 .iter()
745 .zip(&args)
746 .map(|(param, argument)| (param.name.clone(), argument.clone()))
747 .collect();
748 tasks.push(ExpansionTask::LeaveFunction);
749 tasks.push(ExpansionTask::FunctionBody {
750 function_name: func.name,
751 body: func.body,
752 subst,
753 in_conditional_branch,
754 });
755 }
756 ExpansionTask::FunctionBody {
757 function_name,
758 body,
759 subst,
760 in_conditional_branch,
761 } => match body {
762 FuncBody::Arithmetic(expression) => {
763 tasks.push(ExpansionTask::Expression {
764 expression,
765 subst,
766 in_conditional_branch,
767 });
768 }
769 FuncBody::Predicate { result, body } => {
770 if in_conditional_branch {
771 return Err(FunctionError::PredicateCallInConditionalBranch {
772 name: function_name,
773 });
774 }
775 let (literals, result) = self.prepare_predicate_func(
776 &function_name,
777 result,
778 body,
779 subst,
780 used_variables,
781 )?;
782 tasks.push(ExpansionTask::FinishPredicateBody {
783 literal_count: literals.len(),
784 result,
785 });
786 for literal in literals.into_iter().rev() {
787 tasks.push(ExpansionTask::PredicateLiteral(literal));
788 }
789 }
790 FuncBody::Conditional(conditional) => {
791 tasks.push(ExpansionTask::FinishConditional(conditional.cond_op));
792 tasks.push(ExpansionTask::FunctionBody {
793 function_name: function_name.clone(),
794 body: *conditional.else_branch,
795 subst: subst.clone(),
796 in_conditional_branch: true,
797 });
798 tasks.push(ExpansionTask::FunctionBody {
799 function_name,
800 body: *conditional.then_branch,
801 subst: subst.clone(),
802 in_conditional_branch: true,
803 });
804 tasks.push(ExpansionTask::Expression {
805 expression: conditional.cond_right,
806 subst: subst.clone(),
807 in_conditional_branch,
808 });
809 tasks.push(ExpansionTask::Expression {
810 expression: conditional.cond_left,
811 subst,
812 in_conditional_branch,
813 });
814 }
815 },
816 ExpansionTask::LeaveFunction => {
817 debug_assert!(self.depth > 0);
818 self.depth -= 1;
819 }
820 ExpansionTask::PrependGenerated { mut literals } => {
821 let mut expanded = values
822 .pop()
823 .expect("function call produces an expansion value");
824 literals.append(&mut expanded.generated_literals);
825 expanded.generated_literals = literals;
826 values.push(expanded);
827 }
828 ExpansionTask::FinishBinary { constructor } => {
829 let right = values.pop().expect("right expression is expanded");
830 let mut left = values.pop().expect("left expression is expanded");
831 left.generated_literals.extend(right.generated_literals);
832 left.expression =
833 constructor(Box::new(left.expression), Box::new(right.expression));
834 values.push(left);
835 }
836 ExpansionTask::FinishAbs => {
837 let mut expanded = values.pop().expect("absolute-value operand is expanded");
838 expanded.expression = ArithExpr::Abs(Box::new(expanded.expression));
839 values.push(expanded);
840 }
841 ExpansionTask::FinishCast(target) => {
842 let mut expanded = values.pop().expect("cast operand is expanded");
843 expanded.expression = ArithExpr::Cast(Box::new(expanded.expression), target);
844 values.push(expanded);
845 }
846 ExpansionTask::FinishConditional(cond_op) => {
847 let else_value = values.pop().expect("else branch is expanded");
848 let then_value = values.pop().expect("then branch is expanded");
849 let right = values.pop().expect("condition right side is expanded");
850 let mut left = values.pop().expect("condition left side is expanded");
851 left.generated_literals.extend(right.generated_literals);
852 left.generated_literals
853 .extend(then_value.generated_literals);
854 left.generated_literals
855 .extend(else_value.generated_literals);
856 left.expression = ArithExpr::Conditional {
857 cond_left: Box::new(left.expression),
858 cond_op,
859 cond_right: Box::new(right.expression),
860 then_expr: Box::new(then_value.expression),
861 else_expr: Box::new(else_value.expression),
862 };
863 values.push(left);
864 }
865 ExpansionTask::PredicateLiteral(literal) => match literal {
866 PreparedPredicateLiteral::Binding {
867 target,
868 expression,
869 subst,
870 } => {
871 tasks.push(ExpansionTask::FinishPredicateBinding { target });
872 tasks.push(ExpansionTask::Expression {
873 expression,
874 subst,
875 in_conditional_branch: false,
876 });
877 }
878 PreparedPredicateLiteral::Literal(literal) => {
879 predicate_literal_values.push(vec![literal]);
880 }
881 },
882 ExpansionTask::FinishPredicateBinding { target } => {
883 let mut expanded = values
884 .pop()
885 .expect("predicate-body binding expression is expanded");
886 expanded
887 .generated_literals
888 .push(BodyLiteral::IsExpr(IsExpr {
889 target,
890 expr: expanded.expression,
891 }));
892 predicate_literal_values.push(expanded.generated_literals);
893 }
894 ExpansionTask::FinishPredicateBody {
895 literal_count,
896 result,
897 } => {
898 let start = predicate_literal_values
899 .len()
900 .checked_sub(literal_count)
901 .expect("predicate literals produce expansion values");
902 let generated_literals = predicate_literal_values
903 .split_off(start)
904 .into_iter()
905 .flatten()
906 .collect();
907 let (generated_literals, result) =
908 inline_trailing_predicate_result_binding(generated_literals, result);
909 values.push(ExpandedExpression {
910 generated_literals,
911 expression: result,
912 });
913 }
914 }
915 }
916
917 debug_assert!(predicate_literal_values.is_empty());
918 let expanded = values
919 .pop()
920 .expect("expression expansion produces one value");
921 debug_assert!(values.is_empty());
922 Ok(expanded)
923 }
924
925 fn schedule_binary(
926 tasks: &mut Vec<ExpansionTask>,
927 left: ArithExpr,
928 right: ArithExpr,
929 subst: HashMap<String, ArithExpr>,
930 in_conditional_branch: bool,
931 constructor: BinaryExpressionConstructor,
932 ) {
933 tasks.push(ExpansionTask::FinishBinary { constructor });
934 tasks.push(ExpansionTask::Expression {
935 expression: right,
936 subst: subst.clone(),
937 in_conditional_branch,
938 });
939 tasks.push(ExpansionTask::Expression {
940 expression: left,
941 subst,
942 in_conditional_branch,
943 });
944 }
945
946 #[cfg(test)]
947 fn is_predicate_func(&self, name: &str) -> bool {
948 self.registry
949 .get(name)
950 .is_some_and(|function| matches!(function.body, FuncBody::Predicate { .. }))
951 }
952}
953
954use crate::ast::{Program, Rule};
955
956pub fn expand_program_functions(
961 program: &Program,
962 max_depth: u32,
963) -> Result<Program, FunctionError> {
964 if program.functions.is_empty() {
966 return Ok(program.clone());
967 }
968 expand_program_functions_impl(program, max_depth)
969}
970
971pub fn expand_program_functions_owned(
975 program: Program,
976 max_depth: u32,
977) -> Result<Program, FunctionError> {
978 if program.functions.is_empty() {
979 return Ok(program);
980 }
981 expand_program_functions_impl(&program, max_depth)
982}
983
984fn expand_program_functions_impl(
985 program: &Program,
986 max_depth: u32,
987) -> Result<Program, FunctionError> {
988 let mut registry = FunctionRegistry::new();
989 for function in &program.functions {
990 registry.register(function.clone())?;
991 }
992 let mut ctx = ExpansionContext::new(®istry, max_depth);
993
994 let expanded_rules: Result<Vec<Rule>, FunctionError> = program
996 .rules
997 .iter()
998 .map(|rule| expand_rule_functions(&mut ctx, rule))
999 .collect();
1000 let expanded_constraints: Result<Vec<Constraint>, FunctionError> = program
1001 .constraints
1002 .iter()
1003 .map(|constraint| expand_constraint_functions(&mut ctx, constraint))
1004 .collect();
1005
1006 Ok(Program {
1007 rules: expanded_rules?,
1008 directives: program.directives.clone(),
1009 queries: program.queries.clone(),
1010 predicates: program.predicates.clone(),
1011 constraints: expanded_constraints?,
1012 authored_constraint_source_bound: program.authored_constraint_source_bound,
1013 imports: program.imports.clone(),
1014 functions: program.functions.clone(),
1015 domains: program.domains.clone(),
1016 prob_facts: program.prob_facts.clone(),
1017 annotated_disjunctions: program.annotated_disjunctions.clone(),
1018 evidence: program.evidence.clone(),
1019 prob_queries: program.prob_queries.clone(),
1020 neural_predicates: program.neural_predicates.clone(),
1021 learnable_rules: program.learnable_rules.clone(),
1022 })
1023}
1024
1025fn expand_rule_functions(ctx: &mut ExpansionContext, rule: &Rule) -> Result<Rule, FunctionError> {
1027 let mut used_variables: HashSet<String> = rule
1028 .head
1029 .variables()
1030 .into_iter()
1031 .chain(rule.body.iter().flat_map(BodyLiteral::variables))
1032 .map(ToOwned::to_owned)
1033 .collect();
1034 let expanded_body = expand_body_functions(ctx, &rule.body, &mut used_variables)?;
1035
1036 Ok(Rule {
1037 head: rule.head.clone(),
1038 body: expanded_body,
1039 })
1040}
1041
1042fn expand_constraint_functions(
1043 ctx: &mut ExpansionContext,
1044 constraint: &Constraint,
1045) -> Result<Constraint, FunctionError> {
1046 let mut used_variables: HashSet<String> = constraint
1047 .body
1048 .iter()
1049 .flat_map(BodyLiteral::variables)
1050 .map(ToOwned::to_owned)
1051 .collect();
1052 Ok(Constraint {
1053 authored_index: constraint.authored_index,
1054 body: expand_body_functions(ctx, &constraint.body, &mut used_variables)?,
1055 })
1056}
1057
1058fn expand_body_functions(
1059 ctx: &mut ExpansionContext,
1060 body: &[BodyLiteral],
1061 used_variables: &mut HashSet<String>,
1062) -> Result<Vec<BodyLiteral>, FunctionError> {
1063 let mut expanded_body = Vec::new();
1064 for literal in body {
1065 expanded_body.extend(ctx.expand_literal_for_rule(literal, used_variables)?);
1066 }
1067 Ok(expanded_body)
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072 use super::*;
1073 use crate::ast::{FuncDef, FuncParam};
1074
1075 #[test]
1076 fn test_simple_expansion() {
1077 let mut reg = FunctionRegistry::new();
1078
1079 let double = FuncDef {
1081 name: "double".to_string(),
1082 params: vec![FuncParam {
1083 name: "X".to_string(),
1084 typ: None,
1085 }],
1086 return_type: None,
1087 body: FuncBody::Arithmetic(ArithExpr::Add(
1088 Box::new(ArithExpr::Variable("X".to_string())),
1089 Box::new(ArithExpr::Variable("X".to_string())),
1090 )),
1091 is_private: false,
1092 };
1093 reg.register(double).unwrap();
1094
1095 let mut ctx = ExpansionContext::new(®, 100);
1096
1097 let result = ctx.expand_call("double", &[ArithExpr::Integer(5)]).unwrap();
1099
1100 match result {
1101 ArithExpr::Add(l, r) => {
1102 assert!(matches!(*l, ArithExpr::Integer(5)));
1103 assert!(matches!(*r, ArithExpr::Integer(5)));
1104 }
1105 _ => panic!("Expected Add expression"),
1106 }
1107 }
1108
1109 #[test]
1110 fn test_nested_expansion() {
1111 let mut reg = FunctionRegistry::new();
1112
1113 let double = FuncDef {
1115 name: "double".to_string(),
1116 params: vec![FuncParam {
1117 name: "X".to_string(),
1118 typ: None,
1119 }],
1120 return_type: None,
1121 body: FuncBody::Arithmetic(ArithExpr::Add(
1122 Box::new(ArithExpr::Variable("X".to_string())),
1123 Box::new(ArithExpr::Variable("X".to_string())),
1124 )),
1125 is_private: false,
1126 };
1127
1128 let quadruple = FuncDef {
1130 name: "quadruple".to_string(),
1131 params: vec![FuncParam {
1132 name: "X".to_string(),
1133 typ: None,
1134 }],
1135 return_type: None,
1136 body: FuncBody::Arithmetic(ArithExpr::FuncCall {
1137 name: "double".to_string(),
1138 args: vec![ArithExpr::FuncCall {
1139 name: "double".to_string(),
1140 args: vec![ArithExpr::Variable("X".to_string())],
1141 }],
1142 }),
1143 is_private: false,
1144 };
1145
1146 reg.register(double).unwrap();
1147 reg.register(quadruple).unwrap();
1148
1149 let mut ctx = ExpansionContext::new(®, 100);
1150
1151 let result = ctx
1153 .expand_call("quadruple", &[ArithExpr::Integer(2)])
1154 .unwrap();
1155
1156 match &result {
1158 ArithExpr::Add(l, r) => {
1159 assert!(matches!(l.as_ref(), ArithExpr::Add(_, _)));
1160 assert!(matches!(r.as_ref(), ArithExpr::Add(_, _)));
1161 }
1162 _ => panic!("Expected nested Add expression, got {:?}", result),
1163 }
1164 }
1165
1166 #[test]
1167 fn test_max_recursion_depth() {
1168 let mut reg = FunctionRegistry::new();
1169
1170 let infinite = FuncDef {
1172 name: "infinite".to_string(),
1173 params: vec![FuncParam {
1174 name: "X".to_string(),
1175 typ: None,
1176 }],
1177 return_type: None,
1178 body: FuncBody::Arithmetic(ArithExpr::FuncCall {
1179 name: "infinite".to_string(),
1180 args: vec![ArithExpr::Variable("X".to_string())],
1181 }),
1182 is_private: false,
1183 };
1184 reg.register(infinite).unwrap();
1185
1186 let mut ctx = ExpansionContext::new(®, 10);
1187
1188 let result = ctx.expand_call("infinite", &[ArithExpr::Integer(1)]);
1189 assert!(matches!(
1190 result,
1191 Err(FunctionError::MaxRecursionDepth { .. })
1192 ));
1193 }
1194
1195 #[test]
1196 fn test_undefined_function() {
1197 let reg = FunctionRegistry::new();
1198 let mut ctx = ExpansionContext::new(®, 100);
1199
1200 let result = ctx.expand_call("undefined", &[ArithExpr::Integer(1)]);
1201 assert!(matches!(
1202 result,
1203 Err(FunctionError::UndefinedFunction { .. })
1204 ));
1205 }
1206
1207 #[test]
1208 fn test_builtin_function_passthrough() {
1209 let mut reg = FunctionRegistry::new();
1210
1211 let abs_x = FuncDef {
1213 name: "abs_x".to_string(),
1214 params: vec![FuncParam {
1215 name: "X".to_string(),
1216 typ: None,
1217 }],
1218 return_type: None,
1219 body: FuncBody::Arithmetic(ArithExpr::FuncCall {
1220 name: "abs".to_string(),
1221 args: vec![ArithExpr::Variable("X".to_string())],
1222 }),
1223 is_private: false,
1224 };
1225 reg.register(abs_x).unwrap();
1226
1227 let mut ctx = ExpansionContext::new(®, 100);
1228
1229 let result = ctx.expand_call("abs_x", &[ArithExpr::Integer(-5)]).unwrap();
1230
1231 match result {
1233 ArithExpr::FuncCall { name, args } => {
1234 assert_eq!(name, "abs");
1235 assert_eq!(args.len(), 1);
1236 assert!(matches!(args[0], ArithExpr::Integer(-5)));
1237 }
1238 _ => panic!("Expected FuncCall for builtin"),
1239 }
1240 }
1241
1242 #[test]
1243 fn test_variable_substitution() {
1244 let mut reg = FunctionRegistry::new();
1245
1246 let add = FuncDef {
1248 name: "add".to_string(),
1249 params: vec![
1250 FuncParam {
1251 name: "X".to_string(),
1252 typ: None,
1253 },
1254 FuncParam {
1255 name: "Y".to_string(),
1256 typ: None,
1257 },
1258 ],
1259 return_type: None,
1260 body: FuncBody::Arithmetic(ArithExpr::Add(
1261 Box::new(ArithExpr::Variable("X".to_string())),
1262 Box::new(ArithExpr::Variable("Y".to_string())),
1263 )),
1264 is_private: false,
1265 };
1266 reg.register(add).unwrap();
1267
1268 let mut ctx = ExpansionContext::new(®, 100);
1269
1270 let result = ctx
1272 .expand_call("add", &[ArithExpr::Integer(3), ArithExpr::Integer(7)])
1273 .unwrap();
1274
1275 match result {
1276 ArithExpr::Add(l, r) => {
1277 assert!(matches!(*l, ArithExpr::Integer(3)));
1278 assert!(matches!(*r, ArithExpr::Integer(7)));
1279 }
1280 _ => panic!("Expected Add expression"),
1281 }
1282 }
1283
1284 #[test]
1285 fn test_expansion_with_variable_args() {
1286 let mut reg = FunctionRegistry::new();
1287
1288 let double = FuncDef {
1290 name: "double".to_string(),
1291 params: vec![FuncParam {
1292 name: "X".to_string(),
1293 typ: None,
1294 }],
1295 return_type: None,
1296 body: FuncBody::Arithmetic(ArithExpr::Add(
1297 Box::new(ArithExpr::Variable("X".to_string())),
1298 Box::new(ArithExpr::Variable("X".to_string())),
1299 )),
1300 is_private: false,
1301 };
1302 reg.register(double).unwrap();
1303
1304 let mut ctx = ExpansionContext::new(®, 100);
1305
1306 let result = ctx
1308 .expand_call("double", &[ArithExpr::Variable("Y".to_string())])
1309 .unwrap();
1310
1311 match result {
1312 ArithExpr::Add(l, r) => {
1313 assert!(matches!(l.as_ref(), ArithExpr::Variable(n) if n == "Y"));
1314 assert!(matches!(r.as_ref(), ArithExpr::Variable(n) if n == "Y"));
1315 }
1316 _ => panic!("Expected Add expression"),
1317 }
1318 }
1319
1320 #[test]
1321 fn test_predicate_func_expansion() {
1322 let func = FuncDef {
1326 name: "get_parent".to_string(),
1327 params: vec![FuncParam {
1328 name: "X".to_string(),
1329 typ: None,
1330 }],
1331 return_type: None,
1332 body: FuncBody::Predicate {
1333 result: "P".to_string(),
1334 body: vec![BodyLiteral::Positive(Atom {
1335 predicate: "parent".to_string(),
1336 terms: vec![
1337 Term::Variable("X".to_string()),
1338 Term::Variable("P".to_string()),
1339 ],
1340 })],
1341 },
1342 is_private: false,
1343 };
1344
1345 let mut reg = FunctionRegistry::new();
1346 reg.register(func).unwrap();
1347
1348 let mut ctx = ExpansionContext::new(®, 100);
1349
1350 let args = vec![ArithExpr::Variable("alice".to_string())];
1352 let mut used = HashSet::from(["alice".to_string()]);
1353 let expanded = ctx
1354 .expand_expr_for_rule(
1355 &ArithExpr::FuncCall {
1356 name: "get_parent".to_string(),
1357 args,
1358 },
1359 &HashMap::new(),
1360 &mut used,
1361 false,
1362 )
1363 .unwrap();
1364 let body = expanded.generated_literals;
1365 let ArithExpr::Variable(result) = expanded.expression else {
1366 panic!("Expected variable result")
1367 };
1368
1369 assert_ne!(result, "P");
1370 assert_eq!(body.len(), 1);
1371
1372 if let BodyLiteral::Positive(atom) = &body[0] {
1374 assert_eq!(atom.predicate, "parent");
1375 assert!(matches!(&atom.terms[0], Term::Variable(v) if v == "alice"));
1376 assert!(matches!(&atom.terms[1], Term::Variable(v) if v == &result));
1377 } else {
1378 panic!("Expected Positive literal");
1379 }
1380 }
1381
1382 #[test]
1383 fn test_predicate_func_with_constant_arg() {
1384 let func = FuncDef {
1388 name: "get_child".to_string(),
1389 params: vec![FuncParam {
1390 name: "P".to_string(),
1391 typ: None,
1392 }],
1393 return_type: None,
1394 body: FuncBody::Predicate {
1395 result: "C".to_string(),
1396 body: vec![BodyLiteral::Positive(Atom {
1397 predicate: "parent".to_string(),
1398 terms: vec![
1399 Term::Variable("C".to_string()),
1400 Term::Variable("P".to_string()),
1401 ],
1402 })],
1403 },
1404 is_private: false,
1405 };
1406
1407 let mut reg = FunctionRegistry::new();
1408 reg.register(func).unwrap();
1409
1410 let mut ctx = ExpansionContext::new(®, 100);
1411
1412 let args = vec![ArithExpr::Integer(42)];
1414 let mut used = HashSet::new();
1415 let expanded = ctx
1416 .expand_expr_for_rule(
1417 &ArithExpr::FuncCall {
1418 name: "get_child".to_string(),
1419 args,
1420 },
1421 &HashMap::new(),
1422 &mut used,
1423 false,
1424 )
1425 .unwrap();
1426 let body = expanded.generated_literals;
1427 let ArithExpr::Variable(result) = expanded.expression else {
1428 panic!("Expected variable result")
1429 };
1430
1431 assert_ne!(result, "C");
1432 assert_eq!(body.len(), 1);
1433
1434 if let BodyLiteral::Positive(atom) = &body[0] {
1436 assert_eq!(atom.predicate, "parent");
1437 assert!(matches!(&atom.terms[0], Term::Variable(v) if v == &result));
1438 assert!(matches!(&atom.terms[1], Term::Integer(42)));
1439 } else {
1440 panic!("Expected Positive literal");
1441 }
1442 }
1443
1444 #[test]
1445 fn test_predicate_func_multiple_body_literals() {
1446 let func = FuncDef {
1450 name: "get_grandparent".to_string(),
1451 params: vec![FuncParam {
1452 name: "X".to_string(),
1453 typ: None,
1454 }],
1455 return_type: None,
1456 body: FuncBody::Predicate {
1457 result: "G".to_string(),
1458 body: vec![
1459 BodyLiteral::Positive(Atom {
1460 predicate: "parent".to_string(),
1461 terms: vec![
1462 Term::Variable("X".to_string()),
1463 Term::Variable("P".to_string()),
1464 ],
1465 }),
1466 BodyLiteral::Positive(Atom {
1467 predicate: "parent".to_string(),
1468 terms: vec![
1469 Term::Variable("P".to_string()),
1470 Term::Variable("G".to_string()),
1471 ],
1472 }),
1473 ],
1474 },
1475 is_private: false,
1476 };
1477
1478 let mut reg = FunctionRegistry::new();
1479 reg.register(func).unwrap();
1480
1481 let mut ctx = ExpansionContext::new(®, 100);
1482
1483 let args = vec![ArithExpr::Variable("alice".to_string())];
1484 let mut used = HashSet::from(["alice".to_string()]);
1485 let expanded = ctx
1486 .expand_expr_for_rule(
1487 &ArithExpr::FuncCall {
1488 name: "get_grandparent".to_string(),
1489 args,
1490 },
1491 &HashMap::new(),
1492 &mut used,
1493 false,
1494 )
1495 .unwrap();
1496 let body = expanded.generated_literals;
1497 let ArithExpr::Variable(result) = expanded.expression else {
1498 panic!("Expected variable result")
1499 };
1500
1501 assert_ne!(result, "G");
1502 assert_eq!(body.len(), 2);
1503
1504 if let BodyLiteral::Positive(atom) = &body[0] {
1506 assert_eq!(atom.predicate, "parent");
1507 assert!(matches!(&atom.terms[0], Term::Variable(v) if v == "alice"));
1508 assert!(matches!(&atom.terms[1], Term::Variable(v) if v != "P"));
1509 } else {
1510 panic!("Expected Positive literal for first body");
1511 }
1512
1513 if let BodyLiteral::Positive(atom) = &body[1] {
1515 assert_eq!(atom.predicate, "parent");
1516 assert_eq!(atom.terms[0], body[0].atom().unwrap().terms[1]);
1517 assert!(matches!(&atom.terms[1], Term::Variable(v) if v == &result));
1518 } else {
1519 panic!("Expected Positive literal for second body");
1520 }
1521 }
1522
1523 #[test]
1524 fn test_is_predicate_func() {
1525 let mut reg = FunctionRegistry::new();
1526
1527 let arith_func = FuncDef {
1529 name: "double".to_string(),
1530 params: vec![FuncParam {
1531 name: "X".to_string(),
1532 typ: None,
1533 }],
1534 return_type: None,
1535 body: FuncBody::Arithmetic(ArithExpr::Add(
1536 Box::new(ArithExpr::Variable("X".to_string())),
1537 Box::new(ArithExpr::Variable("X".to_string())),
1538 )),
1539 is_private: false,
1540 };
1541
1542 let pred_func = FuncDef {
1544 name: "get_parent".to_string(),
1545 params: vec![FuncParam {
1546 name: "X".to_string(),
1547 typ: None,
1548 }],
1549 return_type: None,
1550 body: FuncBody::Predicate {
1551 result: "P".to_string(),
1552 body: vec![BodyLiteral::Positive(Atom {
1553 predicate: "parent".to_string(),
1554 terms: vec![
1555 Term::Variable("X".to_string()),
1556 Term::Variable("P".to_string()),
1557 ],
1558 })],
1559 },
1560 is_private: false,
1561 };
1562
1563 reg.register(arith_func).unwrap();
1564 reg.register(pred_func).unwrap();
1565
1566 let ctx = ExpansionContext::new(®, 100);
1567
1568 assert!(!ctx.is_predicate_func("double"));
1569 assert!(ctx.is_predicate_func("get_parent"));
1570 assert!(!ctx.is_predicate_func("nonexistent"));
1571 }
1572
1573 #[test]
1574 fn generated_variable_names_round_trip_underscored_identifiers() {
1575 let generated = generated_function_variable_name("get_parent", "Parent_Value", 42);
1576 assert_eq!(
1577 generated_function_variable_source(&generated, "get_parent"),
1578 Some("Parent_Value")
1579 );
1580 assert_eq!(generated, "__XLOG_FUNCTION_GET_PARENT_Parent_Value_42");
1581 assert!(generated_function_variable_source(
1582 "__XLOG_FUNCTION_GET_PARENT_Parent_Value_not_a_counter",
1583 "get_parent"
1584 )
1585 .is_none());
1586 }
1587}