Skip to main content

logicaffeine_compile/
compile.rs

1//! LOGOS Compilation Pipeline
2//!
3//! This module provides the end-to-end compilation pipeline that transforms
4//! LOGOS source code into executable Rust programs.
5//!
6//! # Pipeline Overview
7//!
8//! ```text
9//! LOGOS Source (.md)
10//!       │
11//!       ▼
12//! ┌───────────────────┐
13//! │  1. Lexer         │ Tokenize source
14//! └─────────┬─────────┘
15//!           ▼
16//! ┌───────────────────┐
17//! │  2. Discovery     │ Type & policy definitions
18//! └─────────┬─────────┘
19//!           ▼
20//! ┌───────────────────┐
21//! │  3. Parser        │ Build AST
22//! └─────────┬─────────┘
23//!           ▼
24//! ┌───────────────────┐
25//! │  4. Analysis      │ Escape, ownership, verification
26//! └─────────┬─────────┘
27//!           ▼
28//! ┌───────────────────┐
29//! │  5. CodeGen       │ Emit Rust source
30//! └─────────┬─────────┘
31//!           ▼
32//!     Rust Source
33//! ```
34//!
35//! # Compilation Functions
36//!
37//! | Function | Analysis | Use Case |
38//! |----------|----------|----------|
39//! | [`compile_to_rust`] | Escape only | Basic compilation |
40//! | [`compile_to_rust_checked`] | Escape + Ownership | Use with `--check` flag |
41//! | `compile_to_rust_verified` | All + Z3 | Formal verification (requires `verification` feature) |
42//! | [`compile_project`] | Multi-file | Projects with imports |
43//! | [`compile_and_run`] | Full + Execute | Development workflow |
44//!
45//! # Examples
46//!
47//! ## Basic Compilation
48//!
49//! ```
50//! # use logicaffeine_compile::compile::compile_to_rust;
51//! # use logicaffeine_compile::ParseError;
52//! # fn main() -> Result<(), ParseError> {
53//! let source = "## Main\nLet x be 5.\nShow x.";
54//! let rust_code = compile_to_rust(source)?;
55//! // rust_code contains:
56//! // fn main() {
57//! //     let x = 5;
58//! //     println!("{}", x);
59//! // }
60//! # Ok(())
61//! # }
62//! ```
63//!
64//! ## With Ownership Checking
65//!
66//! ```
67//! # use logicaffeine_compile::compile::compile_to_rust_checked;
68//! let source = "## Main\nLet x be 5.\nGive x to y.\nShow x.";
69//! let result = compile_to_rust_checked(source);
70//! // Returns Err: "x has already been given away"
71//! ```
72
73use std::fs;
74use std::io::Write;
75use std::path::Path;
76use std::process::Command;
77
78// Runtime crates paths (relative to workspace root)
79const 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/// A declared external crate dependency from a `## Requires` block.
98#[derive(Debug, Clone)]
99pub struct CrateDependency {
100    pub name: String,
101    pub version: String,
102    pub features: Vec<String>,
103}
104
105/// Full compilation output including generated Rust code and extracted dependencies.
106#[derive(Debug)]
107pub struct CompileOutput {
108    pub rust_code: String,
109    pub dependencies: Vec<CrateDependency>,
110    /// Generated C header content (populated when C exports exist).
111    pub c_header: Option<String>,
112    /// Generated Python ctypes bindings (populated when C exports exist).
113    pub python_bindings: Option<String>,
114    /// Generated TypeScript type declarations (.d.ts content, populated when C exports exist).
115    pub typescript_types: Option<String>,
116    /// Generated TypeScript FFI bindings (.js content, populated when C exports exist).
117    pub typescript_bindings: Option<String>,
118}
119
120/// Interpret LOGOS source and return output as a string.
121///
122/// Runs the full pipeline (lex → discovery → parse → interpret) without
123/// generating Rust code. Useful for sub-second feedback during development.
124///
125/// # Arguments
126///
127/// * `source` - LOGOS source code as a string
128///
129/// # Returns
130///
131/// The collected output from `Show` statements, joined by newlines.
132///
133/// # Errors
134///
135/// Returns [`ParseError`] if parsing fails or the interpreter encounters
136/// a runtime error.
137pub fn interpret_program(source: &str) -> Result<String, ParseError> {
138    use futures::executor::block_on;
139
140    let result = block_on(crate::ui_bridge::interpret_for_ui(source));
141    if let Some(err) = result.error {
142        Err(ParseError {
143            kind: crate::error::ParseErrorKind::Custom(err),
144            span: crate::token::Span::default(),
145        })
146    } else {
147        Ok(result.lines.join("\n"))
148    }
149}
150
151/// Compile LOGOS source to Rust source code.
152///
153/// This is the basic compilation function that runs lexing, parsing, and
154/// escape analysis before generating Rust code.
155///
156/// # Arguments
157///
158/// * `source` - LOGOS source code as a string
159///
160/// # Returns
161///
162/// Generated Rust source code on success.
163///
164/// # Errors
165///
166/// Returns [`ParseError`] if:
167/// - Lexical analysis fails (invalid tokens)
168/// - Parsing fails (syntax errors)
169/// - Escape analysis fails (zone-local values escaping)
170///
171/// # Example
172///
173/// ```
174/// # use logicaffeine_compile::compile::compile_to_rust;
175/// # use logicaffeine_compile::ParseError;
176/// # fn main() -> Result<(), ParseError> {
177/// let source = "## Main\nLet x be 5.\nShow x.";
178/// let rust_code = compile_to_rust(source)?;
179/// assert!(rust_code.contains("let x = 5;"));
180/// # Ok(())
181/// # }
182/// ```
183pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
184    compile_program_full(source).map(|o| o.rust_code)
185}
186
187/// Compile LOGOS source and return full output including dependency metadata.
188///
189/// This is the primary compilation entry point that returns both the generated
190/// Rust code and any crate dependencies declared in `## Requires` blocks.
191pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
192    let mut interner = Interner::new();
193    let mut lexer = Lexer::new(source, &mut interner);
194    let tokens = lexer.tokenize();
195
196    // Pass 1: Discovery - scan for type definitions and policies
197    let (type_registry, policy_registry) = {
198        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
199        let result = discovery.run_full();
200        (result.types, result.policies)
201    };
202    // Clone for codegen (parser takes ownership)
203    let codegen_registry = type_registry.clone();
204    let codegen_policies = policy_registry.clone();
205
206    let mut world_state = WorldState::new();
207    let expr_arena = Arena::new();
208    let term_arena = Arena::new();
209    let np_arena = Arena::new();
210    let sym_arena = Arena::new();
211    let role_arena = Arena::new();
212    let pp_arena = Arena::new();
213    let stmt_arena: Arena<Stmt> = Arena::new();
214    let imperative_expr_arena: Arena<Expr> = Arena::new();
215    let type_expr_arena: Arena<TypeExpr> = Arena::new();
216
217    let ast_ctx = AstContext::with_types(
218        &expr_arena,
219        &term_arena,
220        &np_arena,
221        &sym_arena,
222        &role_arena,
223        &pp_arena,
224        &stmt_arena,
225        &imperative_expr_arena,
226        &type_expr_arena,
227    );
228
229    // Pass 2: Parse with type context
230    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
231    // Note: Don't call process_block_headers() - parse_program handles blocks itself
232
233    let stmts = parser.parse_program()?;
234
235    // Pass 2.5: Optimization - constant folding and dead code elimination
236    let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
237
238    // Extract dependencies before escape analysis
239    let mut dependencies = extract_dependencies(&stmts, &interner)?;
240
241    // FFI: Auto-inject wasm-bindgen dependency if any function is exported for WASM
242    let needs_wasm_bindgen = stmts.iter().any(|stmt| {
243        if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
244            interner.resolve(*target).eq_ignore_ascii_case("wasm")
245        } else {
246            false
247        }
248    });
249    if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
250        dependencies.push(CrateDependency {
251            name: "wasm-bindgen".to_string(),
252            version: "0.2".to_string(),
253            features: vec![],
254        });
255    }
256
257    // Pass 3: Escape analysis - check for zone escape violations
258    // This catches obvious cases like returning zone-local variables
259    let mut escape_checker = EscapeChecker::new(&interner);
260    escape_checker.check_program(&stmts).map_err(|e| {
261        // Convert EscapeError to ParseError for now
262        // The error message is already Socratic from EscapeChecker
263        ParseError {
264            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
265            span: e.span,
266        }
267    })?;
268
269    // Note: Static verification is available when the `verification` feature is enabled,
270    // but must be explicitly invoked via compile_to_rust_verified().
271
272    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
273
274    // Universal ABI: Generate C header + bindings if any C exports exist
275    let has_c = stmts.iter().any(|stmt| {
276        if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
277            match export_target {
278                None => true,
279                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
280            }
281        } else {
282            false
283        }
284    });
285
286    let c_header = if has_c {
287        Some(generate_c_header(&stmts, "module", &interner, &codegen_registry))
288    } else {
289        None
290    };
291
292    // Auto-inject serde_json dependency when C exports exist (needed for collection to_json and portable struct JSON accessors)
293    if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
294        dependencies.push(CrateDependency {
295            name: "serde_json".to_string(),
296            version: "1".to_string(),
297            features: vec![],
298        });
299    }
300
301    let python_bindings = if has_c {
302        Some(generate_python_bindings(&stmts, "module", &interner, &codegen_registry))
303    } else {
304        None
305    };
306
307    let (typescript_bindings, typescript_types) = if has_c {
308        let (js, dts) = generate_typescript_bindings(&stmts, "module", &interner, &codegen_registry);
309        (Some(js), Some(dts))
310    } else {
311        (None, None)
312    };
313
314    Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
315}
316
317/// Extract crate dependencies from `Stmt::Require` nodes.
318///
319/// Deduplicates by crate name: same name + same version keeps one copy.
320/// Same name + different version returns a `ParseError`.
321/// Preserves declaration order (first occurrence wins).
322fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Result<Vec<CrateDependency>, ParseError> {
323    use std::collections::HashMap;
324
325    let mut seen: HashMap<String, String> = HashMap::new(); // name → version
326    let mut deps: Vec<CrateDependency> = Vec::new();
327
328    for stmt in stmts {
329        if let Stmt::Require { crate_name, version, features, span } = stmt {
330            let name = interner.resolve(*crate_name).to_string();
331            let ver = interner.resolve(*version).to_string();
332
333            if let Some(existing_ver) = seen.get(&name) {
334                if *existing_ver != ver {
335                    return Err(ParseError {
336                        kind: crate::error::ParseErrorKind::Custom(format!(
337                            "Conflicting versions for crate \"{}\": \"{}\" and \"{}\".",
338                            name, existing_ver, ver
339                        )),
340                        span: *span,
341                    });
342                }
343                // Same name + same version: skip duplicate
344            } else {
345                seen.insert(name.clone(), ver.clone());
346                deps.push(CrateDependency {
347                    name,
348                    version: ver,
349                    features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
350                });
351            }
352        }
353    }
354
355    Ok(deps)
356}
357
358/// Compile LOGOS source to Rust with ownership checking enabled.
359///
360/// This runs the lightweight ownership analysis pass that catches use-after-move
361/// errors with control flow awareness. The analysis is fast enough to run on
362/// every keystroke in an IDE.
363///
364/// # Arguments
365///
366/// * `source` - LOGOS source code as a string
367///
368/// # Returns
369///
370/// Generated Rust source code on success.
371///
372/// # Errors
373///
374/// Returns [`ParseError`] if:
375/// - Any error from [`compile_to_rust`] occurs
376/// - Ownership analysis detects use-after-move
377/// - Ownership analysis detects use-after-borrow violations
378///
379/// # Example
380///
381/// ```
382/// # use logicaffeine_compile::compile::compile_to_rust_checked;
383/// // This will fail ownership checking
384/// let source = "## Main\nLet x be 5.\nGive x to y.\nShow x.";
385/// let result = compile_to_rust_checked(source);
386/// assert!(result.is_err()); // "x has already been given away"
387/// ```
388///
389/// # Use Case
390///
391/// Use this function with the `--check` CLI flag for instant feedback on
392/// ownership errors before running the full Rust compilation.
393pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
394    let mut interner = Interner::new();
395    let mut lexer = Lexer::new(source, &mut interner);
396    let tokens = lexer.tokenize();
397
398    // Pass 1: Discovery - scan for type definitions and policies
399    let (type_registry, policy_registry) = {
400        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
401        let result = discovery.run_full();
402        (result.types, result.policies)
403    };
404    // Clone for codegen (parser takes ownership)
405    let codegen_registry = type_registry.clone();
406    let codegen_policies = policy_registry.clone();
407
408    let mut world_state = WorldState::new();
409    let expr_arena = Arena::new();
410    let term_arena = Arena::new();
411    let np_arena = Arena::new();
412    let sym_arena = Arena::new();
413    let role_arena = Arena::new();
414    let pp_arena = Arena::new();
415    let stmt_arena: Arena<Stmt> = Arena::new();
416    let imperative_expr_arena: Arena<Expr> = Arena::new();
417    let type_expr_arena: Arena<TypeExpr> = Arena::new();
418
419    let ast_ctx = AstContext::with_types(
420        &expr_arena,
421        &term_arena,
422        &np_arena,
423        &sym_arena,
424        &role_arena,
425        &pp_arena,
426        &stmt_arena,
427        &imperative_expr_arena,
428        &type_expr_arena,
429    );
430
431    // Pass 2: Parse with type context
432    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
433    let stmts = parser.parse_program()?;
434
435    // Pass 3: Escape analysis
436    let mut escape_checker = EscapeChecker::new(&interner);
437    escape_checker.check_program(&stmts).map_err(|e| {
438        ParseError {
439            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
440            span: e.span,
441        }
442    })?;
443
444    // Pass 4: Ownership analysis
445    // Catches use-after-move errors with control flow awareness
446    let mut ownership_checker = OwnershipChecker::new(&interner);
447    ownership_checker.check_program(&stmts).map_err(|e| {
448        ParseError {
449            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
450            span: e.span,
451        }
452    })?;
453
454    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
455
456    Ok(rust_code)
457}
458
459/// Compile LOGOS source to Rust with full Z3 static verification.
460///
461/// This runs the Z3-based verifier on Assert statements before code generation,
462/// proving that assertions hold for all possible inputs. This is the most
463/// thorough compilation mode, suitable for high-assurance code.
464///
465/// # Arguments
466///
467/// * `source` - LOGOS source code as a string
468///
469/// # Returns
470///
471/// Generated Rust source code on success.
472///
473/// # Errors
474///
475/// Returns [`ParseError`] if:
476/// - Any error from [`compile_to_rust`] occurs
477/// - Z3 cannot prove an Assert statement
478/// - Refinement type constraints cannot be satisfied
479/// - Termination cannot be proven for loops with `decreasing`
480///
481/// # Example
482///
483/// ```no_run
484/// # use logicaffeine_compile::compile::compile_to_rust_verified;
485/// # use logicaffeine_compile::ParseError;
486/// # fn main() -> Result<(), ParseError> {
487/// let source = r#"
488/// ## Main
489/// Let x: { it: Int | it > 0 } be 5.
490/// Assert that x > 0.
491/// "#;
492/// let rust_code = compile_to_rust_verified(source)?;
493/// # Ok(())
494/// # }
495/// ```
496///
497/// # Feature Flag
498///
499/// This function requires the `verification` feature to be enabled:
500///
501/// ```toml
502/// [dependencies]
503/// logicaffeine_compile = { version = "...", features = ["verification"] }
504/// ```
505#[cfg(feature = "verification")]
506pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
507    use crate::verification::VerificationPass;
508
509    let mut interner = Interner::new();
510    let mut lexer = Lexer::new(source, &mut interner);
511    let tokens = lexer.tokenize();
512
513    // Pass 1: Discovery - scan for type definitions and policies
514    let (type_registry, policy_registry) = {
515        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
516        let result = discovery.run_full();
517        (result.types, result.policies)
518    };
519    // Clone for codegen (parser takes ownership)
520    let codegen_registry = type_registry.clone();
521    let codegen_policies = policy_registry.clone();
522
523    let mut world_state = WorldState::new();
524    let expr_arena = Arena::new();
525    let term_arena = Arena::new();
526    let np_arena = Arena::new();
527    let sym_arena = Arena::new();
528    let role_arena = Arena::new();
529    let pp_arena = Arena::new();
530    let stmt_arena: Arena<Stmt> = Arena::new();
531    let imperative_expr_arena: Arena<Expr> = Arena::new();
532    let type_expr_arena: Arena<TypeExpr> = Arena::new();
533
534    let ast_ctx = AstContext::with_types(
535        &expr_arena,
536        &term_arena,
537        &np_arena,
538        &sym_arena,
539        &role_arena,
540        &pp_arena,
541        &stmt_arena,
542        &imperative_expr_arena,
543        &type_expr_arena,
544    );
545
546    // Pass 2: Parse with type context
547    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
548    let stmts = parser.parse_program()?;
549
550    // Pass 3: Escape analysis
551    let mut escape_checker = EscapeChecker::new(&interner);
552    escape_checker.check_program(&stmts).map_err(|e| {
553        ParseError {
554            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
555            span: e.span,
556        }
557    })?;
558
559    // Pass 4: Static verification
560    let mut verifier = VerificationPass::new(&interner);
561    verifier.verify_program(&stmts).map_err(|e| {
562        ParseError {
563            kind: crate::error::ParseErrorKind::Custom(format!(
564                "Verification Failed:\n\n{}",
565                e
566            )),
567            span: crate::token::Span::default(),
568        }
569    })?;
570
571    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
572
573    Ok(rust_code)
574}
575
576/// Compile LOGOS source and write output to a directory as a Cargo project.
577///
578/// Creates a complete Cargo project structure with:
579/// - `src/main.rs` containing the generated Rust code
580/// - `Cargo.toml` with runtime dependencies
581/// - `crates/` directory with runtime crate copies
582///
583/// # Arguments
584///
585/// * `source` - LOGOS source code as a string
586/// * `output_dir` - Directory to create the Cargo project in
587///
588/// # Errors
589///
590/// Returns [`CompileError`] if:
591/// - Compilation fails (wrapped as `CompileError::Parse`)
592/// - File system operations fail (wrapped as `CompileError::Io`)
593///
594/// # Example
595///
596/// ```no_run
597/// # use logicaffeine_compile::compile::{compile_to_dir, CompileError};
598/// # use std::path::Path;
599/// # fn main() -> Result<(), CompileError> {
600/// let source = "## Main\nShow \"Hello\".";
601/// compile_to_dir(source, Path::new("/tmp/my_project"))?;
602/// // Now /tmp/my_project is a buildable Cargo project
603/// # Ok(())
604/// # }
605/// ```
606pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
607    let output = compile_program_full(source).map_err(CompileError::Parse)?;
608
609    // Create output directory structure
610    let src_dir = output_dir.join("src");
611    fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
612
613    // Write main.rs (codegen already includes the use statements)
614    let main_path = src_dir.join("main.rs");
615    let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
616    file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
617
618    // Write Cargo.toml with runtime crate dependencies
619    let mut cargo_toml = String::from(r#"[package]
620name = "logos_output"
621version = "0.1.0"
622edition = "2021"
623
624[dependencies]
625logicaffeine-data = { path = "./crates/logicaffeine_data" }
626logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
627tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
628"#);
629
630    // Append user-declared dependencies from ## Requires blocks
631    for dep in &output.dependencies {
632        if dep.features.is_empty() {
633            let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
634        } else {
635            let feats = dep.features.iter()
636                .map(|f| format!("\"{}\"", f))
637                .collect::<Vec<_>>()
638                .join(", ");
639            let _ = writeln!(
640                cargo_toml,
641                "{} = {{ version = \"{}\", features = [{}] }}",
642                dep.name, dep.version, feats
643            );
644        }
645    }
646
647    let cargo_path = output_dir.join("Cargo.toml");
648    let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
649    file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
650
651    // Copy runtime crates to output directory
652    copy_runtime_crates(output_dir)?;
653
654    Ok(())
655}
656
657/// Copy the runtime crates to the output directory.
658/// Copies logicaffeine_data and logicaffeine_system.
659pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
660    let crates_dir = output_dir.join("crates");
661    fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
662
663    // Find workspace root
664    let workspace_root = find_workspace_root()?;
665
666    // Copy logicaffeine_data
667    let data_src = workspace_root.join(CRATES_DATA_PATH);
668    let data_dest = crates_dir.join("logicaffeine_data");
669    copy_dir_recursive(&data_src, &data_dest)?;
670
671    // Copy logicaffeine_system
672    let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
673    let system_dest = crates_dir.join("logicaffeine_system");
674    copy_dir_recursive(&system_src, &system_dest)?;
675
676    // Also need to copy logicaffeine_base since both crates depend on it
677    let base_src = workspace_root.join("crates/logicaffeine_base");
678    let base_dest = crates_dir.join("logicaffeine_base");
679    copy_dir_recursive(&base_src, &base_dest)?;
680
681    Ok(())
682}
683
684/// Find the workspace root directory.
685fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
686    // Try CARGO_MANIFEST_DIR first
687    if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
688        // Go up from crates/logicaffeine_compile to workspace root
689        let path = Path::new(&manifest_dir);
690        if let Some(parent) = path.parent().and_then(|p| p.parent()) {
691            if parent.join("Cargo.toml").exists() {
692                return Ok(parent.to_path_buf());
693            }
694        }
695    }
696
697    // Fallback to current directory traversal
698    let mut current = std::env::current_dir()
699        .map_err(|e| CompileError::Io(e.to_string()))?;
700
701    loop {
702        if current.join("Cargo.toml").exists() && current.join("crates").exists() {
703            return Ok(current);
704        }
705        if !current.pop() {
706            return Err(CompileError::Io("Could not find workspace root".to_string()));
707        }
708    }
709}
710
711/// Recursively copy a directory.
712/// Skips files that disappear during copy (race condition with parallel builds).
713fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
714    fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
715
716    for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
717        let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
718        let src_path = entry.path();
719        let file_name = entry.file_name();
720        let dst_path = dst.join(&file_name);
721
722        // Skip target directory, build artifacts, and lock files
723        if file_name == "target"
724            || file_name == ".git"
725            || file_name == "Cargo.lock"
726            || file_name == ".DS_Store"
727        {
728            continue;
729        }
730
731        // Skip files that start with a dot (hidden files)
732        if file_name.to_string_lossy().starts_with('.') {
733            continue;
734        }
735
736        // Check if path still exists (race condition protection)
737        if !src_path.exists() {
738            continue;
739        }
740
741        if src_path.is_dir() {
742            copy_dir_recursive(&src_path, &dst_path)?;
743        } else if file_name == "Cargo.toml" {
744            // Special handling for Cargo.toml: remove [workspace] line
745            // which can interfere with nested crate dependencies
746            match fs::read_to_string(&src_path) {
747                Ok(content) => {
748                    let filtered: String = content
749                        .lines()
750                        .filter(|line| !line.trim().starts_with("[workspace]"))
751                        .collect::<Vec<_>>()
752                        .join("\n");
753                    fs::write(&dst_path, filtered)
754                        .map_err(|e| CompileError::Io(e.to_string()))?;
755                }
756                Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
757                Err(e) => return Err(CompileError::Io(e.to_string())),
758            }
759        } else {
760            match fs::copy(&src_path, &dst_path) {
761                Ok(_) => {}
762                Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
763                Err(e) => return Err(CompileError::Io(e.to_string())),
764            }
765        }
766    }
767
768    Ok(())
769}
770
771/// Compile and run a LOGOS program end-to-end.
772///
773/// This function performs the full compilation workflow:
774/// 1. Compile LOGOS to Rust via [`compile_to_dir`]
775/// 2. Run `cargo build` with JSON diagnostics
776/// 3. Translate any rustc errors to LOGOS-friendly messages
777/// 4. Run the compiled program via `cargo run`
778///
779/// # Arguments
780///
781/// * `source` - LOGOS source code as a string
782/// * `output_dir` - Directory to create the temporary Cargo project in
783///
784/// # Returns
785///
786/// The stdout output of the executed program.
787///
788/// # Errors
789///
790/// Returns [`CompileError`] if:
791/// - Compilation fails (see [`compile_to_dir`])
792/// - Rust compilation fails (`CompileError::Build` or `CompileError::Ownership`)
793/// - The program crashes at runtime (`CompileError::Runtime`)
794///
795/// # Diagnostic Translation
796///
797/// When rustc reports errors (e.g., E0382 for use-after-move), this function
798/// uses the [`diagnostic`](crate::diagnostic) module to translate them into
799/// LOGOS-friendly Socratic error messages.
800///
801/// # Example
802///
803/// ```no_run
804/// # use logicaffeine_compile::compile::{compile_and_run, CompileError};
805/// # use std::path::Path;
806/// # fn main() -> Result<(), CompileError> {
807/// let source = "## Main\nShow \"Hello, World!\".";
808/// let output = compile_and_run(source, Path::new("/tmp/run"))?;
809/// assert_eq!(output.trim(), "Hello, World!");
810/// # Ok(())
811/// # }
812/// ```
813pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
814    compile_to_dir(source, output_dir)?;
815
816    // Run cargo build with JSON message format for structured error parsing
817    let build_output = Command::new("cargo")
818        .arg("build")
819        .arg("--message-format=json")
820        .current_dir(output_dir)
821        .output()
822        .map_err(|e| CompileError::Io(e.to_string()))?;
823
824    if !build_output.status.success() {
825        let stderr = String::from_utf8_lossy(&build_output.stderr);
826        let stdout = String::from_utf8_lossy(&build_output.stdout);
827
828        // Try to parse JSON diagnostics and translate them
829        let diagnostics = parse_rustc_json(&stdout);
830
831        if !diagnostics.is_empty() {
832            // Create a basic source map with the LOGOS source
833            let source_map = SourceMap::new(source.to_string());
834            let interner = Interner::new();
835
836            if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
837                return Err(CompileError::Ownership(logos_error));
838            }
839        }
840
841        // Fallback to raw error if translation fails
842        return Err(CompileError::Build(stderr.to_string()));
843    }
844
845    // Run the compiled program
846    let run_output = Command::new("cargo")
847        .arg("run")
848        .arg("--quiet")
849        .current_dir(output_dir)
850        .output()
851        .map_err(|e| CompileError::Io(e.to_string()))?;
852
853    if !run_output.status.success() {
854        let stderr = String::from_utf8_lossy(&run_output.stderr);
855        return Err(CompileError::Runtime(stderr.to_string()));
856    }
857
858    let stdout = String::from_utf8_lossy(&run_output.stdout);
859    Ok(stdout.to_string())
860}
861
862/// Compile a LOGOS source file.
863/// For single-file compilation without dependencies.
864pub fn compile_file(path: &Path) -> Result<String, CompileError> {
865    let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
866    compile_to_rust(&source).map_err(CompileError::Parse)
867}
868
869/// Compile a multi-file LOGOS project with dependency resolution.
870///
871/// This function:
872/// 1. Reads the entry file
873/// 2. Scans for dependencies in the abstract (Markdown links)
874/// 3. Recursively loads and discovers types from dependencies
875/// 4. Compiles with the combined type registry
876///
877/// # Arguments
878/// * `entry_file` - The main entry file to compile (root is derived from parent directory)
879///
880/// # Example
881/// ```no_run
882/// # use logicaffeine_compile::compile::compile_project;
883/// # use std::path::Path;
884/// let result = compile_project(Path::new("/project/main.md"));
885/// ```
886pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
887    use crate::loader::Loader;
888    use crate::analysis::discover_with_imports;
889
890    let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
891    let mut loader = Loader::new(root_path);
892    let mut interner = Interner::new();
893
894    // Read the entry file
895    let source = fs::read_to_string(entry_file)
896        .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
897
898    // Discover types from entry file and all imports
899    let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
900        .map_err(|e| CompileError::Io(e))?;
901
902    // Now compile with the discovered types
903    compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
904        .map_err(CompileError::Parse)
905}
906
907/// Compile LOGOS source with a pre-populated type registry, returning full output.
908/// Returns both generated Rust code and extracted dependencies.
909fn compile_to_rust_with_registry_full(
910    source: &str,
911    type_registry: crate::analysis::TypeRegistry,
912    interner: &mut Interner,
913) -> Result<CompileOutput, ParseError> {
914    let mut lexer = Lexer::new(source, interner);
915    let tokens = lexer.tokenize();
916
917    // Discovery pass for policies (types already discovered)
918    let policy_registry = {
919        let mut discovery = DiscoveryPass::new(&tokens, interner);
920        discovery.run_full().policies
921    };
922
923    let codegen_registry = type_registry.clone();
924    let codegen_policies = policy_registry.clone();
925
926    let mut world_state = WorldState::new();
927    let expr_arena = Arena::new();
928    let term_arena = Arena::new();
929    let np_arena = Arena::new();
930    let sym_arena = Arena::new();
931    let role_arena = Arena::new();
932    let pp_arena = Arena::new();
933    let stmt_arena: Arena<Stmt> = Arena::new();
934    let imperative_expr_arena: Arena<Expr> = Arena::new();
935    let type_expr_arena: Arena<TypeExpr> = Arena::new();
936
937    let ast_ctx = AstContext::with_types(
938        &expr_arena,
939        &term_arena,
940        &np_arena,
941        &sym_arena,
942        &role_arena,
943        &pp_arena,
944        &stmt_arena,
945        &imperative_expr_arena,
946        &type_expr_arena,
947    );
948
949    let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
950    let stmts = parser.parse_program()?;
951
952    // Extract dependencies before escape analysis
953    let mut dependencies = extract_dependencies(&stmts, interner)?;
954
955    // FFI: Auto-inject wasm-bindgen dependency if any function is exported for WASM
956    let needs_wasm_bindgen = stmts.iter().any(|stmt| {
957        if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
958            interner.resolve(*target).eq_ignore_ascii_case("wasm")
959        } else {
960            false
961        }
962    });
963    if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
964        dependencies.push(CrateDependency {
965            name: "wasm-bindgen".to_string(),
966            version: "0.2".to_string(),
967            features: vec![],
968        });
969    }
970
971    let mut escape_checker = EscapeChecker::new(interner);
972    escape_checker.check_program(&stmts).map_err(|e| {
973        ParseError {
974            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
975            span: e.span,
976        }
977    })?;
978
979    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner);
980
981    // Universal ABI: Generate C header + bindings if any C exports exist
982    let has_c = stmts.iter().any(|stmt| {
983        if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
984            match export_target {
985                None => true,
986                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
987            }
988        } else {
989            false
990        }
991    });
992
993    let c_header = if has_c {
994        Some(generate_c_header(&stmts, "module", interner, &codegen_registry))
995    } else {
996        None
997    };
998
999    if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
1000        dependencies.push(CrateDependency {
1001            name: "serde_json".to_string(),
1002            version: "1".to_string(),
1003            features: vec![],
1004        });
1005    }
1006
1007    let python_bindings = if has_c {
1008        Some(generate_python_bindings(&stmts, "module", interner, &codegen_registry))
1009    } else {
1010        None
1011    };
1012
1013    let (typescript_bindings, typescript_types) = if has_c {
1014        let (js, dts) = generate_typescript_bindings(&stmts, "module", interner, &codegen_registry);
1015        (Some(js), Some(dts))
1016    } else {
1017        (None, None)
1018    };
1019
1020    Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
1021}
1022
1023/// Errors that can occur during the LOGOS compilation pipeline.
1024///
1025/// This enum represents the different stages where compilation can fail,
1026/// from parsing through to runtime execution.
1027///
1028/// # Error Hierarchy
1029///
1030/// ```text
1031/// CompileError
1032/// ├── Parse      ← Lexing, parsing, or static analysis
1033/// ├── Io         ← File system operations
1034/// ├── Build      ← Rust compilation (cargo build)
1035/// ├── Ownership  ← Translated borrow checker errors
1036/// └── Runtime    ← Program execution failure
1037/// ```
1038///
1039/// # Error Translation
1040///
1041/// The `Ownership` variant contains LOGOS-friendly error messages translated
1042/// from rustc's borrow checker errors (E0382, E0505, E0597) using the
1043/// [`diagnostic`](crate::diagnostic) module.
1044#[derive(Debug)]
1045pub enum CompileError {
1046    /// Parsing or static analysis failed.
1047    ///
1048    /// This includes lexer errors, syntax errors, escape analysis failures,
1049    /// ownership analysis failures, and Z3 verification failures.
1050    Parse(ParseError),
1051
1052    /// File system operation failed.
1053    ///
1054    /// Typically occurs when reading source files or writing output.
1055    Io(String),
1056
1057    /// Rust compilation failed (`cargo build`).
1058    ///
1059    /// Contains the raw stderr output from rustc when diagnostic translation
1060    /// was not possible.
1061    Build(String),
1062
1063    /// Runtime execution failed.
1064    ///
1065    /// Contains stderr output from the executed program.
1066    Runtime(String),
1067
1068    /// Translated ownership/borrow checker error with LOGOS-friendly message.
1069    ///
1070    /// This variant is used when rustc reports errors like E0382 (use after move)
1071    /// and we can translate them into natural language error messages that
1072    /// reference the original LOGOS source.
1073    Ownership(LogosError),
1074}
1075
1076impl std::fmt::Display for CompileError {
1077    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1078        match self {
1079            CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
1080            CompileError::Io(e) => write!(f, "IO error: {}", e),
1081            CompileError::Build(e) => write!(f, "Build error: {}", e),
1082            CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
1083            CompileError::Ownership(e) => write!(f, "{}", e),
1084        }
1085    }
1086}
1087
1088impl std::error::Error for CompileError {}
1089
1090#[cfg(test)]
1091mod tests {
1092    use super::*;
1093
1094    #[test]
1095    fn test_compile_let_statement() {
1096        let source = "## Main\nLet x be 5.";
1097        let result = compile_to_rust(source);
1098        assert!(result.is_ok(), "Should compile: {:?}", result);
1099        let rust = result.unwrap();
1100        assert!(rust.contains("fn main()"));
1101        assert!(rust.contains("let x = 5;"));
1102    }
1103
1104    #[test]
1105    fn test_compile_return_statement() {
1106        let source = "## Main\nReturn 42.";
1107        let result = compile_to_rust(source);
1108        assert!(result.is_ok(), "Should compile: {:?}", result);
1109        let rust = result.unwrap();
1110        assert!(rust.contains("return 42;"));
1111    }
1112}