Add null coalescing operator.
This commit is contained in:
parent
0f1e51b1c9
commit
8999872d62
@ -19,7 +19,7 @@ Bug fixes
|
||||
Reserved Symbols
|
||||
----------------
|
||||
|
||||
* `?`, `?.` and `!.` are now reserved symbols.
|
||||
* `?`, `??`, `?.` and `!.` are now reserved symbols.
|
||||
|
||||
Deprecated API's
|
||||
----------------
|
||||
@ -30,6 +30,7 @@ New features
|
||||
------------
|
||||
|
||||
* The _Elvis operator_ (`?.`) is now supported for property access and method calls.
|
||||
* The _null-coalescing operator_ (`??`) is now supported to short-circuit `()` values.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
@ -417,6 +417,8 @@ pub enum Expr {
|
||||
And(Box<BinaryExpr>, Position),
|
||||
/// lhs `||` rhs
|
||||
Or(Box<BinaryExpr>, Position),
|
||||
/// lhs `??` rhs
|
||||
Coalesce(Box<BinaryExpr>, Position),
|
||||
/// Custom syntax
|
||||
Custom(Box<CustomExpr>, Position),
|
||||
}
|
||||
@ -510,11 +512,12 @@ impl fmt::Debug for Expr {
|
||||
}
|
||||
f.finish()
|
||||
}
|
||||
Self::And(x, pos) | Self::Or(x, pos) => {
|
||||
Self::And(x, pos) | Self::Or(x, pos) | Self::Coalesce(x, pos) => {
|
||||
let op_name = match self {
|
||||
Self::And(..) => "And",
|
||||
Self::Or(..) => "Or",
|
||||
expr => unreachable!("Self::And or Self::Or expected but gets {:?}", expr),
|
||||
Self::Coalesce(..) => "Coalesce",
|
||||
expr => unreachable!("`And`, `Or` or `Coalesce` expected but gets {:?}", expr),
|
||||
};
|
||||
|
||||
if !pos.is_none() {
|
||||
@ -696,6 +699,7 @@ impl Expr {
|
||||
| Self::Variable(.., pos)
|
||||
| Self::And(.., pos)
|
||||
| Self::Or(.., pos)
|
||||
| Self::Coalesce(.., pos)
|
||||
| Self::Index(.., pos)
|
||||
| Self::Dot(.., pos)
|
||||
| Self::Custom(.., pos)
|
||||
@ -721,10 +725,15 @@ impl Expr {
|
||||
self.position()
|
||||
}
|
||||
}
|
||||
Self::And(x, ..) | Self::Or(x, ..) | Self::Index(x, ..) | Self::Dot(x, ..) => {
|
||||
x.lhs.start_position()
|
||||
}
|
||||
|
||||
Self::And(x, ..)
|
||||
| Self::Or(x, ..)
|
||||
| Self::Coalesce(x, ..)
|
||||
| Self::Index(x, ..)
|
||||
| Self::Dot(x, ..) => x.lhs.start_position(),
|
||||
|
||||
Self::FnCall(.., pos) => *pos,
|
||||
|
||||
_ => self.position(),
|
||||
}
|
||||
}
|
||||
@ -745,6 +754,7 @@ impl Expr {
|
||||
| Self::Map(.., pos)
|
||||
| Self::And(.., pos)
|
||||
| Self::Or(.., pos)
|
||||
| Self::Coalesce(.., pos)
|
||||
| Self::Dot(.., pos)
|
||||
| Self::Index(.., pos)
|
||||
| Self::Variable(.., pos)
|
||||
@ -770,7 +780,9 @@ impl Expr {
|
||||
|
||||
Self::Map(x, ..) => x.0.iter().map(|(.., v)| v).all(Self::is_pure),
|
||||
|
||||
Self::And(x, ..) | Self::Or(x, ..) => x.lhs.is_pure() && x.rhs.is_pure(),
|
||||
Self::And(x, ..) | Self::Or(x, ..) | Self::Coalesce(x, ..) => {
|
||||
x.lhs.is_pure() && x.rhs.is_pure()
|
||||
}
|
||||
|
||||
Self::Stmt(x) => x.iter().all(Stmt::is_pure),
|
||||
|
||||
@ -828,6 +840,7 @@ impl Expr {
|
||||
| Self::CharConstant(..)
|
||||
| Self::And(..)
|
||||
| Self::Or(..)
|
||||
| Self::Coalesce(..)
|
||||
| Self::Unit(..) => false,
|
||||
|
||||
Self::IntegerConstant(..)
|
||||
@ -892,7 +905,11 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Index(x, ..) | Self::Dot(x, ..) | Expr::And(x, ..) | Expr::Or(x, ..) => {
|
||||
Self::Index(x, ..)
|
||||
| Self::Dot(x, ..)
|
||||
| Expr::And(x, ..)
|
||||
| Expr::Or(x, ..)
|
||||
| Expr::Coalesce(x, ..) => {
|
||||
if !x.lhs.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
|
@ -464,6 +464,17 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Coalesce(x, ..) => {
|
||||
let lhs = self.eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level);
|
||||
|
||||
match lhs {
|
||||
Ok(value) if value.is::<()>() => {
|
||||
self.eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level)
|
||||
}
|
||||
Ok(_) | Err(_) => lhs,
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Custom(custom, pos) => {
|
||||
let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect();
|
||||
// The first token acts as the custom syntax's key
|
||||
|
@ -1073,6 +1073,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||
// lhs || rhs
|
||||
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
|
||||
},
|
||||
// () ?? rhs -> rhs
|
||||
Expr::Coalesce(x, ..) if matches!(x.lhs, Expr::Unit(..)) => {
|
||||
state.set_dirty();
|
||||
*expr = mem::take(&mut x.rhs);
|
||||
},
|
||||
// lhs:constant ?? rhs -> lhs
|
||||
Expr::Coalesce(x, ..) if x.lhs.is_constant() => {
|
||||
state.set_dirty();
|
||||
*expr = mem::take(&mut x.lhs);
|
||||
},
|
||||
|
||||
// eval!
|
||||
Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => {
|
||||
|
@ -1917,8 +1917,8 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
// ??? && ??? = rhs, ??? || ??? = rhs
|
||||
Expr::And(..) | Expr::Or(..) => Err(LexError::ImproperSymbol(
|
||||
// ??? && ??? = rhs, ??? || ??? = rhs, xxx ?? xxx = rhs
|
||||
Expr::And(..) | Expr::Or(..) | Expr::Coalesce(..) => Err(LexError::ImproperSymbol(
|
||||
"=".to_string(),
|
||||
"Possibly a typo of '=='?".to_string(),
|
||||
)
|
||||
@ -2223,6 +2223,18 @@ impl Engine {
|
||||
pos,
|
||||
)
|
||||
}
|
||||
Token::DoubleQuestion => {
|
||||
let rhs = args.pop().unwrap();
|
||||
let current_lhs = args.pop().unwrap();
|
||||
Expr::Coalesce(
|
||||
BinaryExpr {
|
||||
lhs: current_lhs,
|
||||
rhs,
|
||||
}
|
||||
.into(),
|
||||
pos,
|
||||
)
|
||||
}
|
||||
Token::In => {
|
||||
// Swap the arguments
|
||||
let current_lhs = args.remove(0);
|
||||
|
@ -422,6 +422,8 @@ pub enum Token {
|
||||
Period,
|
||||
/// `?.`
|
||||
Elvis,
|
||||
/// `??`
|
||||
DoubleQuestion,
|
||||
/// `..`
|
||||
ExclusiveRange,
|
||||
/// `..=`
|
||||
@ -579,6 +581,7 @@ impl Token {
|
||||
Comma => ",",
|
||||
Period => ".",
|
||||
Elvis => "?.",
|
||||
DoubleQuestion => "??",
|
||||
ExclusiveRange => "..",
|
||||
InclusiveRange => "..=",
|
||||
MapStart => "#{",
|
||||
@ -775,6 +778,7 @@ impl Token {
|
||||
"," => Comma,
|
||||
"." => Period,
|
||||
"?." => Elvis,
|
||||
"??" => DoubleQuestion,
|
||||
".." => ExclusiveRange,
|
||||
"..=" => InclusiveRange,
|
||||
"#{" => MapStart,
|
||||
@ -887,6 +891,7 @@ impl Token {
|
||||
Comma | // ( ... , -expr ) - is unary
|
||||
//Period |
|
||||
//Elvis |
|
||||
//DoubleQuestion |
|
||||
ExclusiveRange | // .. - is unary
|
||||
InclusiveRange | // ..= - is unary
|
||||
LeftBrace | // { -expr } - is unary
|
||||
@ -957,6 +962,8 @@ impl Token {
|
||||
|
||||
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130,
|
||||
|
||||
DoubleQuestion => 135,
|
||||
|
||||
ExclusiveRange | InclusiveRange => 140,
|
||||
|
||||
Plus | Minus => 150,
|
||||
@ -993,11 +1000,11 @@ impl Token {
|
||||
LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus
|
||||
| UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift
|
||||
| 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,
|
||||
| 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,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
@ -2042,6 +2049,10 @@ fn get_next_token_inner(
|
||||
eat_next(stream, pos);
|
||||
return Some((Token::Elvis, start_pos));
|
||||
}
|
||||
('?', '?') => {
|
||||
eat_next(stream, pos);
|
||||
return Some((Token::DoubleQuestion, start_pos));
|
||||
}
|
||||
('?', ..) => return Some((Token::Reserved("?".into()), start_pos)),
|
||||
|
||||
(ch, ..) if ch.is_whitespace() => (),
|
||||
|
@ -135,3 +135,13 @@ fn test_binary_ops() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_ops_null_coalesce() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<INT>("let x = 42; x ?? 123")?, 42);
|
||||
assert_eq!(engine.eval::<INT>("let x = (); x ?? 123")?, 123);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user