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 assert_eq!(err.span.line, 2);
348 }
349}