diff --git a/CHANGELOG.md b/CHANGELOG.md index 88db90a7..3a082f37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Bug fixes --------- * `switch` cases with conditions that evaluate to constant `()` no longer optimize to `false` (should raise a type error during runtime). +* Fixes concatenation of BLOB's and strings, where the BLOB's should be interpreted as UTF-8 encoded strings. New features ------------ @@ -30,6 +31,7 @@ Enhancements * `EvalContext::eval_expression_tree_raw` and `Expression::eval_with_context_raw` are added to allow for not rewinding the `Scope` at the end of a statements block. * A new `range` function variant that takes an exclusive range with a step. +* `as_string` is added to BLOB's to convert it into a string by interpreting it as a UTF-8 byte stream. Version 1.8.0 diff --git a/scripts/static.d.rhai b/scripts/static.d.rhai new file mode 100644 index 00000000..b6c104dc --- /dev/null +++ b/scripts/static.d.rhai @@ -0,0 +1,170 @@ +/// This definition file extends the scope of all scripts. +/// +/// The items defined here simply exist and are available. +/// everywhere. +/// +/// These definitions should be used for built-in functions and +/// local domain-specific environment-provided values. +module static; + +/// Display any data to the standard output. +/// +/// # Example +/// +/// ```rhai +/// let answer = 42; +/// +/// print(`The Answer is ${answer}`); +/// ``` +fn print(data: ?); + +/// Display any data to the standard output in debug format. +/// +/// # Example +/// +/// ```rhai +/// let answer = 42; +/// +/// debug(answer); +/// ``` +fn debug(data: ?); + +/// Get the type of a value. +/// +/// # Example +/// +/// ```rhai +/// let x = "hello, world!"; +/// +/// print(x.type_of()); // prints "string" +/// ``` +fn type_of(data: ?) -> String; + +/// Create a function pointer to a named function. +/// +/// If the specified name is not a valid function name, an error is raised. +/// +/// # Example +/// +/// ```rhai +/// let f = Fn("foo"); // function pointer to 'foo' +/// +/// f.call(42); // call: foo(42) +/// ``` +fn Fn(fn_name: String) -> FnPtr; + +/// Call a function pointed to by a function pointer, +/// passing following arguments to the function call. +/// +/// If an appropriate function is not found, an error is raised. +/// +/// # Example +/// +/// ```rhai +/// let f = Fn("foo"); // function pointer to 'foo' +/// +/// f.call(1, 2, 3); // call: foo(1, 2, 3) +/// ``` +fn call(fn_ptr: FnPtr, ...args: ?) -> ?; + +/// Call a function pointed to by a function pointer, binding the `this` pointer +/// to the object of the method call, and passing on following arguments to the function call. +/// +/// If an appropriate function is not found, an error is raised. +/// +/// # Example +/// +/// ```rhai +/// fn add(x) { +/// this + x +/// } +/// +/// let f = Fn("add"); // function pointer to 'add' +/// +/// let x = 41; +/// +/// let r = x.call(f, 1); // call: add(1) with 'this' = 'x' +/// +/// print(r); // prints 42 +/// ``` +fn call(obj: ?, fn_ptr: FnPtr, ...args: ?) -> ?; + +/// Curry a number of arguments into a function pointer and return it as a new function pointer. +/// +/// # Example +/// +/// ```rhai +/// fn foo(x, y, z) { +/// x + y + z +/// } +/// +/// let f = Fn("foo"); +/// +/// let g = f.curry(1, 2); // curried arguments: 1, 2 +/// +/// g.call(3); // call: foo(1, 2, 3) +/// ``` +fn curry(fn_ptr: FnPtr, ...args: ?) -> FnPtr; + +/// Return `true` if a script-defined function exists with a specified name and +/// number of parameters. +/// +/// # Example +/// +/// ```rhai +/// fn foo(x) { } +/// +/// print(is_def_fn("foo", 1)); // prints true +/// print(is_def_fn("foo", 2)); // prints false +/// print(is_def_fn("foo", 0)); // prints false +/// print(is_def_fn("bar", 1)); // prints false +/// ``` +fn is_def_fn(fn_name: String, num_params: i64) -> bool; + +/// Return `true` if a variable matching a specified name is defined. +/// +/// # Example +/// +/// ```rhai +/// let x = 42; +/// +/// print(is_def_var("x")); // prints true +/// print(is_def_var("foo")); // prints false +/// +/// { +/// let y = 1; +/// print(is_def_var("y")); // prints true +/// } +/// +/// print(is_def_var("y")); // prints false +/// ``` +fn is_def_var(var_name: String) -> bool; + +/// Return `true` if the variable is shared. +/// +/// # Example +/// +/// ```rhai +/// let x = 42; +/// +/// print(is_shared(x)); // prints false +/// +/// let f = || x; // capture 'x', making it shared +/// +/// print(is_shared(x)); // prints true +/// ``` +fn is_shared(variable: ?) -> bool; + +/// Evaluate a text script within the current scope. +/// +/// # Example +/// +/// ```rhai +/// let x = 42; +/// +/// eval("let y = x; x = 123;"); +/// +/// print(x); // prints 123 +/// print(y); // prints 42 +/// ``` +fn eval(script: String) -> ?; diff --git a/src/func/builtin.rs b/src/func/builtin.rs index 5477893c..3e728764 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -293,10 +293,15 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option None, }; } - if type1 == type2 { + if type2 == TypeId::of::() { return match op { - "==" => Some(impl_op!(Blob == Blob)), - "!=" => Some(impl_op!(Blob != Blob)), + "+" => Some(|_, args| { + let mut buf = [0_u8; 4]; + let mut blob = args[0].read_lock::().expect(BUILTIN).clone(); + let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf); + blob.extend(x.as_bytes()); + Ok(Dynamic::from_blob(blob)) + }), _ => None, }; } @@ -503,6 +508,33 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option() { + use crate::Blob; + + return match op { + "+" => Some(|_, args| { + let blob1 = &*args[0].read_lock::().expect(BUILTIN); + let blob2 = &*args[1].read_lock::().expect(BUILTIN); + + Ok(Dynamic::from_blob(if !blob2.is_empty() { + if blob1.is_empty() { + blob2.clone() + } else { + let mut blob = blob1.clone(); + blob.extend(blob2); + blob + } + } else { + blob1.clone() + })) + }), + "==" => Some(impl_op!(Blob == Blob)), + "!=" => Some(impl_op!(Blob != Blob)), + _ => None, + }; + } + if type1 == TypeId::of::<()>() { return match op { "==" => Some(|_, _| Ok(Dynamic::TRUE)), @@ -684,71 +716,39 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio #[cfg(not(feature = "no_index"))] { - // string op= blob - if types_pair == (TypeId::of::(), TypeId::of::()) { - return match op { - "+=" => Some(|_, args| { - let buf = { - let x = args[1].read_lock::().expect(BUILTIN); - if x.is_empty() { - return Ok(Dynamic::UNIT); - } - let s = args[0].read_lock::().expect(BUILTIN); - let mut buf = crate::SmartString::from(s.as_str()); - buf.push_str(&String::from_utf8_lossy(&x)); - buf - }; - let mut s = args[0].write_lock::().expect(BUILTIN); - *s = buf.into(); - Ok(Dynamic::UNIT) - }), - _ => None, - }; - } - // blob op= int - if types_pair == (TypeId::of::(), TypeId::of::()) { - use crate::Blob; + use crate::Blob; + // blob op= int + if types_pair == (TypeId::of::(), TypeId::of::()) { return match op { "+=" => Some(|_, args| { - let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8; - let mut blob = args[0].write_lock::().expect(BUILTIN); - Ok(blob.push(x).into()) + let x = args[1].as_int().expect("`INT`"); + let blob = &mut *args[0].write_lock::().expect(BUILTIN); + Ok(crate::packages::blob_basic::blob_functions::push(blob, x).into()) }), _ => None, }; } // blob op= char - if types_pair == (TypeId::of::(), TypeId::of::()) { - use crate::Blob; - + if types_pair == (TypeId::of::(), TypeId::of::()) { return match op { "+=" => Some(|_, args| { - let mut buf = [0_u8; 4]; - let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf); - let mut blob = args[0].write_lock::().expect(BUILTIN); - Ok(blob.extend(x.as_bytes()).into()) + let x = args[1].as_char().expect("`char`"); + let blob = &mut *args[0].write_lock::().expect(BUILTIN); + Ok(crate::packages::blob_basic::blob_functions::append_char(blob, x).into()) }), _ => None, }; } // blob op= string - if types_pair == (TypeId::of::(), TypeId::of::()) { - use crate::Blob; - + if types_pair == (TypeId::of::(), TypeId::of::()) { return match op { "+=" => Some(|_, args| { - let s: crate::Blob = { - let s = args[1].read_lock::().expect(BUILTIN); - if s.is_empty() { - return Ok(Dynamic::UNIT); - } - s.as_bytes().into() - }; - let mut blob = args[0].write_lock::().expect(BUILTIN); - Ok(blob.extend(s).into()) + let s = std::mem::take(args[1]).cast::(); + let blob = &mut *args[0].write_lock::().expect(BUILTIN); + Ok(crate::packages::blob_basic::blob_functions::append_str(blob, &s).into()) }), _ => None, }; @@ -838,14 +838,13 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { - use crate::packages::blob_basic::blob_functions::*; use crate::Blob; return match op { "+=" => Some(|_, args| { let blob2 = std::mem::take(args[1]).cast::(); let blob1 = &mut *args[0].write_lock::().expect(BUILTIN); - Ok(append(blob1, blob2).into()) + Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into()) }), _ => None, }; diff --git a/src/optimizer.rs b/src/optimizer.rs index ebc029aa..ad3058c5 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -213,9 +213,9 @@ fn optimize_stmt_block( // Flatten blocks loop { - if let Some(n) = statements.iter().enumerate().find_map(|(i, s)| match s { - Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => Some(i), - _ => None, + if let Some(n) = statements.iter().position(|s| match s { + Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => true, + _ => false, }) { let (first, second) = statements.split_at_mut(n); let stmt = mem::take(&mut second[0]); diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index 6cd8ebff..ad26ccac 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -8,7 +8,7 @@ use crate::{ }; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{any::TypeId, mem}; +use std::{any::TypeId, borrow::Cow, mem}; #[cfg(not(feature = "no_float"))] use crate::{FLOAT, FLOAT_BYTES}; @@ -104,6 +104,27 @@ pub mod blob_functions { pub fn to_array(blob: &mut Blob) -> Array { blob.iter().map(|&ch| (ch as INT).into()).collect() } + /// Convert the BLOB into a string. + /// + /// The byte stream must be valid UTF-8, otherwise an error is raised. + /// + /// # Example + /// + /// ```rhai + /// let b = blob(5, 0x42); + /// + /// let x = b.as_string(); + /// + /// print(x); // prints "FFFFF" + /// ``` + pub fn as_string(blob: Blob) -> String { + let s = String::from_utf8_lossy(&blob); + + match s { + Cow::Borrowed(_) => String::from_utf8(blob).unwrap(), + Cow::Owned(_) => s.into_owned(), + } + } /// Return the length of the BLOB. /// /// # Example @@ -200,6 +221,7 @@ pub mod blob_functions { /// /// print(b); // prints "[42]" /// ``` + #[rhai_fn(name = "push", name = "append")] pub fn push(blob: &mut Blob, value: INT) { blob.push((value & 0x000000ff) as u8); } @@ -235,13 +257,13 @@ pub mod blob_functions { /// /// print(b); // prints "[424242424268656c 6c6f]" /// ``` - #[rhai_fn(name = "+=", name = "append")] + #[rhai_fn(name = "append")] pub fn append_str(blob: &mut Blob, string: &str) { if !string.is_empty() { blob.extend(string.as_bytes()); } } - /// Add a string (as UTF-8 encoded byte-stream) to the end of the BLOB + /// Add a character (as UTF-8 encoded byte-stream) to the end of the BLOB /// /// # Example /// @@ -252,38 +274,12 @@ pub mod blob_functions { /// /// print(b); // prints "[424242424221]" /// ``` - #[rhai_fn(name = "+=", name = "append")] + #[rhai_fn(name = "append")] pub fn append_char(blob: &mut Blob, character: char) { let mut buf = [0_u8; 4]; let x = character.encode_utf8(&mut buf); blob.extend(x.as_bytes()); } - /// Add another BLOB to the end of the BLOB, returning it as a new BLOB. - /// - /// # Example - /// - /// ```rhai - /// let b1 = blob(5, 0x42); - /// let b2 = blob(3, 0x11); - /// - /// print(b1 + b2); // prints "[4242424242111111]" - /// - /// print(b1); // prints "[4242424242]" - /// ``` - #[rhai_fn(name = "+")] - pub fn concat(blob1: Blob, blob2: Blob) -> Blob { - if !blob2.is_empty() { - if blob1.is_empty() { - blob2 - } else { - let mut blob = blob1; - blob.extend(blob2); - blob - } - } else { - blob1 - } - } /// Add a byte `value` to the BLOB at a particular `index` position. /// /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte). diff --git a/src/packages/mod.rs b/src/packages/mod.rs index a4d4d612..853ec95a 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -4,20 +4,20 @@ use crate::{Module, Shared}; pub(crate) mod arithmetic; pub(crate) mod array_basic; -mod bit_field; +pub(crate) mod bit_field; pub(crate) mod blob_basic; -mod debugging; -mod fn_basic; +pub(crate) mod debugging; +pub(crate) mod fn_basic; pub(crate) mod iter_basic; -mod lang_core; -mod logic; -mod map_basic; -mod math_basic; -mod pkg_core; -mod pkg_std; -mod string_basic; -mod string_more; -mod time_basic; +pub(crate) mod lang_core; +pub(crate) mod logic; +pub(crate) mod map_basic; +pub(crate) mod math_basic; +pub(crate) mod pkg_core; +pub(crate) mod pkg_std; +pub(crate) mod string_basic; +pub(crate) mod string_more; +pub(crate) mod time_basic; pub use arithmetic::ArithmeticPackage; #[cfg(not(feature = "no_index"))] diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 6a0c2cad..7f751d06 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -87,25 +87,42 @@ mod string_functions { #[cfg(not(feature = "no_index"))] pub mod blob_functions { #[rhai_fn(name = "+", pure)] - pub fn add_append_blob(string: &mut ImmutableString, utf8: Blob) -> ImmutableString { + pub fn add_append(string: &mut ImmutableString, utf8: Blob) -> ImmutableString { if utf8.is_empty() { - string.clone() - } else if string.is_empty() { - String::from_utf8_lossy(&utf8).into_owned().into() + return string.clone(); + } + + let s = String::from_utf8_lossy(&utf8); + + if string.is_empty() { + match s { + std::borrow::Cow::Borrowed(_) => String::from_utf8(utf8).unwrap(), + std::borrow::Cow::Owned(_) => s.into_owned(), + } + .into() } else { - let mut s = crate::SmartString::from(string.as_str()); - s.push_str(&String::from_utf8_lossy(&utf8)); - s.into() + let mut x = SmartString::from(string.as_str()); + x.push_str(s.as_ref()); + x.into() } } - #[rhai_fn(name = "append")] - pub fn add_blob(string: &mut ImmutableString, utf8: Blob) { + #[rhai_fn(name = "+=", name = "append")] + pub fn add(string: &mut ImmutableString, utf8: Blob) { let mut s = crate::SmartString::from(string.as_str()); if !utf8.is_empty() { s.push_str(&String::from_utf8_lossy(&utf8)); *string = s.into(); } } + #[rhai_fn(name = "+")] + pub fn add_prepend(utf8: Blob, string: ImmutableString) -> Blob { + let mut blob = utf8; + + if !string.is_empty() { + blob.extend(string.as_bytes()); + } + blob + } } /// Return the length of the string, in number of characters.