Add in and not in operators

This commit is contained in:
Laurenz 2022-03-15 15:27:36 +01:00
parent ae0a56cdff
commit 77d153d315
9 changed files with 94 additions and 7 deletions

View File

@ -66,6 +66,11 @@ impl Array {
Arc::make_mut(&mut self.0).push(value); Arc::make_mut(&mut self.0).push(value);
} }
/// Whether the array contains a specific value.
pub fn contains(&self, value: &Value) -> bool {
self.0.contains(value)
}
/// Clear the array. /// Clear the array.
pub fn clear(&mut self) { pub fn clear(&mut self) {
if Arc::strong_count(&self.0) == 1 { if Arc::strong_count(&self.0) == 1 {

View File

@ -61,6 +61,11 @@ impl Dict {
Arc::make_mut(&mut self.0).insert(key, value); Arc::make_mut(&mut self.0).insert(key, value);
} }
/// Whether the dictionary contains a specific key.
pub fn contains_key(&self, key: &str) -> bool {
self.0.contains_key(key)
}
/// Clear the dictionary. /// Clear the dictionary.
pub fn clear(&mut self) { pub fn clear(&mut self) {
if Arc::strong_count(&self.0) == 1 { if Arc::strong_count(&self.0) == 1 {

View File

@ -344,6 +344,8 @@ impl Eval for BinaryExpr {
BinOp::Leq => self.apply(ctx, scp, ops::leq), BinOp::Leq => self.apply(ctx, scp, ops::leq),
BinOp::Gt => self.apply(ctx, scp, ops::gt), BinOp::Gt => self.apply(ctx, scp, ops::gt),
BinOp::Geq => self.apply(ctx, scp, ops::geq), BinOp::Geq => self.apply(ctx, scp, ops::geq),
BinOp::In => self.apply(ctx, scp, ops::in_),
BinOp::NotIn => self.apply(ctx, scp, ops::not_in),
BinOp::Assign => self.assign(ctx, scp, |_, b| Ok(b)), BinOp::Assign => self.assign(ctx, scp, |_, b| Ok(b)),
BinOp::AddAssign => self.assign(ctx, scp, ops::add), BinOp::AddAssign => self.assign(ctx, scp, ops::add),
BinOp::SubAssign => self.assign(ctx, scp, ops::sub), BinOp::SubAssign => self.assign(ctx, scp, ops::sub),

View File

@ -335,3 +335,31 @@ pub fn compare(lhs: &Value, rhs: &Value) -> Option<Ordering> {
_ => Option::None, _ => Option::None,
} }
} }
/// Test whether one value is "in" another one.
pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> {
if let Some(b) = contains(&lhs, &rhs) {
Ok(Bool(b))
} else {
mismatch!("cannot apply 'in' to {} and {}", lhs, rhs)
}
}
/// Test whether one value is "not in" another one.
pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> {
if let Some(b) = contains(&lhs, &rhs) {
Ok(Bool(!b))
} else {
mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs)
}
}
/// Test for containment.
pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
Some(match (lhs, rhs) {
(Value::Str(a), Value::Str(b)) => b.contains(a.as_str()),
(Value::Str(a), Value::Dict(b)) => b.contains_key(a),
(a, Value::Array(b)) => b.contains(a),
_ => return Option::None,
})
}

View File

@ -79,7 +79,7 @@ pub fn new() -> Scope {
std.def_fn("max", utility::max); std.def_fn("max", utility::max);
std.def_fn("even", utility::even); std.def_fn("even", utility::even);
std.def_fn("odd", utility::odd); std.def_fn("odd", utility::odd);
std.def_fn("mod", utility::modulo); std.def_fn("mod", utility::mod_);
std.def_fn("range", utility::range); std.def_fn("range", utility::range);
std.def_fn("rgb", utility::rgb); std.def_fn("rgb", utility::rgb);
std.def_fn("cmyk", utility::cmyk); std.def_fn("cmyk", utility::cmyk);

View File

@ -59,7 +59,7 @@ pub fn odd(_: &mut Context, args: &mut Args) -> TypResult<Value> {
} }
/// The modulo of two numbers. /// The modulo of two numbers.
pub fn modulo(_: &mut Context, args: &mut Args) -> TypResult<Value> { pub fn mod_(_: &mut Context, args: &mut Args) -> TypResult<Value> {
let Spanned { v: v1, span: span1 } = args.expect("integer or float")?; let Spanned { v: v1, span: span1 } = args.expect("integer or float")?;
let Spanned { v: v2, span: span2 } = args.expect("integer or float")?; let Spanned { v: v2, span: span2 } = args.expect("integer or float")?;

View File

@ -376,9 +376,18 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult {
with_expr(p, marker)?; with_expr(p, marker)?;
} }
let op = match p.peek().and_then(BinOp::from_token) { let op = if p.eat_if(&NodeKind::Not) {
Some(binop) => binop, if p.at(&NodeKind::In) {
None => break, BinOp::NotIn
} else {
p.expected("keyword `in`");
return Err(ParseError);
}
} else {
match p.peek().and_then(BinOp::from_token) {
Some(binop) => binop,
None => break,
}
}; };
let mut prec = op.precedence(); let mut prec = op.precedence();

View File

@ -551,9 +551,17 @@ node! {
impl BinaryExpr { impl BinaryExpr {
/// The binary operator: `+`. /// The binary operator: `+`.
pub fn op(&self) -> BinOp { pub fn op(&self) -> BinOp {
let mut not = false;
self.0 self.0
.children() .children()
.find_map(|node| BinOp::from_token(node.kind())) .find_map(|node| match node.kind() {
NodeKind::Not => {
not = true;
None
}
NodeKind::In if not => Some(BinOp::NotIn),
_ => BinOp::from_token(node.kind()),
})
.expect("binary expression is missing operator") .expect("binary expression is missing operator")
} }
@ -601,6 +609,10 @@ pub enum BinOp {
Geq, Geq,
/// The assignment operator: `=`. /// The assignment operator: `=`.
Assign, Assign,
/// The containment operator: `in`.
In,
/// The inversed containment operator: `not in`.
NotIn,
/// The add-assign operator: `+=`. /// The add-assign operator: `+=`.
AddAssign, AddAssign,
/// The subtract-assign oeprator: `-=`. /// The subtract-assign oeprator: `-=`.
@ -628,6 +640,7 @@ impl BinOp {
NodeKind::Gt => Self::Gt, NodeKind::Gt => Self::Gt,
NodeKind::GtEq => Self::Geq, NodeKind::GtEq => Self::Geq,
NodeKind::Eq => Self::Assign, NodeKind::Eq => Self::Assign,
NodeKind::In => Self::In,
NodeKind::PlusEq => Self::AddAssign, NodeKind::PlusEq => Self::AddAssign,
NodeKind::HyphEq => Self::SubAssign, NodeKind::HyphEq => Self::SubAssign,
NodeKind::StarEq => Self::MulAssign, NodeKind::StarEq => Self::MulAssign,
@ -649,6 +662,8 @@ impl BinOp {
Self::Leq => 4, Self::Leq => 4,
Self::Gt => 4, Self::Gt => 4,
Self::Geq => 4, Self::Geq => 4,
Self::In => 4,
Self::NotIn => 4,
Self::And => 3, Self::And => 3,
Self::Or => 2, Self::Or => 2,
Self::Assign => 1, Self::Assign => 1,
@ -674,6 +689,8 @@ impl BinOp {
Self::Leq => Associativity::Left, Self::Leq => Associativity::Left,
Self::Gt => Associativity::Left, Self::Gt => Associativity::Left,
Self::Geq => Associativity::Left, Self::Geq => Associativity::Left,
Self::In => Associativity::Left,
Self::NotIn => Associativity::Left,
Self::Assign => Associativity::Right, Self::Assign => Associativity::Right,
Self::AddAssign => Associativity::Right, Self::AddAssign => Associativity::Right,
Self::SubAssign => Associativity::Right, Self::SubAssign => Associativity::Right,
@ -697,6 +714,8 @@ impl BinOp {
Self::Leq => "<=", Self::Leq => "<=",
Self::Gt => ">", Self::Gt => ">",
Self::Geq => ">=", Self::Geq => ">=",
Self::In => "in",
Self::NotIn => "not in",
Self::Assign => "=", Self::Assign => "=",
Self::AddAssign => "+=", Self::AddAssign => "+=",
Self::SubAssign => "-=", Self::SubAssign => "-=",

View File

@ -165,7 +165,26 @@
{ x += "thing" } #test(x, "something") { x += "thing" } #test(x, "something")
--- ---
// Test with operator. // Test `in` operator.
#test("hi" in "worship", true)
#test("hi" in ("we", "hi", "bye"), true)
#test("Hey" in "abHeyCd", true)
#test("Hey" in "abheyCd", false)
#test(5 in range(10), true)
#test(12 in range(10), false)
#test("" in (), false)
#test("key" in (key: "value"), true)
#test("value" in (key: "value"), false)
#test("Hey" not in "abheyCd", true)
#test("a" not
/* fun comment? */ in "abc", false)
---
// Error: 9 expected keyword `in`
{"a" not}
---
// Test `with` operator.
// Apply positional arguments. // Apply positional arguments.
#let add(x, y) = x + y #let add(x, y) = x + y