use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::Command;
const CRATES_DATA_PATH: &str = "crates/logicaffeine_data";
const CRATES_SYSTEM_PATH: &str = "crates/logicaffeine_system";
use std::fmt::Write as FmtWrite;
use crate::analysis::{DiscoveryPass, EscapeChecker, OwnershipChecker, PolicyRegistry};
use crate::arena::Arena;
use crate::arena_ctx::AstContext;
use crate::ast::{Expr, Stmt, TypeExpr};
use crate::codegen::{codegen_program, generate_c_header, generate_python_bindings, generate_typescript_bindings};
use crate::diagnostic::{parse_rustc_json, translate_diagnostics, LogosError};
use crate::drs::WorldState;
use crate::error::ParseError;
use crate::intern::Interner;
use crate::lexer::Lexer;
use crate::parser::Parser;
use crate::sourcemap::SourceMap;
#[derive(Debug, Clone)]
pub struct CrateDependency {
pub name: String,
pub version: String,
pub features: Vec<String>,
}
#[derive(Debug)]
pub struct CompileOutput {
pub rust_code: String,
pub dependencies: Vec<CrateDependency>,
pub c_header: Option<String>,
pub python_bindings: Option<String>,
pub typescript_types: Option<String>,
pub typescript_bindings: Option<String>,
}
pub fn interpret_program(source: &str) -> Result<String, ParseError> {
let result = crate::ui_bridge::interpret_for_ui_sync(source);
if let Some(err) = result.error {
Err(ParseError {
kind: crate::error::ParseErrorKind::Custom(err),
span: crate::token::Span::default(),
})
} else {
Ok(result.lines.join("\n"))
}
}
pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
compile_program_full(source).map(|o| o.rust_code)
}
pub fn compile_to_c(source: &str) -> Result<String, ParseError> {
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let (type_registry, _policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let codegen_registry = type_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena, &term_arena, &np_arena, &sym_arena,
&role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
Ok(crate::codegen_c::codegen_program_c(&stmts, &codegen_registry, &interner))
}
pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let codegen_registry = type_registry.clone();
let codegen_policies = policy_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
let mut dependencies = extract_dependencies(&stmts, &interner)?;
let needs_wasm_bindgen = stmts.iter().any(|stmt| {
if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
interner.resolve(*target).eq_ignore_ascii_case("wasm")
} else {
false
}
});
if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
dependencies.push(CrateDependency {
name: "wasm-bindgen".to_string(),
version: "0.2".to_string(),
features: vec![],
});
}
let mut escape_checker = EscapeChecker::new(&interner);
escape_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
.map_err(|e| ParseError {
kind: e.to_parse_error_kind(&interner),
span: crate::token::Span::default(),
})?;
let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
let has_c = stmts.iter().any(|stmt| {
if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
match export_target {
None => true,
Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
}
} else {
false
}
});
let c_header = if has_c {
Some(generate_c_header(&stmts, "module", &interner, &codegen_registry))
} else {
None
};
if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
dependencies.push(CrateDependency {
name: "serde_json".to_string(),
version: "1".to_string(),
features: vec![],
});
}
let python_bindings = if has_c {
Some(generate_python_bindings(&stmts, "module", &interner, &codegen_registry))
} else {
None
};
let (typescript_bindings, typescript_types) = if has_c {
let (js, dts) = generate_typescript_bindings(&stmts, "module", &interner, &codegen_registry);
(Some(js), Some(dts))
} else {
(None, None)
};
Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
}
fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Result<Vec<CrateDependency>, ParseError> {
use std::collections::HashMap;
let mut seen: HashMap<String, String> = HashMap::new(); let mut deps: Vec<CrateDependency> = Vec::new();
for stmt in stmts {
if let Stmt::Require { crate_name, version, features, span } = stmt {
let name = interner.resolve(*crate_name).to_string();
let ver = interner.resolve(*version).to_string();
if let Some(existing_ver) = seen.get(&name) {
if *existing_ver != ver {
return Err(ParseError {
kind: crate::error::ParseErrorKind::Custom(format!(
"Conflicting versions for crate \"{}\": \"{}\" and \"{}\".",
name, existing_ver, ver
)),
span: *span,
});
}
} else {
seen.insert(name.clone(), ver.clone());
deps.push(CrateDependency {
name,
version: ver,
features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
});
}
}
}
Ok(deps)
}
pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let codegen_registry = type_registry.clone();
let codegen_policies = policy_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let mut escape_checker = EscapeChecker::new(&interner);
escape_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let mut ownership_checker = OwnershipChecker::new(&interner);
ownership_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
.map_err(|e| ParseError {
kind: e.to_parse_error_kind(&interner),
span: crate::token::Span::default(),
})?;
let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
Ok(rust_code)
}
#[cfg(feature = "verification")]
pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
use crate::verification::VerificationPass;
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let codegen_registry = type_registry.clone();
let codegen_policies = policy_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let mut escape_checker = EscapeChecker::new(&interner);
escape_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let mut verifier = VerificationPass::new(&interner);
verifier.verify_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(format!(
"Verification Failed:\n\n{}",
e
)),
span: crate::token::Span::default(),
}
})?;
let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
.map_err(|e| ParseError {
kind: e.to_parse_error_kind(&interner),
span: crate::token::Span::default(),
})?;
let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
Ok(rust_code)
}
pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
let output = compile_program_full(source).map_err(CompileError::Parse)?;
let src_dir = output_dir.join("src");
fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
let main_path = src_dir.join("main.rs");
let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
let mut cargo_toml = String::from(r#"[package]
name = "logos_output"
version = "0.1.0"
edition = "2021"
[dependencies]
logicaffeine-data = { path = "./crates/logicaffeine_data" }
logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
[target.'cfg(target_os = "linux")'.dependencies]
logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full", "io-uring"] }
"#);
for dep in &output.dependencies {
if dep.features.is_empty() {
let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
} else {
let feats = dep.features.iter()
.map(|f| format!("\"{}\"", f))
.collect::<Vec<_>>()
.join(", ");
let _ = writeln!(
cargo_toml,
"{} = {{ version = \"{}\", features = [{}] }}",
dep.name, dep.version, feats
);
}
}
cargo_toml.push_str("\n[profile.release]\nlto = true\nopt-level = 3\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\n");
let cargo_path = output_dir.join("Cargo.toml");
let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
let cargo_config_dir = output_dir.join(".cargo");
fs::create_dir_all(&cargo_config_dir).map_err(|e| CompileError::Io(e.to_string()))?;
let config_content = "[build]\nrustflags = [\"-C\", \"target-cpu=native\"]\n";
let config_path = cargo_config_dir.join("config.toml");
fs::write(&config_path, config_content).map_err(|e| CompileError::Io(e.to_string()))?;
copy_runtime_crates(output_dir)?;
Ok(())
}
pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
let crates_dir = output_dir.join("crates");
fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
let workspace_root = find_workspace_root()?;
let data_src = workspace_root.join(CRATES_DATA_PATH);
let data_dest = crates_dir.join("logicaffeine_data");
copy_dir_recursive(&data_src, &data_dest)?;
deworkspace_cargo_toml(&data_dest.join("Cargo.toml"))?;
let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
let system_dest = crates_dir.join("logicaffeine_system");
copy_dir_recursive(&system_src, &system_dest)?;
deworkspace_cargo_toml(&system_dest.join("Cargo.toml"))?;
let base_src = workspace_root.join("crates/logicaffeine_base");
let base_dest = crates_dir.join("logicaffeine_base");
copy_dir_recursive(&base_src, &base_dest)?;
deworkspace_cargo_toml(&base_dest.join("Cargo.toml"))?;
Ok(())
}
fn deworkspace_cargo_toml(cargo_toml_path: &Path) -> Result<(), CompileError> {
let content = fs::read_to_string(cargo_toml_path)
.map_err(|e| CompileError::Io(e.to_string()))?;
let mut result = String::with_capacity(content.len());
for line in content.lines() {
let trimmed = line.trim();
if trimmed == "edition.workspace = true" {
result.push_str("edition = \"2021\"");
} else if trimmed == "rust-version.workspace = true" {
result.push_str("rust-version = \"1.75\"");
} else if trimmed == "authors.workspace = true"
|| trimmed == "repository.workspace = true"
|| trimmed == "homepage.workspace = true"
|| trimmed == "documentation.workspace = true"
|| trimmed == "keywords.workspace = true"
|| trimmed == "categories.workspace = true"
|| trimmed == "license.workspace = true"
{
continue;
} else if trimmed.contains(".workspace = true") {
continue;
} else {
result.push_str(line);
}
result.push('\n');
}
fs::write(cargo_toml_path, result)
.map_err(|e| CompileError::Io(e.to_string()))?;
Ok(())
}
fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
if let Ok(workspace) = std::env::var("LOGOS_WORKSPACE") {
let path = Path::new(&workspace);
if path.join("Cargo.toml").exists() && path.join("crates").exists() {
return Ok(path.to_path_buf());
}
}
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
let path = Path::new(&manifest_dir);
if let Some(parent) = path.parent().and_then(|p| p.parent()) {
if parent.join("Cargo.toml").exists() {
return Ok(parent.to_path_buf());
}
}
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let mut candidate = dir.to_path_buf();
for _ in 0..5 {
if candidate.join("Cargo.toml").exists() && candidate.join("crates").exists() {
return Ok(candidate);
}
if !candidate.pop() {
break;
}
}
}
}
let mut current = std::env::current_dir()
.map_err(|e| CompileError::Io(e.to_string()))?;
loop {
if current.join("Cargo.toml").exists() && current.join("crates").exists() {
return Ok(current);
}
if !current.pop() {
return Err(CompileError::Io(
"Could not find workspace root. Set LOGOS_WORKSPACE env var or run from within the workspace.".to_string()
));
}
}
}
fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
let src_path = entry.path();
let file_name = entry.file_name();
let dst_path = dst.join(&file_name);
if file_name == "target"
|| file_name == ".git"
|| file_name == "Cargo.lock"
|| file_name == ".DS_Store"
{
continue;
}
if file_name.to_string_lossy().starts_with('.') {
continue;
}
if !src_path.exists() {
continue;
}
if src_path.is_dir() {
copy_dir_recursive(&src_path, &dst_path)?;
} else if file_name == "Cargo.toml" {
match fs::read_to_string(&src_path) {
Ok(content) => {
let filtered: String = content
.lines()
.filter(|line| !line.trim().starts_with("[workspace]"))
.collect::<Vec<_>>()
.join("\n");
fs::write(&dst_path, filtered)
.map_err(|e| CompileError::Io(e.to_string()))?;
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
Err(e) => return Err(CompileError::Io(e.to_string())),
}
} else {
match fs::copy(&src_path, &dst_path) {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
Err(e) => return Err(CompileError::Io(e.to_string())),
}
}
}
Ok(())
}
pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
compile_to_rust_checked(source).map_err(CompileError::Parse)?;
compile_to_dir(source, output_dir)?;
let build_output = Command::new("cargo")
.arg("build")
.arg("--message-format=json")
.current_dir(output_dir)
.output()
.map_err(|e| CompileError::Io(e.to_string()))?;
if !build_output.status.success() {
let stderr = String::from_utf8_lossy(&build_output.stderr);
let stdout = String::from_utf8_lossy(&build_output.stdout);
let diagnostics = parse_rustc_json(&stdout);
if !diagnostics.is_empty() {
let source_map = SourceMap::new(source.to_string());
let interner = Interner::new();
if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
return Err(CompileError::Ownership(logos_error));
}
}
return Err(CompileError::Build(stderr.to_string()));
}
let run_output = Command::new("cargo")
.arg("run")
.arg("--quiet")
.current_dir(output_dir)
.output()
.map_err(|e| CompileError::Io(e.to_string()))?;
if !run_output.status.success() {
let stderr = String::from_utf8_lossy(&run_output.stderr);
return Err(CompileError::Runtime(stderr.to_string()));
}
let stdout = String::from_utf8_lossy(&run_output.stdout);
Ok(stdout.to_string())
}
pub fn compile_file(path: &Path) -> Result<String, CompileError> {
let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
compile_to_rust(&source).map_err(CompileError::Parse)
}
pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
use crate::loader::Loader;
use crate::analysis::discover_with_imports;
let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
let mut loader = Loader::new(root_path);
let mut interner = Interner::new();
let source = fs::read_to_string(entry_file)
.map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
.map_err(|e| CompileError::Io(e))?;
compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
.map_err(CompileError::Parse)
}
fn compile_to_rust_with_registry_full(
source: &str,
type_registry: crate::analysis::TypeRegistry,
interner: &mut Interner,
) -> Result<CompileOutput, ParseError> {
let mut lexer = Lexer::new(source, interner);
let tokens = lexer.tokenize();
let policy_registry = {
let mut discovery = DiscoveryPass::new(&tokens, interner);
discovery.run_full().policies
};
let codegen_registry = type_registry.clone();
let codegen_policies = policy_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let mut dependencies = extract_dependencies(&stmts, interner)?;
let needs_wasm_bindgen = stmts.iter().any(|stmt| {
if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
interner.resolve(*target).eq_ignore_ascii_case("wasm")
} else {
false
}
});
if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
dependencies.push(CrateDependency {
name: "wasm-bindgen".to_string(),
version: "0.2".to_string(),
features: vec![],
});
}
let mut escape_checker = EscapeChecker::new(interner);
escape_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let type_env = crate::analysis::check_program(&stmts, interner, &codegen_registry)
.map_err(|e| ParseError {
kind: e.to_parse_error_kind(interner),
span: crate::token::Span::default(),
})?;
let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner, &type_env);
let has_c = stmts.iter().any(|stmt| {
if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
match export_target {
None => true,
Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
}
} else {
false
}
});
let c_header = if has_c {
Some(generate_c_header(&stmts, "module", interner, &codegen_registry))
} else {
None
};
if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
dependencies.push(CrateDependency {
name: "serde_json".to_string(),
version: "1".to_string(),
features: vec![],
});
}
let python_bindings = if has_c {
Some(generate_python_bindings(&stmts, "module", interner, &codegen_registry))
} else {
None
};
let (typescript_bindings, typescript_types) = if has_c {
let (js, dts) = generate_typescript_bindings(&stmts, "module", interner, &codegen_registry);
(Some(js), Some(dts))
} else {
(None, None)
};
Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
}
#[derive(Debug)]
pub enum CompileError {
Parse(ParseError),
Io(String),
Build(String),
Runtime(String),
Ownership(LogosError),
}
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
CompileError::Io(e) => write!(f, "IO error: {}", e),
CompileError::Build(e) => write!(f, "Build error: {}", e),
CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
CompileError::Ownership(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for CompileError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile_let_statement() {
let source = "## Main\nLet x be 5.";
let result = compile_to_rust(source);
assert!(result.is_ok(), "Should compile: {:?}", result);
let rust = result.unwrap();
assert!(rust.contains("fn main()"));
assert!(rust.contains("let x = 5;"));
}
#[test]
fn test_compile_return_statement() {
let source = "## Main\nReturn 42.";
let result = compile_to_rust(source);
assert!(result.is_ok(), "Should compile: {:?}", result);
let rust = result.unwrap();
assert!(rust.contains("return 42;"));
}
}