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    let result = crate::ui_bridge::interpret_for_ui_sync(source);
139    if let Some(err) = result.error {
140        Err(ParseError {
141            kind: crate::error::ParseErrorKind::Custom(err),
142            span: crate::token::Span::default(),
143        })
144    } else {
145        Ok(result.lines.join("\n"))
146    }
147}
148
149/// Compile LOGOS source to Rust source code.
150///
151/// This is the basic compilation function that runs lexing, parsing, and
152/// escape analysis before generating Rust code.
153///
154/// # Arguments
155///
156/// * `source` - LOGOS source code as a string
157///
158/// # Returns
159///
160/// Generated Rust source code on success.
161///
162/// # Errors
163///
164/// Returns [`ParseError`] if:
165/// - Lexical analysis fails (invalid tokens)
166/// - Parsing fails (syntax errors)
167/// - Escape analysis fails (zone-local values escaping)
168///
169/// # Example
170///
171/// ```
172/// # use logicaffeine_compile::compile::compile_to_rust;
173/// # use logicaffeine_compile::ParseError;
174/// # fn main() -> Result<(), ParseError> {
175/// let source = "## Main\nLet x be 5.\nShow x.";
176/// let rust_code = compile_to_rust(source)?;
177/// assert!(rust_code.contains("let x = 5;"));
178/// # Ok(())
179/// # }
180/// ```
181pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
182    compile_program_full(source).map(|o| o.rust_code)
183}
184
185/// Compile LOGOS source to C code (benchmark-only subset).
186///
187/// Produces a self-contained C file with embedded runtime that can be
188/// compiled with `gcc -O2 -o program output.c`.
189pub fn compile_to_c(source: &str) -> Result<String, ParseError> {
190    let mut interner = Interner::new();
191    let mut lexer = Lexer::new(source, &mut interner);
192    let tokens = lexer.tokenize();
193
194    let (type_registry, _policy_registry) = {
195        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
196        let result = discovery.run_full();
197        (result.types, result.policies)
198    };
199    let codegen_registry = type_registry.clone();
200
201    let mut world_state = WorldState::new();
202    let expr_arena = Arena::new();
203    let term_arena = Arena::new();
204    let np_arena = Arena::new();
205    let sym_arena = Arena::new();
206    let role_arena = Arena::new();
207    let pp_arena = Arena::new();
208    let stmt_arena: Arena<Stmt> = Arena::new();
209    let imperative_expr_arena: Arena<Expr> = Arena::new();
210    let type_expr_arena: Arena<TypeExpr> = Arena::new();
211
212    let ast_ctx = AstContext::with_types(
213        &expr_arena, &term_arena, &np_arena, &sym_arena,
214        &role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
215        &type_expr_arena,
216    );
217
218    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
219    let stmts = parser.parse_program()?;
220    let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
221
222    Ok(crate::codegen_c::codegen_program_c(&stmts, &codegen_registry, &interner))
223}
224
225/// Compile LOGOS source and return full output including dependency metadata.
226///
227/// This is the primary compilation entry point that returns both the generated
228/// Rust code and any crate dependencies declared in `## Requires` blocks.
229pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
230    let mut interner = Interner::new();
231    let mut lexer = Lexer::new(source, &mut interner);
232    let tokens = lexer.tokenize();
233
234    // Pass 1: Discovery - scan for type definitions and policies
235    let (type_registry, policy_registry) = {
236        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
237        let result = discovery.run_full();
238        (result.types, result.policies)
239    };
240    // Clone for codegen (parser takes ownership)
241    let codegen_registry = type_registry.clone();
242    let codegen_policies = policy_registry.clone();
243
244    let mut world_state = WorldState::new();
245    let expr_arena = Arena::new();
246    let term_arena = Arena::new();
247    let np_arena = Arena::new();
248    let sym_arena = Arena::new();
249    let role_arena = Arena::new();
250    let pp_arena = Arena::new();
251    let stmt_arena: Arena<Stmt> = Arena::new();
252    let imperative_expr_arena: Arena<Expr> = Arena::new();
253    let type_expr_arena: Arena<TypeExpr> = Arena::new();
254
255    let ast_ctx = AstContext::with_types(
256        &expr_arena,
257        &term_arena,
258        &np_arena,
259        &sym_arena,
260        &role_arena,
261        &pp_arena,
262        &stmt_arena,
263        &imperative_expr_arena,
264        &type_expr_arena,
265    );
266
267    // Pass 2: Parse with type context
268    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
269    // Note: Don't call process_block_headers() - parse_program handles blocks itself
270
271    let stmts = parser.parse_program()?;
272
273    // Pass 2.5: Optimization - constant folding and dead code elimination
274    let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
275
276    // Extract dependencies before escape analysis
277    let mut dependencies = extract_dependencies(&stmts, &interner)?;
278
279    // FFI: Auto-inject wasm-bindgen dependency if any function is exported for WASM
280    let needs_wasm_bindgen = stmts.iter().any(|stmt| {
281        if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
282            interner.resolve(*target).eq_ignore_ascii_case("wasm")
283        } else {
284            false
285        }
286    });
287    if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
288        dependencies.push(CrateDependency {
289            name: "wasm-bindgen".to_string(),
290            version: "0.2".to_string(),
291            features: vec![],
292        });
293    }
294
295    // Pass 3: Escape analysis - check for zone escape violations
296    // This catches obvious cases like returning zone-local variables
297    let mut escape_checker = EscapeChecker::new(&interner);
298    escape_checker.check_program(&stmts).map_err(|e| {
299        // Convert EscapeError to ParseError for now
300        // The error message is already Socratic from EscapeChecker
301        ParseError {
302            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
303            span: e.span,
304        }
305    })?;
306
307    // Note: Static verification is available when the `verification` feature is enabled,
308    // but must be explicitly invoked via compile_to_rust_verified().
309
310    let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
311        .map_err(|e| ParseError {
312            kind: e.to_parse_error_kind(&interner),
313            span: crate::token::Span::default(),
314        })?;
315    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
316
317    // Universal ABI: Generate C header + bindings if any C exports exist
318    let has_c = stmts.iter().any(|stmt| {
319        if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
320            match export_target {
321                None => true,
322                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
323            }
324        } else {
325            false
326        }
327    });
328
329    let c_header = if has_c {
330        Some(generate_c_header(&stmts, "module", &interner, &codegen_registry))
331    } else {
332        None
333    };
334
335    // Auto-inject serde_json dependency when C exports exist (needed for collection to_json and portable struct JSON accessors)
336    if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
337        dependencies.push(CrateDependency {
338            name: "serde_json".to_string(),
339            version: "1".to_string(),
340            features: vec![],
341        });
342    }
343
344    let python_bindings = if has_c {
345        Some(generate_python_bindings(&stmts, "module", &interner, &codegen_registry))
346    } else {
347        None
348    };
349
350    let (typescript_bindings, typescript_types) = if has_c {
351        let (js, dts) = generate_typescript_bindings(&stmts, "module", &interner, &codegen_registry);
352        (Some(js), Some(dts))
353    } else {
354        (None, None)
355    };
356
357    Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
358}
359
360/// Extract crate dependencies from `Stmt::Require` nodes.
361///
362/// Deduplicates by crate name: same name + same version keeps one copy.
363/// Same name + different version returns a `ParseError`.
364/// Preserves declaration order (first occurrence wins).
365fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Result<Vec<CrateDependency>, ParseError> {
366    use std::collections::HashMap;
367
368    let mut seen: HashMap<String, String> = HashMap::new(); // name → version
369    let mut deps: Vec<CrateDependency> = Vec::new();
370
371    for stmt in stmts {
372        if let Stmt::Require { crate_name, version, features, span } = stmt {
373            let name = interner.resolve(*crate_name).to_string();
374            let ver = interner.resolve(*version).to_string();
375
376            if let Some(existing_ver) = seen.get(&name) {
377                if *existing_ver != ver {
378                    return Err(ParseError {
379                        kind: crate::error::ParseErrorKind::Custom(format!(
380                            "Conflicting versions for crate \"{}\": \"{}\" and \"{}\".",
381                            name, existing_ver, ver
382                        )),
383                        span: *span,
384                    });
385                }
386                // Same name + same version: skip duplicate
387            } else {
388                seen.insert(name.clone(), ver.clone());
389                deps.push(CrateDependency {
390                    name,
391                    version: ver,
392                    features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
393                });
394            }
395        }
396    }
397
398    Ok(deps)
399}
400
401/// Compile LOGOS source to Rust with ownership checking enabled.
402///
403/// This runs the lightweight ownership analysis pass that catches use-after-move
404/// errors with control flow awareness. The analysis is fast enough to run on
405/// every keystroke in an IDE.
406///
407/// # Arguments
408///
409/// * `source` - LOGOS source code as a string
410///
411/// # Returns
412///
413/// Generated Rust source code on success.
414///
415/// # Errors
416///
417/// Returns [`ParseError`] if:
418/// - Any error from [`compile_to_rust`] occurs
419/// - Ownership analysis detects use-after-move
420/// - Ownership analysis detects use-after-borrow violations
421///
422/// # Example
423///
424/// ```
425/// # use logicaffeine_compile::compile::compile_to_rust_checked;
426/// // This will fail ownership checking
427/// let source = "## Main\nLet x be 5.\nGive x to y.\nShow x.";
428/// let result = compile_to_rust_checked(source);
429/// assert!(result.is_err()); // "x has already been given away"
430/// ```
431///
432/// # Use Case
433///
434/// Use this function with the `--check` CLI flag for instant feedback on
435/// ownership errors before running the full Rust compilation.
436pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
437    let mut interner = Interner::new();
438    let mut lexer = Lexer::new(source, &mut interner);
439    let tokens = lexer.tokenize();
440
441    // Pass 1: Discovery - scan for type definitions and policies
442    let (type_registry, policy_registry) = {
443        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
444        let result = discovery.run_full();
445        (result.types, result.policies)
446    };
447    // Clone for codegen (parser takes ownership)
448    let codegen_registry = type_registry.clone();
449    let codegen_policies = policy_registry.clone();
450
451    let mut world_state = WorldState::new();
452    let expr_arena = Arena::new();
453    let term_arena = Arena::new();
454    let np_arena = Arena::new();
455    let sym_arena = Arena::new();
456    let role_arena = Arena::new();
457    let pp_arena = Arena::new();
458    let stmt_arena: Arena<Stmt> = Arena::new();
459    let imperative_expr_arena: Arena<Expr> = Arena::new();
460    let type_expr_arena: Arena<TypeExpr> = Arena::new();
461
462    let ast_ctx = AstContext::with_types(
463        &expr_arena,
464        &term_arena,
465        &np_arena,
466        &sym_arena,
467        &role_arena,
468        &pp_arena,
469        &stmt_arena,
470        &imperative_expr_arena,
471        &type_expr_arena,
472    );
473
474    // Pass 2: Parse with type context
475    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
476    let stmts = parser.parse_program()?;
477
478    // Pass 3: Escape analysis
479    let mut escape_checker = EscapeChecker::new(&interner);
480    escape_checker.check_program(&stmts).map_err(|e| {
481        ParseError {
482            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
483            span: e.span,
484        }
485    })?;
486
487    // Pass 4: Ownership analysis
488    // Catches use-after-move errors with control flow awareness
489    let mut ownership_checker = OwnershipChecker::new(&interner);
490    ownership_checker.check_program(&stmts).map_err(|e| {
491        ParseError {
492            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
493            span: e.span,
494        }
495    })?;
496
497    let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
498        .map_err(|e| ParseError {
499            kind: e.to_parse_error_kind(&interner),
500            span: crate::token::Span::default(),
501        })?;
502    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
503
504    Ok(rust_code)
505}
506
507/// Compile LOGOS source to Rust with full Z3 static verification.
508///
509/// This runs the Z3-based verifier on Assert statements before code generation,
510/// proving that assertions hold for all possible inputs. This is the most
511/// thorough compilation mode, suitable for high-assurance code.
512///
513/// # Arguments
514///
515/// * `source` - LOGOS source code as a string
516///
517/// # Returns
518///
519/// Generated Rust source code on success.
520///
521/// # Errors
522///
523/// Returns [`ParseError`] if:
524/// - Any error from [`compile_to_rust`] occurs
525/// - Z3 cannot prove an Assert statement
526/// - Refinement type constraints cannot be satisfied
527/// - Termination cannot be proven for loops with `decreasing`
528///
529/// # Example
530///
531/// ```no_run
532/// # use logicaffeine_compile::compile::compile_to_rust_verified;
533/// # use logicaffeine_compile::ParseError;
534/// # fn main() -> Result<(), ParseError> {
535/// let source = r#"
536/// ## Main
537/// Let x: { it: Int | it > 0 } be 5.
538/// Assert that x > 0.
539/// "#;
540/// let rust_code = compile_to_rust_verified(source)?;
541/// # Ok(())
542/// # }
543/// ```
544///
545/// # Feature Flag
546///
547/// This function requires the `verification` feature to be enabled:
548///
549/// ```toml
550/// [dependencies]
551/// logicaffeine_compile = { version = "...", features = ["verification"] }
552/// ```
553#[cfg(feature = "verification")]
554pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
555    use crate::verification::VerificationPass;
556
557    let mut interner = Interner::new();
558    let mut lexer = Lexer::new(source, &mut interner);
559    let tokens = lexer.tokenize();
560
561    // Pass 1: Discovery - scan for type definitions and policies
562    let (type_registry, policy_registry) = {
563        let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
564        let result = discovery.run_full();
565        (result.types, result.policies)
566    };
567    // Clone for codegen (parser takes ownership)
568    let codegen_registry = type_registry.clone();
569    let codegen_policies = policy_registry.clone();
570
571    let mut world_state = WorldState::new();
572    let expr_arena = Arena::new();
573    let term_arena = Arena::new();
574    let np_arena = Arena::new();
575    let sym_arena = Arena::new();
576    let role_arena = Arena::new();
577    let pp_arena = Arena::new();
578    let stmt_arena: Arena<Stmt> = Arena::new();
579    let imperative_expr_arena: Arena<Expr> = Arena::new();
580    let type_expr_arena: Arena<TypeExpr> = Arena::new();
581
582    let ast_ctx = AstContext::with_types(
583        &expr_arena,
584        &term_arena,
585        &np_arena,
586        &sym_arena,
587        &role_arena,
588        &pp_arena,
589        &stmt_arena,
590        &imperative_expr_arena,
591        &type_expr_arena,
592    );
593
594    // Pass 2: Parse with type context
595    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
596    let stmts = parser.parse_program()?;
597
598    // Pass 3: Escape analysis
599    let mut escape_checker = EscapeChecker::new(&interner);
600    escape_checker.check_program(&stmts).map_err(|e| {
601        ParseError {
602            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
603            span: e.span,
604        }
605    })?;
606
607    // Pass 4: Static verification
608    let mut verifier = VerificationPass::new(&interner);
609    verifier.verify_program(&stmts).map_err(|e| {
610        ParseError {
611            kind: crate::error::ParseErrorKind::Custom(format!(
612                "Verification Failed:\n\n{}",
613                e
614            )),
615            span: crate::token::Span::default(),
616        }
617    })?;
618
619    let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
620        .map_err(|e| ParseError {
621            kind: e.to_parse_error_kind(&interner),
622            span: crate::token::Span::default(),
623        })?;
624    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
625
626    Ok(rust_code)
627}
628
629/// Compile LOGOS source and write output to a directory as a Cargo project.
630///
631/// Creates a complete Cargo project structure with:
632/// - `src/main.rs` containing the generated Rust code
633/// - `Cargo.toml` with runtime dependencies
634/// - `crates/` directory with runtime crate copies
635///
636/// # Arguments
637///
638/// * `source` - LOGOS source code as a string
639/// * `output_dir` - Directory to create the Cargo project in
640///
641/// # Errors
642///
643/// Returns [`CompileError`] if:
644/// - Compilation fails (wrapped as `CompileError::Parse`)
645/// - File system operations fail (wrapped as `CompileError::Io`)
646///
647/// # Example
648///
649/// ```no_run
650/// # use logicaffeine_compile::compile::{compile_to_dir, CompileError};
651/// # use std::path::Path;
652/// # fn main() -> Result<(), CompileError> {
653/// let source = "## Main\nShow \"Hello\".";
654/// compile_to_dir(source, Path::new("/tmp/my_project"))?;
655/// // Now /tmp/my_project is a buildable Cargo project
656/// # Ok(())
657/// # }
658/// ```
659pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
660    let output = compile_program_full(source).map_err(CompileError::Parse)?;
661
662    // Create output directory structure
663    let src_dir = output_dir.join("src");
664    fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
665
666    // Write main.rs (codegen already includes the use statements)
667    let main_path = src_dir.join("main.rs");
668    let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
669    file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
670
671    // Write Cargo.toml with runtime crate dependencies
672    let mut cargo_toml = String::from(r#"[package]
673name = "logos_output"
674version = "0.1.0"
675edition = "2021"
676
677[dependencies]
678logicaffeine-data = { path = "./crates/logicaffeine_data" }
679logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
680tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
681
682[target.'cfg(target_os = "linux")'.dependencies]
683logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full", "io-uring"] }
684"#);
685
686    // Append user-declared dependencies from ## Requires blocks
687    for dep in &output.dependencies {
688        if dep.features.is_empty() {
689            let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
690        } else {
691            let feats = dep.features.iter()
692                .map(|f| format!("\"{}\"", f))
693                .collect::<Vec<_>>()
694                .join(", ");
695            let _ = writeln!(
696                cargo_toml,
697                "{} = {{ version = \"{}\", features = [{}] }}",
698                dep.name, dep.version, feats
699            );
700        }
701    }
702
703    cargo_toml.push_str("\n[profile.release]\nlto = true\nopt-level = 3\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\n");
704
705    let cargo_path = output_dir.join("Cargo.toml");
706    let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
707    file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
708
709    // Write .cargo/config.toml with target-cpu=native for optimal codegen.
710    // Enables SIMD auto-vectorization and CPU-specific instruction selection.
711    let cargo_config_dir = output_dir.join(".cargo");
712    fs::create_dir_all(&cargo_config_dir).map_err(|e| CompileError::Io(e.to_string()))?;
713    let config_content = "[build]\nrustflags = [\"-C\", \"target-cpu=native\"]\n";
714    let config_path = cargo_config_dir.join("config.toml");
715    fs::write(&config_path, config_content).map_err(|e| CompileError::Io(e.to_string()))?;
716
717    // Copy runtime crates to output directory
718    copy_runtime_crates(output_dir)?;
719
720    Ok(())
721}
722
723/// Copy the runtime crates to the output directory.
724/// Copies logicaffeine_data and logicaffeine_system.
725pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
726    let crates_dir = output_dir.join("crates");
727    fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
728
729    // Find workspace root
730    let workspace_root = find_workspace_root()?;
731
732    // Copy logicaffeine_data
733    let data_src = workspace_root.join(CRATES_DATA_PATH);
734    let data_dest = crates_dir.join("logicaffeine_data");
735    copy_dir_recursive(&data_src, &data_dest)?;
736    deworkspace_cargo_toml(&data_dest.join("Cargo.toml"))?;
737
738    // Copy logicaffeine_system
739    let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
740    let system_dest = crates_dir.join("logicaffeine_system");
741    copy_dir_recursive(&system_src, &system_dest)?;
742    deworkspace_cargo_toml(&system_dest.join("Cargo.toml"))?;
743
744    // Also need to copy logicaffeine_base since both crates depend on it
745    let base_src = workspace_root.join("crates/logicaffeine_base");
746    let base_dest = crates_dir.join("logicaffeine_base");
747    copy_dir_recursive(&base_src, &base_dest)?;
748    deworkspace_cargo_toml(&base_dest.join("Cargo.toml"))?;
749
750    Ok(())
751}
752
753/// Resolve workspace-inherited fields in a copied crate's Cargo.toml.
754///
755/// When runtime crates are copied to a standalone project, any fields using
756/// `*.workspace = true` won't resolve because there's no parent workspace.
757/// This rewrites them with concrete values (matching the workspace's settings).
758fn deworkspace_cargo_toml(cargo_toml_path: &Path) -> Result<(), CompileError> {
759    let content = fs::read_to_string(cargo_toml_path)
760        .map_err(|e| CompileError::Io(e.to_string()))?;
761
762    let mut result = String::with_capacity(content.len());
763    for line in content.lines() {
764        let trimmed = line.trim();
765        if trimmed == "edition.workspace = true" {
766            result.push_str("edition = \"2021\"");
767        } else if trimmed == "rust-version.workspace = true" {
768            result.push_str("rust-version = \"1.75\"");
769        } else if trimmed == "authors.workspace = true"
770            || trimmed == "repository.workspace = true"
771            || trimmed == "homepage.workspace = true"
772            || trimmed == "documentation.workspace = true"
773            || trimmed == "keywords.workspace = true"
774            || trimmed == "categories.workspace = true"
775            || trimmed == "license.workspace = true"
776        {
777            // Drop these lines — they're metadata not needed for compilation
778            continue;
779        } else if trimmed.contains(".workspace = true") {
780            // Catch-all: drop any other workspace-inherited fields
781            continue;
782        } else {
783            result.push_str(line);
784        }
785        result.push('\n');
786    }
787
788    fs::write(cargo_toml_path, result)
789        .map_err(|e| CompileError::Io(e.to_string()))?;
790
791    Ok(())
792}
793
794/// Find the workspace root directory.
795fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
796    // 1. Explicit override via LOGOS_WORKSPACE env var
797    if let Ok(workspace) = std::env::var("LOGOS_WORKSPACE") {
798        let path = Path::new(&workspace);
799        if path.join("Cargo.toml").exists() && path.join("crates").exists() {
800            return Ok(path.to_path_buf());
801        }
802    }
803
804    // 2. Try CARGO_MANIFEST_DIR (works during cargo build of largo itself)
805    if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
806        let path = Path::new(&manifest_dir);
807        if let Some(parent) = path.parent().and_then(|p| p.parent()) {
808            if parent.join("Cargo.toml").exists() {
809                return Ok(parent.to_path_buf());
810            }
811        }
812    }
813
814    // 3. Infer from the largo binary's own location
815    //    e.g. /workspace/target/release/largo → /workspace
816    if let Ok(exe) = std::env::current_exe() {
817        if let Some(dir) = exe.parent() {
818            // Walk up from the binary's directory
819            let mut candidate = dir.to_path_buf();
820            for _ in 0..5 {
821                if candidate.join("Cargo.toml").exists() && candidate.join("crates").exists() {
822                    return Ok(candidate);
823                }
824                if !candidate.pop() {
825                    break;
826                }
827            }
828        }
829    }
830
831    // 4. Fallback to current directory traversal
832    let mut current = std::env::current_dir()
833        .map_err(|e| CompileError::Io(e.to_string()))?;
834
835    loop {
836        if current.join("Cargo.toml").exists() && current.join("crates").exists() {
837            return Ok(current);
838        }
839        if !current.pop() {
840            return Err(CompileError::Io(
841                "Could not find workspace root. Set LOGOS_WORKSPACE env var or run from within the workspace.".to_string()
842            ));
843        }
844    }
845}
846
847/// Recursively copy a directory.
848/// Skips files that disappear during copy (race condition with parallel builds).
849fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
850    fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
851
852    for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
853        let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
854        let src_path = entry.path();
855        let file_name = entry.file_name();
856        let dst_path = dst.join(&file_name);
857
858        // Skip target directory, build artifacts, and lock files
859        if file_name == "target"
860            || file_name == ".git"
861            || file_name == "Cargo.lock"
862            || file_name == ".DS_Store"
863        {
864            continue;
865        }
866
867        // Skip files that start with a dot (hidden files)
868        if file_name.to_string_lossy().starts_with('.') {
869            continue;
870        }
871
872        // Check if path still exists (race condition protection)
873        if !src_path.exists() {
874            continue;
875        }
876
877        if src_path.is_dir() {
878            copy_dir_recursive(&src_path, &dst_path)?;
879        } else if file_name == "Cargo.toml" {
880            // Special handling for Cargo.toml: remove [workspace] line
881            // which can interfere with nested crate dependencies
882            match fs::read_to_string(&src_path) {
883                Ok(content) => {
884                    let filtered: String = content
885                        .lines()
886                        .filter(|line| !line.trim().starts_with("[workspace]"))
887                        .collect::<Vec<_>>()
888                        .join("\n");
889                    fs::write(&dst_path, filtered)
890                        .map_err(|e| CompileError::Io(e.to_string()))?;
891                }
892                Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
893                Err(e) => return Err(CompileError::Io(e.to_string())),
894            }
895        } else {
896            match fs::copy(&src_path, &dst_path) {
897                Ok(_) => {}
898                Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
899                Err(e) => return Err(CompileError::Io(e.to_string())),
900            }
901        }
902    }
903
904    Ok(())
905}
906
907/// Compile and run a LOGOS program end-to-end.
908///
909/// This function performs the full compilation workflow:
910/// 1. Compile LOGOS to Rust via [`compile_to_dir`]
911/// 2. Run `cargo build` with JSON diagnostics
912/// 3. Translate any rustc errors to LOGOS-friendly messages
913/// 4. Run the compiled program via `cargo run`
914///
915/// # Arguments
916///
917/// * `source` - LOGOS source code as a string
918/// * `output_dir` - Directory to create the temporary Cargo project in
919///
920/// # Returns
921///
922/// The stdout output of the executed program.
923///
924/// # Errors
925///
926/// Returns [`CompileError`] if:
927/// - Compilation fails (see [`compile_to_dir`])
928/// - Rust compilation fails (`CompileError::Build` or `CompileError::Ownership`)
929/// - The program crashes at runtime (`CompileError::Runtime`)
930///
931/// # Diagnostic Translation
932///
933/// When rustc reports errors (e.g., E0382 for use-after-move), this function
934/// uses the [`diagnostic`](crate::diagnostic) module to translate them into
935/// LOGOS-friendly Socratic error messages.
936///
937/// # Example
938///
939/// ```no_run
940/// # use logicaffeine_compile::compile::{compile_and_run, CompileError};
941/// # use std::path::Path;
942/// # fn main() -> Result<(), CompileError> {
943/// let source = "## Main\nShow \"Hello, World!\".";
944/// let output = compile_and_run(source, Path::new("/tmp/run"))?;
945/// assert_eq!(output.trim(), "Hello, World!");
946/// # Ok(())
947/// # }
948/// ```
949pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
950    // Pre-check: catch ownership errors (use-after-move) with friendly messages
951    // before codegen runs (codegen defensively clones, masking these errors)
952    compile_to_rust_checked(source).map_err(CompileError::Parse)?;
953
954    compile_to_dir(source, output_dir)?;
955
956    // Run cargo build with JSON message format for structured error parsing
957    let build_output = Command::new("cargo")
958        .arg("build")
959        .arg("--message-format=json")
960        .current_dir(output_dir)
961        .output()
962        .map_err(|e| CompileError::Io(e.to_string()))?;
963
964    if !build_output.status.success() {
965        let stderr = String::from_utf8_lossy(&build_output.stderr);
966        let stdout = String::from_utf8_lossy(&build_output.stdout);
967
968        // Try to parse JSON diagnostics and translate them
969        let diagnostics = parse_rustc_json(&stdout);
970
971        if !diagnostics.is_empty() {
972            // Create a basic source map with the LOGOS source
973            let source_map = SourceMap::new(source.to_string());
974            let interner = Interner::new();
975
976            if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
977                return Err(CompileError::Ownership(logos_error));
978            }
979        }
980
981        // Fallback to raw error if translation fails
982        return Err(CompileError::Build(stderr.to_string()));
983    }
984
985    // Run the compiled program
986    let run_output = Command::new("cargo")
987        .arg("run")
988        .arg("--quiet")
989        .current_dir(output_dir)
990        .output()
991        .map_err(|e| CompileError::Io(e.to_string()))?;
992
993    if !run_output.status.success() {
994        let stderr = String::from_utf8_lossy(&run_output.stderr);
995        return Err(CompileError::Runtime(stderr.to_string()));
996    }
997
998    let stdout = String::from_utf8_lossy(&run_output.stdout);
999    Ok(stdout.to_string())
1000}
1001
1002/// Compile a LOGOS source file.
1003/// For single-file compilation without dependencies.
1004pub fn compile_file(path: &Path) -> Result<String, CompileError> {
1005    let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
1006    compile_to_rust(&source).map_err(CompileError::Parse)
1007}
1008
1009/// Compile a multi-file LOGOS project with dependency resolution.
1010///
1011/// This function:
1012/// 1. Reads the entry file
1013/// 2. Scans for dependencies in the abstract (Markdown links)
1014/// 3. Recursively loads and discovers types from dependencies
1015/// 4. Compiles with the combined type registry
1016///
1017/// # Arguments
1018/// * `entry_file` - The main entry file to compile (root is derived from parent directory)
1019///
1020/// # Example
1021/// ```no_run
1022/// # use logicaffeine_compile::compile::compile_project;
1023/// # use std::path::Path;
1024/// let result = compile_project(Path::new("/project/main.md"));
1025/// ```
1026pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
1027    use crate::loader::Loader;
1028    use crate::analysis::discover_with_imports;
1029
1030    let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
1031    let mut loader = Loader::new(root_path);
1032    let mut interner = Interner::new();
1033
1034    // Read the entry file
1035    let source = fs::read_to_string(entry_file)
1036        .map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
1037
1038    // Discover types from entry file and all imports
1039    let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
1040        .map_err(|e| CompileError::Io(e))?;
1041
1042    // Now compile with the discovered types
1043    compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
1044        .map_err(CompileError::Parse)
1045}
1046
1047/// Compile LOGOS source with a pre-populated type registry, returning full output.
1048/// Returns both generated Rust code and extracted dependencies.
1049fn compile_to_rust_with_registry_full(
1050    source: &str,
1051    type_registry: crate::analysis::TypeRegistry,
1052    interner: &mut Interner,
1053) -> Result<CompileOutput, ParseError> {
1054    let mut lexer = Lexer::new(source, interner);
1055    let tokens = lexer.tokenize();
1056
1057    // Discovery pass for policies (types already discovered)
1058    let policy_registry = {
1059        let mut discovery = DiscoveryPass::new(&tokens, interner);
1060        discovery.run_full().policies
1061    };
1062
1063    let codegen_registry = type_registry.clone();
1064    let codegen_policies = policy_registry.clone();
1065
1066    let mut world_state = WorldState::new();
1067    let expr_arena = Arena::new();
1068    let term_arena = Arena::new();
1069    let np_arena = Arena::new();
1070    let sym_arena = Arena::new();
1071    let role_arena = Arena::new();
1072    let pp_arena = Arena::new();
1073    let stmt_arena: Arena<Stmt> = Arena::new();
1074    let imperative_expr_arena: Arena<Expr> = Arena::new();
1075    let type_expr_arena: Arena<TypeExpr> = Arena::new();
1076
1077    let ast_ctx = AstContext::with_types(
1078        &expr_arena,
1079        &term_arena,
1080        &np_arena,
1081        &sym_arena,
1082        &role_arena,
1083        &pp_arena,
1084        &stmt_arena,
1085        &imperative_expr_arena,
1086        &type_expr_arena,
1087    );
1088
1089    let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
1090    let stmts = parser.parse_program()?;
1091
1092    // Extract dependencies before escape analysis
1093    let mut dependencies = extract_dependencies(&stmts, interner)?;
1094
1095    // FFI: Auto-inject wasm-bindgen dependency if any function is exported for WASM
1096    let needs_wasm_bindgen = stmts.iter().any(|stmt| {
1097        if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
1098            interner.resolve(*target).eq_ignore_ascii_case("wasm")
1099        } else {
1100            false
1101        }
1102    });
1103    if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
1104        dependencies.push(CrateDependency {
1105            name: "wasm-bindgen".to_string(),
1106            version: "0.2".to_string(),
1107            features: vec![],
1108        });
1109    }
1110
1111    let mut escape_checker = EscapeChecker::new(interner);
1112    escape_checker.check_program(&stmts).map_err(|e| {
1113        ParseError {
1114            kind: crate::error::ParseErrorKind::Custom(e.to_string()),
1115            span: e.span,
1116        }
1117    })?;
1118
1119    let type_env = crate::analysis::check_program(&stmts, interner, &codegen_registry)
1120        .map_err(|e| ParseError {
1121            kind: e.to_parse_error_kind(interner),
1122            span: crate::token::Span::default(),
1123        })?;
1124    let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner, &type_env);
1125
1126    // Universal ABI: Generate C header + bindings if any C exports exist
1127    let has_c = stmts.iter().any(|stmt| {
1128        if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
1129            match export_target {
1130                None => true,
1131                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
1132            }
1133        } else {
1134            false
1135        }
1136    });
1137
1138    let c_header = if has_c {
1139        Some(generate_c_header(&stmts, "module", interner, &codegen_registry))
1140    } else {
1141        None
1142    };
1143
1144    if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
1145        dependencies.push(CrateDependency {
1146            name: "serde_json".to_string(),
1147            version: "1".to_string(),
1148            features: vec![],
1149        });
1150    }
1151
1152    let python_bindings = if has_c {
1153        Some(generate_python_bindings(&stmts, "module", interner, &codegen_registry))
1154    } else {
1155        None
1156    };
1157
1158    let (typescript_bindings, typescript_types) = if has_c {
1159        let (js, dts) = generate_typescript_bindings(&stmts, "module", interner, &codegen_registry);
1160        (Some(js), Some(dts))
1161    } else {
1162        (None, None)
1163    };
1164
1165    Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
1166}
1167
1168/// Errors that can occur during the LOGOS compilation pipeline.
1169///
1170/// This enum represents the different stages where compilation can fail,
1171/// from parsing through to runtime execution.
1172///
1173/// # Error Hierarchy
1174///
1175/// ```text
1176/// CompileError
1177/// ├── Parse      ← Lexing, parsing, or static analysis
1178/// ├── Io         ← File system operations
1179/// ├── Build      ← Rust compilation (cargo build)
1180/// ├── Ownership  ← Translated borrow checker errors
1181/// └── Runtime    ← Program execution failure
1182/// ```
1183///
1184/// # Error Translation
1185///
1186/// The `Ownership` variant contains LOGOS-friendly error messages translated
1187/// from rustc's borrow checker errors (E0382, E0505, E0597) using the
1188/// [`diagnostic`](crate::diagnostic) module.
1189#[derive(Debug)]
1190pub enum CompileError {
1191    /// Parsing or static analysis failed.
1192    ///
1193    /// This includes lexer errors, syntax errors, escape analysis failures,
1194    /// ownership analysis failures, and Z3 verification failures.
1195    Parse(ParseError),
1196
1197    /// File system operation failed.
1198    ///
1199    /// Typically occurs when reading source files or writing output.
1200    Io(String),
1201
1202    /// Rust compilation failed (`cargo build`).
1203    ///
1204    /// Contains the raw stderr output from rustc when diagnostic translation
1205    /// was not possible.
1206    Build(String),
1207
1208    /// Runtime execution failed.
1209    ///
1210    /// Contains stderr output from the executed program.
1211    Runtime(String),
1212
1213    /// Translated ownership/borrow checker error with LOGOS-friendly message.
1214    ///
1215    /// This variant is used when rustc reports errors like E0382 (use after move)
1216    /// and we can translate them into natural language error messages that
1217    /// reference the original LOGOS source.
1218    Ownership(LogosError),
1219}
1220
1221impl std::fmt::Display for CompileError {
1222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1223        match self {
1224            CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
1225            CompileError::Io(e) => write!(f, "IO error: {}", e),
1226            CompileError::Build(e) => write!(f, "Build error: {}", e),
1227            CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
1228            CompileError::Ownership(e) => write!(f, "{}", e),
1229        }
1230    }
1231}
1232
1233impl std::error::Error for CompileError {}
1234
1235#[cfg(test)]
1236mod tests {
1237    use super::*;
1238
1239    #[test]
1240    fn test_compile_let_statement() {
1241        let source = "## Main\nLet x be 5.";
1242        let result = compile_to_rust(source);
1243        assert!(result.is_ok(), "Should compile: {:?}", result);
1244        let rust = result.unwrap();
1245        assert!(rust.contains("fn main()"));
1246        assert!(rust.contains("let x = 5;"));
1247    }
1248
1249    #[test]
1250    fn test_compile_return_statement() {
1251        let source = "## Main\nReturn 42.";
1252        let result = compile_to_rust(source);
1253        assert!(result.is_ok(), "Should compile: {:?}", result);
1254        let rust = result.unwrap();
1255        assert!(rust.contains("return 42;"));
1256    }
1257}