Optimize op-assignment statement.

This commit is contained in:
Stephen Chung 2020-05-25 20:14:31 +08:00
parent fca140ef55
commit 95e67c48bd
11 changed files with 341 additions and 234 deletions

View File

@ -1389,10 +1389,10 @@ 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: The following standard methods (defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `len` | _none_ | returns the number of characters (not number of bytes) in the string | | `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 | | `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 | | `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string | | `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters | | `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 | | `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
@ -1464,9 +1464,9 @@ 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: The following methods (defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | ----------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end | | `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, `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 | | `+` 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 | | `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) | | `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
@ -1586,12 +1586,12 @@ 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: The following methods (defined in the [`BasicMapPackage`](#packages) but excluded if using a [raw `Engine`]) operate on object maps:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | property name | does the object map contain a property of a particular name? | | `has` | property name | does the object map contain a property of a particular name? |
| `len` | _none_ | returns the number of properties | | `len` | _none_ | returns the number of properties |
| `clear` | _none_ | empties the object map | | `clear` | _none_ | empties the object map |
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | | `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, `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 | | `+` 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`] | | `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`] | | `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |

View File

@ -12,7 +12,7 @@ fn bench_iterations_1000(bench: &mut Bencher) {
let x = 1_000; let x = 1_000;
while x > 0 { while x > 0 {
x = x - 1; x -= 1;
} }
"#; "#;

View File

@ -6,7 +6,7 @@ let x = 10;
loop { loop {
print(x); print(x);
x = x - 1; x -= 1;
if x <= 0 { break; } if x <= 0 { break; }
} }

View File

@ -7,7 +7,7 @@ let x = 1_000_000;
print("Ready... Go!"); print("Ready... Go!");
while x > 0 { while x > 0 {
x = x - 1; x -= 1;
} }
print("Finished. Run time = " + now.elapsed() + " seconds."); print("Finished. Run time = " + now.elapsed() + " seconds.");

View File

@ -4,5 +4,5 @@ let x = 10;
while x > 0 { while x > 0 {
print(x); print(x);
x = x - 1; x -= 1;
} }

View File

@ -1354,10 +1354,11 @@ impl Engine {
let def_value = Some(&def_value); let def_value = Some(&def_value);
let pos = rhs.position(); let pos = rhs.position();
let hashes = (
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // 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())),
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); 0,
let hashes = (hash_fn, 0); );
let (r, _) = self.call_fn_raw( let (r, _) = self.call_fn_raw(
None, state, lib, op, hashes, args, false, def_value, pos, level, None, state, lib, op, hashes, args, false, def_value, pos, level,
@ -1417,12 +1418,13 @@ impl Engine {
// lhs = rhs // lhs = rhs
Expr::Assignment(x) => { Expr::Assignment(x) => {
let op_pos = x.2; let lhs_expr = &x.0;
let rhs_val = self.eval_expr(scope, state, lib, &x.1, level)?; 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 op= rhs
// name = rhs if let Expr::Variable(x) = &x.0 {
Expr::Variable(x) => {
let ((name, pos), modules, hash_var, index) = x.as_ref(); let ((name, pos), modules, hash_var, index) = x.as_ref();
let index = if state.always_search { None } else { *index }; let index = if state.always_search { None } else { *index };
let mod_and_hash = modules.as_ref().map(|m| (m.as_ref(), *hash_var)); let mod_and_hash = modules.as_ref().map(|m| (m.as_ref(), *hash_var));
@ -1433,6 +1435,22 @@ impl Engine {
ScopeEntryType::Constant => Err(Box::new( ScopeEntryType::Constant => Err(Box::new(
EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos), 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)?;
}
Ok(Default::default())
}
ScopeEntryType::Normal => { ScopeEntryType::Normal => {
*lhs_ptr = rhs_val; *lhs_ptr = rhs_val;
Ok(Default::default()) Ok(Default::default())
@ -1440,23 +1458,35 @@ impl Engine {
// End variable cannot be a module // End variable cannot be a module
ScopeEntryType::Module => unreachable!(), ScopeEntryType::Module => unreachable!(),
} }
} } else {
// idx_lhs[idx_expr] = rhs 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"))] #[cfg(not(feature = "no_index"))]
Expr::Index(x) => { Expr::Index(x) => self.eval_dot_index_chain(
let new_val = Some(rhs_val);
self.eval_dot_index_chain(
scope, state, lib, &x.0, &x.1, true, x.2, level, new_val, scope, state, lib, &x.0, &x.1, true, x.2, level, new_val,
) ),
} // dot_lhs.dot_rhs op= rhs
// dot_lhs.dot_rhs = rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Dot(x) => { Expr::Dot(x) => self.eval_dot_index_chain(
let new_val = Some(rhs_val);
self.eval_dot_index_chain(
scope, state, lib, &x.0, &x.1, false, op_pos, level, new_val, scope, state, lib, &x.0, &x.1, false, op_pos, level, new_val,
) ),
}
// Error assignment to constant // Error assignment to constant
expr if expr.is_constant() => { expr if expr.is_constant() => {
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
@ -1470,6 +1500,7 @@ impl Engine {
))), ))),
} }
} }
}
// lhs[idx_expr] // lhs[idx_expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1675,7 +1706,7 @@ impl Engine {
let result = self.eval_expr(scope, state, lib, expr, level)?; let result = self.eval_expr(scope, state, lib, expr, level)?;
Ok(match expr.as_ref() { 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(), Expr::Assignment(_) => Default::default(),
_ => result, _ => result,
}) })
@ -2079,3 +2110,85 @@ fn run_builtin_binary_op(
Ok(None) 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)
}

View File

@ -380,26 +380,26 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
stmt => Expr::Stmt(Box::new((stmt, x.1))), stmt => Expr::Stmt(Box::new((stmt, x.1))),
}, },
// id = expr // id = expr
Expr::Assignment(x) => match x.1 { Expr::Assignment(x) => match x.2 {
//id = id2 = expr2 //id = id2 op= expr2
Expr::Assignment(x2) => match (x.0, x2.0) { Expr::Assignment(x2) if x.1 == "=" => match (x.0, x2.0) {
// var = var = expr2 -> var = expr2 // var = var op= expr2 -> var op= expr2
(Expr::Variable(a), Expr::Variable(b)) (Expr::Variable(a), Expr::Variable(b))
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 => if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
{ {
// Assignment to the same variable - fold // Assignment to the same variable - fold
state.set_dirty(); 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) => { (id1, id2) => {
Expr::Assignment(Box::new(( 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 // id op= expr
expr => Expr::Assignment(Box::new((x.0, optimize_expr(expr, state), x.2))), expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
}, },
// lhs.rhs // lhs.rhs

View File

@ -53,6 +53,10 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
x.extend(y); x.extend(y);
Ok(()) Ok(())
}); });
lib.set_fn_2_mut("+=", |x: &mut Array, y: Array| {
x.extend(y);
Ok(())
});
lib.set_fn_2( lib.set_fn_2(
"+", "+",
|mut x: Array, y: Array| { |mut x: Array, y: Array| {

View File

@ -42,6 +42,15 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
Ok(()) 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( lib.set_fn_2(
"+", "+",
|mut map1: Map, map2: Map| { |mut map1: Map, map2: Map| {

View File

@ -105,10 +105,24 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
Ok(s.into()) 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| { lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| {
shared_make_mut(s).push(ch); shared_make_mut(s).push(ch);
Ok(()) 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( lib.set_fn_2_mut(
"append", "append",
|s: &mut ImmutableString, s2: ImmutableString| { |s: &mut ImmutableString, s2: ImmutableString| {

View File

@ -405,8 +405,8 @@ pub enum Expr {
Option<Dynamic>, Option<Dynamic>,
)>, )>,
), ),
/// expr = expr /// expr op= expr
Assignment(Box<(Expr, Expr, Position)>), Assignment(Box<(Expr, Cow<'static, str>, Expr, Position)>),
/// lhs.rhs /// lhs.rhs
Dot(Box<(Expr, Expr, Position)>), Dot(Box<(Expr, Expr, Position)>),
/// expr[expr] /// expr[expr]
@ -508,12 +508,13 @@ impl Expr {
Self::Stmt(x) => x.1, Self::Stmt(x) => x.1,
Self::Variable(x) => (x.0).1, Self::Variable(x) => (x.0).1,
Self::FnCall(x) => (x.0).2, Self::FnCall(x) => (x.0).2,
Self::Assignment(x) => x.0.position(),
Self::And(x) | Self::Or(x) | Self::In(x) => x.2, Self::And(x) | Self::Or(x) | Self::In(x) => x.2,
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos, 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::True(pos) => *pos = new_pos,
Self::False(pos) => *pos = new_pos, Self::False(pos) => *pos = new_pos,
Self::Unit(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::Dot(x) => x.2 = new_pos,
Self::Index(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"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x) => { Expr::FloatConstant(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "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( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(x.1)) .into_err(lhs.position()))
}
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))
} }
_ => (), _ => (),
@ -920,32 +916,25 @@ fn parse_index_chain<'a>(
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x) => { Expr::FloatConstant(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "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( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(x.1)) .into_err(lhs.position()))
}
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))
} }
_ => (), _ => (),
@ -953,46 +942,46 @@ fn parse_index_chain<'a>(
// lhs[float] // lhs[float]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x) => { x @ Expr::FloatConstant(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a float".into(), "Array access expects integer index, not a float".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// lhs[char] // lhs[char]
Expr::CharConstant(x) => { x @ Expr::CharConstant(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a character".into(), "Array access expects integer index, not a character".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// lhs[??? = ??? ] // lhs[??? = ??? ]
Expr::Assignment(x) => { x @ Expr::Assignment(_) => {
return Err(PERR::MalformedIndexExpr( 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[()] // lhs[()]
Expr::Unit(pos) => { x @ Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not ()".into(), "Array access expects integer index, not ()".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???] // 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( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(), "Array access expects integer index, not a boolean".into(),
) )
.into_err(x.2)) .into_err(x.position()))
} }
// lhs[true], lhs[false] // lhs[true], lhs[false]
Expr::True(pos) | Expr::False(pos) => { x @ Expr::True(_) | x @ Expr::False(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(), "Array access expects integer index, not a boolean".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// All other expressions // All other expressions
_ => (), _ => (),
@ -1391,17 +1380,22 @@ fn parse_unary<'a>(
} }
fn make_assignment_stmt<'a>( fn make_assignment_stmt<'a>(
fn_name: Cow<'static, str>,
state: &mut ParseState, state: &mut ParseState,
lhs: Expr, lhs: Expr,
rhs: Expr, rhs: Expr,
pos: Position, pos: Position,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
match &lhs { 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) => { Expr::Variable(x) => {
let ((name, name_pos), _, _, index) = x.as_ref(); let ((name, name_pos), _, _, index) = x.as_ref();
match state.stack[(state.len() - index.unwrap().get())].1 { 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 // Constant values cannot be assigned to
ScopeEntryType::Constant => { ScopeEntryType::Constant => {
Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) 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::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) => { Expr::Variable(x) => {
let ((name, name_pos), _, _, index) = x.as_ref(); let ((name, name_pos), _, _, index) = x.as_ref();
match state.stack[(state.len() - index.unwrap().get())].1 { 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 // Constant values cannot be assigned to
ScopeEntryType::Constant => { ScopeEntryType::Constant => {
Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos))
@ -1447,11 +1445,7 @@ fn parse_op_assignment_stmt<'a>(
} }
let op = match token { let op = match token {
Token::Equals => { Token::Equals => "".into(),
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::PlusAssign => Token::Plus.syntax(), Token::PlusAssign => Token::Plus.syntax(),
Token::MinusAssign => Token::Minus.syntax(), Token::MinusAssign => Token::Minus.syntax(),
Token::MultiplyAssign => Token::Multiply.syntax(), Token::MultiplyAssign => Token::Multiply.syntax(),
@ -1467,20 +1461,9 @@ fn parse_op_assignment_stmt<'a>(
_ => return Ok(lhs), _ => return Ok(lhs),
}; };
input.next(); let (_, pos) = input.next().unwrap();
let lhs_copy = lhs.clone();
let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?; let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?;
make_assignment_stmt(op, state, lhs, rhs, pos)
// 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 a dot expression. /// Make a dot expression.
@ -1553,33 +1536,26 @@ fn make_dot_expr(
/// Make an 'in' expression. /// Make an 'in' expression.
fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> { fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
match (&lhs, &rhs) { 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( return Err(PERR::MalformedInExpr(
"'in' expression expects a string, array or object map".into(), "'in' expression expects a string, array or object map".into(),
) )
.into_err(x.1)) .into_err(x.position()))
}
(_, 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))
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(_, Expr::FloatConstant(x)) => { (_, x @ Expr::FloatConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression expects a string, array or object map".into(), "'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! // "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" // 123.456 in "xxxx"
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(Expr::FloatConstant(x), Expr::StringConstant(_)) => { (x @ Expr::FloatConstant(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a float".into(), "'in' expression for a string expects a string, not a float".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// 123 in "xxxx" // 123 in "xxxx"
(Expr::IntegerConstant(x), Expr::StringConstant(_)) => { (x @ Expr::IntegerConstant(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a number".into(), "'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", // (??? && ???) 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" // true in "xxxx", false in "xxxx"
(Expr::True(pos), Expr::StringConstant(_)) (x @ Expr::And(_), Expr::StringConstant(_))
| (Expr::False(pos), 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( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a boolean".into(), "'in' expression for a string expects a string, not a boolean".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// [???, ???, ???] in "xxxx" // [???, ???, ???] in "xxxx"
(Expr::Array(x), Expr::StringConstant(_)) => { (x @ Expr::Array(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not an array".into(), "'in' expression for a string expects a string, not an array".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// #{...} in "xxxx" // #{...} in "xxxx"
(Expr::Map(x), Expr::StringConstant(_)) => { (x @ Expr::Map(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not an object map".into(), "'in' expression for a string expects a string, not an object map".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// (??? = ???) in "xxxx" // (??? = ???) in "xxxx"
(Expr::Assignment(x), Expr::StringConstant(_)) => { (x @ Expr::Assignment(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( 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" // () in "xxxx"
(Expr::Unit(pos), Expr::StringConstant(_)) => { (x @ Expr::Unit(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not ()".into(), "'in' expression for a string expects a string, not ()".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// "xxx" in #{...}, 'x' in #{...} - OK! // "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 #{...} // 123.456 in #{...}
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(Expr::FloatConstant(x), Expr::Map(_)) => { (x @ Expr::FloatConstant(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a float".into(), "'in' expression for an object map expects a string, not a float".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// 123 in #{...} // 123 in #{...}
(Expr::IntegerConstant(x), Expr::Map(_)) => { (x @ Expr::IntegerConstant(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a number".into(), "'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 #{...}, // (??? && ???) 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 #{...} // 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( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a boolean".into(), "'in' expression for an object map expects a string, not a boolean".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// [???, ???, ???] in #{..} // [???, ???, ???] in #{..}
(Expr::Array(x), Expr::Map(_)) => { (x @ Expr::Array(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not an array".into(), "'in' expression for an object map expects a string, not an array".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// #{...} in #{..} // #{...} in #{..}
(Expr::Map(x), Expr::Map(_)) => { (x @ Expr::Map(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not an object map".into(), "'in' expression for an object map expects a string, not an object map".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// (??? = ???) in #{...} // (??? = ???) in #{...}
(Expr::Assignment(x), Expr::Map(_)) => { (x @ Expr::Assignment(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( 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 #{...} // () in #{...}
(Expr::Unit(pos), Expr::Map(_)) => { (x @ Expr::Unit(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( 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 ()".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
_ => (), _ => (),