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    ast::{Module, ModuleKind},
13    diagnostics::{IntoDiagnostic, NamedSource, Report, SourceCode, WrapErr, report},
14    library::{LibraryNamespace, LibraryPath},
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<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    /// 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    /// [LibraryPath] 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<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    /// Get the default [`ParseOptions`] for compiling a library module.
67    pub fn for_library() -> Self {
68        Self {
69            kind: ModuleKind::Library,
70            ..Default::default()
71        }
72    }
73
74    /// Get the default [`ParseOptions`] for compiling a kernel module.
75    pub fn for_kernel() -> Self {
76        Self {
77            kind: ModuleKind::Kernel,
78            ..Default::default()
79        }
80    }
81}
82
83// PARSE TRAIT
84// ================================================================================================
85
86/// This trait is meant to be implemented by any type that can be parsed to a [Module],
87/// to allow methods which expect a [Module] to accept things like:
88///
89/// * A [Module] which was previously parsed or deserialized
90/// * A string representing the source code of a [Module].
91/// * A path to a file containing the source code of a [Module].
92/// * A vector of [crate::ast::Form]s comprising the contents of a [Module].
93pub trait Parse: Sized {
94    /// Parse (or convert) `self` into an executable [Module].
95    ///
96    /// See [`Parse::parse_with_options`] for more details.
97    #[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    /// Parse (or convert) `self` into a [Module] using the provided `options`.
103    ///
104    /// Returns a [Report] if parsing fails due to a parsing or semantic analysis error,
105    /// or if the module provided is of the wrong kind (e.g. we expected a library module but got
106    /// an executable module).
107    ///
108    /// See the documentation for [`ParseOptions`] to see how parsing can be configured.
109    fn parse_with_options(
110        self,
111        source_manager: &dyn SourceManager,
112        options: ParseOptions,
113    ) -> Result<Box<Module>, Report>;
114}
115
116// PARSE IMPLEMENTATIONS FOR MODULES
117// ------------------------------------------------------------------------------------------------
118
119impl 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
173// PARSE IMPLEMENTATIONS FOR STRINGS
174// ------------------------------------------------------------------------------------------------
175
176impl 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
263// PARSE IMPLEMENTATIONS FOR BYTES
264// ------------------------------------------------------------------------------------------------
265
266impl 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// PARSE IMPLEMENTATIONS FOR FILES
350// ------------------------------------------------------------------------------------------------
351
352#[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}