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#[derive(Debug, Clone)]
25pub struct Options {
26 pub kind: ModuleKind,
30 pub warnings_as_errors: bool,
32 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 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 pub fn for_library() -> Self {
71 Self {
72 kind: ModuleKind::Library,
73 ..Default::default()
74 }
75 }
76
77 pub fn for_kernel() -> Self {
79 Self {
80 kind: ModuleKind::Kernel,
81 ..Default::default()
82 }
83 }
84}
85
86pub trait Compile: Sized {
97 #[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 fn compile_with_options(
113 self,
114 source_manager: &dyn SourceManager,
115 options: Options,
116 ) -> Result<Box<Module>, Report>;
117}
118
119impl 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
176impl 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
265impl 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#[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}