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