pub mod artifact;
pub mod codegen;
pub mod frontend;
pub mod ir;
use artifact::{assemble_artifact, RunarArtifact};
use codegen::emit::emit;
use codegen::optimizer::optimize_stack_ops;
use codegen::stack::lower_to_stack;
use ir::loader::{load_ir, load_ir_from_str};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct CompileOptions {
pub disable_constant_folding: bool,
pub parse_only: bool,
pub validate_only: bool,
pub typecheck_only: bool,
pub constructor_args: std::collections::HashMap<String, serde_json::Value>,
}
impl Default for CompileOptions {
fn default() -> Self {
Self {
disable_constant_folding: false,
parse_only: false,
validate_only: false,
typecheck_only: false,
constructor_args: std::collections::HashMap::new(),
}
}
}
fn apply_constructor_args(program: &mut ir::ANFProgram, args: &std::collections::HashMap<String, serde_json::Value>) {
if args.is_empty() {
return;
}
for prop in &mut program.properties {
if let Some(val) = args.get(&prop.name) {
prop.initial_value = Some(val.clone());
}
}
}
pub struct CompileResult {
pub contract: Option<frontend::ast::ContractNode>,
pub anf: Option<ir::ANFProgram>,
pub diagnostics: Vec<frontend::diagnostic::Diagnostic>,
pub success: bool,
pub artifact: Option<RunarArtifact>,
pub script_hex: Option<String>,
pub script_asm: Option<String>,
}
impl CompileResult {
fn new() -> Self {
Self {
contract: None,
anf: None,
diagnostics: Vec::new(),
success: false,
artifact: None,
script_hex: None,
script_asm: None,
}
}
fn has_errors(&self) -> bool {
self.diagnostics.iter().any(|d| d.severity == frontend::diagnostic::Severity::Error)
}
}
pub fn compile_from_ir(path: &Path) -> Result<RunarArtifact, String> {
compile_from_ir_with_options(path, &CompileOptions::default())
}
pub fn compile_from_ir_with_options(path: &Path, opts: &CompileOptions) -> Result<RunarArtifact, String> {
let program = load_ir(path)?;
compile_from_program_with_options(&program, opts)
}
pub fn compile_from_ir_str(json_str: &str) -> Result<RunarArtifact, String> {
compile_from_ir_str_with_options(json_str, &CompileOptions::default())
}
pub fn compile_from_ir_str_with_options(json_str: &str, opts: &CompileOptions) -> Result<RunarArtifact, String> {
let program = load_ir_from_str(json_str)?;
compile_from_program_with_options(&program, opts)
}
pub fn compile_from_source(path: &Path) -> Result<RunarArtifact, String> {
compile_from_source_with_options(path, &CompileOptions::default())
}
pub fn compile_from_source_with_options(path: &Path, opts: &CompileOptions) -> Result<RunarArtifact, String> {
let source = std::fs::read_to_string(path)
.map_err(|e| format!("reading source file: {}", e))?;
let file_name = path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "contract.ts".to_string());
compile_from_source_str_with_options(&source, Some(&file_name), opts)
}
pub fn compile_from_source_str(
source: &str,
file_name: Option<&str>,
) -> Result<RunarArtifact, String> {
compile_from_source_str_with_options(source, file_name, &CompileOptions::default())
}
pub fn compile_from_source_str_with_options(
source: &str,
file_name: Option<&str>,
opts: &CompileOptions,
) -> Result<RunarArtifact, String> {
let parse_result = frontend::parser::parse_source(source, file_name);
if !parse_result.errors.is_empty() {
let error_msgs: Vec<String> = parse_result.errors.iter().map(|e| e.to_string()).collect();
return Err(format!("Parse errors:\n {}", error_msgs.join("\n ")));
}
let contract = parse_result
.contract
.ok_or_else(|| "No contract found in source file".to_string())?;
let validation = frontend::validator::validate(&contract);
if !validation.errors.is_empty() {
return Err(format!(
"Validation errors:\n {}",
validation.error_strings().join("\n ")
));
}
for w in &validation.warnings {
eprintln!("Validation warning: {}", w);
}
let tc_result = frontend::typecheck::typecheck(&contract);
if !tc_result.errors.is_empty() {
return Err(format!(
"Type-check errors:\n {}",
tc_result.error_strings().join("\n ")
));
}
let mut anf_program = frontend::anf_lower::lower_to_anf(&contract);
apply_constructor_args(&mut anf_program, &opts.constructor_args);
if !opts.disable_constant_folding {
anf_program = frontend::constant_fold::fold_constants(&anf_program);
}
let anf_program = frontend::anf_optimize::optimize_ec(anf_program);
let backend_opts = CompileOptions { disable_constant_folding: true, ..Default::default() };
compile_from_program_with_options(&anf_program, &backend_opts)
}
pub fn compile_source_to_ir(path: &Path) -> Result<ir::ANFProgram, String> {
compile_source_to_ir_with_options(path, &CompileOptions::default())
}
pub fn compile_source_to_ir_with_options(path: &Path, opts: &CompileOptions) -> Result<ir::ANFProgram, String> {
let source = std::fs::read_to_string(path)
.map_err(|e| format!("reading source file: {}", e))?;
let file_name = path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "contract.ts".to_string());
compile_source_str_to_ir_with_options(&source, Some(&file_name), opts)
}
pub fn compile_source_str_to_ir(
source: &str,
file_name: Option<&str>,
) -> Result<ir::ANFProgram, String> {
compile_source_str_to_ir_with_options(source, file_name, &CompileOptions::default())
}
pub fn compile_source_str_to_ir_with_options(
source: &str,
file_name: Option<&str>,
opts: &CompileOptions,
) -> Result<ir::ANFProgram, String> {
let parse_result = frontend::parser::parse_source(source, file_name);
if !parse_result.errors.is_empty() {
let error_msgs: Vec<String> = parse_result.errors.iter().map(|e| e.to_string()).collect();
return Err(format!("Parse errors:\n {}", error_msgs.join("\n ")));
}
let contract = parse_result
.contract
.ok_or_else(|| "No contract found in source file".to_string())?;
let validation = frontend::validator::validate(&contract);
if !validation.errors.is_empty() {
return Err(format!(
"Validation errors:\n {}",
validation.error_strings().join("\n ")
));
}
let tc_result = frontend::typecheck::typecheck(&contract);
if !tc_result.errors.is_empty() {
return Err(format!(
"Type-check errors:\n {}",
tc_result.error_strings().join("\n ")
));
}
let mut anf_program = frontend::anf_lower::lower_to_anf(&contract);
apply_constructor_args(&mut anf_program, &opts.constructor_args);
if !opts.disable_constant_folding {
anf_program = frontend::constant_fold::fold_constants(&anf_program);
}
Ok(frontend::anf_optimize::optimize_ec(anf_program))
}
pub fn frontend_validate(source: &str, file_name: Option<&str>) -> (Vec<String>, Vec<String>) {
let parse_result = frontend::parser::parse_source(source, file_name);
if !parse_result.errors.is_empty() {
return (parse_result.error_strings(), vec![]);
}
let contract = match parse_result.contract {
Some(c) => c,
None => return (vec!["No contract found".to_string()], vec![]),
};
let result = frontend::validator::validate(&contract);
(result.error_strings(), result.warning_strings())
}
pub fn compile_from_program(program: &ir::ANFProgram) -> Result<RunarArtifact, String> {
compile_from_program_with_options(program, &CompileOptions::default())
}
pub fn compile_from_program_with_options(program: &ir::ANFProgram, opts: &CompileOptions) -> Result<RunarArtifact, String> {
let mut program = program.clone();
if !opts.disable_constant_folding {
program = frontend::constant_fold::fold_constants(&program);
}
let optimized = frontend::anf_optimize::optimize_ec(program);
let mut stack_methods = lower_to_stack(&optimized)?;
for method in &mut stack_methods {
let new_ops = optimize_stack_ops(&method.ops);
method.source_locs = vec![None; new_ops.len()];
method.ops = new_ops;
}
let emit_result = emit(&stack_methods)?;
let artifact = assemble_artifact(
&optimized,
&emit_result.script_hex,
&emit_result.script_asm,
emit_result.constructor_slots,
emit_result.code_separator_index,
emit_result.code_separator_indices,
true, emit_result.source_map,
);
Ok(artifact)
}
pub fn compile_from_source_str_with_result(
source: &str,
file_name: Option<&str>,
opts: &CompileOptions,
) -> CompileResult {
use frontend::diagnostic::Diagnostic;
let mut result = CompileResult::new();
let parse_result = frontend::parser::parse_source(source, file_name);
result.diagnostics.extend(parse_result.errors);
result.contract = parse_result.contract;
if result.has_errors() || result.contract.is_none() {
if result.contract.is_none() && !result.has_errors() {
result.diagnostics.push(Diagnostic::error(
"No contract found in source file",
None,
));
}
return result;
}
if opts.parse_only {
result.success = !result.has_errors();
return result;
}
let contract = result.contract.as_ref().unwrap();
let validation = frontend::validator::validate(contract);
result.diagnostics.extend(validation.errors);
result.diagnostics.extend(validation.warnings);
if result.has_errors() {
return result;
}
if opts.validate_only {
result.success = !result.has_errors();
return result;
}
let tc_result = frontend::typecheck::typecheck(contract);
result.diagnostics.extend(tc_result.errors);
if result.has_errors() {
return result;
}
if opts.typecheck_only {
result.success = !result.has_errors();
return result;
}
let mut anf_program = frontend::anf_lower::lower_to_anf(contract);
apply_constructor_args(&mut anf_program, &opts.constructor_args);
if !opts.disable_constant_folding {
anf_program = frontend::constant_fold::fold_constants(&anf_program);
}
anf_program = frontend::anf_optimize::optimize_ec(anf_program);
result.anf = Some(anf_program.clone());
let stack_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
lower_to_stack(&anf_program)
}));
let mut stack_methods = match stack_result {
Ok(Ok(methods)) => methods,
Ok(Err(e)) => {
result.diagnostics.push(Diagnostic::error(
format!("stack lowering: {}", e),
None,
));
return result;
}
Err(panic_val) => {
let msg = if let Some(s) = panic_val.downcast_ref::<&str>() {
format!("stack lowering panic: {}", s)
} else if let Some(s) = panic_val.downcast_ref::<String>() {
format!("stack lowering panic: {}", s)
} else {
"stack lowering panic: unknown error".to_string()
};
result.diagnostics.push(Diagnostic::error(msg, None));
return result;
}
};
for method in &mut stack_methods {
let new_ops = optimize_stack_ops(&method.ops);
method.source_locs = vec![None; new_ops.len()];
method.ops = new_ops;
}
let emit_result_outer = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
emit(&stack_methods)
}));
match emit_result_outer {
Ok(Ok(emit_result)) => {
let anf_ref = result.anf.as_ref().unwrap();
let artifact = assemble_artifact(
anf_ref,
&emit_result.script_hex,
&emit_result.script_asm,
emit_result.constructor_slots,
emit_result.code_separator_index,
emit_result.code_separator_indices,
true,
emit_result.source_map,
);
result.script_hex = Some(emit_result.script_hex);
result.script_asm = Some(emit_result.script_asm);
result.artifact = Some(artifact);
}
Ok(Err(e)) => {
result.diagnostics.push(Diagnostic::error(
format!("emit: {}", e),
None,
));
}
Err(panic_val) => {
let msg = if let Some(s) = panic_val.downcast_ref::<&str>() {
format!("emit panic: {}", s)
} else if let Some(s) = panic_val.downcast_ref::<String>() {
format!("emit panic: {}", s)
} else {
"emit panic: unknown error".to_string()
};
result.diagnostics.push(Diagnostic::error(msg, None));
}
}
result.success = !result.has_errors();
result
}
pub fn compile_from_source_with_result(
path: &Path,
opts: &CompileOptions,
) -> CompileResult {
use frontend::diagnostic::Diagnostic;
let source = match std::fs::read_to_string(path) {
Ok(s) => s,
Err(e) => {
let mut result = CompileResult::new();
result.diagnostics.push(Diagnostic::error(
format!("reading source file: {}", e),
None,
));
return result;
}
};
let file_name = path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "contract.ts".to_string());
compile_from_source_str_with_result(&source, Some(&file_name), opts)
}