logo_interp/
parser.rs

1use std::collections::{HashMap, HashSet};
2use crate::core::*;
3
4lazy_static! {
5    static ref TERMINATOR_CHARS: HashSet<char>
6        = HashSet::from(['[', ']', '(', ')', '*', '/', '=']);
7}
8
9fn is_terminator_char(ch: char) -> bool {
10    return ch.is_whitespace() || TERMINATOR_CHARS.contains(&ch);
11}
12
13pub fn parse(source: &str) -> Result<Vec<LogoValue>, String> {
14    #[derive(PartialEq)]
15    enum Mode {
16        None,
17        Word,
18        DoubleQuoteString,
19        SingleQuoteString,
20    }
21    let mut mode = Mode::None;
22    let mut pending_word = String::new();
23
24    let mut list_stack: Vec<Vec<LogoValue>> = Vec::new();
25    list_stack.push(Vec::new());
26    for ch in source.chars() {
27        if (mode == Mode::Word || mode == Mode::DoubleQuoteString) && is_terminator_char(ch) {
28            if mode == Mode::Word {
29                list_stack.last_mut().unwrap().push(LogoValue::Word(Word(pending_word)));
30            }
31            else {
32                list_stack.last_mut().unwrap().push(LogoValue::String(pending_word));
33            }
34            pending_word = String::new();
35            mode = Mode::None;
36        }
37        if mode == Mode::SingleQuoteString && ch == '\'' {
38            list_stack.last_mut().unwrap().push(LogoValue::String(pending_word));
39            pending_word = String::new();
40            mode = Mode::None;
41            continue;
42        }
43
44        if mode != Mode::None {
45            pending_word.push(ch);
46            continue;
47        }
48
49        if ch.is_whitespace() {}
50        else if ch == '[' {
51            list_stack.push(Vec::new());
52        }
53        else if ch == ']' {
54            let last_list = list_stack.pop().unwrap();
55            match list_stack.last_mut() {
56                Some(stack) => stack.push(LogoValue::List(last_list)),
57                None => return Err("Not matched closing bracket".to_string())
58            }
59        }
60        else if ch == '"' {
61            mode = Mode::DoubleQuoteString;
62        }
63        else if ch == '\'' {
64            mode = Mode::SingleQuoteString;
65        }
66        else if TERMINATOR_CHARS.contains(&ch) {
67            list_stack.last_mut().unwrap().push(LogoValue::Word(Word(ch.to_string())));
68        }
69        else {
70            mode = Mode::Word;
71            pending_word = String::from(ch);
72        }
73    }
74    match mode {
75        Mode::None => {},
76        Mode::Word => list_stack.last_mut().unwrap().push(LogoValue::Word(Word(pending_word))),
77        Mode::DoubleQuoteString => list_stack.last_mut().unwrap().push(LogoValue::String(pending_word)),
78        Mode::SingleQuoteString => {
79            return Err(String::from("Missing closing quote"))
80        }
81    }
82    if list_stack.len() > 1 {
83        return Err(String::from("Missing closing bracket"));
84    }
85    return Ok(process_plus_minus(list_stack.pop().unwrap()));
86}
87
88fn process_plus_minus(list: Vec<LogoValue>) -> Vec<LogoValue> {
89    let mut result = Vec::with_capacity(list.len());
90    for val in list {
91        match val {
92            LogoValue::String(s) => result.push(LogoValue::String(s)),
93            LogoValue::List(sublist) => result.push(LogoValue::List(process_plus_minus(sublist))),
94            LogoValue::Word(word) => {
95                let mut cur = String::new();
96                for ch in word.0.chars() {
97                    if ch != '+' && ch != '-' {
98                        cur.push(ch);
99                        continue;
100                    }
101                    if cur.is_empty() {
102                        cur.push(ch);
103                    }
104                    else {
105                        result.push(LogoValue::Word(Word(cur)));
106                        result.push(LogoValue::Word(Word(ch.to_string())));
107                        cur = String::new();
108                    }
109                }
110                if !cur.is_empty() {
111                    result.push(LogoValue::Word(Word(cur)));
112                }
113            }
114        }
115    }
116    result
117}
118
119pub fn parse_procedures(source: &str) -> Result<HashMap<String, LogoProcedure>, String> {
120    let mut result = HashMap::new();
121    let mut name = String::new();
122    let mut arg_names = Vec::new();
123    let mut code = Vec::new();
124    let values = parse(source)?;
125    #[derive(PartialEq)]
126    enum Mode {
127        None,
128        Name,
129        Params,
130        Body
131    }
132    let mut mode = Mode::None;
133
134    for value in values {
135        if mode == Mode::None {
136            if let LogoValue::Word(word) = &value {
137                if word.0.to_lowercase() == "to" {
138                    mode = Mode::Name;
139                    continue;
140                }
141            }
142        }
143        if mode == Mode::Name {
144            if let LogoValue::Word(word) = &value {
145                name = word.0.to_lowercase();
146                mode = Mode::Params;
147                continue;
148            }
149        }
150        if mode == Mode::Params {
151            if let LogoValue::Word(word) = &value {
152                if let Some(arg_name) = word.0.strip_prefix(":") {
153                    arg_names.push(arg_name.to_lowercase());
154                    continue;
155                }
156            }
157            mode = Mode::Body;
158        }
159        if mode == Mode::Body {
160            if let LogoValue::Word(word) = &value {
161                if word.0.to_lowercase() == "end" {
162                    mode = Mode::None;
163                    result.insert(name, LogoProcedure {arg_names, code});
164                    name = String::new();
165                    arg_names = Vec::new();
166                    code = Vec::new();
167                    continue;
168                }
169            }
170            code.push(value);
171            continue;
172        }
173        return Err("Invalid procedure syntax".to_string());
174    }
175
176    if mode != Mode::None {
177        return Err("Invalid procedure syntax".to_string());
178    }
179
180    Ok(result)
181}
182
183#[test]
184fn test_loop_parsing() {
185    let result = parse("repeat 12  [rt 30 repeat 4 [fd   50 rt 90]]");
186    let expected = vec![
187        LogoValue::Word(Word("repeat".to_string())),
188        LogoValue::Word(Word("12".to_string())),
189        LogoValue::List(vec![
190            LogoValue::Word(Word("rt".to_string())),
191            LogoValue::Word(Word("30".to_string())),
192            LogoValue::Word(Word("repeat".to_string())),
193            LogoValue::Word(Word("4".to_string())),
194            LogoValue::List(vec![
195                LogoValue::Word(Word("fd".to_string())),
196                LogoValue::Word(Word("50".to_string())),
197                LogoValue::Word(Word("rt".to_string())),
198                LogoValue::Word(Word("90".to_string())),
199            ])
200        ])
201    ];
202    assert_eq!(result, Ok(expected));
203}
204
205#[test]
206fn test_strings() {
207    let result = parse("\"hello world 'long string' blah");
208    let expected = vec![
209        LogoValue::String("hello".to_string()),
210        LogoValue::Word(Word("world".to_string())),
211        LogoValue::String("long string".to_string()),
212        LogoValue::Word(Word("blah".to_string())),
213    ];
214    assert_eq!(result, Ok(expected))
215}
216
217#[test]
218fn test_errors() {
219    let result = parse("[[]");
220    assert_eq!(result, Err("Missing closing bracket".to_string()));
221    let result = parse("[]]");
222    assert_eq!(result, Err("Not matched closing bracket".to_string()));
223    let result = parse("blah 'long string");
224    assert_eq!(result, Err("Missing closing quote".to_string()));
225}
226
227#[test]
228fn test_math() {
229    let result = parse("2+2");
230    let expected = Ok(vec![
231        LogoValue::Word(Word("2".to_string())),
232        LogoValue::Word(Word("+".to_string())),
233        LogoValue::Word(Word("2".to_string())),
234    ]);
235    assert_eq!(result, expected);
236    let result = parse("2 + 2");
237    assert_eq!(result, expected);
238
239    let result = parse("2 +2");
240    let expected = Ok(vec![
241        LogoValue::Word(Word("2".to_string())),
242        LogoValue::Word(Word("+2".to_string())),
243    ]);
244    assert_eq!(result, expected);
245
246    let result = parse("2 -2");
247    let expected = Ok(vec![
248        LogoValue::Word(Word("2".to_string())),
249        LogoValue::Word(Word("-2".to_string())),
250    ]);
251    assert_eq!(result, expected);
252}