miden_assembly/
compile.rs

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