miden_assembly_syntax/parser/
mod.rs1mod cst;
2mod error;
3#[cfg(test)]
4mod tests;
5mod value;
6
7use alloc::{boxed::Box, collections::BTreeSet, string::ToString, sync::Arc, vec::Vec};
8
9use miden_debug_types::{SourceFile, SourceLanguage, SourceManager, Uri};
10use miden_utils_diagnostics::{IntoDiagnostic, Report};
11
12pub use self::{
13 cst::parse_inline_masm,
14 error::{BinErrorKind, HexErrorKind, LiteralErrorKind, ParsingError},
15 value::{IntValue, PushValue, WordValue},
16};
17use crate::{Path, ast, sema};
18
19pub(crate) const MAX_CONSTANT_EXPR_NESTING: usize = 256;
21
22#[derive(Default)]
28pub struct ModuleParser {
29 kind: Option<ast::ModuleKind>,
34 interned: BTreeSet<Arc<str>>,
51 warnings_as_errors: bool,
53}
54
55impl ModuleParser {
56 pub fn new(kind: Option<ast::ModuleKind>) -> Self {
58 Self {
59 kind,
60 interned: Default::default(),
61 warnings_as_errors: false,
62 }
63 }
64
65 pub fn set_warnings_as_errors(&mut self, yes: bool) {
67 self.warnings_as_errors = yes;
68 }
69
70 pub fn parse(
80 &mut self,
81 path: Option<&Path>,
82 source: Arc<SourceFile>,
83 source_manager: Arc<dyn SourceManager>,
84 ) -> Result<Box<ast::Module>, Report> {
85 use alloc::borrow::Cow;
86
87 let path = match path {
88 Some(path) => Some(Arc::<Path>::from(
89 path.canonicalize()
90 .and_then(|p| p.to_absolute().map(Cow::into_owned))
91 .into_diagnostic()?,
92 )),
93 None => None,
94 };
95 let forms = parse_forms_internal(source.clone(), &mut self.interned)?;
96 sema::analyze(
97 source,
98 self.kind,
99 path.as_deref(),
100 forms,
101 self.warnings_as_errors,
102 source_manager,
103 )
104 .map_err(Report::new)
105 }
106
107 #[cfg(feature = "std")]
109 pub fn parse_file<P>(
110 &mut self,
111 path: Option<&Path>,
112 file_path: P,
113 source_manager: Arc<dyn SourceManager>,
114 ) -> Result<Box<ast::Module>, Report>
115 where
116 P: AsRef<std::path::Path>,
117 {
118 use miden_debug_types::SourceManagerExt;
119 use miden_utils_diagnostics::{IntoDiagnostic, WrapErr};
120
121 let file_path = file_path.as_ref();
122 let source_file =
123 source_manager.load_file(file_path).into_diagnostic().wrap_err_with(|| {
124 format!("failed to load source file from '{}'", file_path.display())
125 })?;
126 self.parse(path, source_file, source_manager)
127 }
128
129 pub fn parse_str(
131 &mut self,
132 path: Option<&Path>,
133 source: impl ToString,
134 source_manager: Arc<dyn SourceManager>,
135 ) -> Result<Box<ast::Module>, Report> {
136 use miden_debug_types::SourceContent;
137
138 let source = source.to_string();
139 let source_file = match path {
140 Some(path) => {
141 let uri = Uri::from(path.as_str().to_string().into_boxed_str());
142 let content =
143 SourceContent::new(SourceLanguage::Masm, uri.clone(), source.into_boxed_str());
144 source_manager.load_from_raw_parts(uri, content)
145 },
146 None => source_manager.load_anonymous(SourceLanguage::Masm, source),
147 };
148 self.parse(path, source_file, source_manager)
149 }
150}
151
152#[cfg(any(test, feature = "testing"))]
157pub fn parse_forms(source: Arc<SourceFile>) -> Result<Vec<ast::Form>, Report> {
158 let mut interned = BTreeSet::default();
159 parse_forms_internal(source, &mut interned)
160}
161
162fn parse_forms_internal(
167 source: Arc<SourceFile>,
168 interned: &mut BTreeSet<Arc<str>>,
169) -> Result<Vec<ast::Form>, Report> {
170 cst::parse_forms(source, interned)
171}
172
173#[cfg(feature = "std")]
184pub fn read_modules_from_root(
185 root: impl AsRef<std::path::Path>,
186 namespace: Option<Arc<Path>>,
187 kind: Option<ast::ModuleKind>,
188 source_manager: Arc<dyn SourceManager>,
189 warnings_as_errors: bool,
190) -> Result<(Box<ast::Module>, Vec<Box<ast::Module>>), Report> {
191 use miden_utils_diagnostics::report;
192
193 let root = root.as_ref();
194 let root = Arc::<std::path::Path>::from(
195 root.canonicalize()
196 .map_err(|err| {
197 Report::msg(format!("invalid root module path '{}': {err}", root.display()))
198 })?
199 .into_boxed_path(),
200 );
201
202 if root
204 .extension()
205 .is_none_or(|ext| !ext.eq_ignore_ascii_case(ast::Module::FILE_EXTENSION))
206 {
207 return Err(Report::msg(format!(
208 "invalid root module path '{}': expected a .masm file",
209 root.display()
210 )));
211 }
212
213 if !root.is_file() {
215 return Err(Report::msg(format!(
216 "invalid root module path '{}': not a file",
217 root.display()
218 )));
219 }
220
221 let root_dir = root
223 .parent()
224 .ok_or_else(|| {
225 Report::msg(format!(
226 "invalid root module path '{}': expected path to have a parent directory",
227 root.display()
228 ))
229 })?
230 .to_path_buf();
231
232 let mut seen = BTreeSet::<Arc<Path>>::new();
233 let mut modules = Vec::new();
234
235 let mut parser = ModuleParser::new(kind);
236 parser.set_warnings_as_errors(warnings_as_errors);
237 let root_ast = parser.parse_file(namespace.as_deref(), &root, source_manager.clone())?;
238
239 let namespace = Arc::<Path>::from(root_ast.path().to_path_buf().into_boxed_path());
240 let submodules = root_ast.submodules().to_vec();
241 seen.insert(namespace.clone());
242 walk_module_tree(
243 namespace,
244 root,
245 root_dir,
246 submodules,
247 source_manager,
248 warnings_as_errors,
249 |module| {
250 if !seen.insert(module.path().into()) {
251 Err(report!("duplicate module '{0}'", module.path()))
252 } else {
253 modules.push(module);
254 Ok(())
255 }
256 },
257 )?;
258
259 Ok((root_ast, modules))
260}
261
262#[cfg(feature = "std")]
263pub fn walk_module_tree<F>(
264 namespace: Arc<Path>,
265 root: Arc<std::path::Path>,
266 current_dir: std::path::PathBuf,
267 submodules: Vec<ast::SubmoduleDecl>,
268 source_manager: Arc<dyn SourceManager>,
269 warnings_as_errors: bool,
270 mut callback: F,
271) -> Result<(), Report>
272where
273 F: FnMut(Box<ast::Module>) -> Result<(), Report>,
274{
275 use miden_debug_types::{Spanned, Uri};
276
277 struct ModuleEntry {
278 pub name: ast::Ident,
279 pub namespace: Arc<Path>,
280 pub directory: Arc<std::path::Path>,
281 pub parent: Arc<std::path::Path>,
282 }
283
284 let current_dir = Arc::<std::path::Path>::from(current_dir.into_boxed_path());
285 let mut visited = BTreeSet::<Arc<std::path::Path>>::from_iter([root.clone()]);
286 let mut worklist = submodules
287 .iter()
288 .map(|sm| ModuleEntry {
289 name: sm.name.clone(),
290 namespace: namespace.clone(),
291 directory: current_dir.clone(),
292 parent: root.clone(),
293 })
294 .collect::<Vec<_>>();
295
296 while let Some(entry) = worklist.pop() {
297 let basename = entry.name.replace('-', "_");
298 let mod_dir = entry.directory.join(&basename);
299 let mod_file = mod_dir.with_extension("masm");
300 let mod_dir_mod_masm = mod_dir.join("mod.masm");
301
302 if mod_file.as_path() == &*entry.parent {
308 let span = entry.name.span();
309 let source_file = source_manager.get(span.source_id()).ok();
310 return Err(ParsingError::SelfReferentialSubmodule {
311 name: entry.name.clone(),
312 parent_module_uri: Uri::from(entry.parent),
313 span,
314 source_file,
315 }
316 .into());
317 }
318
319 let actual_path = if mod_file.is_file() {
320 if mod_dir_mod_masm.is_file() {
321 let span = entry.name.span();
322 let source_file = source_manager.get(span.source_id()).ok();
323 return Err(ParsingError::AmbiguousSubmoduleLocation {
324 name: entry.name,
325 first: Uri::from(mod_file),
326 second: Uri::from(mod_dir_mod_masm),
327 span,
328 source_file,
329 }
330 .into());
331 }
332 mod_file
333 } else if mod_dir_mod_masm.is_file() {
334 mod_dir_mod_masm
335 } else {
336 let span = entry.name.span();
337 let source_file = source_manager.get(span.source_id()).ok();
338 return Err(ParsingError::UndefinedSubmodule {
339 name: entry.name,
340 basename: basename.into_boxed_str(),
341 directory: Uri::from(mod_dir),
342 span,
343 source_file,
344 }
345 .into());
346 };
347
348 let actual_path = Arc::<std::path::Path>::from(actual_path);
349 if !visited.insert(actual_path.clone()) {
350 let span = entry.name.span();
351 let source_file = source_manager.get(span.source_id()).ok();
352 return Err(ParsingError::DuplicateSubmoduleSource {
353 name: entry.name,
354 module_uri: Uri::from(actual_path.as_ref()),
355 span,
356 source_file,
357 }
358 .into());
359 }
360
361 let mut parser = ModuleParser::new(Some(ast::ModuleKind::Library));
362 parser.set_warnings_as_errors(warnings_as_errors);
363 let module_path = Arc::<Path>::from(entry.namespace.join(&entry.name).into_boxed_path());
364 let ast = parser.parse_file(Some(&module_path), &actual_path, source_manager.clone())?;
365
366 let directory = Arc::<std::path::Path>::from(mod_dir);
367 worklist.extend(ast.submodules().iter().map(|sm| ModuleEntry {
368 name: sm.name.clone(),
369 namespace: module_path.clone(),
370 directory: directory.clone(),
371 parent: actual_path.clone(),
372 }));
373
374 callback(ast)?;
375 }
376
377 Ok(())
378}