Skip to content

Commit 2e5040b

Browse files
Add unary minus
More tests for arithmetic
1 parent 50fedf9 commit 2e5040b

File tree

3 files changed

+33
-13
lines changed

3 files changed

+33
-13
lines changed

tests/test_bcftools_validation.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,6 @@ def test_vcf_output_with_output_option(tmp_path, args, vcf_file):
162162
r"query -f '[%POS %QUAL\n]' -i'(QUAL > 10 && POS > 100000)'",
163163
"sample.vcf.gz",
164164
),
165-
# Check arithmetic evaluation in filter queries. All these should
166-
# result to POS=112, which exists.
167-
(r"query -f '%POS\n' -i 'POS=(112 + 1)'", "sample.vcf.gz"),
168-
(r"query -f '%POS\n' -i 'POS =(224 / 2)'", "sample.vcf.gz"),
169-
(r"query -f '%POS\n' -i 'POS= (112 * 3) / 3'", "sample.vcf.gz"),
170-
(r"query -f '%POS\n' -i 'POS=(112 * 3 / 3 )'", "sample.vcf.gz"),
171-
(r"query -f '%POS\n' -i 'POS=25 * 4 + 24 / 2'", "sample.vcf.gz"),
172-
(r"query -f '%POS\n' -i 'POS=112 * -1 * -1'", "sample.vcf.gz"),
173-
(r"query -f '%POS\n' -i '-POS=-112'", "sample.vcf.gz"),
174165
],
175166
)
176167
def test_output(tmp_path, args, vcf_name):
@@ -194,7 +185,9 @@ def test_output(tmp_path, args, vcf_name):
194185
"POS=(112 * 3 / 3 )",
195186
"POS=25 * 4 + 24 / 2",
196187
"POS=112 * -1 * -1",
197-
# "-POS=-112",
188+
"-POS=-112",
189+
"POS=112.25 - 1 / 4",
190+
"POS=112.25e3 * 1e-3 - 0.25",
198191
],
199192
)
200193
def test_query_arithmethic(tmp_path, expr):
@@ -211,7 +204,6 @@ def test_query_arithmethic(tmp_path, expr):
211204
assert vcztools_output == "112\n"
212205

213206

214-
215207
@pytest.mark.parametrize(
216208
("args", "vcf_name"),
217209
[

tests/test_filter.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def test_evaluate(self, expression, data, expected):
9292
("a == b", {"variant_a", "variant_b"}),
9393
("a == b + c", {"variant_a", "variant_b", "variant_c"}),
9494
("(a + 1) < (b + c) - d / a", {f"variant_{x}" for x in "abcd"}),
95+
("-(a + b)", {f"variant_{x}" for x in "ab"}),
9596
],
9697
)
9798
def test_referenced_fields(self, expr, expected):
@@ -103,6 +104,7 @@ def test_referenced_fields(self, expr, expected):
103104
[
104105
("a == b", "(variant_a)==(variant_b)"),
105106
("a + 1", "(variant_a)+(1)"),
107+
("-a + 1", "(-(variant_a))+(1)"),
106108
("a + 1 + 2", "(variant_a)+(1)+(2)"),
107109
("a + (1 + 2)", "(variant_a)+((1)+(2))"),
108110
("POS<10", "(variant_position)<(10)"),
@@ -139,6 +141,7 @@ class TestBcftoolsParser:
139141
"(1 + 2) == (1 + 2 + 3)",
140142
"(1 == 1) != (2 == 2)",
141143
'("x" == "x")',
144+
"-1 == 1 + 2 - 4",
142145
],
143146
)
144147
def test_python_arithmetic_expressions(self, expr):
@@ -157,6 +160,8 @@ def test_python_arithmetic_expressions(self, expr):
157160
("(a - b) < (a + b)", {"a": 7, "b": 6}),
158161
("(a - b) < (a + b)", {"a": 7.0, "b": 6.666}),
159162
("a == a", {"a": 1}),
163+
("-a == -a", {"a": 1}),
164+
("-a == b", {"a": 1, "b": -1}),
160165
('a == "string"', {"a": "string"}),
161166
],
162167
)

vcztools/filter.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
logger = logging.getLogger(__name__)
1111

12+
# Parsing is WAY slower without this!
13+
pp.ParserElement.enablePackrat()
14+
1215

1316
# The parser and evaluation model here are based on the eval_arith example
1417
# in the pyparsing docs:
@@ -52,6 +55,27 @@ def referenced_fields(self):
5255
return frozenset([self.field_name])
5356

5457

58+
# NOTE we should perhaps add a Operator superclass of UnaryMinus,
59+
# BinaryOperator and ComparisonOperator to reduce duplication
60+
# when doing things like referenced_fields. We should probably
61+
# be extracting the operators and operands once.
62+
63+
64+
class UnaryMinus(EvaluationNode):
65+
def eval(self, data):
66+
op, operand = self.tokens
67+
assert op == "-"
68+
return -1 * operand.eval(data)
69+
70+
def __repr__(self):
71+
_, operand = self.tokens
72+
return f"-({repr(operand)})"
73+
74+
def referenced_fields(self):
75+
_, operand = self.tokens
76+
return operand.referenced_fields()
77+
78+
5579
class BinaryOperator(EvaluationNode):
5680
op_map = {
5781
"*": operator.mul,
@@ -143,8 +167,7 @@ def make_bcftools_filter_parser(all_fields=None, map_vcf_identifiers=True):
143167
filter_expression = pp.infix_notation(
144168
constant | identifier,
145169
[
146-
# FIXME Does bcftools support unary minus?
147-
# ("-", 1, pp.OpAssoc.RIGHT, ),
170+
("-", 1, pp.OpAssoc.RIGHT, UnaryMinus),
148171
(pp.one_of("* /"), 2, pp.OpAssoc.LEFT, BinaryOperator),
149172
(pp.one_of("+ -"), 2, pp.OpAssoc.LEFT, BinaryOperator),
150173
(comp_op, 2, pp.OpAssoc.LEFT, ComparisonOperator),

0 commit comments

Comments
 (0)