Fix BLOB and string operations.

This commit is contained in:
Stephen Chung 2022-07-20 21:17:21 +08:00
parent 8215c75a17
commit 753e527cbb
7 changed files with 289 additions and 105 deletions

View File

@ -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). * `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 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. * `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. * 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 Version 1.8.0

170
scripts/static.d.rhai Normal file
View File

@ -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) -> ?;

View File

@ -293,10 +293,15 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
_ => None, _ => None,
}; };
} }
if type1 == type2 { if type2 == TypeId::of::<char>() {
return match op { return match op {
"==" => Some(impl_op!(Blob == Blob)), "+" => Some(|_, args| {
"!=" => Some(impl_op!(Blob != Blob)), let mut buf = [0_u8; 4];
let mut blob = args[0].read_lock::<Blob>().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, _ => None,
}; };
} }
@ -503,6 +508,33 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
}; };
} }
#[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() {
use crate::Blob;
return match op {
"+" => Some(|_, args| {
let blob1 = &*args[0].read_lock::<Blob>().expect(BUILTIN);
let blob2 = &*args[1].read_lock::<Blob>().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::<()>() { if type1 == TypeId::of::<()>() {
return match op { return match op {
"==" => Some(|_, _| Ok(Dynamic::TRUE)), "==" => 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"))] #[cfg(not(feature = "no_index"))]
{ {
// string op= blob use crate::Blob;
if types_pair == (TypeId::of::<ImmutableString>(), TypeId::of::<crate::Blob>()) {
return match op {
"+=" => Some(|_, args| {
let buf = {
let x = args[1].read_lock::<crate::Blob>().expect(BUILTIN);
if x.is_empty() {
return Ok(Dynamic::UNIT);
}
let s = args[0].read_lock::<ImmutableString>().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::<ImmutableString>().expect(BUILTIN);
*s = buf.into();
Ok(Dynamic::UNIT)
}),
_ => None,
};
}
// blob op= int
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<INT>()) {
use crate::Blob;
// blob op= int
if types_pair == (TypeId::of::<Blob>(), TypeId::of::<INT>()) {
return match op { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8; let x = args[1].as_int().expect("`INT`");
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN); let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(blob.push(x).into()) Ok(crate::packages::blob_basic::blob_functions::push(blob, x).into())
}), }),
_ => None, _ => None,
}; };
} }
// blob op= char // blob op= char
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<char>()) { if types_pair == (TypeId::of::<Blob>(), TypeId::of::<char>()) {
use crate::Blob;
return match op { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let mut buf = [0_u8; 4]; let x = args[1].as_char().expect("`char`");
let x = args[1].as_char().expect("`char`").encode_utf8(&mut buf); let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN); Ok(crate::packages::blob_basic::blob_functions::append_char(blob, x).into())
Ok(blob.extend(x.as_bytes()).into())
}), }),
_ => None, _ => None,
}; };
} }
// blob op= string // blob op= string
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<ImmutableString>()) { if types_pair == (TypeId::of::<Blob>(), TypeId::of::<ImmutableString>()) {
use crate::Blob;
return match op { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let s: crate::Blob = { let s = std::mem::take(args[1]).cast::<ImmutableString>();
let s = args[1].read_lock::<ImmutableString>().expect(BUILTIN); let blob = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
if s.is_empty() { Ok(crate::packages::blob_basic::blob_functions::append_str(blob, &s).into())
return Ok(Dynamic::UNIT);
}
s.as_bytes().into()
};
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(blob.extend(s).into())
}), }),
_ => None, _ => None,
}; };
@ -838,14 +838,13 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() { if type1 == TypeId::of::<crate::Blob>() {
use crate::packages::blob_basic::blob_functions::*;
use crate::Blob; use crate::Blob;
return match op { return match op {
"+=" => Some(|_, args| { "+=" => Some(|_, args| {
let blob2 = std::mem::take(args[1]).cast::<Blob>(); let blob2 = std::mem::take(args[1]).cast::<Blob>();
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN); let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(append(blob1, blob2).into()) Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into())
}), }),
_ => None, _ => None,
}; };

View File

