pub mod expand;
pub mod include;
pub mod macro_def;
pub mod source_map;
use {
crate::errors::CompileError,
source_map::{FileRegistry, SourceMap, SourceOrigin},
std::path::PathBuf,
};
#[derive(Debug, Clone)]
pub(crate) struct SourceLine {
pub text: String,
pub origin: SourceOrigin,
}
pub trait FileResolver {
fn resolve(&self, path: &str, relative_to: &str) -> Result<String, std::io::Error>;
}
#[derive(Debug, Clone)]
pub struct FsFileResolver {
pub include_paths: Vec<PathBuf>,
}
impl FsFileResolver {
pub fn new() -> Self {
Self {
include_paths: Vec::new(),
}
}
pub fn with_include_paths(include_paths: Vec<PathBuf>) -> Self {
Self { include_paths }
}
}
impl Default for FsFileResolver {
fn default() -> Self {
Self::new()
}
}
impl FileResolver for FsFileResolver {
fn resolve(&self, path: &str, relative_to: &str) -> Result<String, std::io::Error> {
let base_dir = std::path::Path::new(relative_to)
.parent()
.unwrap_or(std::path::Path::new("."));
let candidate = base_dir.join(path);
if candidate.exists() {
return std::fs::read_to_string(&candidate);
}
for include_dir in &self.include_paths {
let candidate = include_dir.join(path);
if candidate.exists() {
return std::fs::read_to_string(&candidate);
}
}
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("file not found: {}", path),
))
}
}
#[derive(Debug, Clone, Default)]
pub struct MockFileResolver {
files: std::collections::HashMap<String, String>,
}
impl MockFileResolver {
pub fn new() -> Self {
Self::default()
}
pub fn add_file(&mut self, path: &str, content: &str) -> &mut Self {
self.files.insert(path.to_string(), content.to_string());
self
}
}
impl FileResolver for MockFileResolver {
fn resolve(&self, path: &str, _relative_to: &str) -> Result<String, std::io::Error> {
self.files.get(path).cloned().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("file not found: {}", path),
)
})
}
}
pub struct PreprocessResult {
pub expanded_source: String,
pub source_map: SourceMap,
}
#[derive(Debug)]
pub struct PreprocessorError {
pub error: CompileError,
pub origin: Option<SourceOrigin>,
}
pub struct PreprocessFailure {
pub errors: Vec<PreprocessorError>,
pub file_registry: FileRegistry,
}
pub fn preprocess(
source: &str,
source_path: &str,
resolver: Option<&dyn FileResolver>,
) -> Result<PreprocessResult, PreprocessFailure> {
let mut registry = FileRegistry::new();
let lines = match include::resolve_includes(source, source_path, resolver, &mut registry) {
Ok(lines) => lines,
Err(errors) => {
return Err(PreprocessFailure {
errors: errors
.into_iter()
.map(|e| PreprocessorError {
error: e,
origin: None,
})
.collect(),
file_registry: registry,
});
}
};
let (expanded_lines, errors) = match expand::expand_macros(lines) {
Ok(result) => result,
Err(errors) => {
return Err(PreprocessFailure {
errors: errors
.into_iter()
.map(|e| PreprocessorError {
error: e.error,
origin: e.origin,
})
.collect(),
file_registry: registry,
});
}
};
if !errors.is_empty() {
return Err(PreprocessFailure {
errors: errors
.into_iter()
.map(|e| PreprocessorError {
error: e.error,
origin: e.origin,
})
.collect(),
file_registry: registry,
});
}
let mut expanded_source = String::new();
let mut line_origins = Vec::with_capacity(expanded_lines.len());
for line in &expanded_lines {
expanded_source.push_str(&line.text);
expanded_source.push('\n');
line_origins.push(line.origin.clone());
}
let source_map = SourceMap::new(registry, line_origins);
Ok(PreprocessResult {
expanded_source,
source_map,
})
}