1use crate::error::{AamlError, ErrorDiagnostics};
7use crate::pipeline::lexer::{Token, TokenKind};
8use crate::pipeline::tasks::{ExecutionTask, ParseTask};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum ValueNode<'a> {
13 Literal(std::borrow::Cow<'a, str>),
14 Object(std::sync::Arc<[(std::borrow::Cow<'a, str>, Self)]>),
15 List(std::sync::Arc<[Self]>),
16}
17
18impl std::fmt::Display for ValueNode<'_> {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 ValueNode::Literal(s) => write!(f, "{s}"),
22 ValueNode::Object(pairs) => {
23 let formatted_pairs: Vec<String> =
24 pairs.iter().map(|(k, v)| format!("{k} = {v}")).collect();
25 write!(f, "{{ {} }}", formatted_pairs.join(", "))
26 }
27 ValueNode::List(items) => {
28 let formatted_items: Vec<String> = items.iter().map(ToString::to_string).collect();
29 write!(f, "[{}]", formatted_items.join(", "))
30 }
31 }
32 }
33}
34
35#[derive(Debug, Clone)]
37pub enum AstNode<'a> {
38 Assignment {
40 key: std::borrow::Cow<'a, str>,
41 value: ValueNode<'a>,
42 line: usize,
43 },
44 Directive {
46 name: std::borrow::Cow<'a, str>,
47 args: std::borrow::Cow<'a, str>,
48 body: Option<ValueNode<'a>>,
49 line: usize,
50 },
51}
52
53impl AstNode<'_> {
54 #[must_use]
56 pub const fn line(&self) -> usize {
57 match self {
58 AstNode::Assignment { line, .. } | AstNode::Directive { line, .. } => *line,
59 }
60 }
61}
62
63pub trait Parser: Send + Sync {
65 fn parse<'a>(&self, tokens: &[Token<'a>]) -> Result<Vec<AstNode<'a>>, AamlError>;
70
71 fn parse_with_recovery<'a>(&self, tokens: &[Token<'a>]) -> ParseOutput<'a>;
73
74 fn generate_parse_tasks<'a>(&self, ast: &[AstNode<'a>]) -> Vec<ParseTask<'a>>;
76
77 fn generate_execution_tasks<'a>(&self, ast: &[AstNode<'a>]) -> Vec<ExecutionTask<'a>>;
79}
80
81pub struct ParseOutput<'a> {
83 pub ast: Vec<AstNode<'a>>,
84 pub errors: Vec<AamlError>,
85}
86
87pub struct DefaultParser;
89
90impl DefaultParser {
91 #[must_use]
92 pub const fn new() -> Self {
93 Self
94 }
95
96 fn filter_tokens<'a, 'b>(tokens: &'b [Token<'a>]) -> Vec<&'b Token<'a>> {
98 use crate::pipeline::lexer::TokenKind;
99 tokens
100 .iter()
101 .filter(|t| t.kind != TokenKind::Comment)
102 .collect()
103 }
104
105 fn parse_assignment<'a>(
107 tokens: &[&Token<'a>],
108 start: usize,
109 ) -> Result<(std::borrow::Cow<'a, str>, ValueNode<'a>, usize), AamlError> {
110 use crate::pipeline::lexer::TokenKind;
111
112 if start >= tokens.len() {
113 return Err(AamlError::ParseError {
114 line: tokens.get(start).map_or(1, |t| t.line),
115 content: "incomplete assignment".to_string(),
116 details: "Expected: key = value".to_string(),
117 diagnostics: Some(Box::new(ErrorDiagnostics::new(
118 "Incomplete assignment",
119 "Assignment must have at least 3 tokens: key, =, value".to_string(),
120 "Check format: key = value".to_string(),
121 ))),
122 });
123 }
124
125 if tokens[start].kind != TokenKind::Identifier {
126 return Err(AamlError::ParseError {
127 line: tokens[start].line,
128 content: format!("Expected identifier, got {:?}", tokens[start].kind),
129 details: "First token of assignment must be an identifier".to_string(),
130 diagnostics: None,
131 });
132 }
133
134 let mut assign_pos = start + 1;
135 while assign_pos < tokens.len()
136 && tokens[assign_pos].kind != TokenKind::Assign
137 && tokens[assign_pos].kind != TokenKind::Newline
138 {
139 assign_pos += 1;
140 }
141
142 if assign_pos >= tokens.len() || tokens[assign_pos].kind != TokenKind::Assign {
143 let got = tokens.get(start + 1).map_or("<eof>", |t| t.text.as_ref());
144 return Err(AamlError::ParseError {
145 line: tokens.get(start + 1).map_or(tokens[start].line, |t| t.line),
146 content: format!("Expected '=', got '{got}'"),
147 details: "Assignment operator '=' expected after key".to_string(),
148 diagnostics: None,
149 });
150 }
151
152 let key = if assign_pos == start + 1 {
153 tokens[start].text.clone()
154 } else {
155 let key_text = tokens[start..assign_pos]
156 .iter()
157 .map(|t| t.text.as_ref())
158 .collect::<Vec<_>>()
159 .join(" ");
160 key_text.into()
161 };
162
163 let (value, consumed) = Self::parse_value(tokens, assign_pos + 1)?;
164 Ok((key, value, consumed))
165 }
166
167 fn parse_braced_literal<'a, 'b>(
168 tokens: &'b [&'b Token<'a>],
169 start: usize,
170 ) -> Result<(ValueNode<'a>, usize), AamlError> {
171 use crate::pipeline::lexer::TokenKind;
172
173 let mut depth = 0_i32;
174 let mut pos = start;
175
176 while pos < tokens.len() {
177 match tokens[pos].kind {
178 TokenKind::LeftBrace => depth += 1,
179 TokenKind::RightBrace => {
180 depth -= 1;
181 if depth == 0 {
182 let first = &tokens[start];
183 let last = &tokens[pos];
184 let text = &first.source[first.start..last.end];
185 return Ok((ValueNode::Literal(text.into()), pos + 1));
186 }
187 }
188 TokenKind::Newline if depth == 0 => break,
189 _ => {}
190 }
191 pos += 1;
192 }
193
194 Err(AamlError::ParseError {
195 line: tokens[start].line,
196 content: "unclosed brace".to_string(),
197 details: "Expected '}' to close inline object".to_string(),
198 diagnostics: None,
199 })
200 }
201
202 fn parse_value<'a, 'b>(
204 tokens: &'b [&'b Token<'a>],
205 start: usize,
206 ) -> Result<(ValueNode<'a>, usize), AamlError> {
207 use crate::pipeline::lexer::TokenKind;
208
209 if start >= tokens.len() {
210 return Err(AamlError::ParseError {
211 line: tokens.len(),
212 content: "unexpected end of input".to_string(),
213 details: "Expected a value after '='".to_string(),
214 diagnostics: None,
215 });
216 }
217
218 match &tokens[start].kind {
219 TokenKind::LeftBrace => {
220 match Self::parse_inline_object(tokens, start) {
222 Ok((obj, consumed)) => Ok((obj, consumed)),
223 Err(_) => Self::parse_braced_literal(tokens, start),
224 }
225 }
226 TokenKind::LeftBracket => {
227 let (list, consumed) = Self::parse_inline_list(tokens, start)?;
229 Ok((list, consumed))
230 }
231 _ => {
232 let end_pos = tokens[start..]
233 .iter()
234 .position(|t| {
235 matches!(
236 t.kind,
237 TokenKind::Newline
238 | TokenKind::RightBrace
239 | TokenKind::RightBracket
240 | TokenKind::Comma
241 )
242 })
243 .map_or(tokens.len(), |p| start + p);
244
245 if end_pos == start {
246 return Err(AamlError::ParseError {
247 line: tokens[start].line,
248 content: "invalid value".to_string(),
249 details: "Could not parse literal value".to_string(),
250 diagnostics: None,
251 });
252 }
253
254 let first = &tokens[start];
257 let last = &tokens[end_pos - 1];
258 let exact = &first.source[first.start..last.end];
259
260 Ok((ValueNode::Literal(exact.into()), end_pos))
261 }
262 }
263 }
264
265 fn parse_inline_object<'a, 'b>(
267 tokens: &'b [&'b Token<'a>],
268 start: usize,
269 ) -> Result<(ValueNode<'a>, usize), AamlError> {
270 use crate::pipeline::lexer::TokenKind;
271
272 let mut pairs = Vec::new();
273 let mut pos = start + 1;
274 let mut expect_field = true;
275 let mut has_any_field = false;
276
277 while pos < tokens.len() {
278 match tokens[pos].kind {
279 TokenKind::RightBrace if expect_field && has_any_field => {
280 return Err(AamlError::ParseError {
281 line: tokens[pos].line,
282 content: "trailing comma in inline object".to_string(),
283 details: "Expected another field after ',' or remove trailing comma"
284 .to_string(),
285 diagnostics: None,
286 });
287 }
288 TokenKind::RightBrace => return Ok((ValueNode::Object(pairs.into()), pos + 1)),
289 TokenKind::Identifier if expect_field => {
290 pos = Self::parse_inline_object_pair(tokens, pos, &mut pairs)?;
291 expect_field = false;
292 has_any_field = true;
293 }
294 TokenKind::Identifier => {
295 return Err(AamlError::ParseError {
296 line: tokens[pos].line,
297 content: "missing comma in inline object".to_string(),
298 details: "Expected ',' between object fields".to_string(),
299 diagnostics: None,
300 });
301 }
302 TokenKind::Comma if expect_field => {
303 return Err(AamlError::ParseError {
304 line: tokens[pos].line,
305 content: "unexpected comma in inline object".to_string(),
306 details: "Expected an object field before ','".to_string(),
307 diagnostics: None,
308 });
309 }
310 TokenKind::Comma => {
311 expect_field = true;
312 pos += 1;
313 }
314 _ => {
315 return Err(AamlError::ParseError {
316 line: tokens[pos].line,
317 content: "invalid inline object format".to_string(),
318 details: "Expected identifier or closing brace".to_string(),
319 diagnostics: None,
320 });
321 }
322 }
323 }
324
325 Err(AamlError::ParseError {
326 line: tokens[start].line,
327 content: "unclosed brace".to_string(),
328 details: "Expected '}' to close inline object".to_string(),
329 diagnostics: None,
330 })
331 }
332
333 fn parse_inline_object_pair<'a, 'b>(
334 tokens: &'b [&'b Token<'a>],
335 pos: usize,
336 pairs: &mut Vec<(std::borrow::Cow<'a, str>, ValueNode<'a>)>,
337 ) -> Result<usize, AamlError> {
338 use crate::pipeline::lexer::TokenKind;
339 let key: std::borrow::Cow<'a, str> = tokens[pos].text.clone();
340 if pos + 1 >= tokens.len() || tokens[pos + 1].kind != TokenKind::Assign {
341 return Err(AamlError::ParseError {
342 line: tokens[pos].line,
343 content: "invalid inline object format".to_string(),
344 details: "Expected '=' after key".to_string(),
345 diagnostics: None,
346 });
347 }
348
349 let value_pos = pos + 2;
350 let (value, next_pos) = if value_pos < tokens.len()
351 && matches!(
352 tokens[value_pos].kind,
353 TokenKind::RightBrace | TokenKind::Comma
354 ) {
355 (ValueNode::Literal("".into()), value_pos)
356 } else {
357 Self::parse_value(tokens, value_pos)?
358 };
359 pairs.push((key, value));
360 Ok(next_pos)
361 }
362
363 fn parse_inline_list<'a, 'b>(
365 tokens: &'b [&'b Token<'a>],
366 start: usize,
367 ) -> Result<(ValueNode<'a>, usize), AamlError> {
368 use crate::pipeline::lexer::TokenKind;
369
370 let mut items = Vec::new();
371 let mut pos = start + 1;
372
373 while pos < tokens.len() {
374 if tokens[pos].kind == TokenKind::RightBracket {
375 return Ok((ValueNode::List(items.into()), pos + 1));
376 }
377
378 if tokens[pos].kind == TokenKind::Comma {
379 pos += 1;
380 continue;
381 }
382
383 let (value, next_pos) = Self::parse_value(tokens, pos)?;
384 items.push(value);
385 pos = next_pos;
386 }
387
388 Err(AamlError::ParseError {
389 line: tokens[start].line,
390 content: "unclosed bracket".to_string(),
391 details: "Expected ']' to close inline list".to_string(),
392 diagnostics: None,
393 })
394 }
395
396 fn missing_directive_name_error(line: usize) -> AamlError {
397 AamlError::ParseError {
398 line,
399 content: "@".to_string(),
400 details: "Directive name expected after '@'".to_string(),
401 diagnostics: Some(Box::new(ErrorDiagnostics::new(
402 "Missing directive name",
403 "Directive requires a name after '@'".to_string(),
404 "Use format: @directive_name arguments".to_string(),
405 ))),
406 }
407 }
408
409 fn should_stop_directive_args(kind: &TokenKind, brace_count: i32, bracket_count: i32) -> bool {
410 brace_count == 0
411 && bracket_count == 0
412 && (*kind == TokenKind::At || *kind == TokenKind::Newline)
413 }
414
415 fn collect_directive_args(tokens_filtered: &[&Token<'_>], start_pos: usize) -> (String, usize) {
416 let mut args = String::new();
417 let mut arg_pos = start_pos;
418 let mut brace_count = 0;
419 let mut bracket_count = 0;
420
421 while arg_pos < tokens_filtered.len() {
422 let tk = tokens_filtered[arg_pos];
423 match tk.kind {
424 TokenKind::LeftBrace => brace_count += 1,
425 TokenKind::RightBrace => brace_count -= 1,
426 TokenKind::LeftBracket => bracket_count += 1,
427 TokenKind::RightBracket => bracket_count -= 1,
428 _ => {}
429 }
430
431 if Self::should_stop_directive_args(&tk.kind, brace_count, bracket_count) {
432 break;
433 }
434
435 if !args.is_empty() {
436 args.push(' ');
437 }
438 args.push_str(&tk.text);
439 arg_pos += 1;
440 }
441
442 (args, arg_pos)
443 }
444
445 fn validate_schema_directive_args(line: usize, trimmed_args: &str) -> Result<(), AamlError> {
446 let (name_part, body_opt) = trimmed_args.split_once('{').unwrap_or((trimmed_args, ""));
447 if name_part.trim().is_empty() {
448 return Err(AamlError::ParseError {
449 line,
450 content: "@schema".to_string(),
451 details: "Schema name is missing".to_string(),
452 diagnostics: None,
453 });
454 }
455
456 if !trimmed_args.contains('{') || !trimmed_args.ends_with('}') {
457 return Err(AamlError::ParseError {
458 line,
459 content: "@schema".to_string(),
460 details: "Schema block must end with '}'".to_string(),
461 diagnostics: None,
462 });
463 }
464
465 let body = body_opt.trim_end_matches('}').trim();
466 for field in body.split([',', '\n']) {
467 let field = field.trim();
468 if field.is_empty() {
469 continue;
470 }
471
472 if !field.contains(':') {
473 return Err(AamlError::ParseError {
474 line,
475 content: "invalid schema field".to_string(),
476 details: format!("Field '{field}' must be of the form 'name: type'"),
477 diagnostics: None,
478 });
479 }
480 }
481
482 Ok(())
483 }
484
485 fn parse_directive<'a>(
486 tokens_filtered: &[&Token<'a>],
487 pos: &mut usize,
488 line: usize,
489 ) -> Result<AstNode<'a>, AamlError> {
490 if *pos + 1 >= tokens_filtered.len() {
491 return Err(Self::missing_directive_name_error(line));
492 }
493
494 let dir_name: std::borrow::Cow<'a, str> = tokens_filtered[*pos + 1].text.clone();
495 let (args, arg_pos) = Self::collect_directive_args(tokens_filtered, *pos + 2);
496
497 *pos = arg_pos;
498
499 let trimmed_args = args.trim();
500 if &*dir_name == "schema" {
501 Self::validate_schema_directive_args(line, trimmed_args)?;
502 }
503
504 Ok(AstNode::Directive {
505 name: dir_name,
506 args: trimmed_args.to_string().into(),
507 body: None,
508 line,
509 })
510 }
511
512 fn synchronize(tokens_filtered: &[&Token<'_>], mut pos: usize) -> usize {
513 while pos < tokens_filtered.len() {
514 let kind = &tokens_filtered[pos].kind;
515 if *kind == TokenKind::Newline || *kind == TokenKind::RightBrace {
516 return pos + 1;
517 }
518 pos += 1;
519 }
520 pos
521 }
522
523 fn scope_from_key(key: &str) -> std::borrow::Cow<'static, str> {
524 if let Some((prefix, _)) = key.rsplit_once('.') {
525 return format!("root::{}", prefix.replace('.', "::")).into();
526 }
527 std::borrow::Cow::Borrowed("root")
528 }
529
530 fn emit_assignment_parse_tasks<'a>(
531 tasks: &mut Vec<ParseTask<'a>>,
532 key: &str,
533 value: &ValueNode<'a>,
534 line: usize,
535 ) {
536 tasks.push(ParseTask::ProcessVariable {
537 variable_name: key.to_string().into(),
538 value: value.to_string().into(),
539 scope: Self::scope_from_key(key).into_owned().into(),
540 line,
541 });
542
543 if let ValueNode::Object(pairs) = value {
544 for (field, child_value) in pairs.iter() {
545 let child_key = format!("{key}.{field}");
546 Self::emit_assignment_parse_tasks(tasks, &child_key, child_value, line);
547 }
548 }
549 }
550
551 fn build_type_task<'a>(args: &str, line: usize) -> ParseTask<'a> {
552 let (type_name, type_spec) = args
553 .split_once('=')
554 .map_or(("", ""), |(lhs, rhs)| (lhs.trim(), rhs.trim()));
555
556 ParseTask::RegisterType {
557 type_name: type_name.to_string().into(),
558 type_spec: type_spec.to_string().into(),
559 line,
560 }
561 }
562
563 fn build_schema_task<'a>(args: &str, line: usize) -> ParseTask<'a> {
564 let schema_name = args.split_whitespace().next().unwrap_or("").to_string();
565 let body = args
566 .split_once('{')
567 .and_then(|(_, b)| b.rsplit_once('}'))
568 .map_or("", |(b, _)| b);
569 let fields = body
570 .split([',', '\n'])
571 .filter_map(|field_def| {
572 let (field_name, type_name) = field_def.split_once(':')?;
573 let field_name = field_name.trim();
574 let type_name = type_name.trim();
575
576 if field_name.is_empty() {
577 return None;
578 }
579
580 Some(format!("{field_name}:{type_name}"))
581 })
582 .collect::<Vec<_>>()
583 .join(",");
584
585 ParseTask::RegisterSchema {
586 schema_name: schema_name.into(),
587 fields: fields.into(),
588 line,
589 }
590 }
591
592 fn build_directive_task<'a>(
593 name: &std::borrow::Cow<'a, str>,
594 args: &std::borrow::Cow<'a, str>,
595 line: usize,
596 ) -> ParseTask<'a> {
597 match &**name {
598 "type" => Self::build_type_task(args, line),
599 "schema" => Self::build_schema_task(args, line),
600 "derive" => ParseTask::ResolveDeriveImport {
601 derive_path: args.clone(),
602 line,
603 },
604 _ => ParseTask::ExecuteDirective {
605 directive_name: name.clone(),
606 arguments: args.clone(),
607 line,
608 },
609 }
610 }
611}
612
613impl Default for DefaultParser {
614 fn default() -> Self {
615 Self::new()
616 }
617}
618
619impl Parser for DefaultParser {
620 fn parse<'a>(&self, tokens: &[Token<'a>]) -> Result<Vec<AstNode<'a>>, AamlError> {
621 let result = self.parse_with_recovery(tokens);
622 if result.errors.is_empty() {
623 Ok(result.ast)
624 } else {
625 Err(result
626 .errors
627 .into_iter()
628 .next()
629 .unwrap_or_else(|| AamlError::ParseError {
630 line: 1,
631 content: "parse failed".to_string(),
632 details: "Unknown parser failure".to_string(),
633 diagnostics: None,
634 }))
635 }
636 }
637
638 fn parse_with_recovery<'a>(&self, tokens: &[Token<'a>]) -> ParseOutput<'a> {
639 use crate::pipeline::lexer::TokenKind;
640
641 let mut ast: Vec<AstNode<'a>> = Vec::new();
642 let mut errors = Vec::new();
643 let tokens_filtered = Self::filter_tokens(tokens);
644 let mut pos = 0;
645
646 while pos < tokens_filtered.len() {
647 let token = tokens_filtered[pos];
648
649 match &token.kind {
650 TokenKind::At => {
651 match Self::parse_directive(&tokens_filtered, &mut pos, token.line) {
652 Ok(dir_node) => ast.push(dir_node),
653 Err(err) => {
654 errors.push(err);
655 pos = Self::synchronize(&tokens_filtered, pos + 1);
656 }
657 }
658 }
659 TokenKind::Identifier => match Self::parse_assignment(&tokens_filtered, pos) {
660 Ok((key, value, new_pos)) => {
661 ast.push(AstNode::Assignment {
662 key,
663 value,
664 line: token.line,
665 });
666 pos = new_pos;
667 }
668 Err(err) => {
669 errors.push(err);
670 pos = Self::synchronize(&tokens_filtered, pos + 1);
671 }
672 },
673 _ => pos += 1,
674 }
675 }
676
677 ParseOutput { ast, errors }
678 }
679 fn generate_parse_tasks<'a>(&self, ast: &[AstNode<'a>]) -> Vec<ParseTask<'a>> {
680 let mut tasks = Vec::new();
681
682 for node in ast {
683 match node {
684 AstNode::Assignment { key, value, line } => {
685 Self::emit_assignment_parse_tasks(&mut tasks, key, value, *line);
686 }
687 AstNode::Directive {
688 name, args, line, ..
689 } => tasks.push(Self::build_directive_task(name, args, *line)),
690 }
691 }
692
693 tasks
694 }
695
696 fn generate_execution_tasks<'a>(&self, ast: &[AstNode<'a>]) -> Vec<ExecutionTask<'a>> {
697 let mut tasks = Vec::new();
698
699 for node in ast {
700 match node {
701 AstNode::Assignment { key, value, line } => {
702 tasks.push(ExecutionTask::SetValue {
703 key: key.clone(),
704 value: value.to_string().into(),
705 line: *line,
706 });
707 }
708 AstNode::Directive {
710 name, args, line, ..
711 } => {
712 if &**name == "import" {
713 tasks.push(ExecutionTask::ImportFile {
714 file_path: args.clone(),
715 merge_strategy: std::borrow::Cow::Borrowed("merge"),
716 line: *line,
717 });
718 } else if &**name == "derive" {
719 tasks.push(ExecutionTask::ExecuteInheritance {
720 derive_path: args.clone(),
721 child_key: std::borrow::Cow::Borrowed(""), line: *line,
723 });
724 }
725 }
726 }
727 }
728 tasks
729 }
730}
731
732#[cfg(test)]
733mod tests {
734 use super::*;
735 use crate::pipeline::lexer::{DefaultLexer, Lexer};
736
737 #[test]
738 fn test_parse_simple_assignment() {
739 let lexer = DefaultLexer::new();
740 let tokens = lexer.tokenize("key = value").unwrap();
741 let parser = DefaultParser::new();
742 let ast = parser.parse(&tokens).unwrap();
743
744 assert_eq!(ast.len(), 1);
745 match &ast[0] {
746 AstNode::Assignment { key, value, .. } => {
747 assert_eq!(&**key, "key");
748 if let ValueNode::Literal(s) = value {
749 assert_eq!(&**s, "value");
750 } else {
751 panic!("Expected ValueNode::Literal");
752 }
753 }
754 AstNode::Directive { .. } => panic!("Expected assignment"),
755 }
756 }
757
758 #[test]
759 fn test_parse_directive() {
760 let lexer = DefaultLexer::new();
761 let tokens = lexer.tokenize("@import base.aam").unwrap();
762 let parser = DefaultParser::new();
763 let ast = parser.parse(&tokens).unwrap();
764
765 assert_eq!(ast.len(), 1);
766 match &ast[0] {
767 AstNode::Directive { name, args: _, .. } => {
768 assert_eq!(&**name, "import");
769 }
770 AstNode::Assignment { .. } => panic!("Expected directive"),
771 }
772 }
773
774 #[test]
775 fn test_parse_multiple_assignments() {
776 let lexer = DefaultLexer::new();
777 let tokens = lexer.tokenize("a = b\nc = d").unwrap();
778 let parser = DefaultParser::new();
779 let ast = parser.parse(&tokens).unwrap();
780
781 assert_eq!(ast.len(), 2);
782 }
783}