fn parse_stmt()

in rhai/src/parser.rs [3342:3619]


    fn parse_stmt(
        &self,
        input: &mut TokenStream,
        state: &mut ParseState,
        lib: &mut FnLib,
        settings: ParseSettings,
    ) -> ParseResult<Stmt> {
        use AccessMode::{ReadOnly, ReadWrite};

        let mut settings = settings;

        #[cfg(not(feature = "no_function"))]
        #[cfg(feature = "metadata")]
        let comments = {
            let mut comments = StaticVec::<Identifier>::new();
            let mut comments_pos = Position::NONE;
            let mut buf = Identifier::new();

            // Handle doc-comments.
            while let (Token::Comment(ref comment), pos) = input.peek().expect(NEVER_ENDS) {
                if comments_pos.is_none() {
                    comments_pos = *pos;
                }

                if !crate::tokenizer::is_doc_comment(comment) {
                    unreachable!("doc-comment expected but gets {:?}", comment);
                }

                if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) {
                    return Err(PERR::WrongDocComment.into_err(comments_pos));
                }

                match input.next().expect(NEVER_ENDS) {
                    (Token::Comment(comment), pos) => {
                        if comment.contains('\n') {
                            // Assume block comment
                            if !buf.is_empty() {
                                comments.push(buf.clone());
                                buf.clear();
                            }
                            let c =
                                unindent_block_comment(*comment, pos.position().unwrap_or(1) - 1);
                            comments.push(c.into());
                        } else {
                            if !buf.is_empty() {
                                buf.push('\n');
                            }
                            buf.push_str(&comment);
                        }

                        match input.peek().expect(NEVER_ENDS) {
                            (Token::Fn | Token::Private, ..) => break,
                            (Token::Comment(..), ..) => (),
                            _ => return Err(PERR::WrongDocComment.into_err(comments_pos)),
                        }
                    }
                    (token, ..) => unreachable!("Token::Comment expected but gets {:?}", token),
                }
            }

            if !buf.is_empty() {
                comments.push(buf);
            }

            comments
        };

        let (token, token_pos) = match input.peek().expect(NEVER_ENDS) {
            (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)),
            (x, pos) => (x, *pos),
        };

        settings.pos = token_pos;

        match token {
            // ; - empty statement
            Token::SemiColon => {
                eat_token(input, Token::SemiColon);
                Ok(Stmt::Noop(token_pos))
            }

            // { - statements block
            Token::LeftBrace => Ok(self.parse_block(input, state, lib, settings.level_up()?)?),

            // fn ...
            #[cfg(not(feature = "no_function"))]
            Token::Fn if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) => {
                Err(PERR::WrongFnDefinition.into_err(token_pos))
            }

            #[cfg(not(feature = "no_function"))]
            Token::Fn | Token::Private => {
                let access = if matches!(token, Token::Private) {
                    eat_token(input, Token::Private);
                    crate::FnAccess::Private
                } else {
                    crate::FnAccess::Public
                };

                match input.next().expect(NEVER_ENDS) {
                    (Token::Fn, pos) => {
                        // Build new parse state
                        let new_state = &mut ParseState::new(
                            state.external_constants,
                            state.interned_strings,
                            state.tokenizer_control.clone(),
                        );

                        #[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
                        let options = self.options | (settings.options & LangOptions::STRICT_VAR);

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

                        let new_settings = ParseSettings {
                            flags,
                            level: 0,
                            options,
                            pos,
                            #[cfg(not(feature = "unchecked"))]
                            max_expr_depth: self.max_function_expr_depth(),
                        };

                        let f = self.parse_fn(
                            input,
                            new_state,
                            lib,
                            new_settings,
                            access,
                            #[cfg(feature = "metadata")]
                            comments,
                        )?;

                        let hash = calc_fn_hash(None, &f.name, f.params.len());

                        #[cfg(not(feature = "no_object"))]
                        let hash = if let Some(ref this_type) = f.this_type {
                            crate::calc_typed_method_hash(hash, this_type)
                        } else {
                            hash
                        };

                        if !lib.is_empty() && lib.contains_key(&hash) {
                            return Err(PERR::FnDuplicatedDefinition(
                                f.name.to_string(),
                                f.params.len(),
                            )
                            .into_err(pos));
                        }

                        lib.insert(hash, f.into());

                        Ok(Stmt::Noop(pos))
                    }

                    (.., pos) => Err(PERR::MissingToken(
                        Token::Fn.into(),
                        format!("following '{}'", Token::Private),
                    )
                    .into_err(pos)),
                }
            }

            Token::If => self.parse_if(input, state, lib, settings.level_up()?),
            Token::Switch => self.parse_switch(input, state, lib, settings.level_up()?),
            Token::While | Token::Loop if self.allow_looping() => {
                self.parse_while_loop(input, state, lib, settings.level_up()?)
            }
            Token::Do if self.allow_looping() => {
                self.parse_do(input, state, lib, settings.level_up()?)
            }
            Token::For if self.allow_looping() => {
                self.parse_for(input, state, lib, settings.level_up()?)
            }

            Token::Continue
                if self.allow_looping() && settings.has_flag(ParseSettingFlags::BREAKABLE) =>
            {
                let pos = eat_token(input, Token::Continue);
                Ok(Stmt::BreakLoop(None, ASTFlags::empty(), pos))
            }
            Token::Break
                if self.allow_looping() && settings.has_flag(ParseSettingFlags::BREAKABLE) =>
            {
                let pos = eat_token(input, Token::Break);

                let expr = match input.peek().expect(NEVER_ENDS) {
                    // `break` at <EOF>
                    (Token::EOF, ..) => None,
                    // `break` at end of block
                    (Token::RightBrace, ..) => None,
                    // `break;`
                    (Token::SemiColon, ..) => None,
                    // `break`  with expression
                    _ => Some(
                        self.parse_expr(input, state, lib, settings.level_up()?)?
                            .into(),
                    ),
                };

                Ok(Stmt::BreakLoop(expr, ASTFlags::BREAK, pos))
            }
            Token::Continue | Token::Break if self.allow_looping() => {
                Err(PERR::LoopBreak.into_err(token_pos))
            }

            Token::Return | Token::Throw => {
                let (return_type, token_pos) = input
                    .next()
                    .map(|(token, pos)| {
                        let flags = match token {
                            Token::Return => ASTFlags::empty(),
                            Token::Throw => ASTFlags::BREAK,
                            token => unreachable!(
                                "Token::Return or Token::Throw expected but gets {:?}",
                                token
                            ),
                        };
                        (flags, pos)
                    })
                    .expect(NEVER_ENDS);

                match input.peek().expect(NEVER_ENDS) {
                    // `return`/`throw` at <EOF>
                    (Token::EOF, ..) => Ok(Stmt::Return(None, return_type, token_pos)),
                    // `return`/`throw` at end of block
                    (Token::RightBrace, ..)
                        if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) =>
                    {
                        Ok(Stmt::Return(None, return_type, token_pos))
                    }
                    // `return;` or `throw;`
                    (Token::SemiColon, ..) => Ok(Stmt::Return(None, return_type, token_pos)),
                    // `return` or `throw` with expression
                    _ => {
                        let expr = self.parse_expr(input, state, lib, settings.level_up()?)?;
                        Ok(Stmt::Return(Some(expr.into()), return_type, token_pos))
                    }
                }
            }

            Token::Try => self.parse_try_catch(input, state, lib, settings.level_up()?),

            Token::Let => self.parse_let(input, state, lib, settings.level_up()?, ReadWrite, false),
            Token::Const => {
                self.parse_let(input, state, lib, settings.level_up()?, ReadOnly, false)
            }

            #[cfg(not(feature = "no_module"))]
            Token::Import => self.parse_import(input, state, lib, settings.level_up()?),

            #[cfg(not(feature = "no_module"))]
            Token::Export if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) => {
                Err(PERR::WrongExport.into_err(token_pos))
            }

            #[cfg(not(feature = "no_module"))]
            Token::Export => self.parse_export(input, state, lib, settings.level_up()?),

            _ => self.parse_expr_stmt(input, state, lib, settings.level_up()?),
        }
    }