fn parse_primary()

in rhai/src/parser.rs [1383:1791]


    fn parse_primary(
        &self,
        input: &mut TokenStream,
        state: &mut ParseState,
        lib: &mut FnLib,
        settings: ParseSettings,
        options: ChainingFlags,
    ) -> ParseResult<Expr> {
        let (token, token_pos) = input.peek().expect(NEVER_ENDS);

        let mut settings = settings;
        settings.pos = *token_pos;

        let root_expr = match token {
            _ if !(state.expr_filter)(token) => {
                return Err(LexError::UnexpectedInput(token.to_string()).into_err(settings.pos))
            }

            Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),

            Token::Unit => {
                input.next();
                Expr::Unit(settings.pos)
            }

            Token::IntegerConstant(..)
            | Token::CharConstant(..)
            | Token::StringConstant(..)
            | Token::True
            | Token::False => match input.next().expect(NEVER_ENDS).0 {
                Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos),
                Token::CharConstant(c) => Expr::CharConstant(c, settings.pos),
                Token::StringConstant(s) => {
                    Expr::StringConstant(state.get_interned_string(*s), settings.pos)
                }
                Token::True => Expr::BoolConstant(true, settings.pos),
                Token::False => Expr::BoolConstant(false, settings.pos),
                token => unreachable!("token is {:?}", token),
            },
            #[cfg(not(feature = "no_float"))]
            Token::FloatConstant(x) => {
                let x = *x;
                input.next();
                Expr::FloatConstant(x, settings.pos)
            }
            #[cfg(feature = "decimal")]
            Token::DecimalConstant(x) => {
                let x = (**x).into();
                input.next();
                Expr::DynamicConstant(Box::new(x), settings.pos)
            }

            // { - block statement as expression
            Token::LeftBrace if settings.has_option(LangOptions::STMT_EXPR) => {
                match self.parse_block(input, state, lib, settings.level_up()?)? {
                    block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())),
                    stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
                }
            }

            // ( - grouped expression
            Token::LeftParen => {
                settings.pos = eat_token(input, Token::LeftParen);

                let expr = self.parse_expr(input, state, lib, settings.level_up()?)?;

                match input.next().expect(NEVER_ENDS) {
                    // ( ... )
                    (Token::RightParen, ..) => expr,
                    // ( <error>
                    (Token::LexError(err), pos) => return Err(err.into_err(pos)),
                    // ( ... ???
                    (.., pos) => {
                        return Err(PERR::MissingToken(
                            Token::RightParen.into(),
                            "for a matching ( in this expression".into(),
                        )
                        .into_err(pos))
                    }
                }
            }

            // If statement is allowed to act as expressions
            Token::If if settings.has_option(LangOptions::IF_EXPR) => Expr::Stmt(Box::new(
                self.parse_if(input, state, lib, settings.level_up()?)?
                    .into(),
            )),
            // Loops are allowed to act as expressions
            Token::While | Token::Loop
                if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) =>
            {
                Expr::Stmt(Box::new(
                    self.parse_while_loop(input, state, lib, settings.level_up()?)?
                        .into(),
                ))
            }
            Token::Do if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => {
                Expr::Stmt(Box::new(
                    self.parse_do(input, state, lib, settings.level_up()?)?
                        .into(),
                ))
            }
            Token::For if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => {
                Expr::Stmt(Box::new(
                    self.parse_for(input, state, lib, settings.level_up()?)?
                        .into(),
                ))
            }
            // Switch statement is allowed to act as expressions
            Token::Switch if settings.has_option(LangOptions::SWITCH_EXPR) => Expr::Stmt(Box::new(
                self.parse_switch(input, state, lib, settings.level_up()?)?
                    .into(),
            )),

            // | ...
            #[cfg(not(feature = "no_function"))]
            Token::Pipe | Token::Or if settings.has_option(LangOptions::ANON_FN) => {
                // Build new parse state
                let new_interner = &mut StringsInterner::new();
                let new_state = &mut ParseState::new(
                    state.external_constants,
                    new_interner,
                    state.tokenizer_control.clone(),
                );

                // We move the strings interner to the new parse state object by swapping it...
                std::mem::swap(state.interned_strings, new_state.interned_strings);

                #[cfg(not(feature = "no_module"))]
                {
                    // Do not allow storing an index to a globally-imported module
                    // just in case the function is separated from this `AST`.
                    //
                    // Keep them in `global_imports` instead so that strict variables
                    // mode will not complain.
                    new_state.global_imports.clone_from(&state.global_imports);
                    new_state
                        .global_imports
                        .get_or_insert_with(Default::default)
                        .extend(state.imports.as_deref().into_iter().flatten().cloned());
                }

                // Brand new options
                #[cfg(not(feature = "no_closure"))]
                let options = self.options & !LangOptions::STRICT_VAR; // a capturing closure can access variables not defined locally, so turn off Strict Variables mode
                #[cfg(feature = "no_closure")]
                let options = self.options | (settings.options & LangOptions::STRICT_VAR);

                // Brand new flags, turn on function scope and closure scope
                let flags = ParseSettingFlags::FN_SCOPE
                    | ParseSettingFlags::CLOSURE_SCOPE
                    | (settings.flags
                        & (ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES
                            | ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS));

                let new_settings = ParseSettings {
                    flags,
                    options,
                    ..settings
                };

                let result =
                    self.parse_anon_fn(input, new_state, lib, new_settings.level_up()?, state);

                // Restore the strings interner by swapping it back
                std::mem::swap(state.interned_strings, new_state.interned_strings);

                let (expr, fn_def) = result?;

                #[cfg(not(feature = "no_closure"))]
                for Ident { name, pos } in new_state.external_vars.as_deref().into_iter().flatten()
                {
                    let (index, is_func) = state.access_var(name, lib, *pos);

                    if !is_func
                        && index.is_none()
                        && !settings.has_flag(ParseSettingFlags::CLOSURE_SCOPE)
                        && settings.has_option(LangOptions::STRICT_VAR)
                        && !state
                            .external_constants
                            .map_or(false, |scope| scope.contains(name))
                    {
                        // If the parent scope is not inside another capturing closure
                        // then we can conclude that the captured variable doesn't exist.
                        // Under Strict Variables mode, this is not allowed.
                        return Err(PERR::VariableUndefined(name.to_string()).into_err(*pos));
                    }
                }

                let hash_script = calc_fn_hash(None, &fn_def.name, fn_def.params.len());
                lib.insert(hash_script, fn_def);

                expr
            }

            // Interpolated string
            Token::InterpolatedString(..) => {
                let mut segments = FnArgsVec::new_const();
                let settings = settings.level_up()?;

                match input.next().expect(NEVER_ENDS) {
                    (Token::InterpolatedString(s), ..) if s.is_empty() => (),
                    (Token::InterpolatedString(s), pos) => {
                        segments.push(Expr::StringConstant(state.get_interned_string(*s), pos))
                    }
                    token => {
                        unreachable!("Token::InterpolatedString expected but gets {:?}", token)
                    }
                }

                loop {
                    let expr = match self.parse_block(input, state, lib, settings)? {
                        block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())),
                        stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
                    };
                    match expr {
                        Expr::StringConstant(s, ..) if s.is_empty() => (),
                        _ => segments.push(expr),
                    }

                    // Make sure to parse the following as text
                    state.tokenizer_control.borrow_mut().is_within_text = true;

                    match input.next().expect(NEVER_ENDS) {
                        (Token::StringConstant(s), pos) => {
                            if !s.is_empty() {
                                segments
                                    .push(Expr::StringConstant(state.get_interned_string(*s), pos));
                            }
                            // End the interpolated string if it is terminated by a back-tick.
                            break;
                        }
                        (Token::InterpolatedString(s), pos) => {
                            if !s.is_empty() {
                                segments
                                    .push(Expr::StringConstant(state.get_interned_string(*s), pos));
                            }
                        }
                        (Token::LexError(err), pos)
                            if matches!(*err, LexError::UnterminatedString) =>
                        {
                            return Err(err.into_err(pos))
                        }
                        (token, ..) => unreachable!(
                            "string within an interpolated string literal expected but gets {:?}",
                            token
                        ),
                    }
                }

                if segments.is_empty() {
                    Expr::StringConstant(state.get_interned_string(""), settings.pos)
                } else {
                    segments.shrink_to_fit();
                    Expr::InterpolatedString(segments.into(), settings.pos)
                }
            }

            // Array literal
            #[cfg(not(feature = "no_index"))]
            Token::LeftBracket => {
                self.parse_array_literal(input, state, lib, settings.level_up()?)?
            }

            // Map literal
            #[cfg(not(feature = "no_object"))]
            Token::MapStart => self.parse_map_literal(input, state, lib, settings.level_up()?)?,

            // Custom syntax.
            #[cfg(not(feature = "no_custom_syntax"))]
            Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key)
                if self
                    .custom_syntax
                    .as_ref()
                    .map_or(false, |m| m.contains_key(&**key)) =>
            {
                let (key, syntax) = self
                    .custom_syntax
                    .as_ref()
                    .and_then(|m| m.get_key_value(&**key))
                    .unwrap();
                let (.., pos) = input.next().expect(NEVER_ENDS);
                let settings = settings.level_up()?;
                self.parse_custom_syntax(input, state, lib, settings, key, syntax, pos)?
            }

            // Identifier
            Token::Identifier(..) => {
                let ns = Namespace::NONE;

                let s = match input.next().expect(NEVER_ENDS) {
                    (Token::Identifier(s), ..) => s,
                    token => unreachable!("Token::Identifier expected but gets {:?}", token),
                };

                match input.peek().expect(NEVER_ENDS) {
                    // Function call
                    (Token::LeftParen | Token::Bang | Token::Unit, _) => {
                        // Once the identifier consumed we must enable next variables capturing
                        state.allow_capture = true;

                        Expr::Variable(
                            (None, ns, 0, state.get_interned_string(*s)).into(),
                            None,
                            settings.pos,
                        )
                    }
                    // Namespace qualification
                    #[cfg(not(feature = "no_module"))]
                    (token @ Token::DoubleColon, pos) => {
                        if options.contains(ChainingFlags::DISALLOW_NAMESPACES) {
                            return Err(LexError::ImproperSymbol(
                                token.literal_syntax().into(),
                                String::new(),
                            )
                            .into_err(*pos));
                        }

                        // Once the identifier consumed we must enable next variables capturing
                        state.allow_capture = true;

                        let name = state.get_interned_string(*s);
                        Expr::Variable((None, ns, 0, name).into(), None, settings.pos)
                    }
                    // Normal variable access
                    _ => {
                        let (index, is_func) = state.access_var(&s, lib, settings.pos);

                        if !options.contains(ChainingFlags::PROPERTY)
                            && !is_func
                            && index.is_none()
                            && settings.has_option(LangOptions::STRICT_VAR)
                            && !state
                                .external_constants
                                .map_or(false, |scope| scope.contains(&s))
                        {
                            return Err(
                                PERR::VariableUndefined(s.to_string()).into_err(settings.pos)
                            );
                        }

                        let short_index = index
                            .and_then(|x| u8::try_from(x.get()).ok())
                            .and_then(NonZeroU8::new);
                        let name = state.get_interned_string(*s);
                        Expr::Variable((index, ns, 0, name).into(), short_index, settings.pos)
                    }
                }
            }

            // Reserved keyword or symbol
            Token::Reserved(..) => {
                let ns = Namespace::NONE;

                let s = match input.next().expect(NEVER_ENDS) {
                    (Token::Reserved(s), ..) => s,
                    token => unreachable!("Token::Reserved expected but gets {:?}", token),
                };

                match input.peek().expect(NEVER_ENDS).0 {
                    // Function call is allowed to have reserved keyword
                    Token::LeftParen | Token::Bang | Token::Unit
                        if is_reserved_keyword_or_symbol(&s).1 =>
                    {
                        Expr::Variable(
                            (None, ns, 0, state.get_interned_string(*s)).into(),
                            None,
                            settings.pos,
                        )
                    }
                    // Access to `this` as a variable
                    #[cfg(not(feature = "no_function"))]
                    _ if *s == crate::engine::KEYWORD_THIS => {
                        // OK within a function scope
                        if settings.has_flag(ParseSettingFlags::FN_SCOPE) {
                            Expr::ThisPtr(settings.pos)
                        } else {
                            // Cannot access to `this` as a variable not in a function scope
                            let msg = format!("'{s}' can only be used in functions");
                            return Err(
                                LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos)
                            );
                        }
                    }
                    _ => return Err(PERR::Reserved(s.to_string()).into_err(settings.pos)),
                }
            }

            Token::LexError(..) => match input.next().expect(NEVER_ENDS) {
                (Token::LexError(err), ..) => return Err(err.into_err(settings.pos)),
                token => unreachable!("Token::LexError expected but gets {:?}", token),
            },

            _ => return Err(LexError::UnexpectedInput(token.to_string()).into_err(settings.pos)),
        };

        if !(state.expr_filter)(&input.peek().expect(NEVER_ENDS).0) {
            return Ok(root_expr);
        }

        self.parse_postfix(
            input,
            state,
            lib,
            settings,
            root_expr,
            ChainingFlags::empty(),
        )
    }