diff --git a/resources/tests/test_string_repr.clsp b/resources/tests/test_string_repr.clsp new file mode 100644 index 00000000..f094bb24 --- /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/compiler/sexp.rs b/src/compiler/sexp.rs index 94368db1..eee39c7b 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 7e573e13..ec6a31f2 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/compiler.rs b/src/tests/compiler/compiler.rs index 2e2a488a..2a2e6cac 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(