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