Skip to main content

shape_ast/parser/
mod.rs

1//! Parser module for Shape language
2//!
3//! This module contains the complete parser implementation using Pest.
4//! It's organized into submodules for different language constructs.
5
6use crate::ast::Span;
7use crate::error::{Result, ShapeError, SourceLocation};
8use pest::Parser;
9use pest::iterators::Pair;
10use pest_derive::Parser;
11
12/// Extract a lightweight Span from a Pest pair for AST nodes
13pub fn pair_span(pair: &Pair<Rule>) -> Span {
14    let span = pair.as_span();
15    Span::new(span.start(), span.end())
16}
17
18/// Extract source location from a Pest pair for error reporting
19pub(crate) fn pair_location(pair: &Pair<Rule>) -> SourceLocation {
20    let span = pair.as_span();
21    let (line, col) = span.start_pos().line_col();
22    let source_line = span.start_pos().line_of().to_string();
23    let length = span.end() - span.start();
24
25    SourceLocation::new(line, col)
26        .with_length(length)
27        .with_source_line(source_line)
28}
29
30// Submodules for different parsing concerns
31pub mod data_sources;
32pub mod docs;
33pub mod expressions;
34pub mod extensions;
35pub mod functions;
36pub mod items;
37pub mod modules;
38pub mod preprocessor;
39pub mod queries;
40pub mod resilient;
41pub mod statements;
42pub mod stream;
43pub mod string_literals;
44pub mod time;
45pub mod types;
46
47#[cfg(test)]
48mod tests;
49
50use crate::ast::{DocComment, ExportItem, Item, Program};
51
52#[derive(Parser)]
53#[grammar = "src/shape.pest"]
54pub struct ShapeParser;
55
56/// Parse a complete Shape program
57pub fn parse_program(input: &str) -> Result<Program> {
58    let processed = preprocessor::preprocess_semicolons(input);
59    let pairs = ShapeParser::parse(Rule::program, &processed).map_err(|e| {
60        // Use the structured error converter for rich error messages
61        let structured = crate::error::pest_converter::convert_pest_error(&e, &processed);
62        ShapeError::StructuredParse(Box::new(structured))
63    })?;
64
65    let mut items = Vec::new();
66    let mut module_doc_comment = None;
67
68    for pair in pairs {
69        if pair.as_rule() == Rule::program {
70            for inner in pair.into_inner() {
71                match inner.as_rule() {
72                    Rule::program_doc_comment => {
73                        module_doc_comment = Some(docs::parse_doc_comment(inner));
74                    }
75                    Rule::item => {
76                        items.push(parse_item(inner)?);
77                    }
78                    Rule::item_recovery => {
79                        let span = inner.as_span();
80                        let text = inner.as_str().trim();
81                        let preview = if text.len() > 40 {
82                            format!("{}...", &text[..40])
83                        } else {
84                            text.to_string()
85                        };
86                        return Err(ShapeError::ParseError {
87                            message: format!("Syntax error near: {}", preview),
88                            location: Some(
89                                pair_location(&inner).with_length(span.end() - span.start()),
90                            ),
91                        });
92                    }
93                    _ => {}
94                }
95            }
96        }
97    }
98
99    let mut program = Program {
100        items,
101        docs: crate::ast::ProgramDocs::default(),
102    };
103    program.docs = docs::build_program_docs(&program, module_doc_comment.as_ref());
104    Ok(program)
105}
106
107/// Parse an individual item (pattern, query, assignment, or expression)
108pub fn parse_item(pair: pest::iterators::Pair<Rule>) -> Result<Item> {
109    let pair_loc = pair_location(&pair);
110    let mut item_inner = pair.into_inner();
111    let mut doc_comment = None;
112    let mut inner = item_inner.next().ok_or_else(|| ShapeError::ParseError {
113        message: "expected item content".to_string(),
114        location: Some(pair_loc.clone().with_hint(
115            "provide a pattern, query, function, variable declaration, or expression",
116        )),
117    })?;
118
119    if inner.as_rule() == Rule::doc_comment {
120        doc_comment = Some(docs::parse_doc_comment(inner));
121        inner = item_inner.next().ok_or_else(|| ShapeError::ParseError {
122            message: "expected item after doc comment".to_string(),
123            location: Some(pair_loc.clone()),
124        })?;
125    }
126
127    if inner.as_rule() == Rule::item_core {
128        inner = inner
129            .into_inner()
130            .next()
131            .ok_or_else(|| ShapeError::ParseError {
132                message: "expected item content".to_string(),
133                location: Some(pair_loc.clone()),
134            })?;
135    }
136
137    let span = pair_span(&inner);
138    let mut item = match inner.as_rule() {
139        Rule::query => Item::Query(queries::parse_query(inner)?, span),
140        Rule::variable_decl => Item::VariableDecl(items::parse_variable_decl(inner)?, span),
141        Rule::assignment => {
142            let inner_loc = pair_location(&inner);
143            let mut inner = inner.into_inner();
144            let pattern_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
145                message: "expected pattern in assignment".to_string(),
146                location: Some(inner_loc.clone()),
147            })?;
148            let pattern = items::parse_pattern(pattern_pair)?;
149            let value_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
150                message: "expected value expression in assignment".to_string(),
151                location: Some(inner_loc.with_hint("provide a value after '='")),
152            })?;
153            let value = expressions::parse_expression(value_pair)?;
154            Item::Assignment(
155                crate::ast::Assignment { pattern, value },
156                span,
157            )
158        }
159        Rule::expression_stmt => {
160            let inner_loc = pair_location(&inner);
161            let expr_pair = inner
162                .into_inner()
163                .next()
164                .ok_or_else(|| ShapeError::ParseError {
165                    message: "expected expression in statement".to_string(),
166                    location: Some(inner_loc),
167                })?;
168            let expr = expressions::parse_expression(expr_pair)?;
169            Item::Expression(expr, span)
170        }
171        Rule::import_stmt => Item::Import(modules::parse_import_stmt(inner)?, span),
172        Rule::module_decl => Item::Module(modules::parse_module_decl(inner)?, span),
173        Rule::pub_item => Item::Export(modules::parse_export_item(inner)?, span),
174        Rule::struct_type_def => Item::StructType(types::parse_struct_type_def(inner)?, span),
175        Rule::native_struct_type_def => Item::StructType(
176            types::parse_native_struct_type_def(inner)?,
177            span,
178        ),
179        Rule::builtin_type_decl => Item::BuiltinTypeDecl(
180            types::parse_builtin_type_decl(inner)?,
181            span,
182        ),
183        Rule::type_alias_def => Item::TypeAlias(types::parse_type_alias_def(inner)?, span),
184        Rule::interface_def => Item::Interface(types::parse_interface_def(inner)?, span),
185        Rule::trait_def => Item::Trait(types::parse_trait_def(inner)?, span),
186        Rule::enum_def => Item::Enum(types::parse_enum_def(inner)?, span),
187        Rule::extern_native_function_def => Item::ForeignFunction(
188            functions::parse_extern_native_function_def(inner)?,
189            span,
190        ),
191        Rule::foreign_function_def => Item::ForeignFunction(
192            functions::parse_foreign_function_def(inner)?,
193            span,
194        ),
195        Rule::function_def => Item::Function(functions::parse_function_def(inner)?, span),
196        Rule::builtin_function_decl => Item::BuiltinFunctionDecl(
197            functions::parse_builtin_function_decl(inner)?,
198            span,
199        ),
200        Rule::stream_def => Item::Stream(stream::parse_stream_def(inner)?, span),
201        Rule::test_def => {
202            return Err(ShapeError::ParseError {
203                message: "Embedded test definitions are no longer supported in this refactor"
204                    .to_string(),
205                location: None,
206            });
207        }
208        Rule::statement => Item::Statement(statements::parse_statement(inner)?, span),
209        Rule::extend_statement => Item::Extend(
210            extensions::parse_extend_statement(inner)?,
211            span,
212        ),
213        Rule::impl_block => Item::Impl(extensions::parse_impl_block(inner)?, span),
214        Rule::optimize_statement => Item::Optimize(
215            extensions::parse_optimize_statement(inner)?,
216            span,
217        ),
218        Rule::annotation_def => Item::AnnotationDef(
219            extensions::parse_annotation_def(inner)?,
220            span,
221        ),
222        Rule::datasource_def => Item::DataSource(
223            data_sources::parse_datasource_def(inner)?,
224            span,
225        ),
226        Rule::query_decl => Item::QueryDecl(
227            data_sources::parse_query_decl(inner)?,
228            span,
229        ),
230        Rule::comptime_block => {
231            let block_pair = inner
232                .into_inner()
233                .next()
234                .ok_or_else(|| ShapeError::ParseError {
235                    message: "expected block after 'comptime'".to_string(),
236                    location: None,
237                })?;
238            let block_expr = expressions::control_flow::parse_block_expr(block_pair)?;
239            let stmts = expressions::primary::block_items_to_statements(block_expr, span);
240            Item::Comptime(stmts, span)
241        }
242        _ => {
243            return Err(ShapeError::ParseError {
244                message: format!("unexpected item type: {:?}", inner.as_rule()),
245                location: Some(pair_location(&inner)),
246            });
247        }
248    };
249
250    if let Some(doc_comment) = doc_comment {
251        attach_item_doc_comment(&mut item, doc_comment);
252    }
253
254    Ok(item)
255}
256
257fn attach_item_doc_comment(item: &mut Item, doc_comment: DocComment) {
258    match item {
259        Item::Module(module, _) => module.doc_comment = Some(doc_comment),
260        Item::TypeAlias(alias, _) => alias.doc_comment = Some(doc_comment),
261        Item::Interface(interface, _) => interface.doc_comment = Some(doc_comment),
262        Item::Trait(trait_def, _) => trait_def.doc_comment = Some(doc_comment),
263        Item::Enum(enum_def, _) => enum_def.doc_comment = Some(doc_comment),
264        Item::Function(function, _) => function.doc_comment = Some(doc_comment),
265        Item::AnnotationDef(annotation_def, _) => annotation_def.doc_comment = Some(doc_comment),
266        Item::StructType(struct_def, _) => struct_def.doc_comment = Some(doc_comment),
267        Item::BuiltinTypeDecl(ty, _) => ty.doc_comment = Some(doc_comment),
268        Item::BuiltinFunctionDecl(function, _) => function.doc_comment = Some(doc_comment),
269        Item::ForeignFunction(function, _) => function.doc_comment = Some(doc_comment),
270        Item::Export(export, _) => attach_export_doc_comment(&mut export.item, doc_comment),
271        _ => {}
272    }
273}
274
275fn attach_export_doc_comment(item: &mut ExportItem, doc_comment: DocComment) {
276    match item {
277        ExportItem::Function(function) => function.doc_comment = Some(doc_comment),
278        ExportItem::TypeAlias(alias) => alias.doc_comment = Some(doc_comment),
279        ExportItem::Enum(enum_def) => enum_def.doc_comment = Some(doc_comment),
280        ExportItem::Struct(struct_def) => struct_def.doc_comment = Some(doc_comment),
281        ExportItem::Interface(interface) => interface.doc_comment = Some(doc_comment),
282        ExportItem::Trait(trait_def) => trait_def.doc_comment = Some(doc_comment),
283        ExportItem::ForeignFunction(function) => function.doc_comment = Some(doc_comment),
284        ExportItem::Named(_) => {}
285    }
286}
287
288// Re-export commonly used functions for convenience
289pub use expressions::parse_expression;
290pub use items::{parse_pattern, parse_variable_decl};
291pub use types::parse_type_annotation;
292
293/// Parse a single expression from a string
294///
295/// This is useful for parsing expressions extracted from string interpolation.
296pub fn parse_expression_str(input: &str) -> Result<crate::ast::Expr> {
297    let pairs = ShapeParser::parse(Rule::expression, input).map_err(|e| {
298        let structured = crate::error::pest_converter::convert_pest_error(&e, input);
299        ShapeError::StructuredParse(Box::new(structured))
300    })?;
301
302    let pair = pairs
303        .into_iter()
304        .next()
305        .ok_or_else(|| ShapeError::ParseError {
306            message: "Expected expression".to_string(),
307            location: None,
308        })?;
309
310    expressions::parse_expression(pair)
311}