in rhai/src/parser.rs [1794:1993]
fn parse_postfix(
&self,
input: &mut TokenStream,
state: &mut ParseState,
lib: &mut FnLib,
settings: ParseSettings,
mut lhs: Expr,
_options: ChainingFlags,
) -> ParseResult<Expr> {
let mut settings = settings;
// Break just in case `lhs` is `Expr::Dot` or `Expr::Index`
let mut parent_options = ASTFlags::BREAK;
// Tail processing all possible postfix operators
loop {
let (tail_token, ..) = input.peek().expect(NEVER_ENDS);
if !lhs.is_valid_postfix(tail_token) {
break;
}
let (tail_token, tail_pos) = input.next().expect(NEVER_ENDS);
settings.pos = tail_pos;
lhs = match (lhs, tail_token) {
// Qualified function call with !
#[cfg(not(feature = "no_module"))]
(Expr::Variable(x, ..), Token::Bang) if !x.1.is_empty() => {
return match input.peek().expect(NEVER_ENDS) {
(Token::LeftParen | Token::Unit, ..) => {
Err(LexError::UnexpectedInput(Token::Bang.into()).into_err(tail_pos))
}
_ => Err(LexError::ImproperSymbol(
"!".into(),
"'!' cannot be used to call module functions".into(),
)
.into_err(tail_pos)),
};
}
// Function call with !
(Expr::Variable(x, .., pos), Token::Bang) => {
match input.peek().expect(NEVER_ENDS) {
(Token::LeftParen | Token::Unit, ..) => (),
(_, pos) => {
return Err(PERR::MissingToken(
Token::LeftParen.into(),
"to start arguments list of function call".into(),
)
.into_err(*pos))
}
}
let no_args = input.next().expect(NEVER_ENDS).0 == Token::Unit;
let (.., ns, _, name) = *x;
settings.pos = pos;
self.parse_fn_call(input, state, lib, settings, name, no_args, true, ns)?
}
// Function call
(Expr::Variable(x, .., pos), t @ (Token::LeftParen | Token::Unit)) => {
let (.., ns, _, name) = *x;
let no_args = t == Token::Unit;
settings.pos = pos;
self.parse_fn_call(input, state, lib, settings, name, no_args, false, ns)?
}
// Disallowed module separator
#[cfg(not(feature = "no_module"))]
(_, token @ Token::DoubleColon)
if _options.contains(ChainingFlags::DISALLOW_NAMESPACES) =>
{
return Err(LexError::ImproperSymbol(
token.literal_syntax().into(),
String::new(),
)
.into_err(tail_pos))
}
// module access
#[cfg(not(feature = "no_module"))]
(Expr::Variable(x, .., pos), Token::DoubleColon) => {
let (id2, pos2) = parse_var_name(input)?;
let (.., mut namespace, _, name) = *x;
let var_name_def = Ident { name, pos };
namespace.push(var_name_def);
let var_name = state.get_interned_string(id2);
Expr::Variable((None, namespace, 0, var_name).into(), None, pos2)
}
// Indexing
#[cfg(not(feature = "no_index"))]
(expr, token @ (Token::LeftBracket | Token::QuestionBracket)) => {
let opt = match token {
Token::LeftBracket => ASTFlags::empty(),
Token::QuestionBracket => ASTFlags::NEGATED,
_ => unreachable!("`[` or `?[`"),
};
let settings = settings.level_up()?;
self.parse_index_chain(input, state, lib, settings, expr, opt, true)?
}
// Property access
#[cfg(not(feature = "no_object"))]
(expr, op @ (Token::Period | Token::Elvis)) => {
// Expression after dot must start with an identifier
match input.peek().expect(NEVER_ENDS) {
(Token::Identifier(..), ..) => {
// Prevents capturing of the object properties as vars: xxx.<var>
state.allow_capture = false;
}
(Token::Reserved(s), ..) if is_reserved_keyword_or_symbol(s).2 => (),
(Token::Reserved(s), pos) => {
return Err(PERR::Reserved(s.to_string()).into_err(*pos))
}
(.., pos) => return Err(PERR::PropertyExpected.into_err(*pos)),
}
let op_flags = match op {
Token::Period => ASTFlags::empty(),
Token::Elvis => ASTFlags::NEGATED,
_ => unreachable!("`.` or `?.`"),
};
let options = ChainingFlags::PROPERTY | ChainingFlags::DISALLOW_NAMESPACES;
let rhs =
self.parse_primary(input, state, lib, settings.level_up()?, options)?;
Self::make_dot_expr(state, expr, rhs, parent_options, op_flags, tail_pos)?
}
// Unknown postfix operator
(expr, token) => {
unreachable!("unknown postfix operator '{}' for {:?}", token, expr)
}
};
// The chain is now extended
parent_options = ASTFlags::empty();
}
// Optimize chain where the root expression is another chain
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
if matches!(lhs, Expr::Index(ref x, ..) | Expr::Dot(ref x, ..) if matches!(x.lhs, Expr::Index(..) | Expr::Dot(..)))
{
optimize_combo_chain(&mut lhs)
}
// Cache the hash key for namespace-qualified variables
#[cfg(not(feature = "no_module"))]
let namespaced_variable = match lhs {
Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x),
Expr::Index(ref mut x, ..) | Expr::Dot(ref mut x, ..) => match x.lhs {
Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x),
_ => None,
},
_ => None,
};
#[cfg(not(feature = "no_module"))]
if let Some((.., namespace, hash, name)) = namespaced_variable {
if !namespace.is_empty() {
*hash = crate::calc_var_hash(namespace.iter().map(Ident::as_str), name);
#[cfg(not(feature = "no_module"))]
{
let root = namespace.root();
let index = state.find_module(root);
let is_global = false;
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))]
let is_global = is_global || root == crate::engine::KEYWORD_GLOBAL;
if settings.has_option(LangOptions::STRICT_VAR)
&& index.is_none()
&& !is_global
&& !state
.global_imports
.as_deref()
.into_iter()
.flatten()
.any(|m| m.as_str() == root)
&& !self
.global_sub_modules
.as_ref()
.map_or(false, |m| m.contains_key(root))
{
return Err(
PERR::ModuleUndefined(root.into()).into_err(namespace.position())
);
}
namespace.set_index(index);
}
}
}
// Make sure identifiers are valid
Ok(lhs)
}