fn parse_binary_op()

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)
                }
            };
        }
    }