diff --git a/README.md b/README.md
index e99847c0..30878dce 100644
--- a/README.md
+++ b/README.md
@@ -272,6 +272,7 @@ The following primitive types are supported natively:
| **Unicode character** | `char` | `"char"` |
| **Unicode string** | `String` (_not_ `&str`) | `"string"` |
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` |
+| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` |
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ |
| **System number** (current configuration) | `rhai::INT` (`i32` or `i64`),
`rhai::FLOAT` (`f32` or `f64`) | _same as type_ |
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` |
@@ -728,6 +729,8 @@ let a = { 40 + 2 }; // 'a' is set to the value of the statement block, which
Variables
---------
+[variables]: #variables
+
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter, and must start with an ASCII letter before a digit.
@@ -760,7 +763,7 @@ x == 42; // the parent block's 'x' is not changed
Constants
---------
-Constants can be defined using the `const` keyword and are immutable. Constants follow the same naming rules as [variables](#variables).
+Constants can be defined using the `const` keyword and are immutable. Constants follow the same naming rules as [variables].
```rust
const x = 42;
@@ -999,7 +1002,7 @@ let foo = [1, 2, 3][0];
foo == 1;
fn abc() {
- [42, 43, 44] // a function returning an array literal
+ [42, 43, 44] // a function returning an array
}
let foo = abc()[0];
@@ -1040,6 +1043,84 @@ print(y.len()); // prints 0
engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(item)) );
```
+Object maps
+-----------
+
+Object maps are dictionaries. Properties of any type (`Dynamic`) can be freely added and retrieved.
+Object map literals are built within braces '`${`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust)
+and separated by commas '`,`'. The property _name_ can be a simple variable name following the same
+naming rules as [variables], or an arbitrary string literal.
+
+Property values can be accessed via the dot notation (_object_ `.` _property_) or index notation (_object_ `[` _property_ `]`).
+The dot notation allows only property names that follow the same naming rules as [variables].
+The index notation allows setting/getting properties of arbitrary names (even the empty string).
+
+**Important:** Trying to read a non-existent property returns `()` instead of causing an error.
+
+The Rust type of a Rhai object map is `rhai::Map`.
+
+[`type_of()`] an object map returns `"map"`.
+
+Object maps are disabled via the [`no_object`] feature.
+
+The following functions (defined in the standard library but excluded if [`no_stdlib`]) 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 |
+
+Examples:
+
+```rust
+let y = ${ // object map literal with 3 properties
+ a: 1,
+ bar: "hello",
+ "baz!$@": 123.456, // like JS, you can use any string as property names...
+ "": false, // even the empty string!
+
+ a: 42 // <- syntax error: duplicated property name
+};
+
+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
+
+print(y["baz!$@"]); // prints 123.456 - access via index notation
+
+ts.obj = y; // object maps can be assigned completely (by value copy)
+let foo = ts.list.a;
+foo == 42;
+
+let foo = ${ a:1, b:2, c:3 }["a"];
+foo == 1;
+
+fn abc() {
+ ${ a:1, b:2, c:3 } // a function returning an object map
+}
+
+let foo = abc().b;
+foo == 2;
+
+let foo = y["a"];
+foo == 42;
+
+y.has("a") == true;
+y.has("xyz") == false;
+
+y.xyz == (); // A non-existing property returns '()'
+y["xyz"] == ();
+
+print(y.len()); // prints 3
+
+y.clear(); // empty the object map
+
+print(y.len()); // prints 0
+```
+
Comparison operators
--------------------
diff --git a/src/builtin.rs b/src/builtin.rs
index 96aab8c2..4f297572 100644
--- a/src/builtin.rs
+++ b/src/builtin.rs
@@ -10,6 +10,9 @@ use crate::result::EvalAltResult;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
+#[cfg(not(feature = "no_object"))]
+use crate::engine::Map;
+
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
@@ -596,14 +599,20 @@ impl Engine<'_> {
#[cfg(not(feature = "no_index"))]
{
- reg_fn1!(self, "print", debug, String, Array);
- reg_fn1!(self, "debug", debug, String, Array);
+ self.register_fn("print", |x: &mut Array| -> String { format!("{:?}", x) });
+ self.register_fn("debug", |x: &mut Array| -> String { format!("{:?}", x) });
// Register array iterator
self.register_iterator::(|a| {
Box::new(a.downcast_ref::().unwrap().clone().into_iter())
});
}
+
+ #[cfg(not(feature = "no_object"))]
+ {
+ self.register_fn("print", |x: &mut Map| -> String { format!("${:?}", x) });
+ self.register_fn("debug", |x: &mut Map| -> String { format!("${:?}", x) });
+ }
}
// Register range function
@@ -822,6 +831,14 @@ impl Engine<'_> {
});
}
+ // Register map functions
+ #[cfg(not(feature = "no_object"))]
+ {
+ 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());
+ }
+
// Register string concatenate functions
fn prepend(x: T, y: String) -> String {
format!("{}{}", x, y)
diff --git a/src/engine.rs b/src/engine.rs
index 4fd465df..2b24b099 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -26,6 +26,10 @@ use crate::stdlib::{
#[cfg(not(feature = "no_index"))]
pub type Array = Vec;
+/// An dynamic hash map of `Dynamic` values.
+#[cfg(not(feature = "no_object"))]
+pub type Map = HashMap;
+
pub type FnCallArgs<'a> = [&'a mut Variant];
pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result;
@@ -45,6 +49,8 @@ pub(crate) const FUNC_SETTER: &str = "set$";
#[cfg(not(feature = "no_index"))]
enum IndexSourceType {
Array,
+ #[cfg(not(feature = "no_object"))]
+ Map,
String,
Expression,
}
@@ -156,6 +162,8 @@ impl Default for Engine<'_> {
let type_names = [
#[cfg(not(feature = "no_index"))]
(type_name::(), "array"),
+ #[cfg(not(feature = "no_object"))]
+ (type_name::