suiron/built_in_functor.rs
1//! Gets the functor and arity of a complex term.
2//!
3// Cleve Lendon 2023
4
5use std::rc::Rc;
6
7use crate::str_to_chars;
8use crate::chars_to_string;
9
10use super::substitution_set::*;
11use super::built_in_predicates::*;
12use super::unifiable::{*, Unifiable::*};
13
14/// Evaluates a solution node for the functor() predicate.
15///
16/// The functor predicate gets the functor and arity of a complex term.
17/// For example:
18/// <pre>
19/// functor(boss(Zack, Stephen), $Func, $Arity)
20/// </pre>
21///
22/// The first term, an input argument, is a complex term.
23///
24/// $Func will bind to 'boss' and $Arity will bind to '2' (because
25/// there are two arguments, Zack and Stephen). Arity is optional.
26/// The following is valid:
27/// <pre>
28/// functor(boss(Zack, Stephen), $Func)
29/// </pre>
30///
31/// Of course, the first argument would normally be a logic variable.
32/// <pre>
33/// $X = boss(Zack, Stephen), functor($X, $Func)
34/// </pre>
35///
36/// The next goal will not succeed, because the arity is incorrect:
37/// <pre>
38/// functor($X, boss, 3)
39/// </pre>
40///
41/// If the second argument has an asterisk at the end, the match will
42/// test only the start of the string. For example, the following
43/// will succeed:
44/// <pre>
45/// $X = noun_phrase(the blue sky), functor($X, noun*)
46/// </pre>
47///
48/// TODO:
49/// Perhaps the functionality could be expanded to accept a regex string
50/// for the second argument.
51///
52/// This function is called by
53/// [next_solution_bip()](../built_in_predicates/fn.next_solution_bip.html)
54/// in built_in_predicates.rs.
55///
56/// # Arguments
57/// * [BuiltInPredicate](../built_in_predicates/struct.BuiltInPredicate.html)
58/// * [SubstitutionSet](../substitution_set/type.SubstitutionSet.html)
59/// # Return
60/// * [SubstitutionSet](../substitution_set/type.SubstitutionSet.html) or None
61///
62pub fn next_solution_functor<'a>(bip: BuiltInPredicate,
63 ss: &'a Rc<SubstitutionSet<'a>>)
64 -> Option<Rc<SubstitutionSet<'a>>> {
65
66 if let Some(terms) = bip.terms {
67
68 let length = terms.len();
69 if length < 2 || length > 3 { return None; }
70
71 let mut out_terms: Vec<&Unifiable> = vec![];
72
73 for i in 0..length {
74
75 let mut t = &terms[i];
76
77 // If logic variable, get ground term.
78 if let Unifiable::LogicVar{id: _, name: _} = t {
79 match get_ground_term(t, &ss) {
80 Some(new_term) => { t = new_term; },
81 None => { },
82 }
83 }
84 out_terms.push(t);
85
86 } // for
87
88 // Get the terms of the complex term being analyzed.
89 let c_terms = match out_terms[0] {
90 SComplex(terms) => { terms },
91 _ => { return None; },
92 };
93
94 let arity:i64 = (c_terms.len() - 1) as i64;
95 let functor = &c_terms[0];
96
97 if length == 2 { // two arguments
98
99 match &out_terms[1] {
100 Atom(match_string) => {
101 if atoms_match(functor, match_string) {
102 let ss = Rc::clone(ss);
103 return Some(ss);
104 }
105 },
106 LogicVar{id: _, name: _} => {
107 return out_terms[1].unify(functor, &ss);
108 },
109 _ => { return None; },
110 }
111 return None;
112 }
113
114 if length == 3 { // three arguments
115
116 let mut ss = Rc::clone(ss);
117
118 match &out_terms[1] {
119 Atom(match_string) => {
120 if !atoms_match(functor, match_string) { return None; }
121 },
122 LogicVar{id: _, name: _} => {
123 ss = out_terms[1].unify(functor, &ss)?;
124 },
125 _ => { return None; },
126 }
127
128 let arity_term = out_terms[2].clone();
129 return arity_term.unify(&SInteger(arity), &ss);
130 }
131 }
132 return None;
133
134} // next_solution_functor()
135
136// Compares two atoms to see if they match.
137//
138// For the functor predicate, if the second term ends with
139// an asterisk, only the first characters are compared.
140// Eg., the functor predicate below will succeed.
141// $X = noun_phrase(the blue sky), functor($X, noun*)
142//
143// Arguments
144// * functor (atom)
145// * match string (string)
146// Return
147// * true if arguments match
148//
149fn atoms_match(functor: &Unifiable, match_string: &str) -> bool {
150
151 let f = match functor {
152 Unifiable::Atom(f) => { f },
153 _ => { return false; },
154 };
155
156 let chrs = str_to_chars!(match_string);
157 let length = chrs.len();
158 let last_ch = chrs[length - 1];
159
160 if last_ch == '*' {
161 let chrs2 = &chrs[0..length - 1];
162 let s2 = chars_to_string!(chrs2);
163 return f.starts_with(&s2);
164 }
165 else { return f.eq(match_string); }
166
167} // atoms_match
168
169#[cfg(test)]
170mod test {
171
172 use std::rc::Rc;
173 use crate::*;
174 use super::*;
175
176 // Test append() predicate.
177 #[test]
178 fn test_functor() {
179
180 let kb = KnowledgeBase::new();
181
182 // Make a base solution node.
183 let query = parse_query("go").unwrap();
184 let base_node = make_base_node(Rc::new(query), &kb);
185
186 // Make an append() predicate.
187 let append_pred = match parse_subgoal(
188 "append(3.14159, [A, B, C], 6, $Out)") {
189 Ok(goal) => { goal },
190 Err(err) => { panic!("{}", err); },
191 };
192
193 // Recreate variables.
194 let mut var_map = VarMap::new();
195 let append_pred = append_pred.recreate_variables(&mut var_map);
196
197 // Create a solution node.
198 let sn = make_solution_node(Rc::new(append_pred), &kb,
199 empty_ss!(), base_node);
200
201 // Get the solution. This will run next_solution_append().
202 match next_solution(sn) {
203 Some(ss) => {
204 let term = ss[1].clone().unwrap();
205 let s = format!("{}", *term);
206 assert_eq!("[3.14159, A, B, C, 6]", s)
207 },
208 None => { panic!("Append() should join terms together."); },
209 } // match
210
211 } // test_append()
212
213} // test