1use crate::ast::Span;
7use crate::error::{Result, ShapeError, SourceLocation};
8use pest::Parser;
9use pest::iterators::Pair;
10use pest_derive::Parser;
11
12pub fn pair_span(pair: &Pair<Rule>) -> Span {
14 let span = pair.as_span();
15 Span::new(span.start(), span.end())
16}
17
18pub(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
30pub 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
56pub 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 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
107pub 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
264pub use expressions::parse_expression;
266pub use items::{parse_pattern, parse_variable_decl};
267pub use types::parse_type_annotation;
268
269pub 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}