Optimize op-assignment statement.
This commit is contained in:
parent
fca140ef55
commit
95e67c48bd
72
README.md
72
README.md
@ -1388,19 +1388,19 @@ record == "Bob X. Davis: age 42 ❤\n";
|
||||
|
||||
The following standard methods (defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings:
|
||||
|
||||
| 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 at least 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 |
|
||||
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
|
||||
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
|
||||
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
|
||||
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
| 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 at least a specified length |
|
||||
| `+=` operator, `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 |
|
||||
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
|
||||
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
|
||||
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
|
||||
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
|
||||
### Examples
|
||||
|
||||
@ -1463,19 +1463,19 @@ Arrays are disabled via the [`no_index`] feature.
|
||||
|
||||
The following methods (defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays:
|
||||
|
||||
| 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<br/>(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 to at least a specified length |
|
||||
| `clear` | _none_ | empties the array |
|
||||
| `truncate` | target length | 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 |
|
||||
| `+=` operator, `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<br/>(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 to at least 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
|
||||
|
||||
@ -1585,16 +1585,16 @@ Object maps are disabled via the [`no_object`] feature.
|
||||
|
||||
The following methods (defined in the [`BasicMapPackage`](#packages) but excluded if using a [raw `Engine`]) operate on object maps:
|
||||
|
||||
| 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), not available under [`no_index`] |
|
||||
| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
|
||||
| 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) |
|
||||
| `+=` operator, `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), not available under [`no_index`] |
|
||||
| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
|
||||
|
||||
### Examples
|
||||
|
||||
|
@ -12,7 +12,7 @@ fn bench_iterations_1000(bench: &mut Bencher) {
|
||||
let x = 1_000;
|
||||
|
||||
while x > 0 {
|
||||
x = x - 1;
|
||||
x -= 1;
|
||||
}
|
||||
"#;
|
||||
|
||||
|
@ -6,7 +6,7 @@ let x = 10;
|
||||
loop {
|
||||
print(x);
|
||||
|
||||
x = x - 1;
|
||||
x -= 1;
|
||||
|
||||
if x <= 0 { break; }
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ let x = 1_000_000;
|
||||
print("Ready... Go!");
|
||||
|
||||
while x > 0 {
|
||||
x = x - 1;
|
||||
x -= 1;
|
||||
}
|
||||
|
||||
print("Finished. Run time = " + now.elapsed() + " seconds.");
|
||||
|
@ -4,5 +4,5 @@ let x = 10;
|
||||
|
||||
while x > 0 {
|
||||
print(x);
|
||||
x = x - 1;
|
||||
x -= 1;
|
||||
}
|
||||
|
207
src/engine.rs
207
src/engine.rs
@ -1354,10 +1354,11 @@ impl Engine {
|
||||
let def_value = Some(&def_value);
|
||||
let pos = rhs.position();
|
||||
|
||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||
let hash_fn =
|
||||
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
|
||||
let hashes = (hash_fn, 0);
|
||||
let hashes = (
|
||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())),
|
||||
0,
|
||||
);
|
||||
|
||||
let (r, _) = self.call_fn_raw(
|
||||
None, state, lib, op, hashes, args, false, def_value, pos, level,
|
||||
@ -1417,57 +1418,87 @@ impl Engine {
|
||||
|
||||
// lhs = rhs
|
||||
Expr::Assignment(x) => {
|
||||
let op_pos = x.2;
|
||||
let rhs_val = self.eval_expr(scope, state, lib, &x.1, level)?;
|
||||
let lhs_expr = &x.0;
|
||||
let op = x.1.as_ref();
|
||||
let op_pos = x.3;
|
||||
let mut rhs_val = self.eval_expr(scope, state, lib, &x.2, level)?;
|
||||
|
||||
match &x.0 {
|
||||
// name = rhs
|
||||
Expr::Variable(x) => {
|
||||
let ((name, pos), modules, hash_var, index) = x.as_ref();
|
||||
let index = if state.always_search { None } else { *index };
|
||||
let mod_and_hash = modules.as_ref().map(|m| (m.as_ref(), *hash_var));
|
||||
let (lhs_ptr, typ) = search_scope(scope, name, mod_and_hash, index, *pos)?;
|
||||
self.inc_operations(state, *pos)?;
|
||||
// name op= rhs
|
||||
if let Expr::Variable(x) = &x.0 {
|
||||
let ((name, pos), modules, hash_var, index) = x.as_ref();
|
||||
let index = if state.always_search { None } else { *index };
|
||||
let mod_and_hash = modules.as_ref().map(|m| (m.as_ref(), *hash_var));
|
||||
let (lhs_ptr, typ) = search_scope(scope, name, mod_and_hash, index, *pos)?;
|
||||
self.inc_operations(state, *pos)?;
|
||||
|
||||
match typ {
|
||||
ScopeEntryType::Constant => Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos),
|
||||
)),
|
||||
ScopeEntryType::Normal => {
|
||||
*lhs_ptr = rhs_val;
|
||||
Ok(Default::default())
|
||||
match typ {
|
||||
ScopeEntryType::Constant => Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos),
|
||||
)),
|
||||
ScopeEntryType::Normal if !op.is_empty() => {
|
||||
// Complex op-assignment
|
||||
if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() {
|
||||
// Not built in, do function call
|
||||
let mut lhs_val = lhs_ptr.clone();
|
||||
let args = &mut [&mut lhs_val, &mut rhs_val];
|
||||
let hash = calc_fn_hash(empty(), op, 2, empty());
|
||||
*lhs_ptr = self
|
||||
.exec_fn_call(
|
||||
state, lib, op, true, hash, args, false, None, op_pos,
|
||||
level,
|
||||
)
|
||||
.map(|(v, _)| v)?;
|
||||
}
|
||||
// End variable cannot be a module
|
||||
ScopeEntryType::Module => unreachable!(),
|
||||
Ok(Default::default())
|
||||
}
|
||||
ScopeEntryType::Normal => {
|
||||
*lhs_ptr = rhs_val;
|
||||
Ok(Default::default())
|
||||
}
|
||||
// End variable cannot be a module
|
||||
ScopeEntryType::Module => unreachable!(),
|
||||
}
|
||||
// idx_lhs[idx_expr] = rhs
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(x) => {
|
||||
let new_val = Some(rhs_val);
|
||||
self.eval_dot_index_chain(
|
||||
} else {
|
||||
let new_val = Some(if op.is_empty() {
|
||||
rhs_val
|
||||
} else {
|
||||
// Complex op-assignment - always do function call
|
||||
let args = &mut [
|
||||
&mut self.eval_expr(scope, state, lib, lhs_expr, level)?,
|
||||
&mut rhs_val,
|
||||
];
|
||||
let hash = calc_fn_hash(empty(), op, 2, empty());
|
||||
self.exec_fn_call(
|
||||
state, lib, op, true, hash, args, false, None, op_pos, level,
|
||||
)
|
||||
.map(|(v, _)| v)?
|
||||
});
|
||||
|
||||
match &x.0 {
|
||||
// name op= rhs
|
||||
Expr::Variable(_) => unreachable!(),
|
||||
// idx_lhs[idx_expr] op= rhs
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(x) => self.eval_dot_index_chain(
|
||||
scope, state, lib, &x.0, &x.1, true, x.2, level, new_val,
|
||||
)
|
||||
}
|
||||
// dot_lhs.dot_rhs = rhs
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(x) => {
|
||||
let new_val = Some(rhs_val);
|
||||
self.eval_dot_index_chain(
|
||||
),
|
||||
// dot_lhs.dot_rhs op= rhs
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(x) => self.eval_dot_index_chain(
|
||||
scope, state, lib, &x.0, &x.1, false, op_pos, level, new_val,
|
||||
)
|
||||
}
|
||||
// Error assignment to constant
|
||||
expr if expr.is_constant() => {
|
||||
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
|
||||
expr.get_constant_str(),
|
||||
),
|
||||
// Error assignment to constant
|
||||
expr if expr.is_constant() => {
|
||||
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
|
||||
expr.get_constant_str(),
|
||||
expr.position(),
|
||||
)))
|
||||
}
|
||||
// Syntax error
|
||||
expr => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
||||
expr.position(),
|
||||
)))
|
||||
))),
|
||||
}
|
||||
// Syntax error
|
||||
expr => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
||||
expr.position(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1675,7 +1706,7 @@ impl Engine {
|
||||
let result = self.eval_expr(scope, state, lib, expr, level)?;
|
||||
|
||||
Ok(match expr.as_ref() {
|
||||
// If it is an assignment, erase the result at the root
|
||||
// If it is a simple assignment, erase the result at the root
|
||||
Expr::Assignment(_) => Default::default(),
|
||||
_ => result,
|
||||
})
|
||||
@ -2079,3 +2110,85 @@ fn run_builtin_binary_op(
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Build in common operator assignment implementations to avoid the cost of calling a registered function.
|
||||
fn run_builtin_op_assignment(
|
||||
op: &str,
|
||||
x: &mut Dynamic,
|
||||
y: &Dynamic,
|
||||
) -> Result<Option<()>, Box<EvalAltResult>> {
|
||||
use crate::packages::arithmetic::*;
|
||||
|
||||
let args_type = x.type_id();
|
||||
|
||||
if y.type_id() != args_type {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if args_type == TypeId::of::<INT>() {
|
||||
let x = x.downcast_mut::<INT>().unwrap();
|
||||
let y = y.downcast_ref::<INT>().unwrap().clone();
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op {
|
||||
"+" => return Ok(Some(*x = add(*x, y)?)),
|
||||
"-" => return Ok(Some(*x = sub(*x, y)?)),
|
||||
"*" => return Ok(Some(*x = mul(*x, y)?)),
|
||||
"/" => return Ok(Some(*x = div(*x, y)?)),
|
||||
"%" => return Ok(Some(*x = modulo(*x, y)?)),
|
||||
"~" => return Ok(Some(*x = pow_i_i(*x, y)?)),
|
||||
">>" => return Ok(Some(*x = shr(*x, y)?)),
|
||||
"<<" => return Ok(Some(*x = shl(*x, y)?)),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
match op {
|
||||
"+" => return Ok(Some(*x += y)),
|
||||
"-" => return Ok(Some(*x -= y)),
|
||||
"*" => return Ok(Some(*x *= y)),
|
||||
"/" => return Ok(Some(*x /= y)),
|
||||
"%" => return Ok(Some(*x %= y)),
|
||||
"~" => return Ok(Some(*x = pow_i_i_u(x, y))),
|
||||
">>" => return Ok(Some(*x = shr_u(x, y))),
|
||||
"<<" => return Ok(Some(*x = shl_u(x, y))),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match op {
|
||||
"&" => return Ok(Some(*x &= y)),
|
||||
"|" => return Ok(Some(*x |= y)),
|
||||
"^" => return Ok(Some(*x ^= y)),
|
||||
_ => (),
|
||||
}
|
||||
} else if args_type == TypeId::of::<bool>() {
|
||||
let x = x.downcast_mut::<bool>().unwrap();
|
||||
let y = y.downcast_ref::<bool>().unwrap().clone();
|
||||
|
||||
match op {
|
||||
"&" => return Ok(Some(*x = *x && y)),
|
||||
"|" => return Ok(Some(*x = *x || y)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
if args_type == TypeId::of::<FLOAT>() {
|
||||
let x = x.downcast_mut::<FLOAT>().unwrap();
|
||||
let y = y.downcast_ref::<FLOAT>().unwrap().clone();
|
||||
|
||||
match op {
|
||||
"+" => return Ok(Some(*x += y)),
|
||||
"-" => return Ok(Some(*x -= y)),
|
||||
"*" => return Ok(Some(*x *= y)),
|
||||
"/" => return Ok(Some(*x /= y)),
|
||||
"%" => return Ok(Some(*x %= y)),
|
||||
"~" => return Ok(Some(*x = pow_f_f(*x, y)?)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -380,26 +380,26 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
stmt => Expr::Stmt(Box::new((stmt, x.1))),
|
||||
},
|
||||
// id = expr
|
||||
Expr::Assignment(x) => match x.1 {
|
||||
//id = id2 = expr2
|
||||
Expr::Assignment(x2) => match (x.0, x2.0) {
|
||||
// var = var = expr2 -> var = expr2
|
||||
Expr::Assignment(x) => match x.2 {
|
||||
//id = id2 op= expr2
|
||||
Expr::Assignment(x2) if x.1 == "=" => match (x.0, x2.0) {
|
||||
// var = var op= expr2 -> var op= expr2
|
||||
(Expr::Variable(a), Expr::Variable(b))
|
||||
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
|
||||
{
|
||||
// Assignment to the same variable - fold
|
||||
state.set_dirty();
|
||||
Expr::Assignment(Box::new((Expr::Variable(a), optimize_expr(x2.1, state), x.2)))
|
||||
Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3)))
|
||||
}
|
||||
// id1 = id2 = expr2
|
||||
// id1 = id2 op= expr2
|
||||
(id1, id2) => {
|
||||
Expr::Assignment(Box::new((
|
||||
id1, Expr::Assignment(Box::new((id2, optimize_expr(x2.1, state), x2.2))), x.2,
|
||||
id1, x.1, Expr::Assignment(Box::new((id2, x2.1, optimize_expr(x2.2, state), x2.3))), x.3,
|
||||
)))
|
||||
}
|
||||
},
|
||||
// id = expr
|
||||
expr => Expr::Assignment(Box::new((x.0, optimize_expr(expr, state), x.2))),
|
||||
// id op= expr
|
||||
expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
|
||||
},
|
||||
|
||||
// lhs.rhs
|
||||
|
@ -53,6 +53,10 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
||||
x.extend(y);
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2_mut("+=", |x: &mut Array, y: Array| {
|
||||
x.extend(y);
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2(
|
||||
"+",
|
||||
|mut x: Array, y: Array| {
|
||||
|
@ -42,6 +42,15 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
lib.set_fn_2_mut(
|
||||
"+=",
|
||||
|map1: &mut Map, map2: Map| {
|
||||
map2.into_iter().for_each(|(key, value)| {
|
||||
map1.insert(key, value);
|
||||
});
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
lib.set_fn_2(
|
||||
"+",
|
||||
|mut map1: Map, map2: Map| {
|
||||
|
@ -105,10 +105,24 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
|
||||
Ok(s.into())
|
||||
},
|
||||
);
|
||||
lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| {
|
||||
shared_make_mut(s).push(ch);
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| {
|
||||
shared_make_mut(s).push(ch);
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2_mut(
|
||||
"+=",
|
||||
|s: &mut ImmutableString, s2: ImmutableString| {
|
||||
if !s2.is_empty() {
|
||||
shared_make_mut(s).push_str(s2.as_str());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
);
|
||||
lib.set_fn_2_mut(
|
||||
"append",
|
||||
|s: &mut ImmutableString, s2: ImmutableString| {
|
||||
|
243
src/parser.rs
243
src/parser.rs
@ -405,8 +405,8 @@ pub enum Expr {
|
||||
Option<Dynamic>,
|
||||
)>,
|
||||
),
|
||||
/// expr = expr
|
||||
Assignment(Box<(Expr, Expr, Position)>),
|
||||
/// expr op= expr
|
||||
Assignment(Box<(Expr, Cow<'static, str>, Expr, Position)>),
|
||||
/// lhs.rhs
|
||||
Dot(Box<(Expr, Expr, Position)>),
|
||||
/// expr[expr]
|
||||
@ -508,12 +508,13 @@ impl Expr {
|
||||
Self::Stmt(x) => x.1,
|
||||
Self::Variable(x) => (x.0).1,
|
||||
Self::FnCall(x) => (x.0).2,
|
||||
Self::Assignment(x) => x.0.position(),
|
||||
|
||||
Self::And(x) | Self::Or(x) | Self::In(x) => x.2,
|
||||
|
||||
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos,
|
||||
|
||||
Self::Assignment(x) | Self::Dot(x) | Self::Index(x) => x.0.position(),
|
||||
Self::Dot(x) | Self::Index(x) => x.0.position(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -538,7 +539,7 @@ impl Expr {
|
||||
Self::True(pos) => *pos = new_pos,
|
||||
Self::False(pos) => *pos = new_pos,
|
||||
Self::Unit(pos) => *pos = new_pos,
|
||||
Self::Assignment(x) => x.2 = new_pos,
|
||||
Self::Assignment(x) => x.3 = new_pos,
|
||||
Self::Dot(x) => x.2 = new_pos,
|
||||
Self::Index(x) => x.2 = new_pos,
|
||||
}
|
||||
@ -879,30 +880,25 @@ fn parse_index_chain<'a>(
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(x) => {
|
||||
Expr::FloatConstant(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(lhs.position()))
|
||||
}
|
||||
|
||||
Expr::CharConstant(x) => {
|
||||
Expr::CharConstant(_)
|
||||
| Expr::Assignment(_)
|
||||
| Expr::And(_)
|
||||
| Expr::Or(_)
|
||||
| Expr::In(_)
|
||||
| Expr::True(_)
|
||||
| Expr::False(_)
|
||||
| Expr::Unit(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
}
|
||||
Expr::Assignment(x) | Expr::And(x) | Expr::Or(x) | Expr::In(x) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(x.2))
|
||||
}
|
||||
Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(pos))
|
||||
.into_err(lhs.position()))
|
||||
}
|
||||
|
||||
_ => (),
|
||||
@ -920,32 +916,25 @@ fn parse_index_chain<'a>(
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(x) => {
|
||||
Expr::FloatConstant(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(lhs.position()))
|
||||
}
|
||||
|
||||
Expr::CharConstant(x) => {
|
||||
Expr::CharConstant(_)
|
||||
| Expr::Assignment(_)
|
||||
| Expr::And(_)
|
||||
| Expr::Or(_)
|
||||
| Expr::In(_)
|
||||
| Expr::True(_)
|
||||
| Expr::False(_)
|
||||
| Expr::Unit(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
}
|
||||
|
||||
Expr::Assignment(x) | Expr::And(x) | Expr::Or(x) | Expr::In(x) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(x.2))
|
||||
}
|
||||
|
||||
Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(pos))
|
||||
.into_err(lhs.position()))
|
||||
}
|
||||
|
||||
_ => (),
|
||||
@ -953,46 +942,46 @@ fn parse_index_chain<'a>(
|
||||
|
||||
// lhs[float]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(x) => {
|
||||
x @ Expr::FloatConstant(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a float".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// lhs[char]
|
||||
Expr::CharConstant(x) => {
|
||||
x @ Expr::CharConstant(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a character".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// lhs[??? = ??? ]
|
||||
Expr::Assignment(x) => {
|
||||
x @ Expr::Assignment(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not ()".into(),
|
||||
"Array access expects integer index, not an assignment".into(),
|
||||
)
|
||||
.into_err(x.2))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// lhs[()]
|
||||
Expr::Unit(pos) => {
|
||||
x @ Expr::Unit(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not ()".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???]
|
||||
Expr::And(x) | Expr::Or(x) | Expr::In(x) => {
|
||||
x @ Expr::And(_) | x @ Expr::Or(_) | x @ Expr::In(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a boolean".into(),
|
||||
)
|
||||
.into_err(x.2))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// lhs[true], lhs[false]
|
||||
Expr::True(pos) | Expr::False(pos) => {
|
||||
x @ Expr::True(_) | x @ Expr::False(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a boolean".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// All other expressions
|
||||
_ => (),
|
||||
@ -1391,17 +1380,22 @@ fn parse_unary<'a>(
|
||||
}
|
||||
|
||||
fn make_assignment_stmt<'a>(
|
||||
fn_name: Cow<'static, str>,
|
||||
state: &mut ParseState,
|
||||
lhs: Expr,
|
||||
rhs: Expr,
|
||||
pos: Position,
|
||||
) -> Result<Expr, ParseError> {
|
||||
match &lhs {
|
||||
Expr::Variable(x) if x.3.is_none() => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))),
|
||||
Expr::Variable(x) if x.3.is_none() => {
|
||||
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos))))
|
||||
}
|
||||
Expr::Variable(x) => {
|
||||
let ((name, name_pos), _, _, index) = x.as_ref();
|
||||
match state.stack[(state.len() - index.unwrap().get())].1 {
|
||||
ScopeEntryType::Normal => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))),
|
||||
ScopeEntryType::Normal => {
|
||||
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos))))
|
||||
}
|
||||
// Constant values cannot be assigned to
|
||||
ScopeEntryType::Constant => {
|
||||
Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos))
|
||||
@ -1410,11 +1404,15 @@ fn make_assignment_stmt<'a>(
|
||||
}
|
||||
}
|
||||
Expr::Index(x) | Expr::Dot(x) => match &x.0 {
|
||||
Expr::Variable(x) if x.3.is_none() => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))),
|
||||
Expr::Variable(x) if x.3.is_none() => {
|
||||
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos))))
|
||||
}
|
||||
Expr::Variable(x) => {
|
||||
let ((name, name_pos), _, _, index) = x.as_ref();
|
||||
match state.stack[(state.len() - index.unwrap().get())].1 {
|
||||
ScopeEntryType::Normal => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))),
|
||||
ScopeEntryType::Normal => {
|
||||
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos))))
|
||||
}
|
||||
// Constant values cannot be assigned to
|
||||
ScopeEntryType::Constant => {
|
||||
Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos))
|
||||
@ -1447,11 +1445,7 @@ fn parse_op_assignment_stmt<'a>(
|
||||
}
|
||||
|
||||
let op = match token {
|
||||
Token::Equals => {
|
||||
let pos = eat_token(input, Token::Equals);
|
||||
let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?;
|
||||
return make_assignment_stmt(state, lhs, rhs, pos);
|
||||
}
|
||||
Token::Equals => "".into(),
|
||||
Token::PlusAssign => Token::Plus.syntax(),
|
||||
Token::MinusAssign => Token::Minus.syntax(),
|
||||
Token::MultiplyAssign => Token::Multiply.syntax(),
|
||||
@ -1467,20 +1461,9 @@ fn parse_op_assignment_stmt<'a>(
|
||||
_ => return Ok(lhs),
|
||||
};
|
||||
|
||||
input.next();
|
||||
|
||||
let lhs_copy = lhs.clone();
|
||||
let (_, pos) = input.next().unwrap();
|
||||
let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?;
|
||||
|
||||
// lhs op= rhs -> lhs = op(lhs, rhs)
|
||||
let mut args = StaticVec::new();
|
||||
args.push(lhs_copy);
|
||||
args.push(rhs);
|
||||
|
||||
let hash = calc_fn_hash(empty(), &op, args.len(), empty());
|
||||
let rhs_expr = Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None)));
|
||||
|
||||
make_assignment_stmt(state, lhs, rhs_expr, pos)
|
||||
make_assignment_stmt(op, state, lhs, rhs, pos)
|
||||
}
|
||||
|
||||
/// Make a dot expression.
|
||||
@ -1553,33 +1536,26 @@ fn make_dot_expr(
|
||||
/// Make an 'in' expression.
|
||||
fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
|
||||
match (&lhs, &rhs) {
|
||||
(_, Expr::IntegerConstant(x)) => {
|
||||
(_, x @ Expr::IntegerConstant(_))
|
||||
| (_, x @ Expr::And(_))
|
||||
| (_, x @ Expr::Or(_))
|
||||
| (_, x @ Expr::In(_))
|
||||
| (_, x @ Expr::Assignment(_))
|
||||
| (_, x @ Expr::True(_))
|
||||
| (_, x @ Expr::False(_))
|
||||
| (_, x @ Expr::Unit(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression expects a string, array or object map".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
}
|
||||
|
||||
(_, Expr::And(x)) | (_, Expr::Or(x)) | (_, Expr::In(x)) | (_, Expr::Assignment(x)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression expects a string, array or object map".into(),
|
||||
)
|
||||
.into_err(x.2))
|
||||
}
|
||||
|
||||
(_, Expr::True(pos)) | (_, Expr::False(pos)) | (_, Expr::Unit(pos)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression expects a string, array or object map".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(_, Expr::FloatConstant(x)) => {
|
||||
(_, x @ Expr::FloatConstant(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression expects a string, array or object map".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
|
||||
// "xxx" in "xxxx", 'x' in "xxxx" - OK!
|
||||
@ -1588,63 +1564,58 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseErr
|
||||
|
||||
// 123.456 in "xxxx"
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(Expr::FloatConstant(x), Expr::StringConstant(_)) => {
|
||||
(x @ Expr::FloatConstant(_), Expr::StringConstant(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a float".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// 123 in "xxxx"
|
||||
(Expr::IntegerConstant(x), Expr::StringConstant(_)) => {
|
||||
(x @ Expr::IntegerConstant(_), Expr::StringConstant(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a number".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx",
|
||||
(Expr::And(x), Expr::StringConstant(_))
|
||||
| (Expr::Or(x), Expr::StringConstant(_))
|
||||
| (Expr::In(x), Expr::StringConstant(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a boolean".into(),
|
||||
)
|
||||
.into_err(x.2))
|
||||
}
|
||||
// true in "xxxx", false in "xxxx"
|
||||
(Expr::True(pos), Expr::StringConstant(_))
|
||||
| (Expr::False(pos), Expr::StringConstant(_)) => {
|
||||
(x @ Expr::And(_), Expr::StringConstant(_))
|
||||
| (x @ Expr::Or(_), Expr::StringConstant(_))
|
||||
| (x @ Expr::In(_), Expr::StringConstant(_))
|
||||
| (x @ Expr::True(_), Expr::StringConstant(_))
|
||||
| (x @ Expr::False(_), Expr::StringConstant(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a boolean".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// [???, ???, ???] in "xxxx"
|
||||
(Expr::Array(x), Expr::StringConstant(_)) => {
|
||||
(x @ Expr::Array(_), Expr::StringConstant(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not an array".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// #{...} in "xxxx"
|
||||
(Expr::Map(x), Expr::StringConstant(_)) => {
|
||||
(x @ Expr::Map(_), Expr::StringConstant(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not an object map".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// (??? = ???) in "xxxx"
|
||||
(Expr::Assignment(x), Expr::StringConstant(_)) => {
|
||||
(x @ Expr::Assignment(_), Expr::StringConstant(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not ()".into(),
|
||||
"'in' expression for a string expects a string, not an assignment".into(),
|
||||
)
|
||||
.into_err(x.2))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// () in "xxxx"
|
||||
(Expr::Unit(pos), Expr::StringConstant(_)) => {
|
||||
(x @ Expr::Unit(_), Expr::StringConstant(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not ()".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
|
||||
// "xxx" in #{...}, 'x' in #{...} - OK!
|
||||
@ -1652,62 +1623,58 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseErr
|
||||
|
||||
// 123.456 in #{...}
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(Expr::FloatConstant(x), Expr::Map(_)) => {
|
||||
(x @ Expr::FloatConstant(_), Expr::Map(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a float".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// 123 in #{...}
|
||||
(Expr::IntegerConstant(x), Expr::Map(_)) => {
|
||||
(x @ Expr::IntegerConstant(_), Expr::Map(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a number".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...},
|
||||
(Expr::And(x), Expr::Map(_))
|
||||
| (Expr::Or(x), Expr::Map(_))
|
||||
| (Expr::In(x), Expr::Map(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a boolean".into(),
|
||||
)
|
||||
.into_err(x.2))
|
||||
}
|
||||
// true in #{...}, false in #{...}
|
||||
(Expr::True(pos), Expr::Map(_)) | (Expr::False(pos), Expr::Map(_)) => {
|
||||
(x @ Expr::And(_), Expr::Map(_))
|
||||
| (x @ Expr::Or(_), Expr::Map(_))
|
||||
| (x @ Expr::In(_), Expr::Map(_))
|
||||
| (x @ Expr::True(_), Expr::Map(_))
|
||||
| (x @ Expr::False(_), Expr::Map(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a boolean".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// [???, ???, ???] in #{..}
|
||||
(Expr::Array(x), Expr::Map(_)) => {
|
||||
(x @ Expr::Array(_), Expr::Map(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not an array".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// #{...} in #{..}
|
||||
(Expr::Map(x), Expr::Map(_)) => {
|
||||
(x @ Expr::Map(_), Expr::Map(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not an object map".into(),
|
||||
)
|
||||
.into_err(x.1))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// (??? = ???) in #{...}
|
||||
(Expr::Assignment(x), Expr::Map(_)) => {
|
||||
(x @ Expr::Assignment(_), Expr::Map(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not ()".into(),
|
||||
"'in' expression for an object map expects a string, not an assignment".into(),
|
||||
)
|
||||
.into_err(x.2))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// () in #{...}
|
||||
(Expr::Unit(pos), Expr::Map(_)) => {
|
||||
(x @ Expr::Unit(_), Expr::Map(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not ()".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
.into_err(x.position()))
|
||||
}
|
||||
|
||||
_ => (),
|
||||
|
Loading…
Reference in New Issue
Block a user