1macro_rules! span {
3 ($id:expr, $l:expr, $r:expr) => {
4 ::miden_debug_types::SourceSpan::new($id, $l..$r)
5 };
6 ($id:expr, $i:expr) => {
7 ::miden_debug_types::SourceSpan::at($id, $i)
8 };
9}
10
11lalrpop_util::lalrpop_mod!(
12 #[expect(clippy::all)]
13 grammar,
14 "/parser/grammar.rs"
15);
16
17mod error;
18mod lexer;
19mod scanner;
20mod token;
21
22use alloc::{boxed::Box, collections::BTreeSet, string::ToString, sync::Arc, vec::Vec};
23
24use miden_debug_types::{SourceFile, SourceLanguage, SourceManager, Uri};
25use miden_utils_diagnostics::Report;
26
27pub use self::{
28 error::{BinErrorKind, HexErrorKind, LiteralErrorKind, ParsingError},
29 lexer::Lexer,
30 scanner::Scanner,
31 token::{BinEncodedValue, DocumentationType, IntValue, PushValue, Token, WordValue},
32};
33use crate::{Path, ast, sema};
34
35type ParseError<'a> = lalrpop_util::ParseError<u32, Token<'a>, ParsingError>;
39
40#[derive(Default)]
46pub struct ModuleParser {
47 kind: ast::ModuleKind,
52 interned: BTreeSet<Arc<str>>,
69 warnings_as_errors: bool,
71}
72
73impl ModuleParser {
74 pub fn new(kind: ast::ModuleKind) -> Self {
76 Self {
77 kind,
78 interned: Default::default(),
79 warnings_as_errors: false,
80 }
81 }
82
83 pub fn set_warnings_as_errors(&mut self, yes: bool) {
85 self.warnings_as_errors = yes;
86 }
87
88 pub fn parse(
90 &mut self,
91 path: impl AsRef<Path>,
92 source: Arc<SourceFile>,
93 source_manager: Arc<dyn SourceManager>,
94 ) -> Result<Box<ast::Module>, Report> {
95 let path = path.as_ref();
96 let forms = parse_forms_internal(source.clone(), &mut self.interned)
97 .map_err(|err| Report::new(err).with_source_code(source.clone()))?;
98 sema::analyze(source, self.kind, path, forms, self.warnings_as_errors, source_manager)
99 .map_err(Report::new)
100 }
101
102 #[cfg(feature = "std")]
104 pub fn parse_file<N, P>(
105 &mut self,
106 name: N,
107 path: P,
108 source_manager: Arc<dyn SourceManager>,
109 ) -> Result<Box<ast::Module>, Report>
110 where
111 N: AsRef<Path>,
112 P: AsRef<std::path::Path>,
113 {
114 use miden_debug_types::SourceManagerExt;
115 use miden_utils_diagnostics::{IntoDiagnostic, WrapErr};
116
117 let path = path.as_ref();
118 let source_file = source_manager
119 .load_file(path)
120 .into_diagnostic()
121 .wrap_err_with(|| format!("failed to load source file from '{}'", path.display()))?;
122 self.parse(name, source_file, source_manager)
123 }
124
125 pub fn parse_str(
127 &mut self,
128 name: impl AsRef<Path>,
129 source: impl ToString,
130 source_manager: Arc<dyn SourceManager>,
131 ) -> Result<Box<ast::Module>, Report> {
132 use miden_debug_types::SourceContent;
133
134 let name = name.as_ref();
135 let uri = Uri::from(name.as_str().to_string().into_boxed_str());
136 let content = SourceContent::new(
137 SourceLanguage::Masm,
138 uri.clone(),
139 source.to_string().into_boxed_str(),
140 );
141 let source_file = source_manager.load_from_raw_parts(uri, content);
142 self.parse(name, source_file, source_manager)
143 }
144}
145
146#[cfg(any(test, feature = "testing"))]
151pub fn parse_forms(source: Arc<SourceFile>) -> Result<Vec<ast::Form>, ParsingError> {
152 let mut interned = BTreeSet::default();
153 parse_forms_internal(source, &mut interned)
154}
155
156fn parse_forms_internal(
161 source: Arc<SourceFile>,
162 interned: &mut BTreeSet<Arc<str>>,
163) -> Result<Vec<ast::Form>, ParsingError> {
164 let source_id = source.id();
165 let scanner = Scanner::new(source.as_str());
166 let lexer = Lexer::new(source_id, scanner);
167 let felt_type = Arc::new(ast::types::ArrayType::new(ast::types::Type::Felt, 4));
168 grammar::FormsParser::new()
169 .parse(source_id, interned, &felt_type, core::marker::PhantomData, lexer)
170 .map_err(|err| ParsingError::from_parse_error(source_id, err))
171}
172
173#[cfg(feature = "std")]
184pub fn read_modules_from_dir(
185 dir: impl AsRef<std::path::Path>,
186 namespace: impl AsRef<Path>,
187 source_manager: Arc<dyn SourceManager>,
188 warnings_as_errors: bool,
189) -> Result<impl Iterator<Item = Box<ast::Module>>, Report> {
190 use std::collections::{BTreeMap, btree_map::Entry};
191
192 use miden_utils_diagnostics::{IntoDiagnostic, WrapErr, report};
193 use module_walker::{ModuleEntry, WalkModules};
194
195 let dir = dir.as_ref();
196 if !dir.is_dir() {
197 return Err(report!("the provided path '{}' is not a valid directory", dir.display()));
198 }
199
200 if dir.join(ast::Module::ROOT_FILENAME).exists() {
202 return Err(report!("{} is not allowed in the root directory", ast::Module::ROOT_FILENAME));
203 }
204
205 let mut modules = BTreeMap::default();
206
207 let walker = WalkModules::new(namespace.as_ref().to_path_buf(), dir)
208 .into_diagnostic()
209 .wrap_err_with(|| format!("failed to load modules from '{}'", dir.display()))?;
210 for entry in walker {
211 let ModuleEntry { mut name, source_path } = entry?;
212 if name.last().unwrap() == ast::Module::ROOT {
213 name.pop();
214 }
215
216 let mut parser = ModuleParser::new(ast::ModuleKind::Library);
218 parser.set_warnings_as_errors(warnings_as_errors);
219 let ast = parser.parse_file(&name, &source_path, source_manager.clone())?;
220 match modules.entry(name) {
221 Entry::Occupied(ref entry) => {
222 return Err(report!("duplicate module '{0}'", entry.key().clone()));
223 },
224 Entry::Vacant(entry) => {
225 entry.insert(ast);
226 },
227 }
228 }
229
230 Ok(modules.into_values())
231}
232
233#[cfg(feature = "std")]
234mod module_walker {
235 use std::{
236 ffi::OsStr,
237 fs::{self, DirEntry, FileType},
238 io,
239 path::{Path, PathBuf},
240 };
241
242 use miden_utils_diagnostics::{IntoDiagnostic, Report, report};
243
244 use crate::{Path as LibraryPath, PathBuf as LibraryPathBuf, ast::Module};
245
246 pub struct ModuleEntry {
247 pub name: LibraryPathBuf,
248 pub source_path: PathBuf,
249 }
250
251 pub struct WalkModules<'a> {
252 namespace: LibraryPathBuf,
253 root: &'a Path,
254 stack: alloc::collections::VecDeque<io::Result<DirEntry>>,
255 }
256
257 impl<'a> WalkModules<'a> {
258 pub fn new(namespace: LibraryPathBuf, path: &'a Path) -> io::Result<Self> {
259 use alloc::collections::VecDeque;
260
261 let stack = VecDeque::from_iter(fs::read_dir(path)?);
262
263 Ok(Self { namespace, root: path, stack })
264 }
265
266 fn next_entry(
267 &mut self,
268 entry: &DirEntry,
269 ty: &FileType,
270 ) -> Result<Option<ModuleEntry>, Report> {
271 if ty.is_dir() {
272 let dir = entry.path();
273 self.stack.extend(fs::read_dir(dir).into_diagnostic()?);
274 return Ok(None);
275 }
276
277 let mut file_path = entry.path();
278 let is_module = file_path
279 .extension()
280 .map(|ext| ext == AsRef::<OsStr>::as_ref(Module::FILE_EXTENSION))
281 .unwrap_or(false);
282 if !is_module {
283 return Ok(None);
284 }
285
286 file_path.set_extension("");
288 if file_path.is_dir() {
289 return Err(report!(
290 "file and directory with same name are not allowed: {}",
291 file_path.display()
292 ));
293 }
294 let relative_path = file_path
295 .strip_prefix(self.root)
296 .expect("expected path to be a child of the root directory");
297
298 let mut libpath = self.namespace.clone();
300 for component in relative_path.iter() {
301 let component = component.to_str().ok_or_else(|| {
302 let p = entry.path();
303 report!("{} is an invalid directory entry", p.display())
304 })?;
305 LibraryPath::validate(component).into_diagnostic()?;
306 libpath.push(component);
307 }
308 Ok(Some(ModuleEntry { name: libpath, source_path: entry.path() }))
309 }
310 }
311
312 impl Iterator for WalkModules<'_> {
313 type Item = Result<ModuleEntry, Report>;
314
315 fn next(&mut self) -> Option<Self::Item> {
316 loop {
317 let entry = self
318 .stack
319 .pop_front()?
320 .and_then(|entry| entry.file_type().map(|ft| (entry, ft)))
321 .into_diagnostic();
322
323 match entry {
324 Ok((ref entry, ref file_type)) => {
325 match self.next_entry(entry, file_type).transpose() {
326 None => continue,
327 result => break result,
328 }
329 },
330 Err(err) => break Some(Err(err)),
331 }
332 }
333 }
334 }
335}
336
337#[cfg(test)]
341mod tests {
342 use miden_core::assert_matches;
343 use miden_debug_types::SourceId;
344
345 use super::*;
346
347 #[test]
349 fn lex_exp() {
350 let source_id = SourceId::default();
351 let scanner = Scanner::new("begin exp.u9 end");
352 let mut lexer = Lexer::new(source_id, scanner).map(|result| result.map(|(_, t, _)| t));
353 assert_matches!(lexer.next(), Some(Ok(Token::Begin)));
354 assert_matches!(lexer.next(), Some(Ok(Token::ExpU)));
355 assert_matches!(lexer.next(), Some(Ok(Token::Int(n))) if n == 9);
356 assert_matches!(lexer.next(), Some(Ok(Token::End)));
357 }
358
359 #[test]
360 fn lex_block() {
361 let source_id = SourceId::default();
362 let scanner = Scanner::new(
363 "\
364const ERR1 = 1
365
366begin
367 u32assertw
368 u32assertw.err=ERR1
369 u32assertw.err=2
370end
371",
372 );
373 let mut lexer = Lexer::new(source_id, scanner).map(|result| result.map(|(_, t, _)| t));
374 assert_matches!(lexer.next(), Some(Ok(Token::Const)));
375 assert_matches!(lexer.next(), Some(Ok(Token::ConstantIdent("ERR1"))));
376 assert_matches!(lexer.next(), Some(Ok(Token::Equal)));
377 assert_matches!(lexer.next(), Some(Ok(Token::Int(1))));
378 assert_matches!(lexer.next(), Some(Ok(Token::Begin)));
379 assert_matches!(lexer.next(), Some(Ok(Token::U32Assertw)));
380 assert_matches!(lexer.next(), Some(Ok(Token::U32Assertw)));
381 assert_matches!(lexer.next(), Some(Ok(Token::Dot)));
382 assert_matches!(lexer.next(), Some(Ok(Token::Err)));
383 assert_matches!(lexer.next(), Some(Ok(Token::Equal)));
384 assert_matches!(lexer.next(), Some(Ok(Token::ConstantIdent("ERR1"))));
385 assert_matches!(lexer.next(), Some(Ok(Token::U32Assertw)));
386 assert_matches!(lexer.next(), Some(Ok(Token::Dot)));
387 assert_matches!(lexer.next(), Some(Ok(Token::Err)));
388 assert_matches!(lexer.next(), Some(Ok(Token::Equal)));
389 assert_matches!(lexer.next(), Some(Ok(Token::Int(2))));
390 assert_matches!(lexer.next(), Some(Ok(Token::End)));
391 assert_matches!(lexer.next(), Some(Ok(Token::Eof)));
392 }
393
394 #[test]
395 fn lex_emit() {
396 let source_id = SourceId::default();
397 let scanner = Scanner::new(
398 "\
399begin
400 push.1
401 emit.event(\"abc\")
402end
403",
404 );
405 let mut lexer = Lexer::new(source_id, scanner).map(|result| result.map(|(_, t, _)| t));
406 assert_matches!(lexer.next(), Some(Ok(Token::Begin)));
407 assert_matches!(lexer.next(), Some(Ok(Token::Push)));
408 assert_matches!(lexer.next(), Some(Ok(Token::Dot)));
409 assert_matches!(lexer.next(), Some(Ok(Token::Int(1))));
410 assert_matches!(lexer.next(), Some(Ok(Token::Emit)));
411 assert_matches!(lexer.next(), Some(Ok(Token::Dot)));
412 assert_matches!(lexer.next(), Some(Ok(Token::Event)));
413 assert_matches!(lexer.next(), Some(Ok(Token::Lparen)));
414 assert_matches!(lexer.next(), Some(Ok(Token::QuotedIdent("abc"))));
415 assert_matches!(lexer.next(), Some(Ok(Token::Rparen)));
416 assert_matches!(lexer.next(), Some(Ok(Token::End)));
417 assert_matches!(lexer.next(), Some(Ok(Token::Eof)));
418 }
419}