1use std::collections::BTreeMap;
2
3use crate::ast::{Feature, StructDef, StructField, Value, VarFile, VarType, Variable};
4use crate::lexer::{Span, SpannedToken, Token};
5
6#[derive(Debug, Clone, PartialEq)]
7pub struct ParseError {
8 pub message: String,
9 pub span: Span,
10}
11
12struct Parser {
13 tokens: Vec<SpannedToken>,
14 pos: usize,
15}
16
17impl Parser {
18 fn new(tokens: Vec<SpannedToken>) -> Self {
19 Self { tokens, pos: 0 }
20 }
21
22 fn peek(&self) -> Option<&SpannedToken> {
23 self.tokens.get(self.pos)
24 }
25
26 fn advance(&mut self) -> Option<&SpannedToken> {
27 let token = self.tokens.get(self.pos);
28 if token.is_some() {
29 self.pos += 1;
30 }
31 token
32 }
33
34 fn expect(&mut self, expected: &Token) -> Result<&SpannedToken, ParseError> {
35 match self.peek() {
36 Some(t) if &t.token == expected => {
37 self.pos += 1;
38 Ok(&self.tokens[self.pos - 1])
39 }
40 Some(t) => Err(ParseError {
41 message: format!("expected {:?}, found {:?}", expected, t.token),
42 span: t.span.clone(),
43 }),
44 None => Err(ParseError {
45 message: format!("expected {:?}, found end of input", expected),
46 span: self.eof_span(),
47 }),
48 }
49 }
50
51 fn eof_span(&self) -> Span {
52 if let Some(last) = self.tokens.last() {
53 Span {
54 offset: last.span.offset + 1,
55 line: last.span.line,
56 column: last.span.column + 1,
57 }
58 } else {
59 Span {
60 offset: 0,
61 line: 1,
62 column: 1,
63 }
64 }
65 }
66
67 fn current_span(&self) -> Span {
68 match self.peek() {
69 Some(t) => t.span.clone(),
70 None => self.eof_span(),
71 }
72 }
73
74 fn parse_file(&mut self) -> Result<VarFile, ParseError> {
75 let mut structs = Vec::new();
76 let mut features = Vec::new();
77 while self.peek().is_some() {
78 let keyword = self.peek_declaration_keyword()?;
80 match keyword {
81 Token::Struct => structs.push(self.parse_struct_def()?),
82 Token::Feature => features.push(self.parse_feature()?),
83 _ => {
84 return Err(ParseError {
85 message: format!("expected Feature or Struct keyword, found {:?}", keyword),
86 span: self.current_span(),
87 });
88 }
89 }
90 }
91 Ok(VarFile { structs, features })
92 }
93
94 fn peek_declaration_keyword(&self) -> Result<Token, ParseError> {
97 let keyword_pos = self.pos + 2;
101 match self.tokens.get(keyword_pos) {
102 Some(t) => Ok(t.token.clone()),
103 None => Err(ParseError {
104 message: "expected Feature or Struct declaration, found end of input".to_string(),
105 span: self.eof_span(),
106 }),
107 }
108 }
109
110 fn parse_scoped_id(&mut self, scope: &str) -> Result<u32, ParseError> {
111 let (raw_id, span) = match self.advance() {
112 Some(SpannedToken {
113 token: Token::NumberLit(n),
114 span,
115 }) => (*n, span.clone()),
116 Some(t) => {
117 return Err(ParseError {
118 message: format!("expected {} id, found {:?}", scope, t.token),
119 span: t.span.clone(),
120 });
121 }
122 None => {
123 return Err(ParseError {
124 message: format!("expected {} id, found end of input", scope),
125 span: self.eof_span(),
126 });
127 }
128 };
129
130 if raw_id.fract() != 0.0 || raw_id < 0.0 || raw_id > u32::MAX as f64 {
131 return Err(ParseError {
132 message: format!("expected {} id to be a u32, found {}", scope, raw_id),
133 span,
134 });
135 }
136
137 self.expect(&Token::Colon)?;
138
139 Ok(raw_id as u32)
140 }
141
142 fn parse_struct_def(&mut self) -> Result<StructDef, ParseError> {
143 let span = self.current_span();
144 let id = self.parse_scoped_id("struct")?;
145 self.expect(&Token::Struct)?;
146
147 let name = match self.advance() {
148 Some(SpannedToken {
149 token: Token::Ident(name),
150 ..
151 }) => name.clone(),
152 Some(t) => {
153 return Err(ParseError {
154 message: format!("expected struct name, found {:?}", t.token),
155 span: t.span.clone(),
156 });
157 }
158 None => {
159 return Err(ParseError {
160 message: "expected struct name, found end of input".to_string(),
161 span: self.eof_span(),
162 });
163 }
164 };
165
166 self.expect(&Token::Equals)?;
167 self.expect(&Token::LBrace)?;
168
169 let mut fields = Vec::new();
170 while self.peek().is_some_and(|t| t.token != Token::RBrace) {
171 fields.push(self.parse_struct_field()?);
172 }
173
174 self.expect(&Token::RBrace)?;
175
176 Ok(StructDef {
177 id,
178 name,
179 fields,
180 span,
181 })
182 }
183
184 fn parse_struct_field(&mut self) -> Result<StructField, ParseError> {
185 let span = self.current_span();
186 let id = self.parse_scoped_id("field")?;
187 let name = match self.advance() {
188 Some(SpannedToken {
189 token: Token::Ident(name),
190 ..
191 }) => name.clone(),
192 Some(t) => {
193 return Err(ParseError {
194 message: format!("expected field name, found {:?}", t.token),
195 span: t.span.clone(),
196 });
197 }
198 None => {
199 return Err(ParseError {
200 message: "expected field name, found end of input".to_string(),
201 span: self.eof_span(),
202 });
203 }
204 };
205
206 let field_type = match self.advance() {
208 Some(SpannedToken {
209 token: Token::BooleanType,
210 ..
211 }) => VarType::Boolean,
212 Some(SpannedToken {
213 token: Token::IntegerType,
214 ..
215 }) => VarType::Integer,
216 Some(SpannedToken {
217 token: Token::FloatType,
218 ..
219 }) => VarType::Float,
220 Some(SpannedToken {
221 token: Token::StringType,
222 ..
223 }) => VarType::String,
224 Some(t) => {
225 return Err(ParseError {
226 message: format!(
227 "expected field type (Boolean, Integer, Float, or String), found {:?}",
228 t.token
229 ),
230 span: t.span.clone(),
231 });
232 }
233 None => {
234 return Err(ParseError {
235 message: "expected field type, found end of input".to_string(),
236 span: self.eof_span(),
237 });
238 }
239 };
240
241 self.expect(&Token::Equals)?;
242
243 let default = self.parse_primitive_value(&field_type)?;
244
245 Ok(StructField {
246 id,
247 name,
248 field_type,
249 default,
250 span,
251 })
252 }
253
254 fn parse_primitive_value(&mut self, var_type: &VarType) -> Result<Value, ParseError> {
256 match self.advance() {
257 Some(SpannedToken {
258 token: Token::BoolLit(b),
259 ..
260 }) => Ok(Value::Boolean(*b)),
261 Some(SpannedToken {
262 token: Token::NumberLit(n),
263 span,
264 }) => {
265 let n = *n;
266 let span = span.clone();
267 match var_type {
268 VarType::Integer => {
269 if n.fract() != 0.0 {
270 return Err(ParseError {
271 message: format!(
272 "Integer field cannot have fractional default value `{}`",
273 n
274 ),
275 span,
276 });
277 }
278 Ok(Value::Integer(n as i64))
279 }
280 _ => Ok(Value::Float(n)),
281 }
282 }
283 Some(SpannedToken {
284 token: Token::StringLit(s),
285 ..
286 }) => Ok(Value::String(s.clone())),
287 Some(t) => Err(ParseError {
288 message: format!("expected default value, found {:?}", t.token),
289 span: t.span.clone(),
290 }),
291 None => Err(ParseError {
292 message: "expected default value, found end of input".to_string(),
293 span: self.eof_span(),
294 }),
295 }
296 }
297
298 fn parse_struct_literal(&mut self, struct_name: &str) -> Result<Value, ParseError> {
300 match self.advance() {
302 Some(SpannedToken {
303 token: Token::Ident(name),
304 span,
305 }) => {
306 if name != struct_name {
307 return Err(ParseError {
308 message: format!(
309 "expected struct literal `{}`, found `{}`",
310 struct_name, name
311 ),
312 span: span.clone(),
313 });
314 }
315 }
316 Some(t) => {
317 return Err(ParseError {
318 message: format!(
319 "expected struct literal `{}`, found {:?}",
320 struct_name, t.token
321 ),
322 span: t.span.clone(),
323 });
324 }
325 None => {
326 return Err(ParseError {
327 message: format!(
328 "expected struct literal `{}`, found end of input",
329 struct_name
330 ),
331 span: self.eof_span(),
332 });
333 }
334 }
335
336 self.expect(&Token::LBrace)?;
337
338 let mut fields = BTreeMap::new();
339 while self.peek().is_some_and(|t| t.token != Token::RBrace) {
340 let field_name = match self.advance() {
342 Some(SpannedToken {
343 token: Token::Ident(name),
344 ..
345 }) => name.clone(),
346 Some(t) => {
347 return Err(ParseError {
348 message: format!("expected field name, found {:?}", t.token),
349 span: t.span.clone(),
350 });
351 }
352 None => {
353 return Err(ParseError {
354 message: "expected field name, found end of input".to_string(),
355 span: self.eof_span(),
356 });
357 }
358 };
359
360 self.expect(&Token::Equals)?;
361
362 let value = match self.advance() {
365 Some(SpannedToken {
366 token: Token::BoolLit(b),
367 ..
368 }) => Value::Boolean(*b),
369 Some(SpannedToken {
370 token: Token::NumberLit(n),
371 ..
372 }) => {
373 let n = *n;
374 if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
376 Value::Integer(n as i64)
377 } else {
378 Value::Float(n)
379 }
380 }
381 Some(SpannedToken {
382 token: Token::StringLit(s),
383 ..
384 }) => Value::String(s.clone()),
385 Some(t) => {
386 return Err(ParseError {
387 message: format!(
388 "expected field value in struct literal, found {:?}",
389 t.token
390 ),
391 span: t.span.clone(),
392 });
393 }
394 None => {
395 return Err(ParseError {
396 message: "expected field value, found end of input".to_string(),
397 span: self.eof_span(),
398 });
399 }
400 };
401
402 fields.insert(field_name, value);
403 }
404
405 self.expect(&Token::RBrace)?;
406
407 Ok(Value::Struct {
408 struct_name: struct_name.to_string(),
409 fields,
410 })
411 }
412
413 fn parse_feature(&mut self) -> Result<Feature, ParseError> {
414 let span = self.current_span();
415 let id = self.parse_scoped_id("feature")?;
416 self.expect(&Token::Feature)?;
417
418 let name = match self.advance() {
419 Some(SpannedToken {
420 token: Token::Ident(name),
421 ..
422 }) => name.clone(),
423 Some(t) => {
424 return Err(ParseError {
425 message: format!("expected feature name, found {:?}", t.token),
426 span: t.span.clone(),
427 });
428 }
429 None => {
430 return Err(ParseError {
431 message: "expected feature name, found end of input".to_string(),
432 span: self.eof_span(),
433 });
434 }
435 };
436
437 self.expect(&Token::Equals)?;
438 self.expect(&Token::LBrace)?;
439
440 let mut variables = Vec::new();
441 while self.peek().is_some_and(|t| t.token != Token::RBrace) {
442 variables.push(self.parse_variable()?);
443 }
444
445 self.expect(&Token::RBrace)?;
446
447 Ok(Feature {
448 id,
449 name,
450 variables,
451 span,
452 })
453 }
454
455 fn parse_variable(&mut self) -> Result<Variable, ParseError> {
456 let span = self.current_span();
457 let id = self.parse_scoped_id("variable")?;
458 let name = match self.advance() {
459 Some(SpannedToken {
460 token: Token::Ident(name),
461 ..
462 }) => name.clone(),
463 Some(t) => {
464 return Err(ParseError {
465 message: format!("expected variable name, found {:?}", t.token),
466 span: t.span.clone(),
467 });
468 }
469 None => {
470 return Err(ParseError {
471 message: "expected variable name, found end of input".to_string(),
472 span: self.eof_span(),
473 });
474 }
475 };
476
477 let var_type = match self.advance() {
478 Some(SpannedToken {
479 token: Token::BooleanType,
480 ..
481 }) => VarType::Boolean,
482 Some(SpannedToken {
483 token: Token::IntegerType,
484 ..
485 }) => VarType::Integer,
486 Some(SpannedToken {
487 token: Token::FloatType,
488 ..
489 }) => VarType::Float,
490 Some(SpannedToken {
491 token: Token::StringType,
492 ..
493 }) => VarType::String,
494 Some(SpannedToken {
495 token: Token::Ident(name),
496 ..
497 }) => VarType::Struct(name.clone()),
498 Some(t) => {
499 return Err(ParseError {
500 message: format!(
501 "expected type (Boolean, Integer, Float, String, or struct name), found {:?}",
502 t.token
503 ),
504 span: t.span.clone(),
505 });
506 }
507 None => {
508 return Err(ParseError {
509 message: "expected type, found end of input".to_string(),
510 span: self.eof_span(),
511 });
512 }
513 };
514
515 self.expect(&Token::Equals)?;
516
517 let default = if let VarType::Struct(ref struct_name) = var_type {
518 self.parse_struct_literal(struct_name)?
519 } else {
520 self.parse_primitive_value(&var_type)?
521 };
522
523 Ok(Variable {
524 id,
525 name,
526 var_type,
527 default,
528 span,
529 })
530 }
531}
532
533pub fn parse(tokens: Vec<SpannedToken>) -> Result<VarFile, ParseError> {
534 let mut parser = Parser::new(tokens);
535 parser.parse_file()
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541 use crate::lexer::lex;
542
543 fn parse_source(input: &str) -> Result<VarFile, ParseError> {
544 let tokens = lex(input).map_err(|e| ParseError {
545 message: e.message,
546 span: e.span,
547 })?;
548 parse(tokens)
549 }
550
551 #[test]
552 fn parse_single_boolean_variable() {
553 let input = r#"1: Feature Flags = {
554 1: enabled Boolean = true
555}"#;
556 let file = parse_source(input).unwrap();
557 assert_eq!(file.features.len(), 1);
558 assert_eq!(file.features[0].id, 1);
559 assert_eq!(file.features[0].name, "Flags");
560 assert_eq!(file.features[0].variables.len(), 1);
561 assert_eq!(file.features[0].variables[0].id, 1);
562 assert_eq!(file.features[0].variables[0].name, "enabled");
563 assert_eq!(file.features[0].variables[0].var_type, VarType::Boolean);
564 assert_eq!(file.features[0].variables[0].default, Value::Boolean(true));
565 }
566
567 #[test]
568 fn parse_single_integer_variable() {
569 let input = r#"1: Feature Config = {
570 1: max_items Integer = 50
571}"#;
572 let file = parse_source(input).unwrap();
573 assert_eq!(file.features[0].variables[0].var_type, VarType::Integer);
574 assert_eq!(file.features[0].variables[0].default, Value::Integer(50));
575 }
576
577 #[test]
578 fn parse_single_float_variable() {
579 let input = r#"1: Feature Config = {
580 1: ratio Float = 3.14
581}"#;
582 let file = parse_source(input).unwrap();
583 assert_eq!(file.features[0].variables[0].var_type, VarType::Float);
584 assert_eq!(file.features[0].variables[0].default, Value::Float(3.14));
585 }
586
587 #[test]
588 fn parse_float_variable_with_integer_literal() {
589 let input = r#"1: Feature Config = {
590 1: max_items Float = 50
591}"#;
592 let file = parse_source(input).unwrap();
593 assert_eq!(file.features[0].variables[0].var_type, VarType::Float);
594 assert_eq!(file.features[0].variables[0].default, Value::Float(50.0));
595 }
596
597 #[test]
598 fn parse_single_string_variable() {
599 let input = r#"1: Feature Config = {
600 1: title String = "Hello"
601}"#;
602 let file = parse_source(input).unwrap();
603 assert_eq!(file.features[0].variables[0].var_type, VarType::String);
604 assert_eq!(
605 file.features[0].variables[0].default,
606 Value::String("Hello".to_string())
607 );
608 }
609
610 #[test]
611 fn parse_multiple_features() {
612 let input = r#"1: Feature A = {
613 1: x Boolean = true
614}
615
6162: Feature B = {
617 1: y Integer = 42
618}"#;
619 let file = parse_source(input).unwrap();
620 assert_eq!(file.features.len(), 2);
621 assert_eq!(file.features[0].id, 1);
622 assert_eq!(file.features[0].name, "A");
623 assert_eq!(file.features[1].id, 2);
624 assert_eq!(file.features[1].name, "B");
625 }
626
627 #[test]
628 fn parse_example_var_file() {
629 let input = r#"1: Feature Checkout = {
630 1: enabled Boolean = true
631 2: max_items Integer = 50
632 3: header_text String = "Complete your purchase"
633}"#;
634 let file = parse_source(input).unwrap();
635 assert_eq!(file.features.len(), 1);
636 let feature = &file.features[0];
637 assert_eq!(feature.id, 1);
638 assert_eq!(feature.name, "Checkout");
639 assert_eq!(feature.variables.len(), 3);
640 assert_eq!(feature.variables[0].id, 1);
641 assert_eq!(feature.variables[0].name, "enabled");
642 assert_eq!(feature.variables[1].id, 2);
643 assert_eq!(feature.variables[1].name, "max_items");
644 assert_eq!(feature.variables[2].id, 3);
645 assert_eq!(feature.variables[2].name, "header_text");
646 }
647
648 #[test]
649 fn error_missing_lbrace() {
650 let input = "1: Feature Checkout = 1: x Boolean = true }";
651 let err = parse_source(input).unwrap_err();
652 assert!(err.message.contains("expected LBrace"));
653 }
654
655 #[test]
656 fn error_missing_rbrace() {
657 let input = r#"1: Feature Checkout = {
658 1: x Boolean = true"#;
659 let err = parse_source(input).unwrap_err();
660 assert!(err.message.contains("expected RBrace"));
661 }
662
663 #[test]
664 fn error_missing_default_value() {
665 let input = r#"1: Feature Checkout = {
666 1: x Boolean =
667}"#;
668 let err = parse_source(input).unwrap_err();
669 assert!(err.message.contains("expected default value"));
670 }
671
672 #[test]
673 fn error_missing_type() {
674 let input = r#"1: Feature Checkout = {
675 1: x = true
676}"#;
677 let err = parse_source(input).unwrap_err();
678 assert!(err.message.contains("expected type"));
679 }
680
681 #[test]
682 fn error_unknown_type_keyword() {
683 let input = r#"1: Feature Checkout = {
687 1: x Map = 5
688}"#;
689 let err = parse_source(input).unwrap_err();
690 assert!(err.message.contains("expected struct literal"));
691 }
692
693 #[test]
694 fn error_integer_with_fractional_value() {
695 let input = r#"1: Feature Config = {
696 1: ratio Integer = 3.14
697}"#;
698 let err = parse_source(input).unwrap_err();
699 assert!(
700 err.message
701 .contains("Integer field cannot have fractional default value")
702 );
703 }
704
705 #[test]
706 fn error_spans_point_to_correct_location() {
707 let input = "1: Feature Checkout = {\n 1: x = true\n}";
708 let err = parse_source(input).unwrap_err();
709 assert_eq!(err.span.line, 2);
711 }
712
713 #[test]
714 fn parse_struct_definition() {
715 let input = r##"1: Struct Theme = {
716 1: dark_mode Boolean = false
717 2: font_size Integer = 14
718 3: primary_color String = "#000000"
719}"##;
720 let file = parse_source(input).unwrap();
721 assert_eq!(file.structs.len(), 1);
722 assert_eq!(file.features.len(), 0);
723 let s = &file.structs[0];
724 assert_eq!(s.id, 1);
725 assert_eq!(s.name, "Theme");
726 assert_eq!(s.fields.len(), 3);
727 assert_eq!(s.fields[0].name, "dark_mode");
728 assert_eq!(s.fields[0].field_type, VarType::Boolean);
729 assert_eq!(s.fields[0].default, Value::Boolean(false));
730 assert_eq!(s.fields[1].name, "font_size");
731 assert_eq!(s.fields[1].field_type, VarType::Integer);
732 assert_eq!(s.fields[1].default, Value::Integer(14));
733 assert_eq!(s.fields[2].name, "primary_color");
734 assert_eq!(s.fields[2].field_type, VarType::String);
735 assert_eq!(s.fields[2].default, Value::String("#000000".to_string()));
736 }
737
738 #[test]
739 fn parse_struct_and_feature_together() {
740 let input = r#"1: Struct Theme = {
741 1: dark_mode Boolean = false
742}
743
7441: Feature Dashboard = {
745 1: enabled Boolean = true
746 2: theme Theme = Theme {}
747}"#;
748 let file = parse_source(input).unwrap();
749 assert_eq!(file.structs.len(), 1);
750 assert_eq!(file.features.len(), 1);
751 let var = &file.features[0].variables[1];
752 assert_eq!(var.name, "theme");
753 assert_eq!(var.var_type, VarType::Struct("Theme".to_string()));
754 match &var.default {
755 Value::Struct {
756 struct_name,
757 fields,
758 } => {
759 assert_eq!(struct_name, "Theme");
760 assert!(fields.is_empty());
761 }
762 other => panic!("expected Struct value, got {:?}", other),
763 }
764 }
765
766 #[test]
767 fn parse_struct_literal_with_field_overrides() {
768 let input = r#"1: Struct Config = {
769 1: retries Integer = 3
770 2: verbose Boolean = false
771}
772
7731: Feature App = {
774 1: config Config = Config { retries = 5 verbose = true }
775}"#;
776 let file = parse_source(input).unwrap();
777 let var = &file.features[0].variables[0];
778 match &var.default {
779 Value::Struct {
780 struct_name,
781 fields,
782 } => {
783 assert_eq!(struct_name, "Config");
784 assert_eq!(fields.len(), 2);
785 assert_eq!(fields["retries"], Value::Integer(5));
786 assert_eq!(fields["verbose"], Value::Boolean(true));
787 }
788 other => panic!("expected Struct value, got {:?}", other),
789 }
790 }
791
792 #[test]
793 fn error_missing_feature_id() {
794 let input = r#"Feature Checkout = {
795 1: enabled Boolean = true
796}"#;
797 let err = parse_source(input).unwrap_err();
798 assert!(
800 err.message.contains("expected feature id")
801 || err.message.contains("expected Feature or Struct")
802 );
803 }
804
805 #[test]
806 fn error_missing_variable_id() {
807 let input = r#"1: Feature Checkout = {
808 enabled Boolean = true
809}"#;
810 let err = parse_source(input).unwrap_err();
811 assert!(err.message.contains("expected variable id"));
812 }
813
814 #[test]
815 fn error_non_integer_feature_id() {
816 let input = r#"1.5: Feature Checkout = {
817 1: enabled Boolean = true
818}"#;
819 let err = parse_source(input).unwrap_err();
820 assert!(err.message.contains("expected feature id to be a u32"));
821 }
822}