//! Evaluate Bel files on the bootstrap host and print the final character tree. //! This is generic batch/output support, with no compiler or Wasm semantics. use std::{ env, fs, io::{self, Write}, process::ExitCode, }; use bel_rust_repl::{Interpreter, Value, eval_bootstrap, read}; fn evaluate() -> Result> { let mut paths: Vec<_> = env::args_os().skip(1).collect(); let call = if paths.first().is_some_and(|path| path == "--call") { if paths.len() < 4 { return Err("usage: bel-eval-text --call FUNCTION DATA FILE [FILE ...]".into()); } let arguments: Vec<_> = paths.drain(..3).collect(); Some(( arguments[1] .clone() .into_string() .map_err(|_| "invalid function name")?, arguments[2].clone(), )) } else { None }; if paths.is_empty() { return Err("usage: bel-eval-text FILE [FILE ...]".into()); } let mut interpreter = Interpreter::default(); eval_bootstrap(&mut interpreter)?; let mut result = Value::Nil; for path in paths { let source = fs::read_to_string(&path)?; let values = interpreter .eval_source(&source) .map_err(|error| format!("{}: {error}", path.to_string_lossy()))?; result = values .into_iter() .last() .ok_or("expected a final expression")?; } if let Some((name, path)) = call { // Parse the complete data file without evaluating or interpolating it. // The called Bel function receives the list of all source forms. let forms = read(&fs::read_to_string(&path)?)?; let quoted = Value::list([Value::symbol("quote"), Value::list(forms)]); result = interpreter .eval_forms(vec![Value::list([Value::symbol(&name), quoted])])? .pop() .ok_or("expected a call result")?; } // A tree of characters avoids forcing Bel to concatenate long lists under // the bootstrap evaluator's depth limit. Traverse iteratively and bound // total work as well as output, including cyclic or highly shared pairs. let mut text = String::new(); let mut pending = vec![result]; let mut steps = 0; while let Some(value) = pending.pop() { steps += 1; if steps > 256 * 1024 { return Err("text traversal limit reached".into()); } match value { Value::Nil => {} Value::Char(character) => { if text.len() + character.len_utf8() > 64 * 1024 { return Err("text output limit reached (64 KiB)".into()); } text.push(character); } Value::Pair(pair) => { let (head, tail) = pair.borrow().clone(); pending.push(tail); pending.push(head); } Value::Symbol(_) => return Err("final result must be a character tree".into()), } } Ok(text) } fn main() -> ExitCode { match evaluate() { Ok(text) => match io::stdout().lock().write_all(text.as_bytes()) { Ok(()) => ExitCode::SUCCESS, Err(error) => { eprintln!("error: {error}"); ExitCode::FAILURE } }, Err(error) => { eprintln!("error: {error}"); ExitCode::FAILURE } } }