suiron/
parse_goals.rs

1//! Utilities for parsing goals and queries.
2//!
3// Cleve Lendon 2023
4
5use crate::atom;
6use crate::pred;
7use crate::str_to_chars;
8use crate::chars_to_string;
9
10use super::goal::*;
11use super::infix::*;
12use super::operator::*;
13use super::s_complex::*;
14use super::parse_terms::*;
15use super::unifiable::Unifiable;
16use super::built_in_predicates::*;
17
18/// Determines the indices of parentheses in a goal or query.
19///
20/// For example, in the goal `parse($In, $Out)`, the indices are (5, 15).
21///
22/// This function also checks for errors, such as unmatched parentheses.
23///
24/// # Arguments
25/// * vector of chars, representing a goal
26/// # Return
27/// * (left_index, right_index) or error message
28/// # Usage
29/// ```
30/// use suiron::*;
31///
32/// let element_chr = str_to_chars!("element(Iridium, 77)");
33/// match indices_of_parentheses(&element_chr) {
34///     Ok(ind) => {
35///         match ind {
36///             Some((left, right)) => { println!("Indices: {}, {}", left, right); },
37///             None => { println!("No indices were found."); },
38///         } // match
39///     },
40///     Err(msg) => { println!("{}", msg); },
41/// } // match
42/// // Should print: Indices: 7, 19
43/// ```
44///
45pub fn indices_of_parentheses(goal: &Vec<char>)
46                              -> Result<Option<(usize, usize)>, String> {
47
48    let mut left: i32   = -1;  // index of first parenthesis
49    let mut right: i32  = -1;
50    let mut count_left  = 0;
51    let mut count_right = 0;
52
53    for (i, ch) in goal.iter().enumerate() {
54        if *ch == '(' {
55            if left == -1 { left = i as i32; }
56            count_left += 1;
57        }
58        else if *ch == ')' {
59            right = i as i32;
60            count_right += 1;
61        }
62    } // for
63
64    if count_left != count_right {
65        let s = chars_to_string!(goal);
66        return Err(iop_error("Unbalanced parentheses", &s));
67    }
68
69    if right < left {
70        let s = chars_to_string!(goal);
71        return Err(iop_error("Invalid parentheses", &s));
72    }
73
74    if left == -1 { return Ok(None); }
75    return Ok(Some((left as usize, right as usize)));
76
77} // indices_of_parentheses
78
79
80/// Gets terms on left and right-hand side of an infix.
81///
82/// This function divides a string (vector of characters) which contains
83/// an infix,<br> such as `$X = verb` or `$X <= 47`.
84/// It parses the left and right sides, to produce<br>two Unifiable terms.
85///
86/// # Arguments
87/// * vector of chars
88/// * index of infix
89/// * size of infix (1 or 2)
90/// # Return
91/// * (Unifiable, Unifiable) or error message
92/// # Usage
93/// ```
94/// use suiron::*;
95///
96/// let chrs = str_to_chars!("$X < 7");
97/// let (_infix, index) = check_infix(&chrs);
98///
99/// match get_left_and_right(chrs, index, 2) {
100///     Ok((left, right)) => { println!("Left: {left}, Right: {right}");},
101///     Err(err) => { println!("Error: {err}"); },
102/// };
103/// // Prints - Left: $X, Right: 7
104/// ```
105pub fn get_left_and_right(chrs: Vec<char>, index: usize, size: usize)
106                          -> Result<(Unifiable, Unifiable), String> {
107    let arg1 = &chrs[0..index];
108    let arg2 = &chrs[index + size..];
109
110    let term1 = parse_term(&chars_to_string!(arg1))?;
111    let term2 = parse_term(&chars_to_string!(arg2))?;
112    return Ok((term1, term2));
113
114} // get_left_and_right
115
116/// Splits a string representation of a complex term into its functor and terms.
117///
118/// For example, if the complex term is:
119/// <blockquote>
120///    father(Philip, Alize)
121/// </blockquote>
122/// and the indices (index1, index2) are 6 and 20, the function will
123/// return: "father", "Philip, Alize"
124///
125/// This method assumes that index1 and index2 are valid.
126///
127/// # Arguments
128/// * complex term (string)
129/// * index of left parenthesis
130/// * index of right parenthesis
131/// # Return
132/// * (String, String)
133///
134fn split_complex_term(complex: Vec<char>, index1: usize, index2: usize)
135                      -> (String, String) {
136
137      let functor = &complex[0..index1];
138      let terms   = &complex[index1 + 1..index2];
139      return (chars_to_string!(functor), chars_to_string!(terms));
140
141} // split_complex_term
142
143/// Parses a string to produce a goal.
144///
145/// This function parses strings which represent complex terms, such
146/// as `element(Xenon, $N, $W)`, and built-in predicates, such as
147/// `append(…)`, to produce `ComplexGoal`s and `BuiltInGoal`s.
148///
149/// # Arguments
150/// * string to parse
151/// # Result
152/// * [Goal](../goal/enum.Goal.html) or error message
153///
154/// # Note
155/// * This function does not parse And or Or operators. See
156/// [generate_goal()](../tokenizer/fn.generate_goal.html)
157///
158pub fn parse_subgoal(to_parse: &str) -> Result<Goal, String> {
159
160    let s = to_parse.trim();
161
162    if s.len() == 0 {
163        let err = "parse_subgoal() - Empty string.".to_string();
164        return Err(err);
165    }
166
167    let chrs = str_to_chars!(s);
168
169    // Built-in predicates with no arguments.
170    if s == "!" || s == "fail" || s == "nl" {
171        let pred = BuiltInPredicate::new(s.to_string(), None);
172        return Ok(Goal::BuiltInGoal(pred));
173    }
174
175    //--------------------------------------
176    // Handle infixes: = > < >= <= == =
177
178    let (infix, index) = check_infix(&chrs);
179    if infix != Infix::None {
180
181        // An infix can be 1 or 2 characters, eg: <, <=
182        // The last parameter of get_left_and_right() is the
183        // size of the infix. To avoid repeating this call
184        // for each infix, it is called here with an infix size
185        // of 2. Since all infixes must be followed by a space,
186        // this shouldn't be a problem.
187        let (left, right) = get_left_and_right(chrs, index, 2)?;
188
189        let goal = match infix {
190            Infix::Unify => { pred!("unify", left, right) },
191            Infix::Equal => { pred!("equal", left, right) },
192            Infix::LessThan           => { pred!("less_than", left, right) },
193            Infix::LessThanOrEqual    => { pred!("less_than_or_equal", left, right) },
194            Infix::GreaterThan        => { pred!("greater_than", left, right) },
195            Infix::GreaterThanOrEqual => { pred!("greater_than_or_equal", left, right) },
196            _ => {
197                let err = format!("parse_subgoal() - Invalid syntax: {}", s);
198                return Err(err);
199            },
200        }; // let match
201
202        return Ok(goal);
203
204    } // if infix != Infix::None
205
206    // Check for parentheses.
207    let left_index: usize;
208    let right_index: usize;
209    match indices_of_parentheses(&chrs) {
210        Ok(indices) => {
211            match indices {
212                Some((l, r)) => { left_index = l; right_index = r; },
213                None => {
214                    // OK. A goal can be a simple word, without parentheses.
215                    match parse_functor_terms(s, "") {
216                        Ok(c) => { return Ok(Goal::ComplexGoal(c)); },
217                        Err(err) => {
218                            let err = format!("{}{}", err, to_parse);
219                            return Err(err);
220                        },
221                    }
222                },
223            } // match
224        },
225        Err(err) => { return Err(err); },
226    } // match
227
228    let (functor_str, args_str) =
229                     split_complex_term(chrs, left_index, right_index);
230
231    // Check for operators.
232    if functor_str == "time" || functor_str == "not"{
233       return parse_operator_goal(&functor_str, &args_str);
234    }
235
236    let args = parse_arguments(&args_str)?;
237    return Ok(make_goal(&functor_str, args));
238
239} // parse_subgoal
240
241/// Makes a goal from a functor and a vector of unifiable terms.
242///
243/// Complex terms and built-in predicates have the same form:
244/// `functor(term1, term2,…)`.
245/// If the given functor corresponds a built-in predicate, such as print(…)
246/// or append(…), this function will construct the built-in predicate and
247/// wrap it in Goal::BuiltInGoal(). Otherwise, the function will construct
248/// a complex term, and wrap it in Goal::ComplexGoal().
249///
250/// # Arguments
251/// * functor (String)
252/// * arguments (vector of Unifiable terms)
253/// # Result
254/// * Goal
255///
256pub fn make_goal(functor: &str, mut args: Vec<Unifiable>) -> Goal {
257
258    // Is this a built-in predicate?
259    if functor == "fail" || functor == "nl" || functor == "!" { // Ignore args.
260        let pred = BuiltInPredicate::new(functor.to_string(), None);
261        return Goal::BuiltInGoal(pred);
262    }
263
264    if functor == "print" || functor == "append" || functor == "functor" ||
265       functor == "include" || functor == "exclude" ||
266       functor == "print_list" || functor == "unify" || functor == "equal" ||
267       functor == "less_than"    || functor == "less_than_or_equal" ||
268       functor == "greater_than" || functor == "greater_than_or_equal" ||
269       functor == "count" || functor == "include" || functor == "exclude" ||
270       functor == "functor" {
271        let pred = BuiltInPredicate::new(functor.to_string(), Some(args));
272        return Goal::BuiltInGoal(pred);
273    }
274
275    // Create a complex term.
276    let mut unifiables = vec![atom!(functor)];
277    unifiables.append(&mut args);
278    return Goal::ComplexGoal(Unifiable::SComplex(unifiables));
279
280} // make_goal()
281
282/// Makes a goal which has no arguments.
283///
284/// Some built-in predicates, such as `nl` and `fail`, contain no arguments.
285/// Similarly, it might be useful to allow complex terms to consist of a
286/// functor only.
287///
288/// If the given functor corresponds a built-in predicate, such as nl or fail,
289/// this function will construct the predicate and wrap it in Goal::BuiltInGoal().
290/// Otherwise, the function will construct a complex term, and wrap it in
291/// Goal::ComplexGoal().
292///
293/// # Arguments
294/// * functor (String)
295/// # Result
296/// * Goal
297///
298pub fn make_goal_no_args(functor: &str) -> Goal {
299
300    // Is this a built-in predicate?
301    if functor == "fail" || functor == "nl" || functor == "!" {
302        let pred = BuiltInPredicate::new(functor.to_string(), None);
303        return Goal::BuiltInGoal(pred);
304    }
305
306    // Otherwise create a complex term.
307    let unifiables = vec![atom!(functor)];
308    return Goal::ComplexGoal(Unifiable::SComplex(unifiables));
309
310} // make_goal_no_args()
311
312/// Makes a operator goal for the given name and argument.
313///
314/// A built-in predicate or complex term holds a vectors of unifiable terms.
315/// An operator, on the other hand, holds a vector of goals, so it must be
316/// handled separately.
317///
318/// This function does not handle And and Or operators.
319///
320/// # Arguments
321/// * name of operator
322/// * argument string
323/// # Return
324/// * operator goal or error message
325///
326fn parse_operator_goal(name: &str, args_str: &str) -> Result<Goal, String> {
327    let subgoal = parse_subgoal(&args_str)?;
328    match name {
329        "time" => {
330            return Ok(Goal::OperatorGoal(Operator::Time(vec![subgoal])));
331        },
332        "not" => {
333            return Ok(Goal::OperatorGoal(Operator::Not(vec![subgoal])));
334        },
335        _ => {
336           let err = "parse_operator_goal() - Invalid operator.".to_string();
337           return Err(err)
338        },
339    }
340} // parse_operator_goal()
341
342// Formats an error message for indices_of_parentheses().
343// Arguments:
344//   err - error description
345//   bad - string which caused the error
346// Return:
347//   error message (String)
348fn iop_error(err: &str, bad: &str) -> String {
349    format!("indices_of_parentheses() - {}: {}", err, bad)
350}
351
352#[cfg(test)]
353mod test {
354
355    use crate::str_to_chars;
356
357    use super::*;
358
359    #[test]
360    fn test_indices_of_parentheses() {
361
362        let goal_chr = str_to_chars!("parse($In, $Out)");
363        match indices_of_parentheses(&goal_chr) {
364            Ok(indices) => {
365                match indices {
366                    Some(ind) => { assert_eq!((5, 15), ind, "Incorrect indices."); },
367                    None => { panic!("Could not get indices."); },
368                }
369            },
370            Err(err) => { panic!("{err}"); }
371        }
372
373        let goal_chr = str_to_chars!("parse");
374        match indices_of_parentheses(&goal_chr) {
375            Ok(indices) => {
376                match indices {
377                    Some(_) => { panic!("Should not find indices."); },
378                    None => {}, // None found.
379                }
380            },
381            Err(msg) => { panic!("{}", msg); }
382        }
383
384        let goal_chr = str_to_chars!("parse($In, $Out");
385        let err = "indices_of_parentheses() - Unbalanced parentheses: parse($In, $Out";
386        match indices_of_parentheses(&goal_chr) {
387            Ok(_) => { panic!("Should produce error message."); },
388            Err(msg) => { assert_eq!(err, msg, "Unexpected error message."); }
389        }
390
391        let goal_chr = str_to_chars!("parse)$In, $Out(");
392        let err = "indices_of_parentheses() - Invalid parentheses: parse)$In, $Out(";
393        match indices_of_parentheses(&goal_chr) {
394            Ok(_) => { panic!("Should produce error message."); },
395            Err(msg) => { assert_eq!(err, msg, "Unexpected error message."); }
396        }
397    } // test_indices_of_parentheses()
398
399    #[test]
400    fn test_get_left_and_right() {
401
402        let chrs = str_to_chars!("$X < 7");
403        let (_inf, ind) = check_infix(&chrs);
404
405        let (left, right) = match get_left_and_right(chrs, ind, 1) {
406            Ok((left, right)) => (left, right),
407            Err(_err) => { panic!("get_left_and_right() - Should not fail."); }
408        };
409        assert_eq!("$X", left.to_string());
410        assert_eq!("7", right.to_string());
411
412    } // test_get_left_and_right()
413
414} // test