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