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