Skip to main content

variable_core/
parser.rs

1use crate::ast::{Feature, Value, VarFile, VarType, Variable};
2use crate::lexer::{Span, SpannedToken, Token};
3
4#[derive(Debug, Clone, PartialEq)]
5pub struct ParseError {
6    pub message: String,
7    pub span: Span,
8}
9
10struct Parser {
11    tokens: Vec<SpannedToken>,
12    pos: usize,
13}
14
15impl Parser {
16    fn new(tokens: Vec<SpannedToken>) -> Self {
17        Self { tokens, pos: 0 }
18    }
19
20    fn peek(&self) -> Option<&SpannedToken> {
21        self.tokens.get(self.pos)
22    }
23
24    fn advance(&mut self) -> Option<&SpannedToken> {
25        let token = self.tokens.get(self.pos);
26        if token.is_some() {
27            self.pos += 1;
28        }
29        token
30    }
31
32    fn expect(&mut self, expected: &Token) -> Result<&SpannedToken, ParseError> {
33        match self.peek() {
34            Some(t) if &t.token == expected => {
35                self.pos += 1;
36                Ok(&self.tokens[self.pos - 1])
37            }
38            Some(t) => Err(ParseError {
39                message: format!("expected {:?}, found {:?}", expected, t.token),
40                span: t.span.clone(),
41            }),
42            None => Err(ParseError {
43                message: format!("expected {:?}, found end of input", expected),
44                span: self.eof_span(),
45            }),
46        }
47    }
48
49    fn eof_span(&self) -> Span {
50        if let Some(last) = self.tokens.last() {
51            Span {
52                offset: last.span.offset + 1,
53                line: last.span.line,
54                column: last.span.column + 1,
55            }
56        } else {
57            Span {
58                offset: 0,
59                line: 1,
60                column: 1,
61            }
62        }
63    }
64
65    fn current_span(&self) -> Span {
66        match self.peek() {
67            Some(t) => t.span.clone(),
68            None => self.eof_span(),
69        }
70    }
71
72    fn parse_file(&mut self) -> Result<VarFile, ParseError> {
73        let mut features = Vec::new();
74        while self.peek().is_some() {
75            features.push(self.parse_feature()?);
76        }
77        Ok(VarFile { features })
78    }
79
80    fn parse_scoped_id(&mut self, scope: &str) -> Result<u32, ParseError> {
81        let (raw_id, span) = match self.advance() {
82            Some(SpannedToken {
83                token: Token::NumberLit(n),
84                span,
85            }) => (*n, span.clone()),
86            Some(t) => {
87                return Err(ParseError {
88                    message: format!("expected {} id, found {:?}", scope, t.token),
89                    span: t.span.clone(),
90                });
91            }
92            None => {
93                return Err(ParseError {
94                    message: format!("expected {} id, found end of input", scope),
95                    span: self.eof_span(),
96                });
97            }
98        };
99
100        if raw_id.fract() != 0.0 || raw_id < 0.0 || raw_id > u32::MAX as f64 {
101            return Err(ParseError {
102                message: format!("expected {} id to be a u32, found {}", scope, raw_id),
103                span,
104            });
105        }
106
107        self.expect(&Token::Colon)?;
108
109        Ok(raw_id as u32)
110    }
111
112    fn parse_feature(&mut self) -> Result<Feature, ParseError> {
113        let span = self.current_span();
114        let id = self.parse_scoped_id("feature")?;
115        self.expect(&Token::Feature)?;
116
117        let name = match self.advance() {
118            Some(SpannedToken {
119                token: Token::Ident(name),
120                ..
121            }) => name.clone(),
122            Some(t) => {
123                return Err(ParseError {
124                    message: format!("expected feature name, found {:?}", t.token),
125                    span: t.span.clone(),
126                });
127            }
128            None => {
129                return Err(ParseError {
130                    message: "expected feature name, found end of input".to_string(),
131                    span: self.eof_span(),
132                });
133            }
134        };
135
136        self.expect(&Token::Equals)?;
137        self.expect(&Token::LBrace)?;
138
139        let mut variables = Vec::new();
140        while self.peek().is_some_and(|t| t.token != Token::RBrace) {
141            variables.push(self.parse_variable()?);
142        }
143
144        self.expect(&Token::RBrace)?;
145
146        Ok(Feature {
147            id,
148            name,
149            variables,
150            span,
151        })
152    }
153
154    fn parse_variable(&mut self) -> Result<Variable, ParseError> {
155        let span = self.current_span();
156        let id = self.parse_scoped_id("variable")?;
157        self.expect(&Token::Variable)?;
158
159        let name = match self.advance() {
160            Some(SpannedToken {
161                token: Token::Ident(name),
162                ..
163            }) => name.clone(),
164            Some(t) => {
165                return Err(ParseError {
166                    message: format!("expected variable name, found {:?}", t.token),
167                    span: t.span.clone(),
168                });
169            }
170            None => {
171                return Err(ParseError {
172                    message: "expected variable name, found end of input".to_string(),
173                    span: self.eof_span(),
174                });
175            }
176        };
177
178        let var_type = match self.advance() {
179            Some(SpannedToken {
180                token: Token::BooleanType,
181                ..
182            }) => VarType::Boolean,
183            Some(SpannedToken {
184                token: Token::NumberType,
185                ..
186            }) => VarType::Number,
187            Some(SpannedToken {
188                token: Token::StringType,
189                ..
190            }) => VarType::String,
191            Some(t) => {
192                return Err(ParseError {
193                    message: format!(
194                        "expected type (Boolean, Number, or String), found {:?}",
195                        t.token
196                    ),
197                    span: t.span.clone(),
198                });
199            }
200            None => {
201                return Err(ParseError {
202                    message: "expected type, found end of input".to_string(),
203                    span: self.eof_span(),
204                });
205            }
206        };
207
208        self.expect(&Token::Equals)?;
209
210        let default = match self.advance() {
211            Some(SpannedToken {
212                token: Token::BoolLit(b),
213                ..
214            }) => Value::Boolean(*b),
215            Some(SpannedToken {
216                token: Token::NumberLit(n),
217                ..
218            }) => Value::Number(*n),
219            Some(SpannedToken {
220                token: Token::StringLit(s),
221                ..
222            }) => Value::String(s.clone()),
223            Some(t) => {
224                return Err(ParseError {
225                    message: format!("expected default value, found {:?}", t.token),
226                    span: t.span.clone(),
227                });
228            }
229            None => {
230                return Err(ParseError {
231                    message: "expected default value, found end of input".to_string(),
232                    span: self.eof_span(),
233                });
234            }
235        };
236
237        Ok(Variable {
238            id,
239            name,
240            var_type,
241            default,
242            span,
243        })
244    }
245}
246
247pub fn parse(tokens: Vec<SpannedToken>) -> Result<VarFile, ParseError> {
248    let mut parser = Parser::new(tokens);
249    parser.parse_file()
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use crate::lexer::lex;
256
257    fn parse_source(input: &str) -> Result<VarFile, ParseError> {
258        let tokens = lex(input).map_err(|e| ParseError {
259            message: e.message,
260            span: e.span,
261        })?;
262        parse(tokens)
263    }
264
265    #[test]
266    fn parse_single_boolean_variable() {
267        let input = r#"1: Feature Flags = {
268    1: Variable enabled Boolean = true
269}"#;
270        let file = parse_source(input).unwrap();
271        assert_eq!(file.features.len(), 1);
272        assert_eq!(file.features[0].id, 1);
273        assert_eq!(file.features[0].name, "Flags");
274        assert_eq!(file.features[0].variables.len(), 1);
275        assert_eq!(file.features[0].variables[0].id, 1);
276        assert_eq!(file.features[0].variables[0].name, "enabled");
277        assert_eq!(file.features[0].variables[0].var_type, VarType::Boolean);
278        assert_eq!(file.features[0].variables[0].default, Value::Boolean(true));
279    }
280
281    #[test]
282    fn parse_single_number_variable() {
283        let input = r#"1: Feature Config = {
284    1: Variable max_items Number = 50
285}"#;
286        let file = parse_source(input).unwrap();
287        assert_eq!(file.features[0].variables[0].var_type, VarType::Number);
288        assert_eq!(file.features[0].variables[0].default, Value::Number(50.0));
289    }
290
291    #[test]
292    fn parse_single_string_variable() {
293        let input = r#"1: Feature Config = {
294    1: Variable title String = "Hello"
295}"#;
296        let file = parse_source(input).unwrap();
297        assert_eq!(file.features[0].variables[0].var_type, VarType::String);
298        assert_eq!(
299            file.features[0].variables[0].default,
300            Value::String("Hello".to_string())
301        );
302    }
303
304    #[test]
305    fn parse_multiple_features() {
306        let input = r#"1: Feature A = {
307    1: Variable x Boolean = true
308}
309
3102: Feature B = {
311    1: Variable y Number = 42
312}"#;
313        let file = parse_source(input).unwrap();
314        assert_eq!(file.features.len(), 2);
315        assert_eq!(file.features[0].id, 1);
316        assert_eq!(file.features[0].name, "A");
317        assert_eq!(file.features[1].id, 2);
318        assert_eq!(file.features[1].name, "B");
319    }
320
321    #[test]
322    fn parse_example_var_file() {
323        let input = r#"1: Feature Checkout = {
324    1: Variable enabled Boolean = true
325    2: Variable max_items Number = 50
326    3: Variable header_text String = "Complete your purchase"
327}"#;
328        let file = parse_source(input).unwrap();
329        assert_eq!(file.features.len(), 1);
330        let feature = &file.features[0];
331        assert_eq!(feature.id, 1);
332        assert_eq!(feature.name, "Checkout");
333        assert_eq!(feature.variables.len(), 3);
334        assert_eq!(feature.variables[0].id, 1);
335        assert_eq!(feature.variables[0].name, "enabled");
336        assert_eq!(feature.variables[1].id, 2);
337        assert_eq!(feature.variables[1].name, "max_items");
338        assert_eq!(feature.variables[2].id, 3);
339        assert_eq!(feature.variables[2].name, "header_text");
340    }
341
342    #[test]
343    fn error_missing_lbrace() {
344        let input = "1: Feature Checkout = 1: Variable x Boolean = true }";
345        let err = parse_source(input).unwrap_err();
346        assert!(err.message.contains("expected LBrace"));
347    }
348
349    #[test]
350    fn error_missing_rbrace() {
351        let input = r#"1: Feature Checkout = {
352    1: Variable x Boolean = true"#;
353        let err = parse_source(input).unwrap_err();
354        assert!(err.message.contains("expected RBrace"));
355    }
356
357    #[test]
358    fn error_missing_default_value() {
359        let input = r#"1: Feature Checkout = {
360    1: Variable x Boolean =
361}"#;
362        let err = parse_source(input).unwrap_err();
363        assert!(err.message.contains("expected default value"));
364    }
365
366    #[test]
367    fn error_missing_type() {
368        let input = r#"1: Feature Checkout = {
369    1: Variable x = true
370}"#;
371        let err = parse_source(input).unwrap_err();
372        assert!(err.message.contains("expected type"));
373    }
374
375    #[test]
376    fn error_unknown_type_keyword() {
377        let input = r#"1: Feature Checkout = {
378    1: Variable x Integer = 5
379}"#;
380        let err = parse_source(input).unwrap_err();
381        assert!(err.message.contains("expected type"));
382    }
383
384    #[test]
385    fn error_spans_point_to_correct_location() {
386        let input = "1: Feature Checkout = {\n    1: Variable x = true\n}";
387        let err = parse_source(input).unwrap_err();
388        // The "=" is the token where we expected a type
389        assert_eq!(err.span.line, 2);
390    }
391
392    #[test]
393    fn error_missing_feature_id() {
394        let input = r#"Feature Checkout = {
395    1: Variable enabled Boolean = true
396}"#;
397        let err = parse_source(input).unwrap_err();
398        assert!(err.message.contains("expected feature id"));
399    }
400
401    #[test]
402    fn error_missing_variable_id() {
403        let input = r#"1: Feature Checkout = {
404    Variable enabled Boolean = true
405}"#;
406        let err = parse_source(input).unwrap_err();
407        assert!(err.message.contains("expected variable id"));
408    }
409
410    #[test]
411    fn error_non_integer_feature_id() {
412        let input = r#"1.5: Feature Checkout = {
413    1: Variable enabled Boolean = true
414}"#;
415        let err = parse_source(input).unwrap_err();
416        assert!(err.message.contains("expected feature id to be a u32"));
417    }
418}