@ -213,9 +213,9 @@ fn optimize_stmt_block(
// Flatten blocks // Flatten blocks
loop { loop {
if let Some(n) = statements.iter().enumerate().find_map(|(i, s)| match s { if let Some(n) = statements.iter().position(|s| match s {
Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => Some(i), Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent) => true,
_ => None, _ => false,
}) { }) {
let (first, second) = statements.split_at_mut(n); let (first, second) = statements.split_at_mut(n);
let stmt = mem::take(&mut second[0]); let stmt = mem::take(&mut second[0]);

View File

@ -8,7 +8,7 @@ use crate::{
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{any::TypeId, mem}; use std::{any::TypeId, borrow::Cow, mem};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::{FLOAT, FLOAT_BYTES}; use crate::{FLOAT, FLOAT_BYTES};
@ -104,6 +104,27 @@ pub mod blob_functions {
pub fn to_array(blob: &mut Blob) -> Array { pub fn to_array(blob: &mut Blob) -> Array {
blob.iter().map(|&ch| (ch as INT).into()).collect() 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. /// Return the length of the BLOB.
/// ///
/// # Example /// # Example
@ -200,6 +221,7 @@ pub mod blob_functions {
/// ///
/// print(b); // prints "[42]" /// print(b); // prints "[42]"
/// ``` /// ```
#[rhai_fn(name = "push", name = "append")]
pub fn push(blob: &mut Blob, value: INT) { pub fn push(blob: &mut Blob, value: INT) {
blob.push((value & 0x000000ff) as u8); blob.push((value & 0x000000ff) as u8);
} }
@ -235,13 +257,13 @@ pub mod blob_functions {
/// ///
/// print(b); // prints "[424242424268656c 6c6f]" /// print(b); // prints "[424242424268656c 6c6f]"
/// ``` /// ```
#[rhai_fn(name = "+=", name = "append")] #[rhai_fn(name = "append")]
pub fn append_str(blob: &mut Blob, string: &str) { pub fn append_str(blob: &mut Blob, string: &str) {
if !string.is_empty() { if !string.is_empty() {
blob.extend(string.as_bytes()); 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 /// # Example
/// ///
@ -252,38 +274,12 @@ pub mod blob_functions {
/// ///
/// print(b); // prints "[424242424221]" /// print(b); // prints "[424242424221]"
/// ``` /// ```
#[rhai_fn(name = "+=", name = "append")] #[rhai_fn(name = "append")]
pub fn append_char(blob: &mut Blob, character: char) { pub fn append_char(blob: &mut Blob, character: char) {
let mut buf = [0_u8; 4]; let mut buf = [0_u8; 4];
let x = character.encode_utf8(&mut buf); let x = character.encode_utf8(&mut buf);
blob.extend(x.as_bytes()); 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. /// 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). /// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte).

View File

@ -4,20 +4,20 @@ use crate::{Module, Shared};
pub(crate) mod arithmetic; pub(crate) mod arithmetic;
pub(crate) mod array_basic; pub(crate) mod array_basic;
mod bit_field; pub(crate) mod bit_field;
pub(crate) mod blob_basic; pub(crate) mod blob_basic;
mod debugging; pub(crate) mod debugging;
mod fn_basic; pub(crate) mod fn_basic;
pub(crate) mod iter_basic; pub(crate) mod iter_basic;
mod lang_core; pub(crate) mod lang_core;
mod logic; pub(crate) mod logic;
mod map_basic; pub(crate) mod map_basic;
mod math_basic; pub(crate) mod math_basic;
mod pkg_core; pub(crate) mod pkg_core;
mod pkg_std; pub(crate) mod pkg_std;
mod string_basic; pub(crate) mod string_basic;
mod string_more; pub(crate) mod string_more;
mod time_basic; pub(crate) mod time_basic;
pub use arithmetic::ArithmeticPackage; pub use arithmetic::ArithmeticPackage;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]

View File

@ -87,25 +87,42 @@ mod string_functions {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub mod blob_functions { pub mod blob_functions {
#[rhai_fn(name = "+", pure)] #[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() { if utf8.is_empty() {
string.clone() return string.clone();
} else if string.is_empty() { }
String::from_utf8_lossy(&utf8).into_owned().into()
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 { } else {
let mut s = crate::SmartString::from(string.as_str()); let mut x = SmartString::from(string.as_str());
s.push_str(&String::from_utf8_lossy(&utf8)); x.push_str(s.as_ref());
s.into() x.into()
} }
} }
#[rhai_fn(name = "append")] #[rhai_fn(name = "+=", name = "append")]
pub fn add_blob(string: &mut ImmutableString, utf8: Blob) { pub fn add(string: &mut ImmutableString, utf8: Blob) {
let mut s = crate::SmartString::from(string.as_str()); let mut s = crate::SmartString::from(string.as_str());
if !utf8.is_empty() { if !utf8.is_empty() {
s.push_str(&String::from_utf8_lossy(&utf8)); s.push_str(&String::from_utf8_lossy(&utf8));
*string = s.into(); *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. /// Return the length of the string, in number of characters.