rusty_systems/
parser.rs

1//! Tools for parsing L-Systems from text strings.
2//!
3//! Important functions:
4//! * [`parse_prod_string`]
5//! * [`parse_production`]
6
7use crate::error::{Error, ErrorKind};
8use crate::prelude::*;
9use crate::productions::{Production, ProductionBody, ProductionHead};
10use crate::Result;
11
12/// Parse the body of a production rule.
13///
14/// For example, in the string `A -> B C`, the `B C` after the arrow
15/// is the rule's body.
16pub fn parse_production_body(body: &str) -> Result<ProductionBody> {
17    let body = body.trim();
18    if body.is_empty() {
19        return Ok(ProductionBody::empty());
20    }
21
22    let body = body.split_ascii_whitespace();
23
24    let mut body_tokens = Vec::new();
25    let mut chance : Option<f32> = None;
26
27    for (index, term) in body.enumerate() {
28        if index == 0 {
29            if let Ok(val) = term.parse() {
30                chance = Some(val);
31                continue;
32            }
33        }
34
35        body_tokens.push(Symbol::build(term)?);
36    }
37
38    match chance {
39        None => Ok(ProductionBody::new(ProductionString::from(body_tokens))),
40        Some(chance) => ProductionBody::try_with_chance(chance, ProductionString::from(body_tokens))
41    }
42}
43
44/// Parse the head of a production rule.
45pub fn parse_production_head(head: &str) -> Result<ProductionHead> {
46    let head = head.trim();
47
48    if head.is_empty() {
49        return Err(Error::new(ErrorKind::Parse, "no head in production string"));
50    }
51
52    let tokens : Vec<_> = head.split_ascii_whitespace().collect();
53    let split: Vec<_> = tokens.splitn(2, |s| *s == "<").collect();
54
55    let mut left : Option<&[&str]> = None;
56    let mut right : Option<&[&str]> = None;
57
58
59    let remains = if split.len() == 2 {
60        left = Some(split[0]);
61        split[1]
62    } else {
63        split[0]
64    };
65
66    let split : Vec<_> = remains.splitn(2, |s| *s == ">").collect();
67    let remains = if split.len() == 2 {
68        right = Some(split[1]);
69        split[0]
70    } else {
71        split[0]
72    };
73
74    if remains.len() != 1 {
75        return Err(Error::new(ErrorKind::Parse, "There should be exactly one token as the head target"))
76    }
77
78    let center = remains[0];
79    let head_token = Symbol::build(center)?;
80
81    let left = parse_head_context(left);
82    if let Some(Err(e)) = left {
83        return Err(e);
84    }
85
86    let left = left.map(|d| d.unwrap());
87
88    let right = parse_head_context(right);
89    if let Some(Err(e)) = right {
90        return Err(e);
91    }
92
93    let right = right.map(|d| d.unwrap());
94
95    ProductionHead::build(
96        left,
97        head_token,
98        right)
99}
100
101fn parse_head_context(strings: Option<&[&str]>) -> Option<Result<ProductionString>> {
102    strings.map(|strings| {
103        let iter = strings.iter().map(|s| Symbol::build(*s));
104        let error = iter.clone().find(|t| t.is_err());
105
106        if let Some(Err(e)) = error {
107            return Err(e);
108        }
109
110        let tokens: Vec<_> = iter.map(|t| t.unwrap()).collect();
111
112        Ok(ProductionString::from(tokens))
113    })
114}
115
116
117// Parse a production string.
118//
119// Most of the time you likely want to do this using [`System::parse_production`],
120// which is also thread safe. If you want to use this (note that this is not thread safe),
121// you can do so using your own implementations of:
122//
123// * [`ProductionStore`], which stores productions.
124// * [`SymbolStore`], which stores and generates unique tokens.
125//
126// Default implementations exist for
127//
128// * [`std::cell::RefCell<Vec<Production>>`] implements [`ProductionStore`].
129// * [`std::cell::RefCell<std::collections::HashMap<String, prelude::Token>>`] implements [`SymbolStore`].
130//
131// These are easy to use by just wrapping your collections in RefCell. Please note that doing this
132// is not thread safe:
133//
134// ```
135// use std::cell::RefCell;
136// use std::collections::{HashMap, HashSet};
137// use std::sync::Arc;
138// use rusty_systems::productions::Production;
139// use rusty_systems::parser::parse_production;
140// use rusty_systems::symbols::Symbol;
141//
142// // Create your token and production collections at some point
143// // in your code.
144// let tokens : HashSet<u32> = HashSet::new();
145// let productions : Vec<Production> = Vec::new();
146//
147// // ... Do a lot of other stuff. Call functions. Have fun!
148//
149// // Borrow your collections.
150// let token_cell = RefCell::new(tokens);
151// let production_cell = RefCell::new(productions);
152//
153// // Now we can parse a production. Note that because of the underlying
154// // stores, this is not thread safe.
155// let result = parse_production(&token_cell, &production_cell, "Name -> first surname");
156// // Check for errors, etc: result.is_err(), and so on.
157//
158// // Get your collections back from the cells.
159// // You can now look at the added tokens and productions in these collections.
160// let tokens = token_cell.take();
161// let productions = production_cell.take();
162//
163// ```
164pub fn parse_production(production: &str) -> Result<Production> {
165    let production = production.trim();
166    if production.is_empty() {
167        return Err(Error::new(ErrorKind::Parse,
168                              String::from("production string should not be an empty string")));
169    }
170
171    let index = production
172        .find("->")
173        .ok_or_else(|| Error::new(ErrorKind::Parse,
174                                  String::from("supplied string is not a production: ") + production))?;
175
176    let head_str = &production[0..index];
177    let body_str = &production[index + 2..];
178
179    let head = parse_production_head(head_str)?;
180    let body = parse_production_body(body_str)?;
181
182    Ok(Production::new(head, body))
183}
184
185
186
187/// Allows you to parse a text string into a string of [`Symbol`] objects
188/// to then rewrite using a [`System`]
189pub fn parse_prod_string(string: &str) -> Result<ProductionString> {
190    let mut result = ProductionString::default();
191
192    let items = string.trim().split_ascii_whitespace();
193
194
195    for term in items {
196        result.push_symbol(Symbol::build(term)?);
197    }
198
199    Ok(result)
200}
201
202
203
204
205#[cfg(test)]
206mod test {
207    use crate::parser::{parse_production_body, parse_production_head};
208    use crate::symbols::{get_code};
209
210    #[test]
211    fn can_parse_empty_body() {
212        let body = parse_production_body("");
213
214        assert!(body.unwrap().is_empty());
215    }
216
217    #[test]
218    fn can_parse_body_without_chance() {
219        let body = parse_production_body("A B").unwrap();
220
221        assert_eq!(body.len(), 2);
222        assert!(body.chance().is_derived());
223    }
224
225    #[test]
226    fn can_parse_body_with_chance() {
227        let body = parse_production_body("0.3 A B").unwrap();
228
229        assert_eq!(body.len(), 2);
230        assert!(body.chance().is_user_set());
231        assert_eq!(body.chance().unwrap(), 0.3);
232    }
233
234    #[test]
235    fn parsing_production_head() {
236        let head = parse_production_head("A").unwrap();
237        assert_eq!(get_code("A").unwrap(), head.target().code());
238
239        let head = parse_production_head("Pre < A > Post").unwrap();
240        assert_eq!(get_code("A").unwrap(), head.target().code());
241
242        let left = head.pre_context().unwrap();
243        assert_eq!(left.len(), 1);
244        assert_eq!(get_code("Pre").unwrap(), left[0].code());
245
246        let right = head.post_context().unwrap();
247        assert_eq!(right.len(), 1);
248        assert_eq!(get_code("Post").unwrap(), right[0].code());
249    }
250}
251