Skip to main content

shape_ast/parser/
modules.rs

1//! Module system parsing for Shape
2
3use crate::error::{Result, ShapeError};
4use crate::parser::pair_location;
5use pest::iterators::Pair;
6
7use crate::ast::{
8    ExportItem, ExportSpec, ExportStmt, ImportItems, ImportSpec, ImportStmt, Item, ModuleDecl,
9};
10use crate::parser::{Rule, functions, items, pair_span};
11
12/// Parse an import statement
13///
14/// Handles 3 grammar alternatives:
15///   from std::core::math use { a, b }       → Named with path (`::`-separated)
16///   use std::core::math as math             → Namespace with alias
17///   use std::core::math                      → Namespace without alias (binds `math`)
18pub fn parse_import_stmt(pair: Pair<Rule>) -> Result<ImportStmt> {
19    let pair_loc = pair_location(&pair);
20    let mut inner = pair.into_inner();
21    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
22        message: "invalid import statement".to_string(),
23        location: Some(pair_loc.clone()),
24    })?;
25    let first_str = first.as_str();
26    let first_rule = first.as_rule();
27
28    // Dispatch based on first token
29    match first_rule {
30        Rule::module_path => {
31            let module_path = first_str.to_string();
32            match inner.next() {
33                Some(pair) if pair.as_rule() == Rule::import_item_list => {
34                    // "from <module_path> use { ... }"
35                    let specs = parse_import_item_list(pair)?;
36                    Ok(ImportStmt {
37                        items: ImportItems::Named(specs),
38                        from: module_path,
39                    })
40                }
41                Some(pair) if pair.as_rule() == Rule::ident => {
42                    // "use <module_path> as <alias>"
43                    let alias = pair.as_str().to_string();
44                    let local_name = module_path
45                        .rsplit("::")
46                        .next()
47                        .unwrap_or(module_path.as_str())
48                        .to_string();
49                    Ok(ImportStmt {
50                        items: ImportItems::Namespace {
51                            name: local_name,
52                            alias: Some(alias),
53                        },
54                        from: module_path,
55                    })
56                }
57                None => {
58                    // "use <module_path>"
59                    let local_name = module_path
60                        .rsplit("::")
61                        .next()
62                        .unwrap_or(module_path.as_str())
63                        .to_string();
64                    Ok(ImportStmt {
65                        items: ImportItems::Namespace {
66                            name: local_name,
67                            alias: None,
68                        },
69                        from: module_path,
70                    })
71                }
72                _ => Err(ShapeError::ParseError {
73                    message: "unexpected token in use statement".to_string(),
74                    location: Some(pair_loc),
75                }),
76            }
77        }
78        _ => Err(ShapeError::ParseError {
79            message: format!(
80                "unexpected token in import statement: {:?} '{}'",
81                first_rule, first_str
82            ),
83            location: Some(pair_loc.with_hint("use 'from path use { ... }' or 'use path'")),
84        }),
85    }
86}
87
88/// Parse import item list
89fn parse_import_item_list(pair: Pair<Rule>) -> Result<Vec<ImportSpec>> {
90    let mut imports = Vec::new();
91
92    for item_pair in pair.into_inner() {
93        if item_pair.as_rule() == Rule::import_item {
94            imports.push(parse_import_item(item_pair)?);
95        }
96    }
97
98    Ok(imports)
99}
100
101/// Parse a single import item
102fn parse_import_item(pair: Pair<Rule>) -> Result<ImportSpec> {
103    let pair_loc = pair_location(&pair);
104    let mut inner = pair.into_inner();
105
106    let item_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
107        message: "expected import item name".to_string(),
108        location: Some(pair_loc.clone()),
109    })?;
110
111    match item_pair.as_rule() {
112        Rule::annotation_import_item => {
113            let mut annotation_inner = item_pair.into_inner();
114            let name_pair = annotation_inner
115                .next()
116                .ok_or_else(|| ShapeError::ParseError {
117                    message: "expected annotation import name".to_string(),
118                    location: Some(pair_loc.clone()),
119                })?;
120            Ok(ImportSpec {
121                name: name_pair.as_str().to_string(),
122                alias: None,
123                is_annotation: true,
124            })
125        }
126        Rule::regular_import_item => {
127            let mut regular_inner = item_pair.into_inner();
128            let name_pair = regular_inner.next().ok_or_else(|| ShapeError::ParseError {
129                message: "expected import item name".to_string(),
130                location: Some(pair_loc.clone()),
131            })?;
132            Ok(ImportSpec {
133                name: name_pair.as_str().to_string(),
134                alias: regular_inner.next().map(|p| p.as_str().to_string()),
135                is_annotation: false,
136            })
137        }
138        _ => Err(ShapeError::ParseError {
139            message: format!("unexpected import item: {:?}", item_pair.as_rule()),
140            location: Some(pair_location(&item_pair)),
141        }),
142    }
143}
144
145/// Parse a pub item (visibility modifier on definitions)
146pub fn parse_export_item(pair: Pair<Rule>) -> Result<ExportStmt> {
147    let pair_loc = pair_location(&pair);
148    let mut inner = pair.into_inner();
149
150    // Get first token
151    let next_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
152        message: "expected pub item content".to_string(),
153        location: Some(
154            pair_loc
155                .clone()
156                .with_hint("use 'pub fn', 'pub enum', 'pub type', or 'pub { name }'"),
157        ),
158    })?;
159
160    let item = match next_pair.as_rule() {
161        Rule::foreign_function_def => {
162            ExportItem::ForeignFunction(functions::parse_foreign_function_def(next_pair)?)
163        }
164        Rule::extern_native_function_def => {
165            ExportItem::ForeignFunction(functions::parse_extern_native_function_def(next_pair)?)
166        }
167        Rule::function_def => ExportItem::Function(functions::parse_function_def(next_pair)?),
168        Rule::builtin_function_decl => {
169            ExportItem::BuiltinFunction(functions::parse_builtin_function_decl(next_pair)?)
170        }
171        Rule::builtin_type_decl => {
172            ExportItem::BuiltinType(crate::parser::types::parse_builtin_type_decl(next_pair)?)
173        }
174        Rule::type_alias_def => {
175            ExportItem::TypeAlias(crate::parser::types::parse_type_alias_def(next_pair)?)
176        }
177        Rule::enum_def => ExportItem::Enum(crate::parser::types::parse_enum_def(next_pair)?),
178        Rule::struct_type_def => {
179            ExportItem::Struct(crate::parser::types::parse_struct_type_def(next_pair)?)
180        }
181        Rule::native_struct_type_def => ExportItem::Struct(
182            crate::parser::types::parse_native_struct_type_def(next_pair)?,
183        ),
184        Rule::interface_def => {
185            ExportItem::Interface(crate::parser::types::parse_interface_def(next_pair)?)
186        }
187        Rule::trait_def => ExportItem::Trait(crate::parser::types::parse_trait_def(next_pair)?),
188        Rule::annotation_def => {
189            ExportItem::Annotation(crate::parser::extensions::parse_annotation_def(next_pair)?)
190        }
191        Rule::variable_decl => {
192            let var_decl = items::parse_variable_decl(next_pair.clone())?;
193            match var_decl.pattern.as_identifier() {
194                Some(name) => {
195                    let item = ExportItem::Named(vec![ExportSpec {
196                        name: name.to_string(),
197                        alias: None,
198                    }]);
199                    return Ok(ExportStmt {
200                        item,
201                        source_decl: Some(var_decl),
202                    });
203                }
204                None => {
205                    return Err(ShapeError::ParseError {
206                        message: "destructuring patterns are not supported in pub declarations"
207                            .to_string(),
208                        location: Some(
209                            pair_location(&next_pair)
210                                .with_hint("use a simple name: 'pub let name = value'"),
211                        ),
212                    });
213                }
214            }
215        }
216        Rule::export_spec_list => {
217            let specs = parse_export_spec_list(next_pair)?;
218            ExportItem::Named(specs)
219        }
220        _ => {
221            return Err(ShapeError::ParseError {
222                message: format!("unexpected pub item type: {:?}", next_pair.as_rule()),
223                location: Some(pair_location(&next_pair)),
224            });
225        }
226    };
227
228    Ok(ExportStmt {
229        item,
230        source_decl: None,
231    })
232}
233
234/// Parse export specification list
235fn parse_export_spec_list(pair: Pair<Rule>) -> Result<Vec<ExportSpec>> {
236    let mut specs = Vec::new();
237
238    for spec_pair in pair.into_inner() {
239        if spec_pair.as_rule() == Rule::export_spec {
240            specs.push(parse_export_spec(spec_pair)?);
241        }
242    }
243
244    Ok(specs)
245}
246
247/// Parse a single export specification
248fn parse_export_spec(pair: Pair<Rule>) -> Result<ExportSpec> {
249    let pair_loc = pair_location(&pair);
250    let mut inner = pair.into_inner();
251
252    let name_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
253        message: "expected export specification name".to_string(),
254        location: Some(pair_loc),
255    })?;
256    let name = name_pair.as_str().to_string();
257    let alias = inner.next().map(|p| p.as_str().to_string());
258
259    Ok(ExportSpec { name, alias })
260}
261
262/// Parse an inline module declaration: `mod Name { ... }`.
263pub fn parse_module_decl(pair: Pair<Rule>) -> Result<ModuleDecl> {
264    let pair_loc = pair_location(&pair);
265    let mut annotations = Vec::new();
266    let mut name: Option<String> = None;
267    let mut name_span = crate::ast::Span::DUMMY;
268    let mut items_out: Vec<Item> = Vec::new();
269
270    for part in pair.into_inner() {
271        match part.as_rule() {
272            Rule::annotations => {
273                annotations = functions::parse_annotations(part)?;
274            }
275            Rule::ident => {
276                if name.is_none() {
277                    name = Some(part.as_str().to_string());
278                    name_span = pair_span(&part);
279                }
280            }
281            Rule::item => {
282                items_out.push(crate::parser::parse_item(part)?);
283            }
284            Rule::item_recovery => {
285                let span = part.as_span();
286                let text = part.as_str().trim();
287                let preview = if text.len() > 40 {
288                    format!("{}...", &text[..40])
289                } else {
290                    text.to_string()
291                };
292                return Err(ShapeError::ParseError {
293                    message: format!("Syntax error in module body near: {}", preview),
294                    location: Some(pair_location(&part).with_length(span.end() - span.start())),
295                });
296            }
297            _ => {}
298        }
299    }
300
301    let name = name.ok_or_else(|| ShapeError::ParseError {
302        message: "missing module name".to_string(),
303        location: Some(pair_loc),
304    })?;
305
306    Ok(ModuleDecl {
307        name,
308        name_span,
309        doc_comment: None,
310        annotations,
311        items: items_out,
312    })
313}