diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e0291442b..ce1ffb135 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -130,6 +130,7 @@ jobs: pip install --no-index --find-links target/wheels/ chialisp pip install clvm_rs pip install clvm_tools + pip install pyyaml cd resources/tests python test_clvm_step.py python mandelbrot-cldb.py diff --git a/resources/tests/mandelbrot-cldb.py b/resources/tests/mandelbrot-cldb.py index 11a915fb6..25bff8779 100644 --- a/resources/tests/mandelbrot-cldb.py +++ b/resources/tests/mandelbrot-cldb.py @@ -1,9 +1,23 @@ #!/usr/bin/env python import subprocess +import argparse +import yaml +import io -command = ['../../target/debug/cldb', '-p', './mandelbrot/mandelbrot.clsp', '(-192 -128 -144 -96 8)'] -want_output = """--- +parser = argparse.ArgumentParser() +parser.add_argument('-sigil', default=None) +args = parser.parse_args() + +infile = './mandelbrot/mandelbrot.clsp' +if args.sigil: + mb = open(infile).read() + infile = f'{infile}.sigil' + with open(infile, 'w') as outf: + outf.write(mb.replace('*standard-cl-21*', args.sigil)) + +command = ['../../target/debug/cldb', '-p', infile, '(-192 -128 -144 -96 8)'] +want_output = f"""--- - Print: ((escape-at -152 -104) 14) - Print: ((escape-at -160 -104) 14) - Print: ((escape-at -168 -104) 14) @@ -30,8 +44,24 @@ - Print: ((escape-at -192 -128) 5) - Print: "(\\"result\\" \\"||567AAC|68DEEE|78EEEE|78BEEE\\")" - Final: "3356114000950459963475899699747220812557867594760040767593731831711045" - Final-Location: "./mandelbrot/mandelbrot.clsp(8):43" + Final-Location: "{infile}(8):43" """ have_output = subprocess.check_output(command).decode('utf8') -assert have_output == want_output +parsed_have = yaml.safe_load(io.StringIO(have_output)) +parsed_want = yaml.safe_load(io.StringIO(want_output)) + +def dequote(kv): + result = {} + for k in kv.keys(): + result[k] = kv[k].replace('"','') + return result + +if args.sigil: + if 'Final-Location' in parsed_have[-1]: + del parsed_have[-1]['Final-Location'] + del parsed_want[-1]['Final-Location'] + parsed_have = list(map(dequote, parsed_have)) + parsed_want = list(map(dequote, parsed_want)) + +assert parsed_have == parsed_want diff --git a/src/classic/clvm/sexp.rs b/src/classic/clvm/sexp.rs index a20a33bc9..ebb691101 100644 --- a/src/classic/clvm/sexp.rs +++ b/src/classic/clvm/sexp.rs @@ -8,6 +8,8 @@ use clvm_rs::error::EvalErr; use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType, Stream}; use crate::classic::clvm::serialize::sexp_to_stream; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ASExp, ClError, ClassicAllocator}; +use crate::compiler::srcloc::Srcloc; use crate::util::{u8_from_number, Number}; #[derive(Debug)] @@ -344,21 +346,28 @@ pub fn non_nil(allocator: &Allocator, sexp: NodePtr) -> bool { } } -pub fn first(allocator: &Allocator, sexp: NodePtr) -> Result { - match allocator.sexp(sexp) { - SExp::Pair(f, _) => Ok(f), - _ => Err(EvalErr::InternalError( - sexp, - "first of non-cons".to_string(), - )), +pub fn first(allocator: &A, sexp: &A::NodePtr) -> Result { + let exported = allocator.export(sexp); + if let ASExp::Pair(f, _) = allocator.sexp(sexp) { + return Ok(f); } + + Err(ClError( + allocator.loc(sexp), + EvalErr::InternalError(exported, "first of non-cons".to_string()), + )) } -pub fn rest(allocator: &Allocator, sexp: NodePtr) -> Result { - match allocator.sexp(sexp) { - SExp::Pair(_, r) => Ok(r), - _ => Err(EvalErr::InternalError(sexp, "rest of non-cons".to_string())), +pub fn rest(allocator: &A, sexp: &A::NodePtr) -> Result { + let exported = allocator.export(sexp); + if let ASExp::Pair(_, r) = allocator.sexp(sexp) { + return Ok(r); } + + Err(ClError( + allocator.loc(sexp), + EvalErr::InternalError(exported, "rest of non-cons".to_string()), + )) } pub fn atom(allocator: &Allocator, sexp: NodePtr) -> Result, EvalErr> { @@ -372,19 +381,26 @@ pub fn atom(allocator: &Allocator, sexp: NodePtr) -> Result, EvalErr> { } } -pub fn proper_list(allocator: &Allocator, sexp: NodePtr, store: bool) -> Option> { +pub fn proper_list( + allocator: &A, + sexp: &A::NodePtr, + store: bool, +) -> Option> +where + A::NodePtr: Clone, +{ let mut args = vec![]; - let mut args_sexp = sexp; + let mut args_sexp = sexp.clone(); loop { - match allocator.sexp(args_sexp) { - SExp::Atom => { - if !non_nil(allocator, args_sexp) { + match allocator.sexp(&args_sexp) { + ASExp::Atom => { + if allocator.is_nil(&args_sexp) { return Some(args); } else { return None; } } - SExp::Pair(f, r) => { + ASExp::Pair(f, r) => { if store { args.push(f); } @@ -394,21 +410,31 @@ pub fn proper_list(allocator: &Allocator, sexp: NodePtr, store: bool) -> Option< } } -pub fn enlist(allocator: &mut Allocator, vec: &[NodePtr]) -> Result { - let mut built = NodePtr::NIL; +pub fn enlist( + allocator: &mut A, + vec: &[A::NodePtr], +) -> Result +where + A::NodePtr: Clone, +{ + let mut built = allocator.import(Srcloc::start("*nil*"), NodePtr::NIL)?; for i_reverse in 0..vec.len() { let i = vec.len() - i_reverse - 1; - built = allocator.new_pair(vec[i], built)?; + let loc = allocator.loc(&vec[i]); + built = allocator.new_pair(loc, &vec[i], &built)?; } Ok(built) } -pub fn map_m( - allocator: &mut Allocator, +type MapMTransform<'a, A, T> = + &'a dyn Fn(&mut A, T) -> Result<::NodePtr, ClError>; + +pub fn map_m<'a, T, A: ClassicAllocator>( + allocator: &mut A, iter: &mut impl Iterator, - f: &dyn Fn(&mut Allocator, T) -> Result, -) -> Result, EvalErr> { + f: MapMTransform<'a, A, T>, +) -> Result, ClError> { let mut result = Vec::new(); loop { match iter.next() { @@ -427,9 +453,9 @@ pub fn map_m( } } -pub fn fold_m( - allocator: &mut Allocator, - f: &dyn Fn(&mut Allocator, A, B) -> Result, +pub fn fold_m( + allocator: &mut CA, + f: &dyn Fn(&mut CA, A, B) -> Result, start_: A, iter: &mut impl Iterator, ) -> Result { @@ -451,21 +477,28 @@ pub fn fold_m( } } -pub fn equal_to(allocator: &mut Allocator, first_: NodePtr, second_: NodePtr) -> bool { - let mut first = first_; - let mut second = second_; +pub fn equal_to( + allocator: &mut A, + first_: &A::NodePtr, + second_: &A::NodePtr, +) -> bool +where + A::NodePtr: Clone, +{ + let mut first = first_.clone(); + let mut second = second_.clone(); loop { - if first == second { + if allocator.node_equal(&first, &second) { return true; } - match (allocator.sexp(first), allocator.sexp(second)) { - (SExp::Atom, SExp::Atom) => { + match (allocator.sexp(&first), allocator.sexp(&second)) { + (ASExp::Atom, ASExp::Atom) => { // two atoms in scope, both are used - return allocator.atom(first) == allocator.atom(second); + return allocator.atom(&first) == allocator.atom(&second); } - (SExp::Pair(ff, fr), SExp::Pair(rf, rr)) => { - if !equal_to(allocator, ff, rf) { + (ASExp::Pair(ff, fr), ASExp::Pair(rf, rr)) => { + if !equal_to(allocator, &ff, &rf) { return false; } first = fr; @@ -478,19 +511,25 @@ pub fn equal_to(allocator: &mut Allocator, first_: NodePtr, second_: NodePtr) -> } } -pub fn flatten(allocator: &mut Allocator, tree_: NodePtr, res: &mut Vec) { - let mut tree = tree_; +pub fn flatten( + allocator: &mut A, + tree_: &A::NodePtr, + res: &mut Vec, +) where + A::NodePtr: Clone, +{ + let mut tree = tree_.clone(); loop { - match allocator.sexp(tree) { - SExp::Atom => { - if non_nil(allocator, tree) { - res.push(tree); + match allocator.sexp(&tree) { + ASExp::Atom => { + if !allocator.is_nil(&tree) { + res.push(tree.clone()); } return; } - SExp::Pair(l, r) => { - flatten(allocator, l, res); + ASExp::Pair(l, r) => { + flatten(allocator, &l, res); tree = r; } } @@ -501,10 +540,10 @@ pub fn flatten(allocator: &mut Allocator, tree_: NodePtr, res: &mut Vec // the classic chialisp code. pub fn nonempty_last(nil: NodePtr, lst: &[X]) -> Result where - X: Copy, + X: Clone, { lst.last() - .copied() + .cloned() .ok_or_else(|| EvalErr::InternalError(nil, "alist is empty and shouldn't be".to_string())) } @@ -533,57 +572,53 @@ pub enum ThisNode { Here, } -pub trait SelectNode { - fn select_nodes(&self, allocator: &mut Allocator, n: NodePtr) -> Result; +pub trait SelectNode { + fn select_nodes(&self, allocator: &mut A, n: A::NodePtr) -> Result; } -impl SelectNode for ThisNode { - fn select_nodes(&self, _allocator: &mut Allocator, n: NodePtr) -> Result { +impl SelectNode for ThisNode { + fn select_nodes(&self, _allocator: &mut A, n: A::NodePtr) -> Result { Ok(n) } } -impl SelectNode<()> for () { - fn select_nodes(&self, _allocator: &mut Allocator, _n: NodePtr) -> Result<(), EvalErr> { +impl SelectNode<(), A> for () { + fn select_nodes(&self, _allocator: &mut A, _n: A::NodePtr) -> Result<(), ClError> { Ok(()) } } -impl SelectNode> for First +impl SelectNode, A> for First where - R: SelectNode + Clone, + R: SelectNode + Clone, { - fn select_nodes(&self, allocator: &mut Allocator, n: NodePtr) -> Result, EvalErr> { + fn select_nodes(&self, allocator: &mut A, n: A::NodePtr) -> Result, ClError> { let First::Here(f) = &self; let NodeSel::Cons(first, ()) = NodeSel::Cons(f.clone(), ()).select_nodes(allocator, n)?; Ok(First::Here(first)) } } -impl SelectNode> for Rest +impl SelectNode, A> for Rest where - R: SelectNode + Clone, + R: SelectNode + Clone, { - fn select_nodes(&self, allocator: &mut Allocator, n: NodePtr) -> Result, EvalErr> { + fn select_nodes(&self, allocator: &mut A, n: A::NodePtr) -> Result, ClError> { let Rest::Here(f) = &self; let NodeSel::Cons((), rest) = NodeSel::Cons((), f.clone()).select_nodes(allocator, n)?; Ok(Rest::Here(rest)) } } -impl SelectNode> for NodeSel +impl SelectNode, A> for NodeSel where - R: SelectNode, - S: SelectNode, + R: SelectNode, + S: SelectNode, { - fn select_nodes( - &self, - allocator: &mut Allocator, - n: NodePtr, - ) -> Result, EvalErr> { + fn select_nodes(&self, allocator: &mut A, n: A::NodePtr) -> Result, ClError> { let NodeSel::Cons(my_left, my_right) = &self; - let l = first(allocator, n)?; - let r = rest(allocator, n)?; + let l = first(allocator, &n)?; + let r = rest(allocator, &n)?; let first = my_left.select_nodes(allocator, l)?; let rest = my_right.select_nodes(allocator, r)?; Ok(NodeSel::Cons(first, rest)) diff --git a/src/classic/clvm_tools/cmds.rs b/src/classic/clvm_tools/cmds.rs index 293b3f808..2afcaf337 100644 --- a/src/classic/clvm_tools/cmds.rs +++ b/src/classic/clvm_tools/cmds.rs @@ -909,7 +909,7 @@ fn fix_log( 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| { + proper_list(allocator, entry, true).map(|list| { let mut updated = list.to_vec(); updated.push(v); log_result[i] = enlist(allocator, &updated).unwrap(); diff --git a/src/classic/clvm_tools/debug.rs b/src/classic/clvm_tools/debug.rs index 9671a05fa..609877377 100644 --- a/src/classic/clvm_tools/debug.rs +++ b/src/classic/clvm_tools/debug.rs @@ -8,9 +8,9 @@ use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType, Stream} use crate::classic::clvm::serialize::sexp_to_stream; use crate::classic::clvm::sexp::{enlist, proper_list, rest, First, SelectNode, ThisNode}; -use crate::classic::clvm_tools::binutils::disassemble; use crate::classic::clvm_tools::sha256tree::sha256tree; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ClError, ClassicAllocator}; use crate::compiler::comptypes::{CompileErr, CompilerOpts}; use crate::compiler::frontend::frontend; @@ -49,15 +49,29 @@ use crate::compiler::usecheck::check_parameters_used_compileform; /// Contains additional info beside the compiled form for chialisp functions/ /// These can be passed on and used by debuggers and such. -#[derive(Clone)] -pub struct FunctionExtraInfo { +pub struct FunctionExtraInfo +where + A::NodePtr: Clone, +{ /// The form of the original arguments from the source code. - pub args: NodePtr, + pub args: A::NodePtr, /// Whether this function requires the constants and functions of the program /// as an additional hidden parameter. pub has_constants_tree: bool, } +impl Clone for FunctionExtraInfo +where + A::NodePtr: Clone, +{ + fn clone(&self) -> Self { + FunctionExtraInfo { + args: self.args.clone(), + has_constants_tree: self.has_constants_tree, + } + } +} + // // The function below is broken as of 2021/06/22. // /* // export function dump_invocation( @@ -92,23 +106,30 @@ pub struct FunctionExtraInfo { // // @todo Implement here if original python code is fixed. // } // */ -pub fn build_symbol_dump( - allocator: &mut Allocator, - constants_lookup: &HashMap, NodePtr>, - extra_function_data: &HashMap, FunctionExtraInfo>, +pub fn build_symbol_dump( + allocator: &mut A, + constants_lookup: &HashMap, A::NodePtr>, + extra_function_data: &HashMap, FunctionExtraInfo>, run_program: Rc, extra_info: bool, -) -> Result { - let mut map_result: Vec = Vec::new(); +) -> Result +where + A::NodePtr: Clone, +{ + let mut map_result: Vec = Vec::new(); for (k, v) in constants_lookup.iter() { - let run_result = run_program.run_program(allocator, *v, NodePtr::NIL, None)?; + let v_export = allocator.export(v); + let vloc = allocator.loc(v); + let run_result = run_program + .run_program(allocator.allocator(), v_export, NodePtr::NIL, None) + .map_err(|e| allocator.map_err(vloc.clone(), e))?; - let sha256 = sha256tree(allocator, run_result.1).hex(); - let sha_atom = allocator.new_atom(sha256.as_bytes())?; - let name_atom = allocator.new_atom(&k.clone())?; + let sha256 = sha256tree(allocator.allocator(), run_result.1).hex(); + let sha_atom = allocator.new_atom(vloc.clone(), sha256.as_bytes())?; + let name_atom = allocator.new_atom(vloc.clone(), &k.clone())?; - map_result.push(allocator.new_pair(sha_atom, name_atom)?); + map_result.push(allocator.new_pair(vloc.clone(), &sha_atom, &name_atom)?); if !extra_info { continue; @@ -123,16 +144,26 @@ pub fn build_symbol_dump( left_env_atom.append(&mut sha256.as_bytes().to_vec()); left_env_atom.append(&mut "_left_env".as_bytes().to_vec()); - let args_name_atom = allocator.new_atom(&args_atom)?; - let left_env_name_atom = allocator.new_atom(&left_env_atom)?; - - let serialized_args = disassemble(allocator, extra.args, Some(0)); - let serialized_args_atom = allocator.new_atom(serialized_args.as_bytes())?; - - let left_env_value = allocator.new_atom(&[extra.has_constants_tree as u8])?; - - map_result.push(allocator.new_pair(args_name_atom, serialized_args_atom)?); - map_result.push(allocator.new_pair(left_env_name_atom, left_env_value)?); + let args_name_atom = allocator.new_atom(vloc.clone(), &args_atom)?; + let left_env_name_atom = allocator.new_atom(vloc.clone(), &left_env_atom)?; + + let serialized_args = allocator.disassemble(&extra.args, Some(0)); + let serialized_args_atom = + allocator.new_atom(vloc.clone(), serialized_args.as_bytes())?; + + let left_env_value = + allocator.new_atom(vloc.clone(), &[extra.has_constants_tree as u8])?; + + map_result.push(allocator.new_pair( + vloc.clone(), + &args_name_atom, + &serialized_args_atom, + )?); + map_result.push(allocator.new_pair( + vloc.clone(), + &left_env_name_atom, + &left_env_value, + )?); } } @@ -152,7 +183,7 @@ fn text_trace( let mut env = env_; match symbol { Some(sym) => { - env = rest(allocator, env).unwrap_or(NodePtr::NIL); + env = rest(allocator, &env).unwrap_or(NodePtr::NIL); let symbol_atom = allocator.new_atom(sym.as_bytes()).unwrap(); let symbol_list = allocator.new_pair(symbol_atom, env).unwrap(); symbol_val = disassemble_f(allocator, symbol_list); @@ -219,7 +250,7 @@ fn display_trace( display_fun: &DisplayTraceFun, ) { for item in trace { - let item_vec = proper_list(allocator, *item, true).unwrap(); + let item_vec = proper_list(allocator, item, true).unwrap(); let form = item_vec[0]; let env = item_vec[1]; let not_exn = item_vec.len() > 2; @@ -294,11 +325,9 @@ pub fn trace_pre_eval( if recognized.is_none() && symbol_table.is_some() { Ok(None) } else { - m! { - log_entry <- enlist(allocator, &[sexp, args]); - let _ = append_log(allocator, log_entry); - Ok(Some(log_entry)) - } + let log_entry = enlist(allocator, &[sexp, args])?; + append_log(allocator, log_entry); + Ok(Some(log_entry)) } } diff --git a/src/classic/clvm_tools/pattern_match.rs b/src/classic/clvm_tools/pattern_match.rs index a096ce8ef..0a62fe34e 100644 --- a/src/classic/clvm_tools/pattern_match.rs +++ b/src/classic/clvm_tools/pattern_match.rs @@ -1,17 +1,23 @@ use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType}; use crate::classic::clvm::sexp::equal_to; -use clvm_rs::allocator::{Allocator, NodePtr, SExp}; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ + ASExp, BufCarrier, ClassicAllocator, +}; + use std::collections::HashMap; pub const ATOM_MATCH: [u8; 1] = *b"$"; pub const SEXP_MATCH: [u8; 1] = *b":"; -pub fn unify_bindings( - allocator: &mut Allocator, - bindings: HashMap, +pub fn unify_bindings( + allocator: &mut A, + bindings: HashMap, new_key: &[u8], - new_value: NodePtr, -) -> Option> { + new_value: &A::NodePtr, +) -> Option> +where + A::NodePtr: Clone, +{ /* * Try to add a new binding to the list, rejecting it if it conflicts * with an existing binding. @@ -19,25 +25,28 @@ pub fn unify_bindings( let new_key_str = Bytes::new(Some(BytesFromType::Raw(new_key.to_vec()))).decode(); match bindings.get(&new_key_str) { Some(binding) => { - if !equal_to(allocator, *binding, new_value) { + if !equal_to(allocator, binding, new_value) { return None; } Some(bindings) } _ => { let mut new_bindings = bindings.clone(); - new_bindings.insert(new_key_str, new_value); + new_bindings.insert(new_key_str, new_value.clone()); Some(new_bindings) } } } -pub fn match_sexp( - allocator: &mut Allocator, - pattern: NodePtr, - sexp: NodePtr, - known_bindings: HashMap, -) -> Option> { +pub fn match_sexp( + allocator: &mut A, + pattern: &A::NodePtr, + sexp: &A::NodePtr, + known_bindings: HashMap, +) -> Option> +where + A::NodePtr: Clone, +{ /* * Determine if sexp matches the pattern, with the given known bindings already applied. * Returns None if no match, or a (possibly empty) dictionary of bindings if there is a match @@ -51,7 +60,7 @@ pub fn match_sexp( */ match (allocator.sexp(pattern), allocator.sexp(sexp)) { - (SExp::Atom, SExp::Atom) => { + (ASExp::Atom, ASExp::Atom) => { // Two nodes in scope, both used. if allocator.atom(pattern) == allocator.atom(sexp) { Some(known_bindings) @@ -59,73 +68,82 @@ pub fn match_sexp( None } } - (SExp::Pair(pleft, pright), _) => match (allocator.sexp(pleft), allocator.sexp(pright)) { - (SExp::Atom, SExp::Atom) => { - let left_atom = allocator.atom(pleft); - let right_atom = allocator.atom(pright); + (ASExp::Pair(pleft, pright), _) => { + match (allocator.sexp(&pleft), allocator.sexp(&pright)) { + (ASExp::Atom, ASExp::Atom) => { + let left_atom = allocator.atom(&pleft); + let right_atom = allocator.atom(&pright); + + // This is a false positive due to Allocator lifetime. + #[allow(clippy::unnecessary_to_owned)] + match allocator.sexp(sexp) { + ASExp::Atom => { + // Expression is ($ . $), sexp is '$', result: no capture. + // Avoid double borrow. + let sexp_atom = allocator.atom(sexp); + if left_atom.as_ref() == ATOM_MATCH { + if right_atom.as_ref() == ATOM_MATCH { + if sexp_atom.as_ref() == ATOM_MATCH { + return Some(HashMap::new()); + } + return None; + } - // This is a false positive due to Allocator lifetime. - #[allow(clippy::unnecessary_to_owned)] - match allocator.sexp(sexp) { - SExp::Atom => { - // Expression is ($ . $), sexp is '$', result: no capture. - // Avoid double borrow. - let sexp_atom = allocator.atom(sexp); - if left_atom.as_ref() == ATOM_MATCH { - if right_atom.as_ref() == ATOM_MATCH { - if sexp_atom.as_ref() == ATOM_MATCH { + return unify_bindings( + allocator, + known_bindings, + &right_atom.as_ref().to_vec(), + sexp, + ); + } + if left_atom.as_ref() == SEXP_MATCH { + if right_atom.as_ref() == SEXP_MATCH + && sexp_atom.as_ref() == SEXP_MATCH + { return Some(HashMap::new()); } - return None; + + return unify_bindings( + allocator, + known_bindings, + // pat_right_bytes + &right_atom.as_ref().to_vec(), + sexp, + ); } - return unify_bindings( - allocator, - known_bindings, - &right_atom.as_ref().to_vec(), - sexp, - ); + None } - if left_atom.as_ref() == SEXP_MATCH { - if right_atom.as_ref() == SEXP_MATCH && sexp_atom.as_ref() == SEXP_MATCH + ASExp::Pair(sleft, sright) => { + if left_atom.as_ref() == SEXP_MATCH && right_atom.as_ref() != SEXP_MATCH { - return Some(HashMap::new()); + return unify_bindings( + allocator, + known_bindings, + // pat_right_bytes + &right_atom.as_ref().to_vec(), + sexp, + ); } - return unify_bindings( - allocator, - known_bindings, - // pat_right_bytes - &right_atom.as_ref().to_vec(), - sexp, - ); + match_sexp(allocator, &pleft, &sleft, known_bindings).and_then( + |new_bindings| { + match_sexp(allocator, &pright, &sright, new_bindings) + }, + ) } - - None } - SExp::Pair(sleft, sright) => { - if left_atom.as_ref() == SEXP_MATCH && right_atom.as_ref() != SEXP_MATCH { - return unify_bindings( - allocator, - known_bindings, - // pat_right_bytes - &right_atom.as_ref().to_vec(), - sexp, - ); - } - - match_sexp(allocator, pleft, sleft, known_bindings).and_then( - |new_bindings| match_sexp(allocator, pright, sright, new_bindings), + } + _ => match allocator.sexp(sexp) { + ASExp::Atom => None, + ASExp::Pair(sleft, sright) => { + match_sexp(allocator, &pleft, &sleft, known_bindings).and_then( + |new_bindings| match_sexp(allocator, &pright, &sright, new_bindings), ) } - } + }, } - _ => match allocator.sexp(sexp) { - SExp::Atom => None, - SExp::Pair(sleft, sright) => match_sexp(allocator, pleft, sleft, known_bindings) - .and_then(|new_bindings| match_sexp(allocator, pright, sright, new_bindings)), - }, - }, - (SExp::Atom, _) => None, + } + (ASExp::Atom, _) => None, } } diff --git a/src/classic/clvm_tools/stages/stage_2/abstraction.rs b/src/classic/clvm_tools/stages/stage_2/abstraction.rs new file mode 100644 index 000000000..3e8fe3f0e --- /dev/null +++ b/src/classic/clvm_tools/stages/stage_2/abstraction.rs @@ -0,0 +1,307 @@ +use std::rc::Rc; + +use std::ops::Index; + +use clvm_rs::allocator::{Allocator, NodePtr, SExp}; +use clvm_rs::error::EvalErr; + +use crate::classic::clvm::__type_compatibility__::bi_zero; +use crate::classic::clvm_tools::binutils::disassemble; +use crate::compiler::sexp::SExp as ModernSExp; +use crate::compiler::srcloc::Srcloc; +use crate::util::{number_from_u8, u8_from_number}; + +pub enum ASExp { + Pair(T, T), + Atom, +} + +#[derive(Debug)] +pub struct ClError(pub Srcloc, pub EvalErr); + +impl std::fmt::Display for ClError { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(formatter, "{}", self.1) + } +} + +impl From for EvalErr { + fn from(err: ClError) -> Self { + err.1 + } +} + +pub trait BufCarrier<'a> { + fn as_ref(&'a self) -> &'a [u8]; +} + +#[derive(PartialEq, Eq)] +pub struct BufHolder<'a>(clvmr::Atom<'a>); + +impl<'a> Index for BufHolder<'a> { + type Output = u8; + fn index(&self, idx: usize) -> &Self::Output { + &self.as_ref()[idx] + } +} + +impl<'a> BufCarrier<'a> for BufHolder<'a> { + fn as_ref(&'a self) -> &'a [u8] { + self.0.as_ref() + } +} + +pub trait ClassicAllocator { + type NodePtr; + fn loc(&self, node: &Self::NodePtr) -> Srcloc; + fn sexp(&self, node: &Self::NodePtr) -> ASExp; + fn atom<'a>(&'a self, node: &Self::NodePtr) -> BufHolder<'a>; + fn is_nil(&self, node: &Self::NodePtr) -> bool; + fn disassemble(&self, node: &Self::NodePtr, version: Option) -> String; + fn allocator(&mut self) -> &mut Allocator; + fn node_equal(&self, a: &Self::NodePtr, b: &Self::NodePtr) -> bool; + fn map_err(&self, loc: Srcloc, err: EvalErr) -> ClError; + fn new_atom(&mut self, loc: Srcloc, value: &[u8]) -> Result; + fn new_pair( + &mut self, + loc: Srcloc, + a: &Self::NodePtr, + b: &Self::NodePtr, + ) -> Result; + fn import(&mut self, loc: Srcloc, node: NodePtr) -> Result; + fn export(&self, node: &Self::NodePtr) -> NodePtr; +} + +thread_local! { + pub static DEFAULT_SRCLOC: Srcloc = Srcloc::start("*clvm*"); +} + +impl ClassicAllocator for Allocator { + type NodePtr = clvmr::NodePtr; + + fn loc(&self, _node: &Self::NodePtr) -> Srcloc { + DEFAULT_SRCLOC.with(|s| s.clone()) + } + fn sexp(&self, node: &Self::NodePtr) -> ASExp { + match clvmr::Allocator::sexp(self, *node) { + SExp::Pair(a, b) => ASExp::Pair(a, b), + SExp::Atom => ASExp::Atom, + } + } + fn atom<'a>(&'a self, node: &Self::NodePtr) -> BufHolder<'a> { + BufHolder(self.atom(*node)) + } + fn is_nil(&self, node: &Self::NodePtr) -> bool { + *node == NodePtr::NIL + } + fn disassemble(&self, node: &Self::NodePtr, version: Option) -> String { + disassemble(self, *node, version) + } + fn allocator(&mut self) -> &mut Allocator { + self + } + fn node_equal(&self, a: &Self::NodePtr, b: &Self::NodePtr) -> bool { + *a == *b + } + fn map_err(&self, loc: Srcloc, err: EvalErr) -> ClError { + ClError(loc, err) + } + fn new_atom(&mut self, loc: Srcloc, value: &[u8]) -> Result { + let new_atom = + clvmr::Allocator::new_atom(self, value).map_err(|e| self.map_err(loc.clone(), e))?; + self.import(loc, new_atom) + } + fn new_pair( + &mut self, + loc: Srcloc, + a: &Self::NodePtr, + b: &Self::NodePtr, + ) -> Result { + let new_pair = + clvmr::Allocator::new_pair(self, *a, *b).map_err(|e| self.map_err(loc.clone(), e))?; + self.import(loc, new_pair) + } + fn import(&mut self, _loc: Srcloc, node: NodePtr) -> Result { + Ok(node) + } + fn export(&self, node: &Self::NodePtr) -> NodePtr { + *node + } +} + +/// A stage-2 node backed by the compiler's location-aware S-expression. +/// +/// `raw` mirrors `sexp` in the contained CLVM allocator. Stage-2 occasionally +/// has to execute generated CLVM, so retaining both representations lets those +/// boundaries use clvmr without discarding locations from the source tree. +#[derive(Clone, Debug)] +pub struct SExpNode { + pub sexp: Rc, + raw: NodePtr, +} + +impl std::fmt::Display for SExpNode { + fn fmt(&self, formatter: &'_ mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + write!(formatter, "{}", self.sexp) + } +} + +/// Adapts modern compiler S-expressions to the classic stage-2 compiler. +pub struct SExpClassicAllocator { + allocator: Allocator, +} + +impl Default for SExpClassicAllocator { + fn default() -> Self { + Self::new() + } +} + +impl SExpClassicAllocator { + pub fn new() -> Self { + Self { + allocator: Allocator::new(), + } + } + + pub fn from_sexp(&mut self, sexp: Rc) -> Result { + let loc = sexp.loc(); + let raw = match sexp.as_ref() { + ModernSExp::Nil(_) => NodePtr::NIL, + ModernSExp::Cons(_, first, rest) => { + let first = self.from_sexp(first.clone())?; + let rest = self.from_sexp(rest.clone())?; + self.allocator + .new_pair(first.raw, rest.raw) + .map_err(|e| self.map_err(loc.clone(), e))? + } + ModernSExp::Integer(_, value) => if *value == bi_zero() { + self.allocator.new_atom(&[]) + } else { + self.allocator.new_atom(&u8_from_number(value.clone())) + } + .map_err(|e| self.map_err(loc.clone(), e))?, + ModernSExp::QuotedString(_, _, value) | ModernSExp::Atom(_, value) => self + .allocator + .new_atom(value) + .map_err(|e| self.map_err(loc.clone(), e))?, + }; + Ok(SExpNode { sexp, raw }) + } +} + +impl ClassicAllocator for SExpClassicAllocator { + type NodePtr = SExpNode; + + fn loc(&self, node: &Self::NodePtr) -> Srcloc { + node.sexp.loc() + } + + fn sexp(&self, node: &Self::NodePtr) -> ASExp { + match node.sexp.as_ref() { + ModernSExp::Cons(_, first, rest) => { + let SExp::Pair(raw_first, raw_rest) = self.allocator.sexp(node.raw) else { + unreachable!("modern and raw S-expression representations diverged") + }; + ASExp::Pair( + SExpNode { + sexp: first.clone(), + raw: raw_first, + }, + SExpNode { + sexp: rest.clone(), + raw: raw_rest, + }, + ) + } + _ => ASExp::Atom, + } + } + + fn atom<'a>(&'a self, node: &Self::NodePtr) -> BufHolder<'a> { + BufHolder(self.allocator.atom(node.raw)) + } + + fn is_nil(&self, node: &Self::NodePtr) -> bool { + node.raw == NodePtr::NIL + } + + fn disassemble(&self, node: &Self::NodePtr, version: Option) -> String { + disassemble(&self.allocator, node.raw, version) + } + + fn allocator(&mut self) -> &mut Allocator { + &mut self.allocator + } + + fn node_equal(&self, a: &Self::NodePtr, b: &Self::NodePtr) -> bool { + a.raw == b.raw + } + + fn map_err(&self, loc: Srcloc, err: EvalErr) -> ClError { + ClError(loc, err) + } + + fn new_atom(&mut self, loc: Srcloc, value: &[u8]) -> Result { + let raw = self + .allocator + .new_atom(value) + .map_err(|e| ClError(loc.clone(), e))?; + let sexp = if value.is_empty() { + ModernSExp::Nil(loc) + } else { + let integer = number_from_u8(value); + if u8_from_number(integer.clone()) == value { + ModernSExp::Integer(loc, integer) + } else { + ModernSExp::Atom(loc, value.to_vec()) + } + }; + Ok(SExpNode { + sexp: Rc::new(sexp), + raw, + }) + } + + fn new_pair( + &mut self, + loc: Srcloc, + a: &Self::NodePtr, + b: &Self::NodePtr, + ) -> Result { + let raw = self + .allocator + .new_pair(a.raw, b.raw) + .map_err(|e| ClError(loc.clone(), e))?; + Ok(SExpNode { + sexp: Rc::new(ModernSExp::Cons(loc, a.sexp.clone(), b.sexp.clone())), + raw, + }) + } + + fn import(&mut self, loc: Srcloc, node: NodePtr) -> Result { + let sexp = match self.allocator.sexp(node) { + SExp::Atom if node == NodePtr::NIL => Rc::new(ModernSExp::Nil(loc)), + SExp::Atom => { + let value = self.allocator.atom(node); + let value = value.as_ref(); + let integer = number_from_u8(value); + if u8_from_number(integer.clone()) == value { + Rc::new(ModernSExp::Integer(loc, integer)) + } else { + Rc::new(ModernSExp::Atom(loc, value.to_vec())) + } + } + SExp::Pair(first, rest) => { + let first = self.import(loc.clone(), first)?; + let rest = self.import(loc.clone(), rest)?; + Rc::new(ModernSExp::Cons(loc, first.sexp, rest.sexp)) + } + }; + Ok(SExpNode { sexp, raw: node }) + } + + fn export(&self, node: &Self::NodePtr) -> NodePtr { + node.raw + } +} diff --git a/src/classic/clvm_tools/stages/stage_2/compile.rs b/src/classic/clvm_tools/stages/stage_2/compile.rs index bad7b8182..2bddd44a0 100644 --- a/src/classic/clvm_tools/stages/stage_2/compile.rs +++ b/src/classic/clvm_tools/stages/stage_2/compile.rs @@ -3,19 +3,23 @@ use std::rc::Rc; use clvm_rs::allocator::{Allocator, NodePtr, SExp}; use clvm_rs::error::EvalErr; -use clvm_rs::reduction::{Reduction, Response}; +use clvm_rs::reduction::Reduction; use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType}; -use crate::classic::clvm::sexp::{enlist, first, map_m, non_nil, proper_list, rest}; +use crate::classic::clvm::sexp::{enlist, first, map_m, proper_list, rest}; use crate::classic::clvm::OPERATORS_LATEST_VERSION; use crate::classic::clvm::{keyword_from_atom, keyword_to_atom}; -use crate::classic::clvm_tools::binutils::{assemble, disassemble}; +use crate::classic::clvm_tools::binutils::assemble; use crate::classic::clvm_tools::node_path::NodePath; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ + ASExp, BufCarrier, ClError, ClassicAllocator, +}; use crate::classic::clvm_tools::stages::stage_2::defaults::default_macro_lookup; use crate::classic::clvm_tools::stages::stage_2::helpers::{brun, evaluate, quote}; use crate::classic::clvm_tools::stages::stage_2::module::compile_mod; +use crate::compiler::srcloc::Srcloc; const DIAG_OUTPUT: bool = false; @@ -35,20 +39,23 @@ lazy_static! { }; } -struct Closure<'a> { +struct Closure<'a, A: ClassicAllocator> { name: String, #[allow(clippy::type_complexity)] to_run: &'a dyn Fn( - &mut Allocator, - NodePtr, - NodePtr, - NodePtr, + &mut A, + &A::NodePtr, + &A::NodePtr, + &A::NodePtr, Rc, usize, - ) -> Result, + ) -> Result, } -fn compile_bindings<'a>() -> HashMap, Closure<'a>> { +fn compile_bindings<'a, A: ClassicAllocator>() -> HashMap, Closure<'a, A>> +where + A::NodePtr: Clone, +{ let mut bindings = HashMap::new(); let bindings_source = vec![ Closure { @@ -87,145 +94,216 @@ fn unquote_atom() -> Vec { "unquote".as_bytes().to_vec() } -fn com_qq( - allocator: &mut Allocator, +fn com_qq( + allocator: &mut A, ident: String, - macro_lookup: NodePtr, - symbol_table: NodePtr, + macro_lookup: &A::NodePtr, + symbol_table: &A::NodePtr, runner: Rc, - sexp: NodePtr, -) -> Result { + sexp: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ if DIAG_OUTPUT { - println!("com_qq {} {}", ident, disassemble(allocator, sexp, None)); + println!("com_qq {} {}", ident, allocator.disassemble(sexp, None)); } - do_com_prog(allocator, 110, sexp, macro_lookup, symbol_table, runner).map(|x| x.1) + do_com_prog(allocator, 110, sexp, macro_lookup, symbol_table, runner) } -pub fn compile_qq( - allocator: &mut Allocator, - args: NodePtr, - macro_lookup: NodePtr, - symbol_table: NodePtr, +pub fn compile_qq( + allocator: &mut A, + args: &A::NodePtr, + macro_lookup: &A::NodePtr, + symbol_table: &A::NodePtr, runner: Rc, level: usize, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ /* * (qq ATOM) => (q . ATOM) * (qq (unquote X)) => X * (qq (a . B)) => (c (qq a) (qq B)) */ - let sexp = match first(allocator, args) { - Err(e) => { - return Err(e); - } - Ok(x) => x, - }; + let sexp = first(allocator, args)?; + let loc = allocator.loc(&sexp); + let nil_import = allocator.import(loc.clone(), NodePtr::NIL)?; - match allocator.sexp(sexp) { - SExp::Atom => { + match allocator.sexp(&sexp) { + ASExp::Atom => { // (qq ATOM) => (q . ATOM) - quote(allocator, sexp) + quote(allocator, &sexp) } - SExp::Pair(op, sexp_rest) => { - if let SExp::Atom = allocator.sexp(op) { + ASExp::Pair(op, sexp_rest) => { + if let ASExp::Atom = allocator.sexp(&op) { // opbuf => op - let op_atom = allocator.atom(op); + let op_atom = allocator.atom(&op); + let op_loc = allocator.loc(&op); if op_atom.as_ref() == qq_atom() { - return m! { - cons_atom <- allocator.new_atom(&[4]); - subexp <- - compile_qq(allocator, sexp_rest, macro_lookup, symbol_table, runner.clone(), level+1); - quoted_null <- quote(allocator, NodePtr::NIL); - consed <- enlist(allocator, &[cons_atom, subexp, quoted_null]); - run_list <- enlist(allocator, &[cons_atom, op, consed]); - com_qq(allocator, "qq sexp pair".to_string(), macro_lookup, symbol_table, runner, run_list) - }; + let op_loc = allocator.loc(&op); + let cons_atom = allocator.new_atom(op_loc, &[4])?; + let subexp = compile_qq( + allocator, + &sexp_rest, + macro_lookup, + symbol_table, + runner.clone(), + level + 1, + )?; + let quoted_null = quote(allocator, &nil_import)?; + let consed = enlist(allocator, &[cons_atom.clone(), subexp, quoted_null])?; + let run_list = enlist(allocator, &[cons_atom, op, consed])?; + return com_qq( + allocator, + "qq sexp pair".to_string(), + macro_lookup, + symbol_table, + runner, + &run_list, + ); } else if op_atom.as_ref() == unquote_atom() { // opbuf if level == 1 { // (qq (unquote X)) => X - return m! { - sexp_rf <- first(allocator, sexp_rest); - com_qq(allocator, "level 1".to_string(), macro_lookup, symbol_table, runner, sexp_rf) - }; + let sexp_rf = first(allocator, &sexp_rest)?; + return com_qq( + allocator, + "level 1".to_string(), + macro_lookup, + symbol_table, + runner, + &sexp_rf, + ); } - return m! { - // (qq (a . B)) => (c (qq a) (qq B)) - cons_atom <- allocator.new_atom(&[4]); - subexp <- - compile_qq(allocator, sexp_rest, macro_lookup, symbol_table, runner.clone(), level-1); - quoted_null <- quote(allocator, NodePtr::NIL); - consed_subexp <- enlist(allocator, &[cons_atom, subexp, quoted_null]); - run_list <- enlist(allocator, &[cons_atom, op, consed_subexp]); - com_qq(allocator, "qq pair general".to_string(), macro_lookup, symbol_table, runner, run_list) - }; + + // (qq (a . B)) => (c (qq a) (qq B)) + let cons_atom = allocator.new_atom(op_loc, &[4])?; + let subexp = compile_qq( + allocator, + &sexp_rest, + macro_lookup, + symbol_table, + runner.clone(), + level - 1, + )?; + let quoted_null = quote(allocator, &nil_import)?; + let consed_subexp = + enlist(allocator, &[cons_atom.clone(), subexp, quoted_null])?; + let run_list = enlist(allocator, &[cons_atom, op, consed_subexp])?; + + return com_qq( + allocator, + "qq pair general".to_string(), + macro_lookup, + symbol_table, + runner, + &run_list, + ); } } // (qq (a . B)) => (c (qq a) (qq B)) - m! { - cons_atom <- allocator.new_atom(&[4]); - qq <- allocator.new_atom(&qq_atom()); - qq_l <- enlist(allocator, &[qq, op]); - qq_r <- enlist(allocator, &[qq, sexp_rest]); - compiled_l <- com_qq(allocator, "A".to_string(), macro_lookup, symbol_table, runner.clone(), qq_l); - compiled_r <- com_qq(allocator, "B".to_string(), macro_lookup, symbol_table, runner, qq_r); - enlist(allocator, &[cons_atom, compiled_l, compiled_r]) - } + let cons_atom = allocator.new_atom(loc.clone(), &[4])?; + let qq = allocator.new_atom(loc, &qq_atom())?; + let qq_l = enlist(allocator, &[qq.clone(), op])?; + let qq_r = enlist(allocator, &[qq, sexp_rest])?; + let compiled_l = com_qq( + allocator, + "A".to_string(), + macro_lookup, + symbol_table, + runner.clone(), + &qq_l, + )?; + let compiled_r = com_qq( + allocator, + "B".to_string(), + macro_lookup, + symbol_table, + runner, + &qq_r, + )?; + enlist(allocator, &[cons_atom, compiled_l, compiled_r]) } } } -pub fn compile_macros( - allocator: &mut Allocator, - _args: NodePtr, - macro_lookup: NodePtr, - _symbol_table: NodePtr, +pub fn compile_macros( + allocator: &mut A, + _args: &A::NodePtr, + macro_lookup: &A::NodePtr, + _symbol_table: &A::NodePtr, _run_program: Rc, _level: usize, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ quote(allocator, macro_lookup) } -pub fn compile_symbols( - allocator: &mut Allocator, - _args: NodePtr, - _macro_lookup: NodePtr, - symbol_table: NodePtr, +pub fn compile_symbols( + allocator: &mut A, + _args: &A::NodePtr, + _macro_lookup: &A::NodePtr, + symbol_table: &A::NodePtr, _run_program: Rc, _level: usize, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ quote(allocator, symbol_table) } // # Transform "quote" to "q" everywhere. Note that quote will not be compiled if behind qq. // # Overrides symbol table defns. -fn lower_quote_(allocator: &mut Allocator, prog: NodePtr) -> Result { - if !non_nil(allocator, prog) { - return Ok(prog); +fn lower_quote_( + allocator: &mut A, + prog: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(prog); + let exported = allocator.export(prog); + + if allocator.is_nil(prog) { + return Ok(prog.clone()); } if let Some(qlist) = proper_list(allocator, prog, true) { if qlist.is_empty() { - return Ok(prog); + return Ok(prog.clone()); } // quote_node was Atom(q) - let quote_node = qlist[0]; - if let SExp::Atom = allocator.sexp(quote_node) { + let quote_node = &qlist[0]; + if let ASExp::Atom = allocator.sexp(quote_node) { let quote_atom = allocator.atom(quote_node); if quote_atom.as_ref() == b"quote" { if qlist.len() != 2 { // quoted list should be 2: "(quote arg)" - return Err(EvalErr::InternalError(prog, format!("Compilation error while compiling [{}]. quote takes exactly one argument.", disassemble(allocator, prog, None)))); + return Err( + ClError( + loc, + EvalErr::InternalError( + exported, + format!( + "Compilation error while compiling [{}]. quote takes exactly one argument.", + allocator.disassemble(prog, None) + ) + ) + ) + ); } // Note: quote should have exactly one arg, so the length of - return m! { - lowered <- lower_quote(allocator, qlist[1]); - quote(allocator, lowered) - }; + let lowered = lower_quote(allocator, &qlist[1])?; + return quote(allocator, &lowered); } } } @@ -235,26 +313,30 @@ fn lower_quote_(allocator: &mut Allocator, prog: NodePtr) -> Result Result { +pub fn lower_quote( + allocator: &mut A, + prog: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ let res = lower_quote_(allocator, prog); if DIAG_OUTPUT { res.as_ref() .map(|x| { println!( "LOWER_QUOTE {} TO {}", - disassemble(allocator, prog, None), - disassemble(allocator, *x, None) + allocator.disassemble(prog, None), + allocator.disassemble(x, None) ); }) .unwrap_or_else(|_| ()) @@ -262,97 +344,89 @@ pub fn lower_quote(allocator: &mut Allocator, prog: NodePtr) -> Result Response { - m! { - com_atom <- allocator.new_atom("com".as_bytes()); - post_prog <- brun(allocator, macro_code, prog_rest); - - quoted_macros <- quote(allocator, macro_lookup); - quoted_symbols <- quote(allocator, symbol_table); - to_eval <- enlist( - allocator, - &[ - com_atom, - post_prog, - quoted_macros, - quoted_symbols - ] - ); - top_path <- allocator.new_atom(NodePath::new(None).as_path().data()); - evaluate( - allocator, - to_eval, - top_path - ).map(|x| { - if DIAG_OUTPUT { - print!( - "TRY_EXPAND_MACRO {} WITH {} GIVES {} MACROS {} SYMBOLS {}", - disassemble(allocator, macro_code, None), - disassemble(allocator, prog_rest, None), - disassemble(allocator, x, None), - disassemble(allocator, macro_lookup, None), - disassemble(allocator, symbol_table, None) - ); - } - Reduction(1, x) - }) - } +fn try_expand_macro_for_atom_( + allocator: &mut A, + macro_code: &A::NodePtr, + prog_rest: &A::NodePtr, + macro_lookup: &A::NodePtr, + symbol_table: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(macro_code); + let com_atom = allocator.new_atom(loc.clone(), "com".as_bytes())?; + let exported_macro = allocator.export(macro_code); + let exported_prog = allocator.export(prog_rest); + let post_prog = brun(allocator.allocator(), exported_macro, exported_prog)?; + let imported_post = allocator.import(loc.clone(), post_prog)?; + let quoted_macros = quote(allocator, macro_lookup)?; + let quoted_symbols = quote(allocator, symbol_table)?; + let to_eval = enlist( + allocator, + &[com_atom, imported_post, quoted_macros, quoted_symbols], + )?; + let top_path = allocator.new_atom(loc, NodePath::new(None).as_path().data())?; + evaluate(allocator, &to_eval, &top_path).inspect(|x| { + if DIAG_OUTPUT { + println!( + "TRY_EXPAND_MACRO {} WITH {} GIVES {} MACROS {} SYMBOLS {}", + allocator.disassemble(macro_code, None), + allocator.disassemble(prog_rest, None), + allocator.disassemble(x, None), + allocator.disassemble(macro_lookup, None), + allocator.disassemble(symbol_table, None) + ); + } + }) } -pub fn try_expand_macro_for_atom( - allocator: &mut Allocator, - macro_code: NodePtr, - prog_rest: NodePtr, - macro_lookup: NodePtr, - symbol_table: NodePtr, -) -> Response { - m! { - res <- try_expand_macro_for_atom_( - allocator, - macro_code, - prog_rest, - macro_lookup, - symbol_table - ); - Ok(res) - } +pub fn try_expand_macro_for_atom( + allocator: &mut A, + macro_code: &A::NodePtr, + prog_rest: &A::NodePtr, + macro_lookup: &A::NodePtr, + symbol_table: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + try_expand_macro_for_atom_(allocator, macro_code, prog_rest, macro_lookup, symbol_table) } -fn get_macro_program( - allocator: &Allocator, +fn get_macro_program( + allocator: &mut A, operator: &[u8], - macro_lookup: NodePtr, -) -> Result, EvalErr> { + macro_lookup: &A::NodePtr, +) -> Result, ClError> +where + A::NodePtr: Clone, +{ if let Some(mlist) = proper_list(allocator, macro_lookup, true) { for macro_pair in mlist { - match proper_list(allocator, macro_pair, true) { + match proper_list(allocator, ¯o_pair, true) { None => {} Some(mp_list) => { if mp_list.is_empty() { continue; } let value = if mp_list.len() > 1 { - mp_list[1] + mp_list[1].clone() } else { - NodePtr::NIL + let loc = allocator.loc(macro_lookup); + allocator.import(loc, NodePtr::NIL)? }; - match allocator.sexp(mp_list[0]) { - SExp::Atom => { + match allocator.sexp(&mp_list[0]) { + ASExp::Atom => { // was macro_name, but it's singular and probably // not useful to rename. - let atom = allocator.atom(mp_list[0]); + let atom = allocator.atom(&mp_list[0]); if atom.as_ref() == operator { return Ok(Some(value)); } } - SExp::Pair(_, _) => { + ASExp::Pair(_, _) => { continue; } } @@ -364,40 +438,48 @@ fn get_macro_program( Ok(None) } -fn transform_program_atom( - allocator: &mut Allocator, - prog: NodePtr, +fn transform_program_atom( + allocator: &mut A, + prog: &A::NodePtr, a: &[u8], - symbol_table: NodePtr, -) -> Response { - if a == b"@" { - return allocator - .new_atom(NodePath::new(None).as_path().data()) - .map(|x| Reduction(1, x)); + symbol_table: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(prog); + if a == b"@" || a == b"@*env*" { + return allocator.new_atom(loc, NodePath::new(None).as_path().data()); } + match proper_list(allocator, symbol_table, true) { None => {} Some(symlist) => { + let nil_import = allocator.import(loc.clone(), NodePtr::NIL)?; for sym in symlist { - match proper_list(allocator, sym, true) { + match proper_list(allocator, &sym, true) { None => {} Some(v) => { if v.is_empty() { continue; } - let value = if v.len() > 1 { v[1] } else { NodePtr::NIL }; + let value = if v.len() > 1 { + v[1].clone() + } else { + nil_import.clone() + }; - match allocator.sexp(v[0]) { - SExp::Atom => { + match allocator.sexp(&v[0]) { + ASExp::Atom => { // v[0] is close by, and probably not useful to // rename here. - let atom = allocator.atom(v[0]); + let atom = allocator.atom(&v[0]); if atom.as_ref() == a { - return Ok(Reduction(1, value)); + return Ok(value); } } - SExp::Pair(_, _) => {} + ASExp::Pair(_, _) => {} } } } @@ -405,85 +487,91 @@ fn transform_program_atom( } } - quote(allocator, prog).map(|x| Reduction(1, x)) + quote(allocator, prog) } -fn compile_operator_atom( - allocator: &mut Allocator, - prog: NodePtr, +fn compile_operator_atom( + allocator: &mut A, + prog: &A::NodePtr, avec: &[u8], - macro_lookup: NodePtr, - symbol_table: NodePtr, + macro_lookup: &A::NodePtr, + symbol_table: &A::NodePtr, run_program: Rc, -) -> Result, EvalErr> { +) -> Result, ClError> +where + A::NodePtr: Clone, +{ let compile_bindings = compile_bindings(); if *avec == vec![1] { - return Ok(Some(prog)); + return Ok(Some(prog.clone())); } if let Some(f) = compile_bindings.get(avec) { - return m! { - prog_rest <- rest(allocator, prog); - post_prog <- - (f.to_run)( - allocator, - prog_rest, - macro_lookup, - symbol_table, - run_program.clone(), - 1 - ); - quoted_post_prog <- quote(allocator, post_prog); - top_atom <- - allocator.new_atom(NodePath::new(None).as_path().data()); - - let _ = if DIAG_OUTPUT { - print!("COMPILE_BINDINGS {}", disassemble(allocator, quoted_post_prog, None)); - }; - evaluate(allocator, quoted_post_prog, top_atom).map(Some) + let prog_rest = rest(allocator, prog)?; + let post_prog = (f.to_run)( + allocator, + &prog_rest, + macro_lookup, + symbol_table, + run_program.clone(), + 1, + )?; + let quoted_post_prog = quote(allocator, &post_prog)?; + let loc = allocator.loc(prog); + let top_atom = allocator.new_atom(loc, NodePath::new(None).as_path().data())?; + if DIAG_OUTPUT { + print!( + "COMPILE_BINDINGS {}", + allocator.disassemble("ed_post_prog, None) + ); }; + return evaluate(allocator, "ed_post_prog, &top_atom).map(Some); } Ok(None) } -enum SymbolResult { - Direct(NodePtr), - Matched(NodePtr, NodePtr), +enum SymbolResult { + Direct(A::NodePtr), + Matched(A::NodePtr, A::NodePtr), } -fn find_symbol_match( - allocator: &Allocator, +fn find_symbol_match( + allocator: &mut A, opname: &[u8], - r: NodePtr, - symbol_table: NodePtr, -) -> Result, EvalErr> { + r: &A::NodePtr, + symbol_table: &A::NodePtr, +) -> Result>, ClError> +where + A::NodePtr: Clone, +{ if let Some(symlist) = proper_list(allocator, symbol_table, true) { for sym in symlist { - if let Some(symdef) = proper_list(allocator, sym, true) { + if let Some(symdef) = proper_list(allocator, &sym, true) { if symdef.is_empty() { continue; } - match allocator.sexp(symdef[0]) { - SExp::Atom => { - let symbol = symdef[0]; + match allocator.sexp(&symdef[0]) { + ASExp::Atom => { + let symbol = symdef[0].clone(); let value = if symdef.len() == 1 { - NodePtr::NIL + let loc = allocator.loc(r); + allocator.import(loc, NodePtr::NIL)? } else { - symdef[1] + symdef[1].clone() }; - let symbuf = allocator.atom(symdef[0]); + let symbuf = allocator.atom(&symdef[0]); if b"*" == symbuf.as_ref() { - return Ok(Some(SymbolResult::Direct(r))); + return Ok(Some(SymbolResult::Direct(r.clone()))); } else if opname == symbuf.as_ref() { return Ok(Some(SymbolResult::Matched(symbol, value))); } } - SExp::Pair(_, _) => {} + ASExp::Pair(_, _) => {} } } } @@ -492,94 +580,175 @@ fn find_symbol_match( Ok(None) } +pub type SplitRestResult = Option<( + Vec<::NodePtr>, + Option<::NodePtr>, +)>; + +fn split_rest_tail( + allocator: &A, + args: &A::NodePtr, +) -> Result, ClError> +where + A::NodePtr: Clone, +{ + let Some(mut args) = proper_list(allocator, args, true).map(|args| args.to_vec()) else { + return Ok(None); + }; + + let rest_index = args.iter().position(|arg| match allocator.sexp(arg) { + ASExp::Atom => allocator.atom(arg).as_ref() == b"&rest", + ASExp::Pair(_, _) => false, + }); + + let Some(rest_index) = rest_index else { + return Ok(Some((args, None))); + }; + + if rest_index + 2 != args.len() { + return Err(ClError( + allocator.loc(&args[rest_index]), + EvalErr::InternalError( + allocator.export(&args[rest_index]), + "&rest must be followed by exactly one tail expression".to_string(), + ), + )); + } + + let tail = args.pop(); + args.pop(); + Ok(Some((args, tail))) +} + +fn enlist_with_tail( + allocator: &mut A, + args: &[A::NodePtr], + tail: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + let mut result = tail.clone(); + for arg in args.iter().rev() { + result = allocator.new_pair(allocator.loc(arg), arg, &result)?; + } + Ok(result) +} + +fn rest_argument_source( + allocator: &mut A, + args: &[A::NodePtr], + tail: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + let mut result = tail.clone(); + for arg in args.iter().rev() { + let loc = allocator.loc(arg); + let cons = allocator.new_atom(loc.clone(), b"c")?; + result = enlist(allocator, &[cons, arg.clone(), result])?; + } + Ok(result) +} + #[allow(clippy::too_many_arguments)] -fn compile_application( - allocator: &mut Allocator, - prog: NodePtr, - operator: NodePtr, +fn compile_application( + allocator: &mut A, + prog: &A::NodePtr, + operator: &A::NodePtr, opbuf: &[u8], - rest: NodePtr, - macro_lookup: NodePtr, - symbol_table: NodePtr, + rest: &A::NodePtr, + macro_lookup: &A::NodePtr, + symbol_table: &A::NodePtr, run_program: Rc, -) -> Result { - let mut compiled_args = vec![operator]; - - let error_result = Err(EvalErr::InternalError( - prog, - format!( - "can't compile {}, unknown operator", - disassemble(allocator, prog, None) +) -> Result +where + A::NodePtr: Clone, +{ + let mut compiled_args = vec![operator.clone()]; + + let loc = allocator.loc(prog); + let exported_prog = allocator.export(prog); + let error_result = Err(ClError( + loc, + EvalErr::InternalError( + exported_prog, + format!( + "can't compile {}, unknown operator", + allocator.disassemble(prog, None) + ), ), )); if *opbuf == vec![1] || *opbuf == vec![b'q'] { - return allocator.new_pair(operator, rest); + let rest_loc = allocator.loc(rest); + return allocator.new_pair(rest_loc, operator, rest); } - match proper_list(allocator, rest, true) { - Some(prog_args) => { + match split_rest_tail(allocator, rest)? { + Some((prog_args, tail_arg)) => { let mut new_args = map_m(allocator, &mut prog_args.iter(), &|allocator, arg| { do_com_prog( allocator, 544, - *arg, + arg, macro_lookup, symbol_table, run_program.clone(), ) - .map(|x| x.1) })?; + let compiled_tail = match &tail_arg { + Some(tail) => do_com_prog( + allocator, + 544, + tail, + macro_lookup, + symbol_table, + run_program.clone(), + )?, + None => allocator.import(allocator.loc(rest), NodePtr::NIL)?, + }; compiled_args.append(&mut new_args); - let r = enlist(allocator, &compiled_args)?; + let r = enlist_with_tail(allocator, &compiled_args, &compiled_tail)?; if PASS_THROUGH_OPERATORS.contains(opbuf) || (!opbuf.is_empty() && opbuf[0] == b'_') { Ok(r) } else { - find_symbol_match( - allocator, - opbuf, - r, - symbol_table - ).and_then(|x| match x { - Some(SymbolResult::Direct(v)) => { Ok(v) }, - Some(SymbolResult::Matched(_symbol,value)) => { - match proper_list(allocator, rest, true) { - Some(proglist) => { - m! { - apply_atom <- allocator.new_atom(&[2]); - list_atom <- allocator.new_atom("list".as_bytes()); - cons_atom <- allocator.new_atom(&[4]); - com_atom <- allocator.new_atom("com".as_bytes()); - opt_atom <- allocator.new_atom("opt".as_bytes()); - top_atom <- allocator.new_atom(NodePath::new(None).as_path().data()); - left_atom <- allocator.new_atom(NodePath::new(None).first().as_path().data()); - - enlisted <- enlist(allocator, &proglist); - list_application <- allocator.new_pair(list_atom, enlisted); - - quoted_list <- quote(allocator, list_application); - quoted_macros <- quote(allocator, macro_lookup); - quoted_symbols <- quote(allocator, symbol_table); - compiled <- enlist(allocator, &[com_atom, quoted_list, quoted_macros, quoted_symbols]); - to_run <- enlist(allocator, &[opt_atom, compiled]); - new_args <- evaluate(allocator, to_run, top_atom); - - cons_enlisted <- enlist(allocator, &[cons_atom, left_atom, new_args]); - - result <- enlist( - allocator, - &[apply_atom, value, cons_enlisted] - ); - - Ok(result) - } - }, - None => { error_result } - } - }, - None => { error_result } + find_symbol_match(allocator, opbuf, &r, symbol_table).and_then(|x| match x { + Some(SymbolResult::Direct(v)) => Ok(v), + Some(SymbolResult::Matched(_symbol, value)) => { + let loc = allocator.loc(&value); + let apply_atom = allocator.new_atom(loc.clone(), &[2])?; + let list_atom = allocator.new_atom(loc.clone(), "list".as_bytes())?; + let cons_atom = allocator.new_atom(loc.clone(), &[4])?; + let com_atom = allocator.new_atom(loc.clone(), "com".as_bytes())?; + let opt_atom = allocator.new_atom(loc.clone(), "opt".as_bytes())?; + let top_atom = allocator + .new_atom(loc.clone(), NodePath::new(None).as_path().data())?; + let left_atom = allocator + .new_atom(loc.clone(), NodePath::new(None).first().as_path().data())?; + let argument_source = match &tail_arg { + Some(tail) => rest_argument_source(allocator, &prog_args, tail)?, + None => { + let enlisted = enlist(allocator, &prog_args)?; + allocator.new_pair(loc, &list_atom, &enlisted)? + } + }; + let quoted_list = quote(allocator, &argument_source)?; + let quoted_macros = quote(allocator, macro_lookup)?; + let quoted_symbols = quote(allocator, symbol_table)?; + let compiled = enlist( + allocator, + &[com_atom, quoted_list, quoted_macros, quoted_symbols], + )?; + let to_run = enlist(allocator, &[opt_atom, compiled])?; + let new_args = evaluate(allocator, &to_run, &top_atom)?; + let cons_enlisted = enlist(allocator, &[cons_atom, left_atom, new_args])?; + enlist(allocator, &[apply_atom, value, cons_enlisted]) + } + None => error_result, }) } } @@ -587,44 +756,50 @@ fn compile_application( } } -pub fn do_com_prog( - allocator: &mut Allocator, +pub fn do_com_prog( + allocator: &mut A, from: usize, - prog: NodePtr, - macro_lookup: NodePtr, - symbol_table: NodePtr, + prog: &A::NodePtr, + macro_lookup: &A::NodePtr, + symbol_table: &A::NodePtr, run_program: Rc, -) -> Response { +) -> Result +where + A::NodePtr: Clone, +{ if DIAG_OUTPUT { println!( - "START COMPILE {}: {} MACRO {} SYMBOLS {}", + "START COMPILE {}: {}\nMACRO {}\nSYMBOLS {}", from, - disassemble(allocator, prog, None), - disassemble(allocator, macro_lookup, None), - disassemble(allocator, symbol_table, None), + allocator.disassemble(prog, None), + allocator.disassemble(macro_lookup, None), + allocator.disassemble(symbol_table, None), ); } do_com_prog_(allocator, prog, macro_lookup, symbol_table, run_program).inspect(|x| { if DIAG_OUTPUT { println!( - "DO_COM_PROG {}: {} MACRO {} SYMBOLS {} RESULT {}", + "DO_COM_PROG {}: {}\nMACRO {}\nSYMBOLS {}\nRESULT {}", from, - disassemble(allocator, prog, None), - disassemble(allocator, macro_lookup, None), - disassemble(allocator, symbol_table, None), - disassemble(allocator, x.1, None) + allocator.disassemble(prog, None), + allocator.disassemble(macro_lookup, None), + allocator.disassemble(symbol_table, None), + allocator.disassemble(x, None) ); } }) } -fn do_com_prog_( - allocator: &mut Allocator, - prog_: NodePtr, - macro_lookup: NodePtr, - symbol_table: NodePtr, +fn do_com_prog_( + allocator: &mut A, + prog_: &A::NodePtr, + macro_lookup: &A::NodePtr, + symbol_table: &A::NodePtr, run_program: Rc, -) -> Response { +) -> Result +where + A::NodePtr: Clone, +{ /* * Turn the given program `prog` into a clvm program using * the macros to do transformation. @@ -641,32 +816,32 @@ fn do_com_prog_( prog <- lower_quote(allocator, prog_); // quote atoms - match allocator.sexp(prog) { - SExp::Atom => { + match allocator.sexp(&prog) { + ASExp::Atom => { // Note: can't co-borrow with allocator below. - let prog_atom = allocator.atom(prog); + let prog_atom = allocator.atom(&prog); transform_program_atom( allocator, - prog, + &prog, // This is a false positive due to Allocator lifetime. #[allow(clippy::unnecessary_to_owned)] &prog_atom.as_ref().to_vec(), symbol_table ) }, - SExp::Pair(operator,prog_rest) => { - match allocator.sexp(operator) { - SExp::Atom => { + ASExp::Pair(operator,prog_rest) => { + match allocator.sexp(&operator) { + ASExp::Atom => { // Note: can't co-borrow with allocator below. - let op_atom = allocator.atom(operator); + let op_atom = allocator.atom(&operator); let op_buf = op_atom.as_ref().to_vec(); - get_macro_program(allocator, op_atom.as_ref(), macro_lookup). + get_macro_program(allocator, &op_buf, macro_lookup). and_then(|x| match x { Some(value) => { try_expand_macro_for_atom( allocator, - value, - prog_rest, + &value, + &prog_rest, macro_lookup, symbol_table ) @@ -674,7 +849,7 @@ fn do_com_prog_( None => { compile_operator_atom( allocator, - prog, + &prog, &op_buf, macro_lookup, symbol_table, @@ -682,40 +857,35 @@ fn do_com_prog_( ).and_then(|x| x.map(Ok).unwrap_or_else(|| m! { compile_application( allocator, - prog, - operator, + &prog, + &operator, &op_buf, - prog_rest, + &prog_rest, macro_lookup, symbol_table, run_program.clone() ) - })).map(|x| Reduction(1, x)) + })) } }) }, _ => { // (com ((OP) . RIGHT)) => (a (com (q OP)) 1) - m! { - com_atom <- allocator.new_atom("com".as_bytes()); - quoted_op <- quote(allocator, operator); - quoted_macro_lookup <- - quote(allocator, macro_lookup); - quoted_symbol_table <- - quote(allocator, symbol_table); - top_atom <- allocator.new_atom(NodePath::new(None).as_path().data()); - eval_list <- enlist(allocator, &[ - com_atom, - quoted_op, - quoted_macro_lookup, - quoted_symbol_table - ]); - - evaluate( - allocator, eval_list, top_atom - ).and_then(|x| enlist(allocator, &[x])). - map(|x| Reduction(1, x)) - } + let loc = allocator.loc(&operator); + let com_atom = allocator.new_atom(loc.clone(), "com".as_bytes())?; + let quoted_op = quote(allocator, &operator)?; + let quoted_macro_lookup = quote(allocator, macro_lookup)?; + let quoted_symbol_table = quote(allocator, symbol_table)?; + let top_atom = allocator.new_atom(loc, NodePath::new(None).as_path().data())?; + let eval_list = enlist(allocator, &[ + com_atom, + quoted_op, + quoted_macro_lookup, + quoted_symbol_table + ])?; + evaluate( + allocator, &eval_list, &top_atom + ).and_then(|x| enlist(allocator, &[x])) } } } @@ -723,27 +893,32 @@ fn do_com_prog_( } } -pub fn do_com_prog_for_dialect( +pub fn do_com_prog_for_dialect( runner: Rc, - allocator: &mut Allocator, - sexp: NodePtr, -) -> Response { + allocator: &mut A, + sexp: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ match allocator.sexp(sexp) { - SExp::Pair(prog, extras) => { - let mut symbol_table = NodePtr::NIL; + ASExp::Pair(prog, extras) => { + let loc = allocator.loc(sexp); + let imported_nil = allocator.import(loc, NodePtr::NIL)?; + let mut symbol_table = imported_nil; let macro_lookup; let mut elist = Vec::new(); - if let Some(elist_vec) = proper_list(allocator, extras, true) { + if let Some(elist_vec) = proper_list(allocator, &extras, true) { elist = elist_vec.to_vec(); } if elist.is_empty() { macro_lookup = default_macro_lookup(allocator, runner.clone()); } else { - macro_lookup = elist[0]; + macro_lookup = elist[0].clone(); if elist.len() > 1 { - symbol_table = elist[1]; + symbol_table = elist[1].clone(); } } @@ -754,9 +929,9 @@ pub fn do_com_prog_for_dialect( do_com_prog( allocator, 773, - prog, - macro_lookup, - symbol_table, + &prog, + ¯o_lookup, + &symbol_table, runner.clone(), ) //.map(|x| { @@ -770,10 +945,16 @@ pub fn do_com_prog_for_dialect( // x //}) } - _ => Err(EvalErr::InternalError( - sexp, - "Program is not a pair in do_com_prog".to_string(), - )), + _ => { + let exported = allocator.export(sexp); + Err(ClError( + allocator.loc(sexp), + EvalErr::InternalError( + exported, + "Program is not a pair in do_com_prog".to_string(), + ), + )) + } } } @@ -804,18 +985,24 @@ pub fn get_compile_filename( )) } -pub fn get_search_paths( +pub fn get_search_paths( runner: Rc, - allocator: &mut Allocator, -) -> Result, EvalErr> { - let search_paths_prog = assemble(allocator, "(_get_include_paths)")?; - let search_path_result = - runner.run_program(allocator, search_paths_prog, NodePtr::NIL, None)?; - + loc: Srcloc, + allocator: &mut A, +) -> Result, ClError> +where + A::NodePtr: Clone, +{ + let search_paths_result = ((|| { + let search_paths_prog = assemble(allocator.allocator(), "(_get_include_paths)")?; + runner.run_program(allocator.allocator(), search_paths_prog, NodePtr::NIL, None) + })()) + .map_err(|e| ClError(loc.clone(), e))?; let mut res = Vec::new(); - if let Some(l) = proper_list(allocator, search_path_result.1, true) { - for elt in l.iter().copied() { - if let SExp::Atom = allocator.sexp(elt) { + let search_paths_result_import = allocator.import(loc, search_paths_result.1)?; + if let Some(l) = proper_list(allocator, &search_paths_result_import, true) { + for elt in l.iter() { + if let ASExp::Atom = allocator.sexp(elt) { // Only elt in scope. let atom = allocator.atom(elt); res.push(Bytes::new(Some(BytesFromType::Raw(atom.as_ref().to_vec()))).decode()); diff --git a/src/classic/clvm_tools/stages/stage_2/defaults.rs b/src/classic/clvm_tools/stages/stage_2/defaults.rs index 96a395066..c908fdedb 100644 --- a/src/classic/clvm_tools/stages/stage_2/defaults.rs +++ b/src/classic/clvm_tools/stages/stage_2/defaults.rs @@ -1,9 +1,11 @@ use std::rc::Rc; -use clvm_rs::allocator::{Allocator, NodePtr}; +use clvm_rs::allocator::NodePtr; use crate::classic::clvm_tools::binutils::assemble; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; +use crate::classic::clvm_tools::stages::stage_2::abstraction::ClassicAllocator; +use crate::compiler::srcloc::Srcloc; /* "function" is used in front of a constant uncompiled @@ -100,25 +102,46 @@ fn default_macros_src() -> Vec<&'static str> { ] } -fn build_default_macro_lookup( - allocator: &mut Allocator, +fn build_default_macro_lookup( + allocator: &mut A, eval_f: Rc, macros_src: &[String], -) -> NodePtr { - let run = assemble(allocator, "(a (com 2 3) 1)").unwrap(); - let mut default_macro_lookup: NodePtr = NodePtr::NIL; +) -> A::NodePtr { + let run = assemble(allocator.allocator(), "(a (com 2 3) 1)").unwrap(); + let macro_loc = Srcloc::start("*macros*"); + let imported_nil = allocator.import(macro_loc.clone(), NodePtr::NIL).unwrap(); + let mut default_macro_lookup = imported_nil; for macro_src in macros_src { - let macro_sexp = assemble(allocator, macro_src).unwrap(); + let macro_sexp = assemble(allocator.allocator(), macro_src).unwrap(); + let imported_macro_sexp = allocator.import(macro_loc.clone(), macro_sexp).unwrap(); let env = allocator - .new_pair(macro_sexp, default_macro_lookup) + .new_pair( + macro_loc.clone(), + &imported_macro_sexp, + &default_macro_lookup, + ) + .unwrap(); + let exported_env = allocator.export(&env); + let new_macro = eval_f + .run_program(allocator.allocator(), run, exported_env, None) + .unwrap() + .1; + let imported_new_macro = allocator.import(macro_loc.clone(), new_macro).unwrap(); + default_macro_lookup = allocator + .new_pair( + macro_loc.clone(), + &imported_new_macro, + &default_macro_lookup, + ) .unwrap(); - let new_macro = eval_f.run_program(allocator, run, env, None).unwrap().1; - default_macro_lookup = allocator.new_pair(new_macro, default_macro_lookup).unwrap(); } default_macro_lookup } -pub fn default_macro_lookup(allocator: &mut Allocator, runner: Rc) -> NodePtr { +pub fn default_macro_lookup( + allocator: &mut A, + runner: Rc, +) -> A::NodePtr { let macro_srcs: Vec = default_macros_src().iter().map(|s| s.to_string()).collect(); build_default_macro_lookup(allocator, runner.clone(), ¯o_srcs) } diff --git a/src/classic/clvm_tools/stages/stage_2/helpers.rs b/src/classic/clvm_tools/stages/stage_2/helpers.rs index 79b4638e0..830830770 100644 --- a/src/classic/clvm_tools/stages/stage_2/helpers.rs +++ b/src/classic/clvm_tools/stages/stage_2/helpers.rs @@ -1,8 +1,8 @@ use clvm_rs::allocator::{Allocator, NodePtr}; -use clvm_rs::error::EvalErr; use crate::classic::clvm::sexp::enlist; use crate::classic::clvm_tools::node_path::NodePath; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ClError, ClassicAllocator}; lazy_static! { pub static ref QUOTE_ATOM: Vec = vec![1]; @@ -10,30 +10,38 @@ lazy_static! { pub static ref COM_ATOM: Vec = vec![b'c', b'o', b'm']; } -pub fn quote(allocator: &mut Allocator, sexp: NodePtr) -> Result { +pub fn quote( + allocator: &mut A, + sexp: &A::NodePtr, +) -> Result { allocator - .new_atom("E_ATOM) - .and_then(|q| allocator.new_pair(q, sexp)) + .new_atom(allocator.loc(sexp), "E_ATOM) + .and_then(|q| allocator.new_pair(allocator.loc(sexp), &q, sexp)) } // In original python code, the name of this function is `eval`, // but since the name `eval` cannot be used in typescript context, change the name to `evaluate`. -pub fn evaluate( - allocator: &mut Allocator, - prog: NodePtr, - args: NodePtr, -) -> Result { - m! { - a <- allocator.new_atom(&APPLY_ATOM); - enlist(allocator, &[a, prog, args]) - } +pub fn evaluate( + allocator: &mut A, + prog: &A::NodePtr, + args: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(prog); + let a = allocator.new_atom(loc, &APPLY_ATOM)?; + enlist(allocator, &[a, prog.clone(), args.clone()]) } -pub fn run( - allocator: &mut Allocator, - prog: NodePtr, - macro_lookup: NodePtr, -) -> Result { +pub fn run( + allocator: &mut A, + prog: &A::NodePtr, + macro_lookup: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ /* * PROG => (e (com (q . PROG) (mac)) ARGS) * @@ -41,19 +49,16 @@ pub fn run( * function. */ let args = NodePath::new(None).as_path(); - m! { - mac <- quote(allocator, macro_lookup); - com_sexp <- allocator.new_atom(&COM_ATOM); - arg_sexp <- allocator.new_atom(args.data()); - to_eval <- enlist(allocator, &[com_sexp, prog, mac]); - evaluate(allocator, to_eval, arg_sexp) - } + let loc = allocator.loc(prog); + let mac = quote(allocator, macro_lookup)?; + let com_sexp = allocator.new_atom(loc.clone(), &COM_ATOM)?; + let arg_sexp = allocator.new_atom(loc, args.data())?; + let to_eval = enlist(allocator, &[com_sexp, prog.clone(), mac])?; + evaluate(allocator, &to_eval, &arg_sexp) } -pub fn brun(allocator: &mut Allocator, prog: NodePtr, args: NodePtr) -> Result { - m! { - quoted_prog <- quote(allocator, prog); - quoted_args <- quote(allocator, args); - evaluate(allocator, quoted_prog, quoted_args) - } +pub fn brun(allocator: &mut Allocator, prog: NodePtr, args: NodePtr) -> Result { + let quoted_prog = quote(allocator, &prog)?; + let quoted_args = quote(allocator, &args)?; + evaluate(allocator, "ed_prog, "ed_args) } diff --git a/src/classic/clvm_tools/stages/stage_2/inline.rs b/src/classic/clvm_tools/stages/stage_2/inline.rs index dca663fd6..40aa45edb 100644 --- a/src/classic/clvm_tools/stages/stage_2/inline.rs +++ b/src/classic/clvm_tools/stages/stage_2/inline.rs @@ -1,27 +1,32 @@ use crate::classic::clvm::__type_compatibility__::{bi_one, bi_zero}; use crate::classic::clvm::sexp::{enlist, proper_list}; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ + ASExp, BufCarrier, ClError, ClassicAllocator, +}; use crate::compiler::gensym::gensym; + use crate::util::Number; -use clvm_rs::allocator::{Allocator, NodePtr, SExp}; -use clvm_rs::error::EvalErr; use num_bigint::ToBigInt; use std::collections::HashMap; // If this is an at capture of the form // (@ name substructure) // then return name and substructure. -pub fn is_at_capture( - allocator: &Allocator, - tree_first: NodePtr, - tree_rest: NodePtr, -) -> Option<(NodePtr, NodePtr)> { - if let (SExp::Atom, Some(spec)) = ( +pub fn is_at_capture( + allocator: &A, + tree_first: &A::NodePtr, + tree_rest: &A::NodePtr, +) -> Option<(A::NodePtr, A::NodePtr)> +where + A::NodePtr: Clone, +{ + if let (ASExp::Atom, Some(spec)) = ( allocator.sexp(tree_first), proper_list(allocator, tree_rest, true), ) { let first_atom = allocator.atom(tree_first); if first_atom.as_ref() == b"@" && spec.len() == 2 { - return Some((spec[0], spec[1])); + return Some((spec[0].clone(), spec[1].clone())); } } @@ -29,40 +34,56 @@ pub fn is_at_capture( } // (unquote X) -fn wrap_in_unquote(allocator: &mut Allocator, code: NodePtr) -> Result { - let unquote_atom = allocator.new_atom("unquote".as_bytes())?; - enlist(allocator, &[unquote_atom, code]) +fn wrap_in_unquote( + allocator: &mut A, + code: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(code); + let unquote_atom = allocator.new_atom(loc, "unquote".as_bytes())?; + enlist(allocator, &[unquote_atom, code.clone()]) } // (__chia__enlist X) -fn wrap_in_compile_time_list(allocator: &mut Allocator, code: NodePtr) -> Result { - let chia_enlist_atom = allocator.new_atom("__chia__enlist".as_bytes())?; - enlist(allocator, &[chia_enlist_atom, code]) +fn wrap_in_compile_time_list( + allocator: &mut A, + code: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(code); + let chia_enlist_atom = allocator.new_atom(loc, "__chia__enlist".as_bytes())?; + enlist(allocator, &[chia_enlist_atom, code.clone()]) } // Create the sequence of individual tree moves that will translate to // (f ...) and (r ...) wrapping to select the given path from a larger structure. -fn create_path_selection_plan(path: Number, operators: &mut Vec) -> Result<(), EvalErr> { - if path <= bi_one() { - Ok(()) - } else { +fn create_path_selection_plan(path: Number, operators: &mut Vec) { + if path > bi_one() { operators.push(path.clone() % 2_u32.to_bigint().unwrap() == bi_one()); create_path_selection_plan(path / 2_u32.to_bigint().unwrap(), operators) } } // Given a path and code to be wrapped, generate a lookup by path into that code. -fn wrap_path_selection( - allocator: &mut Allocator, +fn wrap_path_selection( + allocator: &mut A, path: Number, - wrapped: NodePtr, -) -> Result { + wrapped: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ let mut operator_stack = Vec::new(); - let mut tail = wrapped; - create_path_selection_plan(path, &mut operator_stack)?; + let mut tail = wrapped.clone(); + create_path_selection_plan(path, &mut operator_stack); for o in operator_stack.iter() { let head_op = if *o { vec![6] } else { vec![5] }; - let head_atom = allocator.new_atom(&head_op)?; + let loc = allocator.loc(wrapped); + let head_atom = allocator.new_atom(loc, &head_op)?; tail = enlist(allocator, &[head_atom, tail])?; } Ok(tail) @@ -77,71 +98,76 @@ fn wrap_path_selection( // as the classic macro system handles destructuring on the source text rather // than the argument values, so we must eliminate all deep references past the // top of the argument list. -fn formulate_path_selections_for_destructuring_arg( - allocator: &mut Allocator, - arg_sexp: NodePtr, +fn formulate_path_selections_for_destructuring_arg( + allocator: &mut A, + arg_sexp: &A::NodePtr, arg_path: Number, arg_depth: Number, - referenced_from: Option, - selections: &mut HashMap, NodePtr>, -) -> Result { + referenced_from: Option, + selections: &mut HashMap, A::NodePtr>, +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(arg_sexp); match allocator.sexp(arg_sexp) { - SExp::Pair(a, b) => { + ASExp::Pair(a, b) => { let next_depth = arg_depth.clone() * 2_u32.to_bigint().unwrap(); - if let Some((capture, substructure)) = is_at_capture(allocator, a, b) { - if let SExp::Atom = allocator.sexp(capture) { - let (new_arg_path, new_arg_depth, tail) = - if let Some(prev_ref) = referenced_from { - (arg_path, arg_depth, prev_ref) - } else { - let capture_code = wrap_in_unquote(allocator, capture)?; - let qtail = - wrap_path_selection(allocator, arg_path + arg_depth, capture_code)?; - (bi_zero(), bi_one(), qtail) - }; + if let Some((capture, substructure)) = is_at_capture(allocator, &a, &b) { + if let ASExp::Atom = allocator.sexp(&capture) { + let (new_arg_path, new_arg_depth, tail) = if let Some(prev_ref) = + referenced_from + { + (arg_path, arg_depth, prev_ref) + } else { + let capture_code = wrap_in_unquote(allocator, &capture)?; + let qtail = + wrap_path_selection(allocator, arg_path + arg_depth, &capture_code)?; + (bi_zero(), bi_one(), qtail) + }; // Was cbuf from capture. - let capture_atom = allocator.atom(capture); - selections.insert(capture_atom.as_ref().to_vec(), tail); + let capture_atom = allocator.atom(&capture); + selections.insert(capture_atom.as_ref().to_vec(), tail.clone()); return formulate_path_selections_for_destructuring_arg( allocator, - substructure, + &substructure, new_arg_path, new_arg_depth, Some(tail), selections, ) - .map(|_| arg_sexp); + .map(|_| arg_sexp.clone()); } } if referenced_from.is_some() { let f = formulate_path_selections_for_destructuring_arg( allocator, - a, + &a, arg_path.clone(), next_depth.clone(), - referenced_from, + referenced_from.clone(), selections, )?; let r = formulate_path_selections_for_destructuring_arg( allocator, - b, + &b, arg_depth + arg_path, next_depth, referenced_from, selections, )?; - allocator.new_pair(f, r) + allocator.new_pair(loc, &f, &r) } else { let ref_name = gensym("destructuring_capture".as_bytes().to_vec()); - let at_atom = allocator.new_atom("@".as_bytes())?; - let name_atom = allocator.new_atom(&ref_name)?; - let new_arg_list = enlist(allocator, &[at_atom, name_atom, arg_sexp])?; + let at_atom = allocator.new_atom(loc.clone(), "@".as_bytes())?; + let name_atom = allocator.new_atom(loc, &ref_name)?; + let new_arg_list = enlist(allocator, &[at_atom, name_atom, arg_sexp.clone()])?; formulate_path_selections_for_destructuring_arg( allocator, - new_arg_list, + &new_arg_list, bi_zero(), bi_one(), None, @@ -149,18 +175,18 @@ fn formulate_path_selections_for_destructuring_arg( ) } } - SExp::Atom => { + ASExp::Atom => { // Note: can't co-borrow with allocator below. let buf_atom = allocator.atom(arg_sexp); let buf = buf_atom.as_ref().to_vec(); if !buf.is_empty() { if let Some(capture) = referenced_from { - let tail = wrap_path_selection(allocator, arg_path + arg_depth, capture)?; + let tail = wrap_path_selection(allocator, arg_path + arg_depth, &capture)?; selections.insert(buf, tail); - return Ok(arg_sexp); + return Ok(arg_sexp.clone()); } } - Ok(arg_sexp) + Ok(arg_sexp.clone()) } } } @@ -221,22 +247,25 @@ fn formulate_path_selections_for_destructuring_arg( // environment but that destructures just its first argument as a list, // so i adapted list into __chia__enlist. // When so wrapped, the user may then destructure the capture argument. -pub fn formulate_path_selections_for_destructuring( - allocator: &mut Allocator, - args_sexp: NodePtr, - selections: &mut HashMap, NodePtr>, -) -> Result { - if let SExp::Pair(a, b) = allocator.sexp(args_sexp) { - if let Some((capture, substructure)) = is_at_capture(allocator, a, b) { - if let SExp::Atom = allocator.sexp(capture) { - let quoted_arg_list = wrap_in_unquote(allocator, capture)?; - let tail = wrap_in_compile_time_list(allocator, quoted_arg_list)?; +pub fn formulate_path_selections_for_destructuring( + allocator: &mut A, + args_sexp: &A::NodePtr, + selections: &mut HashMap, A::NodePtr>, +) -> Result +where + A::NodePtr: Clone, +{ + if let ASExp::Pair(a, b) = allocator.sexp(args_sexp) { + if let Some((capture, substructure)) = is_at_capture(allocator, &a, &b) { + if let ASExp::Atom = allocator.sexp(&capture) { + let quoted_arg_list = wrap_in_unquote(allocator, &capture)?; + let tail = wrap_in_compile_time_list(allocator, "ed_arg_list)?; // Was: cbuf from capture. - let buf_atom = allocator.atom(capture); - selections.insert(buf_atom.as_ref().to_vec(), tail); + let buf_atom = allocator.atom(&capture); + selections.insert(buf_atom.as_ref().to_vec(), tail.clone()); let newsub = formulate_path_selections_for_destructuring_arg( allocator, - substructure, + &substructure, bi_zero(), bi_one(), Some(tail), @@ -247,29 +276,33 @@ pub fn formulate_path_selections_for_destructuring( } let f = formulate_path_selections_for_destructuring_arg( allocator, - a, + &a, bi_zero(), bi_one(), None, selections, )?; - let r = formulate_path_selections_for_destructuring(allocator, b, selections)?; - allocator.new_pair(f, r) + let r = formulate_path_selections_for_destructuring(allocator, &b, selections)?; + let loc = allocator.loc(&b); + allocator.new_pair(loc, &f, &r) } else { - Ok(args_sexp) + Ok(args_sexp.clone()) } } // If true, these arguments represent a destructuring of some kind. // In the case of inlines in classic chialisp, we must adjust how arguments // are passed down to the macro body that gets created for the inline function. -pub fn is_inline_destructure(allocator: &mut Allocator, args_sexp: NodePtr) -> bool { - if let SExp::Pair(a, b) = allocator.sexp(args_sexp) { - if let SExp::Pair(_, _) = allocator.sexp(a) { +pub fn is_inline_destructure( + allocator: &mut A, + args_sexp: &A::NodePtr, +) -> bool { + if let ASExp::Pair(a, b) = allocator.sexp(args_sexp) { + if let ASExp::Pair(_, _) = allocator.sexp(&a) { return true; } - return is_inline_destructure(allocator, b); + return is_inline_destructure(allocator, &b); } false diff --git a/src/classic/clvm_tools/stages/stage_2/mod.rs b/src/classic/clvm_tools/stages/stage_2/mod.rs index 6e24f1bc8..2ba6e230f 100644 --- a/src/classic/clvm_tools/stages/stage_2/mod.rs +++ b/src/classic/clvm_tools/stages/stage_2/mod.rs @@ -1,3 +1,4 @@ +pub mod abstraction; pub mod compile; pub mod defaults; pub mod helpers; diff --git a/src/classic/clvm_tools/stages/stage_2/module.rs b/src/classic/clvm_tools/stages/stage_2/module.rs index bb57a4b99..537aba7a2 100644 --- a/src/classic/clvm_tools/stages/stage_2/module.rs +++ b/src/classic/clvm_tools/stages/stage_2/module.rs @@ -2,46 +2,69 @@ use std::collections::HashMap; use std::collections::HashSet; use std::rc::Rc; -use clvm_rs::allocator::{Allocator, NodePtr, SExp}; +use clvm_rs::allocator::NodePtr; use clvm_rs::error::EvalErr; use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType}; use crate::classic::clvm::sexp::{ - enlist, first, flatten, fold_m, map_m, non_nil, nonempty_last, proper_list, rest, First, - NodeSel, Rest, SelectNode, ThisNode, + enlist, first, flatten, fold_m, map_m, nonempty_last, proper_list, rest, First, NodeSel, Rest, + SelectNode, ThisNode, }; -use crate::classic::clvm_tools::binutils::disassemble; use crate::classic::clvm_tools::debug::{build_symbol_dump, FunctionExtraInfo}; use crate::classic::clvm_tools::node_path::NodePath; use crate::classic::clvm_tools::stages::assemble; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ + ASExp, BufCarrier, ClError, ClassicAllocator, +}; use crate::classic::clvm_tools::stages::stage_2::helpers::{evaluate, quote}; use crate::classic::clvm_tools::stages::stage_2::inline::{ formulate_path_selections_for_destructuring, is_at_capture, is_inline_destructure, }; use crate::classic::clvm_tools::stages::stage_2::optimize::optimize_sexp; use crate::classic::clvm_tools::stages::stage_2::reader::process_embed_file; +use crate::compiler::srcloc::Srcloc; lazy_static! { pub static ref MAIN_NAME: String = "".to_string(); } -struct CollectionResult { - pub functions: HashMap, NodePtr>, - pub constants: HashMap, NodePtr>, - pub macros: Vec<(Vec, NodePtr)>, +struct CollectionResult { + pub functions: HashMap, A::NodePtr>, + pub constants: HashMap, A::NodePtr>, + pub macros: Vec<(Vec, A::NodePtr)>, +} + +struct CompileOutput +where + A::NodePtr: Clone, +{ + pub functions: HashMap, A::NodePtr>, + pub symbols_extra_info: HashMap, FunctionExtraInfo>, } -#[derive(Default)] -struct CompileOutput { - pub functions: HashMap, NodePtr>, - pub symbols_extra_info: HashMap, FunctionExtraInfo>, +impl Default for CompileOutput +where + A::NodePtr: Clone, +{ + fn default() -> Self { + CompileOutput { + functions: HashMap::default(), + symbols_extra_info: HashMap::default(), + } + } } -impl CompileOutput { - pub fn add_definitions(&mut self, other: &CompileOutput) { +impl CompileOutput +where + A::NodePtr: Clone, +{ + pub fn add_definitions(&mut self, other: &CompileOutput) + where + A::NodePtr: Clone, + { for (n, v) in other.functions.iter() { - self.functions.insert(n.to_vec(), *v); + self.functions.insert(n.to_vec(), v.clone()); } for (n, v) in other.symbols_extra_info.iter() { self.symbols_extra_info.insert(n.to_vec(), v.clone()); @@ -50,56 +73,66 @@ impl CompileOutput { } // export type TBuildTree = Bytes | Tuple | []; -fn build_tree(allocator: &mut Allocator, items: &[Vec]) -> Result { +fn build_tree( + allocator: &mut A, + items: &[Vec], +) -> Result +where + A::NodePtr: Clone, +{ if items.is_empty() { - Ok(NodePtr::NIL) + let imported_nil = allocator.import(Srcloc::start("*nil*"), NodePtr::NIL)?; + Ok(imported_nil) } else if items.len() == 1 { - allocator.new_atom(&items[0]) + allocator.new_atom(Srcloc::start("*ident*"), &items[0]) } else { - m! { - let half_size = items.len() >> 1; - left <- build_tree(allocator, &items[..half_size]); - right <- build_tree(allocator, &items[half_size..]); - allocator.new_pair(left, right) - } + let half_size = items.len() >> 1; + let left = build_tree(allocator, &items[..half_size])?; + let right = build_tree(allocator, &items[half_size..])?; + let loc = allocator.loc(&left); + allocator.new_pair(loc, &left, &right) } } // export type TBuildTreeProgram = SExp | [Bytes, TBuildTree, TBuildTree] | [Tuple]; -fn build_tree_program(allocator: &mut Allocator, items: &[NodePtr]) -> Result { +fn build_tree_program( + allocator: &mut A, + items: &[A::NodePtr], +) -> Result +where + A::NodePtr: Clone, +{ // This function takes a Python list of items and turns it into a program that // a binary tree of the items, suitable for casting to an s-expression. let size = items.len(); if size == 0 { - m! { - list_of_nil <- enlist(allocator, &[NodePtr::NIL]); - quote(allocator, list_of_nil) - } + let imported_nil = allocator.import(Srcloc::start("*nil*"), NodePtr::NIL)?; + let list_of_nil = enlist(allocator, &[imported_nil])?; + quote(allocator, &list_of_nil) } else if size == 1 { - Ok(items[0]) + Ok(items[0].clone()) } else { - m! { - let half_size = items.len() >> 1; - left <- - build_tree_program(allocator, &items[..half_size]); - right <- - build_tree_program(allocator, &items[half_size..]); - - cons_atom <- allocator.new_atom(&[4_u8]); - enlist(allocator, &[cons_atom, left, right]) - } + let half_size = items.len() >> 1; + let left = build_tree_program(allocator, &items[..half_size])?; + let right = build_tree_program(allocator, &items[half_size..])?; + let loc = allocator.loc(&left); + let cons_atom = allocator.new_atom(loc, &[4_u8])?; + enlist(allocator, &[cons_atom, left, right]) } } /** * @return Used constants name array in `hex string` format. */ -fn build_used_constants_names( - allocator: &mut Allocator, - functions: &HashMap, NodePtr>, - constants: &HashMap, NodePtr>, - macros: &[(Vec, NodePtr)], -) -> Result>, EvalErr> { +fn build_used_constants_names( + allocator: &mut A, + functions: &HashMap, A::NodePtr>, + constants: &HashMap, A::NodePtr>, + macros: &[(Vec, A::NodePtr)], +) -> Result>, ClError> +where + A::NodePtr: Clone, +{ /* Do a naĂ¯ve pruning of unused symbols. It may be too big, but it shouldn't be too small. Return a list of all atoms used that are also the names of @@ -109,7 +142,7 @@ fn build_used_constants_names( for nv in macros { let (name, value) = nv; - macro_as_dict.insert(name.to_vec(), *value); + macro_as_dict.insert(name.to_vec(), value.clone()); } let mut possible_symbols = HashSet::new(); @@ -137,17 +170,17 @@ fn build_used_constants_names( .flat_map(|v| { v.map(|v| { let mut res = Vec::new(); - flatten(allocator, *v, &mut res); + flatten(allocator, v, &mut res); res }) .unwrap_or_default() }) - .collect::>(); + .collect::>(); let matching_names = matching_names_1.iter().filter_map(|v| { // Only v usefully in scope. - if let SExp::Atom = allocator.sexp(*v) { - let atom = allocator.atom(*v); + if let ASExp::Atom = allocator.sexp(v) { + let atom = allocator.atom(v); Some(atom.as_ref().to_vec()) } else { None @@ -176,58 +209,66 @@ fn build_used_constants_names( } #[allow(clippy::too_many_arguments)] -fn parse_include( - allocator: &mut Allocator, - name: NodePtr, +fn parse_include( + allocator: &mut A, + name: &A::NodePtr, namespace: &mut HashSet>, - functions: &mut HashMap, NodePtr>, - constants: &mut HashMap, NodePtr>, - delayed_constants: &mut HashMap, NodePtr>, - macros: &mut Vec<(Vec, NodePtr)>, + functions: &mut HashMap, A::NodePtr>, + constants: &mut HashMap, A::NodePtr>, + delayed_constants: &mut HashMap, A::NodePtr>, + macros: &mut Vec<(Vec, A::NodePtr)>, run_program: Rc, -) -> Result<(), EvalErr> { - m! { - prog <- assemble( - allocator, - "(_read (_full_path_for_name 1))" - ); - assembled_sexp <- run_program.run_program( - allocator, - prog, - name, - None - ); - match proper_list(allocator, assembled_sexp.1, true) { - None => { Err(EvalErr::InternalError(name, "include returned malformed result".to_string())) }, - Some(assembled) => { - for sexp in assembled { - parse_mod_sexp( - allocator, - sexp, - namespace, - functions, - constants, - delayed_constants, - macros, - run_program.clone() - )?; - }; - Ok(()) - } +) -> Result<(), ClError> +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(name); + let prog = assemble(allocator.allocator(), "(_read (_full_path_for_name 1))") + .map_err(|e| ClError(loc.clone(), e))?; + let name_export = allocator.export(name); + let assembled_sexp = run_program + .run_program(allocator.allocator(), prog, name_export, None) + .map_err(|e| ClError(loc.clone(), e))?; + let assembled_sexp_import = allocator.import(loc.clone(), assembled_sexp.1)?; + if let Some(assembled) = proper_list(allocator, &assembled_sexp_import, true) { + for sexp in assembled { + parse_mod_sexp( + allocator, + &sexp, + namespace, + functions, + constants, + delayed_constants, + macros, + run_program.clone(), + )?; } + return Ok(()); } + + Err(ClError( + loc, + EvalErr::InternalError(name_export, "include returned malformed result".to_string()), + )) } -fn unquote_args( - allocator: &mut Allocator, - code: NodePtr, +fn unquote_args( + allocator: &mut A, + code: &A::NodePtr, args: &[Vec], - matches: &HashMap, NodePtr>, -) -> Result { + matches: &HashMap, A::NodePtr>, + inline_env: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ match allocator.sexp(code) { - SExp::Atom => { + ASExp::Atom => { // Only code in scope. let code_atom = allocator.atom(code); + if code_atom.as_ref() == b"@*env*" { + return Ok(inline_env.clone()); + } let matching_args = args .iter() .filter(|arg| *arg == code_atom.as_ref()) @@ -237,59 +278,147 @@ fn unquote_args( if let Some(argval) = matches.get(&matching_args[0]) { // New case: if we've been given an alternate way of computing // the argument, use it here. - return Ok(*argval); + return Ok(argval.clone()); } - let unquote_atom = allocator.new_atom("unquote".as_bytes())?; - return enlist(allocator, &[unquote_atom, code]); + let loc = allocator.loc(code); + let unquote_atom = allocator.new_atom(loc, "unquote".as_bytes())?; + return enlist(allocator, &[unquote_atom, code.clone()]); } - Ok(code) + Ok(code.clone()) + } + ASExp::Pair(c1, c2) => { + let unquoted_c2 = unquote_args(allocator, &c2, args, matches, inline_env)?; + let unquoted_c1 = unquote_args(allocator, &c1, args, matches, inline_env)?; + let loc = allocator.loc(&c1); + allocator.new_pair(loc, &unquoted_c1, &unquoted_c2) } - SExp::Pair(c1, c2) => { - m! { - unquoted_c2 <- unquote_args(allocator, c2, args, matches); - unquoted_c1 <- unquote_args(allocator, c1, args, matches); - allocator.new_pair(unquoted_c1, unquoted_c2) + } +} + +fn inline_argument_reference( + allocator: &mut A, + arg: &A::NodePtr, + matches: &HashMap, A::NodePtr>, +) -> Result +where + A::NodePtr: Clone, +{ + let reference = match allocator.sexp(arg) { + ASExp::Atom => { + let name = allocator.atom(arg).as_ref().to_vec(); + if let Some(selection) = matches.get(&name) { + return Ok(selection.clone()); } + arg.clone() } + ASExp::Pair(first, rest) => { + let Some((capture, _)) = is_at_capture(allocator, &first, &rest) else { + return Err(ClError( + allocator.loc(arg), + EvalErr::InternalError( + allocator.export(arg), + "inline argument destructuring is missing its source capture".to_string(), + ), + )); + }; + capture + } + }; + + let loc = allocator.loc(&reference); + let unquote = allocator.new_atom(loc, b"unquote")?; + enlist(allocator, &[unquote, reference]) +} + +fn inline_environment( + allocator: &mut A, + args: &A::NodePtr, + matches: &HashMap, A::NodePtr>, +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(args); + let argument_tree = match allocator.sexp(args) { + ASExp::Pair(first, rest) => is_at_capture(allocator, &first, &rest) + .map(|(_, destructure)| destructure) + .unwrap_or_else(|| args.clone()), + ASExp::Atom => args.clone(), + }; + + let cons = allocator.new_atom(loc.clone(), b"c")?; + let left_environment = allocator.new_atom(loc.clone(), &[2])?; + let mut argument_references = Vec::new(); + let mut remaining = argument_tree; + while let ASExp::Pair(first, rest) = allocator.sexp(&remaining) { + argument_references.push(first); + remaining = rest; } + + let mut right_environment = if allocator.is_nil(&remaining) { + allocator.import(loc.clone(), NodePtr::NIL)? + } else { + let tail = inline_argument_reference(allocator, &remaining, matches)?; + let enlist_args = allocator.new_atom(loc.clone(), b"__chia__enlist")?; + enlist(allocator, &[enlist_args, tail])? + }; + for arg in argument_references.iter().rev() { + let argument = inline_argument_reference(allocator, arg, matches)?; + right_environment = enlist(allocator, &[cons.clone(), argument, right_environment])?; + } + enlist(allocator, &[cons, left_environment, right_environment]) } -fn defun_inline_to_macro( - allocator: &mut Allocator, - declaration_sexp: NodePtr, -) -> Result { +fn contains_inline_environment(allocator: &A, code: &A::NodePtr) -> bool { + match allocator.sexp(code) { + ASExp::Atom => allocator.atom(code).as_ref() == b"@*env*", + ASExp::Pair(first, rest) => { + contains_inline_environment(allocator, &first) + || contains_inline_environment(allocator, &rest) + } + } +} + +fn defun_inline_to_macro( + allocator: &mut A, + declaration_sexp: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ let Rest::Here(NodeSel::Cons(name, NodeSel::Cons(arg_spec, First::Here(code)))) = Rest::Here(NodeSel::Cons( ThisNode::Here, NodeSel::Cons(ThisNode::Here, First::Here(ThisNode::Here)), )) - .select_nodes(allocator, declaration_sexp)?; - let defmacro_atom = allocator.new_atom("defmacro".as_bytes())?; + .select_nodes(allocator, declaration_sexp.clone())?; + let loc = allocator.loc(declaration_sexp); + let defmacro_atom = allocator.new_atom(loc, "defmacro".as_bytes())?; let mut destructure_matches = HashMap::new(); - let use_args = if is_inline_destructure(allocator, arg_spec) { + let use_args = if is_inline_destructure(allocator, &arg_spec) { // Given an attempt to destructure via the argument list, we need // to ensure that the inline function receives arguments that are // relative to the _values_ given rather than the code given to // generate the arguments. These overlap when the argument list is // a single level proper list, but not otherwise. - formulate_path_selections_for_destructuring(allocator, arg_spec, &mut destructure_matches)? + formulate_path_selections_for_destructuring(allocator, &arg_spec, &mut destructure_matches)? } else { arg_spec }; - let mut r_vec = vec![defmacro_atom, name, use_args]; + let mut r_vec = vec![defmacro_atom, name, use_args.clone()]; let mut arg_atom_list = Vec::new(); - flatten(allocator, use_args, &mut arg_atom_list); + flatten(allocator, &use_args, &mut arg_atom_list); let arg_name_list = arg_atom_list .iter() .filter_map(|x| { - if let SExp::Atom = allocator.sexp(*x) { + if let ASExp::Atom = allocator.sexp(x) { // only x usefully in scope. - Some(allocator.atom(*x)) + Some(allocator.atom(x)) } else { None } @@ -298,9 +427,21 @@ fn defun_inline_to_macro( .map(|v| v.as_ref().to_vec()) .collect::>>(); - let unquoted_code = unquote_args(allocator, code, &arg_name_list, &destructure_matches)?; + let inline_env = if contains_inline_environment(allocator, &code) { + inline_environment(allocator, &use_args, &destructure_matches)? + } else { + code.clone() + }; + let unquoted_code = unquote_args( + allocator, + &code, + &arg_name_list, + &destructure_matches, + &inline_env, + )?; - let qq_atom = allocator.new_atom("qq".as_bytes())?; + let loc = allocator.loc(&unquoted_code); + let qq_atom = allocator.new_atom(loc, "qq".as_bytes())?; let qq_list = enlist(allocator, &[qq_atom, unquoted_code])?; r_vec.push(qq_list); let res = enlist(allocator, &r_vec)?; @@ -308,35 +449,38 @@ fn defun_inline_to_macro( } #[allow(clippy::too_many_arguments)] -fn parse_mod_sexp( - allocator: &mut Allocator, - declaration_sexp: NodePtr, +fn parse_mod_sexp( + allocator: &mut A, + declaration_sexp: &A::NodePtr, namespace: &mut HashSet>, - functions: &mut HashMap, NodePtr>, - constants: &mut HashMap, NodePtr>, + functions: &mut HashMap, A::NodePtr>, + constants: &mut HashMap, A::NodePtr>, // Delayed constants are new: they represent constant values // but we need the whole module to evaluate them since they // may call local functions (such as sha256tree). - delayed_constants: &mut HashMap, NodePtr>, - macros: &mut Vec<(Vec, NodePtr)>, + delayed_constants: &mut HashMap, A::NodePtr>, + macros: &mut Vec<(Vec, A::NodePtr)>, run_program: Rc, -) -> Result<(), EvalErr> { +) -> Result<(), ClError> +where + A::NodePtr: Clone, +{ let NodeSel::Cons(op_node, First::Here(name_node)) = NodeSel::Cons(ThisNode::Here, First::Here(ThisNode::Here)) - .select_nodes(allocator, declaration_sexp)?; + .select_nodes(allocator, declaration_sexp.clone())?; - let op = match allocator.sexp(op_node) { + let op = match allocator.sexp(&op_node) { // op_node in use. - SExp::Atom => { - let atom = allocator.atom(op_node); + ASExp::Atom => { + let atom = allocator.atom(&op_node); atom.as_ref().to_vec() } _ => Vec::new(), }; - let name = match allocator.sexp(name_node) { + let name = match allocator.sexp(&name_node) { // name_node in use. - SExp::Atom => { - let atom = allocator.atom(name_node); + ASExp::Atom => { + let atom = allocator.atom(&name_node); atom.as_ref().to_vec() } _ => Vec::new(), @@ -345,7 +489,7 @@ fn parse_mod_sexp( if op == "include".as_bytes() { parse_include( allocator, - name_node, + &name_node, namespace, functions, constants, @@ -359,22 +503,28 @@ fn parse_mod_sexp( constants.insert(name, constant); Ok(()) } else if namespace.contains(&name) { - Err(EvalErr::InternalError( - declaration_sexp, - format!( - "symbol \"{}\" redefined", - Bytes::new(Some(BytesFromType::Raw(name))).decode() + let loc = allocator.loc(declaration_sexp); + let dec_export = allocator.export(declaration_sexp); + Err(ClError( + loc, + EvalErr::InternalError( + dec_export, + format!( + "symbol \"{}\" redefined", + Bytes::new(Some(BytesFromType::Raw(name))).decode() + ), ), )) } else { namespace.insert(name.to_vec()); if op == "defmacro".as_bytes() { - macros.push((name.to_vec(), declaration_sexp)); + macros.push((name.to_vec(), declaration_sexp.clone())); Ok(()) } else if op == "defun".as_bytes() { let Rest::Here(Rest::Here(declaration_sexp_rr)) = - Rest::Here(Rest::Here(ThisNode::Here)).select_nodes(allocator, declaration_sexp)?; + Rest::Here(Rest::Here(ThisNode::Here)) + .select_nodes(allocator, declaration_sexp.clone())?; functions.insert(name, declaration_sexp_rr); Ok(()) } else if op == "defun-inline".as_bytes() { @@ -384,33 +534,41 @@ fn parse_mod_sexp( } else if op == "defconstant".as_bytes() { let Rest::Here(Rest::Here(First::Here(frr_of_declaration))) = Rest::Here(Rest::Here(First::Here(ThisNode::Here))) - .select_nodes(allocator, declaration_sexp)?; - let quoted_decl = quote(allocator, frr_of_declaration)?; + .select_nodes(allocator, declaration_sexp.clone())?; + let quoted_decl = quote(allocator, &frr_of_declaration)?; constants.insert(name, quoted_decl); Ok(()) } else if op == "defconst".as_bytes() { // Use a new type-based match language. let Rest::Here(Rest::Here(First::Here(definition))) = Rest::Here(Rest::Here(First::Here(ThisNode::Here))) - .select_nodes(allocator, declaration_sexp)?; + .select_nodes(allocator, declaration_sexp.clone())?; delayed_constants.insert(name, definition); Ok(()) } else { - Err(EvalErr::InternalError( - declaration_sexp, - "expected defun, defmacro, defconst, compile-file or defconstant".to_string(), + let loc = allocator.loc(declaration_sexp); + let export_sexp = allocator.export(declaration_sexp); + Err(ClError( + loc, + EvalErr::InternalError( + export_sexp, + "expected defun, defmacro, defconst, compile-file or defconstant".to_string(), + ), )) } } } -fn compile_mod_stage_1( - allocator: &mut Allocator, - args: NodePtr, - macro_lookup: NodePtr, +fn compile_mod_stage_1( + allocator: &mut A, + args: &A::NodePtr, + macro_lookup: &A::NodePtr, run_program: Rc, produce_extra_info: bool, -) -> Result { +) -> Result, ClError> +where + A::NodePtr: Clone, +{ // stage 1: collect up names of globals (functions, constants, macros) m! { let mut functions = HashMap::new(); @@ -420,19 +578,34 @@ fn compile_mod_stage_1( let mut namespace = HashSet::new(); // eslint-disable-next-line no-constant-condition + let loc = allocator.loc(args); match proper_list(allocator, args, true) { - None => { Err(EvalErr::InternalError(args, "miscompiled mod is not a proper list\n".to_string())) }, + None => { + let export_args = allocator.export(args); + Err( + ClError( + loc, + EvalErr::InternalError(export_args, "miscompiled mod is not a proper list\n".to_string()) + ) + ) + } Some(alist) => { if alist.is_empty() { - return Err(EvalErr::InternalError(args, "miscompiled mod is 0 size\n".to_string())); + let export_args = allocator.export(args); + return Err( + ClError( + loc, + EvalErr::InternalError(export_args, "miscompiled mod is 0 size\n".to_string()) + ) + ); } - let main_local_arguments = alist[0]; + let main_local_arguments = alist[0].clone(); for arg in alist.iter().take(alist.len()-1).skip(1) { parse_mod_sexp( allocator, - *arg, + arg, &mut namespace, &mut functions, &mut constants, @@ -462,14 +635,15 @@ fn compile_mod_stage_1( let mut processed = false; // copy so we can modify delayed_constants. - let delayed_constant_defs: Vec<(Vec, NodePtr)> = - delayed_constants.iter().map(|(k,v)| (k.clone(), *v)).collect(); + let delayed_constant_defs: Vec<(Vec, A::NodePtr)> = + delayed_constants.iter().map(|(k,v)| (k.clone(), v.clone())).collect(); + let nil_import = allocator.import(Srcloc::start("*nil*"), NodePtr::NIL)?; for (name, delayed_body) in delayed_constant_defs.iter() { let main_list = enlist( allocator, - &[NodePtr::NIL, *delayed_body] + &[nil_import.clone(), delayed_body.clone()] )?; result_collection.functions.insert( @@ -502,34 +676,48 @@ fn compile_mod_stage_1( produce_extra_info )?; + let compiled_export = allocator.export(&compiled); + let loc = allocator.loc(&compiled); let compilation_result = run_program.run_program( - allocator, - compiled, + allocator.allocator(), + compiled_export, NodePtr::NIL, None - )?; + ).map_err(|e| { + ClError(loc.clone(), e) + })?; let result = run_program.run_program( - allocator, + allocator.allocator(), compilation_result.1, NodePtr::NIL, None - )?; + ).map_err(|e| { + ClError(loc.clone(), e) + })?; + let result_imp = allocator.import(loc, result.1)?; delayed_constants.remove(name); result_collection.constants.insert( - name.to_vec(), quote(allocator, result.1)? + name.to_vec(), quote(allocator, &result_imp)? ); } if !processed { - return Err(EvalErr::InternalError(NodePtr::NIL, "got stuck untangling defconst dependencies".to_string())); + return Err( + ClError( + loc, + EvalErr::InternalError(NodePtr::NIL, "got stuck untangling defconst dependencies".to_string()) + ) + ); } } - let uncompiled_main = nonempty_last(NodePtr::NIL, &alist)?; + let uncompiled_main = nonempty_last(NodePtr::NIL, &alist).map_err(|e| { + ClError(loc, e) + })?; let main_list = enlist( allocator, @@ -547,42 +735,48 @@ fn compile_mod_stage_1( } // export type TSymbolTable = Array<[SExp, Bytes]>; +pub type Symbol = (::NodePtr, Vec); -fn symbol_table_for_tree( - allocator: &mut Allocator, - tree: NodePtr, +fn symbol_table_for_tree( + allocator: &mut A, + tree: &A::NodePtr, root_node: &NodePath, -) -> Result)>, EvalErr> { - if !non_nil(allocator, tree) { +) -> Result>, ClError> +where + A::NodePtr: Clone, +{ + if allocator.is_nil(tree) { return Ok(Vec::new()); } match allocator.sexp(tree) { - SExp::Atom => Ok(vec![(tree, root_node.as_path().data().to_vec())]), - SExp::Pair(_, _) => { + ASExp::Atom => Ok(vec![(tree.clone(), root_node.as_path().data().to_vec())]), + ASExp::Pair(_, _) => { let left_bytes = NodePath::new(None).first(); let right_bytes = NodePath::new(None).rest(); let NodeSel::Cons(tree_first, tree_rest) = - NodeSel::Cons(ThisNode::Here, ThisNode::Here).select_nodes(allocator, tree)?; + NodeSel::Cons(ThisNode::Here, ThisNode::Here) + .select_nodes(allocator, tree.clone())?; // Allow haskell-like @ capture for destructuring. // If we encounter a form like (@ name substructure) then // treat it as though name captures the current path but // we also continue evaluating at the current position. let mut result_fin = Vec::new(); - if let Some((capture, destructure)) = is_at_capture(allocator, tree_first, tree_rest) { + if let Some((capture, destructure)) = is_at_capture(allocator, &tree_first, &tree_rest) + { // Push the given name here. result_fin.push((capture, root_node.as_path().data().to_vec())); - let mut substructure = symbol_table_for_tree(allocator, destructure, root_node)?; + let mut substructure = symbol_table_for_tree(allocator, &destructure, root_node)?; result_fin.append(&mut substructure); } else { let mut left = - symbol_table_for_tree(allocator, tree_first, &root_node.add(left_bytes))?; + symbol_table_for_tree(allocator, &tree_first, &root_node.add(left_bytes))?; let mut right = - symbol_table_for_tree(allocator, tree_rest, &root_node.add(right_bytes))?; + symbol_table_for_tree(allocator, &tree_rest, &root_node.add(right_bytes))?; result_fin.append(&mut left); result_fin.append(&mut right); @@ -593,80 +787,89 @@ fn symbol_table_for_tree( } } -fn build_macro_lookup_program( - allocator: &mut Allocator, - macro_lookup: NodePtr, - macros: &[(Vec, NodePtr)], +fn build_macro_lookup_program( + allocator: &mut A, + macro_lookup: &A::NodePtr, + macros: &[(Vec, A::NodePtr)], run_program: Rc, -) -> Result { - m! { - com_atom <- allocator.new_atom("com".as_bytes()); - cons_atom <- allocator.new_atom(&[4]); - opt_atom <- allocator.new_atom("opt".as_bytes()); - - let runner = || run_program.clone(); - macro_lookup_program <- quote(allocator, macro_lookup); - result_program <- fold_m( - allocator, - &|allocator, macro_lookup_program, macro_def: &(Vec, NodePtr)| m! { - cons_list <- - enlist( - allocator, - &[cons_atom, macro_def.1, macro_lookup_program] - ); - quoted_to_compile <- quote(allocator, cons_list); - compile_form <- - enlist( - allocator, - &[com_atom, quoted_to_compile, macro_lookup_program] - ); - opt_form <- enlist(allocator, &[opt_atom, compile_form]); - top_atom <- allocator.new_atom(NodePath::new(None).as_path().data()); - macro_evaluated <- evaluate(allocator, opt_form, top_atom); - optimize_sexp(allocator, macro_evaluated, runner()) - }, - macro_lookup_program, - &mut macros.iter() - ); - Ok(result_program) - } +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(macro_lookup); + let com_atom = allocator.new_atom(loc.clone(), "com".as_bytes())?; + let cons_atom = allocator.new_atom(loc.clone(), &[4])?; + let opt_atom = allocator.new_atom(loc.clone(), "opt".as_bytes())?; + + let runner = || run_program.clone(); + let macro_lookup_program = quote(allocator, macro_lookup)?; + fold_m( + allocator, + &|allocator, macro_lookup_program, macro_def: &(Vec, A::NodePtr)| { + let cons_list = enlist( + allocator, + &[ + cons_atom.clone(), + macro_def.1.clone(), + macro_lookup_program.clone(), + ], + )?; + let quoted_to_compile = quote(allocator, &cons_list)?; + let compile_form = enlist( + allocator, + &[com_atom.clone(), quoted_to_compile, macro_lookup_program], + )?; + let opt_form = enlist(allocator, &[opt_atom.clone(), compile_form])?; + let loc = allocator.loc(&opt_form); + let top_atom = allocator.new_atom(loc, NodePath::new(None).as_path().data())?; + let macro_evaluated = evaluate(allocator, &opt_form, &top_atom)?; + optimize_sexp(allocator, ¯o_evaluated, runner()) + }, + macro_lookup_program, + &mut macros.iter(), + ) } #[allow(clippy::too_many_arguments)] -fn add_one_function( - allocator: &mut Allocator, +fn add_one_function( + allocator: &mut A, args_root_node: &NodePath, - macro_lookup_program: NodePtr, - constants_symbol_table: &[(NodePtr, Vec)], + macro_lookup_program: &A::NodePtr, + constants_symbol_table: &[Symbol], name: &[u8], - lambda_expression: NodePtr, + lambda_expression: &A::NodePtr, has_constants_tree: bool, -) -> Result { - let mut compile: CompileOutput = Default::default(); - let com_atom = allocator.new_atom("com".as_bytes())?; - let opt_atom = allocator.new_atom("opt".as_bytes())?; +) -> Result, ClError> +where + A::NodePtr: Clone, +{ + let mut compile: CompileOutput = Default::default(); + let loc = allocator.loc(lambda_expression); + let com_atom = allocator.new_atom(loc.clone(), "com".as_bytes())?; + let opt_atom = allocator.new_atom(loc.clone(), "opt".as_bytes())?; let function_args = first(allocator, lambda_expression)?; - let local_symbol_table = symbol_table_for_tree(allocator, function_args, args_root_node)?; + let local_symbol_table = symbol_table_for_tree(allocator, &function_args, args_root_node)?; let mut all_symbols = local_symbol_table; all_symbols.append(&mut constants_symbol_table.to_owned()); let lambda_form_content = rest(allocator, lambda_expression)?; - let lambda_body = first(allocator, lambda_form_content)?; - let quoted_lambda_expr = quote(allocator, lambda_body)?; + let lambda_body = first(allocator, &lambda_form_content)?; + let quoted_lambda_expr = quote(allocator, &lambda_body)?; let all_symbols_list_sexp = map_m(allocator, &mut all_symbols.iter(), &|allocator, pair| { - let path_atom = allocator.new_atom(&pair.1)?; - enlist(allocator, &[pair.0, path_atom]) + let loc = allocator.loc(&lambda_body); + let path_atom = allocator.new_atom(loc, &pair.1)?; + enlist(allocator, &[pair.0.clone(), path_atom]) })?; let all_symbols_list = enlist(allocator, &all_symbols_list_sexp)?; - let quoted_symbols = quote(allocator, all_symbols_list)?; + let quoted_symbols = quote(allocator, &all_symbols_list)?; let com_list = enlist( allocator, &[ com_atom, quoted_lambda_expr, - macro_lookup_program, + macro_lookup_program.clone(), quoted_symbols, ], )?; @@ -684,15 +887,18 @@ fn add_one_function( Ok(compile) } -fn compile_functions( - allocator: &mut Allocator, - functions: &HashMap, NodePtr>, - macro_lookup_program: NodePtr, - constants_symbol_table: &[(NodePtr, Vec)], +fn compile_functions( + allocator: &mut A, + functions: &HashMap, A::NodePtr>, + macro_lookup_program: &A::NodePtr, + constants_symbol_table: &[Symbol], args_root_node: &NodePath, has_constants_tree: bool, -) -> Result { - let mut compiled: CompileOutput = Default::default(); +) -> Result, ClError> +where + A::NodePtr: Clone, +{ + let mut compiled: CompileOutput = Default::default(); for (name, exp) in functions.iter() { compiled.add_definitions(&add_one_function( @@ -701,7 +907,7 @@ fn compile_functions( macro_lookup_program, constants_symbol_table, name, - *exp, + exp, has_constants_tree, )?); } @@ -711,29 +917,37 @@ fn compile_functions( // Add an entry for main's arguments, named __chia__main_arguments in the // symbols, to the symbol list, placing it at the front for simplicity. -fn add_main_args( - allocator: &mut Allocator, - args: NodePtr, - symbols: NodePtr, -) -> Result { - let entry_name = allocator.new_atom("__chia__main_arguments".as_bytes())?; - let entry_value_string = disassemble(allocator, args, None); - let entry_value = allocator.new_atom(entry_value_string.as_bytes())?; - let entry_cons = allocator.new_pair(entry_name, entry_value)?; - allocator.new_pair(entry_cons, symbols) +fn add_main_args( + allocator: &mut A, + args: &A::NodePtr, + symbols: &A::NodePtr, +) -> Result { + let entry_value_loc = allocator.loc(args); + let entry_name = + allocator.new_atom(entry_value_loc.clone(), "__chia__main_arguments".as_bytes())?; + let entry_value_string = allocator.disassemble(args, None); + let entry_value = allocator.new_atom(entry_value_loc, entry_value_string.as_bytes())?; + let entry_cons_loc = allocator.loc(args); + let entry_cons = allocator.new_pair(entry_cons_loc, &entry_name, &entry_value)?; + let sym_loc = allocator.loc(symbols); + allocator.new_pair(sym_loc, &entry_cons, symbols) } -fn finish_compile_from_collection( - allocator: &mut Allocator, - args: NodePtr, - macro_lookup: NodePtr, +fn finish_compile_from_collection( + allocator: &mut A, + args: &A::NodePtr, + macro_lookup: &A::NodePtr, run_program: Rc, - cr: &CollectionResult, + cr: &CollectionResult, produce_extra_info: bool, -) -> Result { - let a_atom = allocator.new_atom(&[2])?; - let cons_atom = allocator.new_atom(&[4])?; - let opt_atom = allocator.new_atom("opt".as_bytes())?; +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(macro_lookup); + let a_atom = allocator.new_atom(loc.clone(), &[2])?; + let cons_atom = allocator.new_atom(loc.clone(), &[4])?; + let opt_atom = allocator.new_atom(loc.clone(), "opt".as_bytes())?; // move macros into the macro lookup let macro_lookup_program = @@ -756,18 +970,18 @@ fn finish_compile_from_collection( }; let constants_symbol_table = - symbol_table_for_tree(allocator, constants_tree, &constants_root_node)?; + symbol_table_for_tree(allocator, &constants_tree, &constants_root_node)?; let compiled = compile_functions( allocator, &cr.functions, - macro_lookup_program, + ¯o_lookup_program, &constants_symbol_table, &args_root_node, has_constants_tree, )?; - let main_path = compiled.functions[MAIN_NAME.as_bytes()]; + let main_path = compiled.functions[MAIN_NAME.as_bytes()].clone(); if has_constants_tree { let mut all_constants_lookup = HashMap::new(); @@ -778,17 +992,18 @@ fn finish_compile_from_collection( } for (k, v) in cr.constants.iter() { - all_constants_lookup.insert(k.to_vec(), *v); + all_constants_lookup.insert(k.to_vec(), v.clone()); } let all_constants_list = all_constants_names .iter() .filter_map(|name| all_constants_lookup.get(name)) - .copied() - .collect::>(); + .cloned() + .collect::>(); let all_constants_tree_program = build_tree_program(allocator, &all_constants_list)?; - let top_atom = allocator.new_atom(NodePath::new(None).as_path().data())?; + let loc = allocator.loc(macro_lookup); + let top_atom = allocator.new_atom(loc.clone(), NodePath::new(None).as_path().data())?; let arg_tree = enlist( allocator, &[cons_atom, all_constants_tree_program, top_atom], @@ -796,7 +1011,7 @@ fn finish_compile_from_collection( let apply_list = enlist(allocator, &[a_atom, main_path, arg_tree])?; - let quoted_apply_list = quote(allocator, apply_list)?; + let quoted_apply_list = quote(allocator, &apply_list)?; let opt_list = enlist(allocator, &[opt_atom, quoted_apply_list])?; let symbols_no_main = build_symbol_dump( allocator, @@ -807,49 +1022,61 @@ fn finish_compile_from_collection( )?; let first_of_args = first(allocator, args)?; let symbols = if produce_extra_info { - add_main_args(allocator, first_of_args, symbols_no_main)? + add_main_args(allocator, &first_of_args, &symbols_no_main)? } else { symbols_no_main }; let to_run = assemble( - allocator, + allocator.allocator(), if produce_extra_info { "(_set_symbol_table (c (c (q . \"source_file\") (_get_source_file)) 1))" } else { "(_set_symbol_table 1)" }, - )?; + ) + .map_err(|e| ClError(loc.clone(), e))?; - run_program.run_program(allocator, to_run, symbols, None)?; + let exported_symbols = allocator.export(&symbols); + run_program + .run_program(allocator.allocator(), to_run, exported_symbols, None) + .map_err(|e| ClError(loc, e))?; Ok(opt_list) } else { - let top_atom = allocator.new_atom(NodePath::new(None).as_path().data())?; + let top_atom = allocator.new_atom(loc, NodePath::new(None).as_path().data())?; let apply_list = enlist(allocator, &[a_atom, main_path, top_atom])?; - let quoted_apply_list = quote(allocator, apply_list)?; + let quoted_apply_list = quote(allocator, &apply_list)?; enlist(allocator, &[opt_atom, quoted_apply_list]) } } -pub fn compile_mod( - allocator: &mut Allocator, - args: NodePtr, - macro_lookup: NodePtr, - _symbol_table: NodePtr, +pub fn compile_mod( + allocator: &mut A, + args: &A::NodePtr, + macro_lookup: &A::NodePtr, + _symbol_table: &A::NodePtr, run_program: Rc, _level: usize, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ // Deal with the "mod" keyword. - let produce_extra_info_prog = assemble(allocator, "(_symbols_extra_info)")?; + let loc = allocator.loc(macro_lookup); + let produce_extra_info_prog = assemble(allocator.allocator(), "(_symbols_extra_info)") + .map_err(|e| ClError(loc.clone(), e))?; let produce_extra_info_null = NodePtr::NIL; - let extra_info_res = run_program.run_program( - allocator, - produce_extra_info_prog, - produce_extra_info_null, - None, - )?; - let produce_extra_info = non_nil(allocator, extra_info_res.1); + let extra_info_res = run_program + .run_program( + allocator.allocator(), + produce_extra_info_prog, + produce_extra_info_null, + None, + ) + .map_err(|e| ClError(loc.clone(), e))?; + let imported_extra_info = allocator.import(loc, extra_info_res.1)?; + let produce_extra_info = !allocator.is_nil(&imported_extra_info); let cr = compile_mod_stage_1( allocator, diff --git a/src/classic/clvm_tools/stages/stage_2/operators.rs b/src/classic/clvm_tools/stages/stage_2/operators.rs index fc65fecaa..87db83739 100644 --- a/src/classic/clvm_tools/stages/stage_2/operators.rs +++ b/src/classic/clvm_tools/stages/stage_2/operators.rs @@ -15,8 +15,8 @@ use clvm_rs::run_program::run_program_with_pre_eval; use crate::classic::clvm::__type_compatibility__::{Bytes, BytesFromType, Stream}; use crate::classic::clvm::OPERATORS_LATEST_VERSION; -use crate::classic::clvm::keyword_from_atom; use crate::classic::clvm::sexp::proper_list; +use crate::classic::clvm::{keyword_from_atom, keyword_to_atom}; use crate::classic::clvm_tools::binutils::{assemble_from_ir, disassemble_to_ir_with_kw}; use crate::classic::clvm_tools::ir::reader::read_ir; @@ -25,6 +25,9 @@ use crate::classic::clvm_tools::sha256tree::TreeHash; use crate::classic::clvm_tools::stages::stage_0::{ choose_run_flags, DefaultProgramRunner, OriginalDialect, RunProgramOption, TRunProgram, }; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ + ASExp, BufCarrier, ClError, ClassicAllocator, +}; use crate::classic::clvm_tools::stages::stage_2::compile::do_com_prog_for_dialect; use crate::classic::clvm_tools::stages::stage_2::optimize::do_optimize; @@ -69,11 +72,14 @@ pub struct CompilerOperatorsInternal { /// is given. If the file can't be found in any search path, use the expression /// the user gave to cause the file to be searched for in the error result. /// They're searched in order so repetition doesn't do anything. (suggested Q+A) -pub fn full_path_for_filename( - parent_sexp: NodePtr, +pub fn full_path_for_filename( + allocator: &A, + parent_sexp: &A::NodePtr, filename: &str, search_paths: &[String], -) -> Result { +) -> Result { + let loc = allocator.loc(parent_sexp); + let exported = allocator.export(parent_sexp); if filename.starts_with("*") { return Ok(filename.to_string()); }; @@ -88,17 +94,22 @@ pub fn full_path_for_filename( .map(|x| x.to_owned()) .map(Ok) .unwrap_or_else(|| { - Err(EvalErr::InternalError( - parent_sexp, - format!("could not compute absolute path for the combination of search path {path} and file name {filename} during text conversion from path_buf") - )) + Err( + ClError( + loc, + EvalErr::InternalError( + exported, + format!("could not compute absolute path for the combination of search path {path} and file name {filename} during text conversion from path_buf") + ) + ) + ) }); } } - Err(EvalErr::InternalError( - parent_sexp, - "can't open file".to_string(), + Err(ClError( + loc, + EvalErr::InternalError(exported, "can't open file".to_string()), )) } @@ -331,7 +342,8 @@ impl CompilerOperatorsInternal { return convert_filename(allocator, &filename); } - let full_name = full_path_for_filename(sexp, &filename, &self.search_paths)?; + let full_name = + full_path_for_filename(allocator, &sexp, &filename, &self.search_paths)?; return convert_filename(allocator, &full_name); } } @@ -351,7 +363,7 @@ impl CompilerOperatorsInternal { table: NodePtr, ) -> Result { if let Some(symtable) = - proper_list(allocator, table, true).and_then(|t| proper_list(allocator, t[0], true)) + proper_list(allocator, &table, true).and_then(|t| proper_list(allocator, &t[0], true)) { for kv in symtable.iter() { if let SExp::Pair(hash, name) = allocator.sexp(*kv) { @@ -384,6 +396,67 @@ impl CompilerOperatorsInternal { .and_then(|o| o.disassembly_ver()) .unwrap_or(OPERATORS_LATEST_VERSION) } + + fn is_modern(&self) -> bool { + self.get_compiler_opts() + .map(|o| o.dialect().strict) + .unwrap_or(false) + } +} + +fn assemble_operator_heads( + allocator: &mut A, + sexp: &A::NodePtr, + operator_position: bool, +) -> Result +where + A::NodePtr: Clone, +{ + match allocator.sexp(sexp) { + ASExp::Pair(first, rest) => { + // A pair in the head of an argument list starts a nested expression. + let first_is_expression = matches!(allocator.sexp(&first), ASExp::Pair(_, _)); + let first = assemble_operator_heads( + allocator, + &first, + operator_position || first_is_expression, + )?; + let rest = assemble_operator_heads(allocator, &rest, false)?; + allocator.new_pair(allocator.loc(sexp), &first, &rest) + } + ASExp::Atom => { + if !operator_position { + return Ok(sexp.clone()); + } + let replacement = { + let atom = allocator.atom(sexp); + std::str::from_utf8(atom.as_ref()) + .ok() + .and_then(|name| keyword_to_atom(OPERATORS_LATEST_VERSION).get(name)) + .cloned() + }; + replacement.map_or_else( + || Ok(sexp.clone()), + |value| allocator.new_atom(allocator.loc(sexp), &value), + ) + } + } +} + +fn assemble_com_program( + allocator: &mut A, + sexp: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ + match allocator.sexp(sexp) { + ASExp::Pair(program, extras) => { + let program = assemble_operator_heads(allocator, &program, true)?; + allocator.new_pair(allocator.loc(sexp), &program, &extras) + } + ASExp::Atom => Ok(sexp.clone()), + } } impl Dialect for CompilerOperatorsInternal { @@ -452,9 +525,18 @@ impl Dialect for CompilerOperatorsInternal { } else if opbuf == b"_write" { self.write(allocator, sexp) } else if opbuf == b"com" { - do_com_prog_for_dialect(self.get_runner(), allocator, sexp) + let assembled; + let sexp = if self.is_modern() { + assembled = assemble_com_program(allocator, &sexp)?; + &assembled + } else { + &sexp + }; + let result = do_com_prog_for_dialect(self.get_runner(), allocator, sexp)?; + Ok(Reduction(1, result)) } else if opbuf == b"opt" { - do_optimize(self.get_runner(), allocator, &self.opt_memo, sexp) + let result = do_optimize(self.get_runner(), allocator, &self.opt_memo, &sexp)?; + Ok(Reduction(1, result)) } else if opbuf == b"_set_symbol_table" { self.set_symbol_table(allocator, sexp) } else if opbuf == b"_get_compile_filename" { @@ -572,3 +654,66 @@ pub fn run_program_for_search_paths( ops.parent.set_runner(ops.parent.clone()); ops } + +#[cfg(test)] +mod tests { + use super::assemble_com_program; + use crate::classic::clvm_tools::stages::stage_2::abstraction::{ + ASExp, BufCarrier, ClassicAllocator, SExpClassicAllocator, + }; + use crate::compiler::sexp::{enlist, SExp}; + use crate::compiler::srcloc::Srcloc; + use std::rc::Rc; + + #[test] + fn modern_com_program_assembles_operator_heads_with_their_source_locations() { + let filename = Rc::new("*assemble-operator-test*".to_string()); + let program_loc = Srcloc::new(filename.clone(), 1, 1); + let quote_loc = Srcloc::new(filename.clone(), 1, 2); + let argument_loc = Srcloc::new(filename.clone(), 1, 4); + let nested_loc = Srcloc::new(filename.clone(), 1, 6); + let nested_quote_loc = Srcloc::new(filename, 1, 7); + + let nested = Rc::new(enlist( + nested_loc, + &[Rc::new(SExp::Atom(nested_quote_loc.clone(), b"q".to_vec()))], + )); + let program = Rc::new(enlist( + program_loc.clone(), + &[ + Rc::new(SExp::Atom(quote_loc.clone(), b"q".to_vec())), + Rc::new(SExp::Atom(argument_loc.clone(), b"q".to_vec())), + nested, + ], + )); + let com_arguments = Rc::new(enlist(program_loc, &[program])); + + let mut allocator = SExpClassicAllocator::new(); + let com_arguments = allocator.from_sexp(com_arguments).unwrap(); + let assembled = assemble_com_program(&mut allocator, &com_arguments).unwrap(); + + let ASExp::Pair(program, _) = allocator.sexp(&assembled) else { + panic!("com arguments must be a pair"); + }; + let ASExp::Pair(operator, arguments) = allocator.sexp(&program) else { + panic!("program must be a pair"); + }; + assert_eq!(allocator.atom(&operator).as_ref(), &[1]); + assert_eq!(allocator.loc(&operator), quote_loc); + + let ASExp::Pair(argument, remaining) = allocator.sexp(&arguments) else { + panic!("program arguments must be a pair"); + }; + assert_eq!(allocator.atom(&argument).as_ref(), b"q"); + assert_eq!(allocator.loc(&argument), argument_loc); + + let ASExp::Pair(nested, _) = allocator.sexp(&remaining) else { + panic!("nested expression must be present"); + }; + let ASExp::Pair(nested_operator, _) = allocator.sexp(&nested) else { + panic!("nested expression must be a pair"); + }; + assert_eq!(allocator.atom(&nested_operator).as_ref(), &[1]); + assert_eq!(allocator.loc(&nested_operator), nested_quote_loc); + } +} diff --git a/src/classic/clvm_tools/stages/stage_2/optimize.rs b/src/classic/clvm_tools/stages/stage_2/optimize.rs index b9b054795..758b08fe3 100644 --- a/src/classic/clvm_tools/stages/stage_2/optimize.rs +++ b/src/classic/clvm_tools/stages/stage_2/optimize.rs @@ -6,21 +6,21 @@ use std::rc::Rc; use clvm_rs::error::EvalErr; use num_bigint::ToBigInt; -use clvm_rs::allocator::{Allocator, NodePtr, SExp}; +use clvm_rs::allocator::NodePtr; use clvm_rs::cost::Cost; -use clvm_rs::reduction::{Reduction, Response}; use crate::classic::clvm::__type_compatibility__::{bi_one, bi_zero}; -use crate::classic::clvm::sexp::{ - atom, enlist, equal_to, first, fold_m, map_m, non_nil, proper_list, -}; -use crate::classic::clvm_tools::binutils::disassemble; +use crate::classic::clvm::sexp::{enlist, equal_to, first, fold_m, map_m, proper_list}; use crate::classic::clvm_tools::node_path::NodePath; use crate::classic::clvm_tools::pattern_match::match_sexp; use crate::classic::clvm_tools::stages::assemble; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ + ASExp, BufCarrier, ClError, ClassicAllocator, +}; use crate::classic::clvm_tools::stages::stage_2::helpers::quote; use crate::classic::clvm_tools::stages::stage_2::operators::AllocatorRefOrTreeHash; +use crate::compiler::srcloc::Srcloc; use crate::util::{number_from_u8, u8_from_number}; @@ -30,49 +30,55 @@ pub struct DoOptProg {} const DEBUG_OPTIMIZATIONS: bool = false; const DIAG_OPTIMIZATIONS: bool = false; -pub fn seems_constant_tail(allocator: &mut Allocator, sexp_: NodePtr) -> bool { - let mut sexp = sexp_; +fn seems_constant_tail(allocator: &mut A, sexp_: &A::NodePtr) -> bool +where + A::NodePtr: Clone, +{ + let mut sexp = sexp_.clone(); loop { - match allocator.sexp(sexp) { - SExp::Pair(l, r) => { - if !seems_constant(allocator, l) { + match allocator.sexp(&sexp) { + ASExp::Pair(l, r) => { + if !seems_constant(allocator, &l) { return false; } sexp = r; } - SExp::Atom => { - return sexp == NodePtr::NIL; + ASExp::Atom => { + return allocator.is_nil(&sexp); } } } } -pub fn seems_constant(allocator: &mut Allocator, sexp: NodePtr) -> bool { +pub fn seems_constant(allocator: &mut A, sexp: &A::NodePtr) -> bool +where + A::NodePtr: Clone, +{ match allocator.sexp(sexp) { - SExp::Atom => { - return sexp == NodePtr::NIL; + ASExp::Atom => { + return allocator.is_nil(sexp); } - SExp::Pair(operator, r) => { - match allocator.sexp(operator) { - SExp::Atom => { + ASExp::Pair(operator, r) => { + match allocator.sexp(&operator) { + ASExp::Atom => { // Was buf of operator. - let atom = allocator.atom(operator); + let atom = allocator.atom(&operator); if atom.as_ref().len() == 1 && atom.as_ref()[0] == 1 { return true; } else if atom.as_ref().len() == 1 && atom.as_ref()[0] == 8 { return false; } } - SExp::Pair(_, _) => { - if !seems_constant(allocator, operator) { + ASExp::Pair(_, _) => { + if !seems_constant(allocator, &operator) { return false; } } } - if !seems_constant_tail(allocator, r) { + if !seems_constant_tail(allocator, &r) { return false; } } @@ -80,65 +86,63 @@ pub fn seems_constant(allocator: &mut Allocator, sexp: NodePtr) -> bool { true } -pub fn constant_optimizer( - allocator: &mut Allocator, +pub fn constant_optimizer( + allocator: &mut A, _memo: &RefCell>, - r: NodePtr, + r: &A::NodePtr, _max_cost: Cost, runner: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ /* * If the expression does not depend upon @ anywhere, * it's a constant. So we can simply evaluate it and * return the quoted result. */ - if let SExp::Pair(first, _) = allocator.sexp(r) { + if let ASExp::Pair(first, _) = allocator.sexp(r) { // first relevant in scope. - if let SExp::Atom = allocator.sexp(first) { - let buf = allocator.atom(first); + if let ASExp::Atom = allocator.sexp(&first) { + let buf = allocator.atom(&first); if buf.as_ref().len() == 1 && buf.as_ref()[0] == 1 { // Short circuit already quoted expression. - return Ok(r); + return Ok(r.clone()); } } } let sc_r = seems_constant(allocator, r); - let nn_r = non_nil(allocator, r); + let nn_r = !allocator.is_nil(r); if DIAG_OPTIMIZATIONS { println!( - "COPT {} SC_R {} NN_R {}", - disassemble(allocator, r, None), + "COPT SC_R {} NN_R {} {}", sc_r, - nn_r + nn_r, + allocator.disassemble(r, None), ); } if sc_r && nn_r { - return m! { - res <- runner.run_program( - allocator, - r, - NodePtr::NIL, - None + let r_export = allocator.export(r); + let res = runner + .run_program(allocator.allocator(), r_export, NodePtr::NIL, None) + .map_err(|e| ClError(allocator.loc(r), e))?; + let r1 = allocator.import(allocator.loc(r), res.1)?; + if DIAG_OPTIMIZATIONS { + println!( + "CONSTANT_OPTIMIZER {} TO {}", + allocator.disassemble(r, None), + allocator.disassemble(&r1, None) ); - let r1 = res.1; - let _ = if DIAG_OPTIMIZATIONS { - println!( - "CONSTANT_OPTIMIZER {} TO {}", - disassemble(allocator, r, None), - disassemble(allocator, r1, None) - ); - }; - quoted <- quote(allocator, r1); - Ok(quoted) }; + return quote(allocator, &r1); } - Ok(r) + Ok(r.clone()) } -pub fn is_args_call(allocator: &Allocator, r: NodePtr) -> bool { - if let SExp::Atom = allocator.sexp(r) { +pub fn is_args_call(allocator: &A, r: &A::NodePtr) -> bool { + if let ASExp::Atom = allocator.sexp(r) { // Only r in scope. let buf = allocator.atom(r); buf.as_ref().len() == 1 && buf.as_ref()[0] == 1 @@ -147,16 +151,22 @@ pub fn is_args_call(allocator: &Allocator, r: NodePtr) -> bool { } } -pub fn cons_q_a_optimizer_pattern(allocator: &mut Allocator) -> NodePtr { - assemble(allocator, "(a (q . (: . sexp)) (: . args))").unwrap() +pub fn cons_q_a_optimizer_pattern(allocator: &mut A) -> A::NodePtr { + let assembled = assemble(allocator.allocator(), "(a (q . (: . sexp)) (: . args))").unwrap(); + allocator + .import(Srcloc::start("*cons_q_a_optimizer_pattern*"), assembled) + .unwrap() } -pub fn cons_q_a_optimizer( - allocator: &mut Allocator, +pub fn cons_q_a_optimizer( + allocator: &mut A, _memo: &RefCell>, - r: NodePtr, + r: &A::NodePtr, _eval_f: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ let cons_q_a_optimizer_pattern = cons_q_a_optimizer_pattern(allocator); /* @@ -164,137 +174,158 @@ pub fn cons_q_a_optimizer( * (a (q . SEXP) @) => SEXP */ - let matched = match_sexp(allocator, cons_q_a_optimizer_pattern, r, HashMap::new()); + let matched = match_sexp(allocator, &cons_q_a_optimizer_pattern, r, HashMap::new()); match ( - matched.as_ref().and_then(|t1| t1.get("args").copied()), - matched.as_ref().and_then(|t1| t1.get("sexp").copied()), + matched.as_ref().and_then(|t1| t1.get("args").cloned()), + matched.as_ref().and_then(|t1| t1.get("sexp").cloned()), ) { (Some(args), Some(sexp)) => { - if is_args_call(allocator, args) { + if is_args_call(allocator, &args) { Ok(sexp) } else { - Ok(r) + Ok(r.clone()) } } - _ => Ok(r), + _ => Ok(r.clone()), } } -fn cons_pattern(allocator: &mut Allocator) -> NodePtr { - assemble(allocator, "(c (: . first) (: . rest)))").unwrap() +fn cons_pattern(allocator: &mut A) -> A::NodePtr { + let assembled = assemble(allocator.allocator(), "(c (: . first) (: . rest)))").unwrap(); + allocator + .import(Srcloc::start("*cons_pattern*"), assembled) + .unwrap() } -fn cons_f(allocator: &mut Allocator, args: NodePtr) -> Result { - m! { - let cons_pattern = cons_pattern(allocator); - if let Some(first) = match_sexp(allocator, cons_pattern, args, HashMap::new()).and_then(|t| t.get("first").copied()) { - Ok(first) - } else { - m! { - first_atom <- allocator.new_atom(&[5]); - tail <- allocator.new_pair(args, NodePtr::NIL); - allocator.new_pair(first_atom, tail) - } - } +fn cons_f(allocator: &mut A, args: &A::NodePtr) -> Result +where + A::NodePtr: Clone, +{ + let cons_pattern = cons_pattern(allocator); + let pair_loc = allocator.loc(args); + if let Some(first) = match_sexp(allocator, &cons_pattern, args, HashMap::new()) + .and_then(|t| t.get("first").cloned()) + { + Ok(first) + } else { + let first_loc = allocator.loc(args); + let first_atom = allocator.new_atom(first_loc.clone(), &[5])?; + let nil = allocator.import(first_loc, NodePtr::NIL)?; + let tail = allocator.new_pair(pair_loc.clone(), args, &nil)?; + allocator.new_pair(pair_loc, &first_atom, &tail) } } -fn cons_r(allocator: &mut Allocator, args: NodePtr) -> Result { - m! { - let cons_pattern = cons_pattern(allocator); - if let Some(rest) = match_sexp(allocator, cons_pattern, args, HashMap::new()).and_then(|t| t.get("rest").copied()) { - Ok(rest) - } else { - m! { - rest_atom <- allocator.new_atom(&[6]); - tail <- allocator.new_pair(args, NodePtr::NIL); - allocator.new_pair(rest_atom, tail) - } - } +fn cons_r(allocator: &mut A, args: &A::NodePtr) -> Result +where + A::NodePtr: Clone, +{ + let cons_pattern = cons_pattern(allocator); + let pair_loc = allocator.loc(args); + if let Some(rest) = match_sexp(allocator, &cons_pattern, args, HashMap::new()) + .and_then(|t| t.get("rest").cloned()) + { + Ok(rest) + } else { + let rest_loc = allocator.loc(args); + let rest_atom = allocator.new_atom(rest_loc.clone(), &[6])?; + let nil = allocator.import(rest_loc, NodePtr::NIL)?; + let tail = allocator.new_pair(pair_loc.clone(), args, &nil)?; + allocator.new_pair(pair_loc, &rest_atom, &tail) } } -fn path_from_args( - allocator: &mut Allocator, - sexp: NodePtr, - new_args: NodePtr, -) -> Result { +fn path_from_args( + allocator: &mut A, + sexp: &A::NodePtr, + new_args: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ match allocator.sexp(sexp) { - SExp::Atom => { + ASExp::Atom => { // Only sexp in scope. let atom = allocator.atom(sexp); let v = number_from_u8(atom.as_ref()); if v <= bi_one() { - Ok(new_args) + Ok(new_args.clone()) } else { - let sexp = allocator.new_atom(&u8_from_number(v.clone() >> 1).to_vec())?; + let loc = allocator.loc(sexp); + let sexp = allocator.new_atom(loc, &u8_from_number(v.clone() >> 1).to_vec())?; if (v & 1_u32.to_bigint().unwrap()) != bi_zero() { let cons_r_res = cons_r(allocator, new_args)?; - path_from_args(allocator, sexp, cons_r_res) + path_from_args(allocator, &sexp, &cons_r_res) } else { let cons_f_res = cons_f(allocator, new_args)?; - path_from_args(allocator, sexp, cons_f_res) + path_from_args(allocator, &sexp, &cons_f_res) } } } - _ => Ok(new_args), + _ => Ok(new_args.clone()), } } -pub fn sub_args( - allocator: &mut Allocator, - sexp: NodePtr, - new_args: NodePtr, -) -> Result { +pub fn sub_args( + allocator: &mut A, + sexp: &A::NodePtr, + new_args: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ match allocator.sexp(sexp) { - SExp::Atom => path_from_args(allocator, sexp, new_args), - SExp::Pair(first_pre, rest) => { + ASExp::Atom => path_from_args(allocator, sexp, new_args), + ASExp::Pair(first_pre, rest) => { let first; - match allocator.sexp(first_pre) { - SExp::Pair(_, _) => { - first = sub_args(allocator, first_pre, new_args)?; + match allocator.sexp(&first_pre) { + ASExp::Pair(_, _) => { + first = sub_args(allocator, &first_pre, new_args)?; } - SExp::Atom => { + ASExp::Atom => { // Atom is a reflection of first_pre. - let atom = allocator.atom(first_pre); + let atom = allocator.atom(&first_pre); if atom.as_ref().len() == 1 && atom.as_ref()[0] == 1 { - return Ok(sexp); + return Ok(sexp.clone()); } else { first = first_pre; } } } - match proper_list(allocator, rest, true) { - Some(tail_args) => m! { - res <- map_m( - allocator, - &mut tail_args.iter(), - &|allocator, elt| { - sub_args(allocator, *elt, new_args) - } - ); - tail_list <- enlist(allocator, &res); - allocator.new_pair(first, tail_list) - }, + match proper_list(allocator, &rest, true) { + Some(tail_args) => { + let res = map_m(allocator, &mut tail_args.iter(), &|allocator, elt| { + sub_args(allocator, elt, new_args) + })?; + let tail_list = enlist(allocator, &res)?; + let first_loc = allocator.loc(&first); + allocator.new_pair(first_loc, &first, &tail_list) + } None => path_from_args(allocator, sexp, new_args), } } } } -fn var_change_optimizer_cons_eval_pattern(allocator: &mut Allocator) -> NodePtr { - assemble(allocator, "(a (q . (: . sexp)) (: . args))").unwrap() +fn var_change_optimizer_cons_eval_pattern(allocator: &mut A) -> A::NodePtr { + let a = assemble(allocator.allocator(), "(a (q . (: . sexp)) (: . args))").unwrap(); + allocator + .import(Srcloc::start("*var_change_optimizer_cons_eval_pattern*"), a) + .unwrap() } -pub fn var_change_optimizer_cons_eval( - allocator: &mut Allocator, +pub fn var_change_optimizer_cons_eval( + allocator: &mut A, memo: &RefCell>, - r: NodePtr, + r: &A::NodePtr, eval_f: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ /* * This applies the transform * (a (q . (op SEXP1...)) (ARGS)) => (q . RET_VAL) where ARGS != @ @@ -307,59 +338,66 @@ pub fn var_change_optimizer_cons_eval( */ let pattern = var_change_optimizer_cons_eval_pattern(allocator); - match match_sexp(allocator, pattern, r, HashMap::new()).as_ref() { - None => Ok(r), + let export_r = allocator.export(r); + match match_sexp(allocator, &pattern, r, HashMap::new()).as_ref() { + None => Ok(r.clone()), Some(t1) => { let original_args = t1.get("args").ok_or_else(|| { - EvalErr::InternalError(r, "bad pattern match on args".to_string()) + ClError( + allocator.loc(r), + EvalErr::InternalError(export_r, "bad pattern match on args".to_string()), + ) })?; if DIAG_OPTIMIZATIONS { println!( "XXX ORIGINAL_ARGS {}", - disassemble(allocator, *original_args, None) + allocator.disassemble(original_args, None) ); }; let original_call = t1.get("sexp").ok_or_else(|| { - EvalErr::InternalError(r, "bad pattern match on sexp".to_string()) + ClError( + allocator.loc(r), + EvalErr::InternalError(export_r, "bad pattern match on sexp".to_string()), + ) })?; if DIAG_OPTIMIZATIONS { println!( "XXX ORIGINAL_CALL {}", - disassemble(allocator, *original_call, None) + allocator.disassemble(original_call, None) ); }; - let new_eval_sexp_args = sub_args(allocator, *original_call, *original_args)?; + let new_eval_sexp_args = sub_args(allocator, original_call, original_args)?; if DIAG_OPTIMIZATIONS { println!( "XXX new_eval_sexp_args {} ORIG {}", - disassemble(allocator, new_eval_sexp_args, None), - disassemble(allocator, *original_args, None) + allocator.disassemble(&new_eval_sexp_args, None), + allocator.disassemble(original_args, None) ); }; // Do not iterate into a quoted value as if it were a list - if seems_constant(allocator, new_eval_sexp_args) { + if seems_constant(allocator, &new_eval_sexp_args) { if DIAG_OPTIMIZATIONS { println!("XXX seems_constant"); } - optimize_sexp_(allocator, memo, new_eval_sexp_args, eval_f) + optimize_sexp_(allocator, memo, &new_eval_sexp_args, eval_f) } else { if DIAG_OPTIMIZATIONS { println!("XXX does not seems_constant"); }; - proper_list(allocator, new_eval_sexp_args, true) + proper_list(allocator, &new_eval_sexp_args, true) .map(|new_operands| { let mut opt_operands = Vec::new(); for item in new_operands.iter() { opt_operands.push(optimize_sexp_( allocator, memo, - *item, + item, eval_f.clone(), )?); } @@ -371,14 +409,14 @@ pub fn var_change_optimizer_cons_eval( println!( "XXX opt_operands {} {}", acc, - disassemble(allocator, val, None) + allocator.disassemble(&val, None) ); } - let increment = match allocator.sexp(val) { - SExp::Pair(val_first, _) => match allocator.sexp(val_first) { - SExp::Atom => { + let increment = match allocator.sexp(&val) { + ASExp::Pair(val_first, _) => match allocator.sexp(&val_first) { + ASExp::Atom => { // Atom reflects val_first. - let vf_buf = allocator.atom(val_first); + let vf_buf = allocator.atom(&val_first); (vf_buf.as_ref().len() != 1 || vf_buf.as_ref()[0] != 1) as i32 } @@ -387,10 +425,10 @@ pub fn var_change_optimizer_cons_eval( _ => 0, }; - Ok::<_, EvalErr>(acc + increment) + Ok::<_, ClError>(acc + increment) }, 0, - &mut opt_operands.iter().copied(), + &mut opt_operands.iter().cloned(), )?; if DIAG_OPTIMIZATIONS { @@ -400,40 +438,43 @@ pub fn var_change_optimizer_cons_eval( if non_constant_count < 1 { enlist(allocator, &opt_operands) } else { - Ok(r) + Ok(r.clone()) } }) - .unwrap_or(Ok(r)) + .unwrap_or(Ok(r.clone())) } } } } -pub fn children_optimizer( - allocator: &mut Allocator, +pub fn children_optimizer( + allocator: &mut A, memo: &RefCell>, - r: NodePtr, + r: &A::NodePtr, eval_f: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ // Recursively apply optimizations to all non-quoted child nodes. match proper_list(allocator, r, true) { - None => Ok(r), + None => Ok(r.clone()), Some(list) => { if list.is_empty() { - return Ok(r); + return Ok(r.clone()); } - if let SExp::Atom = allocator.sexp(list[0]) { - let atom = allocator.atom(list[0]); + if let ASExp::Atom = allocator.sexp(&list[0]) { + let atom = allocator.atom(&list[0]); if atom.as_ref().to_vec() == vec![1] { - return Ok(r); + return Ok(r.clone()); } } let mut optimized = Vec::new(); let mut different = false; for item in list.iter() { - let res = optimize_sexp_(allocator, memo, *item, eval_f.clone())?; - if different || !equal_to(allocator, *item, res) { + let res = optimize_sexp_(allocator, memo, item, eval_f.clone())?; + if different || !equal_to(allocator, item, &res) { different = true; } optimized.push(res); @@ -445,26 +486,35 @@ pub fn children_optimizer( // If we didn't produce any different children, skip producing // a new list and return r. Take advantage of using a consistent // allocator to help the cache. - Ok(r) + Ok(r.clone()) } } } } -fn cons_optimizer_pattern_first(allocator: &mut Allocator) -> NodePtr { - assemble(allocator, "(f (c (: . first) (: . rest)))").unwrap() +fn cons_optimizer_pattern_first(allocator: &mut A) -> A::NodePtr { + let a = assemble(allocator.allocator(), "(f (c (: . first) (: . rest)))").unwrap(); + allocator + .import(Srcloc::start("*cons_optimizer_pattern_first*"), a) + .unwrap() } -fn cons_optimizer_pattern_rest(allocator: &mut Allocator) -> NodePtr { - assemble(allocator, "(r (c (: . first) (: . rest)))").unwrap() +fn cons_optimizer_pattern_rest(allocator: &mut A) -> A::NodePtr { + let a = assemble(allocator.allocator(), "(r (c (: . first) (: . rest)))").unwrap(); + allocator + .import(Srcloc::start("*cons_optimizer_pattern_rest*"), a) + .unwrap() } -fn cons_optimizer( - allocator: &mut Allocator, +fn cons_optimizer( + allocator: &mut A, _memo: &RefCell>, - r: NodePtr, + r: &A::NodePtr, _eval_f: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ /* * This applies the transform * (f (c A B)) => A @@ -476,18 +526,18 @@ fn cons_optimizer( m! { let t1 = match_sexp( - allocator, cons_optimizer_pattern_first, r, HashMap::new() + allocator, &cons_optimizer_pattern_first, r, HashMap::new() ); - match t1.and_then(|t| t.get("first").copied()) { + match t1.and_then(|t| t.get("first").cloned()) { Some(first) => Ok(first), _ => { m! { let t2 = match_sexp( - allocator, cons_optimizer_pattern_rest, r, HashMap::new() + allocator, &cons_optimizer_pattern_rest, r, HashMap::new() ); - match t2.and_then(|t| t.get("rest").copied()) { + match t2.and_then(|t| t.get("rest").cloned()) { Some(rest) => Ok(rest), - _ => Ok(r) + _ => Ok(r.clone()) } } } @@ -495,20 +545,29 @@ fn cons_optimizer( } } -fn first_atom_pattern(allocator: &mut Allocator) -> NodePtr { - assemble(allocator, "(f ($ . atom))").unwrap() +fn first_atom_pattern(allocator: &mut A) -> A::NodePtr { + let a = assemble(allocator.allocator(), "(f ($ . atom))").unwrap(); + allocator + .import(Srcloc::start("*first_atom_pattern*"), a) + .unwrap() } -fn rest_atom_pattern(allocator: &mut Allocator) -> NodePtr { - assemble(allocator, "(r ($ . atom))").unwrap() +fn rest_atom_pattern(allocator: &mut A) -> A::NodePtr { + let a = assemble(allocator.allocator(), "(r ($ . atom))").unwrap(); + allocator + .import(Srcloc::start("*first_atom_pattern*"), a) + .unwrap() } -fn path_optimizer( - allocator: &mut Allocator, +fn path_optimizer( + allocator: &mut A, _memo: &RefCell>, - r: NodePtr, + r: &A::NodePtr, _eval_f: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ let first_atom_pattern = first_atom_pattern(allocator); let rest_atom_pattern = rest_atom_pattern(allocator); @@ -519,93 +578,117 @@ fn path_optimizer( * (r N) => B */ - let first_match = match_sexp(allocator, first_atom_pattern, r, HashMap::new()); - let rest_match = match_sexp(allocator, rest_atom_pattern, r, HashMap::new()); + let first_match = match_sexp(allocator, &first_atom_pattern, r, HashMap::new()); + let rest_match = match_sexp(allocator, &rest_atom_pattern, r, HashMap::new()); match (first_match, rest_match) { (Some(first), _) => { match first .get("atom") - .and_then(|a| atom(allocator, *a).ok()) - .map(|atom| number_from_u8(&atom)) + .filter(|a| matches!(allocator.sexp(a), ASExp::Atom)) + .map(|a| (allocator.loc(a), allocator.atom(a))) + .map(|(loc, atom)| (loc, number_from_u8(atom.as_ref()))) { - Some(atom) => { + Some((loc, atom)) => { let node = NodePath::new(Some(atom)).add(NodePath::new(None).first()); - allocator.new_atom(node.as_path().data()) + allocator.new_atom(loc, node.as_path().data()) } - _ => Ok(r), + _ => Ok(r.clone()), } } (_, Some(rest)) => { match rest .get("atom") - .and_then(|a| atom(allocator, *a).ok()) - .map(|atom| number_from_u8(&atom)) + .filter(|a| matches!(allocator.sexp(a), ASExp::Atom)) + .map(|a| (allocator.loc(a), allocator.atom(a))) + .map(|(loc, atom)| (loc, number_from_u8(atom.as_ref()))) { - Some(atom) => { + Some((loc, atom)) => { let node = NodePath::new(Some(atom)).add(NodePath::new(None).rest()); - allocator.new_atom(node.as_path().data()) + allocator.new_atom(loc, node.as_path().data()) } - _ => Ok(r), + _ => Ok(r.clone()), } } - _ => Ok(r), + _ => Ok(r.clone()), } } -fn quote_pattern_1(allocator: &mut Allocator) -> NodePtr { - assemble(allocator, "(q . 0)").unwrap() +fn quote_pattern_1(allocator: &mut A) -> A::NodePtr { + let a = assemble(allocator.allocator(), "(q . 0)").unwrap(); + allocator + .import(Srcloc::start("*quote_pattern_1*"), a) + .unwrap() } -fn quote_null_optimizer( - allocator: &mut Allocator, +fn quote_null_optimizer( + allocator: &mut A, _memo: &RefCell>, - r: NodePtr, + r: &A::NodePtr, _eval_f: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ let quote_pattern_1 = quote_pattern_1(allocator); // This applies the transform `(q . 0)` => `0` - let t1 = match_sexp(allocator, quote_pattern_1, r, HashMap::new()); - Ok(t1.map(|_| NodePtr::NIL).unwrap_or_else(|| r)) + let t1 = match_sexp(allocator, "e_pattern_1, r, HashMap::new()); + let loc = allocator.loc(r); + let imported_nil = allocator.import(loc, NodePtr::NIL)?; + Ok(t1.map(|_| imported_nil).unwrap_or_else(|| r.clone())) } -fn apply_null_pattern_1(allocator: &mut Allocator) -> NodePtr { - assemble(allocator, "(a 0 . (: . rest))").unwrap() +fn apply_null_pattern_1(allocator: &mut A) -> A::NodePtr { + let a = assemble(allocator.allocator(), "(a 0 . (: . rest))").unwrap(); + allocator + .import(Srcloc::start("*apply_null_pattern_1*"), a) + .unwrap() } -fn apply_null_optimizer( - allocator: &mut Allocator, +fn apply_null_optimizer( + allocator: &mut A, _memo: &RefCell>, - r: NodePtr, + r: &A::NodePtr, _eval_f: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ let apply_null_pattern_1 = apply_null_pattern_1(allocator); // This applies the transform `(a 0 ARGS)` => `0` - let t1 = match_sexp(allocator, apply_null_pattern_1, r, HashMap::new()); - Ok(t1.map(|_| NodePtr::NIL).unwrap_or_else(|| r)) + let t1 = match_sexp(allocator, &apply_null_pattern_1, r, HashMap::new()); + let loc = allocator.loc(r); + let imported_nil = allocator.import(loc, NodePtr::NIL)?; + Ok(t1.map(|_| imported_nil).unwrap_or_else(|| r.clone())) } -struct OptimizerRunner<'a> { +struct OptimizerRunner<'a, A: ClassicAllocator> +where + A::NodePtr: Clone, +{ pub name: String, #[allow(clippy::type_complexity)] to_run: &'a dyn Fn( - &mut Allocator, + &mut A, &RefCell>, - NodePtr, + &A::NodePtr, Rc, - ) -> Result, + ) -> Result, } -impl<'a> OptimizerRunner<'a> { +impl<'a, A: ClassicAllocator> OptimizerRunner<'a, A> +where + A::NodePtr: Clone, +{ pub fn invoke( &self, - allocator: &mut Allocator, + allocator: &mut A, memo: &RefCell>, - r: NodePtr, + r: &A::NodePtr, eval_f: Rc, - ) -> Result { + ) -> Result { (self.to_run)(allocator, memo, r, eval_f) } @@ -613,11 +696,11 @@ impl<'a> OptimizerRunner<'a> { pub fn new( name: &str, to_run: &'a dyn Fn( - &mut Allocator, + &mut A, &RefCell>, - NodePtr, + &A::NodePtr, Rc, - ) -> Result, + ) -> Result, ) -> Self { OptimizerRunner { name: name.to_string(), @@ -626,31 +709,37 @@ impl<'a> OptimizerRunner<'a> { } } -pub fn optimize_sexp_( - allocator: &mut Allocator, +pub fn optimize_sexp_( + allocator: &mut A, memo: &RefCell>, - r_: NodePtr, + r_: &A::NodePtr, eval_f: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ // First compare the NodePtr to see if we've cached this exact one. // Note that this scoping is here to prevent the borrowed mutable ref from // preventing us from using memo downstream when we've done one optimize // pass and need to cache the result. + let exported_r = allocator.export(r_); { let memo_ref: Ref> = memo.borrow(); let memo: &HashMap = &memo_ref; - if let Some(res) = memo.get(&AllocatorRefOrTreeHash::new_from_nodeptr(r_)) { - return Ok(*res); + if let Some(res) = memo.get(&AllocatorRefOrTreeHash::new_from_nodeptr(exported_r)) { + let imported_cached = allocator.import(allocator.loc(r_), *res)?; + return Ok(imported_cached); } } // Fall back to treehash comparison since we didn't get an exact pointer hit. - let footprint = AllocatorRefOrTreeHash::new_from_sexp(allocator, r_); + let footprint = AllocatorRefOrTreeHash::new_from_sexp(allocator.allocator(), exported_r); { let memo_ref: Ref> = memo.borrow(); let memo: &HashMap = &memo_ref; if let Some(res) = memo.get(&footprint) { - return Ok(*res); + let imported_cached = allocator.import(allocator.loc(r_), *res)?; + return Ok(imported_cached); } } @@ -658,7 +747,7 @@ pub fn optimize_sexp_( * Optimize an s-expression R written for clvm to R_opt where * (a R args) == (a R_opt args) for ANY args. */ - let optimizers: Vec = vec![ + let optimizers: Vec> = vec![ OptimizerRunner::new("cons_optimizer", &cons_optimizer), OptimizerRunner::new("constant_optimizer", &|allocator, memo, r, eval_f| { constant_optimizer(allocator, memo, r, 0, eval_f.clone()) @@ -674,25 +763,26 @@ pub fn optimize_sexp_( OptimizerRunner::new("apply_null_optimizer", &apply_null_optimizer), ]; - let mut r = r_; + let mut r = r_.clone(); loop { - let start_r = r; + let start_r = r.clone(); + let start_r_export = allocator.export(&r); let mut name = "".to_string(); - match allocator.sexp(r) { - SExp::Atom => { - return Ok(r); + match allocator.sexp(&r) { + ASExp::Atom => { + return Ok(r.clone()); } - SExp::Pair(_, _) => { + ASExp::Pair(_, _) => { for opt in optimizers.iter() { name.clone_from(&opt.name); - match opt.invoke(allocator, memo, r, eval_f.clone()) { + match opt.invoke(allocator, memo, &r, eval_f.clone()) { Err(e) => { return Err(e); } Ok(res) => { - if !equal_to(allocator, r, res) { + if !equal_to(allocator, &r, &res) { r = res; break; } @@ -700,12 +790,16 @@ pub fn optimize_sexp_( } } - if equal_to(allocator, start_r, r) { + if equal_to(allocator, &start_r, &r) { memo.replace_with(|mr| { let mut work = HashMap::new(); swap(&mut work, mr); - work.insert(footprint.clone(), start_r); - work.insert(AllocatorRefOrTreeHash::new_from_nodeptr(r), start_r); + work.insert(footprint.clone(), start_r_export); + let r_export = allocator.export(&r); + work.insert( + AllocatorRefOrTreeHash::new_from_nodeptr(r_export), + start_r_export, + ); work }); @@ -716,8 +810,8 @@ pub fn optimize_sexp_( println!( "OPT-{:?}[{}] => {}", name, - disassemble(allocator, start_r, None), - disassemble(allocator, r, None) + allocator.disassemble(&start_r, None), + allocator.disassemble(&r, None) ); } } @@ -725,34 +819,39 @@ pub fn optimize_sexp_( } } -pub fn optimize_sexp( - allocator: &mut Allocator, - r: NodePtr, +pub fn optimize_sexp( + allocator: &mut A, + r: &A::NodePtr, eval_f: Rc, -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ let optimized = RefCell::new(HashMap::new()); if DIAG_OPTIMIZATIONS { - println!("START OPTIMIZE {}", disassemble(allocator, r, None)); + println!("START OPTIMIZE {}", allocator.disassemble(r, None)); } optimize_sexp_(allocator, &optimized, r, eval_f).inspect(|x| { if DIAG_OPTIMIZATIONS { println!( "OPTIMIZE_SEXP {} GIVING {}", - disassemble(allocator, r, None), - disassemble(allocator, *x, None) + allocator.disassemble(r, None), + allocator.disassemble(x, None) ); } }) } -pub fn do_optimize( +pub fn do_optimize( runner: Rc, - allocator: &mut Allocator, + allocator: &mut A, memo: &RefCell>, - r: NodePtr, -) -> Response { + r: &A::NodePtr, +) -> Result +where + A::NodePtr: Clone, +{ let r_first = first(allocator, r)?; - optimize_sexp_(allocator, memo, r_first, runner.clone()) - .map(|optimized| Reduction(1, optimized)) + optimize_sexp_(allocator, memo, &r_first, runner.clone()) } diff --git a/src/classic/clvm_tools/stages/stage_2/reader.rs b/src/classic/clvm_tools/stages/stage_2/reader.rs index 9eb439266..0ea39d108 100644 --- a/src/classic/clvm_tools/stages/stage_2/reader.rs +++ b/src/classic/clvm_tools/stages/stage_2/reader.rs @@ -2,13 +2,16 @@ use std::fs; use std::rc::Rc; use clvm_rs::error::EvalErr; -use clvmr::allocator::{Allocator, NodePtr, SExp}; +use clvmr::allocator::NodePtr; use crate::classic::clvm::__type_compatibility__::{Bytes, Stream, UnvalidatedBytesFromType}; use crate::classic::clvm::serialize::{sexp_from_stream, SimpleCreateCLVMObject}; use crate::classic::clvm::sexp::{proper_list, rest}; use crate::classic::clvm_tools::stages::assemble; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ + ASExp, BufCarrier, ClError, ClassicAllocator, +}; use crate::classic::clvm_tools::stages::stage_2::compile::get_search_paths; use crate::classic::clvm_tools::stages::stage_2::helpers::quote; use crate::classic::clvm_tools::stages::stage_2::operators::full_path_for_filename; @@ -25,21 +28,32 @@ pub struct PresentFile { /// Given u8 data from a hex file, build an sexp from it. /// This is used for the compile-file and embed-file feature. -pub fn convert_hex_to_sexp( - allocator: &mut Allocator, +pub fn convert_hex_to_sexp( + allocator: &mut A, + parent_sexp: &A::NodePtr, file_data: &[u8], -) -> Result { +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(parent_sexp); let content_bytes = Bytes::new_validated(Some(UnvalidatedBytesFromType::Hex(decode_string( file_data, )))) - .map_err(|e| EvalErr::InternalError(NodePtr::NIL, e.to_string()))?; + .map_err(|e| { + ClError( + loc.clone(), + EvalErr::InternalError(NodePtr::NIL, e.to_string()), + ) + })?; let mut reader_stream = Stream::new(Some(content_bytes)); - Ok(sexp_from_stream( - allocator, + let incoming_data = sexp_from_stream( + allocator.allocator(), &mut reader_stream, Box::new(SimpleCreateCLVMObject {}), - )? - .1) + ) + .map_err(|e| ClError(loc.clone(), e))?; + allocator.import(loc, incoming_data.1) } /// Given a runner (which in the case of classic, contains the search paths as @@ -47,18 +61,26 @@ pub fn convert_hex_to_sexp( /// time runner), try to find a file to embed given its name. Try to report an /// error nicely by using the form the user gave (parent_sexp) in the error /// report. -pub fn read_file( +pub fn read_file( runner: Rc, - allocator: &mut Allocator, - parent_sexp: NodePtr, + allocator: &mut A, + parent_sexp: &A::NodePtr, filename: &str, -) -> Result { - let search_paths = get_search_paths(runner, allocator)?; - let full_path = full_path_for_filename(parent_sexp, filename, &search_paths)?; +) -> Result +where + A::NodePtr: Clone, +{ + let loc = allocator.loc(parent_sexp); + let search_paths = get_search_paths(runner, loc.clone(), allocator)?; + let full_path = full_path_for_filename(allocator, parent_sexp, filename, &search_paths)?; + let export = allocator.export(parent_sexp); fs::read(full_path.clone()) .map_err(|x| { - EvalErr::InternalError(parent_sexp, format!("error reading {full_path}: {x:?}")) + ClError( + loc, + EvalErr::InternalError(export, format!("error reading {full_path}: {x:?}")), + ) }) .map(|data| PresentFile { data, @@ -72,11 +94,14 @@ pub fn read_file( /// or (compile-file constant-name filename) /// Return the resulting constant name and a quoted expression suitable for use /// as a constant or an error if the file wasn't found. -pub fn process_embed_file( - allocator: &mut Allocator, +pub fn process_embed_file( + allocator: &mut A, runner: Rc, - declaration_sexp: NodePtr, -) -> Result<(Vec, NodePtr), EvalErr> { + declaration_sexp: &A::NodePtr, +) -> Result<(Vec, A::NodePtr), ClError> +where + A::NodePtr: Clone, +{ // Include the file's contents in the constant pool. // The user can specify the format to read: // @@ -84,24 +109,28 @@ pub fn process_embed_file( // hex // sexp let rest_of_decl = rest(allocator, declaration_sexp)?; - if let Some(l) = proper_list(allocator, rest_of_decl, true) { + let loc = allocator.loc(declaration_sexp); + let export = allocator.export(declaration_sexp); + if let Some(l) = proper_list(allocator, &rest_of_decl, true) { if l.len() != 3 { - return Err(EvalErr::InternalError( - declaration_sexp, - "must have a type and a name".to_string(), + let loc = allocator.loc(declaration_sexp); + let dec_export = allocator.export(declaration_sexp); + return Err(ClError( + loc, + EvalErr::InternalError(dec_export, "must have a type and a name".to_string()), )); } - if let (SExp::Atom, SExp::Atom, SExp::Atom) = ( - allocator.sexp(l[0]), - allocator.sexp(l[1]), - allocator.sexp(l[2]), + if let (ASExp::Atom, ASExp::Atom, ASExp::Atom) = ( + allocator.sexp(&l[0]), + allocator.sexp(&l[1]), + allocator.sexp(&l[2]), ) { // Note: we don't want to keep borrowing here because we // need the mutable borrow below. - let name_atom = allocator.atom(l[0]); - let kind_atom = allocator.atom(l[1]); - let filename_atom = allocator.atom(l[2]); + let name_atom = allocator.atom(&l[0]); + let kind_atom = allocator.atom(&l[1]); + let filename_atom = allocator.atom(&l[2]); let name_buf = name_atom.as_ref().to_vec(); let kind_buf = kind_atom.as_ref().to_vec(); let filename_buf = filename_atom.as_ref().to_vec(); @@ -112,7 +141,7 @@ pub fn process_embed_file( declaration_sexp, &decode_string(&filename_buf), )?; - allocator.new_atom(&file.data)? + allocator.new_atom(loc, &file.data)? } else if kind_buf == b"hex" { let file = read_file( runner, @@ -120,7 +149,7 @@ pub fn process_embed_file( declaration_sexp, &decode_string(&filename_buf), )?; - convert_hex_to_sexp(allocator, &file.data)? + convert_hex_to_sexp(allocator, declaration_sexp, &file.data)? } else if kind_buf == b"sexp" { let file = read_file( runner, @@ -128,25 +157,27 @@ pub fn process_embed_file( declaration_sexp, &decode_string(&filename_buf), )?; - assemble(allocator, &decode_string(&file.data))? + let assembled = assemble(allocator.allocator(), &decode_string(&file.data)) + .map_err(|e| ClError(loc.clone(), e))?; + allocator.import(loc, assembled)? } else { - return Err(EvalErr::InternalError( - declaration_sexp, - "no such embed kind".to_string(), + return Err(ClError( + loc, + EvalErr::InternalError(export, "no such embed kind".to_string()), )); }; - Ok((name_buf.to_vec(), quote(allocator, file_data)?)) + Ok((name_buf.to_vec(), quote(allocator, &file_data)?)) } else { - Err(EvalErr::InternalError( - declaration_sexp, - "malformed embed-file".to_string(), + Err(ClError( + loc, + EvalErr::InternalError(export, "malformed embed-file".to_string()), )) } } else { - Err(EvalErr::InternalError( - declaration_sexp, - "must be a proper list".to_string(), + Err(ClError( + loc, + EvalErr::InternalError(export, "must be a proper list".to_string()), )) } } diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index c12c250d9..0976b8afe 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -1557,6 +1557,7 @@ pub fn hoist_body_let_binding( BodyForm::Let(LetFormKind::Parallel, letdata) => { let mut out_defuns = Vec::new(); let defun_name = gensym("letbinding".as_bytes().to_vec()); + let force_non_inline = opts.dialect().classic_codegen && outer_context.is_some(); let mut revised_bindings = Vec::new(); for b in letdata.bindings.iter() { @@ -1575,7 +1576,7 @@ pub fn hoist_body_let_binding( })); } let generated_defun = generate_let_defun( - opts, + opts.clone(), letdata.loc.clone(), None, &defun_name, @@ -1584,12 +1585,29 @@ pub fn hoist_body_let_binding( revised_bindings.to_vec(), letdata.body.clone(), ); + let generated_defun = if force_non_inline { + if let HelperForm::Defun(_, defun) = generated_defun { + HelperForm::Defun(false, defun) + } else { + unreachable!() + } + } else { + generated_defun + }; out_defuns.push(generated_defun); let mut let_args = generate_let_args(letdata.loc.clone(), revised_bindings.to_vec()); let pass_env = outer_context .map(create_let_env_expression) .unwrap_or_else(|| { + // Modern codegen wraps the module arguments in its function + // environment. Classic stage 2 exposes them directly. + if opts.dialect().classic_codegen { + return BodyForm::Value(SExp::Atom( + letdata.loc.clone(), + "@*env*".as_bytes().to_vec(), + )); + } BodyForm::Call( letdata.loc.clone(), vec![ @@ -1712,7 +1730,11 @@ pub fn process_helper_let_bindings( while i < result.len() { match result[i].clone() { HelperForm::Defun(inline, defun) => { - let context = if inline { + // Let helpers take the enclosing function's argument tree as + // their first argument. Classic codegen exposes function + // arguments directly via @*env*, so reconstruct that tree at + // the call site even for non-inline functions. + let context = if inline || opts.dialect().classic_codegen { Some(defun.args.clone()) } else { None diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index e75674405..0175a6111 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -14,6 +14,13 @@ use clvm_rs::allocator::Allocator; use crate::classic::clvm::__type_compatibility__::{bi_one, bi_zero}; use crate::classic::clvm_tools::ir::r#type::NEW_BIT_CONSTANTS; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; +use crate::classic::clvm_tools::stages::stage_2::abstraction::{ + ASExp, BufCarrier, ClassicAllocator, SExpClassicAllocator, +}; +use crate::classic::clvm_tools::stages::stage_2::defaults::default_macro_lookup; +use crate::classic::clvm_tools::stages::stage_2::module::compile_mod; +use crate::classic::clvm_tools::stages::stage_2::operators::run_program_for_search_paths; +use crate::classic::clvm_tools::stages::stage_2::optimize::optimize_sexp; use crate::classic::clvm::__type_compatibility__::Stream; use crate::classic::clvm::sexp::sexp_as_bin; @@ -21,9 +28,9 @@ use crate::compiler::clvm::{convert_to_clvm_rs, run, sha256tree, NewStyleIntConv use crate::compiler::codegen::{codegen, hoist_body_let_binding, process_helper_let_bindings}; use crate::compiler::comptypes::{ BodyForm, CompileErr, CompileForm, CompileModuleComponent, CompileModuleOutput, CompilerOpts, - CompilerOutput, ConstantKind, DefunData, Export, FrontendOutput, HelperForm, ImportLongName, - IncludeDesc, IncludeProcessType, ModulePhase, PrimaryCodegen, StandalonePhaseInfo, - SyntheticType, + CompilerOutput, ConstantKind, DefconstData, DefmacData, DefunData, Export, FrontendOutput, + HelperForm, ImportLongName, IncludeDesc, IncludeProcessType, ModulePhase, NamespaceData, + PrimaryCodegen, StandalonePhaseInfo, SyntheticType, }; use crate::compiler::dialect::{AcceptedDialect, KNOWN_DIALECTS}; use crate::compiler::frontend::frontend; @@ -151,6 +158,108 @@ pub fn do_desugar( }) } +pub fn expand_com_forms_expr( + opts: Rc, + body: Rc, +) -> Result, CompileErr> { + if let BodyForm::Call(l, c, tail) = &*body { + if c.is_empty() { + return Ok(body); + } + + if let BodyForm::Value(atom) = &*c[0] { + if c.len() == 2 && atom.atomize() == SExp::Atom(atom.loc(), b"com".to_vec()) { + return Ok(Rc::new(BodyForm::Call( + l.clone(), + vec![ + Rc::new(BodyForm::Value(SExp::Atom( + c[0].loc(), + b"function".to_vec(), + ))), + c[1].clone(), + ], + None, + ))); + } + } + + let mut call_args = vec![c[0].clone()]; + for item in c.iter().skip(1) { + call_args.push(expand_com_forms_expr(opts.clone(), item.clone())?); + } + + let new_tail = if let Some(t) = &tail { + Some(expand_com_forms_expr(opts, t.clone())?) + } else { + None + }; + + return Ok(Rc::new(BodyForm::Call(l.clone(), call_args, new_tail))); + } + + Ok(body) +} + +fn expand_com_forms_helper( + context: &mut BasicCompileContext, + opts: Rc, + helper: &HelperForm, +) -> Result { + match helper { + HelperForm::Defconstant(defconst) => Ok(HelperForm::Defconstant(DefconstData { + body: expand_com_forms_expr(opts, defconst.body.clone())?, + ..defconst.clone() + })), + HelperForm::Defmacro(defmacro) => Ok(HelperForm::Defmacro(DefmacData { + program: Rc::new(expand_com_forms( + context, + opts, + defmacro.program.as_ref().clone(), + )?), + ..defmacro.clone() + })), + HelperForm::Defun(inline, defun) => Ok(HelperForm::Defun( + *inline, + Box::new(DefunData { + body: expand_com_forms_expr(opts, defun.body.clone())?, + ..*defun.clone() + }), + )), + HelperForm::Defnamespace(namespace) => { + let helpers = namespace + .helpers + .iter() + .map(|helper| expand_com_forms_helper(context, opts.clone(), helper)) + .collect::, _>>()?; + Ok(HelperForm::Defnamespace(Box::new(NamespaceData { + helpers, + ..*namespace.clone() + }))) + } + HelperForm::Defnsref(_) => Ok(helper.clone()), + } +} + +/// Translate modern `com` forms for a backend that only implements `function`. +pub fn expand_com_forms( + context: &mut BasicCompileContext, + opts: Rc, + compileform: CompileForm, +) -> Result { + let helpers = compileform + .helpers + .iter() + .map(|helper| expand_com_forms_helper(context, opts.clone(), helper)) + .collect::, _>>()?; + let exp = expand_com_forms_expr(opts, compileform.exp.clone())?; + + Ok(CompileForm { + helpers, + exp, + ..compileform + }) +} + /// Given a compileform, compile it to clvm. This comes after preprocessing /// and desugaring. pub fn finish_compilation( @@ -158,6 +267,14 @@ pub fn finish_compilation( opts: Rc, p2: CompileForm, ) -> Result { + if opts.dialect().classic_codegen { + let mut modern_dialect = opts.dialect(); + modern_dialect.classic_codegen = false; + let modern_opts = opts.set_dialect(modern_dialect); + let p3 = expand_com_forms(context, modern_opts, p2)?; + return classic_codegen(opts, p3); + } + let p3 = context.post_desugar_optimization(opts.clone(), p2)?; // generate code from AST, optionally with optimization @@ -168,12 +285,70 @@ pub fn finish_compilation( Ok(g2) } +/// Generate code from a desugared modern CompileForm with classic stage 2. +/// +/// The classic compiler's normal final optimization is part of its code +/// generation contract. No modern optimization hooks run on this path. +fn classic_codegen(opts: Rc, program: CompileForm) -> Result { + let language_flags = if opts.dialect().extra_numeric_constants { + NEW_BIT_CONSTANTS + } else { + 0 + }; + let runner = run_program_for_search_paths( + &opts.filename(), + &opts.get_search_paths(), + false, + language_flags, + ); + runner.set_compiler_opts(Some(opts.clone())); + + let mut allocator = SExpClassicAllocator::new(); + let macro_lookup = default_macro_lookup(&mut allocator, runner.clone()); + let nil = allocator + .import(program.loc(), clvm_rs::allocator::NodePtr::NIL) + .map_err(|e| CompileErr(e.0, e.1.to_string()))?; + let args = allocator + .from_sexp(program.to_sexp()) + .map_err(|e| CompileErr(e.0, e.1.to_string()))?; + let generated = compile_mod( + &mut allocator, + &args, + ¯o_lookup, + &nil, + runner.clone(), + 0, + ) + .map_err(|e| CompileErr(e.0, e.1.to_string()))?; + let optimized = optimize_sexp(&mut allocator, &generated, runner) + .map_err(|e| CompileErr(e.0, e.1.to_string()))?; + + // compile_mod produces a compiler program. Optimizing the primary module + // evaluates that program and quotes the resulting CLVM as constant data. + // Included modules still need the quote while they are being compiled. + if opts.module_phase().is_none() && program.loc.file.as_str() == opts.filename() { + if let ASExp::Pair(operator, compiled) = allocator.sexp(&optimized) { + if matches!(allocator.sexp(&operator), ASExp::Atom) + && allocator.atom(&operator).as_ref() == [1] + { + return Ok(compiled.sexp.as_ref().clone()); + } + } + } + + Ok(optimized.sexp.as_ref().clone()) +} + pub fn compile_from_compileform( context: &mut BasicCompileContext, opts: Rc, p0: CompileForm, ) -> Result { - let p1 = context.frontend_optimization(opts.clone(), p0)?; + let p1 = if opts.dialect().classic_codegen { + p0 + } else { + context.frontend_optimization(opts.clone(), p0)? + }; // Resolve includes, convert program source to lexemes let p2 = do_desugar(opts.clone(), &p1)?; @@ -747,7 +922,14 @@ pub fn compile_pre_forms( opts = opts.set_stdenv(dialect.strict).set_dialect(dialect); } - let p0 = frontend(opts.clone(), pre_forms)?; + let frontend_opts = if opts.dialect().classic_codegen { + let mut modern_dialect = opts.dialect(); + modern_dialect.classic_codegen = false; + opts.set_dialect(modern_dialect) + } else { + opts.clone() + }; + let p0 = frontend(frontend_opts, pre_forms)?; match p0 { FrontendOutput::CompileForm(p0) => Ok(CompilerOutput::Program( diff --git a/src/compiler/dialect.rs b/src/compiler/dialect.rs index dc9ee5ac5..8b7783de3 100644 --- a/src/compiler/dialect.rs +++ b/src/compiler/dialect.rs @@ -23,6 +23,8 @@ pub struct AcceptedDialect { pub extra_numeric_constants: bool, // Include fix for downstream cse dominance pub cse_dominance: bool, + // Use the modern frontend and desugaring with classic stage-2 code generation. + pub classic_codegen: bool, } /// A package containing the content we should insert when a dialect include is @@ -59,6 +61,7 @@ lazy_static! { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }, content: indoc! {"( (defconstant *chialisp-version* 22) @@ -75,6 +78,7 @@ lazy_static! { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }, content: indoc! {"( (defconstant *chialisp-version* 22) @@ -91,6 +95,7 @@ lazy_static! { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }, content: indoc! {"( (defconstant *chialisp-version* 23) @@ -107,6 +112,7 @@ lazy_static! { int_fix: true, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }, content: indoc! {"( (defconstant *chialisp-version* 23) @@ -123,6 +129,7 @@ lazy_static! { int_fix: true, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }, content: indoc! {"( (defconstant *chialisp-version* 24) @@ -139,6 +146,7 @@ lazy_static! { int_fix: true, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }, content: indoc! {"( (defconstant *chialisp-version* 25) @@ -155,6 +163,7 @@ lazy_static! { int_fix: true, extra_numeric_constants: true, cse_dominance: false, + classic_codegen: false, }, content: indoc! {"( (defconstant *chialisp-version* 25) @@ -171,6 +180,7 @@ lazy_static! { int_fix: true, extra_numeric_constants: true, cse_dominance: false, + classic_codegen: false, }, content: indoc! {"()"}.to_string(), }, @@ -184,6 +194,24 @@ lazy_static! { int_fix: true, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, + }, + content: indoc! {"( + (defconstant *chialisp-version* 26) + )"} + .to_string(), + }, + ), + ( + "*standard-cl-26-classic*", + DialectDescription { + accepted: AcceptedDialect { + stepping: Some(26), + strict: true, + int_fix: true, + extra_numeric_constants: false, + cse_dominance: false, + classic_codegen: true, }, content: indoc! {"( (defconstant *chialisp-version* 26) @@ -228,7 +256,7 @@ fn include_dialect(allocator: &Allocator, e: &[NodePtr]) -> Option AcceptedDialect { let mut result = AcceptedDialect::default(); - if let Some(l) = proper_list(allocator, sexp, true) { + if let Some(l) = proper_list(allocator, &sexp, true) { if l.len() == 2 { if let Some(dialect) = include_dialect(allocator, &l) { return dialect; diff --git a/src/compiler/optimize/mod.rs b/src/compiler/optimize/mod.rs index b2543f80f..1e800e678 100644 --- a/src/compiler/optimize/mod.rs +++ b/src/compiler/optimize/mod.rs @@ -22,6 +22,7 @@ use crate::classic::clvm::__type_compatibility__::bi_one; use crate::classic::clvm::__type_compatibility__::bi_zero; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; +use crate::classic::clvm_tools::stages::stage_2::abstraction::ClError; use crate::classic::clvm_tools::stages::stage_2::optimize::optimize_sexp; use crate::compiler::clvm::{convert_from_clvm_rs, convert_to_clvm_rs, run}; @@ -638,6 +639,8 @@ fn fe_opt( context: &mut BasicCompileContext, opts: Rc, compileform: CompileForm, + only_inline: bool, + use_main_env: bool, ) -> Result { let runner = context.runner(); let evaluator = Evaluator::new(opts.clone(), runner.clone(), compileform.helpers.clone()); @@ -670,13 +673,20 @@ fn fe_opt( } } let new_evaluator = Evaluator::new(opts.clone(), runner.clone(), optimized_helpers.clone()); + let mut main_env = HashMap::new(); + let main_args = if use_main_env { + build_reflex_captures(&mut main_env, compileform.args.clone()); + compileform.args.clone() + } else { + Rc::new(SExp::Nil(compileform.args.loc())) + }; let shrunk = new_evaluator.shrink_bodyform( context, - Rc::new(SExp::Nil(compileform.args.loc())), - &HashMap::new(), + main_args, + &main_env, compileform.exp.clone(), - true, + only_inline, Some(EVAL_STACK_LIMIT), )?; @@ -699,12 +709,12 @@ pub fn run_optimizer( RunFailure::RunExn(s, e) => CompileErr(s, format!("exception {e}\n")), })?; - let optimized = optimize_sexp(allocator, to_clvm_rs.1, runner) + let optimized = optimize_sexp(allocator, &to_clvm_rs.1, runner) .map_err(|e| { CompileErr( to_clvm_rs.0.clone(), match e { - EvalErr::InternalError(_, e) => e.to_string(), + ClError(l, EvalErr::InternalError(_, e)) => format!("{l}: {e}"), _ => e.to_string(), }, ) diff --git a/src/compiler/optimize/strategy.rs b/src/compiler/optimize/strategy.rs index 075c37056..c9de18bc3 100644 --- a/src/compiler/optimize/strategy.rs +++ b/src/compiler/optimize/strategy.rs @@ -43,7 +43,7 @@ impl Optimization for ExistingStrategy { Box::new(self.clone()), ); // Front end optimization - fe_opt(wrapper.context(), opts.clone(), p0) + fe_opt(wrapper.context(), opts.clone(), p0, true, false) } else { Ok(p0) } diff --git a/src/tests/classic/optimize.rs b/src/tests/classic/optimize.rs index f8e2e5517..3395bc29d 100644 --- a/src/tests/classic/optimize.rs +++ b/src/tests/classic/optimize.rs @@ -18,7 +18,7 @@ fn test_cons_q_a(src: String) -> String { let input_ir = read_ir(&src, 0).unwrap(); let assembled = assemble_from_ir(&mut allocator, Rc::new(input_ir)).unwrap(); let runner = run_program_for_search_paths("*test*", &vec![".".to_string()], false, 0); - let optimized = cons_q_a_optimizer(&mut allocator, &memo, assembled, runner.clone()).unwrap(); + let optimized = cons_q_a_optimizer(&mut allocator, &memo, &assembled, runner.clone()).unwrap(); disassemble(&mut allocator, optimized, Some(0)) } @@ -28,7 +28,7 @@ fn test_children_optimizer(src: String) -> String { let input_ir = read_ir(&src, 0).unwrap(); let assembled = assemble_from_ir(&mut allocator, Rc::new(input_ir)).unwrap(); let runner = run_program_for_search_paths("*test*", &vec![".".to_string()], false, 0); - let optimized = children_optimizer(&mut allocator, &memo, assembled, runner.clone()).unwrap(); + let optimized = children_optimizer(&mut allocator, &memo, &assembled, runner.clone()).unwrap(); disassemble(&mut allocator, optimized, Some(0)) } @@ -39,7 +39,7 @@ fn test_constant_optimizer(src: String) -> String { let assembled = assemble_from_ir(&mut allocator, Rc::new(input_ir)).unwrap(); let runner = run_program_for_search_paths("*test*", &vec![".".to_string()], false, 0); let optimized = - constant_optimizer(&mut allocator, &memo, assembled, 0, runner.clone()).unwrap(); + constant_optimizer(&mut allocator, &memo, &assembled, 0, runner.clone()).unwrap(); disassemble(&mut allocator, optimized, Some(0)) } @@ -48,7 +48,7 @@ fn test_optimizer(src: String) -> String { let input_ir = read_ir(&src, 0).unwrap(); let assembled = assemble_from_ir(&mut allocator, Rc::new(input_ir)).unwrap(); let runner = run_program_for_search_paths("*test*", &vec![".".to_string()], false, 0); - let optimized = optimize_sexp(&mut allocator, assembled, runner.clone()).unwrap(); + let optimized = optimize_sexp(&mut allocator, &assembled, runner.clone()).unwrap(); disassemble(&mut allocator, optimized, Some(0)) } @@ -58,7 +58,7 @@ fn test_sub_args(src: String) -> String { let assembled = assemble_from_ir(&mut allocator, Rc::new(input_ir)).unwrap(); match allocator.sexp(assembled) { SExp::Pair(a, b) => { - let optimized = sub_args(&mut allocator, a, b).unwrap(); + let optimized = sub_args(&mut allocator, &a, &b).unwrap(); disassemble(&mut allocator, optimized, Some(0)) } _ => { @@ -133,7 +133,7 @@ fn seems_constant_quote_test() { let src = "(q . 15)".to_string(); let input_ir = read_ir(&src, 0).unwrap(); let assembled = assemble_from_ir(&mut allocator, Rc::new(input_ir)).unwrap(); - assert_eq!(seems_constant(&mut allocator, assembled), true); + assert_eq!(seems_constant(&mut allocator, &assembled), true); } #[test] diff --git a/src/tests/classic/run.rs b/src/tests/classic/run.rs index 0d4129e11..42f873d35 100644 --- a/src/tests/classic/run.rs +++ b/src/tests/classic/run.rs @@ -2840,3 +2840,81 @@ fn test_big_env_program_overflow_and_fix() { program_from_earlier_chialisp_hex.trim() ); } + +#[test] +fn test_equivalence_smoke_modern_neoclassic() { + let program_neo = indoc! {" +(mod (A) + (include *standard-cl-26-classic*) + + (let ((B (+ A 1))) (* B A)) + ) + "} + .to_string(); + + let program_modern = program_neo.replace("cl-26-classic", "cl-26").to_string(); + let clvm_modern = do_basic_run(&vec!["run".to_string(), program_modern.to_string()]); + let clvm_neo = do_basic_run(&vec!["run".to_string(), program_neo.to_string()]); + + for a in 0..12 { + let brun_arg = format!("({a})"); + let output_modern = do_basic_brun(&vec![ + "brun".to_string(), + clvm_modern.to_string(), + brun_arg.to_string(), + ]); + let b = a + 1; + let expected = if a == 0 { + "()".to_string() + } else { + (a * b).to_string() + }; + assert_eq!(output_modern.trim(), expected); + + let output_neo = do_basic_brun(&vec!["brun".to_string(), clvm_neo.to_string(), brun_arg]); + assert_eq!(output_neo.trim(), output_modern.trim()); + } +} + +#[test] +fn test_equivalence_smoke_if_macro_modern_neoclassic() { + let program_neo = indoc! {" +(mod (A) + (include *standard-cl-26-classic*) + + (if A (* A 2) (+ A 1)) + ) + "} + .to_string(); + + let program_modern = program_neo.replace("cl-26-classic", "cl-26").to_string(); + let clvm_modern = do_basic_run(&vec!["run".to_string(), program_modern.to_string()]); + let clvm_neo = do_basic_run(&vec!["run".to_string(), program_neo.to_string()]); + + for a in 0..2 { + let brun_arg = format!("({a})"); + let output_modern = do_basic_brun(&vec![ + "brun".to_string(), + clvm_modern.to_string(), + brun_arg.to_string(), + ]); + let expected = if a == 0 { 1 } else { a * 2 }.to_string(); + assert_eq!(output_modern.trim(), expected); + + let output_neo = do_basic_brun(&vec!["brun".to_string(), clvm_neo.to_string(), brun_arg]); + assert_eq!(output_neo.trim(), output_modern.trim()); + } +} + +#[test] +fn test_equivalence_mandelbrot_classic() { + let program = fs::read_to_string("resources/tests/mandelbrot/mandelbrot.clsp").unwrap(); + let classic = program.replace("cl-21", "cl-26-classic").to_string(); + let mandelbrot_clvm = do_basic_run(&vec!["run".to_string(), classic.to_string()]); + let mandelbrot_smoke_run = do_basic_brun(&vec![ + "brun".to_string(), + mandelbrot_clvm, + "(16 16 32 32 16)".to_string(), + ]); + assert_eq!(mandelbrot_smoke_run.trim(), "\"||E\""); +} diff --git a/src/tests/classic/smoke.rs b/src/tests/classic/smoke.rs index 386cb3856..9c9c6f6c3 100644 --- a/src/tests/classic/smoke.rs +++ b/src/tests/classic/smoke.rs @@ -25,6 +25,7 @@ use crate::classic::clvm_tools::node_path::NodePath; use crate::classic::clvm_tools::pattern_match::match_sexp; use crate::classic::clvm_tools::stages; use crate::classic::clvm_tools::stages::stage_0::{DefaultProgramRunner, TRunProgram}; +use crate::classic::clvm_tools::stages::stage_2::abstraction::ClError; use crate::classic::clvm_tools::stages::stage_2::operators::run_program_for_search_paths; use crate::classic::clvm_tools::stages::stage_2::optimize::sub_args; use crate::classic::platform::argparse::{ @@ -699,10 +700,10 @@ fn test_check_simple_arg_path_0() { assert_eq!(cp.as_path().raw(), &[4]); } -fn assert_node_find_error(r: Result) { +fn assert_node_find_error(r: Result) { assert!(r.is_err()); } -fn assert_node_not_error(r: Result) -> X { +fn assert_node_not_error(r: Result) -> X { r.unwrap() } @@ -806,7 +807,7 @@ fn test_pattern_match_dollar_for_dollar() { let pattern = assemble(&mut allocator, "($ . $)").expect("should assemble"); let target_expr = assemble(&mut allocator, "$").expect("should assemble"); let empty_map = HashMap::new(); - let matched = match_sexp(&mut allocator, pattern, target_expr, empty_map.clone()); + let matched = match_sexp(&mut allocator, &pattern, &target_expr, empty_map.clone()); // Returns empty map. assert_eq!(Some(empty_map), matched); } @@ -817,7 +818,7 @@ fn test_pattern_match_colon_for_colon() { let pattern = assemble(&mut allocator, "(: . :)").expect("should assemble"); let target_expr = assemble(&mut allocator, ":").expect("should assemble"); let empty_map = HashMap::new(); - let matched = match_sexp(&mut allocator, pattern, target_expr, empty_map.clone()); + let matched = match_sexp(&mut allocator, &pattern, &target_expr, empty_map.clone()); // Returns empty map. assert_eq!(Some(empty_map), matched); } @@ -827,7 +828,7 @@ fn test_sub_args() { let mut allocator = Allocator::new(); let expr_sexp = assemble(&mut allocator, "(body 2 5)").expect("should assemble"); let new_args = assemble(&mut allocator, "(test1 test2)").expect("should assemble"); - let result = sub_args(&mut allocator, expr_sexp, new_args).expect("should run"); + let result = sub_args(&mut allocator, &expr_sexp, &new_args).expect("should run"); assert_eq!( disassemble(&mut allocator, result, None), "(\"body\" (f (\"test1\" \"test2\")) (f (r (\"test1\" \"test2\"))))" diff --git a/src/tests/classic/stage_2.rs b/src/tests/classic/stage_2.rs index 671346c4a..dd1ad14f8 100644 --- a/src/tests/classic/stage_2.rs +++ b/src/tests/classic/stage_2.rs @@ -38,13 +38,13 @@ fn test_expand_macro( let symbols_source = assemble_from_ir(allocator, Rc::new(symbols_ir)).unwrap(); let exp_res = try_expand_macro_for_atom( allocator, - macro_source, - prog_source, - macros_source, - symbols_source, + ¯o_source, + &prog_source, + ¯os_source, + &symbols_source, ) .unwrap(); - disassemble(allocator, exp_res.1, Some(0)) + disassemble(allocator, exp_res, Some(0)) } fn test_inner_expansion( @@ -73,8 +73,16 @@ fn test_do_com_prog( let macro_lookup = assemble_from_ir(allocator, Rc::new(macro_ir)).unwrap(); let sym_ir = read_ir(&symbol_table_src, 0).unwrap(); let symbol_table = assemble_from_ir(allocator, Rc::new(sym_ir)).unwrap(); - let result = do_com_prog(allocator, 849, program, macro_lookup, symbol_table, runner).unwrap(); - disassemble(allocator, result.1, Some(0)) + let result = do_com_prog( + allocator, + 849, + &program, + ¯o_lookup, + &symbol_table, + runner, + ) + .unwrap(); + disassemble(allocator, result, Some(0)) } #[test] @@ -150,7 +158,7 @@ fn test_compile_assert_2() { fn test_stage_2_quote() { let mut allocator = Allocator::new(); let assembled = assemble(&mut allocator, "(1 2 3)").unwrap(); - let quoted = quote(&mut allocator, assembled).unwrap(); + let quoted = quote(&mut allocator, &assembled).unwrap(); assert_eq!(disassemble(&mut allocator, quoted, Some(0)), "(q 1 2 3)"); } @@ -159,7 +167,7 @@ fn test_stage_2_evaluate() { let mut allocator = Allocator::new(); let prog = assemble(&mut allocator, "(q 16 2 3)").unwrap(); let args = assemble(&mut allocator, "(q 9 15)").unwrap(); - let to_eval = evaluate(&mut allocator, prog, args).unwrap(); + let to_eval = evaluate(&mut allocator, &prog, &args).unwrap(); assert_eq!( disassemble(&mut allocator, to_eval, Some(0)), "(a (q 16 2 3) (q 9 15))" @@ -171,7 +179,7 @@ fn test_stage_2_run() { let mut allocator = Allocator::new(); let prog = assemble(&mut allocator, "(q 16 2 3)").unwrap(); let macro_lookup_throw = assemble(&mut allocator, "(q 9)").unwrap(); - let to_eval = run(&mut allocator, prog, macro_lookup_throw).unwrap(); + let to_eval = run(&mut allocator, &prog, ¯o_lookup_throw).unwrap(); assert_eq!( disassemble(&mut allocator, to_eval, Some(0)), "(a (\"com\" (q 16 2 3) (q 1 9)) 1)" @@ -188,7 +196,7 @@ fn test_present_file_smoke_not_exists() { let res = read_file( runner, &mut allocator, - sexp_triggering_read, + &sexp_triggering_read, "test-embed-not-exist.clsp", ); assert!(res.is_err()); @@ -201,7 +209,7 @@ fn test_present_file_smoke_exists() { run_program_for_search_paths("*test*", &vec!["resources/tests".to_string()], false, 0); let sexp_triggering_read = assemble(&mut allocator, "(embed-file test-file sexp embed.sexp)") .expect("should assemble"); - let res = read_file(runner, &mut allocator, sexp_triggering_read, "embed.sexp") + let res = read_file(runner, &mut allocator, &sexp_triggering_read, "embed.sexp") .expect("should exist"); assert_eq!(decode_string(&res.data), "(23 24 25)"); } @@ -215,7 +223,7 @@ fn test_process_embed_file_as_sexp() { .expect("should assemble"); let want_exp = assemble(&mut allocator, "(q 23 24 25)").expect("should assemble"); let (name, content) = - process_embed_file(&mut allocator, runner, declaration_sexp).expect("should work"); + process_embed_file(&mut allocator, runner, &declaration_sexp).expect("should work"); assert_eq!( disassemble(&mut allocator, want_exp, Some(0)), disassemble(&mut allocator, content, Some(0)) @@ -240,7 +248,7 @@ fn test_process_embed_file_as_sexp_in_an_unexpected_location() { let res = read_file( runner, &mut allocator, - sexp_triggering_read, + &sexp_triggering_read, "fact.clvm.hex", ); assert!(res.is_err()); @@ -261,7 +269,7 @@ fn test_process_embed_file_as_sexp_in_an_expected_location() { let res = read_file( runner, &mut allocator, - sexp_triggering_read, + &sexp_triggering_read, "fact.clvm.hex", ) .expect("should exist"); @@ -281,8 +289,8 @@ fn test_process_embed_file_as_hex() { ) .expect("should assemble"); let (name, content) = - process_embed_file(&mut allocator, runner, declaration_sexp).expect("should work"); - let matching_part_of_decl = rest(&mut allocator, content).expect("should be quoted"); + process_embed_file(&mut allocator, runner, &declaration_sexp).expect("should work"); + let matching_part_of_decl = rest(&mut allocator, &content).expect("should be quoted"); let mut outstream = Stream::new(None); call_tool( &mut outstream, diff --git a/src/tests/compiler/cldb.rs b/src/tests/compiler/cldb.rs index 02d4128eb..364444771 100644 --- a/src/tests/compiler/cldb.rs +++ b/src/tests/compiler/cldb.rs @@ -125,6 +125,70 @@ fn test_run_clvm_in_cldb() { ); } +struct RecordsRecursiveSteps { + steps: Vec<(String, String)>, +} + +impl StepOfCldbViewer for RecordsRecursiveSteps { + fn show(&mut self, step: &RunStep, output: Option>) -> bool { + let arguments = output + .and_then(|values| values.get("Arguments").cloned()) + .unwrap_or_default(); + self.steps.push((step.loc().to_string(), arguments)); + true + } +} + +#[test] +fn test_classic_codegen_recursive_source_locations() { + let program_name = "classic_codegen_fact.clsp"; + let program_code = indoc! {" + (mod (N) + (include *standard-cl-26-classic*) + (defun fact (X) + (if (> X 1) + (* X (fact (- X 1))) + 1)) + (fact N)) + "}; + let mut allocator = Allocator::new(); + let runner = Rc::new(DefaultProgramRunner::new()); + let opts = Rc::new(DefaultCompilerOpts::new(program_name)); + let mut symbols = HashMap::new(); + let args = parse_sexp(Srcloc::start("*args*"), "(4)".bytes()).expect("should parse")[0].clone(); + let program = compile_file(&mut allocator, runner, opts, program_code, &mut symbols) + .expect("should compile"); + let mut watcher = RecordsRecursiveSteps { steps: Vec::new() }; + + assert_eq!( + run_clvm_in_cldb( + program_name, + Rc::new(program_code.lines().map(str::to_string).collect()), + Rc::new(program.to_sexp()), + symbols, + args, + &mut watcher, + 0, + ), + Some("24".to_string()) + ); + + let recursive_arguments: Vec<&str> = watcher + .steps + .iter() + .filter(|(loc, _)| loc == "classic_codegen_fact.clsp(4):10") + .map(|(_, arguments)| arguments.as_str()) + .collect(); + assert_eq!( + recursive_arguments, + vec!["(4 1)", "(3 1)", "(2 1)", "(1 1)"] + ); + assert!(watcher + .steps + .iter() + .any(|(loc, _)| loc.starts_with("classic_codegen_fact.clsp(5):"))); +} + #[test] fn test_cldb_hex_to_modern_sexp_smoke_0() { let mut allocator = Allocator::new(); @@ -288,6 +352,55 @@ fn test_cldb_hierarchy_mode() { compare_run_output(result, run_entries); } +#[test] +fn test_classic_codegen_mandelbrot() { + let input_file = "resources/tests/mandelbrot/mandelbrot.clsp"; + let input_program = fs::read_to_string(input_file) + .expect("test resource should exist") + .replace("*standard-cl-21*", "*standard-cl-26-classic*"); + let mut allocator = Allocator::new(); + let runner = Rc::new(DefaultProgramRunner::new()); + // The run and cldb commands both enable frontend optimization for + // stepping dialects newer than 22. + let cldb_opts = Rc::new(DefaultCompilerOpts::new(input_file)).set_optimize(true); + let mut symbols = HashMap::new(); + let cldb_program = compile_file( + &mut allocator, + runner.clone(), + cldb_opts, + &input_program, + &mut symbols, + ) + .expect("cldb should compile"); + let run_opts = Rc::new(DefaultCompilerOpts::new(input_file)).set_optimize(true); + let run_program = compile_file( + &mut allocator, + runner, + run_opts, + &input_program, + &mut HashMap::new(), + ) + .expect("run should compile"); + assert_eq!(cldb_program.to_sexp(), run_program.to_sexp()); + let args = parse_sexp(Srcloc::start("*args*"), "(-192 -128 -144 -96 8)".bytes()) + .expect("should parse args")[0] + .clone(); + let program_lines = Rc::new(input_program.lines().map(str::to_string).collect()); + + assert_eq!( + run_clvm_in_cldb( + input_file, + program_lines, + Rc::new(cldb_program.to_sexp()), + symbols, + args, + &mut DoesntWatchCldb {}, + 0, + ), + Some("3356114000950459963475899699747220812557867594760040767593731831711045".to_string()) + ); +} + #[test] fn test_execute_program_and_capture_arguments() { let compiled_symbols_text = diff --git a/src/tests/compiler/compiler.rs b/src/tests/compiler/compiler.rs index 91d4a5d92..959923a04 100644 --- a/src/tests/compiler/compiler.rs +++ b/src/tests/compiler/compiler.rs @@ -1,4 +1,5 @@ use std::collections::{BTreeSet, HashMap}; +use std::fs; use std::rc::Rc; use clvm_rs::allocator::Allocator; @@ -52,6 +53,7 @@ fn run_string_maybe_opt( int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }); } @@ -89,6 +91,91 @@ pub fn run_string_strict(content: &String, args: &String) -> Result, Co run_string_maybe_opt(content, args, false, true) } +#[test] +fn test_modern_frontend_with_classic_codegen_semantics() { + let program = indoc! {" + (mod (N) + (include *standard-cl-26-classic*) + (defun fact (X) + (if (> X 1) + (let ((NEXT (- X 1))) + (assign REST (fact NEXT) + (* X REST))) + 1)) + (defun apply-one (F X) (a F (list X))) + (apply-one (lambda (X) (fact X)) N)) + "} + .to_string(); + + assert_eq!( + run_string(&program, &"(5)".to_string()) + .unwrap() + .to_string(), + "120" + ); +} + +#[test] +fn test_classic_codegen_function_call_with_rest_tail() { + let program = indoc! {" + (mod (Xs) + (include *standard-cl-26-classic*) + (defun collect (A B C D) + (list A B C D)) + (collect 5 &rest Xs)) + "} + .to_string(); + + assert_eq!( + run_string(&program, &"((7 11 13))".to_string()) + .unwrap() + .to_string(), + "(5 7 11 13)" + ); +} + +#[test] +fn test_classic_codegen_environment_pseudo_variable() { + let program = indoc! {" + (mod (X) + (include *standard-cl-26-classic*) + (defun regular (A B) + (r @*env*)) + (defun-inline inlined (A B) + (r @*env*)) + (defun-inline destructured ((A B) C) + (r @*env*)) + (defun-inline variadic (A . Rest) + (r @*env*)) + (list + (regular X (+ X 1)) + (inlined (+ X 2) (+ X 3)) + (destructured (list X (+ X 1)) (+ X 2)) + (variadic (+ X 4) (+ X 5) (+ X 6)))) + "} + .to_string(); + + assert_eq!( + run_string(&program, &"(5)".to_string()) + .unwrap() + .to_string(), + "((5 6) (7 8) ((5 6) 7) (9 10 11))" + ); +} + +#[test] +fn test_classic_codegen_matches_modern_bls_program_result() { + let modern = + fs::read_to_string("resources/tests/bls/modern-bls-verify-signature.clsp").unwrap(); + let classic_codegen = modern.replace("*standard-cl-21*", "*standard-cl-26-classic*"); + let args = "(0x0102030405)".to_string(); + + assert_eq!( + run_string(&classic_codegen, &args).unwrap(), + run_string(&modern, &args).unwrap() + ); +} + // Given some renaming that leaves behind gensym style names with _$_ in them, // order them and use a locally predictable renaming scheme to give them a final // test checkable value. @@ -2432,6 +2519,7 @@ fn test_handle_explicit_empty_atom() { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }); let atom = |s: &str| Rc::new(SExp::Atom(srcloc.clone(), s.as_bytes().to_vec())); diff --git a/src/tests/compiler/optimizer/cse.rs b/src/tests/compiler/optimizer/cse.rs index 8b3f4c71e..5886ecb2d 100644 --- a/src/tests/compiler/optimizer/cse.rs +++ b/src/tests/compiler/optimizer/cse.rs @@ -888,6 +888,7 @@ fn test_generated_cse(n: u32) { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }); let opts23 = opts .set_dialect(AcceptedDialect { @@ -896,6 +897,7 @@ fn test_generated_cse(n: u32) { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }) .set_optimize(true); let mut symbols = HashMap::new(); diff --git a/src/tests/compiler/optimizer/cse_fuzz.rs b/src/tests/compiler/optimizer/cse_fuzz.rs index b9f26306a..140e5de07 100644 --- a/src/tests/compiler/optimizer/cse_fuzz.rs +++ b/src/tests/compiler/optimizer/cse_fuzz.rs @@ -249,6 +249,7 @@ impl PropertyTestState for TrickyAssignExpectation { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }) .set_optimize(true), ) diff --git a/src/tests/compiler/optimizer/cse_regression.rs b/src/tests/compiler/optimizer/cse_regression.rs index 87c2bb5e3..3c8cbbdfc 100644 --- a/src/tests/compiler/optimizer/cse_regression.rs +++ b/src/tests/compiler/optimizer/cse_regression.rs @@ -110,6 +110,7 @@ fn test_cse_merge_regression() { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }; let new_opts: Rc = Rc::new(DefaultCompilerOpts::new("test.clsp")) .set_dialect(dialect.clone()) diff --git a/src/tests/compiler/optimizer/deinline.rs b/src/tests/compiler/optimizer/deinline.rs index c66dd9bc1..c0b28a751 100644 --- a/src/tests/compiler/optimizer/deinline.rs +++ b/src/tests/compiler/optimizer/deinline.rs @@ -14,6 +14,7 @@ fn stepping_over_24_returns_true_for_module_compile_without_stepping() { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }); assert!( !stepping_over_24(opts.clone()), @@ -36,6 +37,7 @@ fn stepping_over_24_returns_false_for_stepping_23() { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }); assert!(!stepping_over_24(opts)); } @@ -49,6 +51,7 @@ fn stepping_over_24_returns_true_for_stepping_25() { int_fix: true, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }); assert!(stepping_over_24(opts)); } diff --git a/src/tests/compiler/optimizer/depgraph.rs b/src/tests/compiler/optimizer/depgraph.rs index 09b297cf8..bd11c92b3 100644 --- a/src/tests/compiler/optimizer/depgraph.rs +++ b/src/tests/compiler/optimizer/depgraph.rs @@ -17,6 +17,7 @@ fn get_depgraph_for_program(prog: &str) -> FunctionDependencyGraph { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }); let compileform = frontend(opts.clone(), &forms).expect("should frontend"); diff --git a/src/tests/compiler/optimizer/output.rs b/src/tests/compiler/optimizer/output.rs index 8df13bc52..dd33c1312 100644 --- a/src/tests/compiler/optimizer/output.rs +++ b/src/tests/compiler/optimizer/output.rs @@ -137,6 +137,7 @@ fn run_string_get_program_and_output_with_includes( int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }, optimize: false, fe_opt, @@ -338,6 +339,7 @@ const SPEC_23: OptimizationRunSpec = OptimizationRunSpec { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }, optimize: true, fe_opt: true, diff --git a/src/tests/compiler/preprocessor.rs b/src/tests/compiler/preprocessor.rs index c91ae6d9c..d9b8047c3 100644 --- a/src/tests/compiler/preprocessor.rs +++ b/src/tests/compiler/preprocessor.rs @@ -529,6 +529,7 @@ fn test_preprocess_basic_list() { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }); let mut includes = Vec::new(); let parsed_lst: Vec> = parsed_forms[0] @@ -578,6 +579,7 @@ fn test_preprocessor_tours_includes_properly() { int_fix: false, extra_numeric_constants: false, cse_dominance: false, + classic_codegen: false, }); let parsed = parse_sexp(Srcloc::start(pname), prog.bytes()).expect("should parse"); let parsed_lst: Vec> = parsed[0]