1pub 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#[derive(Debug, Clone)]
22pub struct CompileOptions {
23 pub disable_constant_folding: bool,
25 pub parse_only: bool,
27 pub validate_only: bool,
29 pub typecheck_only: bool,
31 pub constructor_args: std::collections::HashMap<String, serde_json::Value>,
34}
35
36impl Default for CompileOptions {
37 fn default() -> Self {
38 Self {
39 disable_constant_folding: false,
40 parse_only: false,
41 validate_only: false,
42 typecheck_only: false,
43 constructor_args: std::collections::HashMap::new(),
44 }
45 }
46}
47
48fn apply_constructor_args(program: &mut ir::ANFProgram, args: &std::collections::HashMap<String, serde_json::Value>) {
50 if args.is_empty() {
51 return;
52 }
53 for prop in &mut program.properties {
54 if let Some(val) = args.get(&prop.name) {
55 prop.initial_value = Some(val.clone());
56 }
57 }
58}
59
60pub struct CompileResult {
70 pub contract: Option<frontend::ast::ContractNode>,
72 pub anf: Option<ir::ANFProgram>,
74 pub diagnostics: Vec<frontend::diagnostic::Diagnostic>,
76 pub success: bool,
78 pub artifact: Option<RunarArtifact>,
80 pub script_hex: Option<String>,
82 pub script_asm: Option<String>,
84}
85
86impl CompileResult {
87 fn new() -> Self {
88 Self {
89 contract: None,
90 anf: None,
91 diagnostics: Vec::new(),
92 success: false,
93 artifact: None,
94 script_hex: None,
95 script_asm: None,
96 }
97 }
98
99 fn has_errors(&self) -> bool {
100 self.diagnostics.iter().any(|d| d.severity == frontend::diagnostic::Severity::Error)
101 }
102}
103
104pub fn compile_from_ir(path: &Path) -> Result<RunarArtifact, String> {
106 compile_from_ir_with_options(path, &CompileOptions::default())
107}
108
109pub fn compile_from_ir_with_options(path: &Path, opts: &CompileOptions) -> Result<RunarArtifact, String> {
111 let program = load_ir(path)?;
112 compile_from_program_with_options(&program, opts)
113}
114
115pub fn compile_from_ir_str(json_str: &str) -> Result<RunarArtifact, String> {
117 compile_from_ir_str_with_options(json_str, &CompileOptions::default())
118}
119
120pub fn compile_from_ir_str_with_options(json_str: &str, opts: &CompileOptions) -> Result<RunarArtifact, String> {
122 let program = load_ir_from_str(json_str)?;
123 compile_from_program_with_options(&program, opts)
124}
125
126pub fn compile_from_source(path: &Path) -> Result<RunarArtifact, String> {
128 compile_from_source_with_options(path, &CompileOptions::default())
129}
130
131pub fn compile_from_source_with_options(path: &Path, opts: &CompileOptions) -> Result<RunarArtifact, String> {
133 let source = std::fs::read_to_string(path)
134 .map_err(|e| format!("reading source file: {}", e))?;
135 let file_name = path
136 .file_name()
137 .map(|n| n.to_string_lossy().to_string())
138 .unwrap_or_else(|| "contract.ts".to_string());
139 compile_from_source_str_with_options(&source, Some(&file_name), opts)
140}
141
142pub fn compile_from_source_str(
144 source: &str,
145 file_name: Option<&str>,
146) -> Result<RunarArtifact, String> {
147 compile_from_source_str_with_options(source, file_name, &CompileOptions::default())
148}
149
150pub fn compile_from_source_str_with_options(
152 source: &str,
153 file_name: Option<&str>,
154 opts: &CompileOptions,
155) -> Result<RunarArtifact, String> {
156 let parse_result = frontend::parser::parse_source(source, file_name);
158 if !parse_result.errors.is_empty() {
159 let error_msgs: Vec<String> = parse_result.errors.iter().map(|e| e.to_string()).collect();
160 return Err(format!("Parse errors:\n {}", error_msgs.join("\n ")));
161 }
162
163 let contract = parse_result
164 .contract
165 .ok_or_else(|| "No contract found in source file".to_string())?;
166
167 let validation = frontend::validator::validate(&contract);
169 if !validation.errors.is_empty() {
170 return Err(format!(
171 "Validation errors:\n {}",
172 validation.error_strings().join("\n ")
173 ));
174 }
175 for w in &validation.warnings {
176 eprintln!("Validation warning: {}", w);
177 }
178
179 let tc_result = frontend::typecheck::typecheck(&contract);
181 if !tc_result.errors.is_empty() {
182 return Err(format!(
183 "Type-check errors:\n {}",
184 tc_result.error_strings().join("\n ")
185 ));
186 }
187
188 let mut anf_program = frontend::anf_lower::lower_to_anf(&contract);
190
191 apply_constructor_args(&mut anf_program, &opts.constructor_args);
193
194 if !opts.disable_constant_folding {
196 anf_program = frontend::constant_fold::fold_constants(&anf_program);
197 }
198
199 let anf_program = frontend::anf_optimize::optimize_ec(anf_program);
201
202 let backend_opts = CompileOptions { disable_constant_folding: true, ..Default::default() };
205 compile_from_program_with_options(&anf_program, &backend_opts)
206}
207
208pub fn compile_source_to_ir(path: &Path) -> Result<ir::ANFProgram, String> {
210 compile_source_to_ir_with_options(path, &CompileOptions::default())
211}
212
213pub fn compile_source_to_ir_with_options(path: &Path, opts: &CompileOptions) -> Result<ir::ANFProgram, String> {
215 let source = std::fs::read_to_string(path)
216 .map_err(|e| format!("reading source file: {}", e))?;
217 let file_name = path
218 .file_name()
219 .map(|n| n.to_string_lossy().to_string())
220 .unwrap_or_else(|| "contract.ts".to_string());
221 compile_source_str_to_ir_with_options(&source, Some(&file_name), opts)
222}
223
224pub fn compile_source_str_to_ir(
226 source: &str,
227 file_name: Option<&str>,
228) -> Result<ir::ANFProgram, String> {
229 compile_source_str_to_ir_with_options(source, file_name, &CompileOptions::default())
230}
231
232pub fn compile_source_str_to_ir_with_options(
234 source: &str,
235 file_name: Option<&str>,
236 opts: &CompileOptions,
237) -> Result<ir::ANFProgram, String> {
238 let parse_result = frontend::parser::parse_source(source, file_name);
239 if !parse_result.errors.is_empty() {
240 let error_msgs: Vec<String> = parse_result.errors.iter().map(|e| e.to_string()).collect();
241 return Err(format!("Parse errors:\n {}", error_msgs.join("\n ")));
242 }
243
244 let contract = parse_result
245 .contract
246 .ok_or_else(|| "No contract found in source file".to_string())?;
247
248 let validation = frontend::validator::validate(&contract);
249 if !validation.errors.is_empty() {
250 return Err(format!(
251 "Validation errors:\n {}",
252 validation.error_strings().join("\n ")
253 ));
254 }
255
256 let tc_result = frontend::typecheck::typecheck(&contract);
257 if !tc_result.errors.is_empty() {
258 return Err(format!(
259 "Type-check errors:\n {}",
260 tc_result.error_strings().join("\n ")
261 ));
262 }
263
264 let mut anf_program = frontend::anf_lower::lower_to_anf(&contract);
265
266 apply_constructor_args(&mut anf_program, &opts.constructor_args);
268
269 if !opts.disable_constant_folding {
271 anf_program = frontend::constant_fold::fold_constants(&anf_program);
272 }
273
274 Ok(frontend::anf_optimize::optimize_ec(anf_program))
275}
276
277pub fn frontend_validate(source: &str, file_name: Option<&str>) -> (Vec<String>, Vec<String>) {
280 let parse_result = frontend::parser::parse_source(source, file_name);
281 if !parse_result.errors.is_empty() {
282 return (parse_result.error_strings(), vec![]);
283 }
284 let contract = match parse_result.contract {
285 Some(c) => c,
286 None => return (vec!["No contract found".to_string()], vec![]),
287 };
288 let result = frontend::validator::validate(&contract);
289 (result.error_strings(), result.warning_strings())
290}
291
292pub fn compile_from_program(program: &ir::ANFProgram) -> Result<RunarArtifact, String> {
294 compile_from_program_with_options(program, &CompileOptions::default())
295}
296
297pub fn compile_from_program_with_options(program: &ir::ANFProgram, opts: &CompileOptions) -> Result<RunarArtifact, String> {
299 let mut program = program.clone();
301 if !opts.disable_constant_folding {
302 program = frontend::constant_fold::fold_constants(&program);
303 }
304
305 let optimized = frontend::anf_optimize::optimize_ec(program);
307
308 let mut stack_methods = lower_to_stack(&optimized)?;
310
311 for method in &mut stack_methods {
315 let new_ops = optimize_stack_ops(&method.ops);
316 method.source_locs = vec![None; new_ops.len()];
319 method.ops = new_ops;
320 }
321
322 let emit_result = emit(&stack_methods)?;
324
325 let artifact = assemble_artifact(
326 &optimized,
327 &emit_result.script_hex,
328 &emit_result.script_asm,
329 emit_result.constructor_slots,
330 emit_result.code_separator_index,
331 emit_result.code_separator_indices,
332 true, emit_result.source_map,
334 );
335 Ok(artifact)
336}
337
338pub fn compile_from_source_str_with_result(
348 source: &str,
349 file_name: Option<&str>,
350 opts: &CompileOptions,
351) -> CompileResult {
352 use frontend::diagnostic::Diagnostic;
353
354 let mut result = CompileResult::new();
355
356 let parse_result = frontend::parser::parse_source(source, file_name);
358 result.diagnostics.extend(parse_result.errors);
359 result.contract = parse_result.contract;
360
361 if result.has_errors() || result.contract.is_none() {
362 if result.contract.is_none() && !result.has_errors() {
363 result.diagnostics.push(Diagnostic::error(
364 "No contract found in source file",
365 None,
366 ));
367 }
368 return result;
369 }
370
371 if opts.parse_only {
372 result.success = !result.has_errors();
373 return result;
374 }
375
376 let contract = result.contract.as_ref().unwrap();
378 let validation = frontend::validator::validate(contract);
379 result.diagnostics.extend(validation.errors);
380 result.diagnostics.extend(validation.warnings);
381
382 if result.has_errors() {
383 return result;
384 }
385
386 if opts.validate_only {
387 result.success = !result.has_errors();
388 return result;
389 }
390
391 let tc_result = frontend::typecheck::typecheck(contract);
393 result.diagnostics.extend(tc_result.errors);
394
395 if result.has_errors() {
396 return result;
397 }
398
399 if opts.typecheck_only {
400 result.success = !result.has_errors();
401 return result;
402 }
403
404 let mut anf_program = frontend::anf_lower::lower_to_anf(contract);
406
407 apply_constructor_args(&mut anf_program, &opts.constructor_args);
409
410 if !opts.disable_constant_folding {
412 anf_program = frontend::constant_fold::fold_constants(&anf_program);
413 }
414
415 anf_program = frontend::anf_optimize::optimize_ec(anf_program);
417 result.anf = Some(anf_program.clone());
418
419 let stack_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
421 lower_to_stack(&anf_program)
422 }));
423
424 let mut stack_methods = match stack_result {
425 Ok(Ok(methods)) => methods,
426 Ok(Err(e)) => {
427 result.diagnostics.push(Diagnostic::error(
428 format!("stack lowering: {}", e),
429 None,
430 ));
431 return result;
432 }
433 Err(panic_val) => {
434 let msg = if let Some(s) = panic_val.downcast_ref::<&str>() {
435 format!("stack lowering panic: {}", s)
436 } else if let Some(s) = panic_val.downcast_ref::<String>() {
437 format!("stack lowering panic: {}", s)
438 } else {
439 "stack lowering panic: unknown error".to_string()
440 };
441 result.diagnostics.push(Diagnostic::error(msg, None));
442 return result;
443 }
444 };
445
446 for method in &mut stack_methods {
448 let new_ops = optimize_stack_ops(&method.ops);
449 method.source_locs = vec![None; new_ops.len()];
450 method.ops = new_ops;
451 }
452
453 let emit_result_outer = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
455 emit(&stack_methods)
456 }));
457
458 match emit_result_outer {
459 Ok(Ok(emit_result)) => {
460 let anf_ref = result.anf.as_ref().unwrap();
461 let artifact = assemble_artifact(
462 anf_ref,
463 &emit_result.script_hex,
464 &emit_result.script_asm,
465 emit_result.constructor_slots,
466 emit_result.code_separator_index,
467 emit_result.code_separator_indices,
468 true,
469 emit_result.source_map,
470 );
471 result.script_hex = Some(emit_result.script_hex);
472 result.script_asm = Some(emit_result.script_asm);
473 result.artifact = Some(artifact);
474 }
475 Ok(Err(e)) => {
476 result.diagnostics.push(Diagnostic::error(
477 format!("emit: {}", e),
478 None,
479 ));
480 }
481 Err(panic_val) => {
482 let msg = if let Some(s) = panic_val.downcast_ref::<&str>() {
483 format!("emit panic: {}", s)
484 } else if let Some(s) = panic_val.downcast_ref::<String>() {
485 format!("emit panic: {}", s)
486 } else {
487 "emit panic: unknown error".to_string()
488 };
489 result.diagnostics.push(Diagnostic::error(msg, None));
490 }
491 }
492
493 result.success = !result.has_errors();
494 result
495}
496
497pub fn compile_from_source_with_result(
499 path: &Path,
500 opts: &CompileOptions,
501) -> CompileResult {
502 use frontend::diagnostic::Diagnostic;
503
504 let source = match std::fs::read_to_string(path) {
505 Ok(s) => s,
506 Err(e) => {
507 let mut result = CompileResult::new();
508 result.diagnostics.push(Diagnostic::error(
509 format!("reading source file: {}", e),
510 None,
511 ));
512 return result;
513 }
514 };
515 let file_name = path
516 .file_name()
517 .map(|n| n.to_string_lossy().to_string())
518 .unwrap_or_else(|| "contract.ts".to_string());
519 compile_from_source_str_with_result(&source, Some(&file_name), opts)
520}