miden_assembly_syntax/
parse.rs

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    Path, PathBuf,
13    ast::{Module, ModuleKind},
14    diagnostics::{IntoDiagnostic, NamedSource, Report, SourceCode, WrapErr, report},
15};
16
17// PARSE OPTIONS
18// ================================================================================================
19
20/// The set of options which can be used to control the behavior of the [`Parse`] trait.
21#[derive(Debug, Clone)]
22pub struct ParseOptions {
23    /// The kind of [Module] to parse.
24    ///
25    /// The default kind is executable.
26    pub kind: ModuleKind,
27    /// When true, promote warning diagnostics to errors
28    pub warnings_as_errors: bool,
29    /// The name to give the parsed [Module]
30    ///
31    /// This option overrides `namespace`.
32    ///
33    /// If unset, and there is no name associated with the item being parsed (e.g. a file path)
34    /// then the path will consist of just a namespace; using the value of `namespace` if provided,
35    /// or deriving one from `kind`.
36    pub path: Option<Arc<Path>>,
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    /// Configure a set of [`ParseOptions`] to parse a [`Module`] with the given `kind` and `path`.
50    ///
51    /// This is primarily useful when compiling a module from source code that has no meaningful
52    /// [Path] associated with it, such as when compiling from a `str`. This will override
53    /// the default name derived from the given [ModuleKind].
54    pub fn new(kind: ModuleKind, path: impl AsRef<Path>) -> Self {
55        Self {
56            kind,
57            path: Some(path.as_ref().into()),
58            ..Default::default()
59        }
60    }
61
62    /// Get the default [`ParseOptions`] for compiling a library module.
63    pub fn for_library() -> Self {
64        Self {
65            kind: ModuleKind::Library,
66            ..Default::default()
67        }
68    }
69
70    /// Get the default [`ParseOptions`] for compiling a kernel module.
71    pub fn for_kernel() -> Self {
72        Self {
73            kind: ModuleKind::Kernel,
74            ..Default::default()
75        }
76    }
77}
78
79// PARSE TRAIT
80// ================================================================================================
81
82/// This trait is meant to be implemented by any type that can be parsed to a [Module],
83/// to allow methods which expect a [Module] to accept things like:
84///
85/// * A [Module] which was previously parsed or deserialized
86/// * A string representing the source code of a [Module].
87/// * A path to a file containing the source code of a [Module].
88/// * A vector of [crate::ast::Form]s comprising the contents of a [Module].
89pub trait Parse: Sized {
90    /// Parse (or convert) `self` into an executable [Module].
91    ///
92    /// See [`Parse::parse_with_options`] for more details.
93    #[inline]
94    fn parse(self, source_manager: Arc<dyn SourceManager>) -> Result<Box<Module>, Report> {
95        self.parse_with_options(source_manager, ParseOptions::default())
96    }
97
98    /// Parse (or convert) `self` into a [Module] using the provided `options`.
99    ///
100    /// Returns a [Report] if parsing fails due to a parsing or semantic analysis error,
101    /// or if the module provided is of the wrong kind (e.g. we expected a library module but got
102    /// an executable module).
103    ///
104    /// See the documentation for [`ParseOptions`] to see how parsing can be configured.
105    fn parse_with_options(
106        self,
107        source_manager: Arc<dyn SourceManager>,
108        options: ParseOptions,
109    ) -> Result<Box<Module>, Report>;
110}
111
112// PARSE IMPLEMENTATIONS FOR MODULES
113// ------------------------------------------------------------------------------------------------
114
115impl Parse for Module {
116    #[inline(always)]
117    fn parse_with_options(
118        self,
119        source_manager: Arc<dyn SourceManager>,
120        options: ParseOptions,
121    ) -> Result<Box<Module>, Report> {
122        Box::new(self).parse_with_options(source_manager, options)
123    }
124}
125
126impl Parse for &Module {
127    #[inline(always)]
128    fn parse_with_options(
129        self,
130        source_manager: Arc<dyn SourceManager>,
131        options: ParseOptions,
132    ) -> Result<Box<Module>, Report> {
133        Box::new(self.clone()).parse_with_options(source_manager, options)
134    }
135}
136
137impl Parse for Box<Module> {
138    fn parse_with_options(
139        mut self,
140        _source_manager: Arc<dyn SourceManager>,
141        options: ParseOptions,
142    ) -> Result<Box<Module>, Report> {
143        let actual = self.kind();
144        if actual == options.kind {
145            if let Some(path) = options.path {
146                self.set_path(path);
147            }
148            Ok(self)
149        } else {
150            Err(report!(
151                "compilation failed: expected a {} module, but got a {actual} module",
152                options.kind
153            ))
154        }
155    }
156}
157
158impl Parse for Arc<Module> {
159    #[inline(always)]
160    fn parse_with_options(
161        self,
162        source_manager: Arc<dyn SourceManager>,
163        options: ParseOptions,
164    ) -> Result<Box<Module>, Report> {
165        Box::new(Arc::unwrap_or_clone(self)).parse_with_options(source_manager, options)
166    }
167}
168
169// PARSE IMPLEMENTATIONS FOR STRINGS
170// ------------------------------------------------------------------------------------------------
171
172impl Parse for Arc<SourceFile> {
173    fn parse_with_options(
174        self,
175        source_manager: Arc<dyn SourceManager>,
176        options: ParseOptions,
177    ) -> Result<Box<Module>, Report> {
178        let source_file = source_manager.copy_into(&self);
179        let path = match options.path {
180            Some(path) => path,
181            None => source_file
182                .uri()
183                .path()
184                .parse::<PathBuf>()
185                .map(|p| p.into())
186                .into_diagnostic()
187                .wrap_err("cannot parse module as it has an invalid path/name")?,
188        };
189        let mut parser = Module::parser(options.kind);
190        parser.set_warnings_as_errors(options.warnings_as_errors);
191        parser.parse(path, source_file, source_manager)
192    }
193}
194
195impl Parse for &str {
196    #[inline(always)]
197    fn parse_with_options(
198        self,
199        source_manager: Arc<dyn SourceManager>,
200        options: ParseOptions,
201    ) -> Result<Box<Module>, Report> {
202        self.to_string().into_boxed_str().parse_with_options(source_manager, options)
203    }
204}
205
206impl Parse for &String {
207    #[inline(always)]
208    fn parse_with_options(
209        self,
210        source_manager: Arc<dyn SourceManager>,
211        options: ParseOptions,
212    ) -> Result<Box<Module>, Report> {
213        self.clone().into_boxed_str().parse_with_options(source_manager, options)
214    }
215}
216
217impl Parse for String {
218    fn parse_with_options(
219        self,
220        source_manager: Arc<dyn SourceManager>,
221        options: ParseOptions,
222    ) -> Result<Box<Module>, Report> {
223        self.into_boxed_str().parse_with_options(source_manager, options)
224    }
225}
226
227impl Parse for Box<str> {
228    fn parse_with_options(
229        self,
230        source_manager: Arc<dyn SourceManager>,
231        options: ParseOptions,
232    ) -> Result<Box<Module>, Report> {
233        let path = options.path.as_deref().unwrap_or_else(|| match options.kind {
234            ModuleKind::Library => Path::new("nofile"),
235            ModuleKind::Executable => Path::exec_path(),
236            ModuleKind::Kernel => Path::kernel_path(),
237        });
238        let name = Uri::from(path.as_str().to_string().into_boxed_str());
239        let mut parser = Module::parser(options.kind);
240        parser.set_warnings_as_errors(options.warnings_as_errors);
241        let content = SourceContent::new(SourceLanguage::Masm, name.clone(), self);
242        let source_file = source_manager.load_from_raw_parts(name, content);
243        parser.parse(path, source_file, source_manager)
244    }
245}
246
247impl Parse for Cow<'_, str> {
248    #[inline(always)]
249    fn parse_with_options(
250        self,
251        source_manager: Arc<dyn SourceManager>,
252        options: ParseOptions,
253    ) -> Result<Box<Module>, Report> {
254        self.into_owned().into_boxed_str().parse_with_options(source_manager, options)
255    }
256}
257
258// PARSE IMPLEMENTATIONS FOR BYTES
259// ------------------------------------------------------------------------------------------------
260
261impl Parse for &[u8] {
262    #[inline]
263    fn parse_with_options(
264        self,
265        source_manager: Arc<dyn SourceManager>,
266        options: ParseOptions,
267    ) -> Result<Box<Module>, Report> {
268        core::str::from_utf8(self)
269            .map_err(|err| {
270                Report::from(crate::parser::ParsingError::from_utf8_error(Default::default(), err))
271                    .with_source_code(self.to_vec())
272            })
273            .wrap_err("parsing failed: invalid source code")
274            .and_then(|source| source.parse_with_options(source_manager, options))
275    }
276}
277
278impl Parse for Vec<u8> {
279    #[inline]
280    fn parse_with_options(
281        self,
282        source_manager: Arc<dyn SourceManager>,
283        options: ParseOptions,
284    ) -> Result<Box<Module>, Report> {
285        String::from_utf8(self)
286            .map_err(|err| {
287                let error = crate::parser::ParsingError::from_utf8_error(
288                    Default::default(),
289                    err.utf8_error(),
290                );
291                Report::from(error).with_source_code(err.into_bytes())
292            })
293            .wrap_err("parsing failed: invalid source code")
294            .and_then(|source| source.into_boxed_str().parse_with_options(source_manager, options))
295    }
296}
297impl Parse for Box<[u8]> {
298    #[inline(always)]
299    fn parse_with_options(
300        self,
301        source_manager: Arc<dyn SourceManager>,
302        options: ParseOptions,
303    ) -> Result<Box<Module>, Report> {
304        Vec::from(self).parse_with_options(source_manager, options)
305    }
306}
307
308impl<T> Parse for NamedSource<T>
309where
310    T: SourceCode + AsRef<[u8]>,
311{
312    fn parse_with_options(
313        self,
314        source_manager: Arc<dyn SourceManager>,
315        options: ParseOptions,
316    ) -> Result<Box<Module>, Report> {
317        let path = match options.path {
318            Some(path) => path,
319            None => self
320                .name()
321                .parse::<PathBuf>()
322                .map(|p| p.into())
323                .into_diagnostic()
324                .wrap_err("cannot parse module as it has an invalid path/name")?,
325        };
326        let content = core::str::from_utf8(self.inner().as_ref())
327            .map_err(|err| {
328                let error = crate::parser::ParsingError::from_utf8_error(Default::default(), err);
329                Report::from(error)
330            })
331            .wrap_err("parsing failed: expected source code to be valid utf-8")?;
332        let name = Uri::from(self.name());
333        let content = SourceContent::new(
334            SourceLanguage::Masm,
335            name.clone(),
336            content.to_string().into_boxed_str(),
337        );
338        let source_file = source_manager.load_from_raw_parts(name, content);
339        let mut parser = Module::parser(options.kind);
340        parser.set_warnings_as_errors(options.warnings_as_errors);
341        parser.parse(path, source_file, source_manager)
342    }
343}
344
345// PARSE IMPLEMENTATIONS FOR FILES
346// ------------------------------------------------------------------------------------------------
347
348#[cfg(feature = "std")]
349impl Parse for &std::path::Path {
350    fn parse_with_options(
351        self,
352        source_manager: Arc<dyn SourceManager>,
353        options: ParseOptions,
354    ) -> Result<Box<Module>, Report> {
355        use std::path::Component;
356
357        use miden_debug_types::SourceManagerExt;
358
359        use crate::{Path, PathError};
360
361        let path = match options.path {
362            Some(path) => path,
363            None => {
364                let mut buf = match options.kind {
365                    ModuleKind::Library => PathBuf::default(),
366                    ModuleKind::Executable => Path::exec_path().to_path_buf(),
367                    ModuleKind::Kernel => Path::kernel_path().to_path_buf(),
368                };
369                self.components()
370                    .skip_while(|component| {
371                        matches!(
372                            component,
373                            Component::Prefix(_)
374                                | Component::RootDir
375                                | Component::ParentDir
376                                | Component::CurDir
377                        )
378                    })
379                    .try_for_each(|component| {
380                        let part: &str = component
381                            .as_os_str()
382                            .to_str()
383                            .ok_or(PathError::InvalidUtf8)
384                            .and_then(|s| Path::validate(s).map(|_| s))
385                            .into_diagnostic()
386                            .wrap_err("invalid module path")?;
387                        buf.push(part);
388
389                        Ok::<(), Report>(())
390                    })?;
391
392                buf.into()
393            },
394        };
395        let source_file = source_manager
396            .load_file(self)
397            .into_diagnostic()
398            .wrap_err("source manager is unable to load file")?;
399        let mut parser = Module::parser(options.kind);
400        parser.parse(path, source_file, source_manager)
401    }
402}
403
404#[cfg(feature = "std")]
405impl Parse for std::path::PathBuf {
406    #[inline(always)]
407    fn parse_with_options(
408        self,
409        source_manager: Arc<dyn SourceManager>,
410        options: ParseOptions,
411    ) -> Result<Box<Module>, Report> {
412        self.as_path().parse_with_options(source_manager, options)
413    }
414}