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