1use alloc::{
2 borrow::Cow,
3 boxed::Box,
4 string::{String, ToString},
5 sync::Arc,
6 vec::Vec,
7};
8
9use miden_debug_types::{SourceContent, SourceFile, SourceLanguage, SourceManager, Uri};
10
11use crate::{
12 ast::{Module, ModuleKind},
13 diagnostics::{IntoDiagnostic, NamedSource, Report, SourceCode, WrapErr, report},
14 library::{LibraryNamespace, LibraryPath},
15};
16
17#[derive(Debug, Clone)]
22pub struct ParseOptions {
23 pub kind: ModuleKind,
27 pub warnings_as_errors: bool,
29 pub path: Option<LibraryPath>,
37}
38
39impl Default for ParseOptions {
40 fn default() -> Self {
41 Self {
42 kind: ModuleKind::Executable,
43 warnings_as_errors: false,
44 path: None,
45 }
46 }
47}
48impl ParseOptions {
49 pub fn new<P, E>(kind: ModuleKind, path: P) -> Result<Self, E>
55 where
56 P: TryInto<LibraryPath, Error = E>,
57 {
58 let path = path.try_into()?;
59 Ok(Self {
60 kind,
61 path: Some(path),
62 ..Default::default()
63 })
64 }
65
66 pub fn for_library() -> Self {
68 Self {
69 kind: ModuleKind::Library,
70 ..Default::default()
71 }
72 }
73
74 pub fn for_kernel() -> Self {
76 Self {
77 kind: ModuleKind::Kernel,
78 ..Default::default()
79 }
80 }
81}
82
83pub trait Parse: Sized {
94 #[inline]
98 fn parse(self, source_manager: &dyn SourceManager) -> Result<Box<Module>, Report> {
99 self.parse_with_options(source_manager, ParseOptions::default())
100 }
101
102 fn parse_with_options(
110 self,
111 source_manager: &dyn SourceManager,
112 options: ParseOptions,
113 ) -> Result<Box<Module>, Report>;
114}
115
116impl Parse for Module {
120 #[inline(always)]
121 fn parse_with_options(
122 self,
123 source_manager: &dyn SourceManager,
124 options: ParseOptions,
125 ) -> Result<Box<Module>, Report> {
126 Box::new(self).parse_with_options(source_manager, options)
127 }
128}
129
130impl Parse for &Module {
131 #[inline(always)]
132 fn parse_with_options(
133 self,
134 source_manager: &dyn SourceManager,
135 options: ParseOptions,
136 ) -> Result<Box<Module>, Report> {
137 Box::new(self.clone()).parse_with_options(source_manager, options)
138 }
139}
140
141impl Parse for Box<Module> {
142 fn parse_with_options(
143 mut self,
144 _source_manager: &dyn SourceManager,
145 options: ParseOptions,
146 ) -> Result<Box<Module>, Report> {
147 let actual = self.kind();
148 if actual == options.kind {
149 if let Some(path) = options.path {
150 self.set_path(path);
151 }
152 Ok(self)
153 } else {
154 Err(report!(
155 "compilation failed: expected a {} module, but got a {actual} module",
156 options.kind
157 ))
158 }
159 }
160}
161
162impl Parse for Arc<Module> {
163 #[inline(always)]
164 fn parse_with_options(
165 self,
166 source_manager: &dyn SourceManager,
167 options: ParseOptions,
168 ) -> Result<Box<Module>, Report> {
169 Box::new(Arc::unwrap_or_clone(self)).parse_with_options(source_manager, options)
170 }
171}
172
173impl Parse for Arc<SourceFile> {
177 fn parse_with_options(
178 self,
179 source_manager: &dyn SourceManager,
180 options: ParseOptions,
181 ) -> Result<Box<Module>, Report> {
182 let source_file = source_manager.copy_into(&self);
183 let path = match options.path {
184 Some(path) => path,
185 None => source_file
186 .uri()
187 .path()
188 .parse::<LibraryPath>()
189 .into_diagnostic()
190 .wrap_err("cannot parse module as it has an invalid path/name")?,
191 };
192 let mut parser = Module::parser(options.kind);
193 parser.set_warnings_as_errors(options.warnings_as_errors);
194 parser.parse(path, source_file)
195 }
196}
197
198impl Parse for &str {
199 #[inline(always)]
200 fn parse_with_options(
201 self,
202 source_manager: &dyn SourceManager,
203 options: ParseOptions,
204 ) -> Result<Box<Module>, Report> {
205 self.to_string().into_boxed_str().parse_with_options(source_manager, options)
206 }
207}
208
209impl Parse for &String {
210 #[inline(always)]
211 fn parse_with_options(
212 self,
213 source_manager: &dyn SourceManager,
214 options: ParseOptions,
215 ) -> Result<Box<Module>, Report> {
216 self.clone().into_boxed_str().parse_with_options(source_manager, options)
217 }
218}
219
220impl Parse for String {
221 fn parse_with_options(
222 self,
223 source_manager: &dyn SourceManager,
224 options: ParseOptions,
225 ) -> Result<Box<Module>, Report> {
226 self.into_boxed_str().parse_with_options(source_manager, options)
227 }
228}
229
230impl Parse for Box<str> {
231 fn parse_with_options(
232 self,
233 source_manager: &dyn SourceManager,
234 options: ParseOptions,
235 ) -> Result<Box<Module>, Report> {
236 let path = options.path.unwrap_or_else(|| {
237 LibraryPath::from(match options.kind {
238 ModuleKind::Library => LibraryNamespace::Anon,
239 ModuleKind::Executable => LibraryNamespace::Exec,
240 ModuleKind::Kernel => LibraryNamespace::Kernel,
241 })
242 });
243 let name = Uri::from(path.path().into_owned().into_boxed_str());
244 let mut parser = Module::parser(options.kind);
245 parser.set_warnings_as_errors(options.warnings_as_errors);
246 let content = SourceContent::new(SourceLanguage::Masm, name.clone(), self);
247 let source_file = source_manager.load_from_raw_parts(name, content);
248 parser.parse(path, source_file)
249 }
250}
251
252impl Parse for Cow<'_, str> {
253 #[inline(always)]
254 fn parse_with_options(
255 self,
256 source_manager: &dyn SourceManager,
257 options: ParseOptions,
258 ) -> Result<Box<Module>, Report> {
259 self.into_owned().into_boxed_str().parse_with_options(source_manager, options)
260 }
261}
262
263impl Parse for &[u8] {
267 #[inline]
268 fn parse_with_options(
269 self,
270 source_manager: &dyn SourceManager,
271 options: ParseOptions,
272 ) -> Result<Box<Module>, Report> {
273 core::str::from_utf8(self)
274 .map_err(|err| {
275 Report::from(crate::parser::ParsingError::from_utf8_error(Default::default(), err))
276 .with_source_code(self.to_vec())
277 })
278 .wrap_err("parsing failed: invalid source code")
279 .and_then(|source| source.parse_with_options(source_manager, options))
280 }
281}
282
283impl Parse for Vec<u8> {
284 #[inline]
285 fn parse_with_options(
286 self,
287 source_manager: &dyn SourceManager,
288 options: ParseOptions,
289 ) -> Result<Box<Module>, Report> {
290 String::from_utf8(self)
291 .map_err(|err| {
292 let error = crate::parser::ParsingError::from_utf8_error(
293 Default::default(),
294 err.utf8_error(),
295 );
296 Report::from(error).with_source_code(err.into_bytes())
297 })
298 .wrap_err("parsing failed: invalid source code")
299 .and_then(|source| source.into_boxed_str().parse_with_options(source_manager, options))
300 }
301}
302impl Parse for Box<[u8]> {
303 #[inline(always)]
304 fn parse_with_options(
305 self,
306 source_manager: &dyn SourceManager,
307 options: ParseOptions,
308 ) -> Result<Box<Module>, Report> {
309 Vec::from(self).parse_with_options(source_manager, options)
310 }
311}
312
313impl<T> Parse for NamedSource<T>
314where
315 T: SourceCode + AsRef<[u8]>,
316{
317 fn parse_with_options(
318 self,
319 source_manager: &dyn SourceManager,
320 options: ParseOptions,
321 ) -> Result<Box<Module>, Report> {
322 let path = match options.path {
323 Some(path) => path,
324 None => self
325 .name()
326 .parse::<LibraryPath>()
327 .into_diagnostic()
328 .wrap_err("cannot parse module as it has an invalid path/name")?,
329 };
330 let content = core::str::from_utf8(self.inner().as_ref())
331 .map_err(|err| {
332 let error = crate::parser::ParsingError::from_utf8_error(Default::default(), err);
333 Report::from(error)
334 })
335 .wrap_err("parsing failed: expected source code to be valid utf-8")?;
336 let name = Uri::from(self.name());
337 let content = SourceContent::new(
338 SourceLanguage::Masm,
339 name.clone(),
340 content.to_string().into_boxed_str(),
341 );
342 let source_file = source_manager.load_from_raw_parts(name, content);
343 let mut parser = Module::parser(options.kind);
344 parser.set_warnings_as_errors(options.warnings_as_errors);
345 parser.parse(path, source_file)
346 }
347}
348
349#[cfg(feature = "std")]
353impl Parse for &std::path::Path {
354 fn parse_with_options(
355 self,
356 source_manager: &dyn SourceManager,
357 options: ParseOptions,
358 ) -> Result<Box<Module>, Report> {
359 use std::path::Component;
360
361 use miden_debug_types::SourceManagerExt;
362
363 use crate::{ast::Ident, library::PathError};
364
365 let path = match options.path {
366 Some(path) => path,
367 None => {
368 let ns = match options.kind {
369 ModuleKind::Library => LibraryNamespace::Anon,
370 ModuleKind::Executable => LibraryNamespace::Exec,
371 ModuleKind::Kernel => LibraryNamespace::Kernel,
372 };
373 let mut parts = Vec::default();
374 self.components()
375 .skip_while(|component| {
376 matches!(
377 component,
378 Component::Prefix(_)
379 | Component::RootDir
380 | Component::ParentDir
381 | Component::CurDir
382 )
383 })
384 .try_for_each(|component| {
385 let part = component
386 .as_os_str()
387 .to_str()
388 .ok_or(PathError::InvalidUtf8)
389 .and_then(|s| Ident::new(s).map_err(PathError::InvalidComponent))
390 .into_diagnostic()
391 .wrap_err("invalid module path")?;
392 parts.push(part);
393
394 Ok::<(), Report>(())
395 })?;
396 LibraryPath::new_from_components(ns, parts)
397 },
398 };
399 let source_file = source_manager
400 .load_file(self)
401 .into_diagnostic()
402 .wrap_err("source manager is unable to load file")?;
403 let mut parser = Module::parser(options.kind);
404 parser.parse(path, source_file)
405 }
406}
407
408#[cfg(feature = "std")]
409impl Parse for std::path::PathBuf {
410 #[inline(always)]
411 fn parse_with_options(
412 self,
413 source_manager: &dyn SourceManager,
414 options: ParseOptions,
415 ) -> Result<Box<Module>, Report> {
416 self.as_path().parse_with_options(source_manager, options)
417 }
418}