Support Elvis operator.
This commit is contained in:
parent
206318e14c
commit
0f1e51b1c9
@ -26,6 +26,11 @@ Deprecated API's
|
||||
|
||||
* `FnPtr::num_curried` is deprecated in favor of `FnPtr::curry().len()`.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* The _Elvis operator_ (`?.`) is now supported for property access and method calls.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
|
@ -400,18 +400,18 @@ pub enum Expr {
|
||||
Stmt(Box<StmtBlock>),
|
||||
/// func `(` expr `,` ... `)`
|
||||
FnCall(Box<FnCallExpr>, Position),
|
||||
/// lhs `.` rhs
|
||||
/// lhs `.` rhs | lhs `?.` rhs
|
||||
///
|
||||
/// ### Flags
|
||||
///
|
||||
/// No flags are defined at this time. Use [`NONE`][ASTFlags::NONE].
|
||||
/// [`NEGATED`][ASTFlags::NEGATED] = `?.` (`.` if unset)
|
||||
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
|
||||
Dot(Box<BinaryExpr>, ASTFlags, Position),
|
||||
/// lhs `[` rhs `]`
|
||||
///
|
||||
/// ### Flags
|
||||
///
|
||||
/// [`NONE`][ASTFlags::NONE] = recurse into the indexing chain
|
||||
/// [`BREAK`][ASTFlags::BREAK] = terminate the indexing chain
|
||||
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
|
||||
Index(Box<BinaryExpr>, ASTFlags, Position),
|
||||
/// lhs `&&` rhs
|
||||
And(Box<BinaryExpr>, Position),
|
||||
@ -484,26 +484,37 @@ impl fmt::Debug for Expr {
|
||||
f.debug_list().entries(x.iter()).finish()
|
||||
}
|
||||
Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
|
||||
Self::Index(x, term, pos) => {
|
||||
Self::Index(x, options, pos) => {
|
||||
if !pos.is_none() {
|
||||
display_pos = format!(" @ {:?}", pos);
|
||||
}
|
||||
|
||||
f.debug_struct("Index")
|
||||
.field("lhs", &x.lhs)
|
||||
.field("rhs", &x.rhs)
|
||||
.field("terminate", term)
|
||||
.finish()
|
||||
let mut f = f.debug_struct("Index");
|
||||
|
||||
f.field("lhs", &x.lhs).field("rhs", &x.rhs);
|
||||
if !options.is_empty() {
|
||||
f.field("options", options);
|
||||
}
|
||||
f.finish()
|
||||
}
|
||||
Self::Dot(x, _, pos) | Self::And(x, pos) | Self::Or(x, pos) => {
|
||||
Self::Dot(x, options, pos) => {
|
||||
if !pos.is_none() {
|
||||
display_pos = format!(" @ {:?}", pos);
|
||||
}
|
||||
|
||||
let mut f = f.debug_struct("Dot");
|
||||
|
||||
f.field("lhs", &x.lhs).field("rhs", &x.rhs);
|
||||
if !options.is_empty() {
|
||||
f.field("options", options);
|
||||
}
|
||||
f.finish()
|
||||
}
|
||||
Self::And(x, pos) | Self::Or(x, pos) => {
|
||||
let op_name = match self {
|
||||
Self::Dot(..) => "Dot",
|
||||
Self::And(..) => "And",
|
||||
Self::Or(..) => "Or",
|
||||
expr => unreachable!(
|
||||
"Self::Dot or Self::And or Self::Or expected but gets {:?}",
|
||||
expr
|
||||
),
|
||||
expr => unreachable!("Self::And or Self::Or expected but gets {:?}", expr),
|
||||
};
|
||||
|
||||
if !pos.is_none() {
|
||||
@ -802,7 +813,7 @@ impl Expr {
|
||||
pub const fn is_valid_postfix(&self, token: &Token) -> bool {
|
||||
match token {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Token::Period => return true,
|
||||
Token::Period | Token::Elvis => return true,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Token::LeftBracket => return true,
|
||||
_ => (),
|
||||
|
@ -186,6 +186,11 @@ impl Engine {
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
ChainType::Dotting => {
|
||||
// Check for existence with the Elvis operator
|
||||
if _parent_options.contains(ASTFlags::NEGATED) && target.is::<()>() {
|
||||
return Ok((Dynamic::UNIT, false));
|
||||
}
|
||||
|
||||
match rhs {
|
||||
// xxx.fn_name(arg_expr_list)
|
||||
Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
|
||||
|
@ -912,9 +912,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
// ()?.rhs
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(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_object"))]
|
||||
Expr::Dot(x,_, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
||||
Expr::Dot(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
||||
// map.string
|
||||
(Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
|
||||
let prop = p.2.as_str();
|
||||
@ -932,7 +938,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||
}
|
||||
// ....lhs.rhs
|
||||
#[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); }
|
||||
|
||||
// lhs[rhs]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
|
@ -1639,7 +1639,7 @@ impl Engine {
|
||||
}
|
||||
// Property access
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(expr, Token::Period) => {
|
||||
(expr, op @ Token::Period) | (expr, op @ Token::Elvis) => {
|
||||
// Expression after dot must start with an identifier
|
||||
match input.peek().expect(NEVER_ENDS) {
|
||||
(Token::Identifier(..), ..) => {
|
||||
@ -1654,7 +1654,12 @@ impl Engine {
|
||||
}
|
||||
|
||||
let rhs = self.parse_primary(input, state, lib, settings.level_up())?;
|
||||
Self::make_dot_expr(state, expr, ASTFlags::NONE, rhs, tail_pos)?
|
||||
let op_flags = match op {
|
||||
Token::Period => ASTFlags::NONE,
|
||||
Token::Elvis => ASTFlags::NEGATED,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Self::make_dot_expr(state, expr, rhs, ASTFlags::NONE, op_flags, tail_pos)?
|
||||
}
|
||||
// Unknown postfix operator
|
||||
(expr, token) => unreachable!(
|
||||
@ -1959,14 +1964,22 @@ impl Engine {
|
||||
fn make_dot_expr(
|
||||
state: &mut ParseState,
|
||||
lhs: Expr,
|
||||
parent_options: ASTFlags,
|
||||
rhs: Expr,
|
||||
parent_options: ASTFlags,
|
||||
op_flags: ASTFlags,
|
||||
op_pos: Position,
|
||||
) -> ParseResult<Expr> {
|
||||
match (lhs, rhs) {
|
||||
// lhs[idx_expr].rhs
|
||||
(Expr::Index(mut x, options, pos), rhs) => {
|
||||
x.rhs = Self::make_dot_expr(state, x.rhs, options | parent_options, rhs, op_pos)?;
|
||||
x.rhs = Self::make_dot_expr(
|
||||
state,
|
||||
x.rhs,
|
||||
rhs,
|
||||
options | parent_options,
|
||||
op_flags,
|
||||
op_pos,
|
||||
)?;
|
||||
Ok(Expr::Index(x, ASTFlags::NONE, pos))
|
||||
}
|
||||
// lhs.module::id - syntax error
|
||||
@ -1977,16 +1990,12 @@ impl Engine {
|
||||
// lhs.id
|
||||
(lhs, var_expr @ Expr::Variable(..)) => {
|
||||
let rhs = var_expr.into_property(state);
|
||||
Ok(Expr::Dot(
|
||||
BinaryExpr { lhs, rhs }.into(),
|
||||
ASTFlags::NONE,
|
||||
op_pos,
|
||||
))
|
||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||
}
|
||||
// lhs.prop
|
||||
(lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot(
|
||||
BinaryExpr { lhs, rhs: prop }.into(),
|
||||
ASTFlags::NONE,
|
||||
op_flags,
|
||||
op_pos,
|
||||
)),
|
||||
// lhs.nnn::func(...) - syntax error
|
||||
@ -2023,17 +2032,13 @@ impl Engine {
|
||||
);
|
||||
|
||||
let rhs = Expr::MethodCall(func, func_pos);
|
||||
Ok(Expr::Dot(
|
||||
BinaryExpr { lhs, rhs }.into(),
|
||||
ASTFlags::NONE,
|
||||
op_pos,
|
||||
))
|
||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||
}
|
||||
// lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs]
|
||||
(lhs, rhs @ (Expr::Dot(..) | Expr::Index(..))) => {
|
||||
let (x, term, pos, is_dot) = match rhs {
|
||||
Expr::Dot(x, term, pos) => (x, term, pos, true),
|
||||
Expr::Index(x, term, pos) => (x, term, pos, false),
|
||||
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),
|
||||
};
|
||||
|
||||
@ -2050,22 +2055,18 @@ impl Engine {
|
||||
}
|
||||
// lhs.id.dot_rhs or lhs.id[idx_rhs]
|
||||
Expr::Variable(..) | Expr::Property(..) => {
|
||||
let new_lhs = BinaryExpr {
|
||||
let new_binary = BinaryExpr {
|
||||
lhs: x.lhs.into_property(state),
|
||||
rhs: x.rhs,
|
||||
}
|
||||
.into();
|
||||
|
||||
let rhs = if is_dot {
|
||||
Expr::Dot(new_lhs, term, pos)
|
||||
Expr::Dot(new_binary, options, pos)
|
||||
} else {
|
||||
Expr::Index(new_lhs, term, pos)
|
||||
Expr::Index(new_binary, options, pos)
|
||||
};
|
||||
Ok(Expr::Dot(
|
||||
BinaryExpr { lhs, rhs }.into(),
|
||||
ASTFlags::NONE,
|
||||
op_pos,
|
||||
))
|
||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||
}
|
||||
// lhs.func().dot_rhs or lhs.func()[idx_rhs]
|
||||
Expr::FnCall(mut func, func_pos) => {
|
||||
@ -2083,15 +2084,11 @@ impl Engine {
|
||||
.into();
|
||||
|
||||
let rhs = if is_dot {
|
||||
Expr::Dot(new_lhs, term, pos)
|
||||
Expr::Dot(new_lhs, options, pos)
|
||||
} else {
|
||||
Expr::Index(new_lhs, term, pos)
|
||||
Expr::Index(new_lhs, options, pos)
|
||||
};
|
||||
Ok(Expr::Dot(
|
||||
BinaryExpr { lhs, rhs }.into(),
|
||||
ASTFlags::NONE,
|
||||
op_pos,
|
||||
))
|
||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||
}
|
||||
expr => unreachable!("invalid dot expression: {:?}", expr),
|
||||
}
|
||||
|
@ -420,6 +420,8 @@ pub enum Token {
|
||||
Comma,
|
||||
/// `.`
|
||||
Period,
|
||||
/// `?.`
|
||||
Elvis,
|
||||
/// `..`
|
||||
ExclusiveRange,
|
||||
/// `..=`
|
||||
@ -576,6 +578,7 @@ impl Token {
|
||||
Underscore => "_",
|
||||
Comma => ",",
|
||||
Period => ".",
|
||||
Elvis => "?.",
|
||||
ExclusiveRange => "..",
|
||||
InclusiveRange => "..=",
|
||||
MapStart => "#{",
|
||||
@ -771,6 +774,7 @@ impl Token {
|
||||
"_" => Underscore,
|
||||
"," => Comma,
|
||||
"." => Period,
|
||||
"?." => Elvis,
|
||||
".." => ExclusiveRange,
|
||||
"..=" => InclusiveRange,
|
||||
"#{" => MapStart,
|
||||
@ -877,11 +881,12 @@ impl Token {
|
||||
use Token::*;
|
||||
|
||||
match self {
|
||||
LexError(..) |
|
||||
LexError(..) |
|
||||
SemiColon | // ; - is unary
|
||||
Colon | // #{ foo: - is unary
|
||||
Comma | // ( ... , -expr ) - is unary
|
||||
//Period |
|
||||
//Elvis |
|
||||
ExclusiveRange | // .. - is unary
|
||||
InclusiveRange | // ..= - is unary
|
||||
LeftBrace | // { -expr } - is unary
|
||||
@ -987,12 +992,12 @@ 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 | 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 | Elvis
|
||||
| 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,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
@ -2033,7 +2038,10 @@ fn get_next_token_inner(
|
||||
|
||||
('$', ..) => return Some((Token::Reserved("$".into()), start_pos)),
|
||||
|
||||
('?', '.') => return Some((Token::Reserved("?.".into()), start_pos)),
|
||||
('?', '.') => {
|
||||
eat_next(stream, pos);
|
||||
return Some((Token::Elvis, start_pos));
|
||||
}
|
||||
('?', ..) => return Some((Token::Reserved("?".into()), start_pos)),
|
||||
|
||||
(ch, ..) if ch.is_whitespace() => (),
|
||||
|
@ -390,3 +390,15 @@ fn test_get_set_indexer() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_set_elvis() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<()>("let x = (); x?.foo.bar.baz")?, ());
|
||||
assert_eq!(engine.eval::<()>("let x = (); x?.foo(1,2,3)")?, ());
|
||||
assert_eq!(engine.eval::<()>("let x = #{a:()}; x.a?.foo.bar.baz")?, ());
|
||||
assert_eq!(engine.eval::<String>("let x = 'x'; x?.type_of()")?, "char");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user