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 name_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
107        message: "expected import item name".to_string(),
108        location: Some(pair_loc),
109    })?;
110    let name = name_pair.as_str().to_string();
111    let alias = inner.next().map(|p| p.as_str().to_string());
112
113    Ok(ImportSpec { name, alias })
114}
115
116/// Parse a pub item (visibility modifier on definitions)
117pub fn parse_export_item(pair: Pair<Rule>) -> Result<ExportStmt> {
118    let pair_loc = pair_location(&pair);
119    let mut inner = pair.into_inner();
120
121    // Get first token
122    let next_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
123        message: "expected pub item content".to_string(),
124        location: Some(
125            pair_loc
126                .clone()
127                .with_hint("use 'pub fn', 'pub enum', 'pub type', or 'pub { name }'"),
128        ),
129    })?;
130
131    let item = match next_pair.as_rule() {
132        Rule::foreign_function_def => {
133            ExportItem::ForeignFunction(functions::parse_foreign_function_def(next_pair)?)
134        }
135        Rule::extern_native_function_def => {
136            ExportItem::ForeignFunction(functions::parse_extern_native_function_def(next_pair)?)
137        }
138        Rule::function_def => ExportItem::Function(functions::parse_function_def(next_pair)?),
139        Rule::type_alias_def => {
140            ExportItem::TypeAlias(crate::parser::types::parse_type_alias_def(next_pair)?)
141        }
142        Rule::enum_def => ExportItem::Enum(crate::parser::types::parse_enum_def(next_pair)?),
143        Rule::struct_type_def => {
144            ExportItem::Struct(crate::parser::types::parse_struct_type_def(next_pair)?)
145        }
146        Rule::native_struct_type_def => ExportItem::Struct(
147            crate::parser::types::parse_native_struct_type_def(next_pair)?,
148        ),
149        Rule::interface_def => {
150            ExportItem::Interface(crate::parser::types::parse_interface_def(next_pair)?)
151        }
152        Rule::trait_def => ExportItem::Trait(crate::parser::types::parse_trait_def(next_pair)?),
153        Rule::variable_decl => {
154            let var_decl = items::parse_variable_decl(next_pair.clone())?;
155            match var_decl.pattern.as_identifier() {
156                Some(name) => {
157                    let item = ExportItem::Named(vec![ExportSpec {
158                        name: name.to_string(),
159                        alias: None,
160                    }]);
161                    return Ok(ExportStmt {
162                        item,
163                        source_decl: Some(var_decl),
164                    });
165                }
166                None => {
167                    return Err(ShapeError::ParseError {
168                        message: "destructuring patterns are not supported in pub declarations"
169                            .to_string(),
170                        location: Some(
171                            pair_location(&next_pair)
172                                .with_hint("use a simple name: 'pub let name = value'"),
173                        ),
174                    });
175                }
176            }
177        }
178        Rule::export_spec_list => {
179            let specs = parse_export_spec_list(next_pair)?;
180            ExportItem::Named(specs)
181        }
182        _ => {
183            return Err(ShapeError::ParseError {
184                message: format!("unexpected pub item type: {:?}", next_pair.as_rule()),
185                location: Some(pair_location(&next_pair)),
186            });
187        }
188    };
189
190    Ok(ExportStmt {
191        item,
192        source_decl: None,
193    })
194}
195
196/// Parse export specification list
197fn parse_export_spec_list(pair: Pair<Rule>) -> Result<Vec<ExportSpec>> {
198    let mut specs = Vec::new();
199
200    for spec_pair in pair.into_inner() {
201        if spec_pair.as_rule() == Rule::export_spec {
202            specs.push(parse_export_spec(spec_pair)?);
203        }
204    }
205
206    Ok(specs)
207}
208
209/// Parse a single export specification
210fn parse_export_spec(pair: Pair<Rule>) -> Result<ExportSpec> {
211    let pair_loc = pair_location(&pair);
212    let mut inner = pair.into_inner();
213
214    let name_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
215        message: "expected export specification name".to_string(),
216        location: Some(pair_loc),
217    })?;
218    let name = name_pair.as_str().to_string();
219    let alias = inner.next().map(|p| p.as_str().to_string());
220
221    Ok(ExportSpec { name, alias })
222}
223
224/// Parse an inline module declaration: `mod Name { ... }`.
225pub fn parse_module_decl(pair: Pair<Rule>) -> Result<ModuleDecl> {
226    let pair_loc = pair_location(&pair);
227    let mut annotations = Vec::new();
228    let mut name: Option<String> = None;
229    let mut name_span = crate::ast::Span::DUMMY;
230    let mut items_out: Vec<Item> = Vec::new();
231
232    for part in pair.into_inner() {
233        match part.as_rule() {
234            Rule::annotations => {
235                annotations = functions::parse_annotations(part)?;
236            }
237            Rule::ident => {
238                if name.is_none() {
239                    name = Some(part.as_str().to_string());
240                    name_span = pair_span(&part);
241                }
242            }
243            Rule::item => {
244                items_out.push(crate::parser::parse_item(part)?);
245            }
246            Rule::item_recovery => {
247                let span = part.as_span();
248                let text = part.as_str().trim();
249                let preview = if text.len() > 40 {
250                    format!("{}...", &text[..40])
251                } else {
252                    text.to_string()
253                };
254                return Err(ShapeError::ParseError {
255                    message: format!("Syntax error in module body near: {}", preview),
256                    location: Some(pair_location(&part).with_length(span.end() - span.start())),
257                });
258            }
259            _ => {}
260        }
261    }
262
263    let name = name.ok_or_else(|| ShapeError::ParseError {
264        message: "missing module name".to_string(),
265        location: Some(pair_loc),
266    })?;
267
268    Ok(ModuleDecl {
269        name,
270        name_span,
271        doc_comment: None,
272        annotations,
273        items: items_out,
274    })
275}