suiron/
s_complex.rs

1//! Functions to support complex terms.
2//!
3//! Suiron's complex terms
4//! ([SComplex](../unifiable/enum.Unifiable.html#variant.SComplex))
5//! are similar to Prolog's complex terms. They consist of a functor,
6//! followed by a list of terms between parentheses. Some examples:
7//!
8//! <blockquote>
9//! element(Argon, 18)<br>
10//! father($F, Luke)<br>
11//! pronoun(I, subject, first, singular)<br>
12//! </blockquote>
13//!
14//! In Rust, complex terms are implemented as a vector of unifiable terms.
15//!
16//! # Note
17//! Unlike Prolog, Suiron's
18//! [atoms](../unifiable/enum.Unifiable.html#variant.Atom)
19//! (string constants) can be capitalized or lower case.<br>
20//! [Logic variables](../unifiable/enum.Unifiable.html#variant.LogicVar)
21//! start with a dollar sign and a letter, eg. $F.<br>
22//!
23use super::unifiable::{*, Unifiable::*};
24use super::goal::*;
25use super::logic_var::*;
26use super::parse_terms::*;
27use super::parse_goals::*;
28
29use crate::atom;
30use crate::str_to_chars;
31use crate::chars_to_string;
32
33/// Produces a complex term from a vector of terms.
34///
35/// This function does validity checking. The first term must be an
36/// [atom](../unifiable/enum.Unifiable.html#variant.Atom).
37///
38/// See also [scomplex!](../macro.scomplex.html).
39///
40/// # Arguments
41/// * `terms` - vector of
42/// [Unifiable](../unifiable/enum.Unifiable.html) terms
43/// # Return
44/// * [SComplex](../unifiable/enum.Unifiable.html#variant.SComplex)
45/// # Panics
46/// * If vector is empty.
47/// * If first term (functor) is not an atom.
48/// # Usage
49/// ```
50/// use suiron::*;
51///
52/// let d = atom!("drinks");
53/// let p = atom!("Picard");
54/// let t = atom!("Earl Grey");
55///
56/// let terms = vec![d, p, t];
57/// let cmplx = make_complex(terms);
58/// println!("{}", cmplx);  // Prints: drinks(Picard, Earl Grey)
59/// ```
60pub fn make_complex(terms: Vec<Unifiable>) -> Unifiable {
61    if terms.len() == 0 { panic!("make_complex() - Vector is empty."); }
62    let first = &terms[0];
63    match first {
64        Unifiable::Atom(_) => {},
65        _ => { panic!("make_complex() - First term must be an Atom."); },
66    }
67    return Unifiable::SComplex(terms);
68} // make_complex()
69
70
71/// Produces a query from a vector of terms.
72///
73/// A query is the same as a complex term, but before it can be
74/// submitted to a solver, its logic variables must be given unique IDs.
75///
76/// This function resets the global LOGIC_VAR_ID to 0, and recreates the
77/// logic variable terms of the query.
78///
79/// # Arguments
80/// * `terms` - vector of
81/// [Unifiable](../unifiable/enum.Unifiable.html) terms
82/// # Return
83/// * [Goal](../goal/enum.Goal.html)
84/// # Panics
85/// * If vector is empty.
86/// * If first term (functor) is not an atom.
87/// # Usage
88/// ```
89/// use suiron::*;
90///
91/// let functor = atom!("loves");
92/// let x = logic_var!("$X");
93/// let y = logic_var!("$Y");
94///
95/// let terms = vec![functor, x, y];
96/// let qry = make_query(terms);
97/// println!("{}", qry);  // Prints: loves($X_1, $Y_2)
98/// ```
99pub fn make_query(terms: Vec<Unifiable>) -> Goal {
100
101    // The main bottleneck in Suiron is the time it takes to copy
102    // the substitution set. The substitution set is as large as
103    // the highest variable ID (LOGIC_VAR_ID). Therefore LOGIC_VAR_ID
104    // should be set to 0 for every query.
105    clear_id();  // Reset LOGIC_VAR_ID.
106
107    let mut new_terms: Vec<Unifiable> = vec![];
108    let mut vars = VarMap::new();
109
110    for term in terms {
111        new_terms.push(term.recreate_variables(&mut vars));
112    }
113
114    let c = make_complex(new_terms);
115    Goal::ComplexGoal(c)
116
117} // make_query()
118
119/// Parses a string to produce a complex term.
120///
121/// # Arguments
122/// * `to_parse` - &str
123/// # Return
124/// * `Result` -
125/// Ok([SComplex](../unifiable/enum.Unifiable.html#variant.SComplex))
126/// or Err(message)
127///
128/// # Usage
129/// ```
130/// use suiron::*;
131///
132/// let cmplx = parse_complex("symptom(Covid, fever)");
133/// match cmplx {
134///    Ok(c) => { println!("{}", c); },
135///    Err(err) => { println!("{}", err); },
136/// }
137/// // Prints: symptom(Covid, fever)
138/// ```
139///
140/// # Note
141/// Backslash is used to escape characters, such as the comma.
142/// For example:<br>
143/// ```
144/// use suiron::*;
145///
146/// let cmplx = parse_complex("punctuation(comma, \\,)");
147/// match cmplx {
148///     Ok(c) => { println!("{}", c); },
149///     Err(err) => { println!("{}", err); },
150/// }
151/// // Prints: punctuation(comma, ,)
152/// ```
153///
154/// The backslash is doubled because the Rust compiler also
155/// interprets the backslash.
156///
157pub fn parse_complex(to_parse: &str) -> Result<Unifiable, String> {
158
159    let s = to_parse.trim();
160    let chrs = str_to_chars!(s);
161
162    match validate_complex(s, &chrs) {
163        Some(msg) => { return Err(msg); },
164        None => {},
165    }
166
167    // Get indices, if any.
168    match indices_of_parentheses(&chrs) {
169        Ok(indices) => {
170            match indices {
171                Some((left, right)) => {
172                    let functor = chars_to_string!(chrs[0..left]);
173                    let args    = chars_to_string!(chrs[left + 1..right]);
174                    match parse_functor_terms(&functor, &args) {
175                        Ok(cmplx) => { return Ok(cmplx); }, // OK, return.
176                        Err(err) => { // Adjust error message. Add original string.
177                            let err = format!("{}{}", err, to_parse);
178                            return Err(err);
179                        },
180                    }
181                },
182                None => {
183                    match parse_functor_terms(s, "") {
184                        Ok(cmplx) => { return Ok(cmplx); }, // OK, return.
185                        Err(err) => { // Adjust error message. Add original string.
186                            let err = format!("{} {}", err, to_parse);
187                            return Err(err);
188                        },
189                    }
190                },
191            }
192        },
193        Err(err) => { return Err(err); }
194    }
195} // parse_complex
196
197
198/// Validates the string to be parsed.
199///
200/// If the string to parse is empty or too long, this function
201/// will return an error message. Also, the string must not
202/// begin with a dollar sign or a parenthesis.
203///
204/// # Argument
205/// * `to_parse` - &str
206/// * `chars` - vector of chars
207/// # Return
208/// * `Option` - Some(error_message) or None
209pub fn validate_complex(to_parse: &str, chrs: &Vec<char>) -> Option<String> {
210
211    let length = chrs.len();
212    if length == 0 {
213        let err = format!("Parsing error - Length of string is 0.");
214        return Some(err);
215    }
216
217    if length > 1000 {
218        let err = format!("Parsing error - String is too long.\n{}", to_parse);
219        return Some(err);
220    }
221
222    let first = chrs[0];
223    if first == '$' || first == '(' {
224        let err = format!("Parsing error - First character is invalid: {}", to_parse);
225        return Some(err);
226    }
227    return None;
228
229} // validate_complex()
230
231
232/// Parses a string to produce a query.
233///
234/// Queries are complex terms entered after the query prompt, ?-. For example:<br>
235///
236/// ?- `symptom(flu, $Sym).`<br>
237///
238/// A query is the same as a complex term, but before it can be
239/// submitted to a solver, its logic variables must be given unique IDs.
240///
241/// This function simply calls parse_complex(), then recreates the
242/// logic variables.
243///
244/// # Arguments
245/// * `to_parse` - string to parse
246/// # Return
247/// * `Result` -
248/// Ok([Goal](../goal/enum.Goal.html))
249/// or Err(message)
250///
251/// # Usage
252/// ```
253/// use suiron::*;
254///
255/// match parse_query("loves($X, $Y)") {
256///     Ok(q) => { println!("{}", q); },
257///     Err(err) => { println!("{}", err); },
258/// }
259/// // Prints: loves($X_1, $Y_2)
260/// ```
261///
262/// # Note
263/// Backslash is used to escape characters, such as the comma.
264/// For example:<br>
265/// ```
266/// use suiron::*;
267///
268/// let cmplx = parse_complex("punctuation(comma, \\,)");
269/// match cmplx {
270///     Ok(c) => { println!("{}", c); },
271///     Err(err) => { println!("{}", err); },
272/// }
273/// // Prints: punctuation(comma, ,)
274/// ```
275///
276/// The backslash is doubled because the Rust compiler also
277/// interprets the backslash.
278///
279pub fn parse_query(to_parse: &str) -> Result<Goal, String> {
280
281    // Clean up query.
282    // Perhaps there is an unnecessary period at the end.
283    let mut parse2 = to_parse.to_string();
284    let ch = parse2.chars().last().unwrap();
285    if ch == '.' { parse2.pop(); }
286
287    match parse_complex(&parse2) {
288        Ok(q) => {
289            match q {
290                Unifiable::SComplex(terms) => {
291                    let query = make_query(terms);
292                    return Ok(query);
293                },
294                _ => { panic!("parse_query() - Cannot happen.") },
295            }
296        },
297        Err(err) => { Err(err) },
298    }
299} // parse_query()
300
301/// Parses two string arguments to produce a complex term.
302///
303/// # Arguments
304/// * `functor` - string
305/// * `list of terms` - string
306/// # Return
307/// * `Result` -
308/// Ok([SComplex](../unifiable/enum.Unifiable.html#variant.SComplex))
309/// or Err(message)
310/// # Usage
311/// ```
312/// use suiron::*;
313///
314/// let cmplx = parse_functor_terms("father", "Anakin, Luke");
315/// match cmplx {
316///     Ok(c) => { println!("{}", c); },
317///     Err(err) => { println!("{}", err); },
318/// }
319/// // Prints: Anakin, Luke
320/// ```
321pub fn parse_functor_terms(functor: &str, terms: &str) -> Result<Unifiable, String> {
322
323    let mut new_terms = vec![atom!(functor.trim())];
324    if terms == "" {
325        return Ok(SComplex(new_terms));
326    }
327
328    match parse_arguments(terms) {
329        Ok(terms) => {
330            for term in terms { new_terms.push(term); }
331            return Ok(SComplex(new_terms));
332        },
333        Err(err) => { return Err(err); },
334    }
335
336} // parse_functor_terms
337
338
339#[cfg(test)]
340mod test {
341
342    use crate::*;
343    use super::*;
344
345    /// Validate complex.
346    /// String must not be 0 length, or start with: $ (
347    #[test]
348    fn test_validate_complex() {
349        let s = "aaaaaaaa";
350        let chrs = str_to_chars!(s);
351        match validate_complex(s, &chrs) {
352            Some(err) => {
353                let err = format!("validate_complex() - Should not produce error: {}", err);
354                panic!("{}", err);
355            },
356            None => {},
357        }
358        let s = "";
359        let chrs = str_to_chars!(s);
360        match validate_complex(s, &chrs) {
361            Some(err) => {
362                assert_eq!(err, "Parsing error - Length of string is 0.");
363            },
364            None => { panic!("validate_complex() - Should generate error."); },
365        }
366        let s = "$aaaa";
367        let chrs = str_to_chars!(s);
368        match validate_complex(s, &chrs) {
369            Some(err) => {
370                assert_eq!(err, "Parsing error - First character is invalid: $aaaa");
371            },
372            None => { panic!("validate_complex() - Should generate error."); },
373        }
374    } // test_validate_complex()
375
376    /// Tests creation of a complex term from a vector of terms.
377    #[test]
378    fn test_make_complex() {
379        let d = atom!("drinks");
380        let p = atom!("Picard");
381        let t = atom!("Earl Grey");
382        let terms = vec![d, p, t];
383        let cmplx = make_complex(terms);
384        let s = format!("{}", cmplx);
385        assert_eq!(s, "drinks(Picard, Earl Grey)");
386    } // test_make_complex()
387
388
389    /// Tests creation of a query from a vector of terms.
390    #[test]
391    fn test_make_query() {
392        let functor = atom!("loves");
393        let x = logic_var!("$X");
394        let y = logic_var!("$Y");
395        let terms = vec![functor, x, y];
396        let qry = make_query(terms);
397        assert_eq!(qry.to_string(), "loves($X_1, $Y_2)");
398    } // test_make_query
399
400    /// make_complex() - Panics if the vector is empty.
401    #[test]
402    #[should_panic]
403    fn test_make_complex_panic1() {
404        let terms = vec![];
405        make_complex(terms);
406    }
407
408    /// make_complex() - Panics if the first term is not an Atom.
409    #[test]
410    #[should_panic]
411    fn test_make_complex_panic2() {
412        let d = SInteger(1);
413        let p = atom!("Picard");
414        let t = atom!("Earl Grey");
415        let terms = vec![d, p, t];
416        make_complex(terms);
417    }
418
419    /// parse_functor_terms() should create a valid complex term.
420    #[test]
421    fn test_parse_functor_terms() {
422        match parse_functor_terms("father", "Anakin, Luke") {
423            Ok(c) => {
424                if matches!(c, Unifiable::SComplex(_)) {
425                    assert_eq!("father(Anakin, Luke)", c.to_string());
426                } else {
427                    panic!("parse_functor_terms() - \
428                            Should create a complex term: {}", c)
429                }
430            },
431            Err(err) => { panic!("{}", err); }
432        }
433    } // test_parse_functor_terms()
434
435    /// Test parse_complex() - Test parsing and errors.
436    #[test]
437    fn test_parse_complex() {
438        match parse_complex("father(Anakin, Luke)") {
439            Ok(c) => {
440                if matches!(c, Unifiable::SComplex(_)) {
441                    assert_eq!("father(Anakin, Luke)", c.to_string());
442                } else {
443                    panic!("parse_complex() - Should create a complex term: {}", c);
444                }
445            },
446            Err(err) => { panic!("{}", err); },
447        }
448        match parse_complex("punctuation(comma, \\,)") {
449            Ok(c) => {
450                if matches!(c, Unifiable::SComplex(_)) {
451                    assert_eq!("punctuation(comma, ,)", c.to_string());
452                } else {
453                    panic!("parse_complex() - Should create a complex term: {}", c);
454                }
455            },
456            Err(err) => { panic!("{}", err); }
457        }
458        match parse_complex("father(, Luke)") {
459            Ok(c) => {
460                panic!("parse_complex() - Should generate error: {}", c)
461            },
462            Err(err) => {
463                assert_eq!(err, "parse_arguments() - Missing first argument: father(, Luke)");
464            },
465        }
466        match parse_complex("father(Anakin,)") {
467            Ok(c) => {
468                panic!("parse_complex() - Should generate error: {}", c)
469            },
470            Err(err) => {
471                assert_eq!(err, "parse_arguments() - Missing last argument: father(Anakin,)");
472            },
473        }
474        match parse_complex("father") {
475            Ok(c) => {
476                // Displayed with empty parentheses.
477                assert_eq!("father()", c.to_string());
478            },
479            Err(err) => {
480                panic!("parse_complex() - Should create a complex term: {}", err);
481            },
482        }
483    } // test_parse_complex()
484
485} // test