use core::cell::RefCell;
use std::borrow::Borrow;
use std::collections::{BTreeMap, HashMap};
use std::fs;
use std::io;
use std::io::Write;
use std::mem::swap;
use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::SystemTime;
use core::cmp::max;
use hashlink::LinkedHashMap;
use yaml_rust2::{Yaml, YamlEmitter};
use clvm_rs::allocator::{Allocator, NodePtr};
use clvm_rs::error::EvalErr;
use clvm_rs::run_program::PreEval;
use crate::classic::clvm::__type_compatibility__::{
t, Bytes, BytesFromType, Stream, Tuple, UnvalidatedBytesFromType,
};
use crate::classic::clvm::keyword_from_atom;
use crate::classic::clvm::serialize::{sexp_from_stream, sexp_to_stream, SimpleCreateCLVMObject};
use crate::classic::clvm::sexp::{enlist, proper_list, sexp_as_bin};
use crate::classic::clvm::OPERATORS_LATEST_VERSION;
use crate::classic::clvm_tools::binutils::{assemble_from_ir, disassemble, disassemble_with_kw};
use crate::classic::clvm_tools::clvmc::write_sym_output;
use crate::classic::clvm_tools::comp_input::{get_disassembly_ver, RunAndCompileInputData};
use crate::classic::clvm_tools::debug::check_unused;
use crate::classic::clvm_tools::debug::{
program_hash_from_program_env_cons, start_log_after, trace_pre_eval, trace_to_table,
trace_to_text,
};
use crate::classic::clvm_tools::ir::reader::read_ir;
use crate::classic::clvm_tools::sha256tree::sha256tree;
use crate::classic::clvm_tools::stages;
use crate::classic::clvm_tools::stages::stage_0::{
DefaultProgramRunner, RunProgramOption, TRunProgram,
};
use crate::classic::clvm_tools::stages::stage_2::operators::run_program_for_search_paths;
use crate::classic::platform::PathJoin;
use crate::classic::platform::argparse::{
Argument, ArgumentParser, ArgumentValue, ArgumentValueConv, IntConversion, NArgsSpec,
TArgOptionAction, TArgumentParserProps,
};
use crate::compiler::cldb::{
hex_to_modern_sexp, improve_presentation, CldbNoOverride, CldbRun, CldbRunEnv, FAVOR_HEX,
};
use crate::compiler::cldb_hierarchy::{HierarchialRunner, HierarchialStepResult, RunPurpose};
use crate::compiler::clvm::start_step;
use crate::compiler::compiler::DefaultCompilerOpts;
use crate::compiler::comptypes::{CompileErr, CompilerOpts};
use crate::compiler::frontend::frontend;
use crate::compiler::preprocessor::gather_dependencies;
use crate::compiler::prims;
use crate::compiler::runtypes::RunFailure;
use crate::compiler::sexp;
use crate::compiler::sexp::{decode_string, parse_sexp};
use crate::compiler::srcloc::Srcloc;
use crate::util::collapse;
use crate::util::version;
struct ConversionDesc {
desc: &'static str,
conv: Box<dyn TConversion>,
}
fn get_tool_description(tool_name: &str) -> Option<ConversionDesc> {
if tool_name == "opc" {
Some(ConversionDesc {
desc: "Compile a clvm script.",
conv: Box::new(OpcConversion {}),
})
} else if tool_name == "opd" {
Some(ConversionDesc {
desc: "Disassemble a compiled clvm script from hex.",
conv: Box::new(OpdConversion { op_version: None }),
})
} else {
None
}
}
pub struct PathOrCodeConv {}
impl ArgumentValueConv for PathOrCodeConv {
fn convert(&self, arg: &str) -> Result<ArgumentValue, String> {
match fs::read_to_string(arg) {
Ok(s) => Ok(ArgumentValue::ArgString(Some(arg.to_string()), s)),
Err(_) => Ok(ArgumentValue::ArgString(None, arg.to_string())),
}
}
}
pub trait TConversion {
fn apply_args(&mut self, parsed_args: &HashMap<String, ArgumentValue>);
fn invoke(
&self,
allocator: &mut Allocator,
text: &str,
) -> Result<Tuple<NodePtr, String>, String>;
}
pub fn call_tool_stdout(allocator: &mut Allocator, tool_name: &str, input_args: &[String]) {
let mut stdout_stream = Stream::new(None);
match call_tool(&mut stdout_stream, allocator, tool_name, input_args) {
Ok(_) => {
let s = stdout_stream.get_value();
if s.length() > 0 {
println!("{}", s.decode());
}
}
Err(e) => {
eprintln!("{e}");
}
}
}
pub fn call_tool(
stream: &mut Stream,
allocator: &mut Allocator,
tool_name: &str,
input_args: &[String],
) -> Result<(), String> {
let mut task =
get_tool_description(tool_name).ok_or_else(|| format!("unknown tool {tool_name}"))?;
let props = TArgumentParserProps {
description: task.desc.to_string(),
prog: tool_name.to_string(),
};
let mut parser = ArgumentParser::new(Some(props));
parser.add_argument(
vec!["--version".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Show version".to_string()),
);
parser.add_argument(
vec!["-H".to_string(), "--script-hash".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Show only sha256 tree hash of program".to_string()),
);
parser.add_argument(
vec!["--operators-version".to_string()],
Argument::new()
.set_type(Rc::new(OperatorsVersion {}))
.set_default(ArgumentValue::ArgInt(OPERATORS_LATEST_VERSION as i64)),
);
parser.add_argument(
vec!["path_or_code".to_string()],
Argument::new()
.set_n_args(NArgsSpec::KleeneStar)
.set_type(Rc::new(PathOrCodeConv {}))
.set_help("path to clvm script, or literal script".to_string()),
);
let rest_args: Vec<String> = input_args.iter().skip(1).cloned().collect();
let args_res = parser.parse_args(&rest_args);
let args: HashMap<String, ArgumentValue> = match args_res {
Ok(a) => a,
Err(e) => {
println!("{e}");
return Ok(());
}
};
task.conv.apply_args(&args);
if args.contains_key("version") {
let version = version();
println!("{version}");
return Ok(());
}
let args_path_or_code_val = match args.get("path_or_code") {
None => ArgumentValue::ArgArray(vec![]),
Some(v) => v.clone(),
};
let args_path_or_code = match args_path_or_code_val {
ArgumentValue::ArgArray(v) => v,
_ => vec![],
};
for program in args_path_or_code {
match program {
ArgumentValue::ArgString(_, s) => {
if s == "-" {
return Err("Read stdin is not supported at this time".to_string());
}
let conv_result = task.conv.invoke(allocator, &s)?;
let sexp = *conv_result.first();
let text = conv_result.rest();
if args.contains_key("script_hash") {
let data: Vec<u8> = sha256tree(allocator, sexp).hex().bytes().collect();
stream.write(Bytes::new(Some(BytesFromType::Raw(data))));
} else if !text.is_empty() {
let data: Vec<u8> = text.to_string().bytes().collect();
stream.write(Bytes::new(Some(BytesFromType::Raw(data))));
}
}
_ => {
return Err("inappropriate argument conversion".to_string());
}
}
}
Ok(())
}
pub struct OpcConversion {}
impl TConversion for OpcConversion {
fn apply_args(&mut self, _args: &HashMap<String, ArgumentValue>) {}
fn invoke(
&self,
allocator: &mut Allocator,
hex_text: &str,
) -> Result<Tuple<NodePtr, String>, String> {
read_ir(hex_text)
.map_err(|e| e.to_string())
.and_then(|ir_sexp| {
assemble_from_ir(allocator, Rc::new(ir_sexp)).map_err(|e| match e {
EvalErr::InternalError(_, e) => e.to_string(),
_ => e.to_string(),
})
})
.map(|sexp| t(sexp, sexp_as_bin(allocator, sexp).hex()))
.map(Ok) .unwrap_or_else(|err| Ok(t(NodePtr::NIL, err))) }
}
#[derive(Debug)]
pub struct OpdConversion {
pub op_version: Option<usize>,
}
impl TConversion for OpdConversion {
fn apply_args(&mut self, args: &HashMap<String, ArgumentValue>) {
if let Some(ArgumentValue::ArgInt(i)) = args.get("operators_version") {
self.op_version = Some(*i as usize);
}
}
fn invoke(
&self,
allocator: &mut Allocator,
hex_text: &str,
) -> Result<Tuple<NodePtr, String>, String> {
let mut stream = Stream::new(Some(
match Bytes::new_validated(Some(UnvalidatedBytesFromType::Hex(hex_text.to_string()))) {
Ok(x) => x,
Err(e) => return Err(e.to_string()),
},
));
sexp_from_stream(allocator, &mut stream, Box::new(SimpleCreateCLVMObject {}))
.map_err(|e| match e {
EvalErr::InternalError(_, e) => e.to_string(),
_ => e.to_string(),
})
.map(|sexp| {
let disassembled = disassemble(allocator, sexp.1, self.op_version);
t(sexp.1, disassembled)
})
}
}
pub fn opc(args: &[String]) {
let mut allocator = Allocator::new();
call_tool_stdout(&mut allocator, "opc", args);
}
pub fn opd(args: &[String]) {
let mut allocator = Allocator::new();
call_tool_stdout(&mut allocator, "opd", args);
}
struct StageImport {}
impl ArgumentValueConv for StageImport {
fn convert(&self, arg: &str) -> Result<ArgumentValue, String> {
if arg == "0" {
return Ok(ArgumentValue::ArgInt(0));
} else if arg == "1" {
return Ok(ArgumentValue::ArgInt(1));
} else if arg == "2" {
return Ok(ArgumentValue::ArgInt(2));
}
Err(format!("Unknown stage: {arg}"))
}
}
struct OperatorsVersion {}
impl ArgumentValueConv for OperatorsVersion {
fn convert(&self, arg: &str) -> Result<ArgumentValue, String> {
let ver = arg
.parse::<i64>()
.map_err(|_| format!("expected number 0-{OPERATORS_LATEST_VERSION} but found {arg}"))?;
Ok(ArgumentValue::ArgInt(ver))
}
}
pub fn run(args: &[String]) {
let mut s = Stream::new(None);
launch_tool(&mut s, args, "run", 2);
io::stdout()
.write_all(s.get_value().data())
.expect("stdout");
io::stdout().flush().expect("stdout");
}
pub fn brun(args: &[String]) {
let mut s = Stream::new(None);
launch_tool(&mut s, args, "brun", 0);
if let Err(e) = io::stdout().write_all(s.get_value().data()) {
println!("{e}")
}
io::stdout().flush().expect("stdout");
}
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub enum YamlElement {
String(String),
Array(Vec<YamlElement>),
Subtree(BTreeMap<String, YamlElement>),
}
pub fn to_yaml_element(y: &YamlElement) -> Yaml {
match y {
YamlElement::String(s) => Yaml::String(s.clone()),
YamlElement::Array(a) => {
let array_elts: Vec<Yaml> = a.iter().map(to_yaml_element).collect();
Yaml::Array(array_elts)
}
YamlElement::Subtree(t) => {
let mut h = LinkedHashMap::new();
for (k, v) in t.iter() {
let converted = to_yaml_element(v);
h.insert(Yaml::String(k.clone()), converted);
}
Yaml::Hash(h)
}
}
}
fn to_yaml<T, F>(entries: &[BTreeMap<String, T>], cvt: F) -> Yaml
where
F: Fn(&T) -> YamlElement,
{
let result_array: Vec<Yaml> = entries
.iter()
.map(|tm| {
let mut h = LinkedHashMap::new();
for (k, v) in tm.iter() {
h.insert(Yaml::String(k.clone()), to_yaml_element(&cvt(v)));
}
Yaml::Hash(h)
})
.collect();
Yaml::Array(result_array)
}
fn yamlette_string(to_print: &[BTreeMap<String, YamlElement>]) -> String {
let mut result = String::new();
let mut emitter = YamlEmitter::new(&mut result);
match emitter.dump(&to_yaml(to_print, |s| s.clone())) {
Ok(_) => result,
Err(e) => format!("error producing yaml: {e:?}"),
}
}
pub struct CldbHierarchyArgs {
pub runner: Rc<dyn TRunProgram>,
pub prim_map: Rc<HashMap<Vec<u8>, Rc<sexp::SExp>>>,
pub input_file_name: Option<String>,
pub lines: Rc<Vec<String>>,
pub symbol_table: Rc<HashMap<String, String>>,
pub prog: Rc<sexp::SExp>,
pub args: Rc<sexp::SExp>,
pub flags: u32,
}
pub fn cldb_hierarchy(args: CldbHierarchyArgs) -> Vec<BTreeMap<String, YamlElement>> {
let mut runner = HierarchialRunner::new(
args.runner,
args.prim_map,
args.input_file_name,
args.lines,
args.symbol_table,
args.prog,
args.args,
);
runner.set_flags(args.flags);
let mut output_stack = vec![Vec::new()];
loop {
if runner.is_ended() {
break;
}
match runner.step() {
Ok(HierarchialStepResult::ShapeChange) => {
}
Ok(HierarchialStepResult::Info(Some(info))) => {
let running_frames = runner
.running
.iter()
.map(|f| f.purpose.clone())
.filter(|p| matches!(p, RunPurpose::Main))
.count();
while running_frames >= output_stack.len() {
output_stack.push(Vec::new());
}
let run_idx = runner.running.len() - 1;
let mut function_entry = BTreeMap::new();
function_entry.insert(
"Function-Name".to_string(),
YamlElement::String(runner.running[run_idx].function_name.clone()),
);
let mut arg_values = BTreeMap::new();
for (k, v) in runner.running[run_idx].named_args.iter() {
arg_values.insert(
k.clone(),
YamlElement::String(format!(
"{}",
improve_presentation(v.clone(), args.flags)
)),
);
}
function_entry.insert(
"Function-Args".to_string(),
YamlElement::Subtree(arg_values),
);
let mut info_values = BTreeMap::new();
for (k, v) in info.iter() {
info_values.insert(k.clone(), YamlElement::String(v.clone()));
}
function_entry.insert("Output".to_string(), YamlElement::Subtree(info_values));
let os_last = output_stack.len() - 1;
output_stack[os_last].push(function_entry);
while running_frames < output_stack.len() {
let take_stack = output_stack
.pop()
.unwrap()
.iter()
.map(|e| YamlElement::Subtree(e.clone()))
.collect();
let mut inner_run_item: BTreeMap<String, YamlElement> = BTreeMap::new();
inner_run_item.insert("Compute".to_string(), YamlElement::Array(take_stack));
let os_last = output_stack.len() - 1;
output_stack[os_last].push(inner_run_item);
}
}
Ok(HierarchialStepResult::Info(None)) => {
}
Ok(HierarchialStepResult::Done(Some(info))) => {
let mut done_output = BTreeMap::new();
for (k, v) in info.iter() {
done_output.insert(k.clone(), YamlElement::String(v.clone()));
}
let os_last = output_stack.len() - 1;
output_stack[os_last].push(done_output);
}
Ok(HierarchialStepResult::Done(None)) => {
}
Err(RunFailure::RunErr(l, e)) => {
println!("Runtime Error: {l}: {e}");
break;
}
Err(RunFailure::RunExn(l, e)) => {
println!("Raised exception: {l}: {e}");
break;
}
}
}
let mut result = Vec::new();
swap(&mut result, &mut output_stack[0]);
result
}
pub fn cldb(args: &[String]) {
let mut allocator = Allocator::new();
let mut output = Vec::new();
let tool_name = "cldb".to_string();
let props = TArgumentParserProps {
description: "Execute a clvm script.".to_string(),
prog: format!("clvm_tools {tool_name}"),
};
let mut parser = ArgumentParser::new(Some(props));
parser.add_argument(
vec!["-i".to_string(), "--include".to_string()],
Argument::new()
.set_type(Rc::new(PathJoin {}))
.set_help("add a search path for included files".to_string())
.set_action(TArgOptionAction::Append)
.set_default(ArgumentValue::ArgArray(vec![])),
);
parser.add_argument(
vec!["-O".to_string(), "--optimize".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("run optimizer".to_string()),
);
parser.add_argument(
vec!["-x".to_string(), "--hex".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("parse input program and arguments from hex".to_string()),
);
parser.add_argument(
vec!["-y".to_string(), "--symbol-table".to_string()],
Argument::new()
.set_type(Rc::new(PathOrCodeConv {}))
.set_help("path to symbol file".to_string()),
);
parser.add_argument(
vec!["-X".to_string(), "--favor-hex".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("favor hex output to integer".to_string()),
);
parser.add_argument(
vec!["-p".to_string(), "--only-print".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("only show printing from the program".to_string()),
);
parser.add_argument(
vec!["-t".to_string(), "--tree".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("new style hierarchial view of function calls and args".to_string()),
);
parser.add_argument(
vec!["path_or_code".to_string()],
Argument::new()
.set_type(Rc::new(PathOrCodeConv {}))
.set_help("filepath to clvm script, or a literal script".to_string()),
);
parser.add_argument(
vec!["env".to_string()],
Argument::new()
.set_n_args(NArgsSpec::Optional)
.set_type(Rc::new(PathOrCodeConv {}))
.set_help("clvm script environment, as clvm src, or hex".to_string()),
);
let arg_vec = args[1..].to_vec();
let prog_srcloc = Srcloc::start("*program*");
let args_srcloc = Srcloc::start("*args*");
let errorize =
|output: &mut Vec<BTreeMap<String, YamlElement>>, location: Option<Srcloc>, c: &str| {
let mut parse_error = BTreeMap::new();
if let Some(l) = location {
parse_error.insert(
"Error-Location".to_string(),
YamlElement::String(l.to_string()),
);
};
parse_error.insert("Error".to_string(), YamlElement::String(c.to_string()));
output.push(parse_error.clone());
println!("{}", yamlette_string(output));
};
let parsed_args: HashMap<String, ArgumentValue> = match parser.parse_args(&arg_vec) {
Err(e) => {
println!("FAIL: {e}");
return;
}
Ok(pa) => pa,
};
let symbol_table = parsed_args
.get("symbol_table")
.and_then(|jstring| match jstring {
ArgumentValue::ArgString(_, s) => {
let decoded_symbol_table: Option<HashMap<String, String>> =
serde_json::from_str(s).ok();
decoded_symbol_table
}
_ => None,
});
let parsed = match RunAndCompileInputData::new(&mut allocator, &parsed_args) {
Ok(r) => r,
Err(e) => {
println!("FAIL: {e}");
return;
}
};
let only_print = parsed_args.get("only_print").map(|_| true).unwrap_or(false);
let favor_hex = parsed_args.get("favor_hex").map(|_| true).unwrap_or(false);
let runner = Rc::new(DefaultProgramRunner::new());
let mut use_symbol_table = symbol_table.unwrap_or_default();
let res = match parsed_args.get("hex") {
Some(ArgumentValue::ArgBool(true)) => hex_to_modern_sexp(
&mut allocator,
&use_symbol_table,
prog_srcloc.clone(),
&parsed.program.content,
)
.map_err(|_| CompileErr(prog_srcloc, "Failed to parse hex".to_string())),
_ => parsed.compile_modern(&mut allocator, &mut use_symbol_table),
};
let program = match res {
Ok(r) => r,
Err(c) => {
errorize(&mut output, Some(c.0.clone()), &c.1);
return;
}
};
let env_loc = Srcloc::start("*args*");
let env = match parsed_args.get("hex") {
Some(ArgumentValue::ArgBool(true)) => {
match hex_to_modern_sexp(
&mut allocator,
&HashMap::new(),
args_srcloc,
&parsed.args.content,
) {
Ok(r) => r,
Err(p) => {
let mut parse_error = BTreeMap::new();
parse_error.insert("Error".to_string(), YamlElement::String(p.to_string()));
output.push(parse_error.clone());
println!("{}", yamlette_string(&output));
return;
}
}
}
_ => match parse_sexp(env_loc.clone(), parsed.args.content.bytes()) {
Ok(r) => {
if !r.is_empty() {
r[0].clone()
} else {
Rc::new(sexp::SExp::Nil(env_loc))
}
}
Err(c) => {
let mut parse_error = BTreeMap::new();
parse_error.insert(
"Error-Location".to_string(),
YamlElement::String(c.0.to_string()),
);
parse_error.insert("Error".to_string(), YamlElement::String(c.1));
output.push(parse_error.clone());
println!("{}", yamlette_string(&output));
return;
}
},
};
let mut prim_map = HashMap::new();
for p in prims::prims().iter() {
prim_map.insert(p.0.clone(), Rc::new(p.1.clone()));
}
let program_lines: Rc<Vec<String>> = Rc::new(
parsed
.program
.content
.lines()
.map(|x| x.to_string())
.collect(),
);
let cldbenv = CldbRunEnv::new(
parsed.program.path.clone(),
program_lines.clone(),
Box::new(CldbNoOverride::new_symbols(use_symbol_table.clone())),
);
if parsed_args.contains_key("tree") {
let result = cldb_hierarchy(CldbHierarchyArgs {
runner,
prim_map: Rc::new(prim_map),
input_file_name: parsed.program.path.clone(),
lines: program_lines,
symbol_table: Rc::new(use_symbol_table),
prog: program,
args: env,
flags: if favor_hex { FAVOR_HEX } else { 0 },
});
let string_result = yamlette_string(&result);
println!("{string_result}");
return;
}
let step = start_step(program, env);
let mut cldbrun = CldbRun::new(runner, Rc::new(prim_map), Box::new(cldbenv), step);
if favor_hex {
cldbrun.set_flags(FAVOR_HEX);
}
let print_tree = |output: &mut Vec<_>, result: &BTreeMap<String, String>| {
let mut cvt_subtree = BTreeMap::new();
for (k, v) in result.iter() {
cvt_subtree.insert(k.clone(), YamlElement::String(v.clone()));
}
output.push(cvt_subtree);
};
cldbrun.set_print_only(only_print);
loop {
if cldbrun.is_ended() {
println!("{}", yamlette_string(&output));
return;
}
if let Some(result) = cldbrun.step(&mut allocator) {
if only_print {
if let Some(p) = result.get("Print") {
let mut only_print = BTreeMap::new();
only_print.insert("Print".to_string(), YamlElement::String(p.clone()));
output.push(only_print);
} else {
let is_final = result.contains_key("Final");
let is_throw = result.contains_key("Throw");
let is_failure = result.contains_key("Failure");
if is_final || is_throw || is_failure {
print_tree(&mut output, &result);
}
}
} else {
print_tree(&mut output, &result);
}
}
}
}
struct RunLog<T> {
log_entries: RefCell<Vec<T>>,
}
impl<T> RunLog<T> {
fn push(&self, new_log: T) {
self.log_entries.replace_with(|log| {
let mut empty_log = Vec::new();
swap(&mut empty_log, &mut *log);
empty_log.push(new_log);
empty_log
});
}
fn finish(&self) -> Vec<T> {
let mut empty_log = Vec::new();
self.log_entries.replace_with(|log| {
swap(&mut empty_log, &mut *log);
Vec::new()
});
empty_log
}
}
fn calculate_cost_offset(
allocator: &mut Allocator,
run_program: Rc<dyn TRunProgram>,
run_script: NodePtr,
) -> i64 {
let almost_empty_list = enlist(allocator, &[NodePtr::NIL]).unwrap();
let cost = run_program
.run_program(allocator, run_script, almost_empty_list, None)
.map(|x| x.0)
.unwrap_or_else(|_| 0);
53 - cost as i64
}
fn fix_log(
allocator: &mut Allocator,
log_result: &mut [NodePtr],
log_updates: &[(NodePtr, Option<NodePtr>)],
) {
let mut update_map: HashMap<NodePtr, Option<NodePtr>> = HashMap::new();
for update in log_updates {
update_map.insert(update.0, update.1);
}
for (i, entry) in log_result.to_vec().iter().enumerate() {
update_map.get(entry).and_then(|v| *v).map(|v| {
proper_list(allocator, *entry, true).map(|list| {
let mut updated = list.to_vec();
updated.push(v);
log_result[i] = enlist(allocator, &updated).unwrap();
})
});
}
}
fn perform_preprocessing(
stdout: &mut Stream,
opts: Rc<dyn CompilerOpts>,
input_file: &str,
program_text: &str,
) -> Result<(), CompileErr> {
let srcloc = Srcloc::start(input_file);
let parsed = parse_sexp(srcloc.clone(), program_text.bytes())?;
let stepping_form_text = match opts.dialect().stepping {
Some(21) => Some("(include *strict-cl-21*)".to_string()),
Some(n) => Some(format!("(include *standard-cl-{n}*)")),
_ => None,
};
let frontend = frontend(opts, &parsed)?;
let fe_sexp = frontend.to_sexp();
let with_stepping = if let Some(s) = stepping_form_text {
let parsed_stepping_form = parse_sexp(srcloc.clone(), s.bytes())?;
if let sexp::SExp::Cons(_, a, rest) = fe_sexp.borrow() {
Rc::new(sexp::SExp::Cons(
srcloc.clone(),
a.clone(),
Rc::new(sexp::SExp::Cons(
srcloc.clone(),
parsed_stepping_form[0].clone(),
rest.clone(),
)),
))
} else {
fe_sexp
}
} else {
fe_sexp
};
let whole_mod = sexp::SExp::Cons(
srcloc.clone(),
Rc::new(sexp::SExp::Atom(srcloc, b"mod".to_vec())),
with_stepping,
);
stdout.write_str(&format!("{whole_mod}"));
Ok(())
}
pub fn launch_tool(stdout: &mut Stream, args: &[String], tool_name: &str, default_stage: u32) {
let mut allocator = Allocator::new();
let props = TArgumentParserProps {
description: "Execute a clvm script.".to_string(),
prog: format!("clvm_tools {tool_name}"),
};
let mut parser = ArgumentParser::new(Some(props));
parser.add_argument(
vec!["--version".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Show version".to_string()),
);
parser.add_argument(
vec!["-s".to_string(), "--stage".to_string()],
Argument::new()
.set_type(Rc::new(StageImport {}))
.set_help("stage number to include".to_string())
.set_default(ArgumentValue::ArgInt(default_stage as i64)),
);
parser.add_argument(
vec!["--strict".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Unknown opcodes are always fatal errors in strict mode".to_string()),
);
parser.add_argument(
vec!["-x".to_string(), "--hex".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Read program and environment as hexadecimal bytecode".to_string()),
);
parser.add_argument(
vec!["-v".to_string(), "--verbose".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Display resolve of all reductions, for debugging".to_string()),
);
parser.add_argument(
vec!["-t".to_string(), "--table".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Print diagnostic table of reductions, for debugging".to_string()),
);
parser.add_argument(
vec!["-c".to_string(), "--cost".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Show cost".to_string()),
);
parser.add_argument(
vec!["--time".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Print execution time".to_string()),
);
parser.add_argument(
vec!["-d".to_string(), "--dump".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("dump hex version of final output".to_string()),
);
parser.add_argument(
vec!["--quiet".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Suppress printing the program result".to_string()),
);
parser.add_argument(
vec!["-y".to_string(), "--symbol-table".to_string()],
Argument::new()
.set_type(Rc::new(PathJoin {}))
.set_help(".SYM file generated by compiler".to_string()),
);
parser.add_argument(
vec!["-n".to_string(), "--no-keywords".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Output result as data, not as a program".to_string()),
);
parser.add_argument(
vec!["-i".to_string(), "--include".to_string()],
Argument::new()
.set_type(Rc::new(PathJoin {}))
.set_help("add a search path for included files".to_string())
.set_action(TArgOptionAction::Append)
.set_default(ArgumentValue::ArgArray(vec![])),
);
parser.add_argument(
vec!["path_or_code".to_string()],
Argument::new()
.set_type(Rc::new(PathOrCodeConv {}))
.set_help("filepath to clvm script, or a literal script".to_string()),
);
parser.add_argument(
vec!["env".to_string()],
Argument::new()
.set_n_args(NArgsSpec::Optional)
.set_type(Rc::new(PathOrCodeConv {}))
.set_help("clvm script environment, as clvm src, or hex".to_string()),
);
parser.add_argument(
vec!["-m".to_string(), "--max-cost".to_string()],
Argument::new()
.set_type(Rc::new(IntConversion::new(Rc::new(|| "help".to_string()))))
.set_default(ArgumentValue::ArgInt(11000000000))
.set_help("Maximum cost".to_string()),
);
parser.add_argument(
vec!["-O".to_string(), "--optimize".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("run optimizer".to_string()),
);
parser.add_argument(
vec!["--only-exn".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Only show frames along the exception path".to_string()),
);
parser.add_argument(
vec!["-M".to_string(), "--dependencies".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Visit dependencies and output a list of used files".to_string()),
);
parser.add_argument(
vec!["-g".to_string(), "--extra-syms".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Produce more diagnostic info in symbols".to_string()),
);
parser.add_argument(
vec!["--symbol-output-file".to_string()],
Argument::new()
.set_type(Rc::new(PathJoin {}))
.set_default(ArgumentValue::ArgString(None, "main.sym".to_string())),
);
parser.add_argument(
vec!["--strict".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("For modern dialects, don't treat unknown names as constants".to_string()),
);
parser.add_argument(
vec!["-E".to_string(), "--preprocess".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help("Perform strict mode preprocessing and show the result".to_string()),
);
parser.add_argument(
vec!["--operators-version".to_string()],
Argument::new()
.set_type(Rc::new(OperatorsVersion {}))
.set_default(ArgumentValue::ArgInt(OPERATORS_LATEST_VERSION as i64)),
);
if tool_name == "run" {
parser.add_argument(
vec!["--check-unused-args".to_string()],
Argument::new()
.set_action(TArgOptionAction::StoreTrue)
.set_help(
"check for unused uncurried parameters (by convention lower case)".to_string(),
),
);
}
let arg_vec = args[1..].to_vec();
let parsed_args: HashMap<String, ArgumentValue> = match parser.parse_args(&arg_vec) {
Err(e) => {
stdout.write_str(&format!("FAIL: {e}\n"));
return;
}
Ok(pa) => pa,
};
if parsed_args.contains_key("version") {
let version = version();
println!("{version}");
return;
}
let parsed = match RunAndCompileInputData::new(&mut allocator, &parsed_args) {
Ok(r) => r,
Err(e) => {
stdout.write_str(&format!("FAIL: {e}\n"));
return;
}
};
let empty_map = HashMap::new();
let keywords = match parsed_args.get("no_keywords") {
Some(ArgumentValue::ArgBool(_b)) => &empty_map,
_ => {
keyword_from_atom(get_disassembly_ver(&parsed_args).unwrap_or(OPERATORS_LATEST_VERSION))
}
};
let extra_symbol_info = parsed_args.get("extra_syms").map(|_| true).unwrap_or(false);
let time_start = SystemTime::now();
let time_read_hex = SystemTime::now();
if let (
Some(ArgumentValue::ArgBool(true)),
Some(ArgumentValue::ArgString(file, file_content)),
) = (
parsed_args.get("dependencies"),
parsed_args.get("path_or_code"),
) {
if let Some(filename) = &file {
let opts = DefaultCompilerOpts::new(filename).set_search_paths(&parsed.search_paths);
match gather_dependencies(opts, filename, file_content) {
Err(e) => {
stdout.write_str(&format!("{}: {}\n", e.0, e.1));
}
Ok(res) => {
for r in res.iter() {
stdout.write_str(&decode_string(&r.name));
stdout.write_str("\n");
}
}
}
} else {
stdout.write_str("FAIL: must specify a filename\n");
}
return;
}
let special_runner = run_program_for_search_paths(
&parsed.use_filename(),
&parsed.search_paths,
extra_symbol_info,
);
special_runner.set_operators_version(get_disassembly_ver(&parsed_args));
let dpr = special_runner.clone();
let run_program = special_runner;
let time_assemble = SystemTime::now();
let input_sexp = allocator
.new_pair(parsed.program.parsed, parsed.args.parsed)
.ok();
let mut symbol_table: Option<HashMap<String, String>> = None;
let mut emit_symbol_output = false;
let symbol_table_clone = parsed_args
.get("symbol_table")
.and_then(|jstring| match jstring {
ArgumentValue::ArgString(_, s) => fs::read_to_string(s).ok().and_then(|s| {
let decoded_symbol_table: Option<HashMap<String, String>> =
serde_json::from_str(&s).ok();
decoded_symbol_table
}),
_ => None,
})
.inspect(|st| {
emit_symbol_output = true;
symbol_table = Some(st.clone());
});
if let Some(ArgumentValue::ArgBool(true)) = parsed_args.get("verbose") {
emit_symbol_output = true;
}
if parsed_args.contains_key("table") {
emit_symbol_output = true;
}
let do_check_unused = parsed_args
.get("check_unused_args")
.map(|a| matches!(a, ArgumentValue::ArgBool(true)))
.unwrap_or(false);
let mut stderr_output = |s: String| {
if parsed.dialect.stepping.is_some() {
eprintln!("{s}");
} else {
stdout.write_str(&s);
}
};
if do_check_unused {
let opts = Rc::new(DefaultCompilerOpts::new(&parsed.use_filename()))
.set_search_paths(&parsed.search_paths);
match check_unused(opts, &parsed.program.content) {
Ok((success, output)) => {
stderr_output(output);
if !success {
return;
}
}
Err(e) => {
stderr_output(format!("{}: {}\n", e.0, e.1));
return;
}
}
}
if parsed.dialect.stepping.is_some() {
if parsed_args.contains_key("preprocess") {
if let Err(e) = perform_preprocessing(
stdout,
parsed.opts.clone(),
&parsed.use_filename(),
&parsed.program.content,
) {
stdout.write_str(&format!("{}: {}", e.0, e.1));
}
return;
}
let mut symbol_table = HashMap::new();
let res = parsed
.compile_modern(&mut allocator, &mut symbol_table)
.and_then(|r| {
write_sym_output(&symbol_table, &parsed.symbol_table_output).map_err(|e| {
CompileErr(
Srcloc::start(&parsed.use_filename()),
format!("writing symbols: {e:?}"),
)
})?;
Ok(r)
});
match res {
Ok(r) => {
stdout.write_str(&r.to_string());
}
Err(c) => {
stdout.write_str(&format!("{}: {}", c.0, c.1));
}
}
return;
}
let mut pre_eval_f: Option<PreEval> = None;
let log_entries: Arc<Mutex<RunLog<NodePtr>>> = Arc::new(Mutex::new(RunLog {
log_entries: RefCell::new(Vec::new()),
}));
#[allow(clippy::type_complexity)]
let log_updates: Arc<Mutex<RunLog<(NodePtr, Option<NodePtr>)>>> =
Arc::new(Mutex::new(RunLog {
log_entries: RefCell::new(Vec::new()),
}));
let (pre_eval_req_out, pre_eval_req_in) = channel();
let (pre_eval_resp_out, pre_eval_resp_in): (Sender<()>, Receiver<()>) = channel();
let (post_eval_req_out, post_eval_req_in) = channel();
let (post_eval_resp_out, post_eval_resp_in): (Sender<()>, Receiver<()>) = channel();
let post_eval_fn: Rc<dyn Fn(NodePtr, Option<NodePtr>)> = Rc::new(move |at, n| {
post_eval_req_out.send((at, n)).ok();
post_eval_resp_in.recv().unwrap();
});
#[allow(clippy::type_complexity)]
let pre_eval_fn: Rc<dyn Fn(&mut Allocator, NodePtr)> = Rc::new(move |_allocator, new_log| {
pre_eval_req_out.send(new_log).ok();
pre_eval_resp_in.recv().unwrap();
});
#[allow(clippy::type_complexity)]
let closure: Rc<dyn Fn(NodePtr) -> Box<dyn Fn(&mut Allocator, Option<NodePtr>)>> =
Rc::new(move |v| {
let post_eval_fn_clone = post_eval_fn.clone();
Box::new(move |_allocator, n| {
let post_eval_fn_clone_2 = post_eval_fn_clone.clone();
(*post_eval_fn_clone_2)(v, n)
})
});
if emit_symbol_output {
#[allow(clippy::type_complexity)]
let pre_eval_f_closure: Box<
dyn Fn(
&mut Allocator,
NodePtr,
NodePtr,
)
-> Result<Option<Box<(dyn Fn(&mut Allocator, Option<NodePtr>))>>, EvalErr>,
> = Box::new(move |allocator, sexp, args| {
let pre_eval_clone = pre_eval_fn.clone();
trace_pre_eval(
allocator,
&|allocator, n| (*pre_eval_clone)(allocator, n),
symbol_table_clone.clone(),
sexp,
args,
)
.map(|t| {
t.map(|log_ent| {
let closure_clone = closure.clone();
(*closure_clone)(log_ent)
})
})
});
pre_eval_f = Some(pre_eval_f_closure);
}
let run_script = match parsed_args.get("stage") {
Some(ArgumentValue::ArgInt(0)) => stages::brun(&mut allocator),
_ => stages::run(&mut allocator),
};
let cost_offset = calculate_cost_offset(&mut allocator, run_program.clone(), run_script);
let max_cost = parsed_args
.get("max_cost")
.map(|x| match x {
ArgumentValue::ArgInt(i) => *i - cost_offset,
_ => 0,
})
.unwrap_or_else(|| 0);
let max_cost = max(0, max_cost);
let log_entries_clone = log_entries.clone();
thread::spawn(move || {
let pre_in = pre_eval_req_in;
let pre_out = pre_eval_resp_out;
while let Ok(received) = pre_in.recv() {
{
let locked = log_entries_clone.lock();
locked.unwrap().push(received);
}
pre_out.send(()).ok();
}
});
let log_updates_clone = log_updates.clone();
thread::spawn(move || {
let post_in = post_eval_req_in;
let post_out = post_eval_resp_out;
while let Ok(received) = post_in.recv() {
{
let locked = log_updates_clone.lock();
locked.unwrap().push(received);
}
post_out.send(()).ok();
}
});
let maybe_program_hash = parsed_args
.get("table")
.and_then(|_| program_hash_from_program_env_cons(&mut allocator, input_sexp.unwrap()).ok());
let time_parse_input = SystemTime::now();
let res = run_program
.run_program(
&mut allocator,
run_script,
input_sexp.unwrap(),
Some(RunProgramOption {
max_cost: if max_cost == 0 {
None
} else {
Some(max_cost as u64)
},
pre_eval_f,
operators_version: get_disassembly_ver(&parsed_args)
.unwrap_or(OPERATORS_LATEST_VERSION),
strict: parsed_args
.get("strict")
.map(|_| true)
.unwrap_or_else(|| false),
}),
)
.map(|run_program_result| {
let mut cost: i64 = run_program_result.0 as i64;
let result = run_program_result.1;
let time_done = SystemTime::now();
if parsed_args.contains_key("cost") {
if cost > 0 {
cost += cost_offset;
}
stdout.write_str(&format!("cost = {cost}\n"));
};
if let Some(ArgumentValue::ArgBool(true)) = parsed_args.get("time") {
if parsed_args.contains_key("hex") {
stdout.write_str(&format!(
"read_hex: {}\n",
time_read_hex
.duration_since(time_start)
.unwrap()
.as_millis()
));
} else {
stdout.write_str(&format!(
"assemble_from_ir: {}\n",
time_assemble
.duration_since(time_start)
.unwrap()
.as_millis()
));
stdout.write_str(&format!(
"to_sexp_f: {}\n",
time_parse_input
.duration_since(time_assemble)
.unwrap()
.as_millis()
));
}
stdout.write_str(&format!(
"run_program: {}\n",
time_done
.duration_since(time_parse_input)
.unwrap()
.as_millis()
));
}
let mut run_output = disassemble_with_kw(&allocator, result, keywords);
if let Some(ArgumentValue::ArgBool(true)) = parsed_args.get("dump") {
let mut f = Stream::new(None);
sexp_to_stream(&mut allocator, result, &mut f);
run_output = f.get_value().hex();
} else if let Some(ArgumentValue::ArgBool(true)) = parsed_args.get("quiet") {
run_output = "".to_string();
};
run_output
});
let output = collapse(res.map_err(|ex| {
format!(
"FAIL: {} {}",
match &ex {
EvalErr::InternalError(_, e) => e.to_string(),
_ => ex.to_string(),
},
disassemble_with_kw(&allocator, ex.node_ptr(), keywords)
)
}));
let disassembly_ver = get_disassembly_ver(&parsed_args);
let compile_sym_out = dpr.get_compiles();
if !compile_sym_out.is_empty() {
write_sym_output(&compile_sym_out, &parsed.symbol_table_output).ok();
}
stdout.write_str(&format!("{output}\n"));
let mut log_content = start_log_after(
&mut allocator,
maybe_program_hash,
log_entries.lock().unwrap().finish(),
);
let log_updates = log_updates.lock().unwrap().finish();
fix_log(&mut allocator, &mut log_content, &log_updates);
let only_exn = parsed_args
.get("only_exn")
.map(|_| true)
.unwrap_or_else(|| false);
if emit_symbol_output {
if parsed_args.contains_key("table") {
trace_to_table(
&mut allocator,
stdout,
only_exn,
&log_content,
symbol_table,
&|allocator, p| disassemble(allocator, p, disassembly_ver),
);
} else {
stdout.write_str("\n");
trace_to_text(
&mut allocator,
stdout,
only_exn,
&log_content,
symbol_table,
&|allocator, p| disassemble(allocator, p, disassembly_ver),
);
}
}
}