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