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//! ```ignore
50//! let source = "## Main\nLet x be 5.\nShow x to show.";
51//! let rust_code = compile_to_rust(source)?;
52//! // rust_code contains:
53//! // fn main() {
54//! //     let x = 5;
55//! //     println!("{}", x);
56//! // }
57//! ```
58//!
59//! ## With Ownership Checking
60//!
61//! ```ignore
62//! let source = "## Main\nLet x be 5.\nGive x to y.\nShow x to show.";
63//! let result = compile_to_rust_checked(source);
64//! // Returns Err: "x has already been given away"
65//! ```
66
67use std::fs;
68use std::io::Write;
69use std::path::Path;
70use std::process::Command;
71
72// Runtime crates paths (relative to workspace root)
73const CRATES_DATA_PATH: &str = "crates/logicaffeine_data";
74const CRATES_SYSTEM_PATH: &str = "crates/logicaffeine_system";
75
76use std::fmt::Write as FmtWrite;
77
78use crate::analysis::{DiscoveryPass, EscapeChecker, OwnershipChecker, PolicyRegistry};
79use crate::arena::Arena;
80use crate::arena_ctx::AstContext;
81use crate::ast::{Expr, Stmt, TypeExpr};
82use crate::codegen::codegen_program;
83use crate::diagnostic::{parse_rustc_json, translate_diagnostics, LogosError};
84use crate::drs::WorldState;
85use crate::error::ParseError;
86use crate::intern::Interner;
87use crate::lexer::Lexer;
88use crate::parser::Parser;
89use crate::sourcemap::SourceMap;
90
91/// A declared external crate dependency from a `## Requires` block.
92#[derive(Debug, Clone)]
93pub struct CrateDependency {
94    pub name: String,
95    pub version: String,
96    pub features: Vec<String>,
97}
98
99/// Full compilation output including generated Rust code and extracted dependencies.
100#[derive(Debug)]
101pub struct CompileOutput {
102    pub rust_code: String,
103    pub dependencies: Vec<CrateDependency>,
104}
105
106/// Compile LOGOS source to Rust source code.
107///
108/// This is the basic compilation function that runs lexing, parsing, and
109/// escape analysis before generating Rust code.
110///
111/// # Arguments
112///
113/// * `source` - LOGOS source code as a string
114///
115/// # Returns
116///
117/// Generated Rust source code on success.
118///
119/// # Errors
120///
121/// Returns [`ParseError`] if:
122/// - Lexical analysis fails (invalid tokens)
123/// - Parsing fails (syntax errors)
124/// - Escape analysis fails (zone-local values escaping)
125///
126/// # Example
127///
128/// ```ignore
129/// let source = "## Main\nLet x be 5.\nShow x to show.";
130/// let rust_code = compile_to_rust(source)?;
131/// assert!(rust_code.contains("let x = 5;"));
132/// ```
133pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
134    compile_program_full(source).map(|o| o.rust_code)
135}
136
137/// Compile LOGOS source and return full output including dependency metadata.
138///
139/// This is the primary compilation entry point that returns both the generated
140/// Rust code and any crate dependencies declared in `## Requires` blocks.
141pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
142    let mut interner = Interner::new();
143    let mut lexer = Lexer::new(source, &mut interner);
144    let tokens = lexer.tokenize();
145
146    // Pass 1: Discovery - scan for type definitions and policies
147    let (type_registry, policy_registry) = {
148        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
149        let result = discovery.run_full();
150        (result.types, result.policies)
151    };
152    // Clone for codegen (parser takes ownership)
153    let codegen_registry = type_registry.clone();
154    let codegen_policies = policy_registry.clone();
155
156    let mut world_state = WorldState::new();
157    let expr_arena = Arena::new();
158    let term_arena = Arena::new();
159    let np_arena = Arena::new();
160    let sym_arena = Arena::new();
161    let role_arena = Arena::new();
162    let pp_arena = Arena::new();
163    let stmt_arena: Arena<Stmt> = Arena::new();
164    let imperative_expr_arena: Arena<Expr> = Arena::new();
165    let type_expr_arena: Arena<TypeExpr> = Arena::new();
166
167    let ast_ctx = AstContext::with_types(
168        &expr_arena,
169        &term_arena,
170        &np_arena,
171        &sym_arena,
172        &role_arena,
173        &pp_arena,
174        &stmt_arena,
175        &imperative_expr_arena,
176        &type_expr_arena,
177    );
178
179    // Pass 2: Parse with type context
180    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
181    // Note: Don't call process_block_headers() - parse_program handles blocks itself
182
183    let stmts = parser.parse_program()?;
184
185    // Extract dependencies before escape analysis
186    let dependencies = extract_dependencies(&stmts, &interner);
187
188    // Pass 3: Escape analysis - check for zone escape violations
189    // This catches obvious cases like returning zone-local variables
190    let mut escape_checker = EscapeChecker::new(&interner);
191    escape_checker.check_program(&stmts).map_err(|e| {
192        // Convert EscapeError to ParseError for now
193        // The error message is already Socratic from EscapeChecker
194        ParseError {
195            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
196            span: e.span,
197        }
198    })?;
199
200    // Note: Static verification is available when the `verification` feature is enabled,
201    // but must be explicitly invoked via compile_to_rust_verified().
202
203    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
204
205    Ok(CompileOutput { rust_code, dependencies })
206}
207
208/// Extract crate dependencies from `Stmt::Require` nodes.
209fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Vec<CrateDependency> {
210    stmts.iter().filter_map(|stmt| {
211        if let Stmt::Require { crate_name, version, features, .. } = stmt {
212            Some(CrateDependency {
213                name: interner.resolve(*crate_name).to_string(),
214                version: interner.resolve(*version).to_string(),
215                features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
216            })
217        } else {
218            None
219        }
220    }).collect()
221}
222
223/// Compile LOGOS source to Rust with ownership checking enabled.
224///
225/// This runs the lightweight ownership analysis pass that catches use-after-move
226/// errors with control flow awareness. The analysis is fast enough to run on
227/// every keystroke in an IDE.
228///
229/// # Arguments
230///
231/// * `source` - LOGOS source code as a string
232///
233/// # Returns
234///
235/// Generated Rust source code on success.
236///
237/// # Errors
238///
239/// Returns [`ParseError`] if:
240/// - Any error from [`compile_to_rust`] occurs
241/// - Ownership analysis detects use-after-move
242/// - Ownership analysis detects use-after-borrow violations
243///
244/// # Example
245///
246/// ```ignore
247/// // This will fail ownership checking
248/// let source = "## Main\nLet x be 5.\nGive x to y.\nShow x to show.";
249/// let result = compile_to_rust_checked(source);
250/// assert!(result.is_err()); // "x has already been given away"
251/// ```
252///
253/// # Use Case
254///
255/// Use this function with the `--check` CLI flag for instant feedback on
256/// ownership errors before running the full Rust compilation.
257pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
258    let mut interner = Interner::new();
259    let mut lexer = Lexer::new(source, &mut interner);
260    let tokens = lexer.tokenize();
261
262    // Pass 1: Discovery - scan for type definitions and policies
263    let (type_registry, policy_registry) = {
264        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
265        let result = discovery.run_full();
266        (result.types, result.policies)
267    };
268    // Clone for codegen (parser takes ownership)
269    let codegen_registry = type_registry.clone();
270    let codegen_policies = policy_registry.clone();
271
272    let mut world_state = WorldState::new();
273    let expr_arena = Arena::new();
274    let term_arena = Arena::new();
275    let np_arena = Arena::new();
276    let sym_arena = Arena::new();
277    let role_arena = Arena::new();
278    let pp_arena = Arena::new();
279    let stmt_arena: Arena<Stmt> = Arena::new();
280    let imperative_expr_arena: Arena<Expr> = Arena::new();
281    let type_expr_arena: Arena<TypeExpr> = Arena::new();
282
283    let ast_ctx = AstContext::with_types(
284        &expr_arena,
285        &term_arena,
286        &np_arena,
287        &sym_arena,
288        &role_arena,
289        &pp_arena,
290        &stmt_arena,
291        &imperative_expr_arena,
292        &type_expr_arena,
293    );
294
295    // Pass 2: Parse with type context
296    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
297    let stmts = parser.parse_program()?;
298
299    // Pass 3: Escape analysis
300    let mut escape_checker = EscapeChecker::new(&interner);
301    escape_checker.check_program(&stmts).map_err(|e| {
302        ParseError {
303            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
304            span: e.span,
305        }
306    })?;
307
308    // Pass 4: Ownership analysis
309    // Catches use-after-move errors with control flow awareness
310    let mut ownership_checker = OwnershipChecker::new(&interner);
311    ownership_checker.check_program(&stmts).map_err(|e| {
312        ParseError {
313            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
314            span: e.span,
315        }
316    })?;
317
318    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
319
320    Ok(rust_code)
321}
322
323/// Compile LOGOS source to Rust with full Z3 static verification.
324///
325/// This runs the Z3-based verifier on Assert statements before code generation,
326/// proving that assertions hold for all possible inputs. This is the most
327/// thorough compilation mode, suitable for high-assurance code.
328///
329/// # Arguments
330///
331/// * `source` - LOGOS source code as a string
332///
333/// # Returns
334///
335/// Generated Rust source code on success.
336///
337/// # Errors
338///
339/// Returns [`ParseError`] if:
340/// - Any error from [`compile_to_rust`] occurs
341/// - Z3 cannot prove an Assert statement
342/// - Refinement type constraints cannot be satisfied
343/// - Termination cannot be proven for loops with `decreasing`
344///
345/// # Example
346///
347/// ```ignore
348/// let source = r#"
349/// ## Main
350/// Let x: { it: Int | it > 0 } be 5.
351/// Assert that x > 0.
352/// "#;
353/// let rust_code = compile_to_rust_verified(source)?;
354/// ```
355///
356/// # Feature Flag
357///
358/// This function requires the `verification` feature to be enabled:
359///
360/// ```toml
361/// [dependencies]
362/// logicaffeine_compile = { version = "...", features = ["verification"] }
363/// ```
364#[cfg(feature = "verification")]
365pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
366    use crate::verification::VerificationPass;
367
368    let mut interner = Interner::new();
369    let mut lexer = Lexer::new(source, &mut interner);
370    let tokens = lexer.tokenize();
371
372    // Pass 1: Discovery - scan for type definitions and policies
373    let (type_registry, policy_registry) = {
374        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
375        let result = discovery.run_full();
376        (result.types, result.policies)
377    };
378    // Clone for codegen (parser takes ownership)
379    let codegen_registry = type_registry.clone();
380    let codegen_policies = policy_registry.clone();
381
382    let mut world_state = WorldState::new();
383    let expr_arena = Arena::new();
384    let term_arena = Arena::new();
385    let np_arena = Arena::new();
386    let sym_arena = Arena::new();
387    let role_arena = Arena::new();
388    let pp_arena = Arena::new();
389    let stmt_arena: Arena<Stmt> = Arena::new();
390    let imperative_expr_arena: Arena<Expr> = Arena::new();
391    let type_expr_arena: Arena<TypeExpr> = Arena::new();
392
393    let ast_ctx = AstContext::with_types(
394        &expr_arena,
395        &term_arena,
396        &np_arena,
397        &sym_arena,
398        &role_arena,
399        &pp_arena,
400        &stmt_arena,
401        &imperative_expr_arena,
402        &type_expr_arena,
403    );
404
405    // Pass 2: Parse with type context
406    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
407    let stmts = parser.parse_program()?;
408
409    // Pass 3: Escape analysis
410    let mut escape_checker = EscapeChecker::new(&interner);
411    escape_checker.check_program(&stmts).map_err(|e| {
412        ParseError {
413            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
414            span: e.span,
415        }
416    })?;
417
418    // Pass 4: Static verification
419    let mut verifier = VerificationPass::new(&interner);
420    verifier.verify_program(&stmts).map_err(|e| {
421        ParseError {
422            kind: crate::error::ParseErrorKind::Custom(format!(
423                "Verification Failed:\n\n{}",
424                e
425            )),
426            span: crate::token::Span::default(),
427        }
428    })?;
429
430    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner);
431
432    Ok(rust_code)
433}
434
435/// Compile LOGOS source and write output to a directory as a Cargo project.
436///
437/// Creates a complete Cargo project structure with:
438/// - `src/main.rs` containing the generated Rust code
439/// - `Cargo.toml` with runtime dependencies
440/// - `crates/` directory with runtime crate copies
441///
442/// # Arguments
443///
444/// * `source` - LOGOS source code as a string
445/// * `output_dir` - Directory to create the Cargo project in
446///
447/// # Errors
448///
449/// Returns [`CompileError`] if:
450/// - Compilation fails (wrapped as `CompileError::Parse`)
451/// - File system operations fail (wrapped as `CompileError::Io`)
452///
453/// # Example
454///
455/// ```ignore
456/// let source = "## Main\nShow \"Hello\" to show.";
457/// compile_to_dir(source, Path::new("/tmp/my_project"))?;
458/// // Now /tmp/my_project is a buildable Cargo project
459/// ```
460pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
461    let output = compile_program_full(source).map_err(CompileError::Parse)?;
462
463    // Create output directory structure
464    let src_dir = output_dir.join("src");
465    fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
466
467    // Write main.rs (codegen already includes the use statements)
468    let main_path = src_dir.join("main.rs");
469    let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
470    file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
471
472    // Write Cargo.toml with runtime crate dependencies
473    let mut cargo_toml = String::from(r#"[package]
474name = "logos_output"
475version = "0.1.0"
476edition = "2021"
477
478[dependencies]
479logicaffeine-data = { path = "./crates/logicaffeine_data" }
480logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
481tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
482"#);
483
484    // Append user-declared dependencies from ## Requires blocks
485    for dep in &output.dependencies {
486        if dep.features.is_empty() {
487            let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
488        } else {
489            let feats = dep.features.iter()
490                .map(|f| format!("\"{}\"", f))
491                .collect::<Vec<_>>()
492                .join(", ");
493            let _ = writeln!(
494                cargo_toml,
495                "{} = {{ version = \"{}\", features = [{}] }}",
496                dep.name, dep.version, feats
497            );
498        }
499    }
500
501    let cargo_path = output_dir.join("Cargo.toml");
502    let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
503    file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
504
505    // Copy runtime crates to output directory
506    copy_runtime_crates(output_dir)?;
507
508    Ok(())
509}
510
511/// Copy the runtime crates to the output directory.
512/// Copies logicaffeine_data and logicaffeine_system.
513pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
514    let crates_dir = output_dir.join("crates");
515    fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
516
517    // Find workspace root
518    let workspace_root = find_workspace_root()?;
519
520    // Copy logicaffeine_data
521    let data_src = workspace_root.join(CRATES_DATA_PATH);
522    let data_dest = crates_dir.join("logicaffeine_data");
523    copy_dir_recursive(&data_src, &data_dest)?;
524
525    // Copy logicaffeine_system
526    let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
527    let system_dest = crates_dir.join("logicaffeine_system");
528    copy_dir_recursive(&system_src, &system_dest)?;
529
530    // Also need to copy logicaffeine_base since both crates depend on it
531    let base_src = workspace_root.join("crates/logicaffeine_base");
532    let base_dest = crates_dir.join("logicaffeine_base");
533    copy_dir_recursive(&base_src, &base_dest)?;
534
535    Ok(())
536}
537
538/// Find the workspace root directory.
539fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
540    // Try CARGO_MANIFEST_DIR first
541    if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
542        // Go up from crates/logicaffeine_compile to workspace root
543        let path = Path::new(&manifest_dir);
544        if let Some(parent) = path.parent().and_then(|p| p.parent()) {
545            if parent.join("Cargo.toml").exists() {
546                return Ok(parent.to_path_buf());
547            }
548        }
549    }
550
551    // Fallback to current directory traversal
552    let mut current = std::env::current_dir()
553        .map_err(|e| CompileError::Io(e.to_string()))?;
554
555    loop {
556        if current.join("Cargo.toml").exists() && current.join("crates").exists() {
557            return Ok(current);
558        }
559        if !current.pop() {
560            return Err(CompileError::Io("Could not find workspace root".to_string()));
561        }
562    }
563}
564
565/// Recursively copy a directory.
566/// Skips files that disappear during copy (race condition with parallel builds).
567fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
568    fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
569
570    for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
571        let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
572        let src_path = entry.path();
573        let file_name = entry.file_name();
574        let dst_path = dst.join(&file_name);
575
576        // Skip target directory, build artifacts, and lock files
577        if file_name == "target"
578            || file_name == ".git"
579            || file_name == "Cargo.lock"
580            || file_name == ".DS_Store"
581        {
582            continue;
583        }
584
585        // Skip files that start with a dot (hidden files)
586        if file_name.to_string_lossy().starts_with('.') {
587            continue;
588        }
589
590        // Check if path still exists (race condition protection)
591        if !src_path.exists() {
592            continue;
593        }
594
595        if src_path.is_dir() {
596            copy_dir_recursive(&src_path, &dst_path)?;
597        } else if file_name == "Cargo.toml" {
598            // Special handling for Cargo.toml: remove [workspace] line
599            // which can interfere with nested crate dependencies
600            match fs::read_to_string(&src_path) {
601                Ok(content) => {
602                    let filtered: String = content
603                        .lines()
604                        .filter(|line| !line.trim().starts_with("[workspace]"))
605                        .collect::<Vec<_>>()
606                        .join("\n");
607                    fs::write(&dst_path, filtered)
608                        .map_err(|e| CompileError::Io(e.to_string()))?;
609                }
610                Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
611                Err(e) => return Err(CompileError::Io(e.to_string())),
612            }
613        } else {
614            match fs::copy(&src_path, &dst_path) {
615                Ok(_) => {}
616                Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
617                Err(e) => return Err(CompileError::Io(e.to_string())),
618            }
619        }
620    }
621
622    Ok(())
623}
624
625/// Compile and run a LOGOS program end-to-end.
626///
627/// This function performs the full compilation workflow:
628/// 1. Compile LOGOS to Rust via [`compile_to_dir`]
629/// 2. Run `cargo build` with JSON diagnostics
630/// 3. Translate any rustc errors to LOGOS-friendly messages
631/// 4. Run the compiled program via `cargo run`
632///
633/// # Arguments
634///
635/// * `source` - LOGOS source code as a string
636/// * `output_dir` - Directory to create the temporary Cargo project in
637///
638/// # Returns
639///
640/// The stdout output of the executed program.
641///
642/// # Errors
643///
644/// Returns [`CompileError`] if:
645/// - Compilation fails (see [`compile_to_dir`])
646/// - Rust compilation fails (`CompileError::Build` or `CompileError::Ownership`)
647/// - The program crashes at runtime (`CompileError::Runtime`)
648///
649/// # Diagnostic Translation
650///
651/// When rustc reports errors (e.g., E0382 for use-after-move), this function
652/// uses the [`diagnostic`](crate::diagnostic) module to translate them into
653/// LOGOS-friendly Socratic error messages.
654///
655/// # Example
656///
657/// ```ignore
658/// let source = "## Main\nShow \"Hello, World!\" to show.";
659/// let output = compile_and_run(source, Path::new("/tmp/run"))?;
660/// assert_eq!(output.trim(), "Hello, World!");
661/// ```
662pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
663    compile_to_dir(source, output_dir)?;
664
665    // Run cargo build with JSON message format for structured error parsing
666    let build_output = Command::new("cargo")
667        .arg("build")
668        .arg("--message-format=json")
669        .current_dir(output_dir)
670        .output()
671        .map_err(|e| CompileError::Io(e.to_string()))?;
672
673    if !build_output.status.success() {
674        let stderr = String::from_utf8_lossy(&build_output.stderr);
675        let stdout = String::from_utf8_lossy(&build_output.stdout);
676
677        // Try to parse JSON diagnostics and translate them
678        let diagnostics = parse_rustc_json(&stdout);
679
680        if !diagnostics.is_empty() {
681            // Create a basic source map with the LOGOS source
682            let source_map = SourceMap::new(source.to_string());
683            let interner = Interner::new();
684
685            if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
686                return Err(CompileError::Ownership(logos_error));
687            }
688        }
689
690        // Fallback to raw error if translation fails
691        return Err(CompileError::Build(stderr.to_string()));
692    }
693
694    // Run the compiled program
695    let run_output = Command::new("cargo")
696        .arg("run")
697        .arg("--quiet")
698        .current_dir(output_dir)
699        .output()
700        .map_err(|e| CompileError::Io(e.to_string()))?;
701
702    if !run_output.status.success() {
703        let stderr = String::from_utf8_lossy(&run_output.stderr);
704        return Err(CompileError::Runtime(stderr.to_string()));
705    }
706
707    let stdout = String::from_utf8_lossy(&run_output.stdout);
708    Ok(stdout.to_string())
709}
710
711/// Compile a LOGOS source file.
712/// For single-file compilation without dependencies.
713pub fn compile_file(path: &Path) -> Result<String, CompileError> {
714    let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
715    compile_to_rust(&source).map_err(CompileError::Parse)
716}
717
718/// Compile a multi-file LOGOS project with dependency resolution.
719///
720/// This function:
721/// 1. Reads the entry file
722/// 2. Scans for dependencies in the abstract (Markdown links)
723/// 3. Recursively loads and discovers types from dependencies
724/// 4. Compiles with the combined type registry
725///
726/// # Arguments
727/// * `entry_file` - The main entry file to compile (root is derived from parent directory)
728///
729/// # Example
730/// ```ignore
731/// let result = compile_project(Path::new("/project/main.md"));
732/// ```
733pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
734    use crate::loader::Loader;
735    use crate::analysis::discover_with_imports;
736
737    let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
738    let mut loader = Loader::new(root_path);
739    let mut interner = Interner::new();
740
741    // Read the entry file
742    let source = fs::read_to_string(entry_file)
743        .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
744
745    // Discover types from entry file and all imports
746    let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
747        .map_err(|e| CompileError::Io(e))?;
748
749    // Now compile with the discovered types
750    compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
751        .map_err(CompileError::Parse)
752}
753
754/// Compile LOGOS source with a pre-populated type registry, returning full output.
755/// Returns both generated Rust code and extracted dependencies.
756fn compile_to_rust_with_registry_full(
757    source: &str,
758    type_registry: crate::analysis::TypeRegistry,
759    interner: &mut Interner,
760) -> Result<CompileOutput, ParseError> {
761    let mut lexer = Lexer::new(source, interner);
762    let tokens = lexer.tokenize();
763
764    // Discovery pass for policies (types already discovered)
765    let policy_registry = {
766        let mut discovery = DiscoveryPass::new(&tokens, interner);
767        discovery.run_full().policies
768    };
769
770    let codegen_registry = type_registry.clone();
771    let codegen_policies = policy_registry.clone();
772
773    let mut world_state = WorldState::new();
774    let expr_arena = Arena::new();
775    let term_arena = Arena::new();
776    let np_arena = Arena::new();
777    let sym_arena = Arena::new();
778    let role_arena = Arena::new();
779    let pp_arena = Arena::new();
780    let stmt_arena: Arena<Stmt> = Arena::new();
781    let imperative_expr_arena: Arena<Expr> = Arena::new();
782    let type_expr_arena: Arena<TypeExpr> = Arena::new();
783
784    let ast_ctx = AstContext::with_types(
785        &expr_arena,
786        &term_arena,
787        &np_arena,
788        &sym_arena,
789        &role_arena,
790        &pp_arena,
791        &stmt_arena,
792        &imperative_expr_arena,
793        &type_expr_arena,
794    );
795
796    let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
797    let stmts = parser.parse_program()?;
798
799    // Extract dependencies before escape analysis
800    let dependencies = extract_dependencies(&stmts, interner);
801
802    let mut escape_checker = EscapeChecker::new(interner);
803    escape_checker.check_program(&stmts).map_err(|e| {
804        ParseError {
805            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
806            span: e.span,
807        }
808    })?;
809
810    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner);
811
812    Ok(CompileOutput { rust_code, dependencies })
813}
814
815/// Errors that can occur during the LOGOS compilation pipeline.
816///
817/// This enum represents the different stages where compilation can fail,
818/// from parsing through to runtime execution.
819///
820/// # Error Hierarchy
821///
822/// ```text
823/// CompileError
824/// ├── Parse      ← Lexing, parsing, or static analysis
825/// ├── Io         ← File system operations
826/// ├── Build      ← Rust compilation (cargo build)
827/// ├── Ownership  ← Translated borrow checker errors
828/// └── Runtime    ← Program execution failure
829/// ```
830///
831/// # Error Translation
832///
833/// The `Ownership` variant contains LOGOS-friendly error messages translated
834/// from rustc's borrow checker errors (E0382, E0505, E0597) using the
835/// [`diagnostic`](crate::diagnostic) module.
836#[derive(Debug)]
837pub enum CompileError {
838    /// Parsing or static analysis failed.
839    ///
840    /// This includes lexer errors, syntax errors, escape analysis failures,
841    /// ownership analysis failures, and Z3 verification failures.
842    Parse(ParseError),
843
844    /// File system operation failed.
845    ///
846    /// Typically occurs when reading source files or writing output.
847    Io(String),
848
849    /// Rust compilation failed (`cargo build`).
850    ///
851    /// Contains the raw stderr output from rustc when diagnostic translation
852    /// was not possible.
853    Build(String),
854
855    /// Runtime execution failed.
856    ///
857    /// Contains stderr output from the executed program.
858    Runtime(String),
859
860    /// Translated ownership/borrow checker error with LOGOS-friendly message.
861    ///
862    /// This variant is used when rustc reports errors like E0382 (use after move)
863    /// and we can translate them into natural language error messages that
864    /// reference the original LOGOS source.
865    Ownership(LogosError),
866}
867
868impl std::fmt::Display for CompileError {
869    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
870        match self {
871            CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
872            CompileError::Io(e) => write!(f, "IO error: {}", e),
873            CompileError::Build(e) => write!(f, "Build error: {}", e),
874            CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
875            CompileError::Ownership(e) => write!(f, "{}", e),
876        }
877    }
878}
879
880impl std::error::Error for CompileError {}
881
882#[cfg(test)]
883mod tests {
884    use super::*;
885
886    #[test]
887    fn test_compile_let_statement() {
888        let source = "## Main\nLet x be 5.";
889        let result = compile_to_rust(source);
890        assert!(result.is_ok(), "Should compile: {:?}", result);
891        let rust = result.unwrap();
892        assert!(rust.contains("fn main()"));
893        assert!(rust.contains("let x = 5;"));
894    }
895
896    #[test]
897    fn test_compile_return_statement() {
898        let source = "## Main\nReturn 42.";
899        let result = compile_to_rust(source);
900        assert!(result.is_ok(), "Should compile: {:?}", result);
901        let rust = result.unwrap();
902        assert!(rust.contains("return 42;"));
903    }
904}