use std::path::Path;
use crate::error::ParseError;
use crate::parser::Parser;
#[cfg(feature = "wasm-compile")]
use crate::lower;
#[derive(Debug)]
pub struct CompiledSpirit {
pub wasm: Vec<u8>,
pub source_map: Option<SourceMap>,
pub warnings: Vec<CompilerWarning>,
}
#[derive(Debug, Clone)]
pub struct SourceMap {
pub entries: Vec<SourceMapEntry>,
}
#[derive(Debug, Clone)]
pub struct SourceMapEntry {
pub wasm_offset: u32,
pub source_file: String,
pub line: u32,
pub column: u32,
}
#[derive(Debug, Clone)]
pub struct CompilerWarning {
pub message: String,
pub location: Option<(String, u32, u32)>,
}
#[derive(Debug)]
pub enum CompilerError {
LexError(String),
ParseError(ParseError),
HirError(String),
MlirError(String),
WasmError(String),
IoError(std::io::Error),
ProjectError(String),
}
impl std::fmt::Display for CompilerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CompilerError::LexError(msg) => write!(f, "Lexer error: {}", msg),
CompilerError::ParseError(err) => write!(f, "Parse error: {}", err),
CompilerError::HirError(msg) => write!(f, "HIR lowering error: {}", msg),
CompilerError::MlirError(msg) => write!(f, "MLIR lowering error: {}", msg),
CompilerError::WasmError(msg) => write!(f, "WASM emission error: {}", msg),
CompilerError::IoError(err) => write!(f, "I/O error: {}", err),
CompilerError::ProjectError(msg) => write!(f, "Project error: {}", msg),
}
}
}
impl std::error::Error for CompilerError {}
impl From<ParseError> for CompilerError {
fn from(err: ParseError) -> Self {
CompilerError::ParseError(err)
}
}
impl From<std::io::Error> for CompilerError {
fn from(err: std::io::Error) -> Self {
CompilerError::IoError(err)
}
}
pub fn compile_file(path: &Path) -> Result<CompiledSpirit, CompilerError> {
let source = std::fs::read_to_string(path)?;
let filename = path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("unknown.dol");
compile_source(&source, filename)
}
#[cfg(feature = "wasm-compile")]
pub fn compile_source(source: &str, filename: &str) -> Result<CompiledSpirit, CompilerError> {
let mut warnings = Vec::new();
let mut parser = Parser::new(source);
let ast_file = parser.parse_file().map_err(CompilerError::ParseError)?;
let mut lowering_ctx = lower::LoweringContext::new();
let hir_module = lower::lower_module(&mut lowering_ctx, &ast_file);
for diag in lowering_ctx.diagnostics() {
warnings.push(CompilerWarning {
message: diag.message.clone(),
location: diag
.span
.as_ref()
.map(|span| (filename.to_string(), span.line as u32, span.column as u32)),
});
}
if lowering_ctx.has_errors() {
let errors: Vec<String> = lowering_ctx
.diagnostics()
.iter()
.filter(|d| matches!(d.kind, lower::DiagnosticKind::Error))
.map(|d| d.message.clone())
.collect();
return Err(CompilerError::HirError(errors.join("; ")));
}
let wasm_bytes = generate_placeholder_wasm(&hir_module)?;
Ok(CompiledSpirit {
wasm: wasm_bytes,
source_map: None, warnings,
})
}
#[cfg(feature = "wasm-compile")]
pub fn compile_spirit_project(project_dir: &Path) -> Result<CompiledSpirit, CompilerError> {
let manifest_path = project_dir.join("manifest.toml");
if !manifest_path.exists() {
return Err(CompilerError::ProjectError(format!(
"manifest.toml not found in {}",
project_dir.display()
)));
}
let src_dir = project_dir.join("src");
if !src_dir.exists() {
return Err(CompilerError::ProjectError(format!(
"src/ directory not found in {}",
project_dir.display()
)));
}
let main_dol = src_dir.join("main.dol");
if !main_dol.exists() {
return Err(CompilerError::ProjectError(format!(
"src/main.dol not found in {}",
project_dir.display()
)));
}
compile_file(&main_dol)
}
#[cfg(feature = "wasm-compile")]
fn generate_placeholder_wasm(
_hir_module: &crate::hir::HirModule,
) -> Result<Vec<u8>, CompilerError> {
let mut wasm = Vec::new();
wasm.extend_from_slice(&[0x00, 0x61, 0x73, 0x6D]);
wasm.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]);
Ok(wasm)
}
#[cfg(not(feature = "wasm-compile"))]
pub fn compile_source(_source: &str, _filename: &str) -> Result<CompiledSpirit, CompilerError> {
Err(CompilerError::WasmError(
"WASM compilation requires the 'wasm' feature flag".to_string(),
))
}
#[cfg(not(feature = "wasm-compile"))]
pub fn compile_spirit_project(_project_dir: &Path) -> Result<CompiledSpirit, CompilerError> {
Err(CompilerError::WasmError(
"WASM compilation requires the 'wasm' feature flag".to_string(),
))
}
#[cfg(test)]
#[cfg(feature = "wasm-compile")]
mod tests {
use super::*;
#[test]
#[ignore] fn test_compile_empty_module() {
let source = r#"
module test @ 1.0.0
"#;
let result = compile_source(source, "test.dol");
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
let compiled = result.unwrap();
assert!(!compiled.wasm.is_empty());
assert_eq!(&compiled.wasm[0..4], b"\0asm");
assert_eq!(&compiled.wasm[4..8], &[1, 0, 0, 0]);
}
#[test]
#[ignore] fn test_compile_simple_gene() {
let source = r#"
module test @ 1.0.0
gene Counter {
has value: Int64
constraint non_negative {
this.value >= 0
}
}
exegesis {
A simple counter gene.
}
"#;
let result = compile_source(source, "test.dol");
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
let compiled = result.unwrap();
assert!(!compiled.wasm.is_empty());
assert_eq!(&compiled.wasm[0..4], b"\0asm");
}
#[test]
fn test_compile_with_function() {
let source = r#"
module math @ 1.0.0
fun add(a: Int64, b: Int64) -> Int64 {
return a + b
}
"#;
let result = compile_source(source, "math.dol");
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
let compiled = result.unwrap();
assert!(!compiled.wasm.is_empty());
assert_eq!(&compiled.wasm[0..4], b"\0asm");
}
#[test]
fn test_compile_invalid_syntax() {
let source = r#"
module broken @ 1.0.0
gene Invalid {
this is not valid syntax!!!
}
"#;
let result = compile_source(source, "broken.dol");
assert!(result.is_err());
match result.unwrap_err() {
CompilerError::ParseError(_) => {
}
other => panic!("Expected ParseError, got {:?}", other),
}
}
#[test]
fn test_compiler_error_display() {
let err = CompilerError::WasmError("test error".to_string());
assert_eq!(err.to_string(), "WASM emission error: test error");
let err = CompilerError::HirError("type mismatch".to_string());
assert_eq!(err.to_string(), "HIR lowering error: type mismatch");
}
#[test]
fn test_source_map_entry() {
let entry = SourceMapEntry {
wasm_offset: 42,
source_file: "test.dol".to_string(),
line: 10,
column: 5,
};
assert_eq!(entry.wasm_offset, 42);
assert_eq!(entry.line, 10);
assert_eq!(entry.column, 5);
}
#[test]
fn test_compiler_warning() {
let warning = CompilerWarning {
message: "deprecated syntax".to_string(),
location: Some(("test.dol".to_string(), 5, 10)),
};
assert_eq!(warning.message, "deprecated syntax");
assert!(warning.location.is_some());
}
}