suiron/
infix.rs

1//! Defines infixes for built-in predicates and functions.
2//!
3// Cleve Lendon 2023
4
5use std::fmt;
6
7//-----------Infixes-----------
8#[derive(Debug)]
9#[derive(PartialEq)]
10pub enum Infix {
11    None,
12    /// = Unification operator.
13    Unify,
14    /// == Equal. No unification. Simply compares.
15    Equal,
16    /// >
17    GreaterThan,
18    /// <
19    LessThan,
20    /// >=
21    GreaterThanOrEqual,
22    /// <=
23    LessThanOrEqual,
24    /// +
25    Plus,
26    /// −
27    Minus,
28    /// *
29    Multiply,
30    /// /
31    Divide,
32}
33
34/// Determines whether a string contains an infix: >=, ==, etc.
35///
36/// This function returns the type and index of the infix. For example,<br>
37/// `$X < 6` contains Infix::LessThan at index 3.
38///
39/// This function does not check for arithmetic infixes: `+ - * /`<br>
40/// Arithmetic is done by built-in functions.
41/// See check_arithmetic_infix().
42///
43/// # Arguments
44/// * vector of chars
45/// # Return
46/// * ([Infix](../infix/enum.Infix.html), index)
47///
48/// # Notes
49/// * An infix must be preceded and followed by a space. This is invalid: `$X<6`
50/// * The function ignores characters between double quotes and parentheses.<br>
51/// For example, for the the string of characters `" <= "` (double quotes included),<br>
52/// the function will return (Infix::None, 0).
53///
54/// # Usage
55/// ```
56/// use suiron::*;
57///
58/// let chrs = str_to_chars!("$Age >= 22");
59/// let (infix, index) = check_infix(&chrs);
60/// println!("{infix}, {index}");  // Prints: >=, 5
61/// ```
62pub fn check_infix(chrs: &Vec<char>) -> (Infix, usize) {
63
64    let length = chrs.len();
65    let mut prev   = '#';  // not a space
66
67    let mut i = 0;
68    while i < length {
69
70        let c1 = chrs[i];
71        let mut c2 = '#';
72        if i + 1 < length { c2 = chrs[i + 1]; }
73        let mut c3 = '#';
74        if i + 2 < length { c3 = chrs[i + 2]; }
75
76        // Skip past quoted text: ">>>>>"
77        if c1 == '"' {
78            let mut j = i + 1;
79            while j < length  {
80                let cx = chrs[j];
81                if cx == '"' {
82                    i = j;
83                    break;
84                }
85                j += 1;
86            }
87        }
88        else if c1 == '(' {
89            // Skip past text within parentheses: (...)
90            let mut j = i + 1;
91            while j < length {
92                let cx = chrs[j];
93                if cx == ')' {
94                    i = j;
95                    break;
96                }
97                j += 1;
98            }
99        }
100        else {
101            // Previous character must be space.
102            if prev != ' ' {
103                prev = c1;
104                i += 1;
105                continue;
106            }
107            // Bad:  $X =1
108            // Good: $X = 1
109            if i >= (length - 2) { return (Infix::None, 0); }
110            if c1 == '<' {
111                if c2 == '=' {
112                    if c3 == ' ' {
113                        return (Infix::LessThanOrEqual, i);
114                    }
115                }
116                else if c2 == ' ' {
117                    return (Infix::LessThan, i);
118                }
119            }
120            else if c1 == '>' {
121                if c2 == '=' {
122                    if c3 == ' ' {
123                        return (Infix::GreaterThanOrEqual, i);
124                    }
125                }
126                else if c2 == ' ' {
127                    return (Infix::GreaterThan, i);
128                }
129            }
130            else if c1 == '=' {
131                if c2 == '=' {
132                    if c3 == ' ' {
133                        return (Infix::Equal, i);
134                    }
135                }
136                else if c2 == ' ' {
137                    return (Infix::Unify, i);
138                }
139            }
140
141        } // else
142
143        prev = c1;
144        i += 1;
145
146    } // while
147
148    return (Infix::None, 0);  // failed to find infix
149
150} // check_infix
151
152/// Determines whether a string contains an arithmetic infix: +, -, *, /
153///
154/// This function returns the type and index of the arithmetic infix.<br>
155/// For example, <code>$X * 6</code> contains Infix::Multiply, at index 3.
156///
157/// # Arguments
158/// * vector of chars
159/// # Return
160/// * ([Infix](../infix/enum.Infix.html), index)
161///
162/// # Notes
163/// * An infix must be preceded and followed by a space. This is invalid:  `$X*6`
164/// * The function ignores characters between double quotes and parentheses.<br>
165/// For example, for the the string of characters `" * "` (double quotes included),<br>
166/// the function will return (Infix::None, 0).
167///
168/// # Usage
169/// ```
170/// use suiron::*;
171///
172/// let chrs = str_to_chars!("$Interest * 100");
173/// let (infix, index) = check_arithmetic_infix(&chrs);
174/// println!("{infix}, {index}");  // Prints: *, 10
175/// ```
176pub fn check_arithmetic_infix(chrs: &Vec<char>) -> (Infix, usize) {
177
178    let length = chrs.len();
179    let mut prev   = '#';  // not a space
180
181    let mut i = 0;
182    while i < length {
183
184        let c1 = chrs[i];
185        let mut c2 = '#';
186        if i + 1 < length { c2 = chrs[i + 1]; }
187
188        // Skip past quoted text: ">>>>>"
189        if c1 == '"' {
190            let mut j = i + 1;
191            while j < length  {
192                let cx = chrs[j];
193                if cx == '"' {
194                    i = j; break;
195                }
196                j += 1;
197            }
198        }
199        else if c1 == '(' {
200            // Skip past text within parentheses: (...)
201            let mut j = i + 1;
202            while j < length {
203                let cx = chrs[j];
204                if cx == ')' {
205                    i = j; break;
206                }
207                j += 1;
208            }
209        }
210        else {
211
212            // Previous character must be space.
213            if prev != ' ' {
214                prev = c1;
215                i += 1;
216                continue;
217            }
218
219            // Bad:  $X =1
220            // Good: $X = 1
221            if c1 == '+' { if c2 == ' ' { return (Infix::Plus, i); } }
222            else
223            if c1 == '-' { if c2 == ' ' { return (Infix::Minus, i); } }
224            else
225            if c1 == '*' { if c2 == ' ' { return (Infix::Multiply, i); } }
226            else
227            if c1 == '/' { if c2 == ' ' { return (Infix::Divide, i); } }
228        } // else
229
230        prev = c1;
231        i += 1;
232
233    } // while
234
235    return (Infix::None, 0);  // failed to find infix
236
237} // check_arithmetic_infix
238
239impl fmt::Display for Infix {
240    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
241        return match &self {
242            Infix::None => write!(f, "None"),
243            Infix::Unify => write!(f, "="),
244            Infix::Equal => write!(f, "=="),
245            Infix::GreaterThan => write!(f, ">"),
246            Infix::LessThan => write!(f, "<"),
247            Infix::GreaterThanOrEqual => write!(f, ">="),
248            Infix::LessThanOrEqual => write!(f, "<="),
249            Infix::Plus => write!(f, "+"),
250            Infix::Minus => write!(f, "-"),
251            Infix::Multiply => write!(f, "*"),
252            Infix::Divide => write!(f, "/"),
253        };
254    } // fmt
255} // fmt::Display
256
257#[cfg(test)]
258mod test {
259
260    use crate::str_to_chars;
261    use super::*;
262
263    #[test]
264    fn test_check_infix() {
265
266        let chrs = str_to_chars!("$X = $Y");
267        let (inf, ind) = check_infix(&chrs);
268        assert_eq!(inf, Infix::Unify);
269        assert_eq!(ind, 3, "Unify");
270
271        let chrs = str_to_chars!("$X =$Y");
272        let (inf, ind) = check_infix(&chrs);
273        assert_eq!(inf, Infix::None);
274        assert_eq!(ind, 0);
275
276        let chrs = str_to_chars!("$X > $Y");
277        let (inf, ind) = check_infix(&chrs);
278        assert_eq!(inf, Infix::GreaterThan);
279        assert_eq!(ind, 3, "GreaterThan");
280
281        let chrs = str_to_chars!("$Age >= 50");
282        let (inf, ind) = check_infix(&chrs);
283        assert_eq!(inf, Infix::GreaterThanOrEqual);
284        assert_eq!(ind, 5, "GreaterThanOrEqual");
285
286        let chrs = str_to_chars!("$Height < 152");
287        let (inf, ind) = check_infix(&chrs);
288        assert_eq!(inf, Infix::LessThan);
289        assert_eq!(ind, 8, "LessThan");
290
291        let chrs = str_to_chars!("$Grade <= 60");
292        let (inf, ind) = check_infix(&chrs);
293        assert_eq!(inf, Infix::LessThanOrEqual);
294        assert_eq!(ind, 7, "LessThanOrEqual");
295
296        let chrs = str_to_chars!("100 == $Score");
297        let (inf, ind) = check_infix(&chrs);
298        assert_eq!(inf, Infix::Equal);
299        assert_eq!(ind, 4, "Equal");
300
301        let chrs = str_to_chars!("\" <= \"");
302        let (inf, ind) = check_infix(&chrs);
303        assert_eq!(inf, Infix::None);
304        assert_eq!(ind, 0, "Infix between double quotes should be ignored.");
305
306        //-------------------------------------------------------
307        // There must be a space after an infix. Otherwise ignore.
308
309        let chrs = str_to_chars!("$X =1");
310        let (inf, ind) = check_infix(&chrs);
311        assert_eq!(inf, Infix::None);
312        assert_eq!(ind, 0);
313
314        let chrs = str_to_chars!("$X <=1");
315        let (inf, ind) = check_infix(&chrs);
316        assert_eq!(inf, Infix::None);
317        assert_eq!(ind, 0);
318
319        let chrs = str_to_chars!("$X >=1");
320        let (inf, ind) = check_infix(&chrs);
321        assert_eq!(inf, Infix::None);
322        assert_eq!(ind, 0);
323
324        let chrs = str_to_chars!("$X ==1");
325        let (inf, ind) = check_infix(&chrs);
326        assert_eq!(inf, Infix::None);
327        assert_eq!(ind, 0);
328
329        let chrs = str_to_chars!("$X <1");
330        let (inf, ind) = check_infix(&chrs);
331        assert_eq!(inf, Infix::None);
332        assert_eq!(ind, 0);
333
334        let chrs = str_to_chars!("$X >1");
335        let (inf, ind) = check_infix(&chrs);
336        assert_eq!(inf, Infix::None);
337        assert_eq!(ind, 0);
338    } // test_check_infix()
339
340    #[test]
341    fn test_check_arithmetic_infix() {
342
343        let chrs = str_to_chars!("$X * 7");
344        let (inf, ind) = check_arithmetic_infix(&chrs);
345        assert_eq!(inf, Infix::Multiply);
346        assert_eq!(ind, 3, "Multiply operator");
347
348        let chrs = str_to_chars!("$X / 7");
349        let (inf, ind) = check_arithmetic_infix(&chrs);
350        assert_eq!(inf, Infix::Divide);
351        assert_eq!(ind, 3, "Divide operator");
352
353        let chrs = str_to_chars!("$X + 7");
354        let (inf, ind) = check_arithmetic_infix(&chrs);
355        assert_eq!(inf, Infix::Plus);
356        assert_eq!(ind, 3, "Plus operator");
357
358        let chrs = str_to_chars!("$X - 7");
359        let (inf, ind) = check_arithmetic_infix(&chrs);
360        assert_eq!(inf, Infix::Minus);
361        assert_eq!(ind, 3, "Minus operator");
362
363        // Not an arithmetic operator.
364        let chrs = str_to_chars!("$X < 7");
365        let (inf, ind) = check_arithmetic_infix(&chrs);
366        assert_eq!(inf, Infix::None);
367        assert_eq!(ind, 0, "Not an arithmetic operator");
368
369        // Missing spaces.
370        let chrs = str_to_chars!("$X *7");
371        let (inf, ind) = check_arithmetic_infix(&chrs);
372        assert_eq!(inf, Infix::None);
373        assert_eq!(ind, 0, "Missing space.");
374
375        // Missing spaces.
376        let chrs = str_to_chars!("$X/ 7");
377        let (inf, ind) = check_arithmetic_infix(&chrs);
378        assert_eq!(inf, Infix::None);
379        assert_eq!(ind, 0, "Missing space.");
380
381        // Inside quotes.
382        let chrs = str_to_chars!("\"$X / 7\"");
383        let (inf, ind) = check_arithmetic_infix(&chrs);
384        assert_eq!(inf, Infix::None);
385        assert_eq!(ind, 0, "Between quotes.");
386
387        // Inside parentheses.
388        let chrs = str_to_chars!("($X + 7)");
389        let (inf, ind) = check_arithmetic_infix(&chrs);
390        assert_eq!(inf, Infix::None);
391        assert_eq!(ind, 0, "Between parentheses.");
392
393    } // test_check_arithmetic_infix()
394
395} // test