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 rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
311
312 let has_c = stmts.iter().any(|stmt| {
314 if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
315 match export_target {
316 None => true,
317 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
318 }
319 } else {
320 false
321 }
322 });
323
324 let c_header = if has_c {
325 Some(generate_c_header(&stmts, "module", &interner, &codegen_registry))
326 } else {
327 None
328 };
329
330 if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
332 dependencies.push(CrateDependency {
333 name: "serde_json".to_string(),
334 version: "1".to_string(),
335 features: vec![],
336 });
337 }
338
339 let python_bindings = if has_c {
340 Some(generate_python_bindings(&stmts, "module", &interner, &codegen_registry))
341 } else {
342 None
343 };
344
345 let (typescript_bindings, typescript_types) = if has_c {
346 let (js, dts) = generate_typescript_bindings(&stmts, "module", &interner, &codegen_registry);
347 (Some(js), Some(dts))
348 } else {
349 (None, None)
350 };
351
352 Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
353}
354
355fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Result<Vec<CrateDependency>, ParseError> {
361 use std::collections::HashMap;
362
363 let mut seen: HashMap<String, String> = HashMap::new(); let mut deps: Vec<CrateDependency> = Vec::new();
365
366 for stmt in stmts {
367 if let Stmt::Require { crate_name, version, features, span } = stmt {
368 let name = interner.resolve(*crate_name).to_string();
369 let ver = interner.resolve(*version).to_string();
370
371 if let Some(existing_ver) = seen.get(&name) {
372 if *existing_ver != ver {
373 return Err(ParseError {
374 kind: crate::error::ParseErrorKind::Custom(format!(
375 "Conflicting versions for crate \"{}\": \"{}\" and \"{}\".",
376 name, existing_ver, ver
377 )),
378 span: *span,
379 });
380 }
381 } else {
383 seen.insert(name.clone(), ver.clone());
384 deps.push(CrateDependency {
385 name,
386 version: ver,
387 features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
388 });
389 }
390 }
391 }
392
393 Ok(deps)
394}
395
396pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
432 let mut interner = Interner::new();
433 let mut lexer = Lexer::new(source, &mut interner);
434 let tokens = lexer.tokenize();
435
436 let (type_registry, policy_registry) = {
438 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
439 let result = discovery.run_full();
440 (result.types, result.policies)
441 };
442 let codegen_registry = type_registry.clone();
444 let codegen_policies = policy_registry.clone();
445
446 let mut world_state = WorldState::new();
447 let expr_arena = Arena::new();
448 let term_arena = Arena::new();
449 let np_arena = Arena::new();
450 let sym_arena = Arena::new();
451 let role_arena = Arena::new();
452 let pp_arena = Arena::new();
453 let stmt_arena: Arena<Stmt> = Arena::new();
454 let imperative_expr_arena: Arena<Expr> = Arena::new();
455 let type_expr_arena: Arena<TypeExpr> = Arena::new();
456
457 let ast_ctx = AstContext::with_types(
458 &expr_arena,
459 &term_arena,
460 &np_arena,
461 &sym_arena,
462 &role_arena,
463 &pp_arena,
464 &stmt_arena,
465 &imperative_expr_arena,
466 &type_expr_arena,
467 );
468
469 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
471 let stmts = parser.parse_program()?;
472
473 let mut escape_checker = EscapeChecker::new(&interner);
475 escape_checker.check_program(&stmts).map_err(|e| {
476 ParseError {
477 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
478 span: e.span,
479 }
480 })?;
481
482 let mut ownership_checker = OwnershipChecker::new(&interner);
485 ownership_checker.check_program(&stmts).map_err(|e| {
486 ParseError {
487 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
488 span: e.span,
489 }
490 })?;
491
492 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
493
494 Ok(rust_code)
495}
496
497#[cfg(feature = "verification")]
544pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
545 use crate::verification::VerificationPass;
546
547 let mut interner = Interner::new();
548 let mut lexer = Lexer::new(source, &mut interner);
549 let tokens = lexer.tokenize();
550
551 let (type_registry, policy_registry) = {
553 let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
554 let result = discovery.run_full();
555 (result.types, result.policies)
556 };
557 let codegen_registry = type_registry.clone();
559 let codegen_policies = policy_registry.clone();
560
561 let mut world_state = WorldState::new();
562 let expr_arena = Arena::new();
563 let term_arena = Arena::new();
564 let np_arena = Arena::new();
565 let sym_arena = Arena::new();
566 let role_arena = Arena::new();
567 let pp_arena = Arena::new();
568 let stmt_arena: Arena<Stmt> = Arena::new();
569 let imperative_expr_arena: Arena<Expr> = Arena::new();
570 let type_expr_arena: Arena<TypeExpr> = Arena::new();
571
572 let ast_ctx = AstContext::with_types(
573 &expr_arena,
574 &term_arena,
575 &np_arena,
576 &sym_arena,
577 &role_arena,
578 &pp_arena,
579 &stmt_arena,
580 &imperative_expr_arena,
581 &type_expr_arena,
582 );
583
584 let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
586 let stmts = parser.parse_program()?;
587
588 let mut escape_checker = EscapeChecker::new(&interner);
590 escape_checker.check_program(&stmts).map_err(|e| {
591 ParseError {
592 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
593 span: e.span,
594 }
595 })?;
596
597 let mut verifier = VerificationPass::new(&interner);
599 verifier.verify_program(&stmts).map_err(|e| {
600 ParseError {
601 kind: crate::error::ParseErrorKind::Custom(format!(
602 "Verification Failed:\n\n{}",
603 e
604 )),
605 span: crate::token::Span::default(),
606 }
607 })?;
608
609 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
610
611 Ok(rust_code)
612}
613
614pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
645 let output = compile_program_full(source).map_err(CompileError::Parse)?;
646
647 let src_dir = output_dir.join("src");
649 fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
650
651 let main_path = src_dir.join("main.rs");
653 let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
654 file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
655
656 let mut cargo_toml = String::from(r#"[package]
658name = "logos_output"
659version = "0.1.0"
660edition = "2021"
661
662[dependencies]
663logicaffeine-data = { path = "./crates/logicaffeine_data" }
664logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
665tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
666"#);
667
668 for dep in &output.dependencies {
670 if dep.features.is_empty() {
671 let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
672 } else {
673 let feats = dep.features.iter()
674 .map(|f| format!("\"{}\"", f))
675 .collect::<Vec<_>>()
676 .join(", ");
677 let _ = writeln!(
678 cargo_toml,
679 "{} = {{ version = \"{}\", features = [{}] }}",
680 dep.name, dep.version, feats
681 );
682 }
683 }
684
685 cargo_toml.push_str("\n[profile.release]\nlto = true\nopt-level = 3\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\n");
686
687 let cargo_path = output_dir.join("Cargo.toml");
688 let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
689 file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
690
691 copy_runtime_crates(output_dir)?;
693
694 Ok(())
695}
696
697pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
700 let crates_dir = output_dir.join("crates");
701 fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
702
703 let workspace_root = find_workspace_root()?;
705
706 let data_src = workspace_root.join(CRATES_DATA_PATH);
708 let data_dest = crates_dir.join("logicaffeine_data");
709 copy_dir_recursive(&data_src, &data_dest)?;
710 deworkspace_cargo_toml(&data_dest.join("Cargo.toml"))?;
711
712 let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
714 let system_dest = crates_dir.join("logicaffeine_system");
715 copy_dir_recursive(&system_src, &system_dest)?;
716 deworkspace_cargo_toml(&system_dest.join("Cargo.toml"))?;
717
718 let base_src = workspace_root.join("crates/logicaffeine_base");
720 let base_dest = crates_dir.join("logicaffeine_base");
721 copy_dir_recursive(&base_src, &base_dest)?;
722 deworkspace_cargo_toml(&base_dest.join("Cargo.toml"))?;
723
724 Ok(())
725}
726
727fn deworkspace_cargo_toml(cargo_toml_path: &Path) -> Result<(), CompileError> {
733 let content = fs::read_to_string(cargo_toml_path)
734 .map_err(|e| CompileError::Io(e.to_string()))?;
735
736 let mut result = String::with_capacity(content.len());
737 for line in content.lines() {
738 let trimmed = line.trim();
739 if trimmed == "edition.workspace = true" {
740 result.push_str("edition = \"2021\"");
741 } else if trimmed == "rust-version.workspace = true" {
742 result.push_str("rust-version = \"1.75\"");
743 } else if trimmed == "authors.workspace = true"
744 || trimmed == "repository.workspace = true"
745 || trimmed == "homepage.workspace = true"
746 || trimmed == "documentation.workspace = true"
747 || trimmed == "keywords.workspace = true"
748 || trimmed == "categories.workspace = true"
749 || trimmed == "license.workspace = true"
750 {
751 continue;
753 } else if trimmed.contains(".workspace = true") {
754 continue;
756 } else {
757 result.push_str(line);
758 }
759 result.push('\n');
760 }
761
762 fs::write(cargo_toml_path, result)
763 .map_err(|e| CompileError::Io(e.to_string()))?;
764
765 Ok(())
766}
767
768fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
770 if let Ok(workspace) = std::env::var("LOGOS_WORKSPACE") {
772 let path = Path::new(&workspace);
773 if path.join("Cargo.toml").exists() && path.join("crates").exists() {
774 return Ok(path.to_path_buf());
775 }
776 }
777
778 if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
780 let path = Path::new(&manifest_dir);
781 if let Some(parent) = path.parent().and_then(|p| p.parent()) {
782 if parent.join("Cargo.toml").exists() {
783 return Ok(parent.to_path_buf());
784 }
785 }
786 }
787
788 if let Ok(exe) = std::env::current_exe() {
791 if let Some(dir) = exe.parent() {
792 let mut candidate = dir.to_path_buf();
794 for _ in 0..5 {
795 if candidate.join("Cargo.toml").exists() && candidate.join("crates").exists() {
796 return Ok(candidate);
797 }
798 if !candidate.pop() {
799 break;
800 }
801 }
802 }
803 }
804
805 let mut current = std::env::current_dir()
807 .map_err(|e| CompileError::Io(e.to_string()))?;
808
809 loop {
810 if current.join("Cargo.toml").exists() && current.join("crates").exists() {
811 return Ok(current);
812 }
813 if !current.pop() {
814 return Err(CompileError::Io(
815 "Could not find workspace root. Set LOGOS_WORKSPACE env var or run from within the workspace.".to_string()
816 ));
817 }
818 }
819}
820
821fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
824 fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
825
826 for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
827 let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
828 let src_path = entry.path();
829 let file_name = entry.file_name();
830 let dst_path = dst.join(&file_name);
831
832 if file_name == "target"
834 || file_name == ".git"
835 || file_name == "Cargo.lock"
836 || file_name == ".DS_Store"
837 {
838 continue;
839 }
840
841 if file_name.to_string_lossy().starts_with('.') {
843 continue;
844 }
845
846 if !src_path.exists() {
848 continue;
849 }
850
851 if src_path.is_dir() {
852 copy_dir_recursive(&src_path, &dst_path)?;
853 } else if file_name == "Cargo.toml" {
854 match fs::read_to_string(&src_path) {
857 Ok(content) => {
858 let filtered: String = content
859 .lines()
860 .filter(|line| !line.trim().starts_with("[workspace]"))
861 .collect::<Vec<_>>()
862 .join("\n");
863 fs::write(&dst_path, filtered)
864 .map_err(|e| CompileError::Io(e.to_string()))?;
865 }
866 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
867 Err(e) => return Err(CompileError::Io(e.to_string())),
868 }
869 } else {
870 match fs::copy(&src_path, &dst_path) {
871 Ok(_) => {}
872 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
873 Err(e) => return Err(CompileError::Io(e.to_string())),
874 }
875 }
876 }
877
878 Ok(())
879}
880
881pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
924 compile_to_dir(source, output_dir)?;
925
926 let build_output = Command::new("cargo")
928 .arg("build")
929 .arg("--message-format=json")
930 .current_dir(output_dir)
931 .output()
932 .map_err(|e| CompileError::Io(e.to_string()))?;
933
934 if !build_output.status.success() {
935 let stderr = String::from_utf8_lossy(&build_output.stderr);
936 let stdout = String::from_utf8_lossy(&build_output.stdout);
937
938 let diagnostics = parse_rustc_json(&stdout);
940
941 if !diagnostics.is_empty() {
942 let source_map = SourceMap::new(source.to_string());
944 let interner = Interner::new();
945
946 if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
947 return Err(CompileError::Ownership(logos_error));
948 }
949 }
950
951 return Err(CompileError::Build(stderr.to_string()));
953 }
954
955 let run_output = Command::new("cargo")
957 .arg("run")
958 .arg("--quiet")
959 .current_dir(output_dir)
960 .output()
961 .map_err(|e| CompileError::Io(e.to_string()))?;
962
963 if !run_output.status.success() {
964 let stderr = String::from_utf8_lossy(&run_output.stderr);
965 return Err(CompileError::Runtime(stderr.to_string()));
966 }
967
968 let stdout = String::from_utf8_lossy(&run_output.stdout);
969 Ok(stdout.to_string())
970}
971
972pub fn compile_file(path: &Path) -> Result<String, CompileError> {
975 let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
976 compile_to_rust(&source).map_err(CompileError::Parse)
977}
978
979pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
997 use crate::loader::Loader;
998 use crate::analysis::discover_with_imports;
999
1000 let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
1001 let mut loader = Loader::new(root_path);
1002 let mut interner = Interner::new();
1003
1004 let source = fs::read_to_string(entry_file)
1006 .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
1007
1008 let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
1010 .map_err(|e| CompileError::Io(e))?;
1011
1012 compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
1014 .map_err(CompileError::Parse)
1015}
1016
1017fn compile_to_rust_with_registry_full(
1020 source: &str,
1021 type_registry: crate::analysis::TypeRegistry,
1022 interner: &mut Interner,
1023) -> Result<CompileOutput, ParseError> {
1024 let mut lexer = Lexer::new(source, interner);
1025 let tokens = lexer.tokenize();
1026
1027 let policy_registry = {
1029 let mut discovery = DiscoveryPass::new(&tokens, interner);
1030 discovery.run_full().policies
1031 };
1032
1033 let codegen_registry = type_registry.clone();
1034 let codegen_policies = policy_registry.clone();
1035
1036 let mut world_state = WorldState::new();
1037 let expr_arena = Arena::new();
1038 let term_arena = Arena::new();
1039 let np_arena = Arena::new();
1040 let sym_arena = Arena::new();
1041 let role_arena = Arena::new();
1042 let pp_arena = Arena::new();
1043 let stmt_arena: Arena<Stmt> = Arena::new();
1044 let imperative_expr_arena: Arena<Expr> = Arena::new();
1045 let type_expr_arena: Arena<TypeExpr> = Arena::new();
1046
1047 let ast_ctx = AstContext::with_types(
1048 &expr_arena,
1049 &term_arena,
1050 &np_arena,
1051 &sym_arena,
1052 &role_arena,
1053 &pp_arena,
1054 &stmt_arena,
1055 &imperative_expr_arena,
1056 &type_expr_arena,
1057 );
1058
1059 let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
1060 let stmts = parser.parse_program()?;
1061
1062 let mut dependencies = extract_dependencies(&stmts, interner)?;
1064
1065 let needs_wasm_bindgen = stmts.iter().any(|stmt| {
1067 if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
1068 interner.resolve(*target).eq_ignore_ascii_case("wasm")
1069 } else {
1070 false
1071 }
1072 });
1073 if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
1074 dependencies.push(CrateDependency {
1075 name: "wasm-bindgen".to_string(),
1076 version: "0.2".to_string(),
1077 features: vec![],
1078 });
1079 }
1080
1081 let mut escape_checker = EscapeChecker::new(interner);
1082 escape_checker.check_program(&stmts).map_err(|e| {
1083 ParseError {
1084 kind: crate::error::ParseErrorKind::Custom(e.to_string()),
1085 span: e.span,
1086 }
1087 })?;
1088
1089 let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner);
1090
1091 let has_c = stmts.iter().any(|stmt| {
1093 if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
1094 match export_target {
1095 None => true,
1096 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
1097 }
1098 } else {
1099 false
1100 }
1101 });
1102
1103 let c_header = if has_c {
1104 Some(generate_c_header(&stmts, "module", interner, &codegen_registry))
1105 } else {
1106 None
1107 };
1108
1109 if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
1110 dependencies.push(CrateDependency {
1111 name: "serde_json".to_string(),
1112 version: "1".to_string(),
1113 features: vec![],
1114 });
1115 }
1116
1117 let python_bindings = if has_c {
1118 Some(generate_python_bindings(&stmts, "module", interner, &codegen_registry))
1119 } else {
1120 None
1121 };
1122
1123 let (typescript_bindings, typescript_types) = if has_c {
1124 let (js, dts) = generate_typescript_bindings(&stmts, "module", interner, &codegen_registry);
1125 (Some(js), Some(dts))
1126 } else {
1127 (None, None)
1128 };
1129
1130 Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
1131}
1132
1133#[derive(Debug)]
1155pub enum CompileError {
1156 Parse(ParseError),
1161
1162 Io(String),
1166
1167 Build(String),
1172
1173 Runtime(String),
1177
1178 Ownership(LogosError),
1184}
1185
1186impl std::fmt::Display for CompileError {
1187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1188 match self {
1189 CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
1190 CompileError::Io(e) => write!(f, "IO error: {}", e),
1191 CompileError::Build(e) => write!(f, "Build error: {}", e),
1192 CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
1193 CompileError::Ownership(e) => write!(f, "{}", e),
1194 }
1195 }
1196}
1197
1198impl std::error::Error for CompileError {}
1199
1200#[cfg(test)]
1201mod tests {
1202 use super::*;
1203
1204 #[test]
1205 fn test_compile_let_statement() {
1206 let source = "## Main\nLet x be 5.";
1207 let result = compile_to_rust(source);
1208 assert!(result.is_ok(), "Should compile: {:?}", result);
1209 let rust = result.unwrap();
1210 assert!(rust.contains("fn main()"));
1211 assert!(rust.contains("let x = 5;"));
1212 }
1213
1214 #[test]
1215 fn test_compile_return_statement() {
1216 let source = "## Main\nReturn 42.";
1217 let result = compile_to_rust(source);
1218 assert!(result.is_ok(), "Should compile: {:?}", result);
1219 let rust = result.unwrap();
1220 assert!(rust.contains("return 42;"));
1221 }
1222}