Merge branch 'object_maps'
This commit is contained in:
commit
0a8b324fec
85
README.md
85
README.md
@ -272,6 +272,7 @@ The following primitive types are supported natively:
|
|||||||
| **Unicode character** | `char` | `"char"` |
|
| **Unicode character** | `char` | `"char"` |
|
||||||
| **Unicode string** | `String` (_not_ `&str`) | `"string"` |
|
| **Unicode string** | `String` (_not_ `&str`) | `"string"` |
|
||||||
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` |
|
| **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_ |
|
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ |
|
||||||
| **System number** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) | _same as type_ |
|
| **System number** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) | _same as type_ |
|
||||||
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` |
|
| **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
|
||||||
|
|
||||||
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
|
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.
|
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
|
||||||
---------
|
---------
|
||||||
|
|
||||||
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
|
```rust
|
||||||
const x = 42;
|
const x = 42;
|
||||||
@ -999,7 +1002,7 @@ let foo = [1, 2, 3][0];
|
|||||||
foo == 1;
|
foo == 1;
|
||||||
|
|
||||||
fn abc() {
|
fn abc() {
|
||||||
[42, 43, 44] // a function returning an array literal
|
[42, 43, 44] // a function returning an array
|
||||||
}
|
}
|
||||||
|
|
||||||
let foo = abc()[0];
|
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)) );
|
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
|
Comparison operators
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@ use crate::result::EvalAltResult;
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::engine::Array;
|
use crate::engine::Array;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
use crate::engine::Map;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::parser::FLOAT;
|
use crate::parser::FLOAT;
|
||||||
|
|
||||||
@ -596,14 +599,20 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
{
|
{
|
||||||
reg_fn1!(self, "print", debug, String, Array);
|
self.register_fn("print", |x: &mut Array| -> String { format!("{:?}", x) });
|
||||||
reg_fn1!(self, "debug", debug, String, Array);
|
self.register_fn("debug", |x: &mut Array| -> String { format!("{:?}", x) });
|
||||||
|
|
||||||
// Register array iterator
|
// Register array iterator
|
||||||
self.register_iterator::<Array, _>(|a| {
|
self.register_iterator::<Array, _>(|a| {
|
||||||
Box::new(a.downcast_ref::<Array>().unwrap().clone().into_iter())
|
Box::new(a.downcast_ref::<Array>().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
|
// 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
|
// Register string concatenate functions
|
||||||
fn prepend<T: Display>(x: T, y: String) -> String {
|
fn prepend<T: Display>(x: T, y: String) -> String {
|
||||||
format!("{}{}", x, y)
|
format!("{}{}", x, y)
|
||||||
|
297
src/engine.rs
297
src/engine.rs
@ -26,6 +26,10 @@ use crate::stdlib::{
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub type Array = Vec<Dynamic>;
|
pub type Array = Vec<Dynamic>;
|
||||||
|
|
||||||
|
/// An dynamic hash map of `Dynamic` values.
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub type Map = HashMap<String, Dynamic>;
|
||||||
|
|
||||||
pub type FnCallArgs<'a> = [&'a mut Variant];
|
pub type FnCallArgs<'a> = [&'a mut Variant];
|
||||||
|
|
||||||
pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
|
pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
|
||||||
@ -45,6 +49,8 @@ pub(crate) const FUNC_SETTER: &str = "set$";
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
enum IndexSourceType {
|
enum IndexSourceType {
|
||||||
Array,
|
Array,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Map,
|
||||||
String,
|
String,
|
||||||
Expression,
|
Expression,
|
||||||
}
|
}
|
||||||
@ -156,6 +162,8 @@ impl Default for Engine<'_> {
|
|||||||
let type_names = [
|
let type_names = [
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
(type_name::<Array>(), "array"),
|
(type_name::<Array>(), "array"),
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
(type_name::<Map>(), "map"),
|
||||||
(type_name::<String>(), "string"),
|
(type_name::<String>(), "string"),
|
||||||
(type_name::<Dynamic>(), "dynamic"),
|
(type_name::<Dynamic>(), "dynamic"),
|
||||||
]
|
]
|
||||||
@ -325,6 +333,14 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(prop) = extract_prop_from_getter(fn_name) {
|
if let Some(prop) = extract_prop_from_getter(fn_name) {
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
{
|
||||||
|
// Map property access
|
||||||
|
if let Some(map) = args[0].downcast_ref::<Map>() {
|
||||||
|
return Ok(map.get(prop).cloned().unwrap_or_else(|| ().into_dynamic()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Getter function not found
|
// Getter function not found
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!("- property '{}' unknown or write-only", prop),
|
format!("- property '{}' unknown or write-only", prop),
|
||||||
@ -333,6 +349,17 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(prop) = extract_prop_from_setter(fn_name) {
|
if let Some(prop) = extract_prop_from_setter(fn_name) {
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
{
|
||||||
|
let val = args[1].into_dynamic();
|
||||||
|
|
||||||
|
// Map property update
|
||||||
|
if let Some(map) = args[0].downcast_mut::<Map>() {
|
||||||
|
map.insert(prop.to_string(), val);
|
||||||
|
return Ok(().into_dynamic());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Setter function not found
|
// Setter function not found
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!("- property '{}' unknown or read-only", prop),
|
format!("- property '{}' unknown or read-only", prop),
|
||||||
@ -420,21 +447,17 @@ impl Engine<'_> {
|
|||||||
// xxx.idx_lhs[idx_expr]
|
// xxx.idx_lhs[idx_expr]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
||||||
let (val, _) = match idx_lhs.as_ref() {
|
let val = match idx_lhs.as_ref() {
|
||||||
// xxx.id[idx_expr]
|
// xxx.id[idx_expr]
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = make_getter(id);
|
let get_fn_name = make_getter(id);
|
||||||
let this_ptr = get_this_ptr(scope, src, target);
|
let this_ptr = get_this_ptr(scope, src, target);
|
||||||
(
|
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?,
|
|
||||||
*pos,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
// xxx.???[???][idx_expr]
|
// xxx.???[???][idx_expr]
|
||||||
Expr::Index(_, _, _) => (
|
Expr::Index(_, _, _) => {
|
||||||
self.get_dot_val_helper(scope, src, target, idx_lhs, level)?,
|
self.get_dot_val_helper(scope, src, target, idx_lhs, level)?
|
||||||
*op_pos,
|
}
|
||||||
),
|
|
||||||
// Syntax error
|
// Syntax error
|
||||||
_ => {
|
_ => {
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
@ -444,9 +467,8 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)
|
||||||
self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
|
.map(|(v, _, _)| v)
|
||||||
.map(|(v, _)| v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// xxx.dot_lhs.rhs
|
// xxx.dot_lhs.rhs
|
||||||
@ -464,21 +486,17 @@ impl Engine<'_> {
|
|||||||
// xxx.idx_lhs[idx_expr].rhs
|
// xxx.idx_lhs[idx_expr].rhs
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
||||||
let (val, _) = match idx_lhs.as_ref() {
|
let val = match idx_lhs.as_ref() {
|
||||||
// xxx.id[idx_expr].rhs
|
// xxx.id[idx_expr].rhs
|
||||||
Expr::Property(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = make_getter(id);
|
let get_fn_name = make_getter(id);
|
||||||
let this_ptr = get_this_ptr(scope, src, target);
|
let this_ptr = get_this_ptr(scope, src, target);
|
||||||
(
|
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?,
|
|
||||||
*pos,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
// xxx.???[???][idx_expr].rhs
|
// xxx.???[???][idx_expr].rhs
|
||||||
Expr::Index(_, _, _) => (
|
Expr::Index(_, _, _) => {
|
||||||
self.get_dot_val_helper(scope, src, target, idx_lhs, level)?,
|
self.get_dot_val_helper(scope, src, target, idx_lhs, level)?
|
||||||
*op_pos,
|
}
|
||||||
),
|
|
||||||
// Syntax error
|
// Syntax error
|
||||||
_ => {
|
_ => {
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
@ -488,9 +506,8 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)
|
||||||
self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
|
.and_then(|(mut v, _, _)| {
|
||||||
.and_then(|(mut v, _)| {
|
|
||||||
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level)
|
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -521,7 +538,7 @@ impl Engine<'_> {
|
|||||||
match dot_lhs {
|
match dot_lhs {
|
||||||
// id.???
|
// id.???
|
||||||
Expr::Variable(id, pos) => {
|
Expr::Variable(id, pos) => {
|
||||||
let (entry, _) = Self::search_scope(scope, id, Ok, *pos)?;
|
let (entry, _) = Self::search_scope(scope, id, *pos)?;
|
||||||
|
|
||||||
// Avoid referencing scope which is used below as mut
|
// Avoid referencing scope which is used below as mut
|
||||||
let entry = ScopeSource { name: id, ..entry };
|
let entry = ScopeSource { name: id, ..entry };
|
||||||
@ -534,7 +551,7 @@ impl Engine<'_> {
|
|||||||
// idx_lhs[idx_expr].???
|
// idx_lhs[idx_expr].???
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
||||||
let (src_type, src, idx, mut target) =
|
let (idx_src_type, src, idx, mut target) =
|
||||||
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
|
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
|
||||||
let this_ptr = target.as_mut();
|
let this_ptr = target.as_mut();
|
||||||
let val = self.get_dot_val_helper(scope, None, Some(this_ptr), dot_rhs, level);
|
let val = self.get_dot_val_helper(scope, None, Some(this_ptr), dot_rhs, level);
|
||||||
@ -550,7 +567,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
ScopeEntryType::Normal => {
|
ScopeEntryType::Normal => {
|
||||||
Self::update_indexed_var_in_scope(
|
Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
idx_src_type,
|
||||||
scope,
|
scope,
|
||||||
src,
|
src,
|
||||||
idx,
|
idx,
|
||||||
@ -573,61 +590,85 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a variable within the scope, returning its value and index inside the Scope
|
/// Search for a variable within the scope, returning its value and index inside the Scope
|
||||||
fn search_scope<'a, T>(
|
fn search_scope<'a>(
|
||||||
scope: &'a Scope,
|
scope: &'a Scope,
|
||||||
id: &str,
|
id: &str,
|
||||||
convert: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
|
|
||||||
begin: Position,
|
begin: Position,
|
||||||
) -> Result<(ScopeSource<'a>, T), EvalAltResult> {
|
) -> Result<(ScopeSource<'a>, Dynamic), EvalAltResult> {
|
||||||
scope
|
scope
|
||||||
.get(id)
|
.get(id)
|
||||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
||||||
.and_then(move |(src, value)| convert(value).map(|v| (src, v)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluate the value of an index (must evaluate to INT)
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
fn eval_index_value(
|
|
||||||
&mut self,
|
|
||||||
scope: &mut Scope,
|
|
||||||
idx_expr: &Expr,
|
|
||||||
level: usize,
|
|
||||||
) -> Result<INT, EvalAltResult> {
|
|
||||||
self.eval_expr(scope, idx_expr, level)?
|
|
||||||
.downcast::<INT>()
|
|
||||||
.map(|v| *v)
|
|
||||||
.map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value at the indexed position of a base type
|
/// Get the value at the indexed position of a base type
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn get_indexed_value(
|
fn get_indexed_value(
|
||||||
&self,
|
&mut self,
|
||||||
|
scope: &mut Scope,
|
||||||
val: &Dynamic,
|
val: &Dynamic,
|
||||||
idx: INT,
|
idx_expr: &Expr,
|
||||||
idx_pos: Position,
|
|
||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
) -> Result<(Dynamic, IndexSourceType), EvalAltResult> {
|
level: usize,
|
||||||
|
) -> Result<(Dynamic, IndexSourceType, (Option<usize>, Option<String>)), EvalAltResult> {
|
||||||
|
let idx_pos = idx_expr.position();
|
||||||
|
|
||||||
if val.is::<Array>() {
|
if val.is::<Array>() {
|
||||||
// val_array[idx]
|
// val_array[idx]
|
||||||
let arr = val.downcast_ref::<Array>().expect("array expected");
|
let arr = val.downcast_ref::<Array>().expect("array expected");
|
||||||
|
|
||||||
if idx >= 0 {
|
let idx = *self
|
||||||
|
.eval_expr(scope, idx_expr, level)?
|
||||||
|
.downcast::<INT>()
|
||||||
|
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
|
||||||
|
|
||||||
|
return if idx >= 0 {
|
||||||
arr.get(idx as usize)
|
arr.get(idx as usize)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|v| (v, IndexSourceType::Array))
|
.map(|v| (v, IndexSourceType::Array, (Some(idx as usize), None)))
|
||||||
.ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
|
.ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
|
||||||
} else {
|
} else {
|
||||||
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
|
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else if val.is::<String>() {
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
{
|
||||||
|
if val.is::<Map>() {
|
||||||
|
// val_map[idx]
|
||||||
|
let map = val.downcast_ref::<Map>().expect("array expected");
|
||||||
|
|
||||||
|
let idx = *self
|
||||||
|
.eval_expr(scope, idx_expr, level)?
|
||||||
|
.downcast::<String>()
|
||||||
|
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?;
|
||||||
|
|
||||||
|
return Ok((
|
||||||
|
map.get(&idx).cloned().unwrap_or_else(|| ().into_dynamic()),
|
||||||
|
IndexSourceType::Map,
|
||||||
|
(None, Some(idx)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.is::<String>() {
|
||||||
// val_string[idx]
|
// val_string[idx]
|
||||||
let s = val.downcast_ref::<String>().expect("string expected");
|
let s = val.downcast_ref::<String>().expect("string expected");
|
||||||
|
|
||||||
if idx >= 0 {
|
let idx = *self
|
||||||
|
.eval_expr(scope, idx_expr, level)?
|
||||||
|
.downcast::<INT>()
|
||||||
|
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
|
||||||
|
|
||||||
|
return if idx >= 0 {
|
||||||
s.chars()
|
s.chars()
|
||||||
.nth(idx as usize)
|
.nth(idx as usize)
|
||||||
.map(|ch| (ch.into_dynamic(), IndexSourceType::String))
|
.map(|ch| {
|
||||||
|
(
|
||||||
|
ch.into_dynamic(),
|
||||||
|
IndexSourceType::String,
|
||||||
|
(Some(idx as usize), None),
|
||||||
|
)
|
||||||
|
})
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, idx_pos)
|
EvalAltResult::ErrorStringBounds(s.chars().count(), idx, idx_pos)
|
||||||
})
|
})
|
||||||
@ -637,14 +678,14 @@ impl Engine<'_> {
|
|||||||
idx,
|
idx,
|
||||||
idx_pos,
|
idx_pos,
|
||||||
))
|
))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Error - cannot be indexed
|
// Error - cannot be indexed
|
||||||
Err(EvalAltResult::ErrorIndexingType(
|
return Err(EvalAltResult::ErrorIndexingType(
|
||||||
self.map_type_name(val.type_name()).to_string(),
|
self.map_type_name(val.type_name()).to_string(),
|
||||||
op_pos,
|
op_pos,
|
||||||
))
|
));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an index expression
|
/// Evaluate an index expression
|
||||||
@ -656,36 +697,48 @@ impl Engine<'_> {
|
|||||||
idx_expr: &Expr,
|
idx_expr: &Expr,
|
||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(IndexSourceType, Option<ScopeSource<'a>>, usize, Dynamic), EvalAltResult> {
|
) -> Result<
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
(
|
||||||
|
IndexSourceType,
|
||||||
|
Option<ScopeSource<'a>>,
|
||||||
|
(Option<usize>, Option<String>),
|
||||||
|
Dynamic,
|
||||||
|
),
|
||||||
|
EvalAltResult,
|
||||||
|
> {
|
||||||
match lhs {
|
match lhs {
|
||||||
// id[idx_expr]
|
// id[idx_expr]
|
||||||
Expr::Variable(id, _) => Self::search_scope(
|
Expr::Variable(id, _) => {
|
||||||
scope,
|
let (
|
||||||
&id,
|
ScopeSource {
|
||||||
|val| self.get_indexed_value(&val, idx, idx_expr.position(), op_pos),
|
typ: src_type,
|
||||||
lhs.position(),
|
index: src_idx,
|
||||||
)
|
..
|
||||||
.map(|(src, (val, src_type))| {
|
},
|
||||||
(
|
val,
|
||||||
src_type,
|
) = Self::search_scope(scope, &id, lhs.position())?;
|
||||||
|
|
||||||
|
let (val, idx_src_type, idx) =
|
||||||
|
self.get_indexed_value(scope, &val, idx_expr, op_pos, level)?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
idx_src_type,
|
||||||
Some(ScopeSource {
|
Some(ScopeSource {
|
||||||
name: &id,
|
name: &id,
|
||||||
typ: src.typ,
|
typ: src_type,
|
||||||
index: src.index,
|
index: src_idx,
|
||||||
}),
|
}),
|
||||||
idx as usize,
|
idx,
|
||||||
val,
|
val,
|
||||||
)
|
))
|
||||||
}),
|
}
|
||||||
|
|
||||||
// (expr)[idx_expr]
|
// (expr)[idx_expr]
|
||||||
expr => {
|
expr => {
|
||||||
let val = self.eval_expr(scope, expr, level)?;
|
let val = self.eval_expr(scope, expr, level)?;
|
||||||
|
|
||||||
self.get_indexed_value(&val, idx, idx_expr.position(), op_pos)
|
self.get_indexed_value(scope, &val, idx_expr, op_pos, level)
|
||||||
.map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v))
|
.map(|(v, _, idx)| (IndexSourceType::Expression, None, idx, v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -707,17 +760,25 @@ impl Engine<'_> {
|
|||||||
/// Update the value at an index position in a variable inside the scope
|
/// Update the value at an index position in a variable inside the scope
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn update_indexed_var_in_scope(
|
fn update_indexed_var_in_scope(
|
||||||
src_type: IndexSourceType,
|
idx_src_type: IndexSourceType,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
src: ScopeSource,
|
src: ScopeSource,
|
||||||
idx: usize,
|
idx: (Option<usize>, Option<String>),
|
||||||
new_val: (Dynamic, Position),
|
new_val: (Dynamic, Position),
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
match src_type {
|
match idx_src_type {
|
||||||
// array_id[idx] = val
|
// array_id[idx] = val
|
||||||
IndexSourceType::Array => {
|
IndexSourceType::Array => {
|
||||||
let arr = scope.get_mut_by_type::<Array>(src);
|
let arr = scope.get_mut_by_type::<Array>(src);
|
||||||
arr[idx as usize] = new_val.0;
|
arr[idx.0.expect("should be Some")] = new_val.0;
|
||||||
|
Ok(().into_dynamic())
|
||||||
|
}
|
||||||
|
|
||||||
|
// map_id[idx] = val
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
IndexSourceType::Map => {
|
||||||
|
let arr = scope.get_mut_by_type::<Map>(src);
|
||||||
|
arr.insert(idx.1.expect("should be Some"), new_val.0);
|
||||||
Ok(().into_dynamic())
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -730,7 +791,7 @@ impl Engine<'_> {
|
|||||||
.0
|
.0
|
||||||
.downcast::<char>()
|
.downcast::<char>()
|
||||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||||
Self::str_replace_char(s, idx as usize, ch);
|
Self::str_replace_char(s, idx.0.expect("should be Some"), ch);
|
||||||
Ok(().into_dynamic())
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -742,26 +803,37 @@ impl Engine<'_> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn update_indexed_value(
|
fn update_indexed_value(
|
||||||
mut target: Dynamic,
|
mut target: Dynamic,
|
||||||
idx: usize,
|
idx: (Option<usize>, Option<String>),
|
||||||
new_val: Dynamic,
|
new_val: Dynamic,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
if target.is::<Array>() {
|
if target.is::<Array>() {
|
||||||
let arr = target.downcast_mut::<Array>().expect("array expected");
|
let arr = target.downcast_mut::<Array>().expect("array expected");
|
||||||
arr[idx as usize] = new_val;
|
arr[idx.0.expect("should be Some")] = new_val;
|
||||||
} else if target.is::<String>() {
|
return Ok(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
{
|
||||||
|
if target.is::<Map>() {
|
||||||
|
let map = target.downcast_mut::<Map>().expect("array expected");
|
||||||
|
map.insert(idx.1.expect("should be Some"), new_val);
|
||||||
|
return Ok(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.is::<String>() {
|
||||||
let s = target.downcast_mut::<String>().expect("string expected");
|
let s = target.downcast_mut::<String>().expect("string expected");
|
||||||
// Value must be a character
|
// Value must be a character
|
||||||
let ch = *new_val
|
let ch = *new_val
|
||||||
.downcast::<char>()
|
.downcast::<char>()
|
||||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||||
Self::str_replace_char(s, idx as usize, ch);
|
Self::str_replace_char(s, idx.0.expect("should be Some"), ch);
|
||||||
} else {
|
return Ok(target);
|
||||||
// All other variable types should be an error
|
|
||||||
panic!("array or string source type expected for indexing")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(target)
|
// All other variable types should be an error
|
||||||
|
panic!("array, map or string source type expected for indexing")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chain-evaluate a dot setter
|
/// Chain-evaluate a dot setter
|
||||||
@ -792,13 +864,10 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
|
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
let (_, _, idx) =
|
||||||
Self::update_indexed_value(
|
self.get_indexed_value(scope, &v, idx_expr, *op_pos, level)?;
|
||||||
v,
|
|
||||||
idx as usize,
|
Self::update_indexed_value(v, idx, new_val.0.clone(), new_val.1)
|
||||||
new_val.0.clone(),
|
|
||||||
new_val.1,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.and_then(|mut v| {
|
.and_then(|mut v| {
|
||||||
let set_fn_name = make_setter(id);
|
let set_fn_name = make_setter(id);
|
||||||
@ -842,16 +911,15 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
|
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
let idx = self.eval_index_value(scope, idx_expr, level)?;
|
let (mut target, _, idx) =
|
||||||
let (mut target, _) =
|
self.get_indexed_value(scope, &v, idx_expr, *op_pos, level)?;
|
||||||
self.get_indexed_value(&v, idx, idx_expr.position(), *op_pos)?;
|
|
||||||
|
|
||||||
let val_pos = new_val.1;
|
let val_pos = new_val.1;
|
||||||
let this_ptr = target.as_mut();
|
let this_ptr = target.as_mut();
|
||||||
self.set_dot_val_helper(scope, this_ptr, rhs, new_val, level)?;
|
self.set_dot_val_helper(scope, this_ptr, rhs, new_val, level)?;
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
Self::update_indexed_value(v, idx as usize, target, val_pos)
|
Self::update_indexed_value(v, idx, target, val_pos)
|
||||||
})
|
})
|
||||||
.and_then(|mut v| {
|
.and_then(|mut v| {
|
||||||
let set_fn_name = make_setter(id);
|
let set_fn_name = make_setter(id);
|
||||||
@ -896,7 +964,7 @@ impl Engine<'_> {
|
|||||||
match dot_lhs {
|
match dot_lhs {
|
||||||
// id.???
|
// id.???
|
||||||
Expr::Variable(id, pos) => {
|
Expr::Variable(id, pos) => {
|
||||||
let (entry, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
let (entry, mut target) = Self::search_scope(scope, id, *pos)?;
|
||||||
|
|
||||||
match entry.typ {
|
match entry.typ {
|
||||||
ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant(
|
ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
@ -921,7 +989,7 @@ impl Engine<'_> {
|
|||||||
// TODO - Allow chaining of indexing!
|
// TODO - Allow chaining of indexing!
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx_expr, op_pos) => {
|
Expr::Index(lhs, idx_expr, op_pos) => {
|
||||||
let (src_type, src, idx, mut target) =
|
let (idx_src_type, src, idx, mut target) =
|
||||||
self.eval_index_expr(scope, lhs, idx_expr, *op_pos, level)?;
|
self.eval_index_expr(scope, lhs, idx_expr, *op_pos, level)?;
|
||||||
let val_pos = new_val.1;
|
let val_pos = new_val.1;
|
||||||
let this_ptr = target.as_mut();
|
let this_ptr = target.as_mut();
|
||||||
@ -938,7 +1006,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
ScopeEntryType::Normal => {
|
ScopeEntryType::Normal => {
|
||||||
Self::update_indexed_var_in_scope(
|
Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
idx_src_type,
|
||||||
scope,
|
scope,
|
||||||
src,
|
src,
|
||||||
idx,
|
idx,
|
||||||
@ -973,7 +1041,7 @@ impl Engine<'_> {
|
|||||||
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
||||||
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
||||||
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
||||||
Expr::Variable(id, pos) => Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val),
|
Expr::Variable(id, pos) => Self::search_scope(scope, id, *pos).map(|(_, val)| val),
|
||||||
Expr::Property(_, _) => panic!("unexpected property."),
|
Expr::Property(_, _) => panic!("unexpected property."),
|
||||||
|
|
||||||
// lhs[idx_expr]
|
// lhs[idx_expr]
|
||||||
@ -1020,7 +1088,7 @@ impl Engine<'_> {
|
|||||||
// idx_lhs[idx_expr] = rhs
|
// idx_lhs[idx_expr] = rhs
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
Expr::Index(idx_lhs, idx_expr, op_pos) => {
|
||||||
let (src_type, src, idx, _) =
|
let (idx_src_type, src, idx, _) =
|
||||||
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
|
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
|
||||||
|
|
||||||
if let Some(src) = src {
|
if let Some(src) = src {
|
||||||
@ -1032,7 +1100,7 @@ impl Engine<'_> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
ScopeEntryType::Normal => Ok(Self::update_indexed_var_in_scope(
|
ScopeEntryType::Normal => Ok(Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
idx_src_type,
|
||||||
scope,
|
scope,
|
||||||
src,
|
src,
|
||||||
idx,
|
idx,
|
||||||
@ -1073,7 +1141,7 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(contents, _) => {
|
Expr::Array(contents, _) => {
|
||||||
let mut arr = Vec::new();
|
let mut arr = Array::new();
|
||||||
|
|
||||||
contents.into_iter().try_for_each(|item| {
|
contents.into_iter().try_for_each(|item| {
|
||||||
self.eval_expr(scope, item, level).map(|val| arr.push(val))
|
self.eval_expr(scope, item, level).map(|val| arr.push(val))
|
||||||
@ -1082,6 +1150,19 @@ impl Engine<'_> {
|
|||||||
Ok(Box::new(arr))
|
Ok(Box::new(arr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Map(contents, _) => {
|
||||||
|
let mut map = Map::new();
|
||||||
|
|
||||||
|
contents.into_iter().try_for_each(|item| {
|
||||||
|
self.eval_expr(scope, &item.1, level).map(|val| {
|
||||||
|
map.insert(item.0.clone(), val);
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Box::new(map))
|
||||||
|
}
|
||||||
|
|
||||||
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
|
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
|
||||||
// Has a system function an override?
|
// Has a system function an override?
|
||||||
fn has_override(engine: &Engine, name: &str) -> bool {
|
fn has_override(engine: &Engine, name: &str) -> bool {
|
||||||
|
13
src/error.rs
13
src/error.rs
@ -54,8 +54,13 @@ pub enum ParseErrorType {
|
|||||||
/// An expression in indexing brackets `[]` has syntax error.
|
/// An expression in indexing brackets `[]` has syntax error.
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
MalformedIndexExpr(String),
|
MalformedIndexExpr(String),
|
||||||
|
/// A map definition has duplicated property names. Wrapped is the property name.
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
DuplicatedProperty(String),
|
||||||
/// Invalid expression assigned to constant.
|
/// Invalid expression assigned to constant.
|
||||||
ForbiddenConstantExpr(String),
|
ForbiddenConstantExpr(String),
|
||||||
|
/// Missing a property name for custom types and maps.
|
||||||
|
PropertyExpected,
|
||||||
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
||||||
VariableExpected,
|
VariableExpected,
|
||||||
/// Missing an expression.
|
/// Missing an expression.
|
||||||
@ -121,7 +126,10 @@ impl ParseError {
|
|||||||
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
ParseErrorType::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
||||||
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
|
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||||
|
ParseErrorType::PropertyExpected => "Expecting name of a property",
|
||||||
ParseErrorType::VariableExpected => "Expecting name of a variable",
|
ParseErrorType::VariableExpected => "Expecting name of a variable",
|
||||||
ParseErrorType::ExprExpected(_) => "Expecting an expression",
|
ParseErrorType::ExprExpected(_) => "Expecting an expression",
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -160,6 +168,11 @@ impl fmt::Display for ParseError {
|
|||||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
ParseErrorType::DuplicatedProperty(ref s) => {
|
||||||
|
write!(f, "Duplicated property '{}' for object map literal", s)?
|
||||||
|
}
|
||||||
|
|
||||||
ParseErrorType::ExprExpected(ref s) => write!(f, "Expecting {} expression", s)?,
|
ParseErrorType::ExprExpected(ref s) => write!(f, "Expecting {} expression", s)?,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
@ -68,6 +68,9 @@ pub use scope::Scope;
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub use engine::Array;
|
pub use engine::Array;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub use engine::Map;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub use parser::FLOAT;
|
pub use parser::FLOAT;
|
||||||
|
|
||||||
|
@ -371,19 +371,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
// [ items .. ]
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(items, pos) => {
|
Expr::Array(items, pos) => {
|
||||||
let orig_len = items.len();
|
|
||||||
|
|
||||||
let items: Vec<_> = items
|
let items: Vec<_> = items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|expr| optimize_expr(expr, state))
|
.map(|expr| optimize_expr(expr, state))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if orig_len != items.len() {
|
|
||||||
state.set_dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::Array(items, pos)
|
Expr::Array(items, pos)
|
||||||
}
|
}
|
||||||
|
// [ items .. ]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Map(items, pos) => {
|
||||||
|
let items: Vec<_> = items
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Expr::Map(items, pos)
|
||||||
|
}
|
||||||
// lhs && rhs
|
// lhs && rhs
|
||||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
||||||
// true && rhs -> rhs
|
// true && rhs -> rhs
|
||||||
|
346
src/parser.rs
346
src/parser.rs
@ -11,7 +11,9 @@ use crate::optimize::optimize_into_ast;
|
|||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
char, fmt, format,
|
char,
|
||||||
|
collections::HashMap,
|
||||||
|
fmt, format,
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
ops::Add,
|
ops::Add,
|
||||||
str::Chars,
|
str::Chars,
|
||||||
@ -365,6 +367,9 @@ pub enum Expr {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
/// [ expr, ... ]
|
/// [ expr, ... ]
|
||||||
Array(Vec<Expr>, Position),
|
Array(Vec<Expr>, Position),
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
/// ${ name:expr, ... }
|
||||||
|
Map(Vec<(String, Expr, Position)>, Position),
|
||||||
/// lhs && rhs
|
/// lhs && rhs
|
||||||
And(Box<Expr>, Box<Expr>),
|
And(Box<Expr>, Box<Expr>),
|
||||||
/// lhs || rhs
|
/// lhs || rhs
|
||||||
@ -399,6 +404,13 @@ impl Expr {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_dynamic(),
|
.into_dynamic(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => items
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v, _)| (k.clone(), v.get_constant_value()))
|
||||||
|
.collect::<HashMap<_, _>>()
|
||||||
|
.into_dynamic(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(f, _) => f.into_dynamic(),
|
Expr::FloatConstant(f, _) => f.into_dynamic(),
|
||||||
|
|
||||||
@ -457,6 +469,9 @@ impl Expr {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(_, pos) => *pos,
|
Expr::Array(_, pos) => *pos,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Map(_, pos) => *pos,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(expr, _, _) => expr.position(),
|
Expr::Index(expr, _, _) => expr.position(),
|
||||||
}
|
}
|
||||||
@ -534,6 +549,8 @@ pub enum Token {
|
|||||||
Colon,
|
Colon,
|
||||||
Comma,
|
Comma,
|
||||||
Period,
|
Period,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
MapStart,
|
||||||
Equals,
|
Equals,
|
||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
@ -609,6 +626,8 @@ impl Token {
|
|||||||
Colon => ":",
|
Colon => ":",
|
||||||
Comma => ",",
|
Comma => ",",
|
||||||
Period => ".",
|
Period => ".",
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
MapStart => "${",
|
||||||
Equals => "=",
|
Equals => "=",
|
||||||
True => "true",
|
True => "true",
|
||||||
False => "false",
|
False => "false",
|
||||||
@ -797,6 +816,11 @@ pub struct TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TokenIterator<'a> {
|
impl<'a> TokenIterator<'a> {
|
||||||
|
/// Consume the next character.
|
||||||
|
fn eat_next(&mut self) {
|
||||||
|
self.stream.next();
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
/// Move the current position one character ahead.
|
/// Move the current position one character ahead.
|
||||||
fn advance(&mut self) {
|
fn advance(&mut self) {
|
||||||
self.pos.advance();
|
self.pos.advance();
|
||||||
@ -936,20 +960,17 @@ impl<'a> TokenIterator<'a> {
|
|||||||
match next_char {
|
match next_char {
|
||||||
'0'..='9' | '_' => {
|
'0'..='9' | '_' => {
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
'.' => {
|
'.' => {
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
while let Some(&next_char_in_float) = self.stream.peek() {
|
while let Some(&next_char_in_float) = self.stream.peek() {
|
||||||
match next_char_in_float {
|
match next_char_in_float {
|
||||||
'0'..='9' | '_' => {
|
'0'..='9' | '_' => {
|
||||||
result.push(next_char_in_float);
|
result.push(next_char_in_float);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
@ -960,8 +981,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
if c == '0' =>
|
if c == '0' =>
|
||||||
{
|
{
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
let valid = match ch {
|
let valid = match ch {
|
||||||
'x' | 'X' => [
|
'x' | 'X' => [
|
||||||
@ -992,8 +1012,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.push(next_char_in_hex);
|
result.push(next_char_in_hex);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1047,8 +1066,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
match next_char {
|
match next_char {
|
||||||
x if x.is_ascii_alphanumeric() || x == '_' => {
|
x if x.is_ascii_alphanumeric() || x == '_' => {
|
||||||
result.push(x);
|
result.push(x);
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
@ -1139,10 +1157,16 @@ impl<'a> TokenIterator<'a> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
(']', _) => return Some((Token::RightBracket, pos)),
|
(']', _) => return Some((Token::RightBracket, pos)),
|
||||||
|
|
||||||
|
// Map literal
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
('$', '{') => {
|
||||||
|
self.eat_next();
|
||||||
|
return Some((Token::MapStart, pos));
|
||||||
|
}
|
||||||
|
|
||||||
// Operators
|
// Operators
|
||||||
('+', '=') => {
|
('+', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::PlusAssign, pos));
|
return Some((Token::PlusAssign, pos));
|
||||||
}
|
}
|
||||||
('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, pos)),
|
('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, pos)),
|
||||||
@ -1151,24 +1175,21 @@ impl<'a> TokenIterator<'a> {
|
|||||||
('-', '0'..='9') if self.can_be_unary => negated = true,
|
('-', '0'..='9') if self.can_be_unary => negated = true,
|
||||||
('-', '0'..='9') => return Some((Token::Minus, pos)),
|
('-', '0'..='9') => return Some((Token::Minus, pos)),
|
||||||
('-', '=') => {
|
('-', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::MinusAssign, pos));
|
return Some((Token::MinusAssign, pos));
|
||||||
}
|
}
|
||||||
('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)),
|
('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)),
|
||||||
('-', _) => return Some((Token::Minus, pos)),
|
('-', _) => return Some((Token::Minus, pos)),
|
||||||
|
|
||||||
('*', '=') => {
|
('*', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::MultiplyAssign, pos));
|
return Some((Token::MultiplyAssign, pos));
|
||||||
}
|
}
|
||||||
('*', _) => return Some((Token::Multiply, pos)),
|
('*', _) => return Some((Token::Multiply, pos)),
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
('/', '/') => {
|
('/', '/') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
while let Some(c) = self.stream.next() {
|
while let Some(c) = self.stream.next() {
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
@ -1182,8 +1203,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
('/', '*') => {
|
('/', '*') => {
|
||||||
let mut level = 1;
|
let mut level = 1;
|
||||||
|
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
while let Some(c) = self.stream.next() {
|
while let Some(c) = self.stream.next() {
|
||||||
self.advance();
|
self.advance();
|
||||||
@ -1212,8 +1232,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
('/', '=') => {
|
('/', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::DivideAssign, pos));
|
return Some((Token::DivideAssign, pos));
|
||||||
}
|
}
|
||||||
('/', _) => return Some((Token::Divide, pos)),
|
('/', _) => return Some((Token::Divide, pos)),
|
||||||
@ -1224,25 +1243,21 @@ impl<'a> TokenIterator<'a> {
|
|||||||
('.', _) => return Some((Token::Period, pos)),
|
('.', _) => return Some((Token::Period, pos)),
|
||||||
|
|
||||||
('=', '=') => {
|
('=', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::EqualsTo, pos));
|
return Some((Token::EqualsTo, pos));
|
||||||
}
|
}
|
||||||
('=', _) => return Some((Token::Equals, pos)),
|
('=', _) => return Some((Token::Equals, pos)),
|
||||||
|
|
||||||
('<', '=') => {
|
('<', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::LessThanEqualsTo, pos));
|
return Some((Token::LessThanEqualsTo, pos));
|
||||||
}
|
}
|
||||||
('<', '<') => {
|
('<', '<') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
return Some((
|
return Some((
|
||||||
if self.stream.peek() == Some(&'=') {
|
if self.stream.peek() == Some(&'=') {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
Token::LeftShiftAssign
|
Token::LeftShiftAssign
|
||||||
} else {
|
} else {
|
||||||
Token::LeftShift
|
Token::LeftShift
|
||||||
@ -1253,18 +1268,15 @@ impl<'a> TokenIterator<'a> {
|
|||||||
('<', _) => return Some((Token::LessThan, pos)),
|
('<', _) => return Some((Token::LessThan, pos)),
|
||||||
|
|
||||||
('>', '=') => {
|
('>', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::GreaterThanEqualsTo, pos));
|
return Some((Token::GreaterThanEqualsTo, pos));
|
||||||
}
|
}
|
||||||
('>', '>') => {
|
('>', '>') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
|
|
||||||
return Some((
|
return Some((
|
||||||
if self.stream.peek() == Some(&'=') {
|
if self.stream.peek() == Some(&'=') {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
Token::RightShiftAssign
|
Token::RightShiftAssign
|
||||||
} else {
|
} else {
|
||||||
Token::RightShift
|
Token::RightShift
|
||||||
@ -1275,53 +1287,45 @@ impl<'a> TokenIterator<'a> {
|
|||||||
('>', _) => return Some((Token::GreaterThan, pos)),
|
('>', _) => return Some((Token::GreaterThan, pos)),
|
||||||
|
|
||||||
('!', '=') => {
|
('!', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::NotEqualsTo, pos));
|
return Some((Token::NotEqualsTo, pos));
|
||||||
}
|
}
|
||||||
('!', _) => return Some((Token::Bang, pos)),
|
('!', _) => return Some((Token::Bang, pos)),
|
||||||
|
|
||||||
('|', '|') => {
|
('|', '|') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::Or, pos));
|
return Some((Token::Or, pos));
|
||||||
}
|
}
|
||||||
('|', '=') => {
|
('|', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::OrAssign, pos));
|
return Some((Token::OrAssign, pos));
|
||||||
}
|
}
|
||||||
('|', _) => return Some((Token::Pipe, pos)),
|
('|', _) => return Some((Token::Pipe, pos)),
|
||||||
|
|
||||||
('&', '&') => {
|
('&', '&') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::And, pos));
|
return Some((Token::And, pos));
|
||||||
}
|
}
|
||||||
('&', '=') => {
|
('&', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::AndAssign, pos));
|
return Some((Token::AndAssign, pos));
|
||||||
}
|
}
|
||||||
('&', _) => return Some((Token::Ampersand, pos)),
|
('&', _) => return Some((Token::Ampersand, pos)),
|
||||||
|
|
||||||
('^', '=') => {
|
('^', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::XOrAssign, pos));
|
return Some((Token::XOrAssign, pos));
|
||||||
}
|
}
|
||||||
('^', _) => return Some((Token::XOr, pos)),
|
('^', _) => return Some((Token::XOr, pos)),
|
||||||
|
|
||||||
('%', '=') => {
|
('%', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::ModuloAssign, pos));
|
return Some((Token::ModuloAssign, pos));
|
||||||
}
|
}
|
||||||
('%', _) => return Some((Token::Modulo, pos)),
|
('%', _) => return Some((Token::Modulo, pos)),
|
||||||
|
|
||||||
('~', '=') => {
|
('~', '=') => {
|
||||||
self.stream.next();
|
self.eat_next();
|
||||||
self.advance();
|
|
||||||
return Some((Token::PowerOfAssign, pos));
|
return Some((Token::PowerOfAssign, pos));
|
||||||
}
|
}
|
||||||
('~', _) => return Some((Token::PowerOf, pos)),
|
('~', _) => return Some((Token::PowerOf, pos)),
|
||||||
@ -1435,7 +1439,7 @@ fn parse_call_expr<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an indexing expression.s
|
/// Parse an indexing expression.
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn parse_index_expr<'a>(
|
fn parse_index_expr<'a>(
|
||||||
lhs: Box<Expr>,
|
lhs: Box<Expr>,
|
||||||
@ -1445,7 +1449,7 @@ fn parse_index_expr<'a>(
|
|||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let idx_expr = parse_expr(input, allow_stmt_expr)?;
|
let idx_expr = parse_expr(input, allow_stmt_expr)?;
|
||||||
|
|
||||||
// Check type of indexing - must be integer
|
// Check type of indexing - must be integer or string
|
||||||
match &idx_expr {
|
match &idx_expr {
|
||||||
// lhs[int]
|
// lhs[int]
|
||||||
Expr::IntegerConstant(i, pos) if *i < 0 => {
|
Expr::IntegerConstant(i, pos) if *i < 0 => {
|
||||||
@ -1455,6 +1459,72 @@ fn parse_index_expr<'a>(
|
|||||||
))
|
))
|
||||||
.into_err(*pos))
|
.into_err(*pos))
|
||||||
}
|
}
|
||||||
|
Expr::IntegerConstant(_, pos) => match *lhs {
|
||||||
|
Expr::Array(_, _) | Expr::StringConstant(_, _) => (),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Map(_, _) => {
|
||||||
|
return Err(PERR::MalformedIndexExpr(
|
||||||
|
"Object map access expects string index, not a number".into(),
|
||||||
|
)
|
||||||
|
.into_err(*pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::FloatConstant(_, pos)
|
||||||
|
| Expr::CharConstant(_, pos)
|
||||||
|
| Expr::Assignment(_, _, pos)
|
||||||
|
| Expr::Unit(pos)
|
||||||
|
| Expr::True(pos)
|
||||||
|
| Expr::False(pos) => {
|
||||||
|
return Err(PERR::MalformedIndexExpr(
|
||||||
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
|
||||||
|
return Err(PERR::MalformedIndexExpr(
|
||||||
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
|
)
|
||||||
|
.into_err(lhs.position()))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
|
||||||
|
// lhs[string]
|
||||||
|
Expr::StringConstant(_, pos) => match *lhs {
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Map(_, _) => (),
|
||||||
|
|
||||||
|
Expr::Array(_, _) | Expr::StringConstant(_, _) => {
|
||||||
|
return Err(PERR::MalformedIndexExpr(
|
||||||
|
"Array or string expects numeric index, not a string".into(),
|
||||||
|
)
|
||||||
|
.into_err(*pos))
|
||||||
|
}
|
||||||
|
Expr::FloatConstant(_, pos)
|
||||||
|
| Expr::CharConstant(_, pos)
|
||||||
|
| Expr::Assignment(_, _, pos)
|
||||||
|
| Expr::Unit(pos)
|
||||||
|
| Expr::True(pos)
|
||||||
|
| Expr::False(pos) => {
|
||||||
|
return Err(PERR::MalformedIndexExpr(
|
||||||
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
|
||||||
|
return Err(PERR::MalformedIndexExpr(
|
||||||
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
|
)
|
||||||
|
.into_err(lhs.position()))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
|
||||||
// lhs[float]
|
// lhs[float]
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, pos) => {
|
Expr::FloatConstant(_, pos) => {
|
||||||
@ -1470,13 +1540,6 @@ fn parse_index_expr<'a>(
|
|||||||
)
|
)
|
||||||
.into_err(*pos))
|
.into_err(*pos))
|
||||||
}
|
}
|
||||||
// lhs[string]
|
|
||||||
Expr::StringConstant(_, pos) => {
|
|
||||||
return Err(PERR::MalformedIndexExpr(
|
|
||||||
"Array access expects integer index, not a string".into(),
|
|
||||||
)
|
|
||||||
.into_err(*pos))
|
|
||||||
}
|
|
||||||
// lhs[??? = ??? ], lhs[()]
|
// lhs[??? = ??? ], lhs[()]
|
||||||
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
|
Expr::Assignment(_, _, pos) | Expr::Unit(pos) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
@ -1577,7 +1640,7 @@ fn parse_array_literal<'a>(
|
|||||||
(_, pos) => {
|
(_, pos) => {
|
||||||
return Err(PERR::MissingToken(
|
return Err(PERR::MissingToken(
|
||||||
",".into(),
|
",".into(),
|
||||||
"separate the item of this array literal".into(),
|
"to separate the items of this array literal".into(),
|
||||||
)
|
)
|
||||||
.into_err(*pos))
|
.into_err(*pos))
|
||||||
}
|
}
|
||||||
@ -1598,6 +1661,111 @@ fn parse_array_literal<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a map literal.
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
fn parse_map_literal<'a>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
begin: Position,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
|
) -> Result<Expr, ParseError> {
|
||||||
|
let mut map = Vec::new();
|
||||||
|
|
||||||
|
if !matches!(input.peek(), Some((Token::RightBrace, _))) {
|
||||||
|
while input.peek().is_some() {
|
||||||
|
let (name, pos) = match input.next().ok_or_else(|| {
|
||||||
|
PERR::MissingToken("}".into(), "to end this object map literal".into())
|
||||||
|
.into_err_eof()
|
||||||
|
})? {
|
||||||
|
(Token::Identifier(s), pos) => (s.clone(), pos),
|
||||||
|
(Token::StringConst(s), pos) => (s.clone(), pos),
|
||||||
|
(_, pos) if map.is_empty() => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
"}".into(),
|
||||||
|
"to end this object map literal".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos))
|
||||||
|
}
|
||||||
|
(_, pos) => return Err(PERR::PropertyExpected.into_err(pos)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match input.next().ok_or_else(|| {
|
||||||
|
PERR::MissingToken(
|
||||||
|
":".into(),
|
||||||
|
format!(
|
||||||
|
"to follow the property '{}' in this object map literal",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_err_eof()
|
||||||
|
})? {
|
||||||
|
(Token::Colon, _) => (),
|
||||||
|
(_, pos) => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
":".into(),
|
||||||
|
format!(
|
||||||
|
"to follow the property '{}' in this object map literal",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_err(pos))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let expr = parse_expr(input, allow_stmt_expr)?;
|
||||||
|
|
||||||
|
map.push((name, expr, pos));
|
||||||
|
|
||||||
|
match input.peek().ok_or_else(|| {
|
||||||
|
PERR::MissingToken("}".into(), "to end this object map literal".into())
|
||||||
|
.into_err_eof()
|
||||||
|
})? {
|
||||||
|
(Token::Comma, _) => {
|
||||||
|
input.next();
|
||||||
|
}
|
||||||
|
(Token::RightBrace, _) => break,
|
||||||
|
(Token::Identifier(_), pos) => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
",".into(),
|
||||||
|
"to separate the items of this object map literal".into(),
|
||||||
|
)
|
||||||
|
.into_err(*pos))
|
||||||
|
}
|
||||||
|
(_, pos) => {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
"}".into(),
|
||||||
|
"to end this object map literal".into(),
|
||||||
|
)
|
||||||
|
.into_err(*pos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicating properties
|
||||||
|
map.iter()
|
||||||
|
.enumerate()
|
||||||
|
.try_for_each(|(i, (k1, _, _))| {
|
||||||
|
map.iter()
|
||||||
|
.skip(i + 1)
|
||||||
|
.find(|(k2, _, _)| k2 == k1)
|
||||||
|
.map_or_else(|| Ok(()), |(k2, _, pos)| Err((k2, *pos)))
|
||||||
|
})
|
||||||
|
.map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?;
|
||||||
|
|
||||||
|
// Ending brace
|
||||||
|
match input.peek().ok_or_else(|| {
|
||||||
|
PERR::MissingToken("}".into(), "to end this object map literal".into()).into_err_eof()
|
||||||
|
})? {
|
||||||
|
(Token::RightBrace, _) => {
|
||||||
|
input.next();
|
||||||
|
Ok(Expr::Map(map, begin))
|
||||||
|
}
|
||||||
|
(_, pos) => Err(
|
||||||
|
PERR::MissingToken("]".into(), "to end this object map literal".into()).into_err(*pos),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a primary expression.
|
/// Parse a primary expression.
|
||||||
fn parse_primary<'a>(
|
fn parse_primary<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
@ -1641,6 +1809,11 @@ fn parse_primary<'a>(
|
|||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
parse_array_literal(input, pos, allow_stmt_expr)
|
parse_array_literal(input, pos, allow_stmt_expr)
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
(Token::MapStart, pos) => {
|
||||||
|
can_be_indexed = true;
|
||||||
|
parse_map_literal(input, pos, allow_stmt_expr)
|
||||||
|
}
|
||||||
(Token::True, pos) => Ok(Expr::True(pos)),
|
(Token::True, pos) => Ok(Expr::True(pos)),
|
||||||
(Token::False, pos) => Ok(Expr::False(pos)),
|
(Token::False, pos) => Ok(Expr::False(pos)),
|
||||||
(Token::LexError(err), pos) => Err(PERR::BadInput(err.to_string()).into_err(pos)),
|
(Token::LexError(err), pos) => Err(PERR::BadInput(err.to_string()).into_err(pos)),
|
||||||
@ -1881,27 +2054,30 @@ fn parse_binary_op<'a>(
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Token::Period => {
|
Token::Period => {
|
||||||
fn change_var_to_property(expr: Expr) -> Expr {
|
fn check_property(expr: Expr) -> Result<Expr, ParseError> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
// xxx.lhs.rhs
|
||||||
Box::new(change_var_to_property(*lhs)),
|
Expr::Dot(lhs, rhs, pos) => Ok(Expr::Dot(
|
||||||
Box::new(change_var_to_property(*rhs)),
|
Box::new(check_property(*lhs)?),
|
||||||
|
Box::new(check_property(*rhs)?),
|
||||||
pos,
|
pos,
|
||||||
),
|
)),
|
||||||
|
// xxx.lhs[idx]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx, pos) => {
|
Expr::Index(lhs, idx, pos) => {
|
||||||
Expr::Index(Box::new(change_var_to_property(*lhs)), idx, pos)
|
Ok(Expr::Index(Box::new(check_property(*lhs)?), idx, pos))
|
||||||
}
|
}
|
||||||
Expr::Variable(s, pos) => Expr::Property(s, pos),
|
// xxx.id
|
||||||
expr => expr,
|
Expr::Variable(id, pos) => Ok(Expr::Property(id, pos)),
|
||||||
|
// xxx.prop
|
||||||
|
expr @ Expr::Property(_, _) => Ok(expr),
|
||||||
|
// xxx.fn()
|
||||||
|
expr @ Expr::FunctionCall(_, _, _, _) => Ok(expr),
|
||||||
|
expr => Err(PERR::PropertyExpected.into_err(expr.position())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::Dot(
|
Expr::Dot(Box::new(current_lhs), Box::new(check_property(rhs)?), pos)
|
||||||
Box::new(current_lhs),
|
|
||||||
Box::new(change_var_to_property(rhs)),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comparison operators default to false when passed invalid operands
|
// Comparison operators default to false when passed invalid operands
|
||||||
@ -2086,12 +2262,16 @@ fn parse_for<'a>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// for name in ...
|
// for name in ...
|
||||||
match input
|
match input.next().ok_or_else(|| {
|
||||||
.next()
|
PERR::MissingToken("in".into(), "after the iteration variable".into()).into_err_eof()
|
||||||
.ok_or_else(|| PERR::MissingToken("in".into(), "here".into()).into_err_eof())?
|
})? {
|
||||||
{
|
|
||||||
(Token::In, _) => (),
|
(Token::In, _) => (),
|
||||||
(_, pos) => return Err(PERR::MissingToken("in".into(), "here".into()).into_err(pos)),
|
(_, pos) => {
|
||||||
|
return Err(
|
||||||
|
PERR::MissingToken("in".into(), "after the iteration variable".into())
|
||||||
|
.into_err(pos),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for name in expr { body }
|
// for name in expr { body }
|
||||||
|
@ -36,10 +36,12 @@ pub enum EvalAltResult {
|
|||||||
/// String indexing out-of-bounds.
|
/// String indexing out-of-bounds.
|
||||||
/// Wrapped values are the current number of characters in the string and the index number.
|
/// Wrapped values are the current number of characters in the string and the index number.
|
||||||
ErrorStringBounds(usize, INT, Position),
|
ErrorStringBounds(usize, INT, Position),
|
||||||
/// Trying to index into a type that is not an array and not a string.
|
/// Trying to index into a type that is not an array, an object map, or a string.
|
||||||
ErrorIndexingType(String, Position),
|
ErrorIndexingType(String, Position),
|
||||||
/// Trying to index into an array or string with an index that is not `i64`.
|
/// Trying to index into an array or string with an index that is not `i64`.
|
||||||
ErrorIndexExpr(Position),
|
ErrorNumericIndexExpr(Position),
|
||||||
|
/// Trying to index into a map with an index that is not `String`.
|
||||||
|
ErrorStringIndexExpr(Position),
|
||||||
/// The guard expression in an `if` or `while` statement does not return a boolean value.
|
/// The guard expression in an `if` or `while` statement does not return a boolean value.
|
||||||
ErrorLogicGuard(Position),
|
ErrorLogicGuard(Position),
|
||||||
/// The `for` statement encounters a type that is not an iterator.
|
/// The `for` statement encounters a type that is not an iterator.
|
||||||
@ -81,9 +83,12 @@ impl EvalAltResult {
|
|||||||
}
|
}
|
||||||
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
||||||
Self::ErrorCharMismatch(_) => "Character expected",
|
Self::ErrorCharMismatch(_) => "Character expected",
|
||||||
Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index",
|
Self::ErrorNumericIndexExpr(_) => {
|
||||||
|
"Indexing into an array or string expects an integer index"
|
||||||
|
}
|
||||||
|
Self::ErrorStringIndexExpr(_) => "Indexing into an object map expects a string index",
|
||||||
Self::ErrorIndexingType(_, _) => {
|
Self::ErrorIndexingType(_, _) => {
|
||||||
"Indexing can only be performed on an array or a string"
|
"Indexing can only be performed on an array, an object map, or a string"
|
||||||
}
|
}
|
||||||
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||||
"Array access expects non-negative index"
|
"Array access expects non-negative index"
|
||||||
@ -122,22 +127,26 @@ impl fmt::Display for EvalAltResult {
|
|||||||
let desc = self.desc();
|
let desc = self.desc();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
Self::ErrorFunctionNotFound(s, pos) | Self::ErrorVariableNotFound(s, pos) => {
|
||||||
Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
write!(f, "{}: '{}' ({})", desc, s, pos)
|
||||||
Self::ErrorIndexingType(_, pos) => write!(f, "{} ({})", desc, pos),
|
}
|
||||||
Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
|
||||||
Self::ErrorLogicGuard(pos) => write!(f, "{} ({})", desc, pos),
|
|
||||||
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorIndexingType(_, pos)
|
||||||
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
|
| Self::ErrorNumericIndexExpr(pos)
|
||||||
|
| Self::ErrorStringIndexExpr(pos)
|
||||||
|
| Self::ErrorLogicGuard(pos)
|
||||||
|
| Self::ErrorFor(pos)
|
||||||
|
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||||
|
| Self::ErrorDotExpr(_, pos)
|
||||||
|
| Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
|
|
||||||
Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
||||||
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
||||||
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
|
|
||||||
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
|
|
||||||
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos),
|
|
||||||
Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
|
|
||||||
Self::ErrorRuntime(s, pos) => {
|
Self::ErrorRuntime(s, pos) => {
|
||||||
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
|
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
|
||||||
}
|
}
|
||||||
|
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos),
|
||||||
Self::ErrorLoopBreak(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorLoopBreak(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
|
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
@ -225,7 +234,8 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorArrayBounds(_, _, pos)
|
| Self::ErrorArrayBounds(_, _, pos)
|
||||||
| Self::ErrorStringBounds(_, _, pos)
|
| Self::ErrorStringBounds(_, _, pos)
|
||||||
| Self::ErrorIndexingType(_, pos)
|
| Self::ErrorIndexingType(_, pos)
|
||||||
| Self::ErrorIndexExpr(pos)
|
| Self::ErrorNumericIndexExpr(pos)
|
||||||
|
| Self::ErrorStringIndexExpr(pos)
|
||||||
| Self::ErrorLogicGuard(pos)
|
| Self::ErrorLogicGuard(pos)
|
||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
@ -256,7 +266,8 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorArrayBounds(_, _, ref mut pos)
|
| Self::ErrorArrayBounds(_, _, ref mut pos)
|
||||||
| Self::ErrorStringBounds(_, _, ref mut pos)
|
| Self::ErrorStringBounds(_, _, ref mut pos)
|
||||||
| Self::ErrorIndexingType(_, ref mut pos)
|
| Self::ErrorIndexingType(_, ref mut pos)
|
||||||
| Self::ErrorIndexExpr(ref mut pos)
|
| Self::ErrorNumericIndexExpr(ref mut pos)
|
||||||
|
| Self::ErrorStringIndexExpr(ref mut pos)
|
||||||
| Self::ErrorLogicGuard(ref mut pos)
|
| Self::ErrorLogicGuard(ref mut pos)
|
||||||
| Self::ErrorFor(ref mut pos)
|
| Self::ErrorFor(ref mut pos)
|
||||||
| Self::ErrorVariableNotFound(_, ref mut pos)
|
| Self::ErrorVariableNotFound(_, ref mut pos)
|
||||||
|
63
tests/maps.rs
Normal file
63
tests/maps.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#![cfg(not(feature = "no_object"))]
|
||||||
|
#![cfg(not(feature = "no_index"))]
|
||||||
|
|
||||||
|
use rhai::{AnyExt, Dynamic, Engine, EvalAltResult, Map, RegisterFn, INT};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_indexing() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(r#"let x = ${a: 1, b: 2, c: 3}; x["b"]"#)?,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("let y = ${a: 1, b: 2, c: 3}; y.a = 5; y.a")?,
|
||||||
|
5
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<char>(
|
||||||
|
r#"
|
||||||
|
let y = ${d: 1, "e": ${a: 42, b: 88, "": "93"}, " 123 xyz": 9};
|
||||||
|
y.e[""][1]
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
'3'
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.eval::<()>("let y = ${a: 1, b: 2, c: 3}; y.z")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_assign() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let mut x = engine.eval::<Map>("let x = ${a: 1, b: true, c: \"3\"}; x")?;
|
||||||
|
let box_a = x.remove("a").unwrap();
|
||||||
|
let box_b = x.remove("b").unwrap();
|
||||||
|
let box_c = x.remove("c").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*box_a.downcast::<INT>().unwrap(), 1);
|
||||||
|
assert_eq!(*box_b.downcast::<bool>().unwrap(), true);
|
||||||
|
assert_eq!(*box_c.downcast::<String>().unwrap(), "3");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_return() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let mut x = engine.eval::<Map>("${a: 1, b: true, c: \"3\"}")?;
|
||||||
|
let box_a = x.remove("a").unwrap();
|
||||||
|
let box_b = x.remove("b").unwrap();
|
||||||
|
let box_c = x.remove("c").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*box_a.downcast::<INT>().unwrap(), 1);
|
||||||
|
assert_eq!(*box_b.downcast::<bool>().unwrap(), true);
|
||||||
|
assert_eq!(*box_c.downcast::<String>().unwrap(), "3".to_string());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user