amarok_parser/
lib.rs

1//! Amarok parser: source text → AST.
2//!
3//! Errors are reported via [`amarok_syntax::Diagnostic`], shared with the
4//! interpreter so a single rendering routine handles both phases.
5//!
6//! Internals:
7//! - [`grammar`] — pest-derived `AmarokGrammar`, `Rule`, and pest-error adapter
8//! - [`builders`] — Pair → AST conversion (statements, expressions, helpers)
9
10use amarok_syntax::{Diagnostic, Expression, FileId, Program, Spanned, Statement};
11use pest::Parser;
12
13mod builders;
14mod grammar;
15
16use builders::{build_expression, build_program, build_statement};
17use grammar::{AmarokGrammar, Rule, pest_error_to_diagnostic};
18
19/// Parse a full Amarok program (multiple statements).
20///
21/// All spans on the resulting AST will use [`FileId::DUMMY`]; for multi-file
22/// scenarios use [`parse_program_with_file_id`].
23///
24/// # Errors
25///
26/// Returns an error if the source is not valid Amarok syntax for a full program.
27pub fn parse_program(source: &str) -> Result<Program, Diagnostic> {
28    let mut pairs = AmarokGrammar::parse(Rule::program, source)
29        .map_err(|error: pest::error::Error<Rule>| pest_error_to_diagnostic(&error))?;
30
31    let program_pair = pairs
32        .next()
33        .ok_or_else(|| Diagnostic::new("Expected a program, found nothing."))?;
34
35    build_program(program_pair)
36}
37
38/// Parse a full Amarok program and stamp every span with the given [`FileId`].
39///
40/// # Errors
41///
42/// Returns an error if the source is not valid Amarok syntax for a full program.
43/// Errors are reported with the given `file_id` attached to their span.
44pub fn parse_program_with_file_id(source: &str, file_id: FileId) -> Result<Program, Diagnostic> {
45    let mut program = parse_program(source).map_err(|mut diagnostic| {
46        if let Some(span) = diagnostic.span.as_mut() {
47            span.file_id = file_id;
48        }
49        diagnostic
50    })?;
51    set_program_file_id(&mut program, file_id);
52    Ok(program)
53}
54
55/// Parse a single statement (useful for REPL later).
56///
57/// # Errors
58///
59/// Returns an error if the source is not valid Amarok syntax for a single statement.
60pub fn parse_statement(source: &str) -> Result<Spanned<Statement>, Diagnostic> {
61    let mut pairs = AmarokGrammar::parse(Rule::statement, source)
62        .map_err(|error: pest::error::Error<Rule>| pest_error_to_diagnostic(&error))?;
63
64    let statement_pair = pairs
65        .next()
66        .ok_or_else(|| Diagnostic::new("Expected a statement, found nothing."))?;
67
68    build_statement(statement_pair)
69}
70
71/// Parse a single expression (useful for unit tests and REPL experiments).
72///
73/// # Errors
74///
75/// Returns an error if the source is not valid Amarok syntax for a single expression.
76pub fn parse_expression(source: &str) -> Result<Spanned<Expression>, Diagnostic> {
77    let mut pairs = AmarokGrammar::parse(Rule::expression, source)
78        .map_err(|error: pest::error::Error<Rule>| pest_error_to_diagnostic(&error))?;
79
80    let expression_pair = pairs
81        .next()
82        .ok_or_else(|| Diagnostic::new("Expected an expression, found nothing."))?;
83
84    build_expression(expression_pair)
85}
86
87// --- span rewriting -------------------------------------------------------
88
89fn set_program_file_id(program: &mut Program, file_id: FileId) {
90    for statement in &mut program.statements {
91        set_statement_file_id(statement, file_id);
92    }
93}
94
95fn set_statement_file_id(statement: &mut Spanned<Statement>, file_id: FileId) {
96    statement.span.file_id = file_id;
97    match &mut statement.value {
98        Statement::Assignment { value, .. } => set_expression_file_id(value, file_id),
99        Statement::Expression { expression } => set_expression_file_id(expression, file_id),
100        Statement::Block { statements } => {
101            for child in statements {
102                set_statement_file_id(child, file_id);
103            }
104        }
105        Statement::If {
106            condition,
107            then_branch,
108            else_branch,
109        } => {
110            set_expression_file_id(condition, file_id);
111            for child in then_branch {
112                set_statement_file_id(child, file_id);
113            }
114            for child in else_branch {
115                set_statement_file_id(child, file_id);
116            }
117        }
118        Statement::While { condition, body } => {
119            set_expression_file_id(condition, file_id);
120            for child in body {
121                set_statement_file_id(child, file_id);
122            }
123        }
124        Statement::FunctionDefinition { body, .. } => {
125            for child in body {
126                set_statement_file_id(child, file_id);
127            }
128        }
129        Statement::Return { value } => {
130            if let Some(expression) = value {
131                set_expression_file_id(expression, file_id);
132            }
133        }
134        Statement::Use { .. } => {}
135    }
136}
137
138fn set_expression_file_id(expression: &mut Spanned<Expression>, file_id: FileId) {
139    expression.span.file_id = file_id;
140    match &mut expression.value {
141        Expression::Integer(_) | Expression::String(_) | Expression::Variable(_) => {}
142        Expression::Binary { left, right, .. } => {
143            set_expression_file_id(left, file_id);
144            set_expression_file_id(right, file_id);
145        }
146        Expression::FunctionCall { arguments, .. } => {
147            for argument in arguments {
148                set_expression_file_id(argument, file_id);
149            }
150        }
151    }
152}