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 interpret_program(source: &str) -> Result<String, ParseError> {
138 let result = crate::ui_bridge::interpret_for_ui_sync(source);
139 if let Some(err) = result.error {
140 Err(ParseError {
141 kind: crate::error::ParseErrorKind::Custom(err),
142 span: crate::token::Span::default(),
143 })
144 } else {
145 Ok(result.lines.join("\n"))
146 }
147}
148
149pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
182 compile_program_full(source).map(|o| o.rust_code)
183}
184
185pub fn compile_to_c(source: &str) -> Result<String, ParseError> {
190 let mut interner = Interner::new();
191 let mut lexer = Lexer::new(source, &mut interner);
192 let tokens = lexer.tokenize();
193
194 let (type_registry, _policy_registry) = {
195 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
196 let result = discovery.run_full();
197 (result.types, result.policies)
198 };
199 let codegen_registry = type_registry.clone();
200
201 let mut world_state = WorldState::new();
202 let expr_arena = Arena::new();
203 let term_arena = Arena::new();
204 let np_arena = Arena::new();
205 let sym_arena = Arena::new();
206 let role_arena = Arena::new();
207 let pp_arena = Arena::new();
208 let stmt_arena: Arena<Stmt> = Arena::new();
209 let imperative_expr_arena: Arena<Expr> = Arena::new();
210 let type_expr_arena: Arena<TypeExpr> = Arena::new();
211
212 let ast_ctx = AstContext::with_types(
213 &expr_arena, &term_arena, &np_arena, &sym_arena,
214 &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
215 &type_expr_arena,
216 );
217
218 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
219 let stmts = parser.parse_program()?;
220 let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
221
222 Ok(crate::codegen_c::codegen_program_c(&stmts, &codegen_registry, &interner))
223}
224
225pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
230 let mut interner = Interner::new();
231 let mut lexer = Lexer::new(source, &mut interner);
232 let tokens = lexer.tokenize();
233
234 let (type_registry, policy_registry) = {
236 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
237 let result = discovery.run_full();
238 (result.types, result.policies)
239 };
240 let codegen_registry = type_registry.clone();
242 let codegen_policies = policy_registry.clone();
243
244 let mut world_state = WorldState::new();
245 let expr_arena = Arena::new();
246 let term_arena = Arena::new();
247 let np_arena = Arena::new();
248 let sym_arena = Arena::new();
249 let role_arena = Arena::new();
250 let pp_arena = Arena::new();
251 let stmt_arena: Arena<Stmt> = Arena::new();
252 let imperative_expr_arena: Arena<Expr> = Arena::new();
253 let type_expr_arena: Arena<TypeExpr> = Arena::new();
254
255 let ast_ctx = AstContext::with_types(
256 &expr_arena,
257 &term_arena,
258 &np_arena,
259 &sym_arena,
260 &role_arena,
261 &pp_arena,
262 &stmt_arena,
263 &imperative_expr_arena,
264 &type_expr_arena,
265 );
266
267 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
269 let stmts = parser.parse_program()?;
272
273 let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
275
276 let mut dependencies = extract_dependencies(&stmts, &interner)?;
278
279 let needs_wasm_bindgen = stmts.iter().any(|stmt| {
281 if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
282 interner.resolve(*target).eq_ignore_ascii_case("wasm")
283 } else {
284 false
285 }
286 });
287 if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
288 dependencies.push(CrateDependency {
289 name: "wasm-bindgen".to_string(),
290 version: "0.2".to_string(),
291 features: vec![],
292 });
293 }
294
295 let mut escape_checker = EscapeChecker::new(&interner);
298 escape_checker.check_program(&stmts).map_err(|e| {
299 ParseError {
302 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
303 span: e.span,
304 }
305 })?;
306
307 let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
311 .map_err(|e| ParseError {
312 kind: e.to_parse_error_kind(&interner),
313 span: crate::token::Span::default(),
314 })?;
315 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
316
317 let has_c = stmts.iter().any(|stmt| {
319 if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
320 match export_target {
321 None => true,
322 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
323 }
324 } else {
325 false
326 }
327 });
328
329 let c_header = if has_c {
330 Some(generate_c_header(&stmts, "module", &interner, &codegen_registry))
331 } else {
332 None
333 };
334
335 if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
337 dependencies.push(CrateDependency {
338 name: "serde_json".to_string(),
339 version: "1".to_string(),
340 features: vec![],
341 });
342 }
343
344 let python_bindings = if has_c {
345 Some(generate_python_bindings(&stmts, "module", &interner, &codegen_registry))
346 } else {
347 None
348 };
349
350 let (typescript_bindings, typescript_types) = if has_c {
351 let (js, dts) = generate_typescript_bindings(&stmts, "module", &interner, &codegen_registry);
352 (Some(js), Some(dts))
353 } else {
354 (None, None)
355 };
356
357 Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
358}
359
360fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Result<Vec<CrateDependency>, ParseError> {
366 use std::collections::HashMap;
367
368 let mut seen: HashMap<String, String> = HashMap::new(); let mut deps: Vec<CrateDependency> = Vec::new();
370
371 for stmt in stmts {
372 if let Stmt::Require { crate_name, version, features, span } = stmt {
373 let name = interner.resolve(*crate_name).to_string();
374 let ver = interner.resolve(*version).to_string();
375
376 if let Some(existing_ver) = seen.get(&name) {
377 if *existing_ver != ver {
378 return Err(ParseError {
379 kind: crate::error::ParseErrorKind::Custom(format!(
380 "Conflicting versions for crate \"{}\": \"{}\" and \"{}\".",
381 name, existing_ver, ver
382 )),
383 span: *span,
384 });
385 }
386 } else {
388 seen.insert(name.clone(), ver.clone());
389 deps.push(CrateDependency {
390 name,
391 version: ver,
392 features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
393 });
394 }
395 }
396 }
397
398 Ok(deps)
399}
400
401pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
437 let mut interner = Interner::new();
438 let mut lexer = Lexer::new(source, &mut interner);
439 let tokens = lexer.tokenize();
440
441 let (type_registry, policy_registry) = {
443 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
444 let result = discovery.run_full();
445 (result.types, result.policies)
446 };
447 let codegen_registry = type_registry.clone();
449 let codegen_policies = policy_registry.clone();
450
451 let mut world_state = WorldState::new();
452 let expr_arena = Arena::new();
453 let term_arena = Arena::new();
454 let np_arena = Arena::new();
455 let sym_arena = Arena::new();
456 let role_arena = Arena::new();
457 let pp_arena = Arena::new();
458 let stmt_arena: Arena<Stmt> = Arena::new();
459 let imperative_expr_arena: Arena<Expr> = Arena::new();
460 let type_expr_arena: Arena<TypeExpr> = Arena::new();
461
462 let ast_ctx = AstContext::with_types(
463 &expr_arena,
464 &term_arena,
465 &np_arena,
466 &sym_arena,
467 &role_arena,
468 &pp_arena,
469 &stmt_arena,
470 &imperative_expr_arena,
471 &type_expr_arena,
472 );
473
474 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
476 let stmts = parser.parse_program()?;
477
478 let mut escape_checker = EscapeChecker::new(&interner);
480 escape_checker.check_program(&stmts).map_err(|e| {
481 ParseError {
482 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
483 span: e.span,
484 }
485 })?;
486
487 let mut ownership_checker = OwnershipChecker::new(&interner);
490 ownership_checker.check_program(&stmts).map_err(|e| {
491 ParseError {
492 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
493 span: e.span,
494 }
495 })?;
496
497 let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
498 .map_err(|e| ParseError {
499 kind: e.to_parse_error_kind(&interner),
500 span: crate::token::Span::default(),
501 })?;
502 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
503
504 Ok(rust_code)
505}
506
507#[cfg(feature = "verification")]
554pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
555 use crate::verification::VerificationPass;
556
557 let mut interner = Interner::new();
558 let mut lexer = Lexer::new(source, &mut interner);
559 let tokens = lexer.tokenize();
560
561 let (type_registry, policy_registry) = {
563 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
564 let result = discovery.run_full();
565 (result.types, result.policies)
566 };
567 let codegen_registry = type_registry.clone();
569 let codegen_policies = policy_registry.clone();
570
571 let mut world_state = WorldState::new();
572 let expr_arena = Arena::new();
573 let term_arena = Arena::new();
574 let np_arena = Arena::new();
575 let sym_arena = Arena::new();
576 let role_arena = Arena::new();
577 let pp_arena = Arena::new();
578 let stmt_arena: Arena<Stmt> = Arena::new();
579 let imperative_expr_arena: Arena<Expr> = Arena::new();
580 let type_expr_arena: Arena<TypeExpr> = Arena::new();
581
582 let ast_ctx = AstContext::with_types(
583 &expr_arena,
584 &term_arena,
585 &np_arena,
586 &sym_arena,
587 &role_arena,
588 &pp_arena,
589 &stmt_arena,
590 &imperative_expr_arena,
591 &type_expr_arena,
592 );
593
594 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
596 let stmts = parser.parse_program()?;
597
598 let mut escape_checker = EscapeChecker::new(&interner);
600 escape_checker.check_program(&stmts).map_err(|e| {
601 ParseError {
602 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
603 span: e.span,
604 }
605 })?;
606
607 let mut verifier = VerificationPass::new(&interner);
609 verifier.verify_program(&stmts).map_err(|e| {
610 ParseError {
611 kind: crate::error::ParseErrorKind::Custom(format!(
612 "Verification Failed:\n\n{}",
613 e
614 )),
615 span: crate::token::Span::default(),
616 }
617 })?;
618
619 let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
620 .map_err(|e| ParseError {
621 kind: e.to_parse_error_kind(&interner),
622 span: crate::token::Span::default(),
623 })?;
624 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
625
626 Ok(rust_code)
627}
628
629pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
660 let output = compile_program_full(source).map_err(CompileError::Parse)?;
661
662 let src_dir = output_dir.join("src");
664 fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
665
666 let main_path = src_dir.join("main.rs");
668 let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
669 file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
670
671 let mut cargo_toml = String::from(r#"[package]
673name = "logos_output"
674version = "0.1.0"
675edition = "2021"
676
677[dependencies]
678logicaffeine-data = { path = "./crates/logicaffeine_data" }
679logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
680tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
681
682[target.'cfg(target_os = "linux")'.dependencies]
683logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full", "io-uring"] }
684"#);
685
686 for dep in &output.dependencies {
688 if dep.features.is_empty() {
689 let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
690 } else {
691 let feats = dep.features.iter()
692 .map(|f| format!("\"{}\"", f))
693 .collect::<Vec<_>>()
694 .join(", ");
695 let _ = writeln!(
696 cargo_toml,
697 "{} = {{ version = \"{}\", features = [{}] }}",
698 dep.name, dep.version, feats
699 );
700 }
701 }
702
703 cargo_toml.push_str("\n[profile.release]\nlto = true\nopt-level = 3\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\n");
704
705 let cargo_path = output_dir.join("Cargo.toml");
706 let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
707 file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
708
709 let cargo_config_dir = output_dir.join(".cargo");
712 fs::create_dir_all(&cargo_config_dir).map_err(|e| CompileError::Io(e.to_string()))?;
713 let config_content = "[build]\nrustflags = [\"-C\", \"target-cpu=native\"]\n";
714 let config_path = cargo_config_dir.join("config.toml");
715 fs::write(&config_path, config_content).map_err(|e| CompileError::Io(e.to_string()))?;
716
717 copy_runtime_crates(output_dir)?;
719
720 Ok(())
721}
722
723pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
726 let crates_dir = output_dir.join("crates");
727 fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
728
729 let workspace_root = find_workspace_root()?;
731
732 let data_src = workspace_root.join(CRATES_DATA_PATH);
734 let data_dest = crates_dir.join("logicaffeine_data");
735 copy_dir_recursive(&data_src, &data_dest)?;
736 deworkspace_cargo_toml(&data_dest.join("Cargo.toml"))?;
737
738 let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
740 let system_dest = crates_dir.join("logicaffeine_system");
741 copy_dir_recursive(&system_src, &system_dest)?;
742 deworkspace_cargo_toml(&system_dest.join("Cargo.toml"))?;
743
744 let base_src = workspace_root.join("crates/logicaffeine_base");
746 let base_dest = crates_dir.join("logicaffeine_base");
747 copy_dir_recursive(&base_src, &base_dest)?;
748 deworkspace_cargo_toml(&base_dest.join("Cargo.toml"))?;
749
750 Ok(())
751}
752
753fn deworkspace_cargo_toml(cargo_toml_path: &Path) -> Result<(), CompileError> {
759 let content = fs::read_to_string(cargo_toml_path)
760 .map_err(|e| CompileError::Io(e.to_string()))?;
761
762 let mut result = String::with_capacity(content.len());
763 for line in content.lines() {
764 let trimmed = line.trim();
765 if trimmed == "edition.workspace = true" {
766 result.push_str("edition = \"2021\"");
767 } else if trimmed == "rust-version.workspace = true" {
768 result.push_str("rust-version = \"1.75\"");
769 } else if trimmed == "authors.workspace = true"
770 || trimmed == "repository.workspace = true"
771 || trimmed == "homepage.workspace = true"
772 || trimmed == "documentation.workspace = true"
773 || trimmed == "keywords.workspace = true"
774 || trimmed == "categories.workspace = true"
775 || trimmed == "license.workspace = true"
776 {
777 continue;
779 } else if trimmed.contains(".workspace = true") {
780 continue;
782 } else {
783 result.push_str(line);
784 }
785 result.push('\n');
786 }
787
788 fs::write(cargo_toml_path, result)
789 .map_err(|e| CompileError::Io(e.to_string()))?;
790
791 Ok(())
792}
793
794fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
796 if let Ok(workspace) = std::env::var("LOGOS_WORKSPACE") {
798 let path = Path::new(&workspace);
799 if path.join("Cargo.toml").exists() && path.join("crates").exists() {
800 return Ok(path.to_path_buf());
801 }
802 }
803
804 if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
806 let path = Path::new(&manifest_dir);
807 if let Some(parent) = path.parent().and_then(|p| p.parent()) {
808 if parent.join("Cargo.toml").exists() {
809 return Ok(parent.to_path_buf());
810 }
811 }
812 }
813
814 if let Ok(exe) = std::env::current_exe() {
817 if let Some(dir) = exe.parent() {
818 let mut candidate = dir.to_path_buf();
820 for _ in 0..5 {
821 if candidate.join("Cargo.toml").exists() && candidate.join("crates").exists() {
822 return Ok(candidate);
823 }
824 if !candidate.pop() {
825 break;
826 }
827 }
828 }
829 }
830
831 let mut current = std::env::current_dir()
833 .map_err(|e| CompileError::Io(e.to_string()))?;
834
835 loop {
836 if current.join("Cargo.toml").exists() && current.join("crates").exists() {
837 return Ok(current);
838 }
839 if !current.pop() {
840 return Err(CompileError::Io(
841 "Could not find workspace root. Set LOGOS_WORKSPACE env var or run from within the workspace.".to_string()
842 ));
843 }
844 }
845}
846
847fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
850 fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
851
852 for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
853 let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
854 let src_path = entry.path();
855 let file_name = entry.file_name();
856 let dst_path = dst.join(&file_name);
857
858 if file_name == "target"
860 || file_name == ".git"
861 || file_name == "Cargo.lock"
862 || file_name == ".DS_Store"
863 {
864 continue;
865 }
866
867 if file_name.to_string_lossy().starts_with('.') {
869 continue;
870 }
871
872 if !src_path.exists() {
874 continue;
875 }
876
877 if src_path.is_dir() {
878 copy_dir_recursive(&src_path, &dst_path)?;
879 } else if file_name == "Cargo.toml" {
880 match fs::read_to_string(&src_path) {
883 Ok(content) => {
884 let filtered: String = content
885 .lines()
886 .filter(|line| !line.trim().starts_with("[workspace]"))
887 .collect::<Vec<_>>()
888 .join("\n");
889 fs::write(&dst_path, filtered)
890 .map_err(|e| CompileError::Io(e.to_string()))?;
891 }
892 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
893 Err(e) => return Err(CompileError::Io(e.to_string())),
894 }
895 } else {
896 match fs::copy(&src_path, &dst_path) {
897 Ok(_) => {}
898 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
899 Err(e) => return Err(CompileError::Io(e.to_string())),
900 }
901 }
902 }
903
904 Ok(())
905}
906
907pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
950 compile_to_rust_checked(source).map_err(CompileError::Parse)?;
953
954 compile_to_dir(source, output_dir)?;
955
956 let build_output = Command::new("cargo")
958 .arg("build")
959 .arg("--message-format=json")
960 .current_dir(output_dir)
961 .output()
962 .map_err(|e| CompileError::Io(e.to_string()))?;
963
964 if !build_output.status.success() {
965 let stderr = String::from_utf8_lossy(&build_output.stderr);
966 let stdout = String::from_utf8_lossy(&build_output.stdout);
967
968 let diagnostics = parse_rustc_json(&stdout);
970
971 if !diagnostics.is_empty() {
972 let source_map = SourceMap::new(source.to_string());
974 let interner = Interner::new();
975
976 if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
977 return Err(CompileError::Ownership(logos_error));
978 }
979 }
980
981 return Err(CompileError::Build(stderr.to_string()));
983 }
984
985 let run_output = Command::new("cargo")
987 .arg("run")
988 .arg("--quiet")
989 .current_dir(output_dir)
990 .output()
991 .map_err(|e| CompileError::Io(e.to_string()))?;
992
993 if !run_output.status.success() {
994 let stderr = String::from_utf8_lossy(&run_output.stderr);
995 return Err(CompileError::Runtime(stderr.to_string()));
996 }
997
998 let stdout = String::from_utf8_lossy(&run_output.stdout);
999 Ok(stdout.to_string())
1000}
1001
1002pub fn compile_file(path: &Path) -> Result<String, CompileError> {
1005 let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
1006 compile_to_rust(&source).map_err(CompileError::Parse)
1007}
1008
1009pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
1027 use crate::loader::Loader;
1028 use crate::analysis::discover_with_imports;
1029
1030 let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
1031 let mut loader = Loader::new(root_path);
1032 let mut interner = Interner::new();
1033
1034 let source = fs::read_to_string(entry_file)
1036 .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
1037
1038 let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
1040 .map_err(|e| CompileError::Io(e))?;
1041
1042 compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
1044 .map_err(CompileError::Parse)
1045}
1046
1047fn compile_to_rust_with_registry_full(
1050 source: &str,
1051 type_registry: crate::analysis::TypeRegistry,
1052 interner: &mut Interner,
1053) -> Result<CompileOutput, ParseError> {
1054 let mut lexer = Lexer::new(source, interner);
1055 let tokens = lexer.tokenize();
1056
1057 let policy_registry = {
1059 let mut discovery = DiscoveryPass::new(&tokens, interner);
1060 discovery.run_full().policies
1061 };
1062
1063 let codegen_registry = type_registry.clone();
1064 let codegen_policies = policy_registry.clone();
1065
1066 let mut world_state = WorldState::new();
1067 let expr_arena = Arena::new();
1068 let term_arena = Arena::new();
1069 let np_arena = Arena::new();
1070 let sym_arena = Arena::new();
1071 let role_arena = Arena::new();
1072 let pp_arena = Arena::new();
1073 let stmt_arena: Arena<Stmt> = Arena::new();
1074 let imperative_expr_arena: Arena<Expr> = Arena::new();
1075 let type_expr_arena: Arena<TypeExpr> = Arena::new();
1076
1077 let ast_ctx = AstContext::with_types(
1078 &expr_arena,
1079 &term_arena,
1080 &np_arena,
1081 &sym_arena,
1082 &role_arena,
1083 &pp_arena,
1084 &stmt_arena,
1085 &imperative_expr_arena,
1086 &type_expr_arena,
1087 );
1088
1089 let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
1090 let stmts = parser.parse_program()?;
1091
1092 let mut dependencies = extract_dependencies(&stmts, interner)?;
1094
1095 let needs_wasm_bindgen = stmts.iter().any(|stmt| {
1097 if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
1098 interner.resolve(*target).eq_ignore_ascii_case("wasm")
1099 } else {
1100 false
1101 }
1102 });
1103 if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
1104 dependencies.push(CrateDependency {
1105 name: "wasm-bindgen".to_string(),
1106 version: "0.2".to_string(),
1107 features: vec![],
1108 });
1109 }
1110
1111 let mut escape_checker = EscapeChecker::new(interner);
1112 escape_checker.check_program(&stmts).map_err(|e| {
1113 ParseError {
1114 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
1115 span: e.span,
1116 }
1117 })?;
1118
1119 let type_env = crate::analysis::check_program(&stmts, interner, &codegen_registry)
1120 .map_err(|e| ParseError {
1121 kind: e.to_parse_error_kind(interner),
1122 span: crate::token::Span::default(),
1123 })?;
1124 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner, &type_env);
1125
1126 let has_c = stmts.iter().any(|stmt| {
1128 if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
1129 match export_target {
1130 None => true,
1131 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
1132 }
1133 } else {
1134 false
1135 }
1136 });
1137
1138 let c_header = if has_c {
1139 Some(generate_c_header(&stmts, "module", interner, &codegen_registry))
1140 } else {
1141 None
1142 };
1143
1144 if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
1145 dependencies.push(CrateDependency {
1146 name: "serde_json".to_string(),
1147 version: "1".to_string(),
1148 features: vec![],
1149 });
1150 }
1151
1152 let python_bindings = if has_c {
1153 Some(generate_python_bindings(&stmts, "module", interner, &codegen_registry))
1154 } else {
1155 None
1156 };
1157
1158 let (typescript_bindings, typescript_types) = if has_c {
1159 let (js, dts) = generate_typescript_bindings(&stmts, "module", interner, &codegen_registry);
1160 (Some(js), Some(dts))
1161 } else {
1162 (None, None)
1163 };
1164
1165 Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
1166}
1167
1168#[derive(Debug)]
1190pub enum CompileError {
1191 Parse(ParseError),
1196
1197 Io(String),
1201
1202 Build(String),
1207
1208 Runtime(String),
1212
1213 Ownership(LogosError),
1219}
1220
1221impl std::fmt::Display for CompileError {
1222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1223 match self {
1224 CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
1225 CompileError::Io(e) => write!(f, "IO error: {}", e),
1226 CompileError::Build(e) => write!(f, "Build error: {}", e),
1227 CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
1228 CompileError::Ownership(e) => write!(f, "{}", e),
1229 }
1230 }
1231}
1232
1233impl std::error::Error for CompileError {}
1234
1235#[cfg(test)]
1236mod tests {
1237 use super::*;
1238
1239 #[test]
1240 fn test_compile_let_statement() {
1241 let source = "## Main\nLet x be 5.";
1242 let result = compile_to_rust(source);
1243 assert!(result.is_ok(), "Should compile: {:?}", result);
1244 let rust = result.unwrap();
1245 assert!(rust.contains("fn main()"));
1246 assert!(rust.contains("let x = 5;"));
1247 }
1248
1249 #[test]
1250 fn test_compile_return_statement() {
1251 let source = "## Main\nReturn 42.";
1252 let result = compile_to_rust(source);
1253 assert!(result.is_ok(), "Should compile: {:?}", result);
1254 let rust = result.unwrap();
1255 assert!(rust.contains("return 42;"));
1256 }
1257}