joulec/
lib.rs

1//! Joule Compiler Library
2//!
3//! Provides the programmatic API for invoking the Joule compiler pipeline.
4//! This allows other tools (LSP, REPL, test harness, documentation generator)
5//! to compile Joule source code without spawning a subprocess.
6//!
7//! # Pipeline Stages
8//!
9//! The compilation pipeline proceeds through these stages:
10//! 1. **Lexing** — Source text → token stream
11//! 2. **Parsing** — Token stream → AST
12//! 3. **Type checking** — AST → HIR (with type resolution and energy budget verification)
13//! 4. **MIR lowering** — HIR → MIR (control flow graph with ownership info)
14//! 5. **Borrow checking** — MIR validation (ownership, lifetimes, initialization)
15//! 6. **Code generation** — MIR → target code (Cranelift, LLVM, MLIR, or C)
16//!
17//! # Example
18//!
19//! ```rust,no_run
20//! use joulec::{CompileOptions, compile_source};
21//!
22//! let source = r#"
23//!     pub fn add(a: i32, b: i32) -> i32 {
24//!         a + b
25//!     }
26//! "#;
27//!
28//! let options = CompileOptions::default();
29//! let result = compile_source(source, "example.joule", &options);
30//! ```
31
32use joule_common::{Diagnostic, SourceMap, Symbol};
33use std::collections::{HashMap, HashSet};
34use std::path::Path;
35
36/// Options controlling the compilation pipeline.
37#[derive(Debug, Clone, Default)]
38pub struct CompileOptions {
39    /// Whether to perform energy budget checking during type checking.
40    pub energy_check: bool,
41    /// Optimization level (0-3).
42    pub opt_level: u8,
43    /// File-to-module mapping for multi-file compilation.
44    pub file_to_module: HashMap<u32, Symbol>,
45}
46
47/// The result of a successful compilation through the full pipeline.
48pub struct CompileOutput {
49    /// The type-checked HIR.
50    pub hir: joule_hir::Hir,
51    /// The lowered MIR (after borrow checking).
52    pub mir: joule_mir::MirContext,
53    /// Types that appear in multiple modules with the same name (for C codegen disambiguation).
54    pub conflicting_types: HashSet<Symbol>,
55    /// The source map used during compilation (for error reporting).
56    pub source_map: SourceMap,
57}
58
59/// Compile a single source string through the full pipeline (lex → parse → typecheck → MIR → borrowck).
60///
61/// Returns the compiled MIR context ready for code generation, or diagnostics on failure.
62pub fn compile_source(
63    source: &str,
64    filename: &str,
65    options: &CompileOptions,
66) -> Result<CompileOutput, Vec<Diagnostic>> {
67    let mut source_map = SourceMap::new();
68    let file_id = source_map.add_file(filename.to_string(), source.to_string());
69
70    // 1. Lex
71    let tokens = {
72        let lexer = joule_lexer::Lexer::new(source, file_id);
73        lexer.tokenize()
74    };
75
76    // 2. Parse
77    let ast = {
78        let parser = joule_parser::Parser::new(tokens);
79        parser.parse_file()?
80    };
81
82    // 3. Type check
83    let mut typeck = joule_typeck::TypeChecker::new();
84    typeck.set_energy_budget_checking(options.energy_check);
85    if !options.file_to_module.is_empty() {
86        typeck.set_file_to_module(options.file_to_module.clone());
87    }
88
89    let hir = typeck.check_file(&ast)?;
90    let conflicting_types = typeck.conflicting_types().clone();
91
92    // 4. Lower to MIR
93    let mir = joule_mir::build::build_mir(&hir);
94
95    // 5. Borrow check
96    let mut borrowck = joule_borrowck::BorrowChecker::new();
97    borrowck.check_context(&mir)?;
98
99    Ok(CompileOutput {
100        hir,
101        mir,
102        conflicting_types,
103        source_map,
104    })
105}
106
107/// Compile source and emit C code (the bootstrap self-hosting path).
108///
109/// This is the equivalent of `joulec --emit c` but callable as a library function.
110pub fn compile_to_c(
111    source: &str,
112    filename: &str,
113    options: &CompileOptions,
114) -> Result<String, Vec<Diagnostic>> {
115    let output = compile_source(source, filename, options)?;
116    joule_codegen_c::compile_to_c_with_modules(
117        &output.mir,
118        &options.file_to_module,
119        &output.conflicting_types,
120    )
121    .map_err(|e| vec![Diagnostic::error(format!("C code generation failed: {e}"))])
122}
123
124/// Parse source code into an AST without type checking.
125///
126/// Useful for tools that need syntactic analysis (formatters, linters)
127/// without the overhead of full compilation.
128pub fn parse_source(
129    source: &str,
130    filename: &str,
131) -> Result<(joule_ast::File, SourceMap), Vec<Diagnostic>> {
132    let mut source_map = SourceMap::new();
133    let file_id = source_map.add_file(filename.to_string(), source.to_string());
134
135    let tokens = {
136        let lexer = joule_lexer::Lexer::new(source, file_id);
137        lexer.tokenize()
138    };
139
140    let ast = {
141        let parser = joule_parser::Parser::new(tokens);
142        parser.parse_file()?
143    };
144
145    Ok((ast, source_map))
146}
147
148/// Type check source code and return the HIR.
149///
150/// Useful for tools that need type information (LSP, documentation)
151/// without lowering to MIR.
152pub fn type_check_source(
153    source: &str,
154    filename: &str,
155    options: &CompileOptions,
156) -> Result<(joule_hir::Hir, SourceMap), Vec<Diagnostic>> {
157    let mut source_map = SourceMap::new();
158    let file_id = source_map.add_file(filename.to_string(), source.to_string());
159
160    let tokens = {
161        let lexer = joule_lexer::Lexer::new(source, file_id);
162        lexer.tokenize()
163    };
164
165    let ast = {
166        let parser = joule_parser::Parser::new(tokens);
167        parser.parse_file()?
168    };
169
170    let mut typeck = joule_typeck::TypeChecker::new();
171    typeck.set_energy_budget_checking(options.energy_check);
172    if !options.file_to_module.is_empty() {
173        typeck.set_file_to_module(options.file_to_module.clone());
174    }
175
176    let hir = typeck.check_file(&ast)?;
177    Ok((hir, source_map))
178}
179
180/// Compile multiple source files together (multi-file compilation).
181///
182/// This resolves cross-module references and produces a single MIR context.
183/// Used by the bootstrap compiler path and for project-level compilation.
184pub fn compile_multi_file(
185    sources: &[(&str, &str)], // (filename, source_code) pairs
186    options: &CompileOptions,
187) -> Result<CompileOutput, Vec<Diagnostic>> {
188    let mut source_map = SourceMap::new();
189    let mut all_items: Vec<joule_ast::Item> = Vec::new();
190    let mut file_to_module = options.file_to_module.clone();
191
192    for (filename, source) in sources {
193        let file_id = source_map.add_file(filename.to_string(), source.to_string());
194
195        // Extract module name from filename
196        if let Some(stem) = Path::new(filename).file_stem().and_then(|s| s.to_str()) {
197            file_to_module.insert(file_id, Symbol::intern(stem));
198        }
199
200        let tokens = {
201            let lexer = joule_lexer::Lexer::new(source, file_id);
202            lexer.tokenize()
203        };
204
205        let ast = {
206            let parser = joule_parser::Parser::new(tokens);
207            parser.parse_file()?
208        };
209
210        // Collect non-Use items from all files
211        for item in ast.items {
212            if !matches!(item, joule_ast::Item::Use(_)) {
213                all_items.push(item);
214            }
215        }
216    }
217
218    let merged_ast = joule_ast::File { items: all_items };
219
220    // Type check with cross-module resolution
221    let mut typeck = joule_typeck::TypeChecker::new();
222    typeck.set_energy_budget_checking(options.energy_check);
223    typeck.set_file_to_module(file_to_module.clone());
224
225    let hir = typeck.check_file(&merged_ast)?;
226    let conflicting_types = typeck.conflicting_types().clone();
227
228    // Lower and borrow check
229    let mir = joule_mir::build::build_mir(&hir);
230    let mut borrowck = joule_borrowck::BorrowChecker::new();
231    borrowck.check_context(&mir)?;
232
233    Ok(CompileOutput {
234        hir,
235        mir,
236        conflicting_types,
237        source_map,
238    })
239}