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