Skip to main content

runar_compiler_rust/
lib.rs

1//! Rúnar Compiler (Rust) — library root.
2//!
3//! Full compilation pipeline:
4//!   - IR consumer mode: accepts ANF IR JSON, emits Bitcoin Script.
5//!   - Source mode: compiles `.runar.ts` source files through all passes.
6
7pub mod artifact;
8pub mod codegen;
9pub mod frontend;
10pub mod ir;
11
12use artifact::{assemble_artifact, RunarArtifact};
13use codegen::emit::emit;
14use codegen::optimizer::optimize_stack_ops;
15use codegen::stack::lower_to_stack;
16use ir::loader::{load_ir, load_ir_from_str};
17
18use std::path::Path;
19
20/// Options controlling the compilation pipeline.
21#[derive(Debug, Clone)]
22pub struct CompileOptions {
23    /// When true, skip the constant-folding optimisation pass.
24    pub disable_constant_folding: bool,
25}
26
27impl Default for CompileOptions {
28    fn default() -> Self {
29        Self {
30            disable_constant_folding: false,
31        }
32    }
33}
34
35/// Compile from an ANF IR JSON file on disk.
36pub fn compile_from_ir(path: &Path) -> Result<RunarArtifact, String> {
37    compile_from_ir_with_options(path, &CompileOptions::default())
38}
39
40/// Compile from an ANF IR JSON file on disk, with options.
41pub fn compile_from_ir_with_options(path: &Path, opts: &CompileOptions) -> Result<RunarArtifact, String> {
42    let program = load_ir(path)?;
43    compile_from_program_with_options(&program, opts)
44}
45
46/// Compile from an ANF IR JSON string.
47pub fn compile_from_ir_str(json_str: &str) -> Result<RunarArtifact, String> {
48    compile_from_ir_str_with_options(json_str, &CompileOptions::default())
49}
50
51/// Compile from an ANF IR JSON string, with options.
52pub fn compile_from_ir_str_with_options(json_str: &str, opts: &CompileOptions) -> Result<RunarArtifact, String> {
53    let program = load_ir_from_str(json_str)?;
54    compile_from_program_with_options(&program, opts)
55}
56
57/// Compile from a `.runar.ts` source file on disk.
58pub fn compile_from_source(path: &Path) -> Result<RunarArtifact, String> {
59    compile_from_source_with_options(path, &CompileOptions::default())
60}
61
62/// Compile from a `.runar.ts` source file on disk, with options.
63pub fn compile_from_source_with_options(path: &Path, opts: &CompileOptions) -> Result<RunarArtifact, String> {
64    let source = std::fs::read_to_string(path)
65        .map_err(|e| format!("reading source file: {}", e))?;
66    let file_name = path
67        .file_name()
68        .map(|n| n.to_string_lossy().to_string())
69        .unwrap_or_else(|| "contract.ts".to_string());
70    compile_from_source_str_with_options(&source, Some(&file_name), opts)
71}
72
73/// Compile from a `.runar.ts` source string.
74pub fn compile_from_source_str(
75    source: &str,
76    file_name: Option<&str>,
77) -> Result<RunarArtifact, String> {
78    compile_from_source_str_with_options(source, file_name, &CompileOptions::default())
79}
80
81/// Compile from a `.runar.ts` source string, with options.
82pub fn compile_from_source_str_with_options(
83    source: &str,
84    file_name: Option<&str>,
85    opts: &CompileOptions,
86) -> Result<RunarArtifact, String> {
87    // Pass 1: Parse (auto-selects parser based on file extension)
88    let parse_result = frontend::parser::parse_source(source, file_name);
89    if !parse_result.errors.is_empty() {
90        let error_msgs: Vec<String> = parse_result.errors.iter().map(|e| e.to_string()).collect();
91        return Err(format!("Parse errors:\n  {}", error_msgs.join("\n  ")));
92    }
93
94    let contract = parse_result
95        .contract
96        .ok_or_else(|| "No contract found in source file".to_string())?;
97
98    // Pass 2: Validate
99    let validation = frontend::validator::validate(&contract);
100    if !validation.errors.is_empty() {
101        return Err(format!(
102            "Validation errors:\n  {}",
103            validation.errors.join("\n  ")
104        ));
105    }
106    for w in &validation.warnings {
107        eprintln!("Validation warning: {}", w);
108    }
109
110    // Pass 3: Type-check
111    let tc_result = frontend::typecheck::typecheck(&contract);
112    if !tc_result.errors.is_empty() {
113        return Err(format!(
114            "Type-check errors:\n  {}",
115            tc_result.errors.join("\n  ")
116        ));
117    }
118
119    // Pass 4: ANF Lower
120    let mut anf_program = frontend::anf_lower::lower_to_anf(&contract);
121
122    // Pass 4.25: Constant folding (optional)
123    if !opts.disable_constant_folding {
124        anf_program = frontend::constant_fold::fold_constants(&anf_program);
125    }
126
127    // Pass 4.5: EC optimization
128    let anf_program = frontend::anf_optimize::optimize_ec(anf_program);
129
130    // Passes 5-6: Backend (stack lowering + emit)
131    // Constant folding already ran above; skip it in compile_from_program.
132    let backend_opts = CompileOptions { disable_constant_folding: true };
133    compile_from_program_with_options(&anf_program, &backend_opts)
134}
135
136/// Compile from a `.runar.ts` source file to ANF IR only (passes 1-4).
137pub fn compile_source_to_ir(path: &Path) -> Result<ir::ANFProgram, String> {
138    compile_source_to_ir_with_options(path, &CompileOptions::default())
139}
140
141/// Compile from a `.runar.ts` source file to ANF IR only (passes 1-4), with options.
142pub fn compile_source_to_ir_with_options(path: &Path, opts: &CompileOptions) -> Result<ir::ANFProgram, String> {
143    let source = std::fs::read_to_string(path)
144        .map_err(|e| format!("reading source file: {}", e))?;
145    let file_name = path
146        .file_name()
147        .map(|n| n.to_string_lossy().to_string())
148        .unwrap_or_else(|| "contract.ts".to_string());
149    compile_source_str_to_ir_with_options(&source, Some(&file_name), opts)
150}
151
152/// Compile from a `.runar.ts` source string to ANF IR only (passes 1-4).
153pub fn compile_source_str_to_ir(
154    source: &str,
155    file_name: Option<&str>,
156) -> Result<ir::ANFProgram, String> {
157    compile_source_str_to_ir_with_options(source, file_name, &CompileOptions::default())
158}
159
160/// Compile from a `.runar.ts` source string to ANF IR only (passes 1-4), with options.
161pub fn compile_source_str_to_ir_with_options(
162    source: &str,
163    file_name: Option<&str>,
164    opts: &CompileOptions,
165) -> Result<ir::ANFProgram, String> {
166    let parse_result = frontend::parser::parse_source(source, file_name);
167    if !parse_result.errors.is_empty() {
168        let error_msgs: Vec<String> = parse_result.errors.iter().map(|e| e.to_string()).collect();
169        return Err(format!("Parse errors:\n  {}", error_msgs.join("\n  ")));
170    }
171
172    let contract = parse_result
173        .contract
174        .ok_or_else(|| "No contract found in source file".to_string())?;
175
176    let validation = frontend::validator::validate(&contract);
177    if !validation.errors.is_empty() {
178        return Err(format!(
179            "Validation errors:\n  {}",
180            validation.errors.join("\n  ")
181        ));
182    }
183
184    let tc_result = frontend::typecheck::typecheck(&contract);
185    if !tc_result.errors.is_empty() {
186        return Err(format!(
187            "Type-check errors:\n  {}",
188            tc_result.errors.join("\n  ")
189        ));
190    }
191
192    let mut anf_program = frontend::anf_lower::lower_to_anf(&contract);
193
194    // Pass 4.25: Constant folding (optional)
195    if !opts.disable_constant_folding {
196        anf_program = frontend::constant_fold::fold_constants(&anf_program);
197    }
198
199    Ok(frontend::anf_optimize::optimize_ec(anf_program))
200}
201
202/// Run only the parse + validate passes on a source string.
203/// Returns `(errors, warnings)`. Exposed for testing warnings.
204pub fn frontend_validate(source: &str, file_name: Option<&str>) -> (Vec<String>, Vec<String>) {
205    let parse_result = frontend::parser::parse_source(source, file_name);
206    if !parse_result.errors.is_empty() {
207        return (parse_result.errors, vec![]);
208    }
209    let contract = match parse_result.contract {
210        Some(c) => c,
211        None => return (vec!["No contract found".to_string()], vec![]),
212    };
213    let result = frontend::validator::validate(&contract);
214    (result.errors, result.warnings)
215}
216
217/// Compile a parsed ANF program to a Rúnar artifact.
218pub fn compile_from_program(program: &ir::ANFProgram) -> Result<RunarArtifact, String> {
219    compile_from_program_with_options(program, &CompileOptions::default())
220}
221
222/// Compile a parsed ANF program to a Rúnar artifact, with options.
223pub fn compile_from_program_with_options(program: &ir::ANFProgram, opts: &CompileOptions) -> Result<RunarArtifact, String> {
224    // Pass 4.25: Constant folding (optional, in case we receive unoptimized ANF from IR)
225    let mut program = program.clone();
226    if !opts.disable_constant_folding {
227        program = frontend::constant_fold::fold_constants(&program);
228    }
229
230    // Pass 4.5: EC optimization (in case we receive unoptimized ANF from IR)
231    let optimized = frontend::anf_optimize::optimize_ec(program);
232
233    // Pass 5: Stack lowering
234    let mut stack_methods = lower_to_stack(&optimized)?;
235
236    // Peephole optimization — runs on Stack IR before emission.
237    for method in &mut stack_methods {
238        method.ops = optimize_stack_ops(&method.ops);
239    }
240
241    // Pass 6: Emit
242    let emit_result = emit(&stack_methods)?;
243
244    let artifact = assemble_artifact(
245        &optimized,
246        &emit_result.script_hex,
247        &emit_result.script_asm,
248        emit_result.constructor_slots,
249        emit_result.code_separator_index,
250        emit_result.code_separator_indices,
251        true, // include ANF IR for SDK state auto-computation
252    );
253    Ok(artifact)
254}