diff --git a/resources/tests/cldb_tree/hex.json b/resources/tests/cldb_tree/hex.json new file mode 100644 index 000000000..c75c4ca61 --- /dev/null +++ b/resources/tests/cldb_tree/hex.json @@ -0,0 +1,49 @@ +[ + { + "Compute": [ + { + "Function-Args": {}, + "Function-Name": "test_with_hex.clsp", + "Output": { + "Arguments": "(() ())", + "Operator": "4", + "Operator-Location": "test_with_hex.clsp(1):10-test_with_hex.clsp(1):16", + "Result-Location": "test_with_hex.clsp(1):10-test_with_hex.clsp(1):16", + "Function": "concat", + "Row": "0", + "Value": "(())" + } + } + ] + }, + { + "Compute": [ + { + "Function-Args": {}, + "Function-Name": "test_with_hex.clsp", + "Output": { + "Arguments": "(1 0x42e576f7)", + "Function-Context": "()", + "Function": "concat", + "Operator": "14", + "Operator-Location": "test_with_hex.clsp(1):17", + "Result-Location": "test_with_hex.clsp(1):10-test_with_hex.clsp(1):16", + "Row": "1", + "Value": "0x0142e576f7" + } + } + ] + }, + { + "Compute": [ + { + "Function-Args": {}, + "Function-Name": "test_with_hex.clsp", + "Output": { + "Final": "0x0142e576f7", + "Final-Location": "test_with_hex.clsp(1):10-test_with_hex.clsp(1):16" + } + } + ] + } +] diff --git a/resources/tests/cldb_tree/pre_hex.json b/resources/tests/cldb_tree/pre_hex.json new file mode 100644 index 000000000..a1df3be66 --- /dev/null +++ b/resources/tests/cldb_tree/pre_hex.json @@ -0,0 +1,49 @@ +[ + { + "Compute": [ + { + "Function-Args": {}, + "Function-Name": "test_with_hex.clsp", + "Output": { + "Arguments": "(() ())", + "Operator": "4", + "Operator-Location": "test_with_hex.clsp(1):10-test_with_hex.clsp(1):16", + "Result-Location": "test_with_hex.clsp(1):10-test_with_hex.clsp(1):16", + "Function": "concat", + "Row": "0", + "Value": "(())" + } + } + ] + }, + { + "Compute": [ + { + "Function-Args": {}, + "Function-Name": "test_with_hex.clsp", + "Output": { + "Arguments": "(1 1122334455)", + "Function-Context": "()", + "Function": "concat", + "Operator": "14", + "Operator-Location": "test_with_hex.clsp(1):17", + "Result-Location": "test_with_hex.clsp(1):10-test_with_hex.clsp(1):16", + "Row": "1", + "Value": "5417301751" + } + } + ] + }, + { + "Compute": [ + { + "Function-Args": {}, + "Function-Name": "test_with_hex.clsp", + "Output": { + "Final": "5417301751", + "Final-Location": "test_with_hex.clsp(1):10-test_with_hex.clsp(1):16" + } + } + ] + } +] diff --git a/resources/tests/test_string_repr.clsp b/resources/tests/test_string_repr.clsp new file mode 100644 index 000000000..f094bb247 --- /dev/null +++ b/resources/tests/test_string_repr.clsp @@ -0,0 +1,20 @@ +;; +;; This program is used by the test_quote_string_generation test +;; which checks the representation of these outputs and ensures +;; that they're properly accepted by brun, which uses the classic +;; chialisp tokenizer/parser. +;; +;; the qs macro appends the given string onto "test" +;; the atom macro gives a quoted atom starting with test and +;; ending with the text given in the string. +;; +;; modern's macro system makes these different objects. +;; +(mod () + (include *standard-cl-23*) + + (defmac qs (X) (string-append "test" X)) + (defmac atom (X) (c 1 (string->symbol (string-append "test" X)))) + + (list (qs '"') (qs "'") (qs " hi") (atom '"') (atom "'") (atom "_hi")) + ) diff --git a/src/classic/clvm_tools/cmds.rs b/src/classic/clvm_tools/cmds.rs index 1cc5bfecb..72dba212b 100644 --- a/src/classic/clvm_tools/cmds.rs +++ b/src/classic/clvm_tools/cmds.rs @@ -50,7 +50,9 @@ use crate::classic::platform::argparse::{ TArgOptionAction, TArgumentParserProps, }; -use crate::compiler::cldb::{hex_to_modern_sexp, CldbNoOverride, CldbRun, CldbRunEnv}; +use crate::compiler::cldb::{ + hex_to_modern_sexp, improve_presentation, CldbNoOverride, CldbRun, CldbRunEnv, FAVOR_HEX, +}; use crate::compiler::cldb_hierarchy::{HierarchialRunner, HierarchialStepResult, RunPurpose}; use crate::compiler::clvm::start_step; use crate::compiler::compiler::DefaultCompilerOpts; @@ -403,6 +405,8 @@ pub fn cldb_hierarchy(args: CldbHierarchyArgs) -> Vec Vec, result: &BTreeMap| { let mut cvt_subtree = BTreeMap::new(); for (k, v) in result.iter() { diff --git a/src/compiler/cldb.rs b/src/compiler/cldb.rs index d8bb942cf..82d715fd0 100644 --- a/src/compiler/cldb.rs +++ b/src/compiler/cldb.rs @@ -24,6 +24,8 @@ use crate::compiler::srcloc::Srcloc; use crate::util::u8_from_number; use crate::util::Number; +pub const FAVOR_HEX: u32 = 1; + fn print_atom() -> SExp { SExp::Atom(Srcloc::start("*print*"), b"$print$".to_vec()) } @@ -109,6 +111,7 @@ pub struct CldbRun { in_expr: bool, row: usize, print_only: bool, + flags: u32, outputs_to_step: HashMap, } @@ -133,6 +136,37 @@ fn humanize(a: Rc) -> Rc { } } +pub fn improve_presentation(a: Rc, flags: u32) -> Rc { + match a.borrow() { + SExp::Atom(l, av) => { + if av.len() > 2 && (flags & FAVOR_HEX) != 0 { + Rc::new(SExp::QuotedString(l.clone(), b'x', av.clone())) + } else { + a.clone() + } + } + SExp::Integer(l, i) => { + // If it has a nice string representation then show that. + let bytes_of_int = u8_from_number(i.clone()); + if bytes_of_int.len() > 2 && (flags & FAVOR_HEX) != 0 { + Rc::new(SExp::QuotedString(l.clone(), b'x', bytes_of_int)) + } else { + a.clone() + } + } + SExp::Cons(loc, l, r) => { + let new_l = improve_presentation(l.clone(), flags); + let new_r = improve_presentation(r.clone(), flags); + if Rc::as_ptr(&new_l) == Rc::as_ptr(l) && Rc::as_ptr(&new_r) == Rc::as_ptr(r) { + return a.clone(); + } + + Rc::new(SExp::Cons(loc.clone(), new_l, new_r)) + } + _ => a.clone(), + } +} + fn is_print_request(a: &SExp) -> Option<(Srcloc, Rc)> { if let SExp::Cons(l, f, r) = a { if &print_atom() == f.borrow() { @@ -166,9 +200,14 @@ impl CldbRun { row: 0, outputs_to_step: HashMap::::new(), print_only: false, + flags: 0, } } + pub fn set_flags(&mut self, flags: u32) { + self.flags = flags; + } + pub fn set_print_only(&mut self, pronly: bool) { self.print_only = pronly; } @@ -207,7 +246,10 @@ impl CldbRun { if self.should_print_basic_output() { self.to_print .insert("Result-Location".to_string(), l.to_string()); - self.to_print.insert("Value".to_string(), x.to_string()); + self.to_print.insert( + "Value".to_string(), + improve_presentation(x.clone(), self.flags).to_string(), + ); self.to_print .insert("Row".to_string(), self.row.to_string()); @@ -228,12 +270,14 @@ impl CldbRun { } } Ok(RunStep::Done(l, x)) => { + let final_value = improve_presentation(x.clone(), self.flags); self.to_print .insert("Final-Location".to_string(), l.to_string()); - self.to_print.insert("Final".to_string(), x.to_string()); + self.to_print + .insert("Final".to_string(), final_value.to_string()); self.ended = true; - self.final_result = Some(x.clone()); + self.final_result = Some(final_value); swap(&mut self.to_print, &mut result); produce_result = true; } @@ -269,8 +313,8 @@ impl CldbRun { if should_print_basic_output { self.env.add_context( sexp.borrow(), - c.borrow(), - Some(a.clone()), + improve_presentation(c.clone(), self.flags).borrow(), + Some(improve_presentation(a.clone(), self.flags)), &mut self.to_print, ); self.env.add_function(sexp, &mut self.to_print); @@ -281,7 +325,10 @@ impl CldbRun { Err(RunFailure::RunExn(l, s)) => { self.to_print .insert("Throw-Location".to_string(), l.to_string()); - self.to_print.insert("Throw".to_string(), s.to_string()); + self.to_print.insert( + "Throw".to_string(), + improve_presentation(s.clone(), self.flags).to_string(), + ); swap(&mut self.to_print, &mut result); self.ended = true; diff --git a/src/compiler/cldb_hierarchy.rs b/src/compiler/cldb_hierarchy.rs index ce84f3e7d..2b6e79bad 100644 --- a/src/compiler/cldb_hierarchy.rs +++ b/src/compiler/cldb_hierarchy.rs @@ -57,6 +57,7 @@ pub struct HierarchialRunner { input_file: Option, program_lines: Rc>, prog: Rc, + flags: u32, pub running: Vec, } @@ -233,6 +234,7 @@ impl HierarchialRunner { program_lines, error: false, prog: prog.clone(), + flags: 0, running: vec![HierarchyFrame { purpose: RunPurpose::Main, @@ -258,6 +260,13 @@ impl HierarchialRunner { } } + pub fn set_flags(&mut self, flags: u32) { + self.flags = flags; + for r in self.running.iter_mut() { + r.run.set_flags(flags); + } + } + pub fn is_ended(&self) -> bool { self.running.is_empty() || self.error @@ -267,7 +276,7 @@ impl HierarchialRunner { fn push_synthetic_stack_frame(&mut self, current_env: Rc, info: &RunStepRelevantInfo) { let arg_step = clvm::start_step(info.prog.clone(), info.runtime_argument_values.clone()); - let arg_run = CldbRun::new( + let mut arg_run = CldbRun::new( self.runner.clone(), self.prim_map.clone(), Box::new(CldbRunEnv::new( @@ -278,6 +287,8 @@ impl HierarchialRunner { arg_step, ); + arg_run.set_flags(self.flags); + let mut named_args = HashMap::new(); get_args_from_env( &mut named_args, @@ -306,7 +317,7 @@ impl HierarchialRunner { // Make an empty frame to repopulate (maybe option here?). let step = clvm::start_step(info.prog.clone(), current_env.clone()); - let run = CldbRun::new( + let mut run = CldbRun::new( self.runner.clone(), self.prim_map.clone(), Box::new(CldbRunEnv::new( @@ -317,6 +328,8 @@ impl HierarchialRunner { step, ); + run.set_flags(self.flags); + self.running.push(HierarchyFrame { purpose: RunPurpose::Main, @@ -380,6 +393,8 @@ impl HierarchialRunner { step, ); + self.running[idx].run.set_flags(self.flags); + Ok(HierarchialStepResult::ShapeChange) } else if let Some(info) = relevant_run_step_info(&self.symbol_table, ¤t_step) { // Create a frame based on the last argument. diff --git a/src/compiler/sexp.rs b/src/compiler/sexp.rs index 94368db17..eee39c7b5 100644 --- a/src/compiler/sexp.rs +++ b/src/compiler/sexp.rs @@ -372,9 +372,11 @@ pub fn decode_string(v: &[u8]) -> String { pub fn printable(a: &[u8], quoted: bool) -> bool { !a.iter().any(|ch| { - (*ch as char).is_control() - || !(*ch as char).is_ascii() - || (!quoted && ch.is_ascii_whitespace()) + *ch < 32 + || *ch > 126 + || (!quoted && ((*ch as char).is_ascii_whitespace() || *ch == b'\'')) + || *ch == b'"' + || *ch == b'\\' }) } diff --git a/src/tests/classic/run.rs b/src/tests/classic/run.rs index 7e573e13d..ec6a31f25 100644 --- a/src/tests/classic/run.rs +++ b/src/tests/classic/run.rs @@ -2429,6 +2429,41 @@ fn test_assign_cse_tricky_2() { assert_eq!(program, wanted_repr); } +#[test] +fn test_quote_string_generation() { + // The program run here produces a list of strings and quoted atoms that have + // representations that must be flattened in various ways in this test. + let filename = "resources/tests/test_string_repr.clsp"; + let program = do_basic_run(&vec!["run".to_string(), filename.to_string()]) + .trim() + .to_string(); + // The proram produces this list + // (list (qs '"') (qs "'") (qs " hi") (atom '"') (atom "'") (atom "_hi")) + // + // where qs puts "test" in front of the given string and atom puts test in front of the + // given string, converts it to an atom and quotes it so that it won't be interpreted + // as an identifier. + // + // in other words + // (list 'test"' "test'" "test hi" + // (q . (string->symbol (string-append "test" '"'))) + // (q . (string->symbol (string-append "test" "'"))) + // (q . test_hi) + // ) + // + // The result below shows that the strings and atoms are reproduced as expected. + let wanted_repr = "(4 (1 . 0x7465737422) (4 (1 . \"test'\") (4 (1 . \"test hi\") (4 (1 . 499918271522) (4 (1 . 499918271527) (4 (1 . test_hi) ()))))))"; + assert_eq!(program, wanted_repr); + let brun_result = do_basic_brun(&vec!["brun".to_string(), program]) + .trim() + .to_string(); + // This shows that brun interpreted and passed through the values successfully. + assert_eq!( + brun_result, + "(0x7465737422 \"test'\" \"test hi\" 0x7465737422 \"test'\" \"test_hi\")" + ); +} + #[test] fn test_classic_modpow() { let result = do_basic_brun(&vec![ diff --git a/src/tests/compiler/cldb.rs b/src/tests/compiler/cldb.rs index 83e6e5332..c22074a22 100644 --- a/src/tests/compiler/cldb.rs +++ b/src/tests/compiler/cldb.rs @@ -6,7 +6,7 @@ use clvmr::allocator::Allocator; use crate::classic::clvm_tools::cmds::{cldb_hierarchy, CldbHierarchyArgs, YamlElement}; use crate::classic::clvm_tools::stages::stage_0::DefaultProgramRunner; -use crate::compiler::cldb::{hex_to_modern_sexp, CldbNoOverride, CldbRun, CldbRunEnv}; +use crate::compiler::cldb::{hex_to_modern_sexp, CldbNoOverride, CldbRun, CldbRunEnv, FAVOR_HEX}; use crate::compiler::clvm::{start_step, RunStep}; use crate::compiler::compiler::{compile_file, DefaultCompilerOpts}; use crate::compiler::comptypes::CompilerOpts; @@ -68,6 +68,7 @@ where Box::new(CldbNoOverride::new_symbols(symbols)), ); let mut cldbrun = CldbRun::new(runner, Rc::new(prim_map), Box::new(cldbenv), step); + cldbrun.set_flags(flags); let mut output: BTreeMap = BTreeMap::new(); @@ -400,3 +401,57 @@ fn test_clvm_operator_with_weird_tail() { Some("8".to_string()) ); } + +#[test] +fn test_cldb_with_favor_hex() { + let filename = "favor_hex.clvm"; + let loc = Srcloc::start(filename); + let program = "(concat (1 . 1) (1 . 1122334455))"; + let parsed = parse_sexp(loc.clone(), program.as_bytes().iter().copied()).expect("should parse"); + let args = Rc::new(SExp::Nil(loc)); + let program_lines = Rc::new(vec![program.to_string()]); + + assert_eq!( + run_clvm_in_cldb( + filename, + program_lines, + parsed[0].clone(), + HashMap::new(), + args, + &mut DoesntWatchCldb {}, + FAVOR_HEX, + ), + Some("0x0142e576f7".to_string()) + ); +} + +#[test] +fn test_cldb_hierarchy_hex() { + let json_text = fs::read_to_string("resources/tests/cldb_tree/hex.json") + .expect("test resources should exist: test.json"); + let run_entries: Vec = + serde_json::from_str(&json_text).expect("should contain json"); + let input_program = "(mod () (concat 1 1122334455))".to_string(); + + let input_file = "test_with_hex.clsp"; + + let result = + compile_and_run_program_with_tree(&input_file, &input_program, "()", &vec![], FAVOR_HEX); + + compare_run_output(result, run_entries); +} + +#[test] +fn test_cldb_hierarchy_before_hex() { + let json_text = fs::read_to_string("resources/tests/cldb_tree/pre_hex.json") + .expect("test resources should exist: test.json"); + let run_entries: Vec = + serde_json::from_str(&json_text).expect("should contain json"); + let input_program = "(mod () (concat 1 1122334455))".to_string(); + + let input_file = "test_with_hex.clsp"; + + let result = compile_and_run_program_with_tree(&input_file, &input_program, "()", &vec![], 0); + + compare_run_output(result, run_entries); +} diff --git a/src/tests/compiler/compiler.rs b/src/tests/compiler/compiler.rs index 2e2a488ab..2a2e6cac9 100644 --- a/src/tests/compiler/compiler.rs +++ b/src/tests/compiler/compiler.rs @@ -4,17 +4,20 @@ use std::rc::Rc; use clvm_rs::allocator::Allocator; use crate::classic::clvm::__type_compatibility__::bi_one; +use crate::classic::clvm_tools::binutils::disassemble; use crate::classic::clvm_tools::stages::stage_0::DefaultProgramRunner; use crate::compiler::clvm::run; use crate::compiler::compiler::{compile_file, DefaultCompilerOpts}; use crate::compiler::comptypes::{CompileErr, CompilerOpts}; -use crate::compiler::dialect::AcceptedDialect; +use crate::compiler::dialect::{AcceptedDialect, KNOWN_DIALECTS}; use crate::compiler::frontend::{collect_used_names_sexp, frontend}; use crate::compiler::rename::rename_in_cons; use crate::compiler::runtypes::RunFailure; use crate::compiler::sexp::{decode_string, enlist, parse_sexp, SExp}; use crate::compiler::srcloc::Srcloc; +use crate::tests::classic::run::do_basic_brun; + const TEST_TIMEOUT: usize = 1000000; fn compile_string(content: &String) -> Result { @@ -2449,6 +2452,82 @@ fn test_almost_empty_lambda_gives_error() { assert!(format!("{res:?}").contains("Must provide at least arguments and body to lambda")); } +#[test] +fn test_exhaustive_chars() { + // Verify that we can create a program that gives the expected output using + // every byte value in the first, mid and last position of a value. + let mut substitute = vec![b'x', b'x', b'x']; + + let srcloc = Srcloc::start("*extest*"); + let make_test_program = |sub: Rc| { + // (mod () (include *standard-cl-23.1*) (q . )) + Rc::new(SExp::Cons( + srcloc.clone(), + Rc::new(SExp::Atom(srcloc.clone(), b"mod".to_vec())), + Rc::new(SExp::Cons( + srcloc.clone(), + Rc::new(SExp::Nil(srcloc.clone())), + Rc::new(SExp::Cons( + srcloc.clone(), + Rc::new(SExp::Cons( + srcloc.clone(), + Rc::new(SExp::Atom(srcloc.clone(), b"include".to_vec())), + Rc::new(SExp::Cons( + srcloc.clone(), + Rc::new(SExp::Atom(srcloc.clone(), b"*standard-cl-23.1*".to_vec())), + Rc::new(SExp::Nil(srcloc.clone())), + )), + )), + Rc::new(SExp::Cons( + srcloc.clone(), + Rc::new(SExp::Cons( + srcloc.clone(), + Rc::new(SExp::Integer(srcloc.clone(), bi_one())), + sub, + )), + Rc::new(SExp::Nil(srcloc.clone())), + )), + )), + )), + )) + }; + + let runner = Rc::new(DefaultProgramRunner::new()); + + for i in 0..=2 { + for j in 0..=255 { + substitute[i] = j; + + let sub_qe = Rc::new(SExp::QuotedString(srcloc.clone(), b'"', substitute.clone())); + + let mut allocator = Allocator::new(); + let mut opts: Rc = Rc::new(DefaultCompilerOpts::new("*extest*")); + let dialect = KNOWN_DIALECTS["*standard-cl-23.1*"].accepted.clone(); + opts = opts.set_dialect(dialect); + + let compiled = opts + .compile_program( + &mut allocator, + runner.clone(), + make_test_program(sub_qe), + &mut HashMap::new(), + ) + .expect("should compile"); + + let compiled_output = compiled.to_string(); + let result = do_basic_brun(&vec!["brun".to_string(), compiled_output]) + .trim() + .to_string(); + + let classic_atom = allocator.new_atom(&substitute).expect("should work"); + let disassembled = disassemble(&mut allocator, classic_atom, None); + assert_eq!(result, disassembled); + + substitute[i] = b'x'; + } + } +} + #[test] fn test_odd_hex_works() { let res = run_string(