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 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}