in rhai/src/parser.rs [2370:2538]
fn parse_binary_op(
&self,
input: &mut TokenStream,
state: &mut ParseState,
lib: &mut FnLib,
settings: ParseSettings,
parent_precedence: Option<Precedence>,
lhs: Expr,
) -> ParseResult<Expr> {
let mut settings = settings;
settings.pos = lhs.position();
let mut root = lhs;
loop {
let (current_op, current_pos) = input.peek().expect(NEVER_ENDS);
if !(state.expr_filter)(current_op) {
return Ok(root);
}
let precedence = match current_op {
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(c) => self
.custom_keywords
.as_ref()
.and_then(|m| m.get(&**c))
.copied()
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?,
Token::Reserved(c) if !is_valid_identifier(c) => {
return Err(PERR::UnknownOperator(c.to_string()).into_err(*current_pos))
}
_ => current_op.precedence(),
};
let bind_right = current_op.is_bind_right();
// Bind left to the parent lhs expression if precedence is higher
// If same precedence, then check if the operator binds right
if precedence < parent_precedence || (precedence == parent_precedence && !bind_right) {
return Ok(root);
}
let (op_token, pos) = input.next().expect(NEVER_ENDS);
let rhs = self.parse_unary(input, state, lib, settings)?;
let (next_op, next_pos) = input.peek().expect(NEVER_ENDS);
let next_precedence = match next_op {
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(c) => self
.custom_keywords
.as_ref()
.and_then(|m| m.get(&**c))
.copied()
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?,
Token::Reserved(c) if !is_valid_identifier(c) => {
return Err(PERR::UnknownOperator(c.to_string()).into_err(*next_pos))
}
_ => next_op.precedence(),
};
// Bind to right if the next operator has higher precedence
// If same precedence, then check if the operator binds right
let rhs =
if (precedence == next_precedence && bind_right) || precedence < next_precedence {
self.parse_binary_op(input, state, lib, settings, precedence, rhs)?
} else {
// Otherwise bind to left (even if next operator has the same precedence)
rhs
};
settings = settings.level_up()?;
settings.pos = pos;
let op = op_token.to_string();
let hash = calc_fn_hash(None, &op, 2);
let native_only = !is_valid_function_name(&op);
let mut args = FnArgsVec::new_const();
args.push(root);
args.push(rhs);
args.shrink_to_fit();
let mut op_base = FnCallExpr {
namespace: Namespace::NONE,
name: state.get_interned_string(&op),
hashes: FnCallHashes::from_native_only(hash),
args,
op_token: native_only.then(|| op_token.clone()),
capture_parent_scope: false,
};
root = match op_token {
// '!=' defaults to true when passed invalid operands
Token::NotEqualsTo => op_base.into_fn_call_expr(pos),
// Comparison operators default to false when passed invalid operands
Token::EqualsTo
| Token::LessThan
| Token::LessThanEqualsTo
| Token::GreaterThan
| Token::GreaterThanEqualsTo => {
let pos = op_base.args[0].start_position();
op_base.into_fn_call_expr(pos)
}
Token::Or => {
let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?;
let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?;
Expr::Or(BinaryExpr { lhs, rhs }.into(), pos)
}
Token::And => {
let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?;
let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?;
Expr::And(BinaryExpr { lhs, rhs }.into(), pos)
}
Token::DoubleQuestion => {
let rhs = op_base.args.pop().unwrap();
let lhs = op_base.args.pop().unwrap();
Expr::Coalesce(BinaryExpr { lhs, rhs }.into(), pos)
}
Token::In | Token::NotIn => {
// Swap the arguments
let lhs = op_base.args.remove(0);
let pos = lhs.start_position();
op_base.args.push(lhs);
op_base.args.shrink_to_fit();
// Convert into a call to `contains`
op_base.hashes = FnCallHashes::from_hash(calc_fn_hash(None, OP_CONTAINS, 2));
op_base.name = state.get_interned_string(OP_CONTAINS);
let fn_call = op_base.into_fn_call_expr(pos);
if op_token == Token::In {
fn_call
} else {
// Put a `!` call in front
let mut args = FnArgsVec::new_const();
args.push(fn_call);
let not_base = FnCallExpr {
namespace: Namespace::NONE,
name: state.get_interned_string(OP_NOT),
hashes: FnCallHashes::from_native_only(calc_fn_hash(None, OP_NOT, 1)),
args,
op_token: Some(Token::Bang),
capture_parent_scope: false,
};
not_base.into_fn_call_expr(pos)
}
}
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(s) if self.is_custom_keyword(s.as_str()) => {
op_base.hashes = if native_only {
FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2))
} else {
FnCallHashes::from_hash(calc_fn_hash(None, &s, 2))
};
op_base.into_fn_call_expr(pos)
}
_ => {
let pos = op_base.args[0].start_position();
op_base.into_fn_call_expr(pos)
}
};
}
}