This commit is contained in:
Stephen Chung 2022-11-30 14:11:09 +08:00
parent c509cc896d
commit fc4c8731f0
6 changed files with 166 additions and 119 deletions

View File

@ -23,6 +23,10 @@ Deprecated API's
Net features Net features
------------ ------------
### `!in`
* A new operator `!in` is added which maps to `!(... in ...)`.
### `Engine::call_fn_with_options` ### `Engine::call_fn_with_options`
* `Engine::call_fn_raw` is deprecated in favor of `Engine::call_fn_with_options` which allows setting options for the function call. * `Engine::call_fn_raw` is deprecated in favor of `Engine::call_fn_with_options` which allows setting options for the function call.

View File

@ -90,12 +90,15 @@ impl fmt::Debug for ParseState<'_, '_> {
.field("global", &self.global) .field("global", &self.global)
.field("stack", &self.stack) .field("stack", &self.stack)
.field("block_stack_len", &self.block_stack_len); .field("block_stack_len", &self.block_stack_len);
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
f.field("external_vars", &self.external_vars) f.field("external_vars", &self.external_vars)
.field("allow_capture", &self.allow_capture); .field("allow_capture", &self.allow_capture);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
f.field("imports", &self.imports) f.field("imports", &self.imports)
.field("global_imports", &self.global_imports); .field("global_imports", &self.global_imports);
f.finish() f.finish()
} }
} }
@ -140,24 +143,24 @@ impl<'e, 's> ParseState<'e, 's> {
pub fn find_var(&self, name: &str) -> (usize, bool) { pub fn find_var(&self, name: &str) -> (usize, bool) {
let mut hit_barrier = false; let mut hit_barrier = false;
( let index = self
self.stack .stack
.as_deref() .as_deref()
.into_iter() .into_iter()
.flat_map(|s| s.iter_rev_raw()) .flat_map(|s| s.iter_rev_raw())
.enumerate() .enumerate()
.find(|&(.., (n, ..))| { .find(|&(.., (n, ..))| {
if n == SCOPE_SEARCH_BARRIER_MARKER { if n == SCOPE_SEARCH_BARRIER_MARKER {
// Do not go beyond the barrier // Do not go beyond the barrier
hit_barrier = true; hit_barrier = true;
false false
} else { } else {
n == name n == name
} }
}) })
.map_or(0, |(i, ..)| i + 1), .map_or(0, |(i, ..)| i + 1);
hit_barrier,
) (index, hit_barrier)
} }
/// Find explicitly declared variable by name in the [`ParseState`], searching in reverse order. /// Find explicitly declared variable by name in the [`ParseState`], searching in reverse order.
@ -300,6 +303,7 @@ bitflags! {
const CLOSURE_SCOPE = 0b0000_0100; const CLOSURE_SCOPE = 0b0000_0100;
/// Is the construct being parsed located inside a breakable loop? /// Is the construct being parsed located inside a breakable loop?
const BREAKABLE = 0b0000_1000; const BREAKABLE = 0b0000_1000;
/// Disallow statements in blocks? /// Disallow statements in blocks?
const DISALLOW_STATEMENTS_IN_BLOCKS = 0b0001_0000; const DISALLOW_STATEMENTS_IN_BLOCKS = 0b0001_0000;
/// Disallow unquoted map properties? /// Disallow unquoted map properties?
@ -869,19 +873,15 @@ impl Engine {
let (token, pos) = input.next().expect(NEVER_ENDS); let (token, pos) = input.next().expect(NEVER_ENDS);
let prev_pos = settings.pos; let prev_pos = settings.pos;
settings.pos = pos; settings.pos = pos;
let settings = settings.level_up()?;
// Recursively parse the indexing chain, right-binding each // Recursively parse the indexing chain, right-binding each
let options = match token {
Token::LeftBracket => ASTFlags::NONE,
Token::QuestionBracket => ASTFlags::NEGATED,
_ => unreachable!("`[` or `?[`"),
};
let idx_expr = self.parse_index_chain( let idx_expr = self.parse_index_chain(
input, input, state, lib, idx_expr, options, false, settings,
state,
lib,
idx_expr,
match token {
Token::LeftBracket => ASTFlags::NONE,
Token::QuestionBracket => ASTFlags::NEGATED,
_ => unreachable!("`[` or `?[`"),
},
false,
settings.level_up()?,
)?; )?;
// Indexing binds to right // Indexing binds to right
Ok(Expr::Index( Ok(Expr::Index(
@ -1437,29 +1437,29 @@ impl Engine {
.extend(state.imports.as_deref().into_iter().flatten().cloned()); .extend(state.imports.as_deref().into_iter().flatten().cloned());
} }
// Brand new options
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let options = self.options & !LangOptions::STRICT_VAR; // A capturing closure can access variables not defined locally let options = self.options & !LangOptions::STRICT_VAR; // a capturing closure can access variables not defined locally, so turn off Strict Variables mode
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
let options = self.options | (settings.options & LangOptions::STRICT_VAR); let options = self.options | (settings.options & LangOptions::STRICT_VAR);
let flags = (settings.flags // Brand new flags, turn on function scope
& !ParseSettingFlags::GLOBAL_LEVEL let flags = ParseSettingFlags::FN_SCOPE
& ParseSettingFlags::BREAKABLE) | (settings.flags
| ParseSettingFlags::FN_SCOPE; & (ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES
| ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS));
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let flags = flags | ParseSettingFlags::CLOSURE_SCOPE; let flags = flags | ParseSettingFlags::CLOSURE_SCOPE; // turn on closure scope
let new_settings = ParseSettings { let new_settings = ParseSettings {
level: 0,
flags, flags,
options, options,
#[cfg(not(feature = "unchecked"))]
max_expr_depth: self.max_function_expr_depth(),
..settings ..settings
}; };
let result = self.parse_anon_fn(input, new_state, state, lib, new_settings); let result =
self.parse_anon_fn(input, new_state, state, lib, new_settings.level_up()?);
// Restore the strings interner by swapping it back // Restore the strings interner by swapping it back
std::mem::swap(state.interned_strings, new_state.interned_strings); std::mem::swap(state.interned_strings, new_state.interned_strings);
@ -1582,8 +1582,8 @@ impl Engine {
.and_then(|m| m.get_key_value(&**key)) .and_then(|m| m.get_key_value(&**key))
.unwrap(); .unwrap();
let (.., pos) = input.next().expect(NEVER_ENDS); let (.., pos) = input.next().expect(NEVER_ENDS);
let settings2 = settings.level_up()?; let settings = settings.level_up()?;
self.parse_custom_syntax(input, state, lib, settings2, key, syntax, pos)? self.parse_custom_syntax(input, state, lib, settings, key, syntax, pos)?
} }
// Identifier // Identifier
@ -1789,15 +1789,8 @@ impl Engine {
Token::QuestionBracket => ASTFlags::NEGATED, Token::QuestionBracket => ASTFlags::NEGATED,
_ => unreachable!("`[` or `?[`"), _ => unreachable!("`[` or `?[`"),
}; };
self.parse_index_chain( let settings = settings.level_up()?;
input, self.parse_index_chain(input, state, lib, expr, opt, true, settings)?
state,
lib,
expr,
opt,
true,
settings.level_up()?,
)?
} }
// Property access // Property access
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -1850,12 +1843,11 @@ impl Engine {
{ {
let root = namespace.root(); let root = namespace.root();
let index = state.find_module(root); let index = state.find_module(root);
let is_global = false;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let is_global = root == crate::engine::KEYWORD_GLOBAL; let is_global = 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) if settings.has_option(LangOptions::STRICT_VAR)
&& index.is_none() && index.is_none()
@ -2142,14 +2134,8 @@ impl Engine {
match (lhs, rhs) { match (lhs, rhs) {
// lhs[idx_expr].rhs // lhs[idx_expr].rhs
(Expr::Index(mut x, options, pos), rhs) => { (Expr::Index(mut x, options, pos), rhs) => {
x.rhs = Self::make_dot_expr( let options = options | parent_options;
state, x.rhs = Self::make_dot_expr(state, x.rhs, rhs, options, op_flags, op_pos)?;
x.rhs,
rhs,
options | parent_options,
op_flags,
op_pos,
)?;
Ok(Expr::Index(x, ASTFlags::NONE, pos)) Ok(Expr::Index(x, ASTFlags::NONE, pos))
} }
// lhs.module::id - syntax error // lhs.module::id - syntax error
@ -2403,7 +2389,7 @@ impl Engine {
let lhs = op_base.args.pop().unwrap(); let lhs = op_base.args.pop().unwrap();
Expr::Coalesce(BinaryExpr { lhs, rhs }.into(), pos) Expr::Coalesce(BinaryExpr { lhs, rhs }.into(), pos)
} }
Token::In => { Token::In | Token::NotIn => {
// Swap the arguments // Swap the arguments
let lhs = op_base.args.remove(0); let lhs = op_base.args.remove(0);
let pos = lhs.start_position(); let pos = lhs.start_position();
@ -2413,7 +2399,26 @@ impl Engine {
// Convert into a call to `contains` // Convert into a call to `contains`
op_base.hashes = calc_fn_hash(None, OP_CONTAINS, 2).into(); op_base.hashes = calc_fn_hash(None, OP_CONTAINS, 2).into();
op_base.name = state.get_interned_string(OP_CONTAINS); op_base.name = state.get_interned_string(OP_CONTAINS);
op_base.into_fn_call_expr(pos) let fn_call = op_base.into_fn_call_expr(pos);
if op_token == Token::In {
fn_call
} else {
// Put a `!` call in front
let op = Token::Bang.literal_syntax();
let mut args = StaticVec::new_const();
args.push(fn_call);
let not_base = FnCallExpr {
namespace: Namespace::NONE,
name: state.get_interned_string(op),
hashes: FnCallHashes::from_native(calc_fn_hash(None, op, 1).into()),
args,
op_token: Token::Bang,
capture_parent_scope: false,
};
not_base.into_fn_call_expr(pos)
}
} }
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
@ -2727,10 +2732,13 @@ impl Engine {
) -> ParseResult<Stmt> { ) -> ParseResult<Stmt> {
// do ... // do ...
let mut settings = settings; let mut settings = settings;
let orig_breakable = settings.flags.contains(ParseSettingFlags::BREAKABLE);
settings.flags |= ParseSettingFlags::BREAKABLE;
settings.pos = eat_token(input, Token::Do); settings.pos = eat_token(input, Token::Do);
// do { body } [while|until] guard // do { body } [while|until] guard
settings.flags |= ParseSettingFlags::BREAKABLE;
let body = self.parse_block(input, state, lib, settings.level_up()?)?; let body = self.parse_block(input, state, lib, settings.level_up()?)?;
let negated = match input.next().expect(NEVER_ENDS) { let negated = match input.next().expect(NEVER_ENDS) {
@ -2744,7 +2752,9 @@ impl Engine {
} }
}; };
settings.flags &= !ParseSettingFlags::BREAKABLE; if !orig_breakable {
settings.flags &= !ParseSettingFlags::BREAKABLE;
}
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let guard = self let guard = self
@ -3284,8 +3294,10 @@ impl Engine {
.extend(state.imports.as_deref().into_iter().flatten().cloned()); .extend(state.imports.as_deref().into_iter().flatten().cloned());
} }
// Brand new options
let options = self.options | (settings.options & LangOptions::STRICT_VAR); let options = self.options | (settings.options & LangOptions::STRICT_VAR);
// Brand new flags, turn on function scope
let flags = ParseSettingFlags::FN_SCOPE let flags = ParseSettingFlags::FN_SCOPE
| (settings.flags | (settings.flags
& ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES); & ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES);
@ -3518,7 +3530,7 @@ impl Engine {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: StaticVec<SmartString>, comments: StaticVec<SmartString>,
) -> ParseResult<ScriptFnDef> { ) -> ParseResult<ScriptFnDef> {
let mut settings = settings; let settings = settings;
let (token, pos) = input.next().expect(NEVER_ENDS); let (token, pos) = input.next().expect(NEVER_ENDS);
@ -3585,10 +3597,7 @@ impl Engine {
// Parse function body // Parse function body
let body = match input.peek().expect(NEVER_ENDS) { let body = match input.peek().expect(NEVER_ENDS) {
(Token::LeftBrace, ..) => { (Token::LeftBrace, ..) => self.parse_block(input, state, lib, settings.level_up()?)?,
settings.flags &= !ParseSettingFlags::BREAKABLE;
self.parse_block(input, state, lib, settings.level_up()?)?
}
(.., pos) => return Err(PERR::FnMissingBody(name.into()).into_err(*pos)), (.., pos) => return Err(PERR::FnMissingBody(name.into()).into_err(*pos)),
} }
.into(); .into();
@ -3685,7 +3694,7 @@ impl Engine {
lib: &mut FnLib, lib: &mut FnLib,
settings: ParseSettings, settings: ParseSettings,
) -> ParseResult<(Expr, ScriptFnDef)> { ) -> ParseResult<(Expr, ScriptFnDef)> {
let mut settings = settings; let settings = settings;
let mut params_list = StaticVec::<ImmutableString>::new_const(); let mut params_list = StaticVec::<ImmutableString>::new_const();
if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 { if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 {
@ -3732,7 +3741,6 @@ impl Engine {
} }
// Parse function body // Parse function body
settings.flags &= !ParseSettingFlags::BREAKABLE;
let body = self.parse_stmt(input, state, lib, settings.level_up()?)?; let body = self.parse_stmt(input, state, lib, settings.level_up()?)?;
// External variables may need to be processed in a consistent order, // External variables may need to be processed in a consistent order,

View File

@ -174,6 +174,8 @@ pub enum Token {
For, For,
/// `in` /// `in`
In, In,
/// `!in`
NotIn,
/// `<` /// `<`
LessThan, LessThan,
/// `>` /// `>`
@ -385,6 +387,7 @@ impl Token {
Loop => "loop", Loop => "loop",
For => "for", For => "for",
In => "in", In => "in",
NotIn => "!in",
LessThan => "<", LessThan => "<",
GreaterThan => ">", GreaterThan => ">",
Bang => "!", Bang => "!",
@ -439,37 +442,43 @@ impl Token {
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn is_op_assignment(&self) -> bool { pub const fn is_op_assignment(&self) -> bool {
#[allow(clippy::enum_glob_use)]
use Token::*;
matches!( matches!(
self, self,
Self::PlusAssign PlusAssign
| Self::MinusAssign | MinusAssign
| Self::MultiplyAssign | MultiplyAssign
| Self::DivideAssign | DivideAssign
| Self::LeftShiftAssign | LeftShiftAssign
| Self::RightShiftAssign | RightShiftAssign
| Self::ModuloAssign | ModuloAssign
| Self::PowerOfAssign | PowerOfAssign
| Self::AndAssign | AndAssign
| Self::OrAssign | OrAssign
| Self::XOrAssign | XOrAssign
) )
} }
/// Get the corresponding operator of the token if it is an op-assignment operator. /// Get the corresponding operator of the token if it is an op-assignment operator.
#[must_use] #[must_use]
pub const fn get_base_op_from_assignment(&self) -> Option<Self> { pub const fn get_base_op_from_assignment(&self) -> Option<Self> {
#[allow(clippy::enum_glob_use)]
use Token::*;
Some(match self { Some(match self {
Self::PlusAssign => Self::Plus, PlusAssign => Plus,
Self::MinusAssign => Self::Minus, MinusAssign => Minus,
Self::MultiplyAssign => Self::Multiply, MultiplyAssign => Multiply,
Self::DivideAssign => Self::Divide, DivideAssign => Divide,
Self::LeftShiftAssign => Self::LeftShift, LeftShiftAssign => LeftShift,
Self::RightShiftAssign => Self::RightShift, RightShiftAssign => RightShift,
Self::ModuloAssign => Self::Modulo, ModuloAssign => Modulo,
Self::PowerOfAssign => Self::PowerOf, PowerOfAssign => PowerOf,
Self::AndAssign => Self::Ampersand, AndAssign => Ampersand,
Self::OrAssign => Self::Pipe, OrAssign => Pipe,
Self::XOrAssign => Self::XOr, XOrAssign => XOr,
_ => return None, _ => return None,
}) })
} }
@ -478,37 +487,42 @@ impl Token {
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn has_op_assignment(&self) -> bool { pub const fn has_op_assignment(&self) -> bool {
#[allow(clippy::enum_glob_use)]
use Token::*;
matches!( matches!(
self, self,
Self::Plus Plus | Minus
| Self::Minus | Multiply
| Self::Multiply | Divide
| Self::Divide | LeftShift
| Self::LeftShift | RightShift
| Self::RightShift | Modulo
| Self::Modulo | PowerOf
| Self::PowerOf | Ampersand
| Self::Ampersand | Pipe
| Self::Pipe | XOr
| Self::XOr
) )
} }
/// Get the corresponding op-assignment operator of the token. /// Get the corresponding op-assignment operator of the token.
#[must_use] #[must_use]
pub const fn convert_to_op_assignment(&self) -> Option<Self> { pub const fn convert_to_op_assignment(&self) -> Option<Self> {
#[allow(clippy::enum_glob_use)]
use Token::*;
Some(match self { Some(match self {
Self::Plus => Self::PlusAssign, Plus => PlusAssign,
Self::Minus => Self::MinusAssign, Minus => MinusAssign,
Self::Multiply => Self::MultiplyAssign, Multiply => MultiplyAssign,
Self::Divide => Self::DivideAssign, Divide => DivideAssign,
Self::LeftShift => Self::LeftShiftAssign, LeftShift => LeftShiftAssign,
Self::RightShift => Self::RightShiftAssign, RightShift => RightShiftAssign,
Self::Modulo => Self::ModuloAssign, Modulo => ModuloAssign,
Self::PowerOf => Self::PowerOfAssign, PowerOf => PowerOfAssign,
Self::Ampersand => Self::AndAssign, Ampersand => AndAssign,
Self::Pipe => Self::OrAssign, Pipe => OrAssign,
Self::XOr => Self::XOrAssign, XOr => XOrAssign,
_ => return None, _ => return None,
}) })
} }
@ -560,6 +574,7 @@ impl Token {
"loop" => Loop, "loop" => Loop,
"for" => For, "for" => For,
"in" => In, "in" => In,
"!in" => NotIn,
"<" => LessThan, "<" => LessThan,
">" => GreaterThan, ">" => GreaterThan,
"!" => Bang, "!" => Bang,
@ -700,6 +715,7 @@ impl Token {
While | While |
Until | Until |
In | In |
NotIn |
And | And |
AndAssign | AndAssign |
Or | Or |
@ -731,7 +747,7 @@ impl Token {
EqualsTo | NotEqualsTo => 90, EqualsTo | NotEqualsTo => 90,
In => 110, In | NotIn => 110,
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130, LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130,
@ -1812,6 +1828,15 @@ fn get_next_token_inner(
} }
('>', ..) => return Some((Token::GreaterThan, start_pos)), ('>', ..) => return Some((Token::GreaterThan, start_pos)),
('!', 'i') => {
eat_next(stream, pos);
if stream.peek_next() == Some('n') {
eat_next(stream, pos);
return Some((Token::NotIn, start_pos));
}
stream.unget('i');
return Some((Token::Bang, start_pos));
}
('!', '=') => { ('!', '=') => {
eat_next(stream, pos); eat_next(stream, pos);

View File

@ -21,6 +21,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-1]")?, 3); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-1]")?, 3);
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-3]")?, 1); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[-3]")?, 1);
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?); assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?);
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 42 !in y")?);
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y += 4; y[3]")?, 4); assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y += 4; y[3]")?, 4);
assert_eq!( assert_eq!(
engine.eval::<INT>("let y = [1, 2, 3]; pad(y, 5, 42); len(y)")?, engine.eval::<INT>("let y = [1, 2, 3]; pad(y, 5, 42); len(y)")?,

View File

@ -17,7 +17,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
.compile(r#"let x = "hello, world!";"#) .compile(r#"let x = "hello, world!";"#)
.expect_err("should error") .expect_err("should error")
.err_type(), .err_type(),
ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10) ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10)
); );
assert_eq!( assert_eq!(
@ -25,7 +25,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
.compile(r#"let x = "朝に紅顔、暮に白骨";"#) .compile(r#"let x = "朝に紅顔、暮に白骨";"#)
.expect_err("should error") .expect_err("should error")
.err_type(), .err_type(),
ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10) ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10)
); );
assert!(matches!( assert!(matches!(

View File

@ -34,6 +34,15 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
ParseErrorType::LoopBreak ParseErrorType::LoopBreak
); );
#[cfg(not(feature = "no_function"))]
assert_eq!(
*engine
.compile("loop { let f = || { break; } }")
.expect_err("should error")
.err_type(),
ParseErrorType::LoopBreak
);
assert_eq!( assert_eq!(
*engine *engine
.compile("let x = 0; if x > 0 { continue; }") .compile("let x = 0; if x > 0 { continue; }")