1use std::fs;
74use std::io::Write;
75use std::path::Path;
76use std::process::Command;
77
78const CRATES_DATA_PATH: &str = "crates/logicaffeine_data";
80const CRATES_SYSTEM_PATH: &str = "crates/logicaffeine_system";
81
82use std::fmt::Write as FmtWrite;
83
84use crate::analysis::{DiscoveryPass, EscapeChecker, OwnershipChecker, PolicyRegistry};
85use crate::arena::Arena;
86use crate::arena_ctx::AstContext;
87use crate::ast::{Expr, Stmt, TypeExpr};
88use crate::codegen::{codegen_program, generate_c_header, generate_python_bindings, generate_typescript_bindings};
89use crate::diagnostic::{parse_rustc_json, translate_diagnostics, LogosError};
90use crate::drs::WorldState;
91use crate::error::ParseError;
92use crate::intern::Interner;
93use crate::lexer::Lexer;
94use crate::parser::Parser;
95use crate::sourcemap::SourceMap;
96
97#[derive(Debug, Clone)]
99pub struct CrateDependency {
100 pub name: String,
101 pub version: String,
102 pub features: Vec<String>,
103}
104
105#[derive(Debug)]
107pub struct CompileOutput {
108 pub rust_code: String,
109 pub dependencies: Vec<CrateDependency>,
110 pub c_header: Option<String>,
112 pub python_bindings: Option<String>,
114 pub typescript_types: Option<String>,
116 pub typescript_bindings: Option<String>,
118}
119
120pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
153 compile_program_full(source).map(|o| o.rust_code)
154}
155
156pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
161 let mut interner = Interner::new();
162 let mut lexer = Lexer::new(source, &mut interner);
163 let tokens = lexer.tokenize();
164
165 let (type_registry, policy_registry) = {
167 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
168 let result = discovery.run_full();
169 (result.types, result.policies)
170 };
171 let codegen_registry = type_registry.clone();
173 let codegen_policies = policy_registry.clone();
174
175 let mut world_state = WorldState::new();
176 let expr_arena = Arena::new();
177 let term_arena = Arena::new();
178 let np_arena = Arena::new();
179 let sym_arena = Arena::new();
180 let role_arena = Arena::new();
181 let pp_arena = Arena::new();
182 let stmt_arena: Arena<Stmt> = Arena::new();
183 let imperative_expr_arena: Arena<Expr> = Arena::new();
184 let type_expr_arena: Arena<TypeExpr> = Arena::new();
185
186 let ast_ctx = AstContext::with_types(
187 &expr_arena,
188 &term_arena,
189 &np_arena,
190 &sym_arena,
191 &role_arena,
192 &pp_arena,
193 &stmt_arena,
194 &imperative_expr_arena,
195 &type_expr_arena,
196 );
197
198 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
200 let stmts = parser.parse_program()?;
203
204 let mut dependencies = extract_dependencies(&stmts, &interner)?;
206
207 let needs_wasm_bindgen = stmts.iter().any(|stmt| {
209 if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
210 interner.resolve(*target).eq_ignore_ascii_case("wasm")
211 } else {
212 false
213 }
214 });
215 if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
216 dependencies.push(CrateDependency {
217 name: "wasm-bindgen".to_string(),
218 version: "0.2".to_string(),
219 features: vec![],
220 });
221 }
222
223 let mut escape_checker = EscapeChecker::new(&interner);
226 escape_checker.check_program(&stmts).map_err(|e| {
227 ParseError {
230 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
231 span: e.span,
232 }
233 })?;
234
235 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
239
240 let has_c = stmts.iter().any(|stmt| {
242 if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
243 match export_target {
244 None => true,
245 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
246 }
247 } else {
248 false
249 }
250 });
251
252 let c_header = if has_c {
253 Some(generate_c_header(&stmts, "module", &interner, &codegen_registry))
254 } else {
255 None
256 };
257
258 if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
260 dependencies.push(CrateDependency {
261 name: "serde_json".to_string(),
262 version: "1".to_string(),
263 features: vec![],
264 });
265 }
266
267 let python_bindings = if has_c {
268 Some(generate_python_bindings(&stmts, "module", &interner, &codegen_registry))
269 } else {
270 None
271 };
272
273 let (typescript_bindings, typescript_types) = if has_c {
274 let (js, dts) = generate_typescript_bindings(&stmts, "module", &interner, &codegen_registry);
275 (Some(js), Some(dts))
276 } else {
277 (None, None)
278 };
279
280 Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
281}
282
283fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Result<Vec<CrateDependency>, ParseError> {
289 use std::collections::HashMap;
290
291 let mut seen: HashMap<String, String> = HashMap::new(); let mut deps: Vec<CrateDependency> = Vec::new();
293
294 for stmt in stmts {
295 if let Stmt::Require { crate_name, version, features, span } = stmt {
296 let name = interner.resolve(*crate_name).to_string();
297 let ver = interner.resolve(*version).to_string();
298
299 if let Some(existing_ver) = seen.get(&name) {
300 if *existing_ver != ver {
301 return Err(ParseError {
302 kind: crate::error::ParseErrorKind::Custom(format!(
303 "Conflicting versions for crate \"{}\": \"{}\" and \"{}\".",
304 name, existing_ver, ver
305 )),
306 span: *span,
307 });
308 }
309 } else {
311 seen.insert(name.clone(), ver.clone());
312 deps.push(CrateDependency {
313 name,
314 version: ver,
315 features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
316 });
317 }
318 }
319 }
320
321 Ok(deps)
322}
323
324pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
360 let mut interner = Interner::new();
361 let mut lexer = Lexer::new(source, &mut interner);
362 let tokens = lexer.tokenize();
363
364 let (type_registry, policy_registry) = {
366 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
367 let result = discovery.run_full();
368 (result.types, result.policies)
369 };
370 let codegen_registry = type_registry.clone();
372 let codegen_policies = policy_registry.clone();
373
374 let mut world_state = WorldState::new();
375 let expr_arena = Arena::new();
376 let term_arena = Arena::new();
377 let np_arena = Arena::new();
378 let sym_arena = Arena::new();
379 let role_arena = Arena::new();
380 let pp_arena = Arena::new();
381 let stmt_arena: Arena<Stmt> = Arena::new();
382 let imperative_expr_arena: Arena<Expr> = Arena::new();
383 let type_expr_arena: Arena<TypeExpr> = Arena::new();
384
385 let ast_ctx = AstContext::with_types(
386 &expr_arena,
387 &term_arena,
388 &np_arena,
389 &sym_arena,
390 &role_arena,
391 &pp_arena,
392 &stmt_arena,
393 &imperative_expr_arena,
394 &type_expr_arena,
395 );
396
397 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
399 let stmts = parser.parse_program()?;
400
401 let mut escape_checker = EscapeChecker::new(&interner);
403 escape_checker.check_program(&stmts).map_err(|e| {
404 ParseError {
405 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
406 span: e.span,
407 }
408 })?;
409
410 let mut ownership_checker = OwnershipChecker::new(&interner);
413 ownership_checker.check_program(&stmts).map_err(|e| {
414 ParseError {
415 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
416 span: e.span,
417 }
418 })?;
419
420 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
421
422 Ok(rust_code)
423}
424
425#[cfg(feature = "verification")]
472pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
473 use crate::verification::VerificationPass;
474
475 let mut interner = Interner::new();
476 let mut lexer = Lexer::new(source, &mut interner);
477 let tokens = lexer.tokenize();
478
479 let (type_registry, policy_registry) = {
481 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
482 let result = discovery.run_full();
483 (result.types, result.policies)
484 };
485 let codegen_registry = type_registry.clone();
487 let codegen_policies = policy_registry.clone();
488
489 let mut world_state = WorldState::new();
490 let expr_arena = Arena::new();
491 let term_arena = Arena::new();
492 let np_arena = Arena::new();
493 let sym_arena = Arena::new();
494 let role_arena = Arena::new();
495 let pp_arena = Arena::new();
496 let stmt_arena: Arena<Stmt> = Arena::new();
497 let imperative_expr_arena: Arena<Expr> = Arena::new();
498 let type_expr_arena: Arena<TypeExpr> = Arena::new();
499
500 let ast_ctx = AstContext::with_types(
501 &expr_arena,
502 &term_arena,
503 &np_arena,
504 &sym_arena,
505 &role_arena,
506 &pp_arena,
507 &stmt_arena,
508 &imperative_expr_arena,
509 &type_expr_arena,
510 );
511
512 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
514 let stmts = parser.parse_program()?;
515
516 let mut escape_checker = EscapeChecker::new(&interner);
518 escape_checker.check_program(&stmts).map_err(|e| {
519 ParseError {
520 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
521 span: e.span,
522 }
523 })?;
524
525 let mut verifier = VerificationPass::new(&interner);
527 verifier.verify_program(&stmts).map_err(|e| {
528 ParseError {
529 kind: crate::error::ParseErrorKind::Custom(format!(
530 "Verification Failed:\n\n{}",
531 e
532 )),
533 span: crate::token::Span::default(),
534 }
535 })?;
536
537 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
538
539 Ok(rust_code)
540}
541
542pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
573 let output = compile_program_full(source).map_err(CompileError::Parse)?;
574
575 let src_dir = output_dir.join("src");
577 fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
578
579 let main_path = src_dir.join("main.rs");
581 let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
582 file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
583
584 let mut cargo_toml = String::from(r#"[package]
586name = "logos_output"
587version = "0.1.0"
588edition = "2021"
589
590[dependencies]
591logicaffeine-data = { path = "./crates/logicaffeine_data" }
592logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
593tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
594"#);
595
596 for dep in &output.dependencies {
598 if dep.features.is_empty() {
599 let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
600 } else {
601 let feats = dep.features.iter()
602 .map(|f| format!("\"{}\"", f))
603 .collect::<Vec<_>>()
604 .join(", ");
605 let _ = writeln!(
606 cargo_toml,
607 "{} = {{ version = \"{}\", features = [{}] }}",
608 dep.name, dep.version, feats
609 );
610 }
611 }
612
613 let cargo_path = output_dir.join("Cargo.toml");
614 let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
615 file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
616
617 copy_runtime_crates(output_dir)?;
619
620 Ok(())
621}
622
623pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
626 let crates_dir = output_dir.join("crates");
627 fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
628
629 let workspace_root = find_workspace_root()?;
631
632 let data_src = workspace_root.join(CRATES_DATA_PATH);
634 let data_dest = crates_dir.join("logicaffeine_data");
635 copy_dir_recursive(&data_src, &data_dest)?;
636
637 let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
639 let system_dest = crates_dir.join("logicaffeine_system");
640 copy_dir_recursive(&system_src, &system_dest)?;
641
642 let base_src = workspace_root.join("crates/logicaffeine_base");
644 let base_dest = crates_dir.join("logicaffeine_base");
645 copy_dir_recursive(&base_src, &base_dest)?;
646
647 Ok(())
648}
649
650fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
652 if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
654 let path = Path::new(&manifest_dir);
656 if let Some(parent) = path.parent().and_then(|p| p.parent()) {
657 if parent.join("Cargo.toml").exists() {
658 return Ok(parent.to_path_buf());
659 }
660 }
661 }
662
663 let mut current = std::env::current_dir()
665 .map_err(|e| CompileError::Io(e.to_string()))?;
666
667 loop {
668 if current.join("Cargo.toml").exists() && current.join("crates").exists() {
669 return Ok(current);
670 }
671 if !current.pop() {
672 return Err(CompileError::Io("Could not find workspace root".to_string()));
673 }
674 }
675}
676
677fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
680 fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
681
682 for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
683 let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
684 let src_path = entry.path();
685 let file_name = entry.file_name();
686 let dst_path = dst.join(&file_name);
687
688 if file_name == "target"
690 || file_name == ".git"
691 || file_name == "Cargo.lock"
692 || file_name == ".DS_Store"
693 {
694 continue;
695 }
696
697 if file_name.to_string_lossy().starts_with('.') {
699 continue;
700 }
701
702 if !src_path.exists() {
704 continue;
705 }
706
707 if src_path.is_dir() {
708 copy_dir_recursive(&src_path, &dst_path)?;
709 } else if file_name == "Cargo.toml" {
710 match fs::read_to_string(&src_path) {
713 Ok(content) => {
714 let filtered: String = content
715 .lines()
716 .filter(|line| !line.trim().starts_with("[workspace]"))
717 .collect::<Vec<_>>()
718 .join("\n");
719 fs::write(&dst_path, filtered)
720 .map_err(|e| CompileError::Io(e.to_string()))?;
721 }
722 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
723 Err(e) => return Err(CompileError::Io(e.to_string())),
724 }
725 } else {
726 match fs::copy(&src_path, &dst_path) {
727 Ok(_) => {}
728 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
729 Err(e) => return Err(CompileError::Io(e.to_string())),
730 }
731 }
732 }
733
734 Ok(())
735}
736
737pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
780 compile_to_dir(source, output_dir)?;
781
782 let build_output = Command::new("cargo")
784 .arg("build")
785 .arg("--message-format=json")
786 .current_dir(output_dir)
787 .output()
788 .map_err(|e| CompileError::Io(e.to_string()))?;
789
790 if !build_output.status.success() {
791 let stderr = String::from_utf8_lossy(&build_output.stderr);
792 let stdout = String::from_utf8_lossy(&build_output.stdout);
793
794 let diagnostics = parse_rustc_json(&stdout);
796
797 if !diagnostics.is_empty() {
798 let source_map = SourceMap::new(source.to_string());
800 let interner = Interner::new();
801
802 if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
803 return Err(CompileError::Ownership(logos_error));
804 }
805 }
806
807 return Err(CompileError::Build(stderr.to_string()));
809 }
810
811 let run_output = Command::new("cargo")
813 .arg("run")
814 .arg("--quiet")
815 .current_dir(output_dir)
816 .output()
817 .map_err(|e| CompileError::Io(e.to_string()))?;
818
819 if !run_output.status.success() {
820 let stderr = String::from_utf8_lossy(&run_output.stderr);
821 return Err(CompileError::Runtime(stderr.to_string()));
822 }
823
824 let stdout = String::from_utf8_lossy(&run_output.stdout);
825 Ok(stdout.to_string())
826}
827
828pub fn compile_file(path: &Path) -> Result<String, CompileError> {
831 let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
832 compile_to_rust(&source).map_err(CompileError::Parse)
833}
834
835pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
853 use crate::loader::Loader;
854 use crate::analysis::discover_with_imports;
855
856 let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
857 let mut loader = Loader::new(root_path);
858 let mut interner = Interner::new();
859
860 let source = fs::read_to_string(entry_file)
862 .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
863
864 let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
866 .map_err(|e| CompileError::Io(e))?;
867
868 compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
870 .map_err(CompileError::Parse)
871}
872
873fn compile_to_rust_with_registry_full(
876 source: &str,
877 type_registry: crate::analysis::TypeRegistry,
878 interner: &mut Interner,
879) -> Result<CompileOutput, ParseError> {
880 let mut lexer = Lexer::new(source, interner);
881 let tokens = lexer.tokenize();
882
883 let policy_registry = {
885 let mut discovery = DiscoveryPass::new(&tokens, interner);
886 discovery.run_full().policies
887 };
888
889 let codegen_registry = type_registry.clone();
890 let codegen_policies = policy_registry.clone();
891
892 let mut world_state = WorldState::new();
893 let expr_arena = Arena::new();
894 let term_arena = Arena::new();
895 let np_arena = Arena::new();
896 let sym_arena = Arena::new();
897 let role_arena = Arena::new();
898 let pp_arena = Arena::new();
899 let stmt_arena: Arena<Stmt> = Arena::new();
900 let imperative_expr_arena: Arena<Expr> = Arena::new();
901 let type_expr_arena: Arena<TypeExpr> = Arena::new();
902
903 let ast_ctx = AstContext::with_types(
904 &expr_arena,
905 &term_arena,
906 &np_arena,
907 &sym_arena,
908 &role_arena,
909 &pp_arena,
910 &stmt_arena,
911 &imperative_expr_arena,
912 &type_expr_arena,
913 );
914
915 let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
916 let stmts = parser.parse_program()?;
917
918 let mut dependencies = extract_dependencies(&stmts, interner)?;
920
921 let needs_wasm_bindgen = stmts.iter().any(|stmt| {
923 if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
924 interner.resolve(*target).eq_ignore_ascii_case("wasm")
925 } else {
926 false
927 }
928 });
929 if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
930 dependencies.push(CrateDependency {
931 name: "wasm-bindgen".to_string(),
932 version: "0.2".to_string(),
933 features: vec![],
934 });
935 }
936
937 let mut escape_checker = EscapeChecker::new(interner);
938 escape_checker.check_program(&stmts).map_err(|e| {
939 ParseError {
940 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
941 span: e.span,
942 }
943 })?;
944
945 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner);
946
947 let has_c = stmts.iter().any(|stmt| {
949 if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
950 match export_target {
951 None => true,
952 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
953 }
954 } else {
955 false
956 }
957 });
958
959 let c_header = if has_c {
960 Some(generate_c_header(&stmts, "module", interner, &codegen_registry))
961 } else {
962 None
963 };
964
965 if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
966 dependencies.push(CrateDependency {
967 name: "serde_json".to_string(),
968 version: "1".to_string(),
969 features: vec![],
970 });
971 }
972
973 let python_bindings = if has_c {
974 Some(generate_python_bindings(&stmts, "module", interner, &codegen_registry))
975 } else {
976 None
977 };
978
979 let (typescript_bindings, typescript_types) = if has_c {
980 let (js, dts) = generate_typescript_bindings(&stmts, "module", interner, &codegen_registry);
981 (Some(js), Some(dts))
982 } else {
983 (None, None)
984 };
985
986 Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
987}
988
989#[derive(Debug)]
1011pub enum CompileError {
1012 Parse(ParseError),
1017
1018 Io(String),
1022
1023 Build(String),
1028
1029 Runtime(String),
1033
1034 Ownership(LogosError),
1040}
1041
1042impl std::fmt::Display for CompileError {
1043 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1044 match self {
1045 CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
1046 CompileError::Io(e) => write!(f, "IO error: {}", e),
1047 CompileError::Build(e) => write!(f, "Build error: {}", e),
1048 CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
1049 CompileError::Ownership(e) => write!(f, "{}", e),
1050 }
1051 }
1052}
1053
1054impl std::error::Error for CompileError {}
1055
1056#[cfg(test)]
1057mod tests {
1058 use super::*;
1059
1060 #[test]
1061 fn test_compile_let_statement() {
1062 let source = "## Main\nLet x be 5.";
1063 let result = compile_to_rust(source);
1064 assert!(result.is_ok(), "Should compile: {:?}", result);
1065 let rust = result.unwrap();
1066 assert!(rust.contains("fn main()"));
1067 assert!(rust.contains("let x = 5;"));
1068 }
1069
1070 #[test]
1071 fn test_compile_return_statement() {
1072 let source = "## Main\nReturn 42.";
1073 let result = compile_to_rust(source);
1074 assert!(result.is_ok(), "Should compile: {:?}", result);
1075 let rust = result.unwrap();
1076 assert!(rust.contains("return 42;"));
1077 }
1078}