1use std::fs;
68use std::io::Write;
69use std::path::Path;
70use std::process::Command;
71
72const CRATES_DATA_PATH: &str = "crates/logicaffeine_data";
74const CRATES_SYSTEM_PATH: &str = "crates/logicaffeine_system";
75
76use crate::analysis::{DiscoveryPass, EscapeChecker, OwnershipChecker, PolicyRegistry};
77use crate::arena::Arena;
78use crate::arena_ctx::AstContext;
79use crate::ast::{Expr, Stmt, TypeExpr};
80use crate::codegen::codegen_program;
81use crate::diagnostic::{parse_rustc_json, translate_diagnostics, LogosError};
82use crate::drs::WorldState;
83use crate::error::ParseError;
84use crate::intern::Interner;
85use crate::lexer::Lexer;
86use crate::parser::Parser;
87use crate::sourcemap::SourceMap;
88
89pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
117 let mut interner = Interner::new();
118 let mut lexer = Lexer::new(source, &mut interner);
119 let tokens = lexer.tokenize();
120
121 let (type_registry, policy_registry) = {
123 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
124 let result = discovery.run_full();
125 (result.types, result.policies)
126 };
127 let codegen_registry = type_registry.clone();
129 let codegen_policies = policy_registry.clone();
130
131 let mut world_state = WorldState::new();
132 let expr_arena = Arena::new();
133 let term_arena = Arena::new();
134 let np_arena = Arena::new();
135 let sym_arena = Arena::new();
136 let role_arena = Arena::new();
137 let pp_arena = Arena::new();
138 let stmt_arena: Arena<Stmt> = Arena::new();
139 let imperative_expr_arena: Arena<Expr> = Arena::new();
140 let type_expr_arena: Arena<TypeExpr> = Arena::new();
141
142 let ast_ctx = AstContext::with_types(
143 &expr_arena,
144 &term_arena,
145 &np_arena,
146 &sym_arena,
147 &role_arena,
148 &pp_arena,
149 &stmt_arena,
150 &imperative_expr_arena,
151 &type_expr_arena,
152 );
153
154 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
156 let stmts = parser.parse_program()?;
159
160 let mut escape_checker = EscapeChecker::new(&interner);
163 escape_checker.check_program(&stmts).map_err(|e| {
164 ParseError {
167 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
168 span: e.span,
169 }
170 })?;
171
172 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
176
177 Ok(rust_code)
178}
179
180pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
215 let mut interner = Interner::new();
216 let mut lexer = Lexer::new(source, &mut interner);
217 let tokens = lexer.tokenize();
218
219 let (type_registry, policy_registry) = {
221 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
222 let result = discovery.run_full();
223 (result.types, result.policies)
224 };
225 let codegen_registry = type_registry.clone();
227 let codegen_policies = policy_registry.clone();
228
229 let mut world_state = WorldState::new();
230 let expr_arena = Arena::new();
231 let term_arena = Arena::new();
232 let np_arena = Arena::new();
233 let sym_arena = Arena::new();
234 let role_arena = Arena::new();
235 let pp_arena = Arena::new();
236 let stmt_arena: Arena<Stmt> = Arena::new();
237 let imperative_expr_arena: Arena<Expr> = Arena::new();
238 let type_expr_arena: Arena<TypeExpr> = Arena::new();
239
240 let ast_ctx = AstContext::with_types(
241 &expr_arena,
242 &term_arena,
243 &np_arena,
244 &sym_arena,
245 &role_arena,
246 &pp_arena,
247 &stmt_arena,
248 &imperative_expr_arena,
249 &type_expr_arena,
250 );
251
252 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
254 let stmts = parser.parse_program()?;
255
256 let mut escape_checker = EscapeChecker::new(&interner);
258 escape_checker.check_program(&stmts).map_err(|e| {
259 ParseError {
260 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
261 span: e.span,
262 }
263 })?;
264
265 let mut ownership_checker = OwnershipChecker::new(&interner);
268 ownership_checker.check_program(&stmts).map_err(|e| {
269 ParseError {
270 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
271 span: e.span,
272 }
273 })?;
274
275 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
276
277 Ok(rust_code)
278}
279
280#[cfg(feature = "verification")]
322pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
323 use crate::verification::VerificationPass;
324
325 let mut interner = Interner::new();
326 let mut lexer = Lexer::new(source, &mut interner);
327 let tokens = lexer.tokenize();
328
329 let (type_registry, policy_registry) = {
331 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
332 let result = discovery.run_full();
333 (result.types, result.policies)
334 };
335 let codegen_registry = type_registry.clone();
337 let codegen_policies = policy_registry.clone();
338
339 let mut world_state = WorldState::new();
340 let expr_arena = Arena::new();
341 let term_arena = Arena::new();
342 let np_arena = Arena::new();
343 let sym_arena = Arena::new();
344 let role_arena = Arena::new();
345 let pp_arena = Arena::new();
346 let stmt_arena: Arena<Stmt> = Arena::new();
347 let imperative_expr_arena: Arena<Expr> = Arena::new();
348 let type_expr_arena: Arena<TypeExpr> = Arena::new();
349
350 let ast_ctx = AstContext::with_types(
351 &expr_arena,
352 &term_arena,
353 &np_arena,
354 &sym_arena,
355 &role_arena,
356 &pp_arena,
357 &stmt_arena,
358 &imperative_expr_arena,
359 &type_expr_arena,
360 );
361
362 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
364 let stmts = parser.parse_program()?;
365
366 let mut escape_checker = EscapeChecker::new(&interner);
368 escape_checker.check_program(&stmts).map_err(|e| {
369 ParseError {
370 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
371 span: e.span,
372 }
373 })?;
374
375 let mut verifier = VerificationPass::new(&interner);
377 verifier.verify_program(&stmts).map_err(|e| {
378 ParseError {
379 kind: crate::error::ParseErrorKind::Custom(format!(
380 "Verification Failed:\n\n{}",
381 e
382 )),
383 span: crate::token::Span::default(),
384 }
385 })?;
386
387 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
388
389 Ok(rust_code)
390}
391
392pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
418 let rust_code = compile_to_rust(source).map_err(CompileError::Parse)?;
419
420 let src_dir = output_dir.join("src");
422 fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
423
424 let main_path = src_dir.join("main.rs");
426 let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
427 file.write_all(rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
428
429 let cargo_toml = r#"[package]
431name = "logos_output"
432version = "0.1.0"
433edition = "2021"
434
435[dependencies]
436logicaffeine-data = { path = "./crates/logicaffeine_data" }
437logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
438tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
439"#;
440 let cargo_path = output_dir.join("Cargo.toml");
441 let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
442 file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
443
444 copy_runtime_crates(output_dir)?;
446
447 Ok(())
448}
449
450pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
453 let crates_dir = output_dir.join("crates");
454 fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
455
456 let workspace_root = find_workspace_root()?;
458
459 let data_src = workspace_root.join(CRATES_DATA_PATH);
461 let data_dest = crates_dir.join("logicaffeine_data");
462 copy_dir_recursive(&data_src, &data_dest)?;
463
464 let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
466 let system_dest = crates_dir.join("logicaffeine_system");
467 copy_dir_recursive(&system_src, &system_dest)?;
468
469 let base_src = workspace_root.join("crates/logicaffeine_base");
471 let base_dest = crates_dir.join("logicaffeine_base");
472 copy_dir_recursive(&base_src, &base_dest)?;
473
474 Ok(())
475}
476
477fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
479 if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
481 let path = Path::new(&manifest_dir);
483 if let Some(parent) = path.parent().and_then(|p| p.parent()) {
484 if parent.join("Cargo.toml").exists() {
485 return Ok(parent.to_path_buf());
486 }
487 }
488 }
489
490 let mut current = std::env::current_dir()
492 .map_err(|e| CompileError::Io(e.to_string()))?;
493
494 loop {
495 if current.join("Cargo.toml").exists() && current.join("crates").exists() {
496 return Ok(current);
497 }
498 if !current.pop() {
499 return Err(CompileError::Io("Could not find workspace root".to_string()));
500 }
501 }
502}
503
504fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
507 fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
508
509 for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
510 let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
511 let src_path = entry.path();
512 let file_name = entry.file_name();
513 let dst_path = dst.join(&file_name);
514
515 if file_name == "target"
517 || file_name == ".git"
518 || file_name == "Cargo.lock"
519 || file_name == ".DS_Store"
520 {
521 continue;
522 }
523
524 if file_name.to_string_lossy().starts_with('.') {
526 continue;
527 }
528
529 if !src_path.exists() {
531 continue;
532 }
533
534 if src_path.is_dir() {
535 copy_dir_recursive(&src_path, &dst_path)?;
536 } else if file_name == "Cargo.toml" {
537 match fs::read_to_string(&src_path) {
540 Ok(content) => {
541 let filtered: String = content
542 .lines()
543 .filter(|line| !line.trim().starts_with("[workspace]"))
544 .collect::<Vec<_>>()
545 .join("\n");
546 fs::write(&dst_path, filtered)
547 .map_err(|e| CompileError::Io(e.to_string()))?;
548 }
549 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
550 Err(e) => return Err(CompileError::Io(e.to_string())),
551 }
552 } else {
553 match fs::copy(&src_path, &dst_path) {
554 Ok(_) => {}
555 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
556 Err(e) => return Err(CompileError::Io(e.to_string())),
557 }
558 }
559 }
560
561 Ok(())
562}
563
564pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
602 compile_to_dir(source, output_dir)?;
603
604 let build_output = Command::new("cargo")
606 .arg("build")
607 .arg("--message-format=json")
608 .current_dir(output_dir)
609 .output()
610 .map_err(|e| CompileError::Io(e.to_string()))?;
611
612 if !build_output.status.success() {
613 let stderr = String::from_utf8_lossy(&build_output.stderr);
614 let stdout = String::from_utf8_lossy(&build_output.stdout);
615
616 let diagnostics = parse_rustc_json(&stdout);
618
619 if !diagnostics.is_empty() {
620 let source_map = SourceMap::new(source.to_string());
622 let interner = Interner::new();
623
624 if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
625 return Err(CompileError::Ownership(logos_error));
626 }
627 }
628
629 return Err(CompileError::Build(stderr.to_string()));
631 }
632
633 let run_output = Command::new("cargo")
635 .arg("run")
636 .arg("--quiet")
637 .current_dir(output_dir)
638 .output()
639 .map_err(|e| CompileError::Io(e.to_string()))?;
640
641 if !run_output.status.success() {
642 let stderr = String::from_utf8_lossy(&run_output.stderr);
643 return Err(CompileError::Runtime(stderr.to_string()));
644 }
645
646 let stdout = String::from_utf8_lossy(&run_output.stdout);
647 Ok(stdout.to_string())
648}
649
650pub fn compile_file(path: &Path) -> Result<String, CompileError> {
653 let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
654 compile_to_rust(&source).map_err(CompileError::Parse)
655}
656
657pub fn compile_project(entry_file: &Path) -> Result<String, CompileError> {
673 use crate::loader::Loader;
674 use crate::analysis::discover_with_imports;
675
676 let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
677 let mut loader = Loader::new(root_path);
678 let mut interner = Interner::new();
679
680 let source = fs::read_to_string(entry_file)
682 .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
683
684 let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
686 .map_err(|e| CompileError::Io(e))?;
687
688 compile_to_rust_with_registry(&source, type_registry, &mut interner)
690 .map_err(CompileError::Parse)
691}
692
693fn compile_to_rust_with_registry(
696 source: &str,
697 type_registry: crate::analysis::TypeRegistry,
698 interner: &mut Interner,
699) -> Result<String, ParseError> {
700 let mut lexer = Lexer::new(source, interner);
701 let tokens = lexer.tokenize();
702
703 let policy_registry = {
705 let mut discovery = DiscoveryPass::new(&tokens, interner);
706 discovery.run_full().policies
707 };
708
709 let codegen_registry = type_registry.clone();
710 let codegen_policies = policy_registry.clone();
711
712 let mut world_state = WorldState::new();
713 let expr_arena = Arena::new();
714 let term_arena = Arena::new();
715 let np_arena = Arena::new();
716 let sym_arena = Arena::new();
717 let role_arena = Arena::new();
718 let pp_arena = Arena::new();
719 let stmt_arena: Arena<Stmt> = Arena::new();
720 let imperative_expr_arena: Arena<Expr> = Arena::new();
721 let type_expr_arena: Arena<TypeExpr> = Arena::new();
722
723 let ast_ctx = AstContext::with_types(
724 &expr_arena,
725 &term_arena,
726 &np_arena,
727 &sym_arena,
728 &role_arena,
729 &pp_arena,
730 &stmt_arena,
731 &imperative_expr_arena,
732 &type_expr_arena,
733 );
734
735 let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
736 let stmts = parser.parse_program()?;
737
738 let mut escape_checker = EscapeChecker::new(interner);
739 escape_checker.check_program(&stmts).map_err(|e| {
740 ParseError {
741 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
742 span: e.span,
743 }
744 })?;
745
746 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner);
747
748 Ok(rust_code)
749}
750
751#[derive(Debug)]
773pub enum CompileError {
774 Parse(ParseError),
779
780 Io(String),
784
785 Build(String),
790
791 Runtime(String),
795
796 Ownership(LogosError),
802}
803
804impl std::fmt::Display for CompileError {
805 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
806 match self {
807 CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
808 CompileError::Io(e) => write!(f, "IO error: {}", e),
809 CompileError::Build(e) => write!(f, "Build error: {}", e),
810 CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
811 CompileError::Ownership(e) => write!(f, "{}", e),
812 }
813 }
814}
815
816impl std::error::Error for CompileError {}
817
818#[cfg(test)]
819mod tests {
820 use super::*;
821
822 #[test]
823 fn test_compile_let_statement() {
824 let source = "## Main\nLet x be 5.";
825 let result = compile_to_rust(source);
826 assert!(result.is_ok(), "Should compile: {:?}", result);
827 let rust = result.unwrap();
828 assert!(rust.contains("fn main()"));
829 assert!(rust.contains("let x = 5;"));
830 }
831
832 #[test]
833 fn test_compile_return_statement() {
834 let source = "## Main\nReturn 42.";
835 let result = compile_to_rust(source);
836 assert!(result.is_ok(), "Should compile: {:?}", result);
837 let rust = result.unwrap();
838 assert!(rust.contains("return 42;"));
839 }
840}