fn parse_switch()

in rhai/src/parser.rs [1166:1380]


    fn parse_switch(
        &self,
        input: &mut TokenStream,
        state: &mut ParseState,
        lib: &mut FnLib,
        settings: ParseSettings,
    ) -> ParseResult<Stmt> {
        // switch ...
        let mut settings = settings.level_up()?;
        settings.pos = eat_token(input, Token::Switch);

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

        match input.next().expect(NEVER_ENDS) {
            (Token::LeftBrace, ..) => (),
            (Token::LexError(err), pos) => return Err(err.into_err(pos)),
            (.., pos) => {
                return Err(PERR::MissingToken(
                    Token::LeftBrace.into(),
                    "to start a switch block".into(),
                )
                .into_err(pos))
            }
        }

        let mut expressions = StaticVec::<ConditionalExpr>::new();
        let mut cases = StraightHashMap::<CaseBlocksList>::default();
        let mut ranges = StaticVec::<RangeCase>::new();
        let mut def_case = None;
        let mut def_case_pos = Position::NONE;

        loop {
            const MISSING_RBRACE: &str = "to end this switch block";

            let (case_expr_list, condition) = match input.peek().expect(NEVER_ENDS) {
                (Token::RightBrace, ..) => {
                    eat_token(input, Token::RightBrace);
                    break;
                }
                (Token::EOF, pos) => {
                    return Err(
                        PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
                            .into_err(*pos),
                    )
                }
                (Token::Underscore, pos) if def_case.is_none() => {
                    def_case_pos = *pos;
                    eat_token(input, Token::Underscore);

                    let (if_clause, if_pos) = match_token(input, Token::If);

                    if if_clause {
                        return Err(PERR::WrongSwitchCaseCondition.into_err(if_pos));
                    }

                    (
                        StaticVec::default(),
                        Expr::BoolConstant(true, Position::NONE),
                    )
                }
                _ if def_case.is_some() => {
                    return Err(PERR::WrongSwitchDefaultCase.into_err(def_case_pos))
                }

                _ => {
                    let mut case_expr_list = StaticVec::new();

                    loop {
                        let filter = state.expr_filter;
                        state.expr_filter = |t| t != &Token::Pipe;
                        let expr = self.parse_expr(input, state, lib, settings);
                        state.expr_filter = filter;

                        match expr {
                            Ok(expr) => case_expr_list.push(expr),
                            Err(err) => {
                                return Err(PERR::ExprExpected("literal".into()).into_err(err.1))
                            }
                        }

                        if !match_token(input, Token::Pipe).0 {
                            break;
                        }
                    }

                    let condition = if match_token(input, Token::If).0 {
                        ensure_not_statement_expr(input, "a boolean")?;
                        let guard = self
                            .parse_expr(input, state, lib, settings)?
                            .ensure_bool_expr()?;
                        ensure_not_assignment(input)?;
                        guard
                    } else {
                        Expr::BoolConstant(true, Position::NONE)
                    };
                    (case_expr_list, condition)
                }
            };

            match input.next().expect(NEVER_ENDS) {
                (Token::DoubleArrow, ..) => (),
                (Token::LexError(err), pos) => return Err(err.into_err(pos)),
                (.., pos) => {
                    return Err(PERR::MissingToken(
                        Token::DoubleArrow.into(),
                        "in this switch case".into(),
                    )
                    .into_err(pos))
                }
            };

            let (action_expr, need_comma) =
                if settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) {
                    (self.parse_expr(input, state, lib, settings)?, true)
                } else {
                    let stmt = self.parse_stmt(input, state, lib, settings)?;
                    let need_comma = !stmt.is_self_terminated();

                    let stmt_block: StmtBlock = stmt.into();
                    (Expr::Stmt(stmt_block.into()), need_comma)
                };

            expressions.push((condition, action_expr).into());
            let index = expressions.len() - 1;

            if case_expr_list.is_empty() {
                def_case = Some(index);
            } else {
                for expr in case_expr_list {
                    let value = expr.get_literal_value().ok_or_else(|| {
                        PERR::ExprExpected("a literal".into()).into_err(expr.start_position())
                    })?;

                    let mut range_value: Option<RangeCase> = None;

                    let guard = value.read_lock::<ExclusiveRange>();
                    if let Some(range) = guard {
                        range_value = Some(range.clone().into());
                    } else if let Some(range) = value.read_lock::<InclusiveRange>() {
                        range_value = Some(range.clone().into());
                    }

                    if let Some(mut r) = range_value {
                        if !r.is_empty() {
                            // Other range
                            r.set_index(index);
                            ranges.push(r);
                        }
                        continue;
                    }

                    if !ranges.is_empty() {
                        let forbidden = match value {
                            Dynamic(Union::Int(..)) => true,
                            #[cfg(not(feature = "no_float"))]
                            Dynamic(Union::Float(..)) => true,
                            #[cfg(feature = "decimal")]
                            Dynamic(Union::Decimal(..)) => true,
                            _ => false,
                        };

                        if forbidden {
                            return Err(
                                PERR::WrongSwitchIntegerCase.into_err(expr.start_position())
                            );
                        }
                    }

                    let hasher = &mut get_hasher();
                    value.hash(hasher);
                    let hash = hasher.finish();

                    cases
                        .entry(hash)
                        .and_modify(|cases| cases.push(index))
                        .or_insert_with(|| [index].into());
                }
            }

            match input.peek().expect(NEVER_ENDS) {
                (Token::Comma, ..) => {
                    eat_token(input, Token::Comma);
                }
                (Token::RightBrace, ..) => (),
                (Token::EOF, pos) => {
                    return Err(
                        PERR::MissingToken(Token::RightParen.into(), MISSING_RBRACE.into())
                            .into_err(*pos),
                    )
                }
                (Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)),
                (.., pos) if need_comma => {
                    return Err(PERR::MissingToken(
                        Token::Comma.into(),
                        "to separate the items in this switch block".into(),
                    )
                    .into_err(*pos))
                }
                _ => (),
            }
        }

        expressions.shrink_to_fit();
        cases.shrink_to_fit();
        ranges.shrink_to_fit();

        let cases = SwitchCasesCollection {
            expressions,
            cases,
            ranges,
            def_case,
        };

        Ok(Stmt::Switch((item, cases).into(), settings.pos))
    }