diff --git a/README.md b/README.md index e4d6d354..14b205cf 100644 --- a/README.md +++ b/README.md @@ -1134,18 +1134,18 @@ record == "Bob X. Davis: age 42 ❤\n"; ### Built-in functions -The following standard functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings: +The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings: -| Function | Description | -| ---------- | ------------------------------------------------------------------------ | -| `len` | returns the number of characters (not number of bytes) in the string | -| `pad` | pads the string with an character until a specified number of characters | -| `append` | Adds a character or a string to the end of another string | -| `clear` | empties the string | -| `truncate` | cuts off the string at exactly a specified number of characters | -| `contains` | checks if a certain character or sub-string occurs in the string | -| `replace` | replaces a substring with another | -| `trim` | trims the string | +| Function | Parameter(s) | Description | +| ---------- | ------------------------------------- | -------------------------------------------------------------------- | +| `len` | _none_ | returns the number of characters (not number of bytes) in the string | +| `pad` | character to pad, target length | pads the string with an character to a specified length | +| `append` | character/string to append | Adds a character or a string to the end of another string | +| `clear` | _none_ | empties the string | +| `truncate` | target length | cuts off the string at exactly a specified number of characters | +| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | +| `replace` | target sub-string, replacement string | replaces a substring with another | +| `trim` | _none_ | trims the string of whitespace at the beginning and end | ### Examples @@ -1193,30 +1193,49 @@ Arrays are disabled via the [`no_index`] feature. ### Built-in functions -The following functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on arrays: +The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on arrays: -| Function | Description | -| ------------ | ------------------------------------------------------------------------------------- | -| `push` | inserts an element at the end | -| `append` | concatenates the second array to the end of the first | -| `+` operator | concatenates the first array with the second | -| `pop` | removes the last element and returns it ([`()`] if empty) | -| `shift` | removes the first element and returns it ([`()`] if empty) | -| `len` | returns the number of elements | -| `pad` | pads the array with an element until a specified length | -| `clear` | empties the array | -| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) | +| Function | Parameter(s) | Description | +| ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `push` | element to insert | inserts an element at the end | +| `append` | array to append | concatenates the second array to the end of the first | +| `+` operator | first array, second array | concatenates the first array with the second | +| `insert` | element to insert, position
(beginning if <= 0, end if >= length) | insert an element at a certain index | +| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | +| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | +| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | +| `len` | _none_ | returns the number of elements | +| `pad` | element to pad, target length | pads the array with an element until a specified length | +| `clear` | _none_ | empties the array | +| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | ### Examples ```rust -let y = [1, 2, 3]; // array literal with 3 elements -y[1] = 42; +let y = [2, 3]; // array literal with 2 elements -print(1 in y); // use 'in' to test if an item exists in the array, prints true -print(9 in y); // ... prints false +y.insert(0, 1); // insert element at the beginning +y.insert(999, 4); // insert element at the end -print(y[1]); // prints 42 +y.len() == 4; + +y[0] == 1; +y[1] == 2; +y[2] == 3; +y[3] == 4; + +(1 in y) == true; // use 'in' to test if an item exists in the array +(42 in y) == false; + +y[1] = 42; // array elements can be reassigned + +(42 in y) == true; + +y.remove(2) == 3; // remove element + +y.len() == 3; + +y[2] == 4; // elements after the removed element are shifted ts.list = y; // arrays can be assigned completely (by value copy) let foo = ts.list[1]; @@ -1238,7 +1257,7 @@ foo == 1; y.push(4); // 4 elements y.push(5); // 5 elements -print(y.len()); // prints 5 +y.len() == 5; let first = y.shift(); // remove the first element, 4 elements remaining first == 1; @@ -1246,7 +1265,7 @@ first == 1; let last = y.pop(); // remove the last element, 3 elements remaining last == 5; -print(y.len()); // prints 3 +y.len() == 3; for item in y { // arrays can be iterated with a 'for' statement print(item); @@ -1254,15 +1273,15 @@ for item in y { // arrays can be iterated with a 'for' statement y.pad(10, "hello"); // pad the array up to 10 elements -print(y.len()); // prints 10 +y.len() == 10; y.truncate(5); // truncate the array to 5 elements -print(y.len()); // prints 5 +y.len() == 5; y.clear(); // empty the array -print(y.len()); // prints 0 +y.len() == 0; ``` `push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered: @@ -1294,17 +1313,18 @@ Object maps are disabled via the [`no_object`] feature. ### Built-in functions -The following functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on object maps: +The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on object maps: -| Function | Description | -| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `has` | does the object map contain a property of a particular name? | -| `len` | returns the number of properties | -| `clear` | empties the object map | -| `mixin` | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | -| `+` operator | merges the first object map with the second | -| `keys` | returns an [array] of all the property names (in random order) | -| `values` | returns an [array] of all the property values (in random order) | +| Function | Parameter(s) | Description | +| ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `has` | property name | does the object map contain a property of a particular name? | +| `len` | _none_ | returns the number of properties | +| `clear` | _none_ | empties the object map | +| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | +| `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | +| `+` operator | first object map, second object map | merges the first object map with the second | +| `keys` | _none_ | returns an [array] of all the property names (in random order) | +| `values` | _none_ | returns an [array] of all the property values (in random order) | ### Examples @@ -1322,12 +1342,12 @@ y.a = 42; // access via dot notation y.baz!$@ = 42; // <- syntax error: only proper variable names allowed in dot notation y."baz!$@" = 42; // <- syntax error: strings not allowed in dot notation -print(y.a); // prints 42 +y.a == 42; -print(y["baz!$@"]); // prints 123.456 - access via index notation +y["baz!$@"] == 123.456; // access via index notation -print("baz!$@" in y); // use 'in' to test if a property exists in the object map, prints true -print("z" in y); // ... prints false +"baz!$@" in y == true; // use 'in' to test if a property exists in the object map, prints true +("z" in y) == false; ts.obj = y; // object maps can be assigned completely (by value copy) let foo = ts.list.a; @@ -1352,7 +1372,12 @@ y.has("xyz") == false; y.xyz == (); // a non-existing property returns '()' y["xyz"] == (); -print(y.len()); // prints 3 +y.len() == 3; + +y.remove("a") == 1; // remove property + +y.len() == 2; +y.has("a") == false; for name in keys(y) { // get an array of all the property names via the 'keys' function print(name); @@ -1364,7 +1389,7 @@ for val in values(y) { // get an array of all the property values via the 'valu y.clear(); // empty the object map -print(y.len()); // prints 0 +y.len() == 0; ``` ### Parsing from JSON diff --git a/src/builtin.rs b/src/builtin.rs index dfc709c2..e95dac04 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -620,15 +620,9 @@ impl Engine<'_> { #[cfg(not(feature = "no_object"))] { - self.register_fn(KEYWORD_PRINT, |x: &mut Map| -> String { - format!("#{:?}", x) - }); - self.register_fn(FUNC_TO_STRING, |x: &mut Map| -> String { - format!("#{:?}", x) - }); - self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String { - format!("#{:?}", x) - }); + self.register_fn(KEYWORD_PRINT, |x: &mut Map| format!("#{:?}", x)); + self.register_fn(FUNC_TO_STRING, |x: &mut Map| format!("#{:?}", x)); + self.register_fn(KEYWORD_DEBUG, |x: &mut Map| format!("#{:?}", x)); // Register map access functions #[cfg(not(feature = "no_index"))] @@ -874,6 +868,15 @@ impl Engine<'_> { fn push(list: &mut Array, item: T) { list.push(Box::new(item)); } + fn ins(list: &mut Array, position: INT, item: T) { + if position <= 0 { + list.insert(0, Box::new(item)); + } else if (position as usize) >= list.len() - 1 { + push(list, item); + } else { + list.insert(position as usize, Box::new(item)); + } + } fn pad(list: &mut Array, len: INT, item: T) { if len >= 0 { while list.len() < len as usize { @@ -886,6 +889,7 @@ impl Engine<'_> { reg_fn2x!(self, "push", push, &mut Array, (), String, Array, ()); reg_fn3!(self, "pad", pad, &mut Array, INT, (), INT, bool, char); reg_fn3!(self, "pad", pad, &mut Array, INT, (), String, Array, ()); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), String, Array, ()); self.register_fn("append", |list: &mut Array, array: Array| { list.extend(array) @@ -902,12 +906,15 @@ impl Engine<'_> { reg_fn2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), i8, u8, i16, u16); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), i32, i64, u32, u64); } #[cfg(not(feature = "no_float"))] { reg_fn2x!(self, "push", push, &mut Array, (), f32, f64); reg_fn3!(self, "pad", pad, &mut Array, INT, (), f32, f64); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), f32, f64); } self.register_dynamic_fn("pop", |list: &mut Array| { @@ -920,6 +927,13 @@ impl Engine<'_> { list.remove(0) } }); + self.register_dynamic_fn("remove", |list: &mut Array, len: INT| { + if len < 0 || (len as usize) >= list.len() { + ().into_dynamic() + } else { + list.remove(len as usize) + } + }); self.register_fn("len", |list: &mut Array| list.len() as INT); self.register_fn("clear", |list: &mut Array| list.clear()); self.register_fn("truncate", |list: &mut Array, len: INT| { @@ -935,6 +949,9 @@ impl Engine<'_> { self.register_fn("has", |map: &mut Map, prop: String| map.contains_key(&prop)); self.register_fn("len", |map: &mut Map| map.len() as INT); self.register_fn("clear", |map: &mut Map| map.clear()); + self.register_dynamic_fn("remove", |x: &mut Map, name: String| { + x.remove(&name).unwrap_or(().into_dynamic()) + }); self.register_fn("mixin", |map1: &mut Map, map2: Map| { map2.into_iter().for_each(|(key, value)| { map1.insert(key, value); diff --git a/tests/arrays.rs b/tests/arrays.rs index 33568460..a41f0a63 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -16,20 +16,26 @@ fn test_arrays() -> Result<(), EvalAltResult> { assert_eq!( engine.eval::( r" - let x = [1, 2, 3]; - let y = [4, 5]; - x.append(y); - x.len() + let x = [2, 9]; + x.insert(-1, 1); + x.insert(999, 3); + + let r = x.remove(2); + + let y = [4, 5]; + x.append(y); + + x.len() + r " )?, - 5 + 14 ); assert_eq!( engine.eval::( r" - let x = [1, 2, 3]; - x += [4, 5]; - x.len() + let x = [1, 2, 3]; + x += [4, 5]; + x.len() " )?, 5 @@ -38,9 +44,9 @@ fn test_arrays() -> Result<(), EvalAltResult> { engine .eval::( r" - let x = [1, 2, 3]; - let y = [4, 5]; - x + y + let x = [1, 2, 3]; + let y = [4, 5]; + x + y " )? .len(), diff --git a/tests/maps.rs b/tests/maps.rs index 2002d420..ac591966 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -33,6 +33,16 @@ fn test_map_indexing() -> Result<(), EvalAltResult> { assert!(engine.eval::("let y = #{a: 1, b: 2, c: 3}; 'b' in y")?); assert!(!engine.eval::(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?); + assert_eq!( + engine.eval::( + r#" + let x = #{a: 1, b: 2, c: 3}; + let c = x.remove("c"); + x.len() + c + "# + )?, + 5 + ); assert_eq!( engine.eval::( r"