Move Assignment to Stmt.

This commit is contained in:
Stephen Chung 2020-10-27 23:21:20 +08:00
parent fd2ba54b49
commit 51fca1e757
3 changed files with 174 additions and 195 deletions

View File

@ -1520,159 +1520,6 @@ impl Engine {
// Statement block // Statement block
Expr::Stmt(x) => self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level), Expr::Stmt(x) => self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level),
// var op= rhs
Expr::Assignment(x) if x.0.get_variable_access(false).is_some() => {
let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref();
let mut rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten();
let (mut lhs_ptr, name, typ, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
if !lhs_ptr.is_ref() {
return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into();
}
self.inc_operations(state)
.map_err(|err| err.fill_position(pos))?;
match typ {
// Assignment to constant variable
ScopeEntryType::Constant => Err(Box::new(
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos),
)),
// Normal assignment
ScopeEntryType::Normal if op.is_empty() => {
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = rhs_val;
} else {
*lhs_ptr.as_mut() = rhs_val;
}
Ok(Default::default())
}
// Op-assignment - in order of precedence:
ScopeEntryType::Normal => {
// 1) Native registered overriding function
// 2) Built-in implementation
// 3) Map to `var = var op rhs`
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let arg_types =
once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id()));
let hash_fn = calc_fn_hash(empty(), op, 2, arg_types);
match self
.global_module
.get_fn(hash_fn, false)
.or_else(|| self.packages.get_fn(hash_fn, false))
{
// op= function registered as method
Some(func) if func.is_method() => {
let mut lock_guard;
let lhs_ptr_inner;
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
lock_guard = lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap();
lhs_ptr_inner = lock_guard.deref_mut();
} else {
lhs_ptr_inner = lhs_ptr.as_mut();
}
let args = &mut [lhs_ptr_inner, &mut rhs_val];
// Overriding exact implementation
if func.is_plugin_fn() {
func.get_plugin_fn().call((self, lib).into(), args)?;
} else {
func.get_native_fn()((self, lib).into(), args)?;
}
}
// Built-in op-assignment function
_ if run_builtin_op_assignment(op, lhs_ptr.as_mut(), &rhs_val)?
.is_some() => {}
// Not built-in: expand to `var = var op rhs`
_ => {
let op = &op[..op.len() - 1]; // extract operator without =
// Clone the LHS value
let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val];
// Run function
let (value, _) = self
.exec_fn_call(
state, lib, op, 0, args, false, false, false, None, &None,
level,
)
.map_err(|err| err.fill_position(*op_pos))?;
let value = value.flatten();
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = value;
} else {
*lhs_ptr.as_mut() = value;
}
}
}
Ok(Default::default())
}
}
}
// lhs op= rhs
Expr::Assignment(x) => {
let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref();
let mut rhs_val =
self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?;
let _new_val = if op.is_empty() {
// Normal assignment
Some((rhs_val, rhs_expr.position()))
} else {
// Op-assignment - always map to `lhs = lhs op rhs`
let op = &op[..op.len() - 1]; // extract operator without =
let args = &mut [
&mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?,
&mut rhs_val,
];
let result = self
.exec_fn_call(
state, lib, op, 0, args, false, false, false, None, &None, level,
)
.map(|(v, _)| v)
.map_err(|err| err.fill_position(*op_pos))?;
Some((result, rhs_expr.position()))
};
// Must be either `var[index] op= val` or `var.prop op= val`
match lhs_expr {
// name op= rhs (handled above)
Expr::Variable(_) => unreachable!(),
// idx_lhs[idx_expr] op= rhs
#[cfg(not(feature = "no_index"))]
Expr::Index(_) => {
self.eval_dot_index_chain(
scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val,
)?;
Ok(Default::default())
}
// dot_lhs.dot_rhs op= rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(_) => {
self.eval_dot_index_chain(
scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val,
)?;
Ok(Default::default())
}
// Constant expression (should be caught during parsing)
expr if expr.is_constant() => unreachable!(),
// Syntax error
expr => EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into(),
}
}
// lhs[idx_expr] // lhs[idx_expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(_) => { Expr::Index(_) => {
@ -1810,6 +1657,159 @@ impl Engine {
// Expression as statement // Expression as statement
Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level), Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level),
// var op= rhs
Stmt::Assignment(x, op_pos) if x.0.get_variable_access(false).is_some() => {
let (lhs_expr, op, rhs_expr) = x.as_ref();
let mut rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten();
let (mut lhs_ptr, name, typ, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
if !lhs_ptr.is_ref() {
return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into();
}
self.inc_operations(state)
.map_err(|err| err.fill_position(pos))?;
match typ {
// Assignment to constant variable
ScopeEntryType::Constant => Err(Box::new(
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos),
)),
// Normal assignment
ScopeEntryType::Normal if op.is_empty() => {
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = rhs_val;
} else {
*lhs_ptr.as_mut() = rhs_val;
}
Ok(Default::default())
}
// Op-assignment - in order of precedence:
ScopeEntryType::Normal => {
// 1) Native registered overriding function
// 2) Built-in implementation
// 3) Map to `var = var op rhs`
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let arg_types =
once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id()));
let hash_fn = calc_fn_hash(empty(), op, 2, arg_types);
match self
.global_module
.get_fn(hash_fn, false)
.or_else(|| self.packages.get_fn(hash_fn, false))
{
// op= function registered as method
Some(func) if func.is_method() => {
let mut lock_guard;
let lhs_ptr_inner;
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
lock_guard = lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap();
lhs_ptr_inner = lock_guard.deref_mut();
} else {
lhs_ptr_inner = lhs_ptr.as_mut();
}
let args = &mut [lhs_ptr_inner, &mut rhs_val];
// Overriding exact implementation
if func.is_plugin_fn() {
func.get_plugin_fn().call((self, lib).into(), args)?;
} else {
func.get_native_fn()((self, lib).into(), args)?;
}
}
// Built-in op-assignment function
_ if run_builtin_op_assignment(op, lhs_ptr.as_mut(), &rhs_val)?
.is_some() => {}
// Not built-in: expand to `var = var op rhs`
_ => {
let op = &op[..op.len() - 1]; // extract operator without =
// Clone the LHS value
let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val];
// Run function
let (value, _) = self
.exec_fn_call(
state, lib, op, 0, args, false, false, false, None, &None,
level,
)
.map_err(|err| err.fill_position(*op_pos))?;
let value = value.flatten();
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = value;
} else {
*lhs_ptr.as_mut() = value;
}
}
}
Ok(Default::default())
}
}
}
// lhs op= rhs
Stmt::Assignment(x, op_pos) => {
let (lhs_expr, op, rhs_expr) = x.as_ref();
let mut rhs_val =
self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?;
let _new_val = if op.is_empty() {
// Normal assignment
Some((rhs_val, rhs_expr.position()))
} else {
// Op-assignment - always map to `lhs = lhs op rhs`
let op = &op[..op.len() - 1]; // extract operator without =
let args = &mut [
&mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?,
&mut rhs_val,
];
let result = self
.exec_fn_call(
state, lib, op, 0, args, false, false, false, None, &None, level,
)
.map(|(v, _)| v)
.map_err(|err| err.fill_position(*op_pos))?;
Some((result, rhs_expr.position()))
};
// Must be either `var[index] op= val` or `var.prop op= val`
match lhs_expr {
// name op= rhs (handled above)
Expr::Variable(_) => unreachable!(),
// idx_lhs[idx_expr] op= rhs
#[cfg(not(feature = "no_index"))]
Expr::Index(_) => {
self.eval_dot_index_chain(
scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val,
)?;
Ok(Default::default())
}
// dot_lhs.dot_rhs op= rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(_) => {
self.eval_dot_index_chain(
scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val,
)?;
Ok(Default::default())
}
// Constant expression (should be caught during parsing)
expr if expr.is_constant() => unreachable!(),
// Syntax error
expr => EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into(),
}
}
// Block scope // Block scope
Stmt::Block(statements, _) => { Stmt::Block(statements, _) => {
let prev_scope_len = scope.len(); let prev_scope_len = scope.len();

View File

@ -163,6 +163,11 @@ fn call_fn_with_constant_arguments(
/// Optimize a statement. /// Optimize a statement.
fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
match stmt { match stmt {
// id op= expr
Stmt::Assignment(x, pos) => Stmt::Assignment(
Box::new((optimize_expr(x.0, state), x.1, optimize_expr(x.2, state))),
pos,
),
// if false { if_block } -> Noop // if false { if_block } -> Noop
Stmt::IfThenElse(Expr::False(pos), x, _) if x.1.is_none() => { Stmt::IfThenElse(Expr::False(pos), x, _) if x.1.is_none() => {
state.set_dirty(); state.set_dirty();
@ -442,8 +447,6 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// ( stmt ) // ( stmt )
stmt => Expr::Stmt(Box::new((stmt, x.1))), stmt => Expr::Stmt(Box::new((stmt, x.1))),
}, },
// id op= expr
Expr::Assignment(x) => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(x.2, state), x.3))),
// lhs.rhs // lhs.rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]

View File

@ -753,6 +753,8 @@ pub enum Stmt {
Let(Box<(String, Position)>, Option<Expr>, Position), Let(Box<(String, Position)>, Option<Expr>, Position),
/// const id = expr /// const id = expr
Const(Box<(String, Position)>, Option<Expr>, Position), Const(Box<(String, Position)>, Option<Expr>, Position),
/// expr op= expr
Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position),
/// { stmt; ... } /// { stmt; ... }
Block(Vec<Stmt>, Position), Block(Vec<Stmt>, Position),
/// try { stmt; ... } catch ( var ) { stmt; ... } /// try { stmt; ... } catch ( var ) { stmt; ... }
@ -808,6 +810,7 @@ impl Stmt {
| Self::Continue(pos) | Self::Continue(pos)
| Self::Break(pos) | Self::Break(pos)
| Self::Block(_, pos) | Self::Block(_, pos)
| Self::Assignment(_, pos)
| Self::IfThenElse(_, _, pos) | Self::IfThenElse(_, _, pos)
| Self::While(_, _, pos) | Self::While(_, _, pos)
| Self::Loop(_, pos) | Self::Loop(_, pos)
@ -836,6 +839,7 @@ impl Stmt {
| Self::Continue(pos) | Self::Continue(pos)
| Self::Break(pos) | Self::Break(pos)
| Self::Block(_, pos) | Self::Block(_, pos)
| Self::Assignment(_, pos)
| Self::IfThenElse(_, _, pos) | Self::IfThenElse(_, _, pos)
| Self::While(_, _, pos) | Self::While(_, _, pos)
| Self::Loop(_, pos) | Self::Loop(_, pos)
@ -876,6 +880,7 @@ impl Stmt {
Self::Let(_, _, _) Self::Let(_, _, _)
| Self::Const(_, _, _) | Self::Const(_, _, _)
| Self::Assignment(_, _)
| Self::Expr(_) | Self::Expr(_)
| Self::Continue(_) | Self::Continue(_)
| Self::Break(_) | Self::Break(_)
@ -901,7 +906,7 @@ impl Stmt {
Self::While(condition, block, _) => condition.is_pure() && block.is_pure(), Self::While(condition, block, _) => condition.is_pure() && block.is_pure(),
Self::Loop(block, _) => block.is_pure(), Self::Loop(block, _) => block.is_pure(),
Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(), Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(),
Self::Let(_, _, _) | Self::Const(_, _, _) => false, Self::Let(_, _, _) | Self::Const(_, _, _) | Self::Assignment(_, _) => false,
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false, Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false,
Self::TryCatch(x) => (x.0).0.is_pure() && (x.2).0.is_pure(), Self::TryCatch(x) => (x.0).0.is_pure() && (x.2).0.is_pure(),
@ -1040,8 +1045,6 @@ pub enum Expr {
Option<bool>, // Default value is `bool` in order for `Expr` to be `Hash`. Option<bool>, // Default value is `bool` in order for `Expr` to be `Hash`.
)>, )>,
), ),
/// expr op= expr
Assignment(Box<(Expr, Cow<'static, str>, Expr, Position)>),
/// lhs.rhs /// lhs.rhs
Dot(Box<BinaryExpr>), Dot(Box<BinaryExpr>),
/// expr[expr] /// expr[expr]
@ -1168,7 +1171,6 @@ 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).3, Self::FnCall(x) => (x.0).3,
Self::Assignment(x) => x.0.position(),
Self::And(x) | Self::Or(x) | Self::In(x) => x.pos, Self::And(x) | Self::Or(x) | Self::In(x) => x.pos,
@ -1202,7 +1204,6 @@ impl Expr {
Self::FnCall(x) => (x.0).3 = new_pos, Self::FnCall(x) => (x.0).3 = new_pos,
Self::And(x) | Self::Or(x) | Self::In(x) => x.pos = new_pos, Self::And(x) | Self::Or(x) | Self::In(x) => x.pos = new_pos,
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos, Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos = new_pos,
Self::Assignment(x) => x.3 = new_pos,
Self::Dot(x) | Self::Index(x) => x.pos = new_pos, Self::Dot(x) | Self::Index(x) => x.pos = new_pos,
Self::Custom(x) => x.pos = new_pos, Self::Custom(x) => x.pos = new_pos,
} }
@ -1322,8 +1323,7 @@ impl Expr {
| Self::Or(_) | Self::Or(_)
| Self::True(_) | Self::True(_)
| Self::False(_) | Self::False(_)
| Self::Unit(_) | Self::Unit(_) => false,
| Self::Assignment(_) => false,
Self::StringConstant(_) Self::StringConstant(_)
| Self::Stmt(_) | Self::Stmt(_)
@ -1598,7 +1598,6 @@ fn parse_index_chain(
} }
Expr::CharConstant(_) Expr::CharConstant(_)
| Expr::Assignment(_)
| Expr::And(_) | Expr::And(_)
| Expr::Or(_) | Expr::Or(_)
| Expr::In(_) | Expr::In(_)
@ -1634,7 +1633,6 @@ fn parse_index_chain(
} }
Expr::CharConstant(_) Expr::CharConstant(_)
| Expr::Assignment(_)
| Expr::And(_) | Expr::And(_)
| Expr::Or(_) | Expr::Or(_)
| Expr::In(_) | Expr::In(_)
@ -1665,13 +1663,6 @@ fn parse_index_chain(
) )
.into_err(x.position())) .into_err(x.position()))
} }
// lhs[??? = ??? ]
x @ Expr::Assignment(_) => {
return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not an assignment".into(),
)
.into_err(x.position()))
}
// lhs[()] // lhs[()]
x @ Expr::Unit(_) => { x @ Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
@ -2249,18 +2240,18 @@ fn make_assignment_stmt<'a>(
lhs: Expr, lhs: Expr,
rhs: Expr, rhs: Expr,
pos: Position, pos: Position,
) -> Result<Expr, ParseError> { ) -> Result<Stmt, ParseError> {
match &lhs { match &lhs {
// var (non-indexed) = rhs // var (non-indexed) = rhs
Expr::Variable(x) if x.3.is_none() => { Expr::Variable(x) if x.3.is_none() => {
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos))
} }
// var (indexed) = rhs // var (indexed) = rhs
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.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
ScopeEntryType::Normal => { ScopeEntryType::Normal => {
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) Ok(Stmt::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 => {
@ -2272,14 +2263,14 @@ fn make_assignment_stmt<'a>(
Expr::Index(x) | Expr::Dot(x) => match &x.lhs { Expr::Index(x) | Expr::Dot(x) => match &x.lhs {
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs
Expr::Variable(x) if x.3.is_none() => { Expr::Variable(x) if x.3.is_none() => {
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos))
} }
// var[???] (indexed) = rhs, var.??? (indexed) = rhs // var[???] (indexed) = rhs, var.??? (indexed) = rhs
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.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
ScopeEntryType::Normal => { ScopeEntryType::Normal => {
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) Ok(Stmt::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 => {
@ -2310,7 +2301,7 @@ fn parse_op_assignment_stmt(
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
lhs: Expr, lhs: Expr,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Stmt, ParseError> {
let (token, token_pos) = input.peek().unwrap(); let (token, token_pos) = input.peek().unwrap();
settings.pos = *token_pos; settings.pos = *token_pos;
@ -2332,7 +2323,7 @@ fn parse_op_assignment_stmt(
| Token::OrAssign | Token::OrAssign
| Token::XOrAssign => token.syntax(), | Token::XOrAssign => token.syntax(),
_ => return Ok(lhs), _ => return Ok(Stmt::Expr(lhs)),
}; };
let (_, pos) = input.next().unwrap(); let (_, pos) = input.next().unwrap();
@ -2436,7 +2427,6 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseErr
| (_, x @ Expr::And(_)) | (_, x @ Expr::And(_))
| (_, x @ Expr::Or(_)) | (_, x @ Expr::Or(_))
| (_, x @ Expr::In(_)) | (_, x @ Expr::In(_))
| (_, x @ Expr::Assignment(_))
| (_, x @ Expr::True(_)) | (_, x @ Expr::True(_))
| (_, x @ Expr::False(_)) | (_, x @ Expr::False(_))
| (_, x @ Expr::Unit(_)) => { | (_, x @ Expr::Unit(_)) => {
@ -2499,13 +2489,6 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseErr
) )
.into_err(x.position())) .into_err(x.position()))
} }
// (??? = ???) in "xxxx"
(x @ Expr::Assignment(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not an assignment".into(),
)
.into_err(x.position()))
}
// () in "xxxx" // () in "xxxx"
(x @ Expr::Unit(_), Expr::StringConstant(_)) => { (x @ Expr::Unit(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
@ -2558,13 +2541,6 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseErr
) )
.into_err(x.position())) .into_err(x.position()))
} }
// (??? = ???) in #{...}
(x @ Expr::Assignment(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not an assignment".into(),
)
.into_err(x.position()))
}
// () in #{...} // () in #{...}
(x @ Expr::Unit(_), Expr::Map(_)) => { (x @ Expr::Unit(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
@ -3290,8 +3266,8 @@ fn parse_expr_stmt(
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
let expr = parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?; let stmt = parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?;
Ok(Stmt::Expr(expr)) Ok(stmt)
} }
/// Parse a single statement. /// Parse a single statement.