use alloc::{
borrow::Cow,
boxed::Box,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use miette::miette;
use crate::{
ast::{Module, ModuleKind},
diagnostics::{
IntoDiagnostic, NamedSource, Report, SourceCode, SourceContent, SourceFile, SourceManager,
WrapErr,
},
library::{LibraryNamespace, LibraryPath},
};
#[derive(Debug, Clone)]
pub struct Options {
pub kind: ModuleKind,
pub warnings_as_errors: bool,
pub path: Option<LibraryPath>,
}
impl Default for Options {
fn default() -> Self {
Self {
kind: ModuleKind::Executable,
warnings_as_errors: false,
path: None,
}
}
}
impl Options {
pub fn new<P, E>(kind: ModuleKind, path: P) -> Result<Self, E>
where
P: TryInto<LibraryPath, Error = E>,
{
let path = path.try_into()?;
Ok(Self {
kind,
path: Some(path),
..Default::default()
})
}
pub fn for_library() -> Self {
Self {
kind: ModuleKind::Library,
..Default::default()
}
}
pub fn for_kernel() -> Self {
Self {
kind: ModuleKind::Kernel,
..Default::default()
}
}
}
pub trait Compile: Sized {
#[inline]
fn compile(self, source_manager: &dyn SourceManager) -> Result<Box<Module>, Report> {
self.compile_with_options(source_manager, Options::default())
}
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report>;
}
impl Compile for Module {
#[inline(always)]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
Box::new(self).compile_with_options(source_manager, options)
}
}
impl<'a> Compile for &'a Module {
#[inline(always)]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
Box::new(self.clone()).compile_with_options(source_manager, options)
}
}
impl Compile for Box<Module> {
fn compile_with_options(
mut self,
_source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
let actual = self.kind();
if actual == options.kind {
if let Some(path) = options.path {
self.set_path(path);
}
Ok(self)
} else {
Err(miette!(
"compilation failed: expected a {} module, but got a {actual} module",
options.kind
))
}
}
}
impl Compile for Arc<Module> {
#[inline(always)]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
Box::new(Arc::unwrap_or_clone(self)).compile_with_options(source_manager, options)
}
}
impl Compile for Arc<SourceFile> {
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
let source_file = source_manager.copy_into(&self);
let path = match options.path {
Some(path) => path,
None => source_file
.name()
.parse::<LibraryPath>()
.into_diagnostic()
.wrap_err("cannot compile module as it has an invalid path/name")?,
};
let mut parser = Module::parser(options.kind);
parser.set_warnings_as_errors(options.warnings_as_errors);
parser.parse(path, source_file)
}
}
impl<'a> Compile for &'a str {
#[inline(always)]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
self.to_string().into_boxed_str().compile_with_options(source_manager, options)
}
}
impl<'a> Compile for &'a String {
#[inline(always)]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
self.clone().into_boxed_str().compile_with_options(source_manager, options)
}
}
impl Compile for String {
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
self.into_boxed_str().compile_with_options(source_manager, options)
}
}
impl Compile for Box<str> {
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
let path = options.path.unwrap_or_else(|| {
LibraryPath::from(match options.kind {
ModuleKind::Library => LibraryNamespace::Anon,
ModuleKind::Executable => LibraryNamespace::Exec,
ModuleKind::Kernel => LibraryNamespace::Kernel,
})
});
let name = Arc::<str>::from(path.path().into_owned().into_boxed_str());
let mut parser = Module::parser(options.kind);
parser.set_warnings_as_errors(options.warnings_as_errors);
let content = SourceContent::new(name.clone(), self);
let source_file = source_manager.load_from_raw_parts(name, content);
parser.parse(path, source_file)
}
}
impl<'a> Compile for Cow<'a, str> {
#[inline(always)]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
self.into_owned().into_boxed_str().compile_with_options(source_manager, options)
}
}
impl<'a> Compile for &'a [u8] {
#[inline]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
core::str::from_utf8(self)
.map_err(|err| {
Report::from(crate::parser::ParsingError::from_utf8_error(Default::default(), err))
.with_source_code(self.to_vec())
})
.wrap_err("parsing failed: invalid source code")
.and_then(|source| source.compile_with_options(source_manager, options))
}
}
impl Compile for Vec<u8> {
#[inline]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
String::from_utf8(self)
.map_err(|err| {
let error = crate::parser::ParsingError::from_utf8_error(
Default::default(),
err.utf8_error(),
);
Report::from(error).with_source_code(err.into_bytes())
})
.wrap_err("parsing failed: invalid source code")
.and_then(|source| {
source.into_boxed_str().compile_with_options(source_manager, options)
})
}
}
impl Compile for Box<[u8]> {
#[inline(always)]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
Vec::from(self).compile_with_options(source_manager, options)
}
}
impl<T> Compile for NamedSource<T>
where
T: SourceCode + AsRef<[u8]>,
{
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
let path = match options.path {
Some(path) => path,
None => self
.name()
.parse::<LibraryPath>()
.into_diagnostic()
.wrap_err("cannot compile module as it has an invalid path/name")?,
};
let content = core::str::from_utf8(self.inner().as_ref())
.map_err(|err| {
let error = crate::parser::ParsingError::from_utf8_error(Default::default(), err);
Report::from(error)
})
.wrap_err("parsing failed: expected source code to be valid utf-8")?;
let name = Arc::<str>::from(self.name());
let content = SourceContent::new(name.clone(), content.to_string().into_boxed_str());
let source_file = source_manager.load_from_raw_parts(name, content);
let mut parser = Module::parser(options.kind);
parser.set_warnings_as_errors(options.warnings_as_errors);
parser.parse(path, source_file)
}
}
#[cfg(feature = "std")]
impl<'a> Compile for &'a std::path::Path {
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
use std::path::Component;
use vm_core::debuginfo::SourceManagerExt;
use crate::{ast::Ident, library::PathError};
let path = match options.path {
Some(path) => path,
None => {
let ns = match options.kind {
ModuleKind::Library => LibraryNamespace::Anon,
ModuleKind::Executable => LibraryNamespace::Exec,
ModuleKind::Kernel => LibraryNamespace::Kernel,
};
let mut parts = Vec::default();
self.components()
.skip_while(|component| {
matches!(
component,
Component::Prefix(_)
| Component::RootDir
| Component::ParentDir
| Component::CurDir
)
})
.try_for_each(|component| {
let part = component
.as_os_str()
.to_str()
.ok_or(PathError::InvalidUtf8)
.and_then(|s| Ident::new(s).map_err(PathError::InvalidComponent))
.into_diagnostic()
.wrap_err("invalid module path")?;
parts.push(part);
Ok::<(), Report>(())
})?;
LibraryPath::new_from_components(ns, parts)
},
};
let source_file = source_manager
.load_file(self)
.into_diagnostic()
.wrap_err("source manager is unable to load file")?;
let mut parser = Module::parser(options.kind);
parser.parse(path, source_file)
}
}
#[cfg(feature = "std")]
impl Compile for std::path::PathBuf {
#[inline(always)]
fn compile_with_options(
self,
source_manager: &dyn SourceManager,
options: Options,
) -> Result<Box<Module>, Report> {
self.as_path().compile_with_options(source_manager, options)
}
}