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/// Compile from an ANF IR JSON file on disk.
21pub fn compile_from_ir(path: &Path) -> Result<RunarArtifact, String> {
22    let program = load_ir(path)?;
23    compile_from_program(&program)
24}
25
26/// Compile from an ANF IR JSON string.
27pub fn compile_from_ir_str(json_str: &str) -> Result<RunarArtifact, String> {
28    let program = load_ir_from_str(json_str)?;
29    compile_from_program(&program)
30}
31
32/// Compile from a `.runar.ts` source file on disk.
33pub fn compile_from_source(path: &Path) -> Result<RunarArtifact, String> {
34    let source = std::fs::read_to_string(path)
35        .map_err(|e| format!("reading source file: {}", e))?;
36    let file_name = path
37        .file_name()
38        .map(|n| n.to_string_lossy().to_string())
39        .unwrap_or_else(|| "contract.ts".to_string());
40    compile_from_source_str(&source, Some(&file_name))
41}
42
43/// Compile from a `.runar.ts` source string.
44pub fn compile_from_source_str(
45    source: &str,
46    file_name: Option<&str>,
47) -> Result<RunarArtifact, String> {
48    // Pass 1: Parse (auto-selects parser based on file extension)
49    let parse_result = frontend::parser::parse_source(source, file_name);
50    if !parse_result.errors.is_empty() {
51        let error_msgs: Vec<String> = parse_result.errors.iter().map(|e| e.to_string()).collect();
52        return Err(format!("Parse errors:\n  {}", error_msgs.join("\n  ")));
53    }
54
55    let contract = parse_result
56        .contract
57        .ok_or_else(|| "No contract found in source file".to_string())?;
58
59    // Pass 2: Validate
60    let validation = frontend::validator::validate(&contract);
61    if !validation.errors.is_empty() {
62        return Err(format!(
63            "Validation errors:\n  {}",
64            validation.errors.join("\n  ")
65        ));
66    }
67    for w in &validation.warnings {
68        eprintln!("Validation warning: {}", w);
69    }
70
71    // Pass 3: Type-check
72    let tc_result = frontend::typecheck::typecheck(&contract);
73    if !tc_result.errors.is_empty() {
74        return Err(format!(
75            "Type-check errors:\n  {}",
76            tc_result.errors.join("\n  ")
77        ));
78    }
79
80    // Pass 4: ANF Lower
81    let anf_program = frontend::anf_lower::lower_to_anf(&contract);
82
83    // Pass 4.5: EC optimization
84    let anf_program = frontend::anf_optimize::optimize_ec(anf_program);
85
86    // Passes 5-6: Backend (stack lowering + emit)
87    compile_from_program(&anf_program)
88}
89
90/// Compile from a `.runar.ts` source file to ANF IR only (passes 1-4).
91pub fn compile_source_to_ir(path: &Path) -> Result<ir::ANFProgram, String> {
92    let source = std::fs::read_to_string(path)
93        .map_err(|e| format!("reading source file: {}", e))?;
94    let file_name = path
95        .file_name()
96        .map(|n| n.to_string_lossy().to_string())
97        .unwrap_or_else(|| "contract.ts".to_string());
98    compile_source_str_to_ir(&source, Some(&file_name))
99}
100
101/// Compile from a `.runar.ts` source string to ANF IR only (passes 1-4).
102pub fn compile_source_str_to_ir(
103    source: &str,
104    file_name: Option<&str>,
105) -> Result<ir::ANFProgram, String> {
106    let parse_result = frontend::parser::parse_source(source, file_name);
107    if !parse_result.errors.is_empty() {
108        let error_msgs: Vec<String> = parse_result.errors.iter().map(|e| e.to_string()).collect();
109        return Err(format!("Parse errors:\n  {}", error_msgs.join("\n  ")));
110    }
111
112    let contract = parse_result
113        .contract
114        .ok_or_else(|| "No contract found in source file".to_string())?;
115
116    let validation = frontend::validator::validate(&contract);
117    if !validation.errors.is_empty() {
118        return Err(format!(
119            "Validation errors:\n  {}",
120            validation.errors.join("\n  ")
121        ));
122    }
123
124    let tc_result = frontend::typecheck::typecheck(&contract);
125    if !tc_result.errors.is_empty() {
126        return Err(format!(
127            "Type-check errors:\n  {}",
128            tc_result.errors.join("\n  ")
129        ));
130    }
131
132    let anf_program = frontend::anf_lower::lower_to_anf(&contract);
133    Ok(frontend::anf_optimize::optimize_ec(anf_program))
134}
135
136/// Compile a parsed ANF program to a Rúnar artifact.
137pub fn compile_from_program(program: &ir::ANFProgram) -> Result<RunarArtifact, String> {
138    // Pass 4.5: EC optimization (in case we receive unoptimized ANF from IR)
139    let optimized = frontend::anf_optimize::optimize_ec(program.clone());
140
141    // Pass 5: Stack lowering
142    let mut stack_methods = lower_to_stack(&optimized)?;
143
144    // Peephole optimization — runs on Stack IR before emission.
145    for method in &mut stack_methods {
146        method.ops = optimize_stack_ops(&method.ops);
147    }
148
149    // Pass 6: Emit
150    let emit_result = emit(&stack_methods)?;
151
152    let artifact = assemble_artifact(
153        &optimized,
154        &emit_result.script_hex,
155        &emit_result.script_asm,
156        emit_result.constructor_slots,
157    );
158    Ok(artifact)
159}