1use std::{
18 collections::{HashMap, HashSet},
19 fmt::{self, Debug},
20 hash::Hash,
21 io, iter,
22};
23
24use crate::{
25 analysis, builtin,
26 logic::Predicate,
27 modusfile::{self, Modusfile},
28 translate::translate_modusfile,
29 unification::{compose_extend, compose_no_extend, Rename, Substitution},
30};
31use crate::{builtin::SelectBuiltinResult, unification::RenameWithSubstitution};
32use crate::{
33 logic::{self, Signature},
34 unification::Substitute,
35 wellformed,
36};
37use codespan_reporting::diagnostic::{Diagnostic, Label, Severity};
38use colored::Colorize;
39use logic::{Clause, IRTerm, Literal};
40use ptree::{item::StringItem, print_tree, TreeBuilder, TreeItem};
41
42pub trait Auxiliary: Rename<Self> + Sized {
43 fn aux(anonymous: bool) -> Self;
44}
45
46type RuleId = usize;
47type GoalId = usize;
48type TreeLevel = usize;
49pub(crate) type Goal<T = IRTerm> = Vec<Literal<T>>;
50
51#[derive(Clone, PartialEq, Eq, Hash, Debug)]
53pub enum ClauseId {
54 Rule(RuleId),
55 Query,
56 Builtin(Literal<IRTerm>),
57
58 NegationCheck(Literal<IRTerm>),
61}
62
63#[derive(Clone, PartialEq, Debug)]
65pub struct LiteralOrigin {
66 clause: ClauseId,
67 body_index: usize,
68}
69
70type LiteralGoalId = usize;
72
73#[derive(Clone, PartialEq, Debug)]
75struct LiteralWithHistory {
76 literal: Literal<IRTerm>,
77 introduction: TreeLevel,
78 origin: LiteralOrigin,
79}
80type GoalWithHistory = Vec<LiteralWithHistory>;
81
82#[derive(Clone, PartialEq, Debug)]
87pub struct Tree {
88 goal: GoalWithHistory,
89 level: TreeLevel,
90
91 success_resolvents: HashMap<(LiteralGoalId, ClauseId), (Substitution, Substitution, Tree)>,
93
94 fail_resolvents: HashMap<(LiteralGoalId, ClauseId), (Substitution, Substitution, Tree)>,
96
97 error: Option<ResolutionError>,
103}
104
105impl Tree {
106 fn is_success(&self) -> bool {
107 self.goal.is_empty() || !self.success_resolvents.is_empty()
108 }
109
110 fn resolvents(
111 &self,
112 ) -> HashMap<&(usize, ClauseId), &(HashMap<IRTerm, IRTerm>, HashMap<IRTerm, IRTerm>, Tree)>
113 {
114 self.success_resolvents
115 .iter()
116 .chain(&self.fail_resolvents)
117 .collect::<HashMap<_, _>>()
118 }
119
120 pub fn to_graph(&self, rules: &[Clause]) -> Graph {
122 fn convert(
123 t: &Tree,
124 rules: &[Clause],
125 nodes: &mut Vec<String>,
126 edges: &mut Vec<(usize, usize, String)>,
127 ) {
128 let curr_label = t
129 .goal
130 .iter()
131 .map(|l| l.literal.to_string())
132 .collect::<Vec<_>>()
133 .join(",\n");
134 nodes.push(curr_label);
135 let curr_index = nodes.len() - 1;
136
137 if !t.goal.is_empty() && t.resolvents().is_empty() {
138 let err = t
139 .error
140 .as_ref()
141 .expect("Failed path should store SLD error.");
142 let error_msg = err.to_string();
143 nodes.push("FAIL".to_string());
144 edges.push((curr_index, curr_index + 1, error_msg));
145 } else {
146 let mut resolvent_pairs = t.resolvents().into_iter().collect::<Vec<_>>();
147 resolvent_pairs.sort_by_key(|(k, _)| k.0); for (k, v) in resolvent_pairs {
150 let new_index = nodes.len();
151 convert(&v.2, rules, nodes, edges);
152
153 let edge_label = match &k.1 {
154 ClauseId::Rule(rid) => rules[*rid].head.to_string(),
155 ClauseId::Query => "query".to_string(),
156 ClauseId::Builtin(lit) => lit.to_string(),
157 ClauseId::NegationCheck(lit) => format!("Check {lit}?"),
158 };
159 edges.push((curr_index, new_index, edge_label));
160 }
161 }
162 }
163
164 let name = "SLD_Tree";
165 let mut nodes = Vec::new();
166 let mut edges = Vec::new();
167 convert(self, rules, &mut nodes, &mut edges);
168 Graph { name, nodes, edges }
169 }
170
171 pub fn explain(&self, rules: &[Clause]) -> StringItem {
173 fn dfs(t: &Tree, rules: &[Clause], builder: &mut TreeBuilder) {
174 let mut resolvent_pairs = t.resolvents().into_iter().collect::<Vec<_>>();
175 resolvent_pairs.sort_by_key(|(k, _)| k.0);
176
177 for (k, v) in &resolvent_pairs {
178 let (goal_id, cid) = k;
179
180 let substitution_map: HashMap<_, _> =
181 v.0.iter()
182 .map(|(t1, t2)| (t1.get_original().clone(), t2.clone()))
183 .collect();
184
185 let requirement = match cid {
186 ClauseId::Rule(rid) => rules[*rid]
187 .substitute(&substitution_map)
188 .body
189 .iter()
190 .map(|x| x.to_string())
191 .collect::<Vec<_>>()
192 .join(", "),
193 ClauseId::Query => unimplemented!(),
194 ClauseId::Builtin(lit) => lit.substitute(&v.0).to_string(),
195 ClauseId::NegationCheck(lit) => {
196 format!("{} to have no proof", lit)
197 }
198 };
199 let curr_attempt = format!(
200 "{} requires {}",
201 t.goal[*goal_id].literal,
202 if requirement.is_empty() {
203 "nothing, it's a fact.".to_owned().italic()
204 } else {
205 requirement.italic()
206 }
207 );
208
209 if let Some(e) = &v.2.error {
210 builder.begin_child(format!("{}", e.to_string().bright_red()));
211 } else {
212 builder.begin_child(curr_attempt);
213 }
214 dfs(&v.2, rules, builder);
215 builder.end_child();
216 }
217
218 if t.goal.is_empty() {
219 builder.add_empty_child(format!("{}", "Success".green()));
220 }
221 }
222
223 let mut builder = TreeBuilder::new(format!(
224 "We require {}",
225 self.goal
226 .iter()
227 .map(|l| l.literal.to_string())
228 .collect::<Vec<_>>()
229 .join(", ")
230 ));
231 dfs(self, rules, &mut builder);
232 builder.build()
233 }
234}
235
236type Nd<'a> = (usize, &'a str);
237type Ed<'a> = (Nd<'a>, Nd<'a>, &'a str);
238
239pub struct Graph {
240 name: &'static str,
241 nodes: Vec<String>,
242 edges: Vec<(usize, usize, String)>, }
244
245impl<'a> dot::Labeller<'a, Nd<'a>, Ed<'a>> for Graph {
246 fn graph_id(&'a self) -> dot::Id<'a> {
247 dot::Id::new(self.name).unwrap()
248 }
249
250 fn node_id(&'a self, n: &Nd) -> dot::Id<'a> {
251 dot::Id::new(format!("N{}", n.0)).unwrap()
252 }
253
254 fn node_label(&'a self, n: &Nd<'a>) -> dot::LabelText<'a> {
255 dot::LabelText::LabelStr(n.1.into())
256 }
257
258 fn edge_label(&'a self, e: &Ed<'a>) -> dot::LabelText<'a> {
259 dot::LabelText::LabelStr(e.2.into())
260 }
261}
262
263impl<'a> dot::GraphWalk<'a, Nd<'a>, Ed<'a>> for Graph {
264 fn nodes(&'a self) -> dot::Nodes<'a, Nd<'a>> {
265 self.nodes.iter().map(|n| &n[..]).enumerate().collect()
266 }
267
268 fn edges(&'a self) -> dot::Edges<'a, Ed> {
269 self.edges
270 .iter()
271 .map(|(i, j, label)| {
272 (
273 (*i, &self.nodes[*i][..]),
274 (*j, &self.nodes[*j][..]),
275 &label[..],
276 )
277 })
278 .collect()
279 }
280
281 fn source(&'a self, edge: &Ed<'a>) -> Nd {
282 edge.0
283 }
284
285 fn target(&'a self, edge: &Ed<'a>) -> Nd {
286 edge.1
287 }
288}
289
290#[derive(Clone, Debug, PartialEq)]
295pub struct Proof {
296 pub clause: ClauseId,
297 pub valuation: Substitution,
298 pub children: Vec<Proof>,
299}
300
301impl Proof {
302 fn height(&self) -> usize {
304 self.children
305 .iter()
306 .map(|child| child.height() + 1)
307 .max()
308 .unwrap_or(0)
309 }
310
311 pub fn get_tree(
312 &self,
313 clauses: &Vec<Clause>,
314 pred_kind: &HashMap<Predicate, analysis::Kind>,
315 compact: bool,
316 ) -> impl TreeItem {
317 fn find_matching_end(
318 operator_start: &Literal,
319 start_index: usize,
320 children: &[Proof],
321 ) -> Option<usize> {
322 for i in start_index + 1..children.len() {
323 let child = &children[i];
324 if let ClauseId::Builtin(b) = &child.clause {
325 if b.predicate.naive_predicate_kind().is_logic()
326 && b.predicate.is_operator()
327 && b.predicate.0.ends_with("_end")
328 && b.predicate.0.replace("_end", "_begin") == operator_start.predicate.0
329 && b.args.last() == operator_start.args.last()
330 {
331 return Some(i);
332 }
333 }
334 }
335 None
336 }
337
338 fn dfs(
339 p: &Proof,
340 clauses: &Vec<Clause>,
341 builder: &mut TreeBuilder,
342 pred_kind: &HashMap<Predicate, analysis::Kind>,
343 compact: bool,
344 ) {
345 let mut prev_scope_start_index = 0;
346 let mut prev_scope_end_index = 0;
347 let mut dont_close: HashSet<usize> = HashSet::new();
348
349 for (i, child) in p.children.iter().enumerate() {
350 match &child.clause {
351 ClauseId::Rule(rid) => {
352 if !compact {
353 let s = clauses[*rid].head.substitute(&child.valuation).to_string();
354 builder.begin_child(format!(
355 "{}",
356 if pred_kind.get(&clauses[*rid].head.predicate)
357 == Some(&analysis::Kind::Image)
358 {
359 s.cyan()
360 } else {
361 s.normal()
362 },
363 ));
364 }
365 dfs(&child, clauses, builder, pred_kind, compact);
366 if !compact {
367 builder.end_child();
368 }
369 }
370 ClauseId::Query => {
371 builder.add_empty_child("query".to_string());
372 }
373 ClauseId::Builtin(b) => match b.predicate.naive_predicate_kind() {
374 crate::analysis::Kind::Image => {
375 builder.add_empty_child(format!(
376 "{}",
377 b.substitute(&child.valuation).to_string().cyan()
378 ));
379 }
380 crate::analysis::Kind::Layer => {
381 builder.add_empty_child(format!(
382 "{}",
383 b.substitute(&child.valuation).to_string()
384 ));
385 }
386 crate::analysis::Kind::Logic => {
387 if b.predicate.is_operator() {
388 if b.predicate.0.ends_with("_begin") {
389 let curr_scope_end_index = find_matching_end(b, i, &p.children)
390 .expect("begin operator has an end");
391
392 let curr_scope_start_index = i;
393 if prev_scope_start_index + 1 == curr_scope_start_index
394 && curr_scope_end_index + 1 == prev_scope_end_index
395 {
396 dont_close.insert(prev_scope_end_index);
398 } else {
399 builder.begin_child("(".to_string());
400 }
401 prev_scope_start_index = i;
402 prev_scope_end_index = curr_scope_end_index;
403 } else {
404 if !dont_close.contains(&i) {
405 builder.end_child();
406 }
407
408 builder.add_empty_child(format!(
412 "{}::{}",
413 if dont_close.contains(&i) { " " } else { ")" },
414 b.substitute(&child.valuation).unmangle().to_string()
415 ));
416 }
417 }
418 }
419 },
420 ClauseId::NegationCheck(_) => {} }
422 }
423 }
424
425 let mut builder = TreeBuilder::new("".to_string());
426 dfs(self, clauses, &mut builder, pred_kind, compact);
427
428 builder.build()
429 }
430
431 pub fn pretty_print(
432 &self,
433 clauses: &Vec<Clause>,
434 pred_kind: &HashMap<Predicate, analysis::Kind>,
435 compact: bool,
436 ) -> io::Result<()> {
437 print_tree(&self.get_tree(clauses, pred_kind, compact))
438 }
439}
440
441impl PartialOrd for Proof {
442 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
443 self.height().partial_cmp(&other.height())
444 }
445}
446
447impl Substitute<IRTerm> for GoalWithHistory {
448 type Output = GoalWithHistory;
449 fn substitute(&self, s: &Substitution<IRTerm>) -> Self::Output {
450 self.iter()
451 .map(
452 |LiteralWithHistory {
453 literal,
454 introduction,
455 origin,
456 }| LiteralWithHistory {
457 literal: literal.substitute(s),
458 introduction: *introduction,
459 origin: origin.clone(),
460 },
461 )
462 .collect()
463 }
464}
465
466#[derive(Clone, Eq, PartialEq, Debug, Hash)]
467pub enum ResolutionError {
468 UnknownPredicate(Literal),
470 InsufficientGroundness(Vec<Literal>),
472 MaximumDepthExceeded(Vec<Literal>, usize),
474 BuiltinFailure(Literal, &'static str),
476 InsufficientRules(Literal),
478 InconsistentGroundnessSignature(Vec<Signature>),
480 NegationProof(Literal),
482}
483
484impl fmt::Display for ResolutionError {
485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486 match self {
487 ResolutionError::UnknownPredicate(literal) => {
488 if literal.predicate.is_operator() {
489 write!(f, "unknown operator - {}", literal.predicate)
490 } else {
491 write!(f, "unknown predicate - {}", literal.predicate)
492 }
493 }
494 ResolutionError::InsufficientGroundness(literals) => {
495 write!(f, "insufficient groundness for {} goal(s)", literals.len())
496 }
497 ResolutionError::MaximumDepthExceeded(_, max_depth) => {
498 write!(f, "exceeded maximum depth of {}", max_depth)
499 }
500 ResolutionError::BuiltinFailure(_, builtin_name) => {
501 write!(f, "builtin {} failed to apply or unify", builtin_name)
502 }
503 ResolutionError::InsufficientRules(literal) => write!(
504 f,
505 "could not find a rule to resolve with literal {}",
506 literal
507 ),
508 ResolutionError::InconsistentGroundnessSignature(signatures) => write!(
509 f,
510 "{} clause(s) have inconsistent signatures",
511 signatures.len()
512 ),
513 ResolutionError::NegationProof(lit) => {
514 write!(f, "A proof was found for {}", lit.negated())
515 }
516 }
517 }
518}
519
520impl ResolutionError {
521 pub fn get_diagnostic(self) -> Diagnostic<()> {
522 fn get_position_labels(literals: &[Literal]) -> Vec<Label<()>> {
523 literals
524 .iter()
525 .filter_map(|lit| lit.position.as_ref())
526 .map(|pos| Label::primary((), pos.offset..(pos.offset + pos.length)))
527 .collect()
528 }
529
530 fn get_notes(literals: &[Literal]) -> Vec<String> {
532 literals
533 .iter()
534 .filter_map(|lit| {
535 if lit.position.is_none() {
536 Some(lit.to_string())
537 } else {
538 None
539 }
540 })
541 .collect()
542 }
543
544 let message = self.to_string();
545 let (labels, notes, severity) = match self {
546 ResolutionError::UnknownPredicate(literal) => (
547 get_position_labels(&[literal.clone()]),
548 get_notes(&[literal]),
549 Severity::Error,
550 ),
551 ResolutionError::InsufficientGroundness(literals) => (
552 get_position_labels(&literals),
553 get_notes(&literals),
554 Severity::Warning,
555 ),
556 ResolutionError::MaximumDepthExceeded(literals, _) => (
557 get_position_labels(&literals),
558 get_notes(&literals),
559 Severity::Warning,
560 ),
561 ResolutionError::BuiltinFailure(literal, _) => (
562 get_position_labels(&[literal.clone()]),
563 get_notes(&[literal]),
564 Severity::Warning,
565 ),
566 ResolutionError::InsufficientRules(literal) => (
567 get_position_labels(&[literal.clone()]),
568 get_notes(&[literal]),
569 Severity::Warning,
570 ),
571 ResolutionError::InconsistentGroundnessSignature(sigs) => (
572 Vec::new(),
573 sigs.iter().map(|sig| sig.to_string()).collect(),
574 Severity::Error,
575 ),
576 ResolutionError::NegationProof(lit) => (
577 get_position_labels(&[lit.clone()]),
578 get_notes(&[lit]),
579 Severity::Warning,
580 ),
581 };
582
583 Diagnostic::new(severity)
584 .with_message(message)
585 .with_labels(labels)
586 .with_notes(notes)
587 }
588}
589
590pub struct SLDResult {
595 pub tree: Tree,
596 pub errors: HashSet<ResolutionError>,
597}
598
599impl From<SLDResult> for Result<Tree, Vec<Diagnostic<()>>> {
600 fn from(sld_result: SLDResult) -> Self {
601 if sld_result.tree.is_success() {
602 Ok(sld_result.tree)
603 } else {
604 Err(sld_result
605 .errors
606 .into_iter()
607 .map(ResolutionError::get_diagnostic)
608 .collect::<Vec<_>>())
609 }
610 }
611}
612
613pub fn sld(
616 rules: &[Clause<IRTerm>],
617 goal: &Goal,
618 maxdepth: TreeLevel,
619 store_full_tree: bool,
620) -> SLDResult {
621 fn select(
623 goal: &GoalWithHistory,
624 grounded: &HashMap<Signature, Vec<bool>>,
625 ) -> Result<(LiteralGoalId, LiteralWithHistory), ResolutionError> {
626 for (id, lit) in goal.iter().enumerate() {
627 let literal = &lit.literal;
630
631 let positive_or_grounded_negation = literal.positive
637 || literal
638 .args
639 .iter()
640 .all(|arg| arg.is_constant() || arg.is_underlying_anonymous_variable());
641
642 let select_builtin_res = builtin::select_builtin(literal);
643 if select_builtin_res.0.is_match() && positive_or_grounded_negation {
644 return Ok((id, lit.clone()));
645 }
646
647 let lit_grounded = grounded.get(&literal.signature());
652 if let Some(lit_grounded) = lit_grounded {
653 debug_assert_eq!(lit_grounded.len(), literal.args.len());
654 if positive_or_grounded_negation
655 && literal.args.iter().zip(lit_grounded.iter()).all(
656 |(term, allows_ungrounded)| {
657 *allows_ungrounded || term.is_constant_or_compound_constant()
658 },
659 )
660 {
661 return Ok((id, lit.clone()));
662 } else {
663 continue;
664 }
665 } else if select_builtin_res.0 == SelectBuiltinResult::GroundnessMismatch
666 || (select_builtin_res.0 == SelectBuiltinResult::Match
667 && !positive_or_grounded_negation)
668 {
669 continue;
670 }
671
672 return Err(ResolutionError::UnknownPredicate(literal.clone()));
673 }
674
675 Err(ResolutionError::InsufficientGroundness(
676 goal.iter().map(|lit| lit.literal.clone()).collect(),
677 ))
678 }
679
680 fn resolve(
687 lid: LiteralGoalId,
688 rid: ClauseId,
689 goal: &GoalWithHistory,
690 mgu: &Substitution,
691 rule: &Clause,
692 level: TreeLevel,
693 ) -> GoalWithHistory {
694 let new_goals = rule.body.iter().enumerate().map(|(id, l)| {
695 let origin = LiteralOrigin {
696 clause: rid.clone(),
697 body_index: id,
698 };
699 LiteralWithHistory {
700 literal: l.clone(),
701 introduction: level,
702 origin,
703 }
704 });
705 let g = new_goals
706 .chain(goal.into_iter().enumerate().filter_map(|(i, v)| {
707 if i != lid {
708 Some(v.clone())
709 } else {
710 None
711 }
712 }))
713 .collect::<Vec<_>>();
714 g.substitute(mgu)
715 }
716
717 fn handle_negated_literal(
718 lid: LiteralGoalId,
719 l: LiteralWithHistory,
720 goal: &GoalWithHistory,
721 rules: &[Clause<IRTerm>],
722 maxdepth: TreeLevel,
723 level: TreeLevel,
724 grounded: &HashMap<Signature, Vec<bool>>,
725 store_full_tree: bool,
726 ) -> SLDResult {
727 let mut errs: HashSet<ResolutionError> = HashSet::new();
728
729 let singleton_goal = vec![LiteralWithHistory {
730 literal: l.literal.negated(),
731 ..l
732 }];
733
734 let sld_res = inner(
736 rules,
737 &singleton_goal,
738 maxdepth,
740 0,
741 grounded,
742 store_full_tree,
743 );
744
745 let rid = ClauseId::NegationCheck(l.literal.negated());
746 let mgu = HashMap::new();
747 let renaming = HashMap::new();
748
749 let mut success_resolvents = HashMap::new();
750 let mut fail_resolvents = HashMap::new();
751 if sld_res.tree.is_success() {
752 if store_full_tree {
753 fail_resolvents.insert((lid, rid), (mgu, renaming, sld_res.tree));
754 }
755
756 let err = ResolutionError::NegationProof(l.literal);
757 errs.insert(err.clone());
758 let tree = Tree {
759 goal: goal.to_owned(),
760 level,
761 success_resolvents,
762 fail_resolvents,
763 error: Some(err),
764 };
765
766 return SLDResult { tree, errors: errs };
767 } else {
768 let resolvent = resolve(
769 lid,
770 rid.clone(),
771 goal,
772 &mgu,
773 &Clause {
774 head: l.literal,
775 body: Vec::new(),
776 },
777 level + 1,
778 );
779 let SLDResult { tree, errors } = inner(
780 rules,
781 &resolvent,
782 maxdepth,
783 level + 1,
784 grounded,
785 store_full_tree,
786 );
787
788 if tree.is_success() {
789 success_resolvents.insert((lid, rid), (mgu, renaming, tree));
790 } else if store_full_tree {
791 fail_resolvents.insert((lid, rid), (mgu, renaming, tree));
792 }
793 errs.extend(errors);
794
795 let tree = Tree {
796 goal: goal.to_owned(),
797 level,
798 success_resolvents,
799 fail_resolvents,
800 error: None,
801 };
802
803 return SLDResult { tree, errors: errs };
804 }
805 }
806
807 fn inner(
808 rules: &[Clause<IRTerm>],
809 goal: &GoalWithHistory,
810 maxdepth: TreeLevel,
811 level: TreeLevel,
812 grounded: &HashMap<Signature, Vec<bool>>,
813 store_full_tree: bool,
814 ) -> SLDResult {
815 if goal.is_empty() {
816 let t = Tree {
817 goal: goal.to_owned(),
818 level,
819 success_resolvents: HashMap::new(),
820 fail_resolvents: HashMap::new(),
821 error: None,
822 };
823 SLDResult {
824 tree: t,
825 errors: HashSet::new(),
826 }
827 } else if level >= maxdepth {
828 let error = ResolutionError::MaximumDepthExceeded(
829 goal.iter()
830 .map(|lit_hist| lit_hist.literal.clone())
831 .collect(),
832 maxdepth,
833 );
834 let t = Tree {
835 goal: goal.to_owned(),
836 level,
837 success_resolvents: HashMap::default(),
838 fail_resolvents: HashMap::default(),
839 error: Some(error.clone()),
840 };
841 let errors = vec![error].into_iter().collect();
842 SLDResult { tree: t, errors }
843 } else {
844 let selection_res = select(goal, grounded);
845 if let Err(e) = selection_res {
846 let t = Tree {
847 goal: goal.to_owned(),
848 level,
849 success_resolvents: HashMap::default(),
850 fail_resolvents: HashMap::default(),
851 error: Some(e.clone()),
852 };
853 return SLDResult {
854 tree: t,
855 errors: vec![e].into_iter().collect(),
856 };
857 }
858 let (lid, l) = selection_res.unwrap();
859
860 if !l.literal.positive {
861 return handle_negated_literal(
862 lid,
863 l,
864 goal,
865 rules,
866 maxdepth,
867 level,
868 grounded,
869 store_full_tree,
870 );
871 }
872
873 let mut errs: HashSet<ResolutionError> = HashSet::new();
874
875 let selected_builtin = builtin::select_builtin(&l.literal);
876 let builtin_resolves = match selected_builtin {
877 (SelectBuiltinResult::Match, lit) => lit,
878 _ => None,
879 }
880 .and_then(|pred| pred.apply(&l.literal))
881 .and_then(|unify_cand| {
882 unify_cand.unify(&l.literal).map(|mgu| {
883 (
884 ClauseId::Builtin(unify_cand.clone()),
885 mgu.clone(),
886 Substitution::<IRTerm>::new(),
887 resolve(
888 lid,
889 ClauseId::Builtin(unify_cand.clone()),
890 goal,
891 &mgu,
892 &Clause {
893 head: unify_cand,
894 body: Vec::new(), },
896 level + 1,
897 ),
898 )
899 })
900 });
901
902 let mut leaf_error = None;
903 if selected_builtin.0.is_match() && builtin_resolves.is_none() {
904 let err = ResolutionError::BuiltinFailure(
905 l.literal.clone(),
906 selected_builtin
907 .1
908 .expect("match should provide builtin")
909 .name(),
910 );
911 errs.insert(err.clone());
912 leaf_error = Some(err);
913 }
914
915 let user_rules_resolves = rules
916 .iter()
917 .enumerate()
918 .filter(|(_, c)| c.head.signature() == l.literal.signature())
919 .map(|(rid, c)| (ClauseId::Rule(rid), c.rename_with_sub()))
920 .filter_map(|(rid, (c, renaming))| {
921 c.head.unify(&l.literal).map(|mgu| {
922 (
923 rid.clone(),
924 mgu.clone(),
925 renaming,
926 resolve(lid, rid, goal, &mgu, &c, level + 1),
927 )
928 })
929 })
930 .collect::<Vec<_>>();
931 if !selected_builtin.0.is_match() && user_rules_resolves.is_empty() {
932 let err = ResolutionError::InsufficientRules(l.literal.clone());
933 errs.insert(err.clone());
934 leaf_error = leaf_error.or(Some(err));
935 }
936
937 let mut success_resolvents: HashMap<
938 (LiteralGoalId, ClauseId),
939 (Substitution, Substitution, Tree),
940 > = HashMap::new();
941 let mut fail_resolvents: HashMap<
942 (LiteralGoalId, ClauseId),
943 (Substitution, Substitution, Tree),
944 > = HashMap::new();
945 for (rid, mgu, renaming, resolvent) in
946 builtin_resolves.into_iter().chain(user_rules_resolves)
947 {
948 let SLDResult { tree, errors } = inner(
949 rules,
950 &resolvent,
951 maxdepth,
952 level + 1,
953 grounded,
954 store_full_tree,
955 );
956 if tree.is_success() {
957 success_resolvents.insert((lid, rid), (mgu, renaming, tree));
958 } else if store_full_tree {
959 fail_resolvents.insert((lid, rid), (mgu, renaming, tree));
960 }
961 errs.extend(errors);
962 }
963
964 let tree = Tree {
965 goal: goal.to_owned(),
966 level,
967 success_resolvents,
968 fail_resolvents,
969 error: leaf_error,
970 };
971
972 SLDResult { tree, errors: errs }
973 }
974 }
975
976 let grounded_result = wellformed::check_grounded_variables(rules);
977 let goal_with_history = goal
978 .iter()
979 .enumerate()
980 .map(|(id, l)| {
981 let origin = LiteralOrigin {
982 clause: ClauseId::Query,
983 body_index: id,
984 };
985 LiteralWithHistory {
986 literal: l.clone(),
987 introduction: 0,
988 origin,
989 }
990 })
991 .collect();
992 match grounded_result {
993 Ok(grounded) => inner(
994 rules,
995 &goal_with_history,
996 maxdepth,
997 0,
998 &grounded,
999 store_full_tree,
1000 ),
1001 Err(e) => SLDResult {
1002 tree: Tree {
1003 goal: goal_with_history,
1004 level: 0,
1005 success_resolvents: HashMap::default(),
1006 fail_resolvents: HashMap::default(),
1007 error: Some(ResolutionError::InconsistentGroundnessSignature(
1008 e.iter().cloned().collect(),
1009 )),
1010 },
1011 errors: vec![ResolutionError::InconsistentGroundnessSignature(
1012 e.into_iter().collect(),
1013 )]
1014 .into_iter()
1015 .collect(),
1016 },
1017 }
1018}
1019
1020pub fn solutions(tree: &Tree) -> HashSet<Goal> {
1021 fn inner(tree: &Tree) -> Vec<Substitution> {
1022 if tree.goal.is_empty() {
1023 let s = Substitution::new();
1024 return vec![s];
1025 }
1026 tree.success_resolvents
1027 .iter()
1028 .map(|(_, (mgu, _, subtree))| (mgu, inner(subtree)))
1029 .map(|(mgu, sub)| {
1030 sub.iter()
1031 .map(|s| compose_extend(mgu, s))
1032 .collect::<Vec<Substitution<IRTerm>>>()
1033 })
1034 .flatten()
1035 .collect()
1036 }
1037 inner(tree)
1038 .iter()
1039 .map(|s| {
1040 tree.goal
1041 .iter()
1042 .map(
1043 |LiteralWithHistory {
1044 literal,
1045 introduction: _,
1046 origin: _,
1047 }| literal.substitute(s),
1048 )
1049 .collect()
1050 })
1051 .collect()
1052}
1053
1054#[derive(Clone)]
1055struct PathNode {
1056 resolvent: GoalWithHistory,
1057 applied: ClauseId,
1058 selected: LiteralGoalId,
1059 renaming: Substitution,
1060}
1061
1062type Path = (Vec<PathNode>, Substitution);
1064
1065pub fn proofs(tree: &Tree, rules: &[Clause], goal: &Goal) -> HashMap<Goal, Proof> {
1066 fn flatten_compose(
1067 lid: &LiteralGoalId,
1068 cid: &ClauseId,
1069 mgu: &Substitution,
1070 renaming: &Substitution,
1071 tree: &Tree,
1072 ) -> Vec<Path> {
1073 if tree.goal.is_empty() {
1074 return vec![(
1075 vec![PathNode {
1076 resolvent: tree.goal.clone(),
1077 applied: cid.clone(),
1078 selected: *lid,
1079 renaming: renaming.clone(),
1080 }],
1081 mgu.clone(),
1082 )];
1083 }
1084 tree.success_resolvents
1085 .iter()
1086 .map(|((sub_lid, sub_cid), (sub_mgu, sub_renaming, sub_tree))| {
1087 flatten_compose(sub_lid, sub_cid, sub_mgu, sub_renaming, sub_tree)
1088 .iter()
1089 .map(|(sub_path, sub_val)| {
1090 let mut nodes = vec![PathNode {
1091 resolvent: tree.goal.clone(),
1092 applied: cid.clone(),
1093 selected: *lid,
1094 renaming: renaming.clone(),
1095 }];
1096 let val = compose_extend(mgu, sub_val);
1097 nodes.extend(sub_path.clone());
1098 (nodes, val)
1099 })
1100 .collect::<Vec<Path>>()
1101 })
1102 .flatten()
1103 .collect()
1104 }
1105 fn proof_for_level(
1107 path: &[PathNode],
1108 mgu: &Substitution,
1109 rules: &[Clause],
1110 level: TreeLevel,
1111 ) -> Proof {
1112 let mut sublevels_map: HashMap<usize, TreeLevel> = HashMap::new();
1113 for l in 0..path.len() {
1114 if !path[l].resolvent.is_empty() {
1115 let resolved_child = path[l].resolvent[path[l + 1].selected].clone();
1116 if resolved_child.introduction == level {
1117 sublevels_map.insert(resolved_child.origin.body_index, l + 1);
1118 }
1119 }
1120 }
1121 let children_length = sublevels_map.len();
1122 match path[level].applied {
1123 ClauseId::Query => assert_eq!(children_length, path[0].resolvent.len()),
1124 ClauseId::Rule(rid) => assert_eq!(children_length, rules[rid].body.len()),
1125 ClauseId::Builtin(_) => assert_eq!(children_length, 0),
1126 ClauseId::NegationCheck(_) => assert_eq!(children_length, 0),
1129 };
1130
1131 let mut sublevels = Vec::<TreeLevel>::with_capacity(sublevels_map.len());
1132 for k in sublevels_map.keys() {
1133 assert!(*k < children_length);
1134 }
1135 for i in 0..children_length {
1136 sublevels.push(*sublevels_map.get(&i).unwrap());
1137 }
1138 Proof {
1139 clause: path[level].applied.clone(),
1140 valuation: compose_no_extend(&path[level].renaming, mgu),
1141 children: sublevels
1142 .iter()
1143 .map(|l| proof_for_level(path, mgu, rules, *l))
1144 .collect(),
1145 }
1146 }
1147 let goal_vars = goal
1149 .iter()
1150 .map(|l| l.variables(true))
1151 .reduce(|mut l, r| {
1152 l.extend(r);
1153 l
1154 })
1155 .unwrap_or_default();
1156 let goal_id_renaming: Substitution<IRTerm> =
1157 goal_vars.iter().map(|v| (v.clone(), v.clone())).collect();
1158 let paths = flatten_compose(
1159 &0,
1160 &ClauseId::Query,
1161 &Substitution::new(),
1162 &goal_id_renaming,
1163 tree,
1164 );
1165 let all_proofs: Vec<Proof> = paths
1166 .iter()
1167 .map(|(path, mgu)| proof_for_level(path, mgu, rules, 0))
1168 .collect();
1169
1170 let mut solution_to_proof_tree: HashMap<Goal, Proof> = HashMap::new();
1171 for p in all_proofs {
1172 let solution: Goal = goal.substitute(&p.valuation);
1173 if let Some(existing_proof) = solution_to_proof_tree.get(&solution) {
1175 if existing_proof <= &p {
1176 continue;
1177 }
1178 }
1179 solution_to_proof_tree.insert(solution, p);
1180 }
1181 solution_to_proof_tree
1182}
1183
1184pub fn tree_from_modusfile(
1185 mf: Modusfile,
1186 query: modusfile::Expression,
1187 max_depth: usize,
1188 full_tree: bool,
1189) -> (Goal, Vec<Clause>, SLDResult) {
1190 let goal_pred = Predicate("_query".to_owned());
1195 let user_clause = modusfile::ModusClause {
1196 head: Literal {
1197 positive: true,
1198 position: None,
1199 predicate: goal_pred.clone(),
1200 args: Vec::new(),
1201 },
1202 body: Some(query),
1203 };
1204 let clauses: Vec<Clause> = translate_modusfile(&Modusfile(
1205 mf.0.into_iter().chain(iter::once(user_clause)).collect(),
1206 ));
1207
1208 let q_clause = clauses
1209 .iter()
1210 .find(|c| c.head.predicate == goal_pred)
1211 .expect("should find same predicate name after translation");
1212 let goal = &q_clause.body;
1213
1214 (
1215 goal.clone(),
1216 clauses.clone(),
1217 sld(&clauses, &goal, max_depth, full_tree),
1218 )
1219}
1220
1221#[cfg(test)]
1222mod tests {
1223 use crate::{
1224 logic::SpannedPosition,
1225 modusfile::{Expression, FormatStringFragment, ModusTerm},
1226 };
1227
1228 use super::*;
1229 use serial_test::serial;
1230
1231 fn contains_ignoring_position(it: &HashSet<Vec<Literal>>, lits: &Vec<Literal>) -> bool {
1237 it.iter().any(|curr_lits| {
1238 curr_lits.len() == lits.len()
1239 && curr_lits
1240 .iter()
1241 .zip(lits)
1242 .all(|(l1, l2)| l1.eq_ignoring_position(&l2))
1243 })
1244 }
1245
1246 #[test]
1247 #[serial]
1248 fn simple_solving() {
1249 let goal: Goal<logic::IRTerm> = vec!["a(X)".parse().unwrap()];
1250 let clauses: Vec<logic::Clause> = vec![
1251 "a(X) :- b(X).".parse().unwrap(),
1252 logic::Clause {
1253 head: "b(\"c\")".parse().unwrap(),
1254 body: vec![],
1255 },
1256 logic::Clause {
1257 head: "b(\"d\")".parse().unwrap(),
1258 body: vec![],
1259 },
1260 ];
1261 let tree = sld(&clauses, &goal, 10, true).tree;
1262 let solutions = solutions(&tree);
1263 assert_eq!(solutions.len(), 2);
1264
1265 assert!(contains_ignoring_position(
1266 &solutions,
1267 &vec!["a(\"c\")".parse::<logic::Literal>().unwrap()]
1268 ));
1269 assert!(contains_ignoring_position(
1270 &solutions,
1271 &vec!["a(\"d\")".parse::<logic::Literal>().unwrap()]
1272 ));
1273 }
1274
1275 #[test]
1276 #[serial]
1277 fn simple_solving_with_escape_chars() {
1278 let goal: Goal<logic::IRTerm> = vec!["a".parse().unwrap()];
1279 let clauses: Vec<logic::Clause> = vec![
1280 r#"a :- b("\n")."#.parse().unwrap(),
1281 logic::Clause {
1282 head: r#"b("\n")"#.parse().unwrap(),
1283 body: vec![],
1284 },
1285 ];
1286 let tree = sld(&clauses, &goal, 10, true).tree;
1287 let solutions = solutions(&tree);
1288 assert_eq!(solutions.len(), 1);
1289
1290 assert!(contains_ignoring_position(
1291 &solutions,
1292 &vec![r#"a"#.parse::<logic::Literal>().unwrap()]
1293 ));
1294 }
1295
1296 #[test]
1297 #[serial]
1298 fn simple_negation_solving() {
1299 let goal: Goal<logic::IRTerm> = vec!["a(\"c\")".parse().unwrap()];
1300 let clauses: Vec<logic::Clause> = vec![
1301 "a(X) :- !b(X).".parse().unwrap(),
1302 "b(\"d\").".parse().unwrap(),
1303 ];
1304 let sld_res = sld(&clauses, &goal, 10, true);
1305 let tree = sld_res.tree;
1306 let solutions = solutions(&tree);
1307 assert_eq!(solutions.len(), 1);
1308
1309 assert!(contains_ignoring_position(
1310 &solutions,
1311 &vec!["a(\"c\")".parse::<logic::Literal>().unwrap()]
1312 ));
1313 }
1314
1315 #[test]
1316 #[serial]
1317 fn simple_nongrounded() {
1318 let goal: Goal<logic::IRTerm> = vec!["a(\"b\")".parse().unwrap()];
1319 let clauses: Vec<logic::Clause> = vec![logic::Clause {
1320 head: "a(X)".parse().unwrap(),
1321 body: vec![],
1322 }];
1323 let tree = sld(&clauses, &goal, 10, true).tree;
1324 let solutions = solutions(&tree);
1325 assert_eq!(solutions.len(), 1);
1326 assert!(contains_ignoring_position(
1327 &solutions,
1328 &vec!["a(\"b\")".parse::<logic::Literal>().unwrap()]
1329 ));
1330 }
1331
1332 #[test]
1333 #[serial]
1334 fn simple_nongrounded_invalid() {
1335 let goal: Goal<logic::IRTerm> = vec!["a(X)".parse().unwrap()];
1336 let clauses: Vec<logic::Clause> = vec![logic::Clause {
1337 head: "a(X)".parse().unwrap(),
1338 body: vec![],
1339 }];
1340 let result = sld(&clauses, &goal, 10, true);
1341 assert_eq!(
1342 vec![ResolutionError::InsufficientGroundness(goal)],
1343 result.errors.into_iter().collect::<Vec<_>>()
1344 )
1345 }
1346
1347 #[test]
1348 #[serial]
1349 fn complex_goal() {
1350 let goal: Goal<logic::IRTerm> = vec!["a(X)".parse().unwrap(), "b(X)".parse().unwrap()];
1351 let clauses: Vec<logic::Clause> = vec![
1352 logic::Clause {
1353 head: "a(\"t\")".parse().unwrap(),
1354 body: vec![],
1355 },
1356 logic::Clause {
1357 head: "a(\"f\")".parse().unwrap(),
1358 body: vec![],
1359 },
1360 logic::Clause {
1361 head: "b(\"g\")".parse().unwrap(),
1362 body: vec![],
1363 },
1364 logic::Clause {
1365 head: "b(\"t\")".parse().unwrap(),
1366 body: vec![],
1367 },
1368 ];
1369 let tree = sld(&clauses, &goal, 10, true).tree;
1370 let solutions = solutions(&tree);
1371 assert_eq!(solutions.len(), 1);
1372 assert!(contains_ignoring_position(
1373 &solutions,
1374 &vec!["a(\"t\")".parse().unwrap(), "b(\"t\")".parse().unwrap()]
1375 ));
1376 }
1377
1378 #[test]
1379 #[serial]
1380 fn solving_with_binary_relations() {
1381 let goal: Goal<logic::IRTerm> = vec!["a(X)".parse().unwrap()];
1382 let clauses: Vec<logic::Clause> = vec![
1383 "a(X) :- b(X, Y), c(Y).".parse().unwrap(),
1384 logic::Clause {
1385 head: "b(\"t\", \"f\")".parse().unwrap(),
1386 body: vec![],
1387 },
1388 logic::Clause {
1389 head: "b(\"f\", \"t\")".parse().unwrap(),
1390 body: vec![],
1391 },
1392 logic::Clause {
1393 head: "b(\"g\", \"t\")".parse().unwrap(),
1394 body: vec![],
1395 },
1396 logic::Clause {
1397 head: "c(\"t\")".parse().unwrap(),
1398 body: vec![],
1399 },
1400 ];
1401 let tree = sld(&clauses, &goal, 10, true).tree;
1402 let solutions = solutions(&tree);
1403 assert_eq!(solutions.len(), 2);
1404 assert!(contains_ignoring_position(
1405 &solutions,
1406 &vec!["a(\"f\")".parse().unwrap()]
1407 ));
1408 assert!(contains_ignoring_position(
1409 &solutions,
1410 &vec!["a(\"g\")".parse().unwrap()]
1411 ));
1412 }
1413
1414 #[test]
1415 #[serial]
1416 fn simple_recursion() {
1417 let goal: Goal<logic::IRTerm> = vec!["reach(\"a\", X)".parse().unwrap()];
1418 let clauses: Vec<logic::Clause> = vec![
1419 "reach(X, Y) :- reach(X, Z), arc(Z, Y).".parse().unwrap(),
1420 "reach(X, Y) :- arc(X, Y).".parse().unwrap(),
1421 logic::Clause {
1422 head: "arc(\"a\", \"b\")".parse().unwrap(),
1423 body: vec![],
1424 },
1425 logic::Clause {
1426 head: "arc(\"b\", \"c\")".parse().unwrap(),
1427 body: vec![],
1428 },
1429 logic::Clause {
1430 head: "arc(\"c\", \"d\")".parse().unwrap(),
1431 body: vec![],
1432 },
1433 logic::Clause {
1434 head: "arc(\"d\", \"e\")".parse().unwrap(),
1435 body: vec![],
1436 },
1437 logic::Clause {
1438 head: "arc(\"f\", \"e\")".parse().unwrap(),
1439 body: vec![],
1440 },
1441 logic::Clause {
1442 head: "arc(\"g\", \"f\")".parse().unwrap(),
1443 body: vec![],
1444 },
1445 logic::Clause {
1446 head: "arc(\"g\", \"a\")".parse().unwrap(),
1447 body: vec![],
1448 },
1449 ];
1450 let tree = sld(&clauses, &goal, 15, true).tree;
1451 let solutions = solutions(&tree);
1452 assert_eq!(solutions.len(), 4);
1453 assert!(contains_ignoring_position(
1454 &solutions,
1455 &vec!["reach(\"a\", \"b\")".parse().unwrap()]
1456 ));
1457 assert!(contains_ignoring_position(
1458 &solutions,
1459 &vec!["reach(\"a\", \"c\")".parse().unwrap()]
1460 ));
1461 assert!(contains_ignoring_position(
1462 &solutions,
1463 &vec!["reach(\"a\", \"d\")".parse().unwrap()]
1464 ));
1465 assert!(contains_ignoring_position(
1466 &solutions,
1467 &vec!["reach(\"a\", \"e\")".parse().unwrap()]
1468 ));
1469 }
1470
1471 #[test]
1472 #[serial]
1473 fn string_concat() {
1474 let goal: Goal<logic::IRTerm> =
1475 vec!["string_concat(\"hello\", \"world\", X)".parse().unwrap()];
1476 let clauses: Vec<logic::Clause> = vec![];
1477 let tree = sld(&clauses, &goal, 10, true).tree;
1478 let solutions = solutions(&tree);
1479 assert_eq!(solutions.len(), 1);
1480 assert!(contains_ignoring_position(
1481 &solutions,
1482 &vec!["string_concat(\"hello\", \"world\", \"helloworld\")"
1483 .parse()
1484 .unwrap()]
1485 ));
1486 }
1487
1488 #[test]
1489 #[serial]
1490 fn string_concat_complex() {
1491 let good = ["aaabbb", "aabb"];
1493 let bad = ["aab", "a", "bb"];
1494 for (s, is_good) in good
1495 .iter()
1496 .map(|x| (*x, true))
1497 .chain(bad.iter().map(|x| (*x, false)))
1498 {
1499 let goal: Goal<logic::IRTerm> = vec![format!("a(\"{}\")", s).parse().unwrap()];
1500 let clauses: Vec<logic::Clause> = vec![
1501 logic::Clause {
1502 head: "a(\"ab\")".parse().unwrap(),
1503 body: vec![],
1504 },
1505 "a(S) :- string_concat(\"a\", X, S), string_concat(Y, \"b\", X), a(Y)"
1506 .parse()
1507 .unwrap(),
1508 ];
1509 let tree_res = sld(&clauses, &goal, 50, true);
1510 if is_good {
1511 let solutions = solutions(&tree_res.tree);
1512 assert_eq!(solutions.len(), 1);
1513 assert!(contains_ignoring_position(
1514 &solutions,
1515 &vec![format!("a(\"{}\")", s).parse().unwrap()]
1516 ));
1517 } else {
1518 assert!(!tree_res.tree.is_success());
1519 }
1520 }
1521 }
1522
1523 #[test]
1524 #[serial]
1525 fn leaf_height_is_zero() {
1526 let proof = Proof {
1527 clause: ClauseId::Rule(0),
1528 valuation: HashMap::default(),
1529 children: Vec::new(),
1530 };
1531 assert_eq!(proof.height(), 0);
1532 }
1533
1534 #[test]
1535 #[serial]
1536 fn proof_minimality() {
1537 let goal: Goal<logic::IRTerm> = vec!["foo(X)".parse().unwrap()];
1538 let clauses: Vec<logic::Clause> = vec![
1539 "foo(X) :- bar(X).".parse().unwrap(),
1540 "bar(\"test\").".parse().unwrap(),
1541 "foo(\"test\").".parse().unwrap(),
1542 ];
1543 let tree = sld(&clauses, &goal, 15, true).tree;
1544 let sld_proofs = proofs(&tree, &clauses, &goal);
1545 assert_eq!(sld_proofs.len(), 1);
1546 assert_eq!(
1547 sld_proofs.values().next().unwrap().height(),
1548 1,
1549 "{:?}",
1550 sld_proofs[&goal]
1551 );
1552 }
1553
1554 #[test]
1555 #[serial]
1556 fn tree_from_expression_query() {
1557 let mf: Modusfile = "base_image(\"alpine3.14\"). base_image(\"alpine3.15\")."
1558 .parse()
1559 .unwrap();
1560 let query = Expression::Literal(Literal {
1561 positive: true,
1562 position: None,
1563 predicate: Predicate("base_image".into()),
1564 args: vec!["f\"alpine${X}\"".parse().unwrap()],
1565 });
1566
1567 let (_, _, sld_res) = tree_from_modusfile(mf, query, 20, true);
1568 assert!(sld_res.tree.is_success());
1569 }
1570
1571 #[test]
1572 #[serial]
1573 fn negation_and_builtins() {
1574 let goal: Goal<logic::IRTerm> = vec!["!is_alpine(\"notalpine3.15\")".parse().unwrap()];
1575 let clauses: Vec<logic::Clause> = vec![
1576 "is_alpine(variant) :- string_concat(\"alpine\", version, variant)."
1577 .parse()
1578 .unwrap(),
1579 ];
1580 let sld_res = sld(&clauses, &goal, 10, true);
1581 let tree = sld_res.tree;
1582 let solutions = solutions(&tree);
1583 assert_eq!(solutions.len(), 1);
1584
1585 assert!(contains_ignoring_position(
1586 &solutions,
1587 &vec!["!is_alpine(\"notalpine3.15\")"
1588 .parse::<logic::Literal>()
1589 .unwrap()]
1590 ));
1591 }
1592
1593 #[test]
1594 #[serial]
1595 fn negation_and_anonymous_variable() {
1596 let goal: Goal<logic::IRTerm> = vec!["!is_alpine(\"notalpine3.15\", _)".parse().unwrap()];
1597 let clauses: Vec<logic::Clause> = vec![
1598 "is_alpine(variant, version) :- string_concat(\"alpine\", version, variant)."
1599 .parse()
1600 .unwrap(),
1601 ];
1602 let sld_res = sld(&clauses, &goal, 10, true);
1603 let tree = sld_res.tree;
1604 let solutions = solutions(&tree);
1605 assert_eq!(solutions.len(), 1);
1606
1607 assert!(contains_ignoring_position(&solutions, &goal));
1608 }
1609
1610 #[test]
1611 #[serial]
1612 fn negation_errors_when_unknown() {
1613 let goal: Goal<logic::IRTerm> = vec!["!is_alpine(\"notalpine3.15\", _)".parse().unwrap()];
1614 let clauses: Vec<logic::Clause> = vec![];
1615 let sld_res = sld(&clauses, &goal, 10, true);
1616
1617 assert_eq!(sld_res.errors.len(), 1);
1618 let is_match = matches!(
1619 sld_res.errors.iter().next(),
1620 Some(ResolutionError::UnknownPredicate(_))
1621 );
1622 assert!(is_match);
1623 }
1624
1625 #[test]
1626 #[serial]
1627 fn arrays() {
1628 let goal: Goal<logic::IRTerm> = vec!["app([\"sh\", \"--some-option\"])".parse().unwrap()];
1629 let clauses: Vec<logic::Clause> = vec![
1630 "app(entrypoint_params) :- from(\"alpine\")::set_entrypoint(entrypoint_params)."
1631 .parse()
1632 .unwrap(),
1633 ];
1634 let sld_res = sld(&clauses, &goal, 10, true);
1635 let tree = sld_res.tree;
1636 let solutions = solutions(&tree);
1637 assert_eq!(solutions.len(), 1);
1638
1639 assert!(contains_ignoring_position(
1640 &solutions,
1641 &vec!["app([\"sh\", \"--some-option\"])"
1642 .parse::<logic::Literal>()
1643 .unwrap()]
1644 ));
1645 }
1646
1647 #[test]
1648 #[serial]
1649 fn arrays_error_when_ungrounded() {
1650 let goal: Goal<logic::IRTerm> = vec!["app([\"sh\", someOption])".parse().unwrap()];
1651 let clauses: Vec<logic::Clause> = vec![
1652 "app(entrypoint_params) :- from(\"alpine\")::set_entrypoint(entrypoint_params)."
1653 .parse()
1654 .unwrap(),
1655 ];
1656 let sld_res = sld(&clauses, &goal, 10, true);
1657 assert_eq!(sld_res.errors.len(), 1);
1658 let is_match = matches!(
1659 sld_res.errors.iter().next(),
1660 Some(ResolutionError::InsufficientGroundness(_)),
1661 );
1662 assert!(is_match);
1663 }
1664}