scl/
parser.rs

1use std::path::Path;
2use std::env;
3use std::fs::File;
4use std::io::prelude::*;
5
6use pest::Parser;
7use pest::iterators::Pair;
8
9use errors::Error;
10use value::{Date, Dict, Value};
11
12
13// This include forces recompiling this source file if the grammar file changes.
14// Uncomment it when doing changes to the .pest file
15#[cfg(debug_assertions)]
16const _GRAMMAR: &str = include_str!("scl.pest");
17
18
19#[derive(Parser)]
20#[grammar = "scl.pest"]
21pub struct SclParser;
22
23
24/// A struct that keeps the state of the current file being parsed
25/// in order for the include to work and for the errors to point
26/// to the file.
27/// It is also used when parsing a string.
28#[derive(Debug, PartialEq, Default)]
29struct ParserState<'a> {
30    /// If the path is `None`, we're parsing a string and the include
31    /// should just resolve in whatever directory we're in
32    path: Option<&'a Path>,
33}
34
35impl<'a> ParserState<'a> {
36    // TODO: error on different cast/default type
37    fn parse_env_var(&self, pair: Pair<Rule>) -> Value {
38        let mut key = None;
39        let mut cast = None;
40        let mut default = None;
41
42        for p in pair.into_inner() {
43            match p.as_rule() {
44                Rule::key => {
45                    key = Some(p.into_span().as_str().to_string());
46                },
47                Rule::env_var_cast => {
48                    cast = Some(p.into_span().as_str().to_string());
49                },
50                _ => {
51                    default = Some(self.parse_value(p));
52                }
53            };
54        }
55
56        if let Some(ref c) = cast {
57            if let Some(ref d) = default {
58                if c != d.type_str() {
59                    panic!("TO IMPLEMENT: error on different cast/default type")
60                }
61            }
62        }
63
64        match env::var(&key.unwrap()) {
65            Ok(s) => {
66                if let Some(c) = cast {
67                    // TODO: error handling
68                    match c.as_str() {
69                        "integer" => Value::Integer(s.parse().unwrap()),
70                        "float" => Value::Float(s.parse().unwrap()),
71                        "bool" => Value::Boolean(s.parse().unwrap()),
72                        "date" => Value::Date(Date::from_str(&c)),
73                        _ => unreachable!()
74                    }
75                } else {
76                    Value::String(s)
77                }
78            },
79            Err(_) => default.unwrap(),
80        }
81    }
82
83    // TODO: return an error if types are different
84    fn parse_array(&self, pair: Pair<Rule>) -> Value {
85        let mut items = vec![];
86
87        for p in pair.into_inner() {
88            // we can only have Rule::Value here, no need to match
89            let val = self.parse_value(p.into_inner().next().unwrap());
90            if let Some(last) = items.last() {
91                if !val.same_type(last) {
92                    // TODO: return an error
93                }
94            }
95            items.push(val);
96        }
97
98        Value::Array(items)
99    }
100
101    fn parse_byte_size(&self, pair: Pair<Rule>) -> Value {
102        let mut num: Option<f64> = None;
103
104        for p in pair.into_inner() {
105            match p.as_rule() {
106                Rule::byte_size_number => {
107                    num = Some(p.as_str().parse().unwrap());
108                }
109                Rule::byte_size_unit => {
110                    let n = num.unwrap();
111                    let res = match p.as_str() {
112                        "kB" | "KB" => n * 1e3,
113                        "MB" => n * 1e6,
114                        "GB" => n * 1e9,
115                        "TB" => n * 1e12,
116                        "PB" => n * 1e15,
117                        _ => unreachable!(),
118                    };
119
120                    return Value::Integer(res as i64);
121                }
122                _ => unreachable!(),
123            }
124        }
125
126        unreachable!("Got a byte size without a unit?")
127    }
128
129    fn parse_value(&self, pair: Pair<Rule>) -> Value {
130        match pair.as_rule() {
131            Rule::int => Value::Integer(pair.as_str().parse().unwrap()),
132            Rule::float => Value::Float(pair.as_str().parse().unwrap()),
133            Rule::byte_size => self.parse_byte_size(pair),
134            Rule::boolean => match pair.as_str() {
135                "true" => Value::Boolean(true),
136                "false" => Value::Boolean(false),
137                _ => unreachable!(),
138            },
139            Rule::string => Value::String(pair.as_str().replace("\"", "").to_string()),
140            Rule::multiline_string => {
141                let text = pair.as_str().replace("\"\"\"", "");
142                if text.starts_with('\n') {
143                    Value::String(text.trim_left().to_string())
144                } else {
145                    Value::String(text.to_string())
146                }
147            }
148            Rule::env_var => self.parse_env_var(pair),
149            Rule::date => Value::Date(Date::from_str(pair.as_str())),
150            Rule::array => self.parse_array(pair),
151            Rule::dict => Value::Dict(self.parse_dict(pair).unwrap()), // todo: error handling
152            _ => unreachable!("Got an unexpected value: {:?}", pair),
153        }
154    }
155
156    fn parse_key_value(&self, pair: Pair<Rule>) -> (String, Value) {
157        let mut key = None;
158        let mut value = None;
159
160        for p in pair.into_inner() {
161            match p.as_rule() {
162                Rule::key => {
163                    key = Some(p.into_span().as_str().to_string());
164                }
165                // The grammar made sure we can only have one value or an include
166                Rule::value => {
167                    value = Some(self.parse_value(p.into_inner().next().unwrap()));
168                }
169                Rule::include => {
170                    value = Some(Value::Dict(self.parse_include(p).unwrap())); // TODO: error handling
171                }
172                _ => unreachable!("Got something in key/value other than a key/value: {:?}", p),
173            };
174        }
175
176        (key.unwrap(), value.unwrap())
177    }
178
179    fn parse_dict(&self, pair: Pair<Rule>) -> Result<Dict, Error> {
180        let mut dict = Dict::new();
181
182        for p in pair.into_inner() {
183            match p.as_rule() {
184                Rule::include => {
185                    let included = self.parse_include(p)?;
186                    dict.extend(included);
187                }
188                Rule::key_value => {
189                    let (key, value) = self.parse_key_value(p);
190                    dict.insert(key, value);
191                }
192                _ => unreachable!("unknown dict rule: {:?}", p.as_rule()),
193            }
194        }
195
196        Ok(dict)
197    }
198
199    fn parse_include(&self, pair: Pair<Rule>) -> Result<Dict, Error> {
200        // next inner token is the filename
201        let path = pair.into_inner()
202            .next()
203            .unwrap()
204            .into_span()
205            .as_str()
206            .replace("\"", "");
207
208        // we have to deal wih an include
209        // - if we do not have a current path, just call `parse_file`, we can't
210        // give any context
211        // - if we have one, first join the current dir and the filename
212
213        if let Some(current_path) = self.path {
214            // if the path is absolute, don't append the current path to it
215            if path.starts_with('/') {
216                parse_file(path)
217            } else {
218                // TODO: error handling for parent()?
219                let full_path = current_path.parent().unwrap().join(path);
220                parse_file(&full_path)
221            }
222        } else {
223            parse_file(path)
224        }
225    }
226
227    /// Parse the given string
228    pub fn parse_str(&self, input: &str) -> Result<Dict, Error> {
229        let mut pairs = match SclParser::parse(Rule::document, input) {
230            Ok(p) => p,
231            Err(e) => {
232                let fancy_e = e.renamed_rules(|rule| {
233                    match *rule {
234                        Rule::document => "a key value, an include or a comment".to_string(),
235                        Rule::key => "a key".to_string(),
236                        Rule::boolean => "a boolean (true / false)".to_string(),
237                        Rule::string => "a string".to_string(),
238                        Rule::multiline_string => "a multiline string".to_string(),
239                        Rule::int => "an integer".to_string(),
240                        Rule::float => "a float".to_string(),
241                        Rule::date => "a date".to_string(),
242                        Rule::key_value => "a key value".to_string(),
243                        Rule::byte_size_unit => "a byte size unit (kB / MB / GB / TB / PB)".to_string(),
244                        Rule::value => "string / int / float / byte size / date / bool / array / dict / environment variable".to_string(),
245                        Rule::include => "include".to_string(),
246                        Rule::byte_size_number => "a number".to_string(),
247                        Rule::env_var => "an environment variable".to_string(),
248                        Rule::env_var_cast => "a cast to integer/float/date/bool".to_string(),
249                        Rule::array => "an array".to_string(),
250                        Rule::dict => "a dictionary".to_string(),
251                        _ => format!("TODO: {:?}", rule),
252                    }
253                });
254                return Err(Error::InvalidSyntax(format!("{}", fancy_e)));
255            }
256        };
257
258        // We must have at least a `document` pair if we got there
259        self.parse_dict(pairs.next().unwrap())
260    }
261
262}
263
264/// Parse the file at the given path
265pub fn parse_file<T: AsRef<Path>>(path: T) -> Result<Dict, Error> {
266    let mut f = File::open(&path).expect("file not found");
267    let mut contents = String::new();
268    // TODO: error handling
269    f.read_to_string(&mut contents).expect("something went wrong reading the file");
270
271    let state = ParserState { path: Some(&path.as_ref()) };
272
273    state.parse_str(&contents)
274}
275
276/// Parse the file at the given path
277pub fn parse_str(input: &str) -> Result<Dict, Error> {
278    let state = ParserState { path: None };
279
280    state.parse_str(&input)
281}