use alloc::{
borrow::Cow,
boxed::Box,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use miden_debug_types::{SourceContent, SourceFile, SourceLanguage, SourceManager, Uri};
use crate::{
Path, PathBuf,
ast::{Module, ModuleKind},
diagnostics::{IntoDiagnostic, NamedSource, Report, SourceCode, WrapErr, report},
};
#[derive(Debug, Clone)]
pub struct ParseOptions {
pub kind: ModuleKind,
pub warnings_as_errors: bool,
pub path: Option<Arc<Path>>,
}
impl Default for ParseOptions {
fn default() -> Self {
Self {
kind: ModuleKind::Executable,
warnings_as_errors: false,
path: None,
}
}
}
impl ParseOptions {
pub fn new(kind: ModuleKind, path: impl AsRef<Path>) -> Self {
Self {
kind,
path: Some(path.as_ref().into()),
..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 Parse: Sized {
#[inline]
fn parse(self, source_manager: Arc<dyn SourceManager>) -> Result<Box<Module>, Report> {
self.parse_with_options(source_manager, ParseOptions::default())
}
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report>;
}
impl Parse for Module {
#[inline(always)]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
Box::new(self).parse_with_options(source_manager, options)
}
}
impl Parse for &Module {
#[inline(always)]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
Box::new(self.clone()).parse_with_options(source_manager, options)
}
}
impl Parse for Box<Module> {
fn parse_with_options(
mut self,
_source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> 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(report!(
"compilation failed: expected a {} module, but got a {actual} module",
options.kind
))
}
}
}
impl Parse for Arc<Module> {
#[inline(always)]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
Box::new(Arc::unwrap_or_clone(self)).parse_with_options(source_manager, options)
}
}
impl Parse for Arc<SourceFile> {
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
let source_file = source_manager.copy_into(&self);
let path = match options.path {
Some(path) => path,
None => source_file
.uri()
.path()
.parse::<PathBuf>()
.map(|p| p.into())
.into_diagnostic()
.wrap_err("cannot parse 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, source_manager)
}
}
impl Parse for &str {
#[inline(always)]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
self.to_string().into_boxed_str().parse_with_options(source_manager, options)
}
}
impl Parse for &String {
#[inline(always)]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
self.clone().into_boxed_str().parse_with_options(source_manager, options)
}
}
impl Parse for String {
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
self.into_boxed_str().parse_with_options(source_manager, options)
}
}
impl Parse for Box<str> {
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
let path = options.path.as_deref().unwrap_or_else(|| match options.kind {
ModuleKind::Library => Path::new("nofile"),
ModuleKind::Executable => Path::exec_path(),
ModuleKind::Kernel => Path::kernel_path(),
});
let name = Uri::from(path.as_str().to_string().into_boxed_str());
let mut parser = Module::parser(options.kind);
parser.set_warnings_as_errors(options.warnings_as_errors);
let content = SourceContent::new(SourceLanguage::Masm, name.clone(), self);
let source_file = source_manager.load_from_raw_parts(name, content);
parser.parse(path, source_file, source_manager)
}
}
impl Parse for Cow<'_, str> {
#[inline(always)]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
self.into_owned().into_boxed_str().parse_with_options(source_manager, options)
}
}
impl Parse for &[u8] {
#[inline]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> 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.parse_with_options(source_manager, options))
}
}
impl Parse for Vec<u8> {
#[inline]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> 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().parse_with_options(source_manager, options))
}
}
impl Parse for Box<[u8]> {
#[inline(always)]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
Vec::from(self).parse_with_options(source_manager, options)
}
}
impl<T> Parse for NamedSource<T>
where
T: SourceCode + AsRef<[u8]>,
{
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
let path = match options.path {
Some(path) => path,
None => self
.name()
.parse::<PathBuf>()
.map(|p| p.into())
.into_diagnostic()
.wrap_err("cannot parse 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 = Uri::from(self.name());
let content = SourceContent::new(
SourceLanguage::Masm,
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, source_manager)
}
}
#[cfg(feature = "std")]
impl Parse for &std::path::Path {
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
use std::path::Component;
use miden_debug_types::SourceManagerExt;
use crate::{Path, PathError};
let path = match options.path {
Some(path) => path,
None => {
let mut buf = match options.kind {
ModuleKind::Library => PathBuf::default(),
ModuleKind::Executable => Path::exec_path().to_path_buf(),
ModuleKind::Kernel => Path::kernel_path().to_path_buf(),
};
self.components()
.skip_while(|component| {
matches!(
component,
Component::Prefix(_)
| Component::RootDir
| Component::ParentDir
| Component::CurDir
)
})
.try_for_each(|component| {
let part: &str = component
.as_os_str()
.to_str()
.ok_or(PathError::InvalidUtf8)
.and_then(|s| Path::validate(s).map(|_| s))
.into_diagnostic()
.wrap_err("invalid module path")?;
buf.push(part);
Ok::<(), Report>(())
})?;
buf.into()
},
};
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.set_warnings_as_errors(options.warnings_as_errors);
parser.parse(path, source_file, source_manager)
}
}
#[cfg(feature = "std")]
impl Parse for std::path::PathBuf {
#[inline(always)]
fn parse_with_options(
self,
source_manager: Arc<dyn SourceManager>,
options: ParseOptions,
) -> Result<Box<Module>, Report> {
self.as_path().parse_with_options(source_manager, options)
}
}