fn parse_postfix()

in rhai/src/parser.rs [1794:1993]


    fn parse_postfix(
        &self,
        input: &mut TokenStream,
        state: &mut ParseState,
        lib: &mut FnLib,
        settings: ParseSettings,
        mut lhs: Expr,
        _options: ChainingFlags,
    ) -> ParseResult<Expr> {
        let mut settings = settings;

        // Break just in case `lhs` is `Expr::Dot` or `Expr::Index`
        let mut parent_options = ASTFlags::BREAK;

        // Tail processing all possible postfix operators
        loop {
            let (tail_token, ..) = input.peek().expect(NEVER_ENDS);

            if !lhs.is_valid_postfix(tail_token) {
                break;
            }

            let (tail_token, tail_pos) = input.next().expect(NEVER_ENDS);
            settings.pos = tail_pos;

            lhs = match (lhs, tail_token) {
                // Qualified function call with !
                #[cfg(not(feature = "no_module"))]
                (Expr::Variable(x, ..), Token::Bang) if !x.1.is_empty() => {
                    return match input.peek().expect(NEVER_ENDS) {
                        (Token::LeftParen | Token::Unit, ..) => {
                            Err(LexError::UnexpectedInput(Token::Bang.into()).into_err(tail_pos))
                        }
                        _ => Err(LexError::ImproperSymbol(
                            "!".into(),
                            "'!' cannot be used to call module functions".into(),
                        )
                        .into_err(tail_pos)),
                    };
                }
                // Function call with !
                (Expr::Variable(x, .., pos), Token::Bang) => {
                    match input.peek().expect(NEVER_ENDS) {
                        (Token::LeftParen | Token::Unit, ..) => (),
                        (_, pos) => {
                            return Err(PERR::MissingToken(
                                Token::LeftParen.into(),
                                "to start arguments list of function call".into(),
                            )
                            .into_err(*pos))
                        }
                    }

                    let no_args = input.next().expect(NEVER_ENDS).0 == Token::Unit;

                    let (.., ns, _, name) = *x;
                    settings.pos = pos;

                    self.parse_fn_call(input, state, lib, settings, name, no_args, true, ns)?
                }
                // Function call
                (Expr::Variable(x, .., pos), t @ (Token::LeftParen | Token::Unit)) => {
                    let (.., ns, _, name) = *x;
                    let no_args = t == Token::Unit;
                    settings.pos = pos;

                    self.parse_fn_call(input, state, lib, settings, name, no_args, false, ns)?
                }
                // Disallowed module separator
                #[cfg(not(feature = "no_module"))]
                (_, token @ Token::DoubleColon)
                    if _options.contains(ChainingFlags::DISALLOW_NAMESPACES) =>
                {
                    return Err(LexError::ImproperSymbol(
                        token.literal_syntax().into(),
                        String::new(),
                    )
                    .into_err(tail_pos))
                }
                // module access
                #[cfg(not(feature = "no_module"))]
                (Expr::Variable(x, .., pos), Token::DoubleColon) => {
                    let (id2, pos2) = parse_var_name(input)?;
                    let (.., mut namespace, _, name) = *x;
                    let var_name_def = Ident { name, pos };

                    namespace.push(var_name_def);

                    let var_name = state.get_interned_string(id2);

                    Expr::Variable((None, namespace, 0, var_name).into(), None, pos2)
                }
                // Indexing
                #[cfg(not(feature = "no_index"))]
                (expr, token @ (Token::LeftBracket | Token::QuestionBracket)) => {
                    let opt = match token {
                        Token::LeftBracket => ASTFlags::empty(),
                        Token::QuestionBracket => ASTFlags::NEGATED,
                        _ => unreachable!("`[` or `?[`"),
                    };
                    let settings = settings.level_up()?;
                    self.parse_index_chain(input, state, lib, settings, expr, opt, true)?
                }
                // Property access
                #[cfg(not(feature = "no_object"))]
                (expr, op @ (Token::Period | Token::Elvis)) => {
                    // Expression after dot must start with an identifier
                    match input.peek().expect(NEVER_ENDS) {
                        (Token::Identifier(..), ..) => {
                            // Prevents capturing of the object properties as vars: xxx.<var>
                            state.allow_capture = false;
                        }
                        (Token::Reserved(s), ..) if is_reserved_keyword_or_symbol(s).2 => (),
                        (Token::Reserved(s), pos) => {
                            return Err(PERR::Reserved(s.to_string()).into_err(*pos))
                        }
                        (.., pos) => return Err(PERR::PropertyExpected.into_err(*pos)),
                    }

                    let op_flags = match op {
                        Token::Period => ASTFlags::empty(),
                        Token::Elvis => ASTFlags::NEGATED,
                        _ => unreachable!("`.` or `?.`"),
                    };
                    let options = ChainingFlags::PROPERTY | ChainingFlags::DISALLOW_NAMESPACES;
                    let rhs =
                        self.parse_primary(input, state, lib, settings.level_up()?, options)?;

                    Self::make_dot_expr(state, expr, rhs, parent_options, op_flags, tail_pos)?
                }
                // Unknown postfix operator
                (expr, token) => {
                    unreachable!("unknown postfix operator '{}' for {:?}", token, expr)
                }
            };

            // The chain is now extended
            parent_options = ASTFlags::empty();
        }

        // Optimize chain where the root expression is another chain
        #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
        if matches!(lhs, Expr::Index(ref x, ..) | Expr::Dot(ref x, ..) if matches!(x.lhs, Expr::Index(..) | Expr::Dot(..)))
        {
            optimize_combo_chain(&mut lhs)
        }

        // Cache the hash key for namespace-qualified variables
        #[cfg(not(feature = "no_module"))]
        let namespaced_variable = match lhs {
            Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x),
            Expr::Index(ref mut x, ..) | Expr::Dot(ref mut x, ..) => match x.lhs {
                Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x),
                _ => None,
            },
            _ => None,
        };

        #[cfg(not(feature = "no_module"))]
        if let Some((.., namespace, hash, name)) = namespaced_variable {
            if !namespace.is_empty() {
                *hash = crate::calc_var_hash(namespace.iter().map(Ident::as_str), name);

                #[cfg(not(feature = "no_module"))]
                {
                    let root = namespace.root();
                    let index = state.find_module(root);
                    let is_global = false;

                    #[cfg(not(feature = "no_function"))]
                    #[cfg(not(feature = "no_module"))]
                    let is_global = is_global || root == crate::engine::KEYWORD_GLOBAL;

                    if settings.has_option(LangOptions::STRICT_VAR)
                        && index.is_none()
                        && !is_global
                        && !state
                            .global_imports
                            .as_deref()
                            .into_iter()
                            .flatten()
                            .any(|m| m.as_str() == root)
                        && !self
                            .global_sub_modules
                            .as_ref()
                            .map_or(false, |m| m.contains_key(root))
                    {
                        return Err(
                            PERR::ModuleUndefined(root.into()).into_err(namespace.position())
                        );
                    }

                    namespace.set_index(index);
                }
            }
        }

        // Make sure identifiers are valid
        Ok(lhs)
    }