use crate::api::options::LangOptions;
use crate::ast::{
ASTFlags, BinaryExpr, CaseBlocksList, Expr, FlowControl, FnCallExpr, FnCallHashes, Ident,
OpAssignment, RangeCase, ScriptFuncDef, Stmt, StmtBlock, StmtBlockContainer,
SwitchCasesCollection,
};
use crate::engine::{Precedence, OP_CONTAINS, OP_NOT};
use crate::eval::{Caches, GlobalRuntimeState};
use crate::func::{hashing::get_hasher, StraightHashMap};
use crate::tokenizer::{
is_reserved_keyword_or_symbol, is_valid_function_name, is_valid_identifier, Token, TokenStream,
TokenizerControl,
};
use crate::types::dynamic::{AccessMode, Union};
use crate::{
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec,
ImmutableString, InclusiveRange, LexError, ParseError, Position, Scope, Shared, SmartString,
StaticVec, ThinVec, VarDefInfo, AST, PERR,
};
use bitflags::bitflags;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
convert::TryFrom,
fmt,
hash::{Hash, Hasher},
num::{NonZeroU8, NonZeroUsize},
};
pub type ParseResult<T> = Result<T, ParseError>;
#[cfg(not(feature = "no_function"))]
type FnLib = StraightHashMap<Shared<ScriptFuncDef>>;
const SCOPE_SEARCH_BARRIER_MARKER: &str = "$ BARRIER $";
impl PERR {
#[cold]
#[inline(never)]
fn into_err(self, pos: Position) -> ParseError {
ParseError(self.into(), pos)
}
}
pub struct ParseState<'a, 't, 'f> {
pub input: &'t mut TokenStream<'a>,
pub tokenizer_control: TokenizerControl,
#[cfg(not(feature = "no_function"))]
pub lib: &'f mut FnLib,
pub expr_filter: fn(&Token) -> bool,
pub external_constants: Option<&'a Scope<'a>>,
pub global: Option<Box<GlobalRuntimeState>>,
pub stack: Scope<'a>,
pub frame_pointer: usize,
#[cfg(not(feature = "no_closure"))]
pub external_vars: ThinVec<Ident>,
pub allow_capture: bool,
#[cfg(not(feature = "no_module"))]
pub imports: ThinVec<ImmutableString>,
#[cfg(not(feature = "no_module"))]
pub global_imports: ThinVec<ImmutableString>,
#[cfg(feature = "no_function")]
pub dummy: &'f (),
}
impl fmt::Debug for ParseState<'_, '_, '_> {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("ParseState");
f.field("tokenizer_control", &self.tokenizer_control)
.field("external_constants_scope", &self.external_constants)
.field("global", &self.global)
.field("stack", &self.stack)
.field("frame_pointer", &self.frame_pointer);
#[cfg(not(feature = "no_closure"))]
f.field("external_vars", &self.external_vars)
.field("allow_capture", &self.allow_capture);
#[cfg(not(feature = "no_module"))]
f.field("imports", &self.imports)
.field("global_imports", &self.global_imports);
f.finish()
}
}
impl<'a, 't, 'f> ParseState<'a, 't, 'f> {
#[inline]
#[must_use]
pub fn new(
external_constants: Option<&'a Scope>,
input: &'t mut TokenStream<'a>,
tokenizer_control: TokenizerControl,
#[cfg(not(feature = "no_function"))] lib: &'f mut FnLib,
#[cfg(feature = "no_function")] dummy: &'f (),
) -> Self {
Self {
input,
tokenizer_control,
#[cfg(not(feature = "no_function"))]
lib,
#[cfg(feature = "no_function")]
dummy,
expr_filter: |_| true,
#[cfg(not(feature = "no_closure"))]
external_vars: ThinVec::new(),
allow_capture: true,
external_constants,
global: None,
stack: Scope::new(),
frame_pointer: 0,
#[cfg(not(feature = "no_module"))]
imports: ThinVec::new(),
#[cfg(not(feature = "no_module"))]
global_imports: ThinVec::new(),
}
}
#[must_use]
pub fn find_var(&self, name: &str) -> (usize, bool) {
let mut hit_barrier = false;
let index = self
.stack
.iter_rev_inner()
.position(|(n, ..)| {
if n == SCOPE_SEARCH_BARRIER_MARKER {
hit_barrier = true;
false
} else {
n == name
}
})
.map_or(0, |i| i + 1);
(index, hit_barrier)
}
#[cfg(not(feature = "no_module"))]
#[must_use]
pub fn find_module(&self, name: &str) -> Option<NonZeroUsize> {
self.imports
.iter()
.rev()
.rposition(|n| n == name)
.and_then(|i| NonZeroUsize::new(i + 1))
}
}
bitflags! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct ParseSettingFlags: u8 {
const GLOBAL_LEVEL = 0b0000_0001;
const FN_SCOPE = 0b0000_0010;
const CLOSURE_SCOPE = 0b0000_0100;
const BREAKABLE = 0b0000_1000;
const DISALLOW_STATEMENTS_IN_BLOCKS = 0b0001_0000;
const DISALLOW_UNQUOTED_MAP_PROPERTIES = 0b0010_0000;
}
}
bitflags! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
struct ChainingFlags: u8 {
const PROPERTY = 0b0000_0001;
const DISALLOW_NAMESPACES = 0b0000_0010;
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ParseSettings {
pub flags: ParseSettingFlags,
pub options: LangOptions,
pub level: usize,
pub pos: Position,
#[cfg(not(feature = "unchecked"))]
pub max_expr_depth: usize,
}
impl ParseSettings {
#[inline(always)]
#[must_use]
pub const fn has_flag(&self, flag: ParseSettingFlags) -> bool {
self.flags.intersects(flag)
}
#[inline(always)]
#[must_use]
pub const fn has_option(&self, option: LangOptions) -> bool {
self.options.intersects(option)
}
#[inline]
pub fn level_up(&self) -> ParseResult<Self> {
#[cfg(not(feature = "unchecked"))]
if self.max_expr_depth > 0 && self.level >= self.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(self.pos));
}
Ok(Self {
level: self.level + 1,
..*self
})
}
#[inline]
pub fn level_up_with_position(&self, pos: Position) -> ParseResult<Self> {
let mut x = self.level_up()?;
x.pos = pos;
Ok(x)
}
}
#[cfg(not(feature = "no_function"))]
#[inline]
#[must_use]
pub fn make_anonymous_fn(hash: u64) -> crate::Identifier {
use std::fmt::Write;
let mut buf = crate::Identifier::new_const();
write!(&mut buf, "{}{hash:016x}", crate::engine::FN_ANONYMOUS).unwrap();
buf
}
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn is_anonymous_fn(fn_name: &str) -> bool {
fn_name.starts_with(crate::engine::FN_ANONYMOUS)
}
impl Expr {
fn ensure_bool_expr(self) -> ParseResult<Self> {
let type_name = match self {
Self::Unit(..) => "()",
Self::DynamicConstant(ref v, ..) if !v.is_bool() => v.type_name(),
Self::IntegerConstant(..) => "a number",
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(..) => "a floating-point number",
Self::CharConstant(..) => "a character",
Self::StringConstant(..) => "a string",
Self::InterpolatedString(..) => "a string",
Self::Array(..) => "an array",
Self::Map(..) => "an object map",
_ => return Ok(self),
};
Err(
PERR::MismatchedType("a boolean expression".into(), type_name.into())
.into_err(self.start_position()),
)
}
fn ensure_iterable(self) -> ParseResult<Self> {
let type_name = match self {
Self::Unit(..) => "()",
Self::BoolConstant(..) => "a boolean",
Self::IntegerConstant(..) => "a number",
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(..) => "a floating-point number",
Self::CharConstant(..) => "a character",
Self::Map(..) => "an object map",
_ => return Ok(self),
};
Err(
PERR::MismatchedType("an iterable value".into(), type_name.into())
.into_err(self.start_position()),
)
}
}
fn ensure_not_statement_expr(
input: &mut TokenStream,
type_name: &(impl ToString + ?Sized),
) -> ParseResult<()> {
match input.peek().unwrap() {
(Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)),
_ => Ok(()),
}
}
fn ensure_not_assignment(input: &mut TokenStream) -> ParseResult<()> {
match input.peek().unwrap() {
(token @ Token::Equals, pos) => Err(LexError::ImproperSymbol(
token.literal_syntax().into(),
"Possibly a typo of '=='?".into(),
)
.into_err(*pos)),
_ => Ok(()),
}
}
#[inline(always)]
fn eat_token(input: &mut TokenStream, expected_token: &Token) -> Position {
let (t, pos) = input.next().unwrap();
debug_assert_eq!(
&t,
expected_token,
"{} expected but gets {} at {}",
expected_token.literal_syntax(),
t.literal_syntax(),
pos,
);
pos
}
#[inline]
fn match_token(input: &mut TokenStream, token: &Token) -> (bool, Position) {
let (t, pos) = input.peek().unwrap();
if t == token {
(true, eat_token(input, token))
} else {
(false, *pos)
}
}
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
#[inline]
fn unindent_block_comment(comment: String, pos: usize) -> String {
if pos == 0 || !comment.contains('\n') {
return comment;
}
let offset = comment
.lines()
.skip(1)
.map(|s| s.len() - s.trim_start_matches(' ').len())
.min()
.unwrap_or(pos)
.min(pos);
if offset == 0 {
return comment;
}
comment
.lines()
.enumerate()
.map(|(i, s)| if i > 0 { &s[offset..] } else { s })
.collect::<Vec<_>>()
.join("\n")
}
fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> {
match input.next().unwrap() {
(Token::Identifier(s), pos) => Ok((*s, pos)),
(Token::Reserved(s), pos) if is_valid_identifier(&s) => {
Err(PERR::Reserved(s.to_string()).into_err(pos))
}
(Token::LexError(err), pos) => Err(err.into_err(pos)),
(.., pos) => Err(PERR::VariableExpected.into_err(pos)),
}
}
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn optimize_combo_chain(expr: &mut Expr) {
#[allow(clippy::type_complexity)]
let (mut x, x_options, x_pos, mut root, mut root_options, root_pos, make_sub, make_root): (
_,
_,
_,
_,
_,
_,
fn(_, _, _) -> Expr,
fn(_, _, _) -> Expr,
) = match expr.take() {
#[cfg(not(feature = "no_index"))]
Expr::Index(mut x, opt, pos) => match x.lhs.take() {
Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Index),
#[cfg(not(feature = "no_object"))]
Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Dot),
_ => unreachable!("combo chain expected"),
},
#[cfg(not(feature = "no_object"))]
Expr::Dot(mut x, opt, pos) => match x.lhs.take() {
#[cfg(not(feature = "no_index"))]
Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Index),
Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Dot),
_ => unreachable!("combo chain expected"),
},
_ => unreachable!("combo chain expected"),
};
let mut tail = root.as_mut();
let mut tail_options = &mut root_options;
while !tail_options.intersects(ASTFlags::BREAK) {
match tail.rhs {
Expr::Index(ref mut x, ref mut options2, ..) => {
tail = x.as_mut();
tail_options = options2;
}
#[cfg(not(feature = "no_object"))]
Expr::Dot(ref mut x, ref mut options2, ..) => {
tail = x.as_mut();
tail_options = options2;
}
_ => break,
}
}
tail_options.remove(ASTFlags::BREAK);
x.lhs = tail.rhs.take(); tail.rhs = make_sub(x, x_options, x_pos); *expr = make_root(root, root_options, root_pos);
}
impl Engine {
#[must_use]
fn access_var(
&self,
state: &mut ParseState,
name: &str,
_pos: Position,
) -> (Option<NonZeroUsize>, bool) {
let (index, hit_barrier) = state.find_var(name);
#[cfg(not(feature = "no_function"))]
let is_func_name = state.lib.values().any(|f| f.name == name);
#[cfg(feature = "no_function")]
let is_func_name = false;
#[cfg(not(feature = "no_closure"))]
if state.allow_capture {
if !is_func_name && index == 0 && !state.external_vars.iter().any(|v| v.name == name) {
let name = self.get_interned_string(name);
state.external_vars.push(Ident { name, pos: _pos });
}
} else {
state.allow_capture = true;
}
let index = (!hit_barrier).then(|| NonZeroUsize::new(index)).flatten();
(index, is_func_name)
}
#[cfg(not(feature = "no_object"))]
#[inline]
#[must_use]
fn convert_expr_into_property(&self, expr: Expr) -> Expr {
match expr {
#[cfg(not(feature = "no_module"))]
Expr::Variable(x, ..) if !x.2.is_empty() => unreachable!("qualified property"),
Expr::Variable(x, .., pos) => {
let ident = x.1.clone();
let getter = self.get_interned_getter(&ident);
let hash_get = calc_fn_hash(None, &getter, 1);
let setter = self.get_interned_setter(&ident);
let hash_set = calc_fn_hash(None, &setter, 2);
Expr::Property(
Box::new(((getter, hash_get), (setter, hash_set), ident)),
pos,
)
}
_ => expr,
}
}
fn parse_fn_call(
&self,
state: &mut ParseState,
settings: ParseSettings,
id: ImmutableString,
no_args: bool,
capture_parent_scope: bool,
#[cfg(not(feature = "no_module"))] mut namespace: crate::ast::Namespace,
) -> ParseResult<Expr> {
let (token, token_pos) = if no_args {
&(Token::RightParen, Position::NONE)
} else {
state.input.peek().unwrap()
};
let mut args = FnArgsVec::new();
match token {
Token::EOF => {
return Err(PERR::MissingToken(
Token::RightParen.into(),
format!("to close the arguments list of this function call '{id}'"),
)
.into_err(*token_pos))
}
Token::LexError(err) => return Err(err.clone().into_err(*token_pos)),
Token::RightParen => {
if !no_args {
eat_token(state.input, &Token::RightParen);
}
#[cfg(not(feature = "no_module"))]
let hash = if namespace.is_empty() {
calc_fn_hash(None, &id, 0)
} else {
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.iter().any(|m| m == root)
&& !self.global_sub_modules.contains_key(root)
{
return Err(
PERR::ModuleUndefined(root.into()).into_err(namespace.position())
);
}
namespace.index = index;
calc_fn_hash(namespace.path.iter().map(Ident::as_str), &id, 0)
};
#[cfg(feature = "no_module")]
let hash = calc_fn_hash(None, &id, 0);
let hashes = if is_valid_function_name(&id) {
FnCallHashes::from_hash(hash)
} else {
FnCallHashes::from_native_only(hash)
};
args.shrink_to_fit();
return Ok(FnCallExpr {
name: self.get_interned_string(id),
capture_parent_scope,
op_token: None,
#[cfg(not(feature = "no_module"))]
namespace,
hashes,
args,
}
.into_fn_call_expr(settings.pos));
}
_ => (),
}
let settings = settings.level_up()?;
loop {
match state.input.peek().unwrap() {
(Token::RightParen, ..) => (),
_ => args.push(self.parse_expr(state, settings)?),
}
match state.input.peek().unwrap() {
(Token::RightParen, ..) => {
eat_token(state.input, &Token::RightParen);
#[cfg(not(feature = "no_module"))]
let hash = if namespace.is_empty() {
calc_fn_hash(None, &id, args.len())
} else {
let root = namespace.root();
let index = state.find_module(root);
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))]
let is_global = root == crate::engine::KEYWORD_GLOBAL;
#[cfg(any(feature = "no_function", feature = "no_module"))]
let is_global = false;
if settings.has_option(LangOptions::STRICT_VAR)
&& index.is_none()
&& !is_global
&& !state.global_imports.iter().any(|m| m == root)
&& !self.global_sub_modules.contains_key(root)
{
return Err(
PERR::ModuleUndefined(root.into()).into_err(namespace.position())
);
}
namespace.index = index;
calc_fn_hash(namespace.path.iter().map(Ident::as_str), &id, args.len())
};
#[cfg(feature = "no_module")]
let hash = calc_fn_hash(None, &id, args.len());
let hashes = if is_valid_function_name(&id) {
FnCallHashes::from_hash(hash)
} else {
FnCallHashes::from_native_only(hash)
};
args.shrink_to_fit();
return Ok(FnCallExpr {
name: self.get_interned_string(id),
capture_parent_scope,
op_token: None,
#[cfg(not(feature = "no_module"))]
namespace,
hashes,
args,
}
.into_fn_call_expr(settings.pos));
}
(Token::Comma, ..) => {
eat_token(state.input, &Token::Comma);
}
(Token::EOF, pos) => {
return Err(PERR::MissingToken(
Token::RightParen.into(),
format!("to close the arguments list of this function call '{id}'"),
)
.into_err(*pos))
}
(Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::Comma.into(),
format!("to separate the arguments to function call '{id}'"),
)
.into_err(*pos))
}
}
}
}
#[cfg(not(feature = "no_index"))]
fn parse_index_chain(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
lhs: Expr,
options: ASTFlags,
check_types: bool,
) -> ParseResult<Expr> {
fn check_argument_types(lhs: &Expr, idx_expr: &Expr) -> Result<(), ParseError> {
match *lhs {
Expr::Map(..) => match *idx_expr {
Expr::IntegerConstant(..) => Err(PERR::MalformedIndexExpr(
"Object map expects string index, not a number".into(),
)
.into_err(idx_expr.start_position())),
Expr::StringConstant(..) | Expr::InterpolatedString(..) => Ok(()),
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(..) => Err(PERR::MalformedIndexExpr(
"Object map expects string index, not a float".into(),
)
.into_err(idx_expr.start_position())),
Expr::CharConstant(..) => Err(PERR::MalformedIndexExpr(
"Object map expects string index, not a character".into(),
)
.into_err(idx_expr.start_position())),
Expr::Unit(..) => Err(PERR::MalformedIndexExpr(
"Object map expects string index, not ()".into(),
)
.into_err(idx_expr.start_position())),
Expr::And(..) | Expr::Or(..) | Expr::BoolConstant(..) => {
Err(PERR::MalformedIndexExpr(
"Object map expects string index, not a boolean".into(),
)
.into_err(idx_expr.start_position()))
}
_ => Ok(()),
},
Expr::IntegerConstant(..)
| Expr::Array(..)
| Expr::StringConstant(..)
| Expr::InterpolatedString(..) => match *idx_expr {
Expr::IntegerConstant(..) => Ok(()),
Expr::StringConstant(..) | Expr::InterpolatedString(..) => {
Err(PERR::MalformedIndexExpr(
"Array, string or bit-field expects numeric index, not a string".into(),
)
.into_err(idx_expr.start_position()))
}
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(..) => Err(PERR::MalformedIndexExpr(
"Array, string or bit-field expects integer index, not a float".into(),
)
.into_err(idx_expr.start_position())),
Expr::CharConstant(..) => Err(PERR::MalformedIndexExpr(
"Array, string or bit-field expects integer index, not a character".into(),
)
.into_err(idx_expr.start_position())),
Expr::Unit(..) => Err(PERR::MalformedIndexExpr(
"Array, string or bit-field expects integer index, not ()".into(),
)
.into_err(idx_expr.start_position())),
Expr::And(..) | Expr::Or(..) | Expr::BoolConstant(..) => {
Err(PERR::MalformedIndexExpr(
"Array, string or bit-field expects integer index, not a boolean"
.into(),
)
.into_err(idx_expr.start_position()))
}
_ => Ok(()),
},
_ => Ok(()),
}
}
let idx_expr = self.parse_expr(state, settings.level_up()?)?;
if check_types {
check_argument_types(&lhs, &idx_expr)?;
}
match state.input.peek().unwrap() {
(Token::RightBracket, ..) => {
eat_token(state.input, &Token::RightBracket);
match state.input.peek().unwrap() {
(Token::LeftBracket | Token::QuestionBracket, ..) => {
let (token, pos) = state.input.next().unwrap();
let prev_pos = settings.pos;
settings.pos = pos;
let settings = settings.level_up()?;
let options = match token {
Token::LeftBracket => ASTFlags::empty(),
Token::QuestionBracket => ASTFlags::NEGATED,
_ => unreachable!("`[` or `?[`"),
};
let idx_expr =
self.parse_index_chain(state, settings, idx_expr, options, false)?;
Ok(Expr::Index(
BinaryExpr { lhs, rhs: idx_expr }.into(),
options,
prev_pos,
))
}
_ => Ok(Expr::Index(
BinaryExpr { lhs, rhs: idx_expr }.into(),
options | ASTFlags::BREAK,
settings.pos,
)),
}
}
(Token::LexError(err), pos) => Err(err.clone().into_err(*pos)),
(.., pos) => Err(PERR::MissingToken(
Token::RightBracket.into(),
"for a matching [ in this index expression".into(),
)
.into_err(*pos)),
}
}
#[cfg(not(feature = "no_index"))]
fn parse_array_literal(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
) -> ParseResult<Expr> {
settings.pos = eat_token(state.input, &Token::LeftBracket);
let mut array = ThinVec::new();
loop {
const MISSING_RBRACKET: &str = "to end this array literal";
#[cfg(not(feature = "unchecked"))]
if self.max_array_size() > 0 && array.len() >= self.max_array_size() {
return Err(PERR::LiteralTooLarge(
"Size of array literal".into(),
self.max_array_size(),
)
.into_err(state.input.peek().unwrap().1));
}
match state.input.peek().unwrap() {
(Token::RightBracket, ..) => {
eat_token(state.input, &Token::RightBracket);
break;
}
(Token::EOF, pos) => {
return Err(PERR::MissingToken(
Token::RightBracket.into(),
MISSING_RBRACKET.into(),
)
.into_err(*pos))
}
_ => array.push(self.parse_expr(state, settings.level_up()?)?),
}
match state.input.peek().unwrap() {
(Token::Comma, ..) => {
eat_token(state.input, &Token::Comma);
}
(Token::RightBracket, ..) => (),
(Token::EOF, pos) => {
return Err(PERR::MissingToken(
Token::RightBracket.into(),
MISSING_RBRACKET.into(),
)
.into_err(*pos))
}
(Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::Comma.into(),
"to separate the items of this array literal".into(),
)
.into_err(*pos))
}
};
}
array.shrink_to_fit();
Ok(Expr::Array(array, settings.pos))
}
#[cfg(not(feature = "no_object"))]
fn parse_map_literal(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
) -> ParseResult<Expr> {
settings.pos = eat_token(state.input, &Token::MapStart);
let mut map = StaticVec::<(Ident, Expr)>::new();
let mut template = std::collections::BTreeMap::<crate::Identifier, crate::Dynamic>::new();
loop {
const MISSING_RBRACE: &str = "to end this object map literal";
match state.input.peek().unwrap() {
(Token::RightBrace, ..) => {
eat_token(state.input, &Token::RightBrace);
break;
}
(Token::EOF, pos) => {
return Err(
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
.into_err(*pos),
)
}
_ => (),
}
let (name, pos) = match state.input.next().unwrap() {
(Token::Identifier(..), pos)
if settings.has_flag(ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES) =>
{
return Err(PERR::PropertyExpected.into_err(pos))
}
(Token::Identifier(s) | Token::StringConstant(s), pos) => {
if map.iter().any(|(p, ..)| p.as_str() == s.as_str()) {
return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos));
}
(*s, pos)
}
(Token::InterpolatedString(..), pos) => {
return Err(PERR::PropertyExpected.into_err(pos))
}
(Token::Reserved(s), pos) if is_valid_identifier(&s) => {
return Err(PERR::Reserved(s.to_string()).into_err(pos));
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(Token::EOF, pos) => {
return Err(PERR::MissingToken(
Token::RightBrace.into(),
MISSING_RBRACE.into(),
)
.into_err(pos));
}
(.., pos) if map.is_empty() => {
return Err(PERR::MissingToken(
Token::RightBrace.into(),
MISSING_RBRACE.into(),
)
.into_err(pos));
}
(.., pos) => return Err(PERR::PropertyExpected.into_err(pos)),
};
match state.input.next().unwrap() {
(Token::Colon, ..) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::Colon.into(),
format!("to follow the property '{name}' in this object map literal"),
)
.into_err(pos))
}
};
#[cfg(not(feature = "unchecked"))]
if self.max_map_size() > 0 && map.len() >= self.max_map_size() {
return Err(PERR::LiteralTooLarge(
"Number of properties in object map literal".into(),
self.max_map_size(),
)
.into_err(state.input.peek().unwrap().1));
}
let expr = self.parse_expr(state, settings.level_up()?)?;
template.insert(name.clone(), crate::Dynamic::UNIT);
let name = self.get_interned_string(name);
map.push((Ident { name, pos }, expr));
match state.input.peek().unwrap() {
(Token::Comma, ..) => {
eat_token(state.input, &Token::Comma);
}
(Token::RightBrace, ..) => (),
(Token::Identifier(..), pos) => {
return Err(PERR::MissingToken(
Token::Comma.into(),
"to separate the items of this object map literal".into(),
)
.into_err(*pos))
}
(Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)),
(.., pos) => {
return Err(
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
.into_err(*pos),
)
}
}
}
map.shrink_to_fit();
Ok(Expr::Map((map, template).into(), settings.pos))
}
fn parse_switch(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult<Stmt> {
let settings = settings.level_up_with_position(eat_token(state.input, &Token::Switch))?;
let item = self.parse_expr(state, settings)?;
match state.input.next().unwrap() {
(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 = FnArgsVec::<BinaryExpr>::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 state.input.peek().unwrap() {
(Token::RightBrace, ..) => {
eat_token(state.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(state.input, &Token::Underscore);
let (if_clause, if_pos) = match_token(state.input, &Token::If);
if if_clause {
return Err(PERR::WrongSwitchCaseCondition.into_err(if_pos));
}
(
StaticVec::new_const(),
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_const();
loop {
let filter = state.expr_filter;
state.expr_filter = |t| t != &Token::Pipe;
let expr = self.parse_expr(state, 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(state.input, &Token::Pipe).0 {
break;
}
}
let condition = if match_token(state.input, &Token::If).0 {
ensure_not_statement_expr(state.input, "a boolean")?;
let guard = self.parse_expr(state, settings)?.ensure_bool_expr()?;
ensure_not_assignment(state.input)?;
guard
} else {
Expr::BoolConstant(true, Position::NONE)
};
(case_expr_list, condition)
}
};
match state.input.next().unwrap() {
(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(state, settings)?, true)
} else {
let stmt = self.parse_stmt(state, settings)?;
let need_comma = !stmt.is_self_terminated();
let stmt_block: StmtBlock = stmt.into();
(Expr::Stmt(stmt_block.into()), need_comma)
};
expressions.push(BinaryExpr {
lhs: condition,
rhs: action_expr,
});
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;
if let Some(range) = value.read_lock::<ExclusiveRange>() {
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() {
r.set_index(index);
ranges.push(r);
}
} else 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)
.or_insert(CaseBlocksList::new_const())
.push(index);
}
}
match state.input.peek().unwrap() {
(Token::Comma, ..) => {
eat_token(state.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))
}
fn parse_primary(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
options: ChainingFlags,
) -> ParseResult<Expr> {
let (next_token, next_token_pos) = state.input.peek().unwrap();
settings.pos = *next_token_pos;
let root_expr = match next_token {
_ if !(state.expr_filter)(next_token) => {
return Err(LexError::UnexpectedInput(next_token.to_string()).into_err(settings.pos))
}
Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),
Token::Unit => {
state.input.next();
Expr::Unit(settings.pos)
}
Token::IntegerConstant(..)
| Token::CharConstant(..)
| Token::StringConstant(..)
| Token::True
| Token::False => match state.input.next().unwrap().0 {
Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos),
Token::CharConstant(c) => Expr::CharConstant(c, settings.pos),
Token::StringConstant(s) => {
Expr::StringConstant(self.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),
},
Token::ExclusiveRange | Token::InclusiveRange => Expr::IntegerConstant(0, settings.pos),
#[cfg(not(feature = "no_float"))]
Token::FloatConstant(x) => {
let x = x.0;
state.input.next();
Expr::FloatConstant(x, settings.pos)
}
#[cfg(feature = "decimal")]
Token::DecimalConstant(x) => {
let x = x.0;
state.input.next();
Expr::DynamicConstant(Box::new(x.into()), settings.pos)
}
Token::LeftBrace if settings.has_option(LangOptions::STMT_EXPR) => {
match self.parse_block(state, settings.level_up()?)? {
block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())),
stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
}
}
Token::LeftParen => {
settings.pos = eat_token(state.input, &Token::LeftParen);
let expr = self.parse_expr(state, settings.level_up()?)?;
match state.input.next().unwrap() {
(Token::RightParen, ..) => expr,
(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))
}
}
}
Token::If if settings.has_option(LangOptions::IF_EXPR) => {
Expr::Stmt(Box::new(self.parse_if(state, settings.level_up()?)?.into()))
}
Token::While | Token::Loop
if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) =>
{
Expr::Stmt(Box::new(
self.parse_while_loop(state, settings.level_up()?)?.into(),
))
}
Token::Do if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => {
Expr::Stmt(Box::new(self.parse_do(state, settings.level_up()?)?.into()))
}
Token::For if self.allow_looping() && settings.has_option(LangOptions::LOOP_EXPR) => {
Expr::Stmt(Box::new(
self.parse_for(state, settings.level_up()?)?.into(),
))
}
Token::Switch if settings.has_option(LangOptions::SWITCH_EXPR) => Expr::Stmt(Box::new(
self.parse_switch(state, settings.level_up()?)?.into(),
)),
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))]
Token::Pipe | Token::Or
if settings.has_option(LangOptions::ANON_FN)
&& state.lib.len() >= self.max_functions() =>
{
return Err(PERR::TooManyFunctions.into_err(settings.pos));
}
#[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or if settings.has_option(LangOptions::ANON_FN) => {
self.parse_anon_fn(state, settings, false)?
}
Token::InterpolatedString(..) => {
let mut segments = ThinVec::new();
let settings = settings.level_up()?;
match state.input.next().unwrap() {
(Token::InterpolatedString(s), ..) if s.is_empty() => (),
(Token::InterpolatedString(s), pos) => {
segments.push(Expr::StringConstant(self.get_interned_string(*s), pos))
}
token => {
unreachable!("Token::InterpolatedString expected but gets {:?}", token)
}
}
loop {
let expr = match self.parse_block(state, 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),
}
state.tokenizer_control.borrow_mut().is_within_text = true;
match state.input.next().unwrap() {
(Token::StringConstant(s), pos) => {
if !s.is_empty() {
segments
.push(Expr::StringConstant(self.get_interned_string(*s), pos));
}
break;
}
(Token::InterpolatedString(s), pos) => {
if !s.is_empty() {
segments
.push(Expr::StringConstant(self.get_interned_string(*s), pos));
}
}
(Token::LexError(err), pos) => match *err {
LexError::UnterminatedString | LexError::StringTooLong(_) => {
return Err(err.into_err(pos))
}
_ => unreachable!("improper lex error: {:?}", err),
},
(token, ..) => unreachable!(
"string within an interpolated string literal expected but gets {:?}",
token
),
}
}
if segments.is_empty() {
Expr::StringConstant(self.get_interned_string(""), settings.pos)
} else {
segments.shrink_to_fit();
Expr::InterpolatedString(segments, settings.pos)
}
}
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => self.parse_array_literal(state, settings.level_up()?)?,
#[cfg(not(feature = "no_object"))]
Token::MapStart => self.parse_map_literal(state, settings.level_up()?)?,
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key)
if self.custom_syntax.contains_key(&**key) =>
{
let (key, syntax) = self.custom_syntax.get_key_value(&**key).unwrap();
let (.., pos) = state.input.next().unwrap();
let settings = settings.level_up_with_position(pos)?;
self.parse_custom_syntax(state, settings, key, syntax)?
}
Token::Identifier(..) => {
#[cfg(not(feature = "no_module"))]
let ns = crate::ast::Namespace::NONE;
let s = match state.input.next().unwrap() {
(Token::Identifier(s), ..) => s,
token => unreachable!("Token::Identifier expected but gets {:?}", token),
};
match state.input.peek().unwrap() {
(Token::LeftParen | Token::Bang | Token::Unit, _) => {
state.allow_capture = true;
Expr::Variable(
#[cfg(not(feature = "no_module"))]
(None, self.get_interned_string(*s), ns, 0).into(),
#[cfg(feature = "no_module")]
(None, self.get_interned_string(*s)).into(),
None,
settings.pos,
)
}
#[cfg(not(feature = "no_module"))]
(token @ Token::DoubleColon, pos) => {
if options.intersects(ChainingFlags::DISALLOW_NAMESPACES) {
return Err(LexError::ImproperSymbol(
token.literal_syntax().into(),
String::new(),
)
.into_err(*pos));
}
state.allow_capture = true;
let name = self.get_interned_string(*s);
Expr::Variable((None, name, ns, 0).into(), None, settings.pos)
}
_ => {
let (index, is_func) = self.access_var(state, &s, settings.pos);
if !options.intersects(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 = self.get_interned_string(*s);
Expr::Variable(
#[cfg(not(feature = "no_module"))]
(index, name, ns, 0).into(),
#[cfg(feature = "no_module")]
(index, name).into(),
short_index,
settings.pos,
)
}
}
}
Token::Reserved(..) => {
#[cfg(not(feature = "no_module"))]
let ns = crate::ast::Namespace::NONE;
let s = match state.input.next().unwrap() {
(Token::Reserved(s), ..) => s,
token => unreachable!("Token::Reserved expected but gets {:?}", token),
};
match state.input.peek().unwrap().0 {
Token::LeftParen | Token::Bang | Token::Unit
if is_reserved_keyword_or_symbol(&s).1 =>
{
Expr::Variable(
#[cfg(not(feature = "no_module"))]
(None, self.get_interned_string(*s), ns, 0).into(),
#[cfg(feature = "no_module")]
(None, self.get_interned_string(*s)).into(),
None,
settings.pos,
)
}
#[cfg(not(feature = "no_function"))]
_ if *s == crate::engine::KEYWORD_THIS => {
if settings.has_flag(ParseSettingFlags::FN_SCOPE) {
Expr::ThisPtr(settings.pos)
} else {
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 state.input.next().unwrap() {
(Token::LexError(err), ..) => return Err(err.into_err(settings.pos)),
token => unreachable!("Token::LexError expected but gets {:?}", token),
},
_ => {
return Err(LexError::UnexpectedInput(next_token.to_string()).into_err(settings.pos))
}
};
if !(state.expr_filter)(&state.input.peek().unwrap().0) {
return Ok(root_expr);
}
self.parse_postfix(state, settings, root_expr, ChainingFlags::empty())
}
fn parse_postfix(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
mut lhs: Expr,
_options: ChainingFlags,
) -> ParseResult<Expr> {
let mut _parent_options = ASTFlags::BREAK;
loop {
let (tail_token, ..) = state.input.peek().unwrap();
if !lhs.is_valid_postfix(tail_token) {
break;
}
let (tail_token, tail_pos) = state.input.next().unwrap();
settings.pos = tail_pos;
lhs = match (lhs, tail_token) {
#[cfg(not(feature = "no_module"))]
(Expr::Variable(x, ..), Token::Bang) if !x.2.is_empty() => {
return match state.input.peek().unwrap() {
(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)),
};
}
(Expr::Variable(x, .., pos), Token::Bang) => {
match state.input.peek().unwrap() {
(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 = state.input.next().unwrap().0 == Token::Unit;
#[cfg(not(feature = "no_module"))]
let (_, name, ns, ..) = *x;
#[cfg(feature = "no_module")]
let (_, name) = *x;
settings.pos = pos;
self.parse_fn_call(
state,
settings,
name,
no_args,
true,
#[cfg(not(feature = "no_module"))]
ns,
)?
}
(Expr::Variable(x, .., pos), t @ (Token::LeftParen | Token::Unit)) => {
#[cfg(not(feature = "no_module"))]
let (_, name, ns, ..) = *x;
#[cfg(feature = "no_module")]
let (_, name) = *x;
let no_args = t == Token::Unit;
settings.pos = pos;
self.parse_fn_call(
state,
settings,
name,
no_args,
false,
#[cfg(not(feature = "no_module"))]
ns,
)?
}
#[cfg(not(feature = "no_module"))]
(_, token @ Token::DoubleColon)
if _options.intersects(ChainingFlags::DISALLOW_NAMESPACES) =>
{
return Err(LexError::ImproperSymbol(
token.literal_syntax().into(),
String::new(),
)
.into_err(tail_pos))
}
#[cfg(not(feature = "no_module"))]
(Expr::Variable(x, .., pos), Token::DoubleColon) => {
let (id2, pos2) = parse_var_name(state.input)?;
let (_, name, mut namespace, ..) = *x;
let var_name_def = Ident { name, pos };
namespace.path.push(var_name_def);
let var_name = self.get_interned_string(id2);
Expr::Variable((None, var_name, namespace, 0).into(), None, pos2)
}
#[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(state, settings, expr, opt, true)?
}
#[cfg(not(feature = "no_object"))]
(expr, op @ (Token::Period | Token::Elvis)) => {
match state.input.peek().unwrap() {
(Token::Identifier(..), ..) => {
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(state, settings.level_up()?, options)?;
self.make_dot_expr(expr, rhs, _parent_options, op_flags, tail_pos)?
}
(expr, token) => {
unreachable!("unknown postfix operator '{}' for {:?}", token, expr)
}
};
_parent_options = ASTFlags::empty();
}
#[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);
}
#[cfg(not(feature = "no_module"))]
let namespaced_variable = match lhs {
Expr::Variable(ref mut x, ..) if !x.2.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.2.is_empty() => Some(&mut **x),
_ => None,
},
_ => None,
};
#[cfg(not(feature = "no_module"))]
if let Some((.., name, namespace, hash)) = namespaced_variable {
if !namespace.is_empty() {
*hash = crate::calc_var_hash(namespace.path.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.iter().any(|m| m == root)
&& !self.global_sub_modules.contains_key(root)
{
return Err(
PERR::ModuleUndefined(root.into()).into_err(namespace.position())
);
}
namespace.index = index;
}
}
}
Ok(lhs)
}
fn parse_unary(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
) -> ParseResult<Expr> {
let (token, token_pos) = state.input.peek().unwrap();
if !(state.expr_filter)(token) {
return Err(LexError::UnexpectedInput(token.to_string()).into_err(*token_pos));
}
settings.pos = *token_pos;
match token {
Token::Minus | Token::UnaryMinus => {
let token = token.clone();
let pos = eat_token(state.input, &token);
match self.parse_unary(state, settings.level_up()?)? {
Expr::IntegerConstant(num, ..) => num
.checked_neg()
.map(|i| Expr::IntegerConstant(i, pos))
.or_else(|| {
#[cfg(not(feature = "no_float"))]
return Some(Expr::FloatConstant((-(num as crate::FLOAT)).into(), pos));
#[cfg(feature = "no_float")]
return None;
})
.ok_or_else(|| LexError::MalformedNumber(format!("-{num}")).into_err(pos)),
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x, ..) => Ok(Expr::FloatConstant((-(*x)).into(), pos)),
expr => Ok(FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: crate::ast::Namespace::NONE,
name: self.get_interned_string("-"),
hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "-", 1)),
args: IntoIterator::into_iter([expr]).collect(),
op_token: Some(token),
capture_parent_scope: false,
}
.into_fn_call_expr(pos)),
}
}
Token::Plus | Token::UnaryPlus => {
let token = token.clone();
let pos = eat_token(state.input, &token);
match self.parse_unary(state, settings.level_up()?)? {
expr @ Expr::IntegerConstant(..) => Ok(expr),
#[cfg(not(feature = "no_float"))]
expr @ Expr::FloatConstant(..) => Ok(expr),
expr => Ok(FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: crate::ast::Namespace::NONE,
name: self.get_interned_string("+"),
hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "+", 1)),
args: IntoIterator::into_iter([expr]).collect(),
op_token: Some(token),
capture_parent_scope: false,
}
.into_fn_call_expr(pos)),
}
}
Token::Bang => {
let token = token.clone();
let pos = eat_token(state.input, &Token::Bang);
Ok(FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: crate::ast::Namespace::NONE,
name: self.get_interned_string("!"),
hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "!", 1)),
args: {
let expr = self.parse_unary(state, settings.level_up()?)?;
IntoIterator::into_iter([expr]).collect()
},
op_token: Some(token),
capture_parent_scope: false,
}
.into_fn_call_expr(pos))
}
Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)),
_ => self.parse_primary(state, settings, ChainingFlags::empty()),
}
}
fn make_assignment_stmt(
op: Option<Token>,
state: &mut ParseState,
lhs: Expr,
rhs: Expr,
op_pos: Position,
) -> ParseResult<Stmt> {
#[must_use]
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
match expr {
Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) if parent_is_dot => {
match x.lhs {
Expr::Property(..) if !options.intersects(ASTFlags::BREAK) => {
check_lvalue(&x.rhs, matches!(expr, Expr::Dot(..)))
}
Expr::Property(..) => None,
ref e => Some(e.position()),
}
}
Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) => match x.lhs {
Expr::Property(..) => unreachable!("unexpected Expr::Property in indexing"),
_ if !options.intersects(ASTFlags::BREAK) => {
check_lvalue(&x.rhs, matches!(expr, Expr::Dot(..)))
}
_ => None,
},
Expr::Property(..) if parent_is_dot => None,
Expr::Property(..) => unreachable!("unexpected Expr::Property in indexing"),
e if parent_is_dot => Some(e.position()),
_ => None,
}
}
let op_info = op.map_or_else(
|| OpAssignment::new_assignment(op_pos),
|op| OpAssignment::new_op_assignment_from_token(op, op_pos),
);
match lhs {
Expr::ThisPtr(_) => Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into())),
Expr::Variable(ref x, None, _) if x.0.is_none() => {
Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into()))
}
Expr::Variable(ref x, i, var_pos) => {
let (index, name, ..) = &**x;
let index = i.map_or_else(
|| index.expect("long or short index must be `Some`").get(),
|n| n.get() as usize,
);
match state
.stack
.get_mut_by_index(state.stack.len() - index)
.access_mode()
{
AccessMode::ReadWrite => {
Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into()))
}
AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos))
}
}
}
Expr::Index(ref x, options, ..) | Expr::Dot(ref x, options, ..) => {
let valid_lvalue = if options.intersects(ASTFlags::BREAK) {
None
} else {
check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(..)))
};
if let Some(err_pos) = valid_lvalue {
Err(PERR::AssignmentToInvalidLHS(String::new()).into_err(err_pos))
} else {
match x.lhs {
Expr::Variable(..) | Expr::ThisPtr(..) => {
Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into()))
}
ref expr => {
Err(PERR::AssignmentToInvalidLHS(String::new())
.into_err(expr.position()))
}
}
}
}
ref expr if expr.is_constant() => {
Err(PERR::AssignmentToConstant(String::new()).into_err(lhs.start_position()))
}
Expr::And(..) | Expr::Or(..) | Expr::Coalesce(..) if !op_info.is_op_assignment() => {
Err(LexError::ImproperSymbol(
Token::Equals.literal_syntax().into(),
"Possibly a typo of '=='?".into(),
)
.into_err(op_pos))
}
_ => Err(PERR::AssignmentToInvalidLHS(String::new()).into_err(lhs.position())),
}
}
#[cfg(not(feature = "no_object"))]
fn make_dot_expr(
&self,
lhs: Expr,
rhs: Expr,
parent_options: ASTFlags,
op_flags: ASTFlags,
op_pos: Position,
) -> ParseResult<Expr> {
match (lhs, rhs) {
(Expr::Index(mut x, options, pos), rhs)
if !parent_options.intersects(ASTFlags::BREAK) =>
{
let options = options | parent_options;
x.rhs = self.make_dot_expr(x.rhs, rhs, options, op_flags, op_pos)?;
Ok(Expr::Index(x, ASTFlags::empty(), pos))
}
#[cfg(not(feature = "no_module"))]
(.., Expr::Variable(x, ..)) if !x.2.is_empty() => unreachable!("lhs.ns::id"),
(lhs, var_expr @ Expr::Variable(..)) => {
let rhs = self.convert_expr_into_property(var_expr);
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
}
(lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot(
BinaryExpr { lhs, rhs: prop }.into(),
op_flags,
op_pos,
)),
#[cfg(not(feature = "no_module"))]
(.., Expr::FnCall(f, ..)) if f.is_qualified() => unreachable!("lhs.ns::func()"),
(.., Expr::FnCall(f, func_pos))
if f.args.is_empty()
&& matches!(
&*f.name,
crate::engine::KEYWORD_FN_PTR | crate::engine::KEYWORD_EVAL
) =>
{
let err_msg = format!(
"'{}' should not be called in method style. Try {}(...);",
f.name, f.name
);
Err(LexError::ImproperSymbol(f.name.to_string(), err_msg).into_err(func_pos))
}
(.., Expr::FnCall(f, func_pos)) if f.capture_parent_scope => {
Err(PERR::MalformedCapture(
"method-call style does not support running within the caller's scope".into(),
)
.into_err(func_pos))
}
(lhs, Expr::FnCall(mut f, func_pos)) => {
let args_len = f.args.len() + 1;
f.hashes = if is_valid_function_name(&f.name) {
#[cfg(not(feature = "no_function"))]
{
FnCallHashes::from_script_and_native(
calc_fn_hash(None, &f.name, args_len - 1),
calc_fn_hash(None, &f.name, args_len),
)
}
#[cfg(feature = "no_function")]
{
FnCallHashes::from_native_only(calc_fn_hash(None, &f.name, args_len))
}
} else {
FnCallHashes::from_native_only(calc_fn_hash(None, &f.name, args_len))
};
let rhs = Expr::MethodCall(f, func_pos);
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
}
(lhs, rhs @ (Expr::Dot(..) | Expr::Index(..))) => {
let (x, options, pos, is_dot) = match rhs {
Expr::Dot(x, options, pos) => (x, options, pos, true),
Expr::Index(x, options, pos) => (x, options, pos, false),
expr => unreachable!("Expr::Dot or Expr::Index expected but gets {:?}", expr),
};
match x.lhs {
#[cfg(not(feature = "no_module"))]
Expr::Variable(x, ..) if !x.2.is_empty() => unreachable!("lhs.ns::id..."),
#[cfg(not(feature = "no_module"))]
Expr::FnCall(f, ..) if f.is_qualified() => {
unreachable!("lhs.ns::func()...")
}
Expr::Variable(..) | Expr::Property(..) => {
let new_binary = BinaryExpr {
lhs: self.convert_expr_into_property(x.lhs),
rhs: x.rhs,
}
.into();
let rhs = if is_dot {
Expr::Dot(new_binary, options, pos)
} else {
Expr::Index(new_binary, options, pos)
};
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
}
Expr::FnCall(mut f, func_pos) => {
let args_len = f.args.len() + 1;
f.hashes = if is_valid_function_name(&f.name) {
#[cfg(not(feature = "no_function"))]
{
FnCallHashes::from_script_and_native(
calc_fn_hash(None, &f.name, args_len - 1),
calc_fn_hash(None, &f.name, args_len),
)
}
#[cfg(feature = "no_function")]
{
FnCallHashes::from_native_only(calc_fn_hash(
None, &f.name, args_len,
))
}
} else {
FnCallHashes::from_native_only(calc_fn_hash(None, &f.name, args_len))
};
let new_lhs = BinaryExpr {
lhs: Expr::MethodCall(f, func_pos),
rhs: x.rhs,
}
.into();
let rhs = if is_dot {
Expr::Dot(new_lhs, options, pos)
} else {
Expr::Index(new_lhs, options, pos)
};
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
}
expr => unreachable!("invalid dot expression: {:?}", expr),
}
}
(.., rhs) => Err(PERR::PropertyExpected.into_err(rhs.start_position())),
}
}
fn parse_binary_op(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
parent_precedence: Option<Precedence>,
lhs: Expr,
) -> ParseResult<Expr> {
settings.pos = lhs.position();
let mut root = lhs;
loop {
let (current_op, current_pos) = state.input.peek().unwrap();
if !(state.expr_filter)(current_op) {
return Ok(root);
}
let precedence = match current_op {
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(c) => self
.custom_keywords
.get(&**c)
.copied()
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?,
Token::Reserved(c) if !is_valid_identifier(c) => {
return Err(PERR::UnknownOperator(c.to_string()).into_err(*current_pos))
}
_ => current_op.precedence(),
};
let bind_right = current_op.is_bind_right();
if precedence < parent_precedence || (precedence == parent_precedence && !bind_right) {
return Ok(root);
}
let (op_token, pos) = state.input.next().unwrap();
let rhs = match op_token {
Token::DoubleQuestion
if matches!(
state.input.peek().unwrap().0,
Token::Break | Token::Continue | Token::Return | Token::Throw
) =>
{
let stmt = self.parse_stmt(state, settings)?;
let block: StmtBlock = stmt.into();
Expr::Stmt(block.into())
}
Token::ExclusiveRange | Token::InclusiveRange
if matches!(
state.input.peek().unwrap().0,
Token::RightBracket
| Token::RightParen
| Token::RightBrace
| Token::Comma
| Token::SemiColon
| Token::DoubleArrow
) =>
{
let (_, next_pos) = state.input.peek().unwrap();
Expr::Unit(*next_pos)
}
_ => self.parse_unary(state, settings)?,
};
let (next_op, next_pos) = state.input.peek().unwrap();
let next_precedence = match next_op {
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(c) => self
.custom_keywords
.get(&**c)
.copied()
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?,
Token::Reserved(c) if !is_valid_identifier(c) => {
return Err(PERR::UnknownOperator(c.to_string()).into_err(*next_pos))
}
_ => next_op.precedence(),
};
let rhs =
if (precedence == next_precedence && bind_right) || precedence < next_precedence {
self.parse_binary_op(state, settings.level_up()?, precedence, rhs)?
} else {
rhs
};
settings = settings.level_up()?;
settings.pos = pos;
let op = op_token.to_string();
let hash = calc_fn_hash(None, &op, 2);
let native_only = !is_valid_function_name(&op);
let mut op_base = FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: crate::ast::Namespace::NONE,
name: self.get_interned_string(&op),
hashes: FnCallHashes::from_native_only(hash),
args: IntoIterator::into_iter([root, rhs]).collect(),
op_token: native_only.then(|| op_token.clone()),
capture_parent_scope: false,
};
root = match op_token {
Token::Or => {
let rhs = op_base.args[1].take().ensure_bool_expr()?;
let lhs = op_base.args[0].take().ensure_bool_expr()?;
Expr::Or(BinaryExpr { lhs, rhs }.into(), pos)
}
Token::And => {
let rhs = op_base.args[1].take().ensure_bool_expr()?;
let lhs = op_base.args[0].take().ensure_bool_expr()?;
Expr::And(BinaryExpr { lhs, rhs }.into(), pos)
}
Token::DoubleQuestion => {
let rhs = op_base.args[1].take();
let lhs = op_base.args[0].take();
Expr::Coalesce(BinaryExpr { lhs, rhs }.into(), pos)
}
Token::In | Token::NotIn => {
let (lhs, rhs) = op_base.args.split_first_mut().unwrap();
std::mem::swap(lhs, &mut rhs[0]);
op_base.hashes = FnCallHashes::from_hash(calc_fn_hash(None, OP_CONTAINS, 2));
op_base.name = self.get_interned_string(OP_CONTAINS);
let fn_call = op_base.into_fn_call_expr(pos);
if op_token == Token::In {
fn_call
} else {
let not_base = FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: crate::ast::Namespace::NONE,
name: self.get_interned_string(OP_NOT),
hashes: FnCallHashes::from_native_only(calc_fn_hash(None, OP_NOT, 1)),
args: IntoIterator::into_iter([fn_call]).collect(),
op_token: Some(Token::Bang),
capture_parent_scope: false,
};
not_base.into_fn_call_expr(pos)
}
}
Token::ExclusiveRange | Token::InclusiveRange => op_base.into_fn_call_expr(pos),
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(s) if self.custom_keywords.contains_key(&*s) => {
op_base.hashes = if native_only {
FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2))
} else {
FnCallHashes::from_hash(calc_fn_hash(None, &s, 2))
};
op_base.into_fn_call_expr(pos)
}
_ => op_base.into_fn_call_expr(pos),
};
}
}
#[cfg(not(feature = "no_custom_syntax"))]
fn parse_custom_syntax(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
key: impl Into<ImmutableString>,
syntax: &crate::api::custom_syntax::CustomSyntax,
) -> ParseResult<Expr> {
#[allow(clippy::wildcard_imports)]
use crate::api::custom_syntax::markers::*;
const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax();
const KEYWORD_CLOSE_BRACE: &str = Token::RightBrace.literal_syntax();
let pos = settings.pos;
let mut inputs = FnArgsVec::new();
let mut segments = FnArgsVec::new();
let mut tokens = FnArgsVec::new();
if syntax.scope_may_be_changed {
let marker = self.get_interned_string(SCOPE_SEARCH_BARRIER_MARKER);
state.stack.push(marker, ());
}
let mut user_state = Dynamic::UNIT;
let parse_func = &*syntax.parse;
let mut required_token: ImmutableString = key.into();
tokens.push(required_token.clone());
segments.push(required_token.clone());
loop {
let (fwd_token, fwd_pos) = state.input.peek().unwrap();
settings.pos = *fwd_pos;
let settings = settings.level_up()?;
required_token = match parse_func(&segments, &fwd_token.to_string(), &mut user_state) {
Ok(Some(seg))
if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT)
&& seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() =>
{
inputs.push(Expr::StringConstant(self.get_interned_string(seg), pos));
break;
}
Ok(Some(seg)) => seg,
Ok(None) => break,
Err(err) => return Err(err.0.into_err(settings.pos)),
};
match required_token.as_str() {
CUSTOM_SYNTAX_MARKER_IDENT => {
let (name, pos) = parse_var_name(state.input)?;
let name = self.get_interned_string(name);
segments.push(name.clone());
tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT));
inputs.push(Expr::Variable(
#[cfg(not(feature = "no_module"))]
(None, name, crate::ast::Namespace::NONE, 0).into(),
#[cfg(feature = "no_module")]
(None, name).into(),
None,
pos,
));
}
CUSTOM_SYNTAX_MARKER_SYMBOL => {
let (symbol, pos) = match state.input.next().unwrap() {
(token, pos) if token.is_standard_symbol() => {
Ok((token.literal_syntax().into(), pos))
}
(Token::Reserved(s), pos) if !is_valid_identifier(s.as_str()) => {
Ok((*s, pos))
}
(Token::LexError(err), pos) => Err(err.into_err(pos)),
(.., pos) => Err(PERR::MissingSymbol(String::new()).into_err(pos)),
}?;
let symbol = self.get_interned_string(symbol);
segments.push(symbol.clone());
tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_SYMBOL));
inputs.push(Expr::StringConstant(symbol, pos));
}
CUSTOM_SYNTAX_MARKER_EXPR => {
inputs.push(self.parse_expr(state, settings)?);
let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_EXPR);
segments.push(keyword.clone());
tokens.push(keyword);
}
CUSTOM_SYNTAX_MARKER_BLOCK => match self.parse_block(state, settings)? {
block @ Stmt::Block(..) => {
inputs.push(Expr::Stmt(Box::new(block.into())));
let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_BLOCK);
segments.push(keyword.clone());
tokens.push(keyword);
}
stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
},
#[cfg(not(feature = "no_function"))]
CUSTOM_SYNTAX_MARKER_FUNC => {
let skip = match fwd_token {
Token::Or | Token::Pipe => false,
Token::LeftBrace => true,
_ => {
return Err(PERR::MissingSymbol("Expecting '{' or '|'".into())
.into_err(*fwd_pos))
}
};
inputs.push(self.parse_anon_fn(state, settings, skip)?);
let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_FUNC);
segments.push(keyword.clone());
tokens.push(keyword);
}
CUSTOM_SYNTAX_MARKER_BOOL => match state.input.next().unwrap() {
(b @ (Token::True | Token::False), pos) => {
inputs.push(Expr::BoolConstant(b == Token::True, pos));
segments.push(self.get_interned_string(b.literal_syntax()));
tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_BOOL));
}
(.., pos) => {
return Err(
PERR::MissingSymbol("Expecting 'true' or 'false'".into()).into_err(pos)
)
}
},
CUSTOM_SYNTAX_MARKER_INT => match state.input.next().unwrap() {
(Token::IntegerConstant(i), pos) => {
inputs.push(Expr::IntegerConstant(i, pos));
segments.push(i.to_string().into());
tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_INT));
}
(.., pos) => {
return Err(
PERR::MissingSymbol("Expecting an integer number".into()).into_err(pos)
)
}
},
#[cfg(not(feature = "no_float"))]
CUSTOM_SYNTAX_MARKER_FLOAT => match state.input.next().unwrap() {
(Token::FloatConstant(f), pos) => {
inputs.push(Expr::FloatConstant(f.0, pos));
segments.push(f.1.into());
tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_FLOAT));
}
(.., pos) => {
return Err(
PERR::MissingSymbol("Expecting a floating-point number".into())
.into_err(pos),
)
}
},
CUSTOM_SYNTAX_MARKER_STRING => match state.input.next().unwrap() {
(Token::StringConstant(s), pos) => {
let s = self.get_interned_string(*s);
inputs.push(Expr::StringConstant(s.clone(), pos));
segments.push(s);
tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_STRING));
}
(.., pos) => {
return Err(PERR::MissingSymbol("Expecting a string".into()).into_err(pos))
}
},
s => match state.input.next().unwrap() {
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(Token::Identifier(t) | Token::Reserved(t) | Token::Custom(t), ..)
if *t == s =>
{
segments.push(required_token.clone());
tokens.push(required_token.clone());
}
(t, ..) if t.is_literal() && t.literal_syntax() == s => {
segments.push(required_token.clone());
tokens.push(required_token.clone());
}
(.., pos) => {
return Err(PERR::MissingToken(
s.into(),
format!("for '{}' expression", segments[0]),
)
.into_err(pos))
}
},
}
}
inputs.shrink_to_fit();
tokens.shrink_to_fit();
let self_terminated = matches!(
&*required_token,
CUSTOM_SYNTAX_MARKER_BLOCK |
KEYWORD_SEMICOLON | KEYWORD_CLOSE_BRACE
);
#[cfg(not(feature = "no_function"))]
let self_terminated = required_token == CUSTOM_SYNTAX_MARKER_FUNC || self_terminated;
Ok(Expr::Custom(
crate::ast::CustomExpr {
inputs,
tokens,
state: user_state,
scope_may_be_changed: syntax.scope_may_be_changed,
self_terminated,
}
.into(),
pos,
))
}
fn parse_expr(&self, state: &mut ParseState, mut settings: ParseSettings) -> ParseResult<Expr> {
settings.pos = state.input.peek().unwrap().1;
let precedence = Precedence::new(1);
let settings = settings.level_up()?;
let lhs = self.parse_unary(state, settings)?;
self.parse_binary_op(state, settings, precedence, lhs)
}
fn parse_if(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult<Stmt> {
let settings = settings.level_up_with_position(eat_token(state.input, &Token::If))?;
ensure_not_statement_expr(state.input, "a boolean")?;
let expr = self.parse_expr(state, settings)?.ensure_bool_expr()?;
ensure_not_assignment(state.input)?;
let body = self.parse_block(state, settings)?.into();
let branch = if match_token(state.input, &Token::Else).0 {
match state.input.peek().unwrap() {
(Token::If, ..) => self.parse_if(state, settings)?,
_ => self.parse_block(state, settings)?,
}
} else {
Stmt::Noop(Position::NONE)
}
.into();
Ok(Stmt::If(
FlowControl { expr, body, branch }.into(),
settings.pos,
))
}
fn parse_while_loop(
&self,
state: &mut ParseState,
settings: ParseSettings,
) -> ParseResult<Stmt> {
let mut settings = settings.level_up()?;
let (expr, token_pos) = match state.input.next().unwrap() {
(Token::While, pos) => {
ensure_not_statement_expr(state.input, "a boolean")?;
let expr = self.parse_expr(state, settings)?.ensure_bool_expr()?;
ensure_not_assignment(state.input)?;
(expr, pos)
}
(Token::Loop, pos) => (Expr::Unit(Position::NONE), pos),
token => unreachable!("Token::While or Token::Loop expected but gets {:?}", token),
};
settings.pos = token_pos;
settings.flags |= ParseSettingFlags::BREAKABLE;
let body = self.parse_block(state, settings)?.into();
let branch = StmtBlock::NONE;
Ok(Stmt::While(
FlowControl { expr, body, branch }.into(),
settings.pos,
))
}
fn parse_do(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult<Stmt> {
let mut settings = settings.level_up_with_position(eat_token(state.input, &Token::Do))?;
let orig_breakable = settings.has_flag(ParseSettingFlags::BREAKABLE);
settings.flags |= ParseSettingFlags::BREAKABLE;
let body = self.parse_block(state, settings)?.into();
let negated = match state.input.next().unwrap() {
(Token::While, ..) => ASTFlags::empty(),
(Token::Until, ..) => ASTFlags::NEGATED,
(.., pos) => {
return Err(
PERR::MissingToken(Token::While.into(), "for the do statement".into())
.into_err(pos),
)
}
};
if !orig_breakable {
settings.flags.remove(ParseSettingFlags::BREAKABLE);
}
ensure_not_statement_expr(state.input, "a boolean")?;
let expr = self.parse_expr(state, settings)?.ensure_bool_expr()?;
ensure_not_assignment(state.input)?;
let branch = StmtBlock::NONE;
Ok(Stmt::Do(
FlowControl { expr, body, branch }.into(),
negated,
settings.pos,
))
}
fn parse_for(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult<Stmt> {
let mut settings = settings.level_up_with_position(eat_token(state.input, &Token::For))?;
let (name, name_pos, counter_name, counter_pos) =
if match_token(state.input, &Token::LeftParen).0 {
let (name, name_pos) = parse_var_name(state.input)?;
let (has_comma, pos) = match_token(state.input, &Token::Comma);
if !has_comma {
return Err(PERR::MissingToken(
Token::Comma.into(),
"after the iteration variable name".into(),
)
.into_err(pos));
}
let (counter_name, counter_pos) = parse_var_name(state.input)?;
if counter_name == name {
return Err(PERR::DuplicatedVariable(counter_name.into()).into_err(counter_pos));
}
let (has_close_paren, pos) = match_token(state.input, &Token::RightParen);
if !has_close_paren {
return Err(PERR::MissingToken(
Token::RightParen.into(),
"to close the iteration variable".into(),
)
.into_err(pos));
}
(name, name_pos, Some(counter_name), counter_pos)
} else {
let (name, name_pos) = parse_var_name(state.input)?;
(name, name_pos, None, Position::NONE)
};
match state.input.next().unwrap() {
(Token::In, ..) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::In.into(),
"after the iteration variable".into(),
)
.into_err(pos))
}
}
ensure_not_statement_expr(state.input, "a boolean")?;
let expr = self.parse_expr(state, settings)?.ensure_iterable()?;
let counter_var = counter_name.map(|counter_name| Ident {
name: self.get_interned_string(counter_name),
pos: counter_pos,
});
let loop_var = Ident {
name: self.get_interned_string(name),
pos: name_pos,
};
let prev_stack_len = {
let prev_stack_len = state.stack.len();
if let Some(ref counter_var) = counter_var {
state.stack.push(counter_var.name.clone(), ());
}
state.stack.push(&loop_var.name, ());
prev_stack_len
};
settings.flags |= ParseSettingFlags::BREAKABLE;
let body = self.parse_block(state, settings)?.into();
state.stack.rewind(prev_stack_len);
let branch = StmtBlock::NONE;
Ok(Stmt::For(
Box::new((loop_var, counter_var, FlowControl { expr, body, branch })),
settings.pos,
))
}
fn parse_let(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
access: AccessMode,
is_export: bool,
) -> ParseResult<Stmt> {
settings.pos = state.input.next().unwrap().1;
let (name, pos) = parse_var_name(state.input)?;
if !self.allow_shadowing() && state.stack.get(&name).is_some() {
return Err(PERR::VariableExists(name.into()).into_err(pos));
}
if let Some(ref filter) = self.def_var_filter {
let will_shadow = state.stack.get(&name).is_some();
let global = state
.global
.get_or_insert_with(|| self.new_global_runtime_state().into());
global.level = settings.level;
let is_const = access == AccessMode::ReadOnly;
let info = VarDefInfo::new(&name, is_const, settings.level, will_shadow);
let caches = &mut Caches::new();
let context = EvalContext::new(self, global, caches, &mut state.stack, None);
match filter(false, info, context) {
Ok(true) => (),
Ok(false) => return Err(PERR::ForbiddenVariable(name.into()).into_err(pos)),
Err(err) => {
return Err(match *err {
EvalAltResult::ErrorParsing(e, pos) => e.into_err(pos),
_ => PERR::ForbiddenVariable(name.into()).into_err(pos),
})
}
}
}
let name = self.get_interned_string(name);
let expr = if match_token(state.input, &Token::Equals).0 {
self.parse_expr(state, settings.level_up()?)?
} else {
Expr::Unit(Position::NONE)
};
let export = if is_export {
ASTFlags::EXPORTED
} else {
ASTFlags::empty()
};
let (existing, hit_barrier) = state.find_var(&name);
let existing = if !hit_barrier && existing > 0 {
match state.stack.len() - existing {
#[cfg(not(feature = "no_module"))]
offset if !state.stack.get_entry_by_index(offset).2.is_empty() => None,
offset if offset < state.frame_pointer => None,
offset => Some(offset),
}
} else {
None
};
let idx = if let Some(n) = existing {
state.stack.get_mut_by_index(n).set_access_mode(access);
Some(NonZeroUsize::new(state.stack.len() - n).unwrap())
} else {
state.stack.push_entry(name.clone(), access, Dynamic::UNIT);
None
};
#[cfg(not(feature = "no_module"))]
if is_export {
state
.stack
.add_alias_by_index(state.stack.len() - 1, name.clone());
}
let var_def = (Ident { name, pos }, expr, idx).into();
Ok(match access {
AccessMode::ReadWrite => Stmt::Var(var_def, export, settings.pos),
AccessMode::ReadOnly => Stmt::Var(var_def, ASTFlags::CONSTANT | export, settings.pos),
})
}
#[cfg(not(feature = "no_module"))]
fn parse_import(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult<Stmt> {
let settings = settings.level_up_with_position(eat_token(state.input, &Token::Import))?;
let expr = self.parse_expr(state, settings)?;
let export = if match_token(state.input, &Token::As).0 {
let (name, pos) = parse_var_name(state.input)?;
Ident {
name: self.get_interned_string(name),
pos,
}
} else {
Ident {
name: self.get_interned_string(""),
pos: Position::NONE,
}
};
state.imports.push(export.name.clone());
Ok(Stmt::Import((expr, export).into(), settings.pos))
}
#[cfg(not(feature = "no_module"))]
fn parse_export(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
) -> ParseResult<Stmt> {
settings.pos = eat_token(state.input, &Token::Export);
match state.input.peek().unwrap() {
(Token::Let, pos) => {
let pos = *pos;
let settings = settings.level_up()?;
let mut stmt = self.parse_let(state, settings, AccessMode::ReadWrite, true)?;
stmt.set_position(pos);
return Ok(stmt);
}
(Token::Const, pos) => {
let pos = *pos;
let settings = settings.level_up()?;
let mut stmt = self.parse_let(state, settings, AccessMode::ReadOnly, true)?;
stmt.set_position(pos);
return Ok(stmt);
}
_ => (),
}
let (id, id_pos) = parse_var_name(state.input)?;
let (alias, alias_pos) = if match_token(state.input, &Token::As).0 {
parse_var_name(state.input).map(|(name, pos)| (self.get_interned_string(name), pos))?
} else {
(self.get_interned_string(""), Position::NONE)
};
let (existing, hit_barrier) = state.find_var(&id);
if !hit_barrier && existing > 0 {
state
.stack
.add_alias_by_index(state.stack.len() - existing, alias.clone());
}
let export = (
Ident {
name: self.get_interned_string(id),
pos: id_pos,
},
Ident {
name: alias,
pos: alias_pos,
},
);
Ok(Stmt::Export(export.into(), settings.pos))
}
fn parse_block(&self, state: &mut ParseState, settings: ParseSettings) -> ParseResult<Stmt> {
let brace_start_pos = match state.input.next().unwrap() {
(Token::LeftBrace, pos) => pos,
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::LeftBrace.into(),
"to start a statement block".into(),
)
.into_err(pos))
}
};
let mut settings = settings.level_up_with_position(brace_start_pos)?;
let mut block = StmtBlock::empty(settings.pos);
if settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) {
let stmt = self.parse_expr_stmt(state, settings)?;
block.statements_mut().push(stmt);
return match state.input.next().unwrap() {
(Token::RightBrace, pos) => {
Ok(Stmt::Block(StmtBlock::new(block, settings.pos, pos).into()))
}
(Token::LexError(err), pos) => Err(err.into_err(pos)),
(.., pos) => Err(PERR::MissingToken(
Token::LeftBrace.into(),
"to start a statement block".into(),
)
.into_err(pos)),
};
}
let prev_frame_pointer = state.frame_pointer;
state.frame_pointer = state.stack.len();
#[cfg(not(feature = "no_module"))]
let orig_imports_len = state.imports.len();
let end_pos = loop {
match state.input.peek().unwrap() {
(Token::RightBrace, ..) => break eat_token(state.input, &Token::RightBrace),
(Token::EOF, pos) => {
return Err(PERR::MissingToken(
Token::RightBrace.into(),
"to terminate this block".into(),
)
.into_err(*pos));
}
_ => (),
}
settings.flags.remove(ParseSettingFlags::GLOBAL_LEVEL);
let stmt = self.parse_stmt(state, settings)?;
if stmt.is_noop() {
continue;
}
let need_semicolon = !stmt.is_self_terminated();
block.statements_mut().push(stmt);
match state.input.peek().unwrap() {
(Token::RightBrace, ..) => break eat_token(state.input, &Token::RightBrace),
(Token::SemiColon, ..) if need_semicolon => {
eat_token(state.input, &Token::SemiColon);
}
(Token::SemiColon, ..) if !need_semicolon => {
eat_token(state.input, &Token::SemiColon);
}
_ if !need_semicolon => (),
(Token::LexError(err), err_pos) => return Err(err.clone().into_err(*err_pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::SemiColon.into(),
"to terminate this statement".into(),
)
.into_err(*pos));
}
}
};
state.stack.rewind(state.frame_pointer);
state.frame_pointer = prev_frame_pointer;
#[cfg(not(feature = "no_module"))]
state.imports.truncate(orig_imports_len);
Ok(Stmt::Block(
StmtBlock::new(block, settings.pos, end_pos).into(),
))
}
fn parse_expr_stmt(
&self,
state: &mut ParseState,
mut settings: ParseSettings,
) -> ParseResult<Stmt> {
settings.pos = state.input.peek().unwrap().1;
let expr = self.parse_expr(state, settings)?;
let (op, pos) = match state.input.peek().unwrap() {
(Token::Equals, ..) => (None, eat_token(state.input, &Token::Equals)),
(token, ..) if token.is_op_assignment() => {
state.input.next().map(|(op, pos)| (Some(op), pos)).unwrap()
}
_ => return Ok(Stmt::Expr(expr.into())),
};
settings.pos = pos;
let rhs = self.parse_expr(state, settings)?;
Self::make_assignment_stmt(op, state, expr, rhs, pos)
}
fn parse_stmt(&self, state: &mut ParseState, mut settings: ParseSettings) -> ParseResult<Stmt> {
use AccessMode::{ReadOnly, ReadWrite};
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
let comments = {
let mut comments = StaticVec::<SmartString>::new_const();
let mut comments_pos = Position::NONE;
let mut buf = SmartString::new_const();
while let (Token::Comment(ref comment), pos) = state.input.peek().unwrap() {
if comments_pos.is_none() {
comments_pos = *pos;
}
debug_assert!(
crate::tokenizer::is_doc_comment(comment),
"doc-comment expected but gets {:?}",
comment
);
if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) {
return Err(PERR::WrongDocComment.into_err(comments_pos));
}
match state.input.next().unwrap() {
(Token::Comment(comment), pos) => {
if comment.contains('\n') {
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_str("\n");
}
buf.push_str(&comment);
}
match state.input.peek().unwrap() {
(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 state.input.peek().unwrap() {
(Token::EOF, pos) => return Ok(Stmt::Noop(*pos)),
(x, pos) => (x, *pos),
};
settings.pos = token_pos;
match token {
Token::SemiColon => {
eat_token(state.input, &Token::SemiColon);
Ok(Stmt::Noop(token_pos))
}
Token::LeftBrace => Ok(self.parse_block(state, settings.level_up()?)?),
#[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(state.input, &Token::Private);
crate::FnAccess::Private
} else {
crate::FnAccess::Public
};
match state.input.next().unwrap() {
#[cfg(not(feature = "unchecked"))]
(Token::Fn, pos) if state.lib.len() >= self.max_functions() => {
Err(PERR::TooManyFunctions.into_err(pos))
}
(Token::Fn, pos) => {
let new_state = &mut ParseState::new(
state.external_constants,
state.input,
state.tokenizer_control.clone(),
state.lib,
);
#[cfg(not(feature = "no_module"))]
{
new_state.global_imports.clone_from(&state.global_imports);
new_state.global_imports.extend(state.imports.clone());
}
let options = self.options | (settings.options & LangOptions::STRICT_VAR);
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(
new_state,
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 = f
.this_type
.as_ref()
.map_or(hash, |typ| crate::calc_typed_method_hash(hash, typ));
if state.lib.contains_key(&hash) {
return Err(PERR::FnDuplicatedDefinition(
f.name.to_string(),
f.params.len(),
)
.into_err(pos));
}
state.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(state, settings.level_up()?),
Token::Switch => self.parse_switch(state, settings.level_up()?),
Token::While | Token::Loop if self.allow_looping() => {
self.parse_while_loop(state, settings.level_up()?)
}
Token::Do if self.allow_looping() => self.parse_do(state, settings.level_up()?),
Token::For if self.allow_looping() => self.parse_for(state, settings.level_up()?),
Token::Continue
if self.allow_looping() && settings.has_flag(ParseSettingFlags::BREAKABLE) =>
{
let pos = eat_token(state.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(state.input, &Token::Break);
let expr = match state.input.peek().unwrap() {
(Token::EOF, ..) => None,
(Token::RightBrace, ..) => None,
(Token::SemiColon, ..) => None,
_ => Some(self.parse_expr(state, 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) = state
.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)
})
.unwrap();
match state.input.peek().unwrap() {
(Token::EOF, ..) => Ok(Stmt::Return(None, return_type, token_pos)),
(Token::RightBrace, ..)
if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) =>
{
Ok(Stmt::Return(None, return_type, token_pos))
}
(Token::SemiColon, ..) => Ok(Stmt::Return(None, return_type, token_pos)),
_ => {
let expr = self.parse_expr(state, settings.level_up()?)?;
Ok(Stmt::Return(Some(expr.into()), return_type, token_pos))
}
}
}
Token::Try => self.parse_try_catch(state, settings.level_up()?),
Token::Let => self.parse_let(state, settings.level_up()?, ReadWrite, false),
Token::Const => self.parse_let(state, settings.level_up()?, ReadOnly, false),
#[cfg(not(feature = "no_module"))]
Token::Import => self.parse_import(state, 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(state, settings.level_up()?),
_ => self.parse_expr_stmt(state, settings.level_up()?),
}
}
fn parse_try_catch(
&self,
state: &mut ParseState,
settings: ParseSettings,
) -> ParseResult<Stmt> {
let settings = settings.level_up_with_position(eat_token(state.input, &Token::Try))?;
let body = self.parse_block(state, settings)?.into();
let (matched, catch_pos) = match_token(state.input, &Token::Catch);
if !matched {
return Err(
PERR::MissingToken(Token::Catch.into(), "for the 'try' statement".into())
.into_err(catch_pos),
);
}
let catch_var = if match_token(state.input, &Token::LeftParen).0 {
let (name, pos) = parse_var_name(state.input)?;
let (matched, err_pos) = match_token(state.input, &Token::RightParen);
if !matched {
return Err(PERR::MissingToken(
Token::RightParen.into(),
"to enclose the catch variable".into(),
)
.into_err(err_pos));
}
let name = self.get_interned_string(name);
state.stack.push(name.clone(), ());
Ident { name, pos }
} else {
Ident {
name: self.get_interned_string(""),
pos: Position::NONE,
}
};
let branch = self.parse_block(state, settings)?.into();
let expr = if catch_var.is_empty() {
Expr::Unit(catch_var.pos)
} else {
state.stack.pop();
Expr::Variable(
#[cfg(not(feature = "no_module"))]
(None, catch_var.name, <_>::default(), 0).into(),
#[cfg(feature = "no_module")]
(None, catch_var.name).into(),
None,
catch_var.pos,
)
};
Ok(Stmt::TryCatch(
FlowControl { expr, body, branch }.into(),
settings.pos,
))
}
#[cfg(not(feature = "no_function"))]
fn parse_fn(
&self,
state: &mut ParseState,
settings: ParseSettings,
access: crate::FnAccess,
#[cfg(feature = "metadata")] comments: impl IntoIterator<Item = crate::Identifier>,
) -> ParseResult<ScriptFuncDef> {
let settings = settings.level_up()?;
let (token, pos) = state.input.next().unwrap();
#[cfg(not(feature = "no_object"))]
let ((token, pos), this_type) = {
let (next_token, next_pos) = state.input.peek().unwrap();
match token {
Token::StringConstant(s) if next_token == &Token::Period => {
eat_token(state.input, &Token::Period);
let s = match s.as_str() {
"int" => self.get_interned_string(std::any::type_name::<crate::INT>()),
#[cfg(not(feature = "no_float"))]
"float" => self.get_interned_string(std::any::type_name::<crate::FLOAT>()),
_ => self.get_interned_string(*s),
};
(state.input.next().unwrap(), Some(s))
}
Token::StringConstant(..) => {
return Err(PERR::MissingToken(
Token::Period.into(),
"after the type name for 'this'".into(),
)
.into_err(*next_pos))
}
Token::Identifier(s) if next_token == &Token::Period => {
eat_token(state.input, &Token::Period);
let s = match s.as_str() {
"int" => self.get_interned_string(std::any::type_name::<crate::INT>()),
#[cfg(not(feature = "no_float"))]
"float" => self.get_interned_string(std::any::type_name::<crate::FLOAT>()),
_ => self.get_interned_string(*s),
};
(state.input.next().unwrap(), Some(s))
}
_ => ((token, pos), None),
}
};
let name = match token {
#[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(s) if is_valid_function_name(&s) => *s,
Token::Identifier(s) if is_valid_function_name(&s) => *s,
Token::Reserved(s) => return Err(PERR::Reserved(s.to_string()).into_err(pos)),
_ => return Err(PERR::FnMissingName.into_err(pos)),
};
let no_params = match state.input.peek().unwrap() {
(Token::LeftParen, ..) => {
eat_token(state.input, &Token::LeftParen);
match_token(state.input, &Token::RightParen).0
}
(Token::Unit, ..) => {
eat_token(state.input, &Token::Unit);
true
}
(.., pos) => return Err(PERR::FnMissingParams(name.into()).into_err(*pos)),
};
let mut params = StaticVec::<(ImmutableString, _)>::new_const();
if !no_params {
let sep_err = format!("to separate the parameters of function '{name}'");
loop {
match state.input.next().unwrap() {
(Token::RightParen, ..) => break,
(Token::Identifier(s), pos) => {
if params.iter().any(|(p, _)| p == &*s) {
return Err(
PERR::FnDuplicatedParam(name.into(), s.to_string()).into_err(pos)
);
}
let s = self.get_interned_string(*s);
state.stack.push(s.clone(), ());
params.push((s, pos));
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::RightParen.into(),
format!("to close the parameters list of function '{name}'"),
)
.into_err(pos))
}
}
match state.input.next().unwrap() {
(Token::RightParen, ..) => break,
(Token::Comma, ..) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(.., pos) => {
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
}
}
}
}
let body = match state.input.peek().unwrap() {
(Token::LeftBrace, ..) => self.parse_block(state, settings)?,
(.., pos) => return Err(PERR::FnMissingBody(name.into()).into_err(*pos)),
}
.into();
let mut params: FnArgsVec<_> = params.into_iter().map(|(p, ..)| p).collect();
params.shrink_to_fit();
Ok(ScriptFuncDef {
name: self.get_interned_string(name),
access,
#[cfg(not(feature = "no_object"))]
this_type,
params,
body,
#[cfg(feature = "metadata")]
comments: comments.into_iter().collect(),
})
}
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_closure"))]
fn make_curry_from_externals(
&self,
state: &mut ParseState,
fn_expr: Expr,
externals: impl AsRef<[Ident]> + IntoIterator<Item = Ident>,
pos: Position,
) -> Expr {
if externals.as_ref().is_empty() {
return fn_expr;
}
let num_externals = externals.as_ref().len();
let mut args = FnArgsVec::with_capacity(externals.as_ref().len() + 1);
args.push(fn_expr);
args.extend(
externals
.as_ref()
.iter()
.cloned()
.map(|Ident { name, pos }| {
let (index, is_func) = self.access_var(state, &name, pos);
let idx = match index {
Some(n) if !is_func => u8::try_from(n.get()).ok().and_then(NonZeroU8::new),
_ => None,
};
#[cfg(not(feature = "no_module"))]
return Expr::Variable((index, name, <_>::default(), 0).into(), idx, pos);
#[cfg(feature = "no_module")]
return Expr::Variable((index, name).into(), idx, pos);
}),
);
let expr = FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: crate::ast::Namespace::NONE,
name: self.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY),
hashes: FnCallHashes::from_native_only(calc_fn_hash(
None,
crate::engine::KEYWORD_FN_PTR_CURRY,
num_externals + 1,
)),
args,
op_token: None,
capture_parent_scope: false,
}
.into_fn_call_expr(pos);
let mut statements = StaticVec::with_capacity(2);
statements.push(Stmt::Share(
externals
.into_iter()
.map(|var| {
let (index, _) = self.access_var(state, &var.name, var.pos);
(var, index)
})
.collect::<FnArgsVec<_>>()
.into(),
));
statements.push(Stmt::Expr(expr.into()));
Expr::Stmt(StmtBlock::new(statements, pos, Position::NONE).into())
}
#[cfg(not(feature = "no_function"))]
fn parse_anon_fn(
&self,
state: &mut ParseState,
settings: ParseSettings,
skip_parameters: bool,
) -> ParseResult<Expr> {
let new_state = &mut ParseState::new(
state.external_constants,
state.input,
state.tokenizer_control.clone(),
state.lib,
);
#[cfg(not(feature = "no_module"))]
{
new_state.global_imports.clone_from(&state.global_imports);
new_state.global_imports.extend(state.imports.clone());
}
let mut params_list = StaticVec::<ImmutableString>::new_const();
if !skip_parameters
&& new_state.input.next().unwrap().0 != Token::Or
&& !match_token(new_state.input, &Token::Pipe).0
{
loop {
match new_state.input.next().unwrap() {
(Token::Pipe, ..) => break,
(Token::Identifier(s), pos) => {
if params_list.iter().any(|p| p == &*s) {
return Err(
PERR::FnDuplicatedParam(String::new(), s.to_string()).into_err(pos)
);
}
let s = self.get_interned_string(*s);
new_state.stack.push(s.clone(), ());
params_list.push(s);
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::Pipe.into(),
"to close the parameters list of anonymous function or closure".into(),
)
.into_err(pos))
}
}
match new_state.input.next().unwrap() {
(Token::Pipe, ..) => break,
(Token::Comma, ..) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::Comma.into(),
"to separate the parameters of anonymous function".into(),
)
.into_err(pos))
}
}
}
}
#[cfg(not(feature = "no_closure"))]
let options = self.options & !LangOptions::STRICT_VAR; #[cfg(feature = "no_closure")]
let options = self.options | (settings.options & LangOptions::STRICT_VAR);
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 body = self.parse_stmt(new_state, new_settings.level_up()?)?;
let _ = new_settings; #[cfg(not(feature = "no_closure"))]
let (mut params, _externals) = {
let externals = std::mem::take(&mut new_state.external_vars);
let mut params = FnArgsVec::with_capacity(params_list.len() + externals.len());
params.extend(externals.iter().map(|Ident { name, .. }| name.clone()));
(params, externals)
};
#[cfg(feature = "no_closure")]
let (mut params, _externals) = (
FnArgsVec::with_capacity(params_list.len()),
ThinVec::<Ident>::new(),
);
let _ = new_state; params.append(&mut params_list);
let hasher = &mut get_hasher();
params.iter().for_each(|p| p.hash(hasher));
body.hash(hasher);
let hash = hasher.finish();
let fn_name = self.get_interned_string(make_anonymous_fn(hash));
let fn_def = Shared::new(ScriptFuncDef {
name: fn_name.clone(),
access: crate::FnAccess::Public,
#[cfg(not(feature = "no_object"))]
this_type: None,
params,
body: body.into(),
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
comments: <_>::default(),
});
let fn_ptr = crate::FnPtr {
name: fn_name,
curry: ThinVec::new(),
environ: None,
#[cfg(not(feature = "no_function"))]
fn_def: Some(fn_def.clone()),
};
let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), new_settings.pos);
#[cfg(not(feature = "no_closure"))]
for Ident { name, pos } in &_externals {
let (index, is_func) = self.access_var(state, name, *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))
{
return Err(PERR::VariableUndefined(name.to_string()).into_err(*pos));
}
}
let hash_script = calc_fn_hash(None, &fn_def.name, fn_def.params.len());
state.lib.insert(hash_script, fn_def);
#[cfg(not(feature = "no_closure"))]
let expr = self.make_curry_from_externals(state, expr, _externals, settings.pos);
Ok(expr)
}
pub(crate) fn parse_global_expr(
&self,
mut state: ParseState,
process_settings: impl FnOnce(&mut ParseSettings),
#[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel,
) -> ParseResult<AST> {
let options = self.options & !LangOptions::STMT_EXPR & !LangOptions::LOOP_EXPR;
let mut settings = ParseSettings {
level: 0,
flags: ParseSettingFlags::GLOBAL_LEVEL
| ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS,
options,
pos: Position::START,
#[cfg(not(feature = "unchecked"))]
max_expr_depth: self.max_expr_depth(),
};
process_settings(&mut settings);
let expr = self.parse_expr(&mut state, settings)?;
match state.input.peek().unwrap() {
(Token::EOF, ..) => (),
(token, pos) => return Err(LexError::UnexpectedInput(token.to_string()).into_err(*pos)),
}
let mut statements = StmtBlockContainer::new_const();
statements.push(Stmt::Expr(expr.into()));
#[cfg(not(feature = "no_optimize"))]
return Ok(self.optimize_into_ast(
state.external_constants,
statements,
#[cfg(not(feature = "no_function"))]
state.lib.values().cloned().collect::<Vec<_>>(),
optimization_level,
));
#[cfg(feature = "no_optimize")]
return Ok(AST::new(
statements,
#[cfg(not(feature = "no_function"))]
crate::Module::from(state.lib.values().cloned()),
));
}
fn parse_global_level(
&self,
state: &mut ParseState,
process_settings: impl FnOnce(&mut ParseSettings),
) -> ParseResult<(StmtBlockContainer, Vec<Shared<ScriptFuncDef>>)> {
let mut statements = StmtBlockContainer::new_const();
let mut settings = ParseSettings {
level: 0,
flags: ParseSettingFlags::GLOBAL_LEVEL,
options: self.options,
pos: Position::START,
#[cfg(not(feature = "unchecked"))]
max_expr_depth: self.max_expr_depth(),
};
process_settings(&mut settings);
while state.input.peek().unwrap().0 != Token::EOF {
let stmt = self.parse_stmt(state, settings)?;
if stmt.is_noop() {
continue;
}
let need_semicolon = !stmt.is_self_terminated();
statements.push(stmt);
match state.input.peek().unwrap() {
(Token::EOF, ..) => break,
(Token::SemiColon, ..) if need_semicolon => {
eat_token(state.input, &Token::SemiColon);
}
(Token::SemiColon, ..) if !need_semicolon => (),
_ if !need_semicolon => (),
(Token::LexError(err), pos) => return Err(err.clone().into_err(*pos)),
(.., pos) => {
return Err(PERR::MissingToken(
Token::SemiColon.into(),
"to terminate this statement".into(),
)
.into_err(*pos));
}
}
}
#[cfg(not(feature = "no_function"))]
let lib = state.lib.values().cloned().collect();
#[cfg(feature = "no_function")]
let lib = Vec::new();
Ok((statements, lib))
}
#[inline]
pub(crate) fn parse(
&self,
mut state: ParseState,
#[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel,
) -> ParseResult<AST> {
let (statements, _lib) = self.parse_global_level(&mut state, |_| {})?;
#[cfg(not(feature = "no_optimize"))]
return Ok(self.optimize_into_ast(
state.external_constants,
statements,
#[cfg(not(feature = "no_function"))]
_lib,
optimization_level,
));
#[cfg(feature = "no_optimize")]
return Ok(AST::new(
statements,
#[cfg(not(feature = "no_function"))]
{
let mut new_lib = crate::Module::new();
new_lib.extend(_lib);
new_lib
},
));
}
}