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 =
113        item_inner.next().ok_or_else(|| ShapeError::ParseError {
114            message: "expected item content".to_string(),
115            location: Some(pair_loc.clone().with_hint(
116                "provide a pattern, query, function, variable declaration, or expression",
117            )),
118        })?;
119
120    if inner.as_rule() == Rule::doc_comment {
121        doc_comment = Some(docs::parse_doc_comment(inner));
122        inner = item_inner.next().ok_or_else(|| ShapeError::ParseError {
123            message: "expected item after doc comment".to_string(),
124            location: Some(pair_loc.clone()),
125        })?;
126    }
127
128    if inner.as_rule() == Rule::item_core {
129        inner = inner
130            .into_inner()
131            .next()
132            .ok_or_else(|| ShapeError::ParseError {
133                message: "expected item content".to_string(),
134                location: Some(pair_loc.clone()),
135            })?;
136    }
137
138    let span = pair_span(&inner);
139    let mut item = match inner.as_rule() {
140        Rule::query => Item::Query(queries::parse_query(inner)?, span),
141        Rule::variable_decl => Item::VariableDecl(items::parse_variable_decl(inner)?, span),
142        Rule::assignment => {
143            let inner_loc = pair_location(&inner);
144            let mut inner = inner.into_inner();
145            let pattern_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
146                message: "expected pattern in assignment".to_string(),
147                location: Some(inner_loc.clone()),
148            })?;
149            let pattern = items::parse_pattern(pattern_pair)?;
150            let value_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
151                message: "expected value expression in assignment".to_string(),
152                location: Some(inner_loc.with_hint("provide a value after '='")),
153            })?;
154            let value = expressions::parse_expression(value_pair)?;
155            Item::Assignment(crate::ast::Assignment { pattern, value }, span)
156        }
157        Rule::expression_stmt => {
158            let inner_loc = pair_location(&inner);
159            let expr_pair = inner
160                .into_inner()
161                .next()
162                .ok_or_else(|| ShapeError::ParseError {
163                    message: "expected expression in statement".to_string(),
164                    location: Some(inner_loc),
165                })?;
166            let expr = expressions::parse_expression(expr_pair)?;
167            Item::Expression(expr, span)
168        }
169        Rule::import_stmt => Item::Import(modules::parse_import_stmt(inner)?, span),
170        Rule::module_decl => Item::Module(modules::parse_module_decl(inner)?, span),
171        Rule::pub_item => Item::Export(modules::parse_export_item(inner)?, span),
172        Rule::struct_type_def => Item::StructType(types::parse_struct_type_def(inner)?, span),
173        Rule::native_struct_type_def => {
174            Item::StructType(types::parse_native_struct_type_def(inner)?, span)
175        }
176        Rule::builtin_type_decl => {
177            Item::BuiltinTypeDecl(types::parse_builtin_type_decl(inner)?, span)
178        }
179        Rule::type_alias_def => Item::TypeAlias(types::parse_type_alias_def(inner)?, span),
180        Rule::interface_def => Item::Interface(types::parse_interface_def(inner)?, span),
181        Rule::trait_def => Item::Trait(types::parse_trait_def(inner)?, span),
182        Rule::enum_def => Item::Enum(types::parse_enum_def(inner)?, span),
183        Rule::extern_native_function_def => {
184            Item::ForeignFunction(functions::parse_extern_native_function_def(inner)?, span)
185        }
186        Rule::foreign_function_def => {
187            Item::ForeignFunction(functions::parse_foreign_function_def(inner)?, span)
188        }
189        Rule::function_def => Item::Function(functions::parse_function_def(inner)?, span),
190        Rule::builtin_function_decl => {
191            Item::BuiltinFunctionDecl(functions::parse_builtin_function_decl(inner)?, span)
192        }
193        Rule::stream_def => Item::Stream(stream::parse_stream_def(inner)?, span),
194        Rule::statement => Item::Statement(statements::parse_statement(inner)?, span),
195        Rule::extend_statement => Item::Extend(extensions::parse_extend_statement(inner)?, span),
196        Rule::impl_block => Item::Impl(extensions::parse_impl_block(inner)?, span),
197        Rule::optimize_statement => {
198            Item::Optimize(extensions::parse_optimize_statement(inner)?, span)
199        }
200        Rule::annotation_def => Item::AnnotationDef(extensions::parse_annotation_def(inner)?, span),
201        Rule::datasource_def => Item::DataSource(data_sources::parse_datasource_def(inner)?, span),
202        Rule::query_decl => Item::QueryDecl(data_sources::parse_query_decl(inner)?, span),
203        Rule::comptime_block => {
204            let block_pair = inner
205                .into_inner()
206                .next()
207                .ok_or_else(|| ShapeError::ParseError {
208                    message: "expected block after 'comptime'".to_string(),
209                    location: None,
210                })?;
211            let block_expr = expressions::control_flow::parse_block_expr(block_pair)?;
212            let stmts = expressions::primary::block_items_to_statements(block_expr, span);
213            Item::Comptime(stmts, span)
214        }
215        _ => {
216            return Err(ShapeError::ParseError {
217                message: format!("unexpected item type: {:?}", inner.as_rule()),
218                location: Some(pair_location(&inner)),
219            });
220        }
221    };
222
223    if let Some(doc_comment) = doc_comment {
224        attach_item_doc_comment(&mut item, doc_comment);
225    }
226
227    Ok(item)
228}
229
230fn attach_item_doc_comment(item: &mut Item, doc_comment: DocComment) {
231    match item {
232        Item::Module(module, _) => module.doc_comment = Some(doc_comment),
233        Item::TypeAlias(alias, _) => alias.doc_comment = Some(doc_comment),
234        Item::Interface(interface, _) => interface.doc_comment = Some(doc_comment),
235        Item::Trait(trait_def, _) => trait_def.doc_comment = Some(doc_comment),
236        Item::Enum(enum_def, _) => enum_def.doc_comment = Some(doc_comment),
237        Item::Function(function, _) => function.doc_comment = Some(doc_comment),
238        Item::AnnotationDef(annotation_def, _) => annotation_def.doc_comment = Some(doc_comment),
239        Item::StructType(struct_def, _) => struct_def.doc_comment = Some(doc_comment),
240        Item::BuiltinTypeDecl(ty, _) => ty.doc_comment = Some(doc_comment),
241        Item::BuiltinFunctionDecl(function, _) => function.doc_comment = Some(doc_comment),
242        Item::ForeignFunction(function, _) => function.doc_comment = Some(doc_comment),
243        Item::Export(export, _) => attach_export_doc_comment(&mut export.item, doc_comment),
244        _ => {}
245    }
246}
247
248fn attach_export_doc_comment(item: &mut ExportItem, doc_comment: DocComment) {
249    match item {
250        ExportItem::Function(function) => function.doc_comment = Some(doc_comment),
251        ExportItem::BuiltinFunction(function) => function.doc_comment = Some(doc_comment),
252        ExportItem::BuiltinType(ty) => ty.doc_comment = Some(doc_comment),
253        ExportItem::TypeAlias(alias) => alias.doc_comment = Some(doc_comment),
254        ExportItem::Enum(enum_def) => enum_def.doc_comment = Some(doc_comment),
255        ExportItem::Struct(struct_def) => struct_def.doc_comment = Some(doc_comment),
256        ExportItem::Interface(interface) => interface.doc_comment = Some(doc_comment),
257        ExportItem::Trait(trait_def) => trait_def.doc_comment = Some(doc_comment),
258        ExportItem::Annotation(annotation_def) => annotation_def.doc_comment = Some(doc_comment),
259        ExportItem::ForeignFunction(function) => function.doc_comment = Some(doc_comment),
260        ExportItem::Named(_) => {}
261    }
262}
263
264// Re-export commonly used functions for convenience
265pub use expressions::parse_expression;
266pub use items::{parse_pattern, parse_variable_decl};
267pub use types::parse_type_annotation;
268
269/// Parse a single expression from a string
270///
271/// This is useful for parsing expressions extracted from string interpolation.
272pub fn parse_expression_str(input: &str) -> Result<crate::ast::Expr> {
273    let pairs = ShapeParser::parse(Rule::expression, input).map_err(|e| {
274        let structured = crate::error::pest_converter::convert_pest_error(&e, input);
275        ShapeError::StructuredParse(Box::new(structured))
276    })?;
277
278    let pair = pairs
279        .into_iter()
280        .next()
281        .ok_or_else(|| ShapeError::ParseError {
282            message: "Expected expression".to_string(),
283            location: None,
284        })?;
285
286    expressions::parse_expression(pair)
287}