summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/array.rs5
-rw-r--r--src/eval/dict.rs5
-rw-r--r--src/eval/mod.rs2
-rw-r--r--src/eval/ops.rs28
-rw-r--r--src/library/mod.rs2
-rw-r--r--src/library/utility/math.rs2
-rw-r--r--src/parse/mod.rs15
-rw-r--r--src/syntax/ast.rs21
8 files changed, 74 insertions, 6 deletions
diff --git a/src/eval/array.rs b/src/eval/array.rs
index 5cd8df82..2da1a5f4 100644
--- a/src/eval/array.rs
+++ b/src/eval/array.rs
@@ -66,6 +66,11 @@ impl Array {
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.
pub fn clear(&mut self) {
if Arc::strong_count(&self.0) == 1 {
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
index 03871fa0..9127b2eb 100644
--- a/src/eval/dict.rs
+++ b/src/eval/dict.rs
@@ -61,6 +61,11 @@ impl Dict {
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.
pub fn clear(&mut self) {
if Arc::strong_count(&self.0) == 1 {
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 4ccf377b..2c864036 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -344,6 +344,8 @@ impl Eval for BinaryExpr {
BinOp::Leq => self.apply(ctx, scp, ops::leq),
BinOp::Gt => self.apply(ctx, scp, ops::gt),
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::AddAssign => self.assign(ctx, scp, ops::add),
BinOp::SubAssign => self.assign(ctx, scp, ops::sub),
diff --git a/src/eval/ops.rs b/src/eval/ops.rs
index 04a13fd1..6a8f5284 100644
--- a/src/eval/ops.rs
+++ b/src/eval/ops.rs
@@ -335,3 +335,31 @@ pub fn compare(lhs: &Value, rhs: &Value) -> Option<Ordering> {
_ => 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,
+ })
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 505f1a1d..087ff7ea 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -79,7 +79,7 @@ pub fn new() -> Scope {
std.def_fn("max", utility::max);
std.def_fn("even", utility::even);
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("rgb", utility::rgb);
std.def_fn("cmyk", utility::cmyk);
diff --git a/src/library/utility/math.rs b/src/library/utility/math.rs
index 9389b4b9..e48af426 100644
--- a/src/library/utility/math.rs
+++ b/src/library/utility/math.rs
@@ -59,7 +59,7 @@ pub fn odd(_: &mut Context, args: &mut Args) -> TypResult<Value> {
}
/// 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: v2, span: span2 } = args.expect("integer or float")?;
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index f07fefce..5eaba8b0 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -376,9 +376,18 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult {
with_expr(p, marker)?;
}
- let op = match p.peek().and_then(BinOp::from_token) {
- Some(binop) => binop,
- None => break,
+ let op = if p.eat_if(&NodeKind::Not) {
+ if p.at(&NodeKind::In) {
+ 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();
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 928680e5..b8780590 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -551,9 +551,17 @@ node! {
impl BinaryExpr {
/// The binary operator: `+`.
pub fn op(&self) -> BinOp {
+ let mut not = false;
self.0
.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")
}
@@ -601,6 +609,10 @@ pub enum BinOp {
Geq,
/// The assignment operator: `=`.
Assign,
+ /// The containment operator: `in`.
+ In,
+ /// The inversed containment operator: `not in`.
+ NotIn,
/// The add-assign operator: `+=`.
AddAssign,
/// The subtract-assign oeprator: `-=`.
@@ -628,6 +640,7 @@ impl BinOp {
NodeKind::Gt => Self::Gt,
NodeKind::GtEq => Self::Geq,
NodeKind::Eq => Self::Assign,
+ NodeKind::In => Self::In,
NodeKind::PlusEq => Self::AddAssign,
NodeKind::HyphEq => Self::SubAssign,
NodeKind::StarEq => Self::MulAssign,
@@ -649,6 +662,8 @@ impl BinOp {
Self::Leq => 4,
Self::Gt => 4,
Self::Geq => 4,
+ Self::In => 4,
+ Self::NotIn => 4,
Self::And => 3,
Self::Or => 2,
Self::Assign => 1,
@@ -674,6 +689,8 @@ impl BinOp {
Self::Leq => Associativity::Left,
Self::Gt => Associativity::Left,
Self::Geq => Associativity::Left,
+ Self::In => Associativity::Left,
+ Self::NotIn => Associativity::Left,
Self::Assign => Associativity::Right,
Self::AddAssign => Associativity::Right,
Self::SubAssign => Associativity::Right,
@@ -697,6 +714,8 @@ impl BinOp {
Self::Leq => "<=",
Self::Gt => ">",
Self::Geq => ">=",
+ Self::In => "in",
+ Self::NotIn => "not in",
Self::Assign => "=",
Self::AddAssign => "+=",
Self::SubAssign => "-=",