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_feature(&mut self) -> Result<Feature, ParseError> {
81        let span = self.current_span();
82        self.expect(&Token::Feature)?;
83
84        let name = match self.advance() {
85            Some(SpannedToken {
86                token: Token::Ident(name),
87                ..
88            }) => name.clone(),
89            Some(t) => {
90                return Err(ParseError {
91                    message: format!("expected feature name, found {:?}", t.token),
92                    span: t.span.clone(),
93                });
94            }
95            None => {
96                return Err(ParseError {
97                    message: "expected feature name, found end of input".to_string(),
98                    span: self.eof_span(),
99                });
100            }
101        };
102
103        self.expect(&Token::LBrace)?;
104
105        let mut variables = Vec::new();
106        while self.peek().is_some_and(|t| t.token != Token::RBrace) {
107            variables.push(self.parse_variable()?);
108        }
109
110        self.expect(&Token::RBrace)?;
111
112        Ok(Feature {
113            name,
114            variables,
115            span,
116        })
117    }
118
119    fn parse_variable(&mut self) -> Result<Variable, ParseError> {
120        let span = self.current_span();
121        self.expect(&Token::Variable)?;
122
123        let name = match self.advance() {
124            Some(SpannedToken {
125                token: Token::Ident(name),
126                ..
127            }) => name.clone(),
128            Some(t) => {
129                return Err(ParseError {
130                    message: format!("expected variable name, found {:?}", t.token),
131                    span: t.span.clone(),
132                });
133            }
134            None => {
135                return Err(ParseError {
136                    message: "expected variable name, found end of input".to_string(),
137                    span: self.eof_span(),
138                });
139            }
140        };
141
142        let var_type = match self.advance() {
143            Some(SpannedToken {
144                token: Token::BooleanType,
145                ..
146            }) => VarType::Boolean,
147            Some(SpannedToken {
148                token: Token::NumberType,
149                ..
150            }) => VarType::Number,
151            Some(SpannedToken {
152                token: Token::StringType,
153                ..
154            }) => VarType::String,
155            Some(t) => {
156                return Err(ParseError {
157                    message: format!(
158                        "expected type (Boolean, Number, or String), found {:?}",
159                        t.token
160                    ),
161                    span: t.span.clone(),
162                });
163            }
164            None => {
165                return Err(ParseError {
166                    message: "expected type, found end of input".to_string(),
167                    span: self.eof_span(),
168                });
169            }
170        };
171
172        self.expect(&Token::Equals)?;
173
174        let default = match self.advance() {
175            Some(SpannedToken {
176                token: Token::BoolLit(b),
177                ..
178            }) => Value::Boolean(*b),
179            Some(SpannedToken {
180                token: Token::NumberLit(n),
181                ..
182            }) => Value::Number(*n),
183            Some(SpannedToken {
184                token: Token::StringLit(s),
185                ..
186            }) => Value::String(s.clone()),
187            Some(t) => {
188                return Err(ParseError {
189                    message: format!("expected default value, found {:?}", t.token),
190                    span: t.span.clone(),
191                });
192            }
193            None => {
194                return Err(ParseError {
195                    message: "expected default value, found end of input".to_string(),
196                    span: self.eof_span(),
197                });
198            }
199        };
200
201        Ok(Variable {
202            name,
203            var_type,
204            default,
205            span,
206        })
207    }
208}
209
210pub fn parse(tokens: Vec<SpannedToken>) -> Result<VarFile, ParseError> {
211    let mut parser = Parser::new(tokens);
212    parser.parse_file()
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::lexer::lex;
219
220    fn parse_source(input: &str) -> Result<VarFile, ParseError> {
221        let tokens = lex(input).map_err(|e| ParseError {
222            message: e.message,
223            span: e.span,
224        })?;
225        parse(tokens)
226    }
227
228    #[test]
229    fn parse_single_boolean_variable() {
230        let input = r#"Feature Flags {
231    Variable enabled Boolean = true
232}"#;
233        let file = parse_source(input).unwrap();
234        assert_eq!(file.features.len(), 1);
235        assert_eq!(file.features[0].name, "Flags");
236        assert_eq!(file.features[0].variables.len(), 1);
237        assert_eq!(file.features[0].variables[0].name, "enabled");
238        assert_eq!(file.features[0].variables[0].var_type, VarType::Boolean);
239        assert_eq!(file.features[0].variables[0].default, Value::Boolean(true));
240    }
241
242    #[test]
243    fn parse_single_number_variable() {
244        let input = r#"Feature Config {
245    Variable max_items Number = 50
246}"#;
247        let file = parse_source(input).unwrap();
248        assert_eq!(file.features[0].variables[0].var_type, VarType::Number);
249        assert_eq!(
250            file.features[0].variables[0].default,
251            Value::Number(50.0)
252        );
253    }
254
255    #[test]
256    fn parse_single_string_variable() {
257        let input = r#"Feature Config {
258    Variable title String = "Hello"
259}"#;
260        let file = parse_source(input).unwrap();
261        assert_eq!(file.features[0].variables[0].var_type, VarType::String);
262        assert_eq!(
263            file.features[0].variables[0].default,
264            Value::String("Hello".to_string())
265        );
266    }
267
268    #[test]
269    fn parse_multiple_features() {
270        let input = r#"Feature A {
271    Variable x Boolean = true
272}
273
274Feature B {
275    Variable y Number = 42
276}"#;
277        let file = parse_source(input).unwrap();
278        assert_eq!(file.features.len(), 2);
279        assert_eq!(file.features[0].name, "A");
280        assert_eq!(file.features[1].name, "B");
281    }
282
283    #[test]
284    fn parse_example_var_file() {
285        let input = r#"Feature Checkout {
286    Variable enabled Boolean = true
287    Variable max_items Number = 50
288    Variable header_text String = "Complete your purchase"
289}"#;
290        let file = parse_source(input).unwrap();
291        assert_eq!(file.features.len(), 1);
292        let feature = &file.features[0];
293        assert_eq!(feature.name, "Checkout");
294        assert_eq!(feature.variables.len(), 3);
295        assert_eq!(feature.variables[0].name, "enabled");
296        assert_eq!(feature.variables[1].name, "max_items");
297        assert_eq!(feature.variables[2].name, "header_text");
298    }
299
300    #[test]
301    fn error_missing_lbrace() {
302        let input = "Feature Checkout Variable x Boolean = true }";
303        let err = parse_source(input).unwrap_err();
304        assert!(err.message.contains("expected LBrace"));
305    }
306
307    #[test]
308    fn error_missing_rbrace() {
309        let input = r#"Feature Checkout {
310    Variable x Boolean = true"#;
311        let err = parse_source(input).unwrap_err();
312        assert!(err.message.contains("expected RBrace"));
313    }
314
315    #[test]
316    fn error_missing_default_value() {
317        let input = r#"Feature Checkout {
318    Variable x Boolean =
319}"#;
320        let err = parse_source(input).unwrap_err();
321        assert!(err.message.contains("expected default value"));
322    }
323
324    #[test]
325    fn error_missing_type() {
326        let input = r#"Feature Checkout {
327    Variable x = true
328}"#;
329        let err = parse_source(input).unwrap_err();
330        assert!(err.message.contains("expected type"));
331    }
332
333    #[test]
334    fn error_unknown_type_keyword() {
335        let input = r#"Feature Checkout {
336    Variable x Integer = 5
337}"#;
338        let err = parse_source(input).unwrap_err();
339        assert!(err.message.contains("expected type"));
340    }
341
342    #[test]
343    fn error_spans_point_to_correct_location() {
344        let input = "Feature Checkout {\n    Variable x = true\n}";
345        let err = parse_source(input).unwrap_err();
346        // The "=" is the token where we expected a type
347        assert_eq!(err.span.line, 2);
348    }
349}