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 std::fmt::Write as FmtWrite;
77
78use crate::analysis::{DiscoveryPass, EscapeChecker, OwnershipChecker, PolicyRegistry};
79use crate::arena::Arena;
80use crate::arena_ctx::AstContext;
81use crate::ast::{Expr, Stmt, TypeExpr};
82use crate::codegen::codegen_program;
83use crate::diagnostic::{parse_rustc_json, translate_diagnostics, LogosError};
84use crate::drs::WorldState;
85use crate::error::ParseError;
86use crate::intern::Interner;
87use crate::lexer::Lexer;
88use crate::parser::Parser;
89use crate::sourcemap::SourceMap;
90
91#[derive(Debug, Clone)]
93pub struct CrateDependency {
94 pub name: String,
95 pub version: String,
96 pub features: Vec<String>,
97}
98
99#[derive(Debug)]
101pub struct CompileOutput {
102 pub rust_code: String,
103 pub dependencies: Vec<CrateDependency>,
104}
105
106pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
134 compile_program_full(source).map(|o| o.rust_code)
135}
136
137pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
142 let mut interner = Interner::new();
143 let mut lexer = Lexer::new(source, &mut interner);
144 let tokens = lexer.tokenize();
145
146 let (type_registry, policy_registry) = {
148 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
149 let result = discovery.run_full();
150 (result.types, result.policies)
151 };
152 let codegen_registry = type_registry.clone();
154 let codegen_policies = policy_registry.clone();
155
156 let mut world_state = WorldState::new();
157 let expr_arena = Arena::new();
158 let term_arena = Arena::new();
159 let np_arena = Arena::new();
160 let sym_arena = Arena::new();
161 let role_arena = Arena::new();
162 let pp_arena = Arena::new();
163 let stmt_arena: Arena<Stmt> = Arena::new();
164 let imperative_expr_arena: Arena<Expr> = Arena::new();
165 let type_expr_arena: Arena<TypeExpr> = Arena::new();
166
167 let ast_ctx = AstContext::with_types(
168 &expr_arena,
169 &term_arena,
170 &np_arena,
171 &sym_arena,
172 &role_arena,
173 &pp_arena,
174 &stmt_arena,
175 &imperative_expr_arena,
176 &type_expr_arena,
177 );
178
179 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
181 let stmts = parser.parse_program()?;
184
185 let dependencies = extract_dependencies(&stmts, &interner);
187
188 let mut escape_checker = EscapeChecker::new(&interner);
191 escape_checker.check_program(&stmts).map_err(|e| {
192 ParseError {
195 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
196 span: e.span,
197 }
198 })?;
199
200 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
204
205 Ok(CompileOutput { rust_code, dependencies })
206}
207
208fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Vec<CrateDependency> {
210 stmts.iter().filter_map(|stmt| {
211 if let Stmt::Require { crate_name, version, features, .. } = stmt {
212 Some(CrateDependency {
213 name: interner.resolve(*crate_name).to_string(),
214 version: interner.resolve(*version).to_string(),
215 features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
216 })
217 } else {
218 None
219 }
220 }).collect()
221}
222
223pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
258 let mut interner = Interner::new();
259 let mut lexer = Lexer::new(source, &mut interner);
260 let tokens = lexer.tokenize();
261
262 let (type_registry, policy_registry) = {
264 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
265 let result = discovery.run_full();
266 (result.types, result.policies)
267 };
268 let codegen_registry = type_registry.clone();
270 let codegen_policies = policy_registry.clone();
271
272 let mut world_state = WorldState::new();
273 let expr_arena = Arena::new();
274 let term_arena = Arena::new();
275 let np_arena = Arena::new();
276 let sym_arena = Arena::new();
277 let role_arena = Arena::new();
278 let pp_arena = Arena::new();
279 let stmt_arena: Arena<Stmt> = Arena::new();
280 let imperative_expr_arena: Arena<Expr> = Arena::new();
281 let type_expr_arena: Arena<TypeExpr> = Arena::new();
282
283 let ast_ctx = AstContext::with_types(
284 &expr_arena,
285 &term_arena,
286 &np_arena,
287 &sym_arena,
288 &role_arena,
289 &pp_arena,
290 &stmt_arena,
291 &imperative_expr_arena,
292 &type_expr_arena,
293 );
294
295 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
297 let stmts = parser.parse_program()?;
298
299 let mut escape_checker = EscapeChecker::new(&interner);
301 escape_checker.check_program(&stmts).map_err(|e| {
302 ParseError {
303 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
304 span: e.span,
305 }
306 })?;
307
308 let mut ownership_checker = OwnershipChecker::new(&interner);
311 ownership_checker.check_program(&stmts).map_err(|e| {
312 ParseError {
313 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
314 span: e.span,
315 }
316 })?;
317
318 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
319
320 Ok(rust_code)
321}
322
323#[cfg(feature = "verification")]
365pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
366 use crate::verification::VerificationPass;
367
368 let mut interner = Interner::new();
369 let mut lexer = Lexer::new(source, &mut interner);
370 let tokens = lexer.tokenize();
371
372 let (type_registry, policy_registry) = {
374 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
375 let result = discovery.run_full();
376 (result.types, result.policies)
377 };
378 let codegen_registry = type_registry.clone();
380 let codegen_policies = policy_registry.clone();
381
382 let mut world_state = WorldState::new();
383 let expr_arena = Arena::new();
384 let term_arena = Arena::new();
385 let np_arena = Arena::new();
386 let sym_arena = Arena::new();
387 let role_arena = Arena::new();
388 let pp_arena = Arena::new();
389 let stmt_arena: Arena<Stmt> = Arena::new();
390 let imperative_expr_arena: Arena<Expr> = Arena::new();
391 let type_expr_arena: Arena<TypeExpr> = Arena::new();
392
393 let ast_ctx = AstContext::with_types(
394 &expr_arena,
395 &term_arena,
396 &np_arena,
397 &sym_arena,
398 &role_arena,
399 &pp_arena,
400 &stmt_arena,
401 &imperative_expr_arena,
402 &type_expr_arena,
403 );
404
405 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
407 let stmts = parser.parse_program()?;
408
409 let mut escape_checker = EscapeChecker::new(&interner);
411 escape_checker.check_program(&stmts).map_err(|e| {
412 ParseError {
413 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
414 span: e.span,
415 }
416 })?;
417
418 let mut verifier = VerificationPass::new(&interner);
420 verifier.verify_program(&stmts).map_err(|e| {
421 ParseError {
422 kind: crate::error::ParseErrorKind::Custom(format!(
423 "Verification Failed:\n\n{}",
424 e
425 )),
426 span: crate::token::Span::default(),
427 }
428 })?;
429
430 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
431
432 Ok(rust_code)
433}
434
435pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
461 let output = compile_program_full(source).map_err(CompileError::Parse)?;
462
463 let src_dir = output_dir.join("src");
465 fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
466
467 let main_path = src_dir.join("main.rs");
469 let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
470 file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
471
472 let mut cargo_toml = String::from(r#"[package]
474name = "logos_output"
475version = "0.1.0"
476edition = "2021"
477
478[dependencies]
479logicaffeine-data = { path = "./crates/logicaffeine_data" }
480logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
481tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
482"#);
483
484 for dep in &output.dependencies {
486 if dep.features.is_empty() {
487 let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
488 } else {
489 let feats = dep.features.iter()
490 .map(|f| format!("\"{}\"", f))
491 .collect::<Vec<_>>()
492 .join(", ");
493 let _ = writeln!(
494 cargo_toml,
495 "{} = {{ version = \"{}\", features = [{}] }}",
496 dep.name, dep.version, feats
497 );
498 }
499 }
500
501 let cargo_path = output_dir.join("Cargo.toml");
502 let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
503 file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
504
505 copy_runtime_crates(output_dir)?;
507
508 Ok(())
509}
510
511pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
514 let crates_dir = output_dir.join("crates");
515 fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
516
517 let workspace_root = find_workspace_root()?;
519
520 let data_src = workspace_root.join(CRATES_DATA_PATH);
522 let data_dest = crates_dir.join("logicaffeine_data");
523 copy_dir_recursive(&data_src, &data_dest)?;
524
525 let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
527 let system_dest = crates_dir.join("logicaffeine_system");
528 copy_dir_recursive(&system_src, &system_dest)?;
529
530 let base_src = workspace_root.join("crates/logicaffeine_base");
532 let base_dest = crates_dir.join("logicaffeine_base");
533 copy_dir_recursive(&base_src, &base_dest)?;
534
535 Ok(())
536}
537
538fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
540 if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
542 let path = Path::new(&manifest_dir);
544 if let Some(parent) = path.parent().and_then(|p| p.parent()) {
545 if parent.join("Cargo.toml").exists() {
546 return Ok(parent.to_path_buf());
547 }
548 }
549 }
550
551 let mut current = std::env::current_dir()
553 .map_err(|e| CompileError::Io(e.to_string()))?;
554
555 loop {
556 if current.join("Cargo.toml").exists() && current.join("crates").exists() {
557 return Ok(current);
558 }
559 if !current.pop() {
560 return Err(CompileError::Io("Could not find workspace root".to_string()));
561 }
562 }
563}
564
565fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
568 fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
569
570 for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
571 let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
572 let src_path = entry.path();
573 let file_name = entry.file_name();
574 let dst_path = dst.join(&file_name);
575
576 if file_name == "target"
578 || file_name == ".git"
579 || file_name == "Cargo.lock"
580 || file_name == ".DS_Store"
581 {
582 continue;
583 }
584
585 if file_name.to_string_lossy().starts_with('.') {
587 continue;
588 }
589
590 if !src_path.exists() {
592 continue;
593 }
594
595 if src_path.is_dir() {
596 copy_dir_recursive(&src_path, &dst_path)?;
597 } else if file_name == "Cargo.toml" {
598 match fs::read_to_string(&src_path) {
601 Ok(content) => {
602 let filtered: String = content
603 .lines()
604 .filter(|line| !line.trim().starts_with("[workspace]"))
605 .collect::<Vec<_>>()
606 .join("\n");
607 fs::write(&dst_path, filtered)
608 .map_err(|e| CompileError::Io(e.to_string()))?;
609 }
610 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
611 Err(e) => return Err(CompileError::Io(e.to_string())),
612 }
613 } else {
614 match fs::copy(&src_path, &dst_path) {
615 Ok(_) => {}
616 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
617 Err(e) => return Err(CompileError::Io(e.to_string())),
618 }
619 }
620 }
621
622 Ok(())
623}
624
625pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
663 compile_to_dir(source, output_dir)?;
664
665 let build_output = Command::new("cargo")
667 .arg("build")
668 .arg("--message-format=json")
669 .current_dir(output_dir)
670 .output()
671 .map_err(|e| CompileError::Io(e.to_string()))?;
672
673 if !build_output.status.success() {
674 let stderr = String::from_utf8_lossy(&build_output.stderr);
675 let stdout = String::from_utf8_lossy(&build_output.stdout);
676
677 let diagnostics = parse_rustc_json(&stdout);
679
680 if !diagnostics.is_empty() {
681 let source_map = SourceMap::new(source.to_string());
683 let interner = Interner::new();
684
685 if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
686 return Err(CompileError::Ownership(logos_error));
687 }
688 }
689
690 return Err(CompileError::Build(stderr.to_string()));
692 }
693
694 let run_output = Command::new("cargo")
696 .arg("run")
697 .arg("--quiet")
698 .current_dir(output_dir)
699 .output()
700 .map_err(|e| CompileError::Io(e.to_string()))?;
701
702 if !run_output.status.success() {
703 let stderr = String::from_utf8_lossy(&run_output.stderr);
704 return Err(CompileError::Runtime(stderr.to_string()));
705 }
706
707 let stdout = String::from_utf8_lossy(&run_output.stdout);
708 Ok(stdout.to_string())
709}
710
711pub fn compile_file(path: &Path) -> Result<String, CompileError> {
714 let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
715 compile_to_rust(&source).map_err(CompileError::Parse)
716}
717
718pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
734 use crate::loader::Loader;
735 use crate::analysis::discover_with_imports;
736
737 let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
738 let mut loader = Loader::new(root_path);
739 let mut interner = Interner::new();
740
741 let source = fs::read_to_string(entry_file)
743 .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
744
745 let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
747 .map_err(|e| CompileError::Io(e))?;
748
749 compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
751 .map_err(CompileError::Parse)
752}
753
754fn compile_to_rust_with_registry_full(
757 source: &str,
758 type_registry: crate::analysis::TypeRegistry,
759 interner: &mut Interner,
760) -> Result<CompileOutput, ParseError> {
761 let mut lexer = Lexer::new(source, interner);
762 let tokens = lexer.tokenize();
763
764 let policy_registry = {
766 let mut discovery = DiscoveryPass::new(&tokens, interner);
767 discovery.run_full().policies
768 };
769
770 let codegen_registry = type_registry.clone();
771 let codegen_policies = policy_registry.clone();
772
773 let mut world_state = WorldState::new();
774 let expr_arena = Arena::new();
775 let term_arena = Arena::new();
776 let np_arena = Arena::new();
777 let sym_arena = Arena::new();
778 let role_arena = Arena::new();
779 let pp_arena = Arena::new();
780 let stmt_arena: Arena<Stmt> = Arena::new();
781 let imperative_expr_arena: Arena<Expr> = Arena::new();
782 let type_expr_arena: Arena<TypeExpr> = Arena::new();
783
784 let ast_ctx = AstContext::with_types(
785 &expr_arena,
786 &term_arena,
787 &np_arena,
788 &sym_arena,
789 &role_arena,
790 &pp_arena,
791 &stmt_arena,
792 &imperative_expr_arena,
793 &type_expr_arena,
794 );
795
796 let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
797 let stmts = parser.parse_program()?;
798
799 let dependencies = extract_dependencies(&stmts, interner);
801
802 let mut escape_checker = EscapeChecker::new(interner);
803 escape_checker.check_program(&stmts).map_err(|e| {
804 ParseError {
805 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
806 span: e.span,
807 }
808 })?;
809
810 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner);
811
812 Ok(CompileOutput { rust_code, dependencies })
813}
814
815#[derive(Debug)]
837pub enum CompileError {
838 Parse(ParseError),
843
844 Io(String),
848
849 Build(String),
854
855 Runtime(String),
859
860 Ownership(LogosError),
866}
867
868impl std::fmt::Display for CompileError {
869 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
870 match self {
871 CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
872 CompileError::Io(e) => write!(f, "IO error: {}", e),
873 CompileError::Build(e) => write!(f, "Build error: {}", e),
874 CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
875 CompileError::Ownership(e) => write!(f, "{}", e),
876 }
877 }
878}
879
880impl std::error::Error for CompileError {}
881
882#[cfg(test)]
883mod tests {
884 use super::*;
885
886 #[test]
887 fn test_compile_let_statement() {
888 let source = "## Main\nLet x be 5.";
889 let result = compile_to_rust(source);
890 assert!(result.is_ok(), "Should compile: {:?}", result);
891 let rust = result.unwrap();
892 assert!(rust.contains("fn main()"));
893 assert!(rust.contains("let x = 5;"));
894 }
895
896 #[test]
897 fn test_compile_return_statement() {
898 let source = "## Main\nReturn 42.";
899 let result = compile_to_rust(source);
900 assert!(result.is_ok(), "Should compile: {:?}", result);
901 let rust = result.unwrap();
902 assert!(rust.contains("return 42;"));
903 }
904}