Add elvis operator for indexing.
This commit is contained in:
parent
b9cbeb65d6
commit
2b44778a5c
@ -19,7 +19,7 @@ Bug fixes
|
|||||||
Reserved Symbols
|
Reserved Symbols
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
* `?`, `??`, `?.` and `!.` are now reserved symbols.
|
* `?`, `??`, `?.`, `?[` and `!.` are now reserved symbols.
|
||||||
|
|
||||||
Deprecated API's
|
Deprecated API's
|
||||||
----------------
|
----------------
|
||||||
@ -29,7 +29,7 @@ Deprecated API's
|
|||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* The _Elvis operator_ (`?.`) is now supported for property access and method calls.
|
* The _Elvis operators_ (`?.` and `?[`) are now supported for property access, method calls and indexing.
|
||||||
* The _null-coalescing operator_ (`??`) is now supported to short-circuit `()` values.
|
* The _null-coalescing operator_ (`??`) is now supported to short-circuit `()` values.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
|
@ -411,6 +411,7 @@ pub enum Expr {
|
|||||||
///
|
///
|
||||||
/// ### Flags
|
/// ### Flags
|
||||||
///
|
///
|
||||||
|
/// [`NEGATED`][ASTFlags::NEGATED] = `?[` ... `]` (`[` ... `]` if unset)
|
||||||
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
|
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
|
||||||
Index(Box<BinaryExpr>, ASTFlags, Position),
|
Index(Box<BinaryExpr>, ASTFlags, Position),
|
||||||
/// lhs `&&` rhs
|
/// lhs `&&` rhs
|
||||||
@ -827,7 +828,7 @@ impl Expr {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Token::Period | Token::Elvis => return true,
|
Token::Period | Token::Elvis => return true,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => return true,
|
Token::LeftBracket | Token::QuestionBracket => return true,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,11 @@ impl Engine {
|
|||||||
match chain_type {
|
match chain_type {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
ChainType::Indexing => {
|
ChainType::Indexing => {
|
||||||
|
// Check for existence with the null conditional operator
|
||||||
|
if _parent_options.contains(ASTFlags::NEGATED) && target.is::<()>() {
|
||||||
|
return Ok((Dynamic::UNIT, false));
|
||||||
|
}
|
||||||
|
|
||||||
let pos = rhs.start_position();
|
let pos = rhs.start_position();
|
||||||
|
|
||||||
match rhs {
|
match rhs {
|
||||||
|
@ -940,6 +940,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x,..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
|
Expr::Dot(x,..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
|
||||||
|
|
||||||
|
// ()?[rhs]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(x, options, ..) if options.contains(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
|
||||||
|
state.set_dirty();
|
||||||
|
*expr = mem::take(&mut x.lhs);
|
||||||
|
}
|
||||||
// lhs[rhs]
|
// lhs[rhs]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
||||||
|
@ -641,6 +641,7 @@ impl Engine {
|
|||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
lib: &mut FnLib,
|
lib: &mut FnLib,
|
||||||
lhs: Expr,
|
lhs: Expr,
|
||||||
|
options: ASTFlags,
|
||||||
check_index_type: bool,
|
check_index_type: bool,
|
||||||
settings: ParseSettings,
|
settings: ParseSettings,
|
||||||
) -> ParseResult<Expr> {
|
) -> ParseResult<Expr> {
|
||||||
@ -756,29 +757,35 @@ impl Engine {
|
|||||||
// Any more indexing following?
|
// Any more indexing following?
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
// If another indexing level, right-bind it
|
// If another indexing level, right-bind it
|
||||||
(Token::LeftBracket, ..) => {
|
(Token::LeftBracket, ..) | (Token::QuestionBracket, ..) => {
|
||||||
|
let (token, pos) = input.next().expect(NEVER_ENDS);
|
||||||
let prev_pos = settings.pos;
|
let prev_pos = settings.pos;
|
||||||
settings.pos = eat_token(input, Token::LeftBracket);
|
settings.pos = pos;
|
||||||
// Recursively parse the indexing chain, right-binding each
|
// Recursively parse the indexing chain, right-binding each
|
||||||
let idx_expr = self.parse_index_chain(
|
let idx_expr = self.parse_index_chain(
|
||||||
input,
|
input,
|
||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
idx_expr,
|
idx_expr,
|
||||||
|
match token {
|
||||||
|
Token::LeftBracket => ASTFlags::NONE,
|
||||||
|
Token::QuestionBracket => ASTFlags::NEGATED,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
false,
|
false,
|
||||||
settings.level_up(),
|
settings.level_up(),
|
||||||
)?;
|
)?;
|
||||||
// Indexing binds to right
|
// Indexing binds to right
|
||||||
Ok(Expr::Index(
|
Ok(Expr::Index(
|
||||||
BinaryExpr { lhs, rhs: idx_expr }.into(),
|
BinaryExpr { lhs, rhs: idx_expr }.into(),
|
||||||
ASTFlags::NONE,
|
options,
|
||||||
prev_pos,
|
prev_pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
// Otherwise terminate the indexing chain
|
// Otherwise terminate the indexing chain
|
||||||
_ => Ok(Expr::Index(
|
_ => Ok(Expr::Index(
|
||||||
BinaryExpr { lhs, rhs: idx_expr }.into(),
|
BinaryExpr { lhs, rhs: idx_expr }.into(),
|
||||||
ASTFlags::BREAK,
|
options | ASTFlags::BREAK,
|
||||||
settings.pos,
|
settings.pos,
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
@ -1634,8 +1641,13 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// Indexing
|
// Indexing
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
(expr, Token::LeftBracket) => {
|
(expr, token @ Token::LeftBracket) | (expr, token @ Token::QuestionBracket) => {
|
||||||
self.parse_index_chain(input, state, lib, expr, true, settings.level_up())?
|
let opt = match token {
|
||||||
|
Token::LeftBracket => ASTFlags::NONE,
|
||||||
|
Token::QuestionBracket => ASTFlags::NEGATED,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
self.parse_index_chain(input, state, lib, expr, opt, true, settings.level_up())?
|
||||||
}
|
}
|
||||||
// Property access
|
// Property access
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
@ -421,9 +421,17 @@ pub enum Token {
|
|||||||
/// `.`
|
/// `.`
|
||||||
Period,
|
Period,
|
||||||
/// `?.`
|
/// `?.`
|
||||||
|
///
|
||||||
|
/// Reserved under the `no_object` feature.
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
Elvis,
|
Elvis,
|
||||||
/// `??`
|
/// `??`
|
||||||
DoubleQuestion,
|
DoubleQuestion,
|
||||||
|
/// `?[`
|
||||||
|
///
|
||||||
|
/// Reserved under the `no_object` feature.
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
QuestionBracket,
|
||||||
/// `..`
|
/// `..`
|
||||||
ExclusiveRange,
|
ExclusiveRange,
|
||||||
/// `..=`
|
/// `..=`
|
||||||
@ -580,8 +588,11 @@ impl Token {
|
|||||||
Underscore => "_",
|
Underscore => "_",
|
||||||
Comma => ",",
|
Comma => ",",
|
||||||
Period => ".",
|
Period => ".",
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
Elvis => "?.",
|
Elvis => "?.",
|
||||||
DoubleQuestion => "??",
|
DoubleQuestion => "??",
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
QuestionBracket => "?[",
|
||||||
ExclusiveRange => "..",
|
ExclusiveRange => "..",
|
||||||
InclusiveRange => "..=",
|
InclusiveRange => "..=",
|
||||||
MapStart => "#{",
|
MapStart => "#{",
|
||||||
@ -777,8 +788,11 @@ impl Token {
|
|||||||
"_" => Underscore,
|
"_" => Underscore,
|
||||||
"," => Comma,
|
"," => Comma,
|
||||||
"." => Period,
|
"." => Period,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
"?." => Elvis,
|
"?." => Elvis,
|
||||||
"??" => DoubleQuestion,
|
"??" => DoubleQuestion,
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
"?[" => QuestionBracket,
|
||||||
".." => ExclusiveRange,
|
".." => ExclusiveRange,
|
||||||
"..=" => InclusiveRange,
|
"..=" => InclusiveRange,
|
||||||
"#{" => MapStart,
|
"#{" => MapStart,
|
||||||
@ -892,6 +906,7 @@ impl Token {
|
|||||||
//Period |
|
//Period |
|
||||||
//Elvis |
|
//Elvis |
|
||||||
//DoubleQuestion |
|
//DoubleQuestion |
|
||||||
|
//QuestionBracket |
|
||||||
ExclusiveRange | // .. - is unary
|
ExclusiveRange | // .. - is unary
|
||||||
InclusiveRange | // ..= - is unary
|
InclusiveRange | // ..= - is unary
|
||||||
LeftBrace | // { -expr } - is unary
|
LeftBrace | // { -expr } - is unary
|
||||||
@ -999,12 +1014,18 @@ impl Token {
|
|||||||
match self {
|
match self {
|
||||||
LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus
|
LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus
|
||||||
| UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift
|
| UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift
|
||||||
| RightShift | SemiColon | Colon | DoubleColon | Comma | Period | Elvis
|
| RightShift | SemiColon | Colon | DoubleColon | Comma | Period | DoubleQuestion
|
||||||
| DoubleQuestion | ExclusiveRange | InclusiveRange | MapStart | Equals | LessThan
|
| ExclusiveRange | InclusiveRange | MapStart | Equals | LessThan | GreaterThan
|
||||||
| GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo
|
| LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo | Bang | Pipe
|
||||||
| Bang | Pipe | Or | XOr | Ampersand | And | PlusAssign | MinusAssign
|
| Or | XOr | Ampersand | And | PlusAssign | MinusAssign | MultiplyAssign
|
||||||
| MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign
|
| DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign
|
||||||
| OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => true,
|
| XOrAssign | ModuloAssign | PowerOfAssign => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Elvis => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
QuestionBracket => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -2047,12 +2068,28 @@ fn get_next_token_inner(
|
|||||||
|
|
||||||
('?', '.') => {
|
('?', '.') => {
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
return Some((Token::Elvis, start_pos));
|
return Some((
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Token::Elvis,
|
||||||
|
#[cfg(feature = "no_object")]
|
||||||
|
Token::Reserved("?.".into()),
|
||||||
|
start_pos,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
('?', '?') => {
|
('?', '?') => {
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
return Some((Token::DoubleQuestion, start_pos));
|
return Some((Token::DoubleQuestion, start_pos));
|
||||||
}
|
}
|
||||||
|
('?', '[') => {
|
||||||
|
eat_next(stream, pos);
|
||||||
|
return Some((
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Token::QuestionBracket,
|
||||||
|
#[cfg(feature = "no_index")]
|
||||||
|
Token::Reserved("?[".into()),
|
||||||
|
start_pos,
|
||||||
|
));
|
||||||
|
}
|
||||||
('?', ..) => return Some((Token::Reserved("?".into()), start_pos)),
|
('?', ..) => return Some((Token::Reserved("?".into()), start_pos)),
|
||||||
|
|
||||||
(ch, ..) if ch.is_whitespace() => (),
|
(ch, ..) if ch.is_whitespace() => (),
|
||||||
|
@ -405,3 +405,14 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_arrays_elvis() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<()>("let x = (); x?[2]")?, ());
|
||||||
|
|
||||||
|
engine.run("let x = (); x?[2] = 42")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user