Add !in.
This commit is contained in:
parent
c509cc896d
commit
fc4c8731f0
@ -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.
|
||||||
|
152
src/parser.rs
152
src/parser.rs
@ -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,
|
||||||
|
115
src/tokenizer.rs
115
src/tokenizer.rs
@ -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);
|
||||||
|
|
||||||
|
@ -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)")?,
|
||||||
|
@ -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!(
|
||||||
|
@ -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; }")
|
||||||
|
Loading…
Reference in New Issue
Block a user