Add elvis operator for indexing.
This commit is contained in:
parent
b9cbeb65d6
commit
2b44778a5c
@ -19,7 +19,7 @@ Bug fixes
|
||||
Reserved Symbols
|
||||
----------------
|
||||
|
||||
* `?`, `??`, `?.` and `!.` are now reserved symbols.
|
||||
* `?`, `??`, `?.`, `?[` and `!.` are now reserved symbols.
|
||||
|
||||
Deprecated API's
|
||||
----------------
|
||||
@ -29,7 +29,7 @@ Deprecated API's
|
||||
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.
|
||||
|
||||
Enhancements
|
||||
|
@ -411,6 +411,7 @@ pub enum Expr {
|
||||
///
|
||||
/// ### Flags
|
||||
///
|
||||
/// [`NEGATED`][ASTFlags::NEGATED] = `?[` ... `]` (`[` ... `]` if unset)
|
||||
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
|
||||
Index(Box<BinaryExpr>, ASTFlags, Position),
|
||||
/// lhs `&&` rhs
|
||||
@ -827,7 +828,7 @@ impl Expr {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Token::Period | Token::Elvis => return true,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Token::LeftBracket => return true,
|
||||
Token::LeftBracket | Token::QuestionBracket => return true,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,11 @@ impl Engine {
|
||||
match chain_type {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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();
|
||||
|
||||
match rhs {
|
||||
|
@ -940,6 +940,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
||||
|
@ -641,6 +641,7 @@ impl Engine {
|
||||
state: &mut ParseState,
|
||||
lib: &mut FnLib,
|
||||
lhs: Expr,
|
||||
options: ASTFlags,
|
||||
check_index_type: bool,
|
||||
settings: ParseSettings,
|
||||
) -> ParseResult<Expr> {
|
||||
@ -756,29 +757,35 @@ impl Engine {
|
||||
// Any more indexing following?
|
||||
match input.peek().expect(NEVER_ENDS) {
|
||||
// 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;
|
||||
settings.pos = eat_token(input, Token::LeftBracket);
|
||||
settings.pos = pos;
|
||||
// Recursively parse the indexing chain, right-binding each
|
||||
let idx_expr = self.parse_index_chain(
|
||||
input,
|
||||
state,
|
||||
lib,
|
||||
idx_expr,
|
||||
match token {
|
||||
Token::LeftBracket => ASTFlags::NONE,
|
||||
Token::QuestionBracket => ASTFlags::NEGATED,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
false,
|
||||
settings.level_up(),
|
||||
)?;
|
||||
// Indexing binds to right
|
||||
Ok(Expr::Index(
|
||||
BinaryExpr { lhs, rhs: idx_expr }.into(),
|
||||
ASTFlags::NONE,
|
||||
options,
|
||||
prev_pos,
|
||||
))
|
||||
}
|
||||
// Otherwise terminate the indexing chain
|
||||
_ => Ok(Expr::Index(
|
||||
BinaryExpr { lhs, rhs: idx_expr }.into(),
|
||||
ASTFlags::BREAK,
|
||||
options | ASTFlags::BREAK,
|
||||
settings.pos,
|
||||
)),
|
||||
}
|
||||
@ -1634,8 +1641,13 @@ impl Engine {
|
||||
}
|
||||
// Indexing
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
(expr, Token::LeftBracket) => {
|
||||
self.parse_index_chain(input, state, lib, expr, true, settings.level_up())?
|
||||
(expr, token @ Token::LeftBracket) | (expr, token @ Token::QuestionBracket) => {
|
||||
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
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
|
@ -421,9 +421,17 @@ pub enum Token {
|
||||
/// `.`
|
||||
Period,
|
||||
/// `?.`
|
||||
///
|
||||
/// Reserved under the `no_object` feature.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Elvis,
|
||||
/// `??`
|
||||
DoubleQuestion,
|
||||
/// `?[`
|
||||
///
|
||||
/// Reserved under the `no_object` feature.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
QuestionBracket,
|
||||
/// `..`
|
||||
ExclusiveRange,
|
||||
/// `..=`
|
||||
@ -580,8 +588,11 @@ impl Token {
|
||||
Underscore => "_",
|
||||
Comma => ",",
|
||||
Period => ".",
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Elvis => "?.",
|
||||
DoubleQuestion => "??",
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
QuestionBracket => "?[",
|
||||
ExclusiveRange => "..",
|
||||
InclusiveRange => "..=",
|
||||
MapStart => "#{",
|
||||
@ -777,8 +788,11 @@ impl Token {
|
||||
"_" => Underscore,
|
||||
"," => Comma,
|
||||
"." => Period,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
"?." => Elvis,
|
||||
"??" => DoubleQuestion,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
"?[" => QuestionBracket,
|
||||
".." => ExclusiveRange,
|
||||
"..=" => InclusiveRange,
|
||||
"#{" => MapStart,
|
||||
@ -892,6 +906,7 @@ impl Token {
|
||||
//Period |
|
||||
//Elvis |
|
||||
//DoubleQuestion |
|
||||
//QuestionBracket |
|
||||
ExclusiveRange | // .. - is unary
|
||||
InclusiveRange | // ..= - is unary
|
||||
LeftBrace | // { -expr } - is unary
|
||||
@ -999,12 +1014,18 @@ impl Token {
|
||||
match self {
|
||||
LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus
|
||||
| UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift
|
||||
| RightShift | SemiColon | Colon | DoubleColon | Comma | Period | Elvis
|
||||
| DoubleQuestion | ExclusiveRange | InclusiveRange | MapStart | Equals | LessThan
|
||||
| GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo
|
||||
| Bang | Pipe | Or | XOr | Ampersand | And | PlusAssign | MinusAssign
|
||||
| MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign
|
||||
| OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => true,
|
||||
| RightShift | SemiColon | Colon | DoubleColon | Comma | Period | DoubleQuestion
|
||||
| ExclusiveRange | InclusiveRange | MapStart | Equals | LessThan | GreaterThan
|
||||
| LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo | Bang | Pipe
|
||||
| Or | XOr | Ampersand | And | PlusAssign | MinusAssign | MultiplyAssign
|
||||
| DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign
|
||||
| XOrAssign | ModuloAssign | PowerOfAssign => true,
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Elvis => true,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
QuestionBracket => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
@ -2047,12 +2068,28 @@ fn get_next_token_inner(
|
||||
|
||||
('?', '.') => {
|
||||
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);
|
||||
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)),
|
||||
|
||||
(ch, ..) if ch.is_whitespace() => (),
|
||||
|
@ -405,3 +405,14 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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