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()`.
|
* `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
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -400,18 +400,18 @@ pub enum Expr {
|
|||||||
Stmt(Box<StmtBlock>),
|
Stmt(Box<StmtBlock>),
|
||||||
/// func `(` expr `,` ... `)`
|
/// func `(` expr `,` ... `)`
|
||||||
FnCall(Box<FnCallExpr>, Position),
|
FnCall(Box<FnCallExpr>, Position),
|
||||||
/// lhs `.` rhs
|
/// lhs `.` rhs | lhs `?.` rhs
|
||||||
///
|
///
|
||||||
/// ### Flags
|
/// ### 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),
|
Dot(Box<BinaryExpr>, ASTFlags, Position),
|
||||||
/// lhs `[` rhs `]`
|
/// lhs `[` rhs `]`
|
||||||
///
|
///
|
||||||
/// ### Flags
|
/// ### Flags
|
||||||
///
|
///
|
||||||
/// [`NONE`][ASTFlags::NONE] = recurse into the indexing chain
|
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
|
||||||
/// [`BREAK`][ASTFlags::BREAK] = terminate the indexing chain
|
|
||||||
Index(Box<BinaryExpr>, ASTFlags, Position),
|
Index(Box<BinaryExpr>, ASTFlags, Position),
|
||||||
/// lhs `&&` rhs
|
/// lhs `&&` rhs
|
||||||
And(Box<BinaryExpr>, Position),
|
And(Box<BinaryExpr>, Position),
|
||||||
@ -484,26 +484,37 @@ impl fmt::Debug for Expr {
|
|||||||
f.debug_list().entries(x.iter()).finish()
|
f.debug_list().entries(x.iter()).finish()
|
||||||
}
|
}
|
||||||
Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
|
Self::FnCall(x, ..) => fmt::Debug::fmt(x, f),
|
||||||
Self::Index(x, term, pos) => {
|
Self::Index(x, options, pos) => {
|
||||||
if !pos.is_none() {
|
if !pos.is_none() {
|
||||||
display_pos = format!(" @ {:?}", pos);
|
display_pos = format!(" @ {:?}", pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
f.debug_struct("Index")
|
let mut f = f.debug_struct("Index");
|
||||||
.field("lhs", &x.lhs)
|
|
||||||
.field("rhs", &x.rhs)
|
f.field("lhs", &x.lhs).field("rhs", &x.rhs);
|
||||||
.field("terminate", term)
|
if !options.is_empty() {
|
||||||
.finish()
|
f.field("options", options);
|
||||||
}
|
}
|
||||||
Self::Dot(x, _, pos) | Self::And(x, pos) | Self::Or(x, pos) => {
|
f.finish()
|
||||||
|
}
|
||||||
|
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 {
|
let op_name = match self {
|
||||||
Self::Dot(..) => "Dot",
|
|
||||||
Self::And(..) => "And",
|
Self::And(..) => "And",
|
||||||
Self::Or(..) => "Or",
|
Self::Or(..) => "Or",
|
||||||
expr => unreachable!(
|
expr => unreachable!("Self::And or Self::Or expected but gets {:?}", expr),
|
||||||
"Self::Dot or Self::And or Self::Or expected but gets {:?}",
|
|
||||||
expr
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !pos.is_none() {
|
if !pos.is_none() {
|
||||||
@ -802,7 +813,7 @@ impl Expr {
|
|||||||
pub const fn is_valid_postfix(&self, token: &Token) -> bool {
|
pub const fn is_valid_postfix(&self, token: &Token) -> bool {
|
||||||
match token {
|
match token {
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Token::Period => return true,
|
Token::Period | Token::Elvis => return true,
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => return true,
|
Token::LeftBracket => return true,
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -186,6 +186,11 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
ChainType::Dotting => {
|
ChainType::Dotting => {
|
||||||
|
// Check for existence with the Elvis operator
|
||||||
|
if _parent_options.contains(ASTFlags::NEGATED) && target.is::<()>() {
|
||||||
|
return Ok((Dynamic::UNIT, false));
|
||||||
|
}
|
||||||
|
|
||||||
match rhs {
|
match rhs {
|
||||||
// xxx.fn_name(arg_expr_list)
|
// xxx.fn_name(arg_expr_list)
|
||||||
Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
|
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
|
// lhs.rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[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
|
// map.string
|
||||||
(Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
|
(Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
|
||||||
let prop = p.2.as_str();
|
let prop = p.2.as_str();
|
||||||
@ -932,7 +938,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
|||||||
}
|
}
|
||||||
// ....lhs.rhs
|
// ....lhs.rhs
|
||||||
#[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); }
|
||||||
|
|
||||||
// lhs[rhs]
|
// lhs[rhs]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
@ -1639,7 +1639,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// Property access
|
// Property access
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
(expr, Token::Period) => {
|
(expr, op @ Token::Period) | (expr, op @ Token::Elvis) => {
|
||||||
// Expression after dot must start with an identifier
|
// Expression after dot must start with an identifier
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
(Token::Identifier(..), ..) => {
|
(Token::Identifier(..), ..) => {
|
||||||
@ -1654,7 +1654,12 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let rhs = self.parse_primary(input, state, lib, settings.level_up())?;
|
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
|
// Unknown postfix operator
|
||||||
(expr, token) => unreachable!(
|
(expr, token) => unreachable!(
|
||||||
@ -1959,14 +1964,22 @@ impl Engine {
|
|||||||
fn make_dot_expr(
|
fn make_dot_expr(
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
lhs: Expr,
|
lhs: Expr,
|
||||||
parent_options: ASTFlags,
|
|
||||||
rhs: Expr,
|
rhs: Expr,
|
||||||
|
parent_options: ASTFlags,
|
||||||
|
op_flags: ASTFlags,
|
||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
) -> ParseResult<Expr> {
|
) -> ParseResult<Expr> {
|
||||||
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(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))
|
Ok(Expr::Index(x, ASTFlags::NONE, pos))
|
||||||
}
|
}
|
||||||
// lhs.module::id - syntax error
|
// lhs.module::id - syntax error
|
||||||
@ -1977,16 +1990,12 @@ impl Engine {
|
|||||||
// lhs.id
|
// lhs.id
|
||||||
(lhs, var_expr @ Expr::Variable(..)) => {
|
(lhs, var_expr @ Expr::Variable(..)) => {
|
||||||
let rhs = var_expr.into_property(state);
|
let rhs = var_expr.into_property(state);
|
||||||
Ok(Expr::Dot(
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||||
BinaryExpr { lhs, rhs }.into(),
|
|
||||||
ASTFlags::NONE,
|
|
||||||
op_pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
// lhs.prop
|
// lhs.prop
|
||||||
(lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot(
|
(lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot(
|
||||||
BinaryExpr { lhs, rhs: prop }.into(),
|
BinaryExpr { lhs, rhs: prop }.into(),
|
||||||
ASTFlags::NONE,
|
op_flags,
|
||||||
op_pos,
|
op_pos,
|
||||||
)),
|
)),
|
||||||
// lhs.nnn::func(...) - syntax error
|
// lhs.nnn::func(...) - syntax error
|
||||||
@ -2023,17 +2032,13 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let rhs = Expr::MethodCall(func, func_pos);
|
let rhs = Expr::MethodCall(func, func_pos);
|
||||||
Ok(Expr::Dot(
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||||
BinaryExpr { lhs, rhs }.into(),
|
|
||||||
ASTFlags::NONE,
|
|
||||||
op_pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
// lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs]
|
// lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs]
|
||||||
(lhs, rhs @ (Expr::Dot(..) | Expr::Index(..))) => {
|
(lhs, rhs @ (Expr::Dot(..) | Expr::Index(..))) => {
|
||||||
let (x, term, pos, is_dot) = match rhs {
|
let (x, options, pos, is_dot) = match rhs {
|
||||||
Expr::Dot(x, term, pos) => (x, term, pos, true),
|
Expr::Dot(x, options, pos) => (x, options, pos, true),
|
||||||
Expr::Index(x, term, pos) => (x, term, pos, false),
|
Expr::Index(x, options, pos) => (x, options, pos, false),
|
||||||
expr => unreachable!("Expr::Dot or Expr::Index expected but gets {:?}", expr),
|
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]
|
// lhs.id.dot_rhs or lhs.id[idx_rhs]
|
||||||
Expr::Variable(..) | Expr::Property(..) => {
|
Expr::Variable(..) | Expr::Property(..) => {
|
||||||
let new_lhs = BinaryExpr {
|
let new_binary = BinaryExpr {
|
||||||
lhs: x.lhs.into_property(state),
|
lhs: x.lhs.into_property(state),
|
||||||
rhs: x.rhs,
|
rhs: x.rhs,
|
||||||
}
|
}
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let rhs = if is_dot {
|
let rhs = if is_dot {
|
||||||
Expr::Dot(new_lhs, term, pos)
|
Expr::Dot(new_binary, options, pos)
|
||||||
} else {
|
} else {
|
||||||
Expr::Index(new_lhs, term, pos)
|
Expr::Index(new_binary, options, pos)
|
||||||
};
|
};
|
||||||
Ok(Expr::Dot(
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||||
BinaryExpr { lhs, rhs }.into(),
|
|
||||||
ASTFlags::NONE,
|
|
||||||
op_pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
// lhs.func().dot_rhs or lhs.func()[idx_rhs]
|
// lhs.func().dot_rhs or lhs.func()[idx_rhs]
|
||||||
Expr::FnCall(mut func, func_pos) => {
|
Expr::FnCall(mut func, func_pos) => {
|
||||||
@ -2083,15 +2084,11 @@ impl Engine {
|
|||||||
.into();
|
.into();
|
||||||
|
|
||||||
let rhs = if is_dot {
|
let rhs = if is_dot {
|
||||||
Expr::Dot(new_lhs, term, pos)
|
Expr::Dot(new_lhs, options, pos)
|
||||||
} else {
|
} else {
|
||||||
Expr::Index(new_lhs, term, pos)
|
Expr::Index(new_lhs, options, pos)
|
||||||
};
|
};
|
||||||
Ok(Expr::Dot(
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||||
BinaryExpr { lhs, rhs }.into(),
|
|
||||||
ASTFlags::NONE,
|
|
||||||
op_pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
expr => unreachable!("invalid dot expression: {:?}", expr),
|
expr => unreachable!("invalid dot expression: {:?}", expr),
|
||||||
}
|
}
|
||||||
|
@ -420,6 +420,8 @@ pub enum Token {
|
|||||||
Comma,
|
Comma,
|
||||||
/// `.`
|
/// `.`
|
||||||
Period,
|
Period,
|
||||||
|
/// `?.`
|
||||||
|
Elvis,
|
||||||
/// `..`
|
/// `..`
|
||||||
ExclusiveRange,
|
ExclusiveRange,
|
||||||
/// `..=`
|
/// `..=`
|
||||||
@ -576,6 +578,7 @@ impl Token {
|
|||||||
Underscore => "_",
|
Underscore => "_",
|
||||||
Comma => ",",
|
Comma => ",",
|
||||||
Period => ".",
|
Period => ".",
|
||||||
|
Elvis => "?.",
|
||||||
ExclusiveRange => "..",
|
ExclusiveRange => "..",
|
||||||
InclusiveRange => "..=",
|
InclusiveRange => "..=",
|
||||||
MapStart => "#{",
|
MapStart => "#{",
|
||||||
@ -771,6 +774,7 @@ impl Token {
|
|||||||
"_" => Underscore,
|
"_" => Underscore,
|
||||||
"," => Comma,
|
"," => Comma,
|
||||||
"." => Period,
|
"." => Period,
|
||||||
|
"?." => Elvis,
|
||||||
".." => ExclusiveRange,
|
".." => ExclusiveRange,
|
||||||
"..=" => InclusiveRange,
|
"..=" => InclusiveRange,
|
||||||
"#{" => MapStart,
|
"#{" => MapStart,
|
||||||
@ -882,6 +886,7 @@ impl Token {
|
|||||||
Colon | // #{ foo: - is unary
|
Colon | // #{ foo: - is unary
|
||||||
Comma | // ( ... , -expr ) - is unary
|
Comma | // ( ... , -expr ) - is unary
|
||||||
//Period |
|
//Period |
|
||||||
|
//Elvis |
|
||||||
ExclusiveRange | // .. - is unary
|
ExclusiveRange | // .. - is unary
|
||||||
InclusiveRange | // ..= - is unary
|
InclusiveRange | // ..= - is unary
|
||||||
LeftBrace | // { -expr } - is unary
|
LeftBrace | // { -expr } - is unary
|
||||||
@ -987,12 +992,12 @@ 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 | ExclusiveRange
|
| RightShift | SemiColon | Colon | DoubleColon | Comma | Period | Elvis
|
||||||
| InclusiveRange | MapStart | Equals | LessThan | GreaterThan | LessThanEqualsTo
|
| ExclusiveRange | InclusiveRange | MapStart | Equals | LessThan | GreaterThan
|
||||||
| GreaterThanEqualsTo | EqualsTo | NotEqualsTo | Bang | Pipe | Or | XOr | Ampersand
|
| LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo | Bang | Pipe
|
||||||
| And | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign
|
| Or | XOr | Ampersand | And | PlusAssign | MinusAssign | MultiplyAssign
|
||||||
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
|
| DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign
|
||||||
| PowerOfAssign => true,
|
| XOrAssign | ModuloAssign | PowerOfAssign => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -2033,7 +2038,10 @@ fn get_next_token_inner(
|
|||||||
|
|
||||||
('$', ..) => return Some((Token::Reserved("$".into()), start_pos)),
|
('$', ..) => 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)),
|
('?', ..) => return Some((Token::Reserved("?".into()), start_pos)),
|
||||||
|
|
||||||
(ch, ..) if ch.is_whitespace() => (),
|
(ch, ..) if ch.is_whitespace() => (),
|
||||||
|
@ -390,3 +390,15 @@ fn test_get_set_indexer() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
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