Skip to content

Commit 0ab162f

Browse files
Raise error on function calls
Full query language now parsed. So Closes #176
1 parent 6fa0106 commit 0ab162f

File tree

2 files changed

+24
-3
lines changed

2 files changed

+24
-3
lines changed

tests/test_filter.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ def test_invalid_expressions(self, parser, expression):
5353
"(DP4[0]+DP4[1])/(DP4[2]+DP4[3]) > 0.3",
5454
filter_mod.UnsupportedArraySubscriptError,
5555
),
56+
("binom(FMT/AD)", filter_mod.UnsupportedFunctionsError),
57+
("fisher(INFO/DP4)", filter_mod.UnsupportedFunctionsError),
58+
("fisher(FMT/ADF,FMT/ADR)", filter_mod.UnsupportedFunctionsError),
59+
("N_PASS(GQ>90)", filter_mod.UnsupportedFunctionsError),
5660
],
5761
)
5862
def test_unsupported_syntax(self, parser, expression, exception_class):

vcztools/filter.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ class UnsupportedSampleFilteringError(UnsupportedFilteringFeatureError):
5252
feature = "Per-sample filter expressions"
5353

5454

55+
class UnsupportedFunctionsError(UnsupportedFilteringFeatureError):
56+
issue = "190"
57+
feature = "Function evaluation"
58+
59+
5560
# The parser and evaluation model here are based on the eval_arith example
5661
# in the pyparsing docs:
5762
# https://github.com/pyparsing/pyparsing/blob/master/examples/eval_arith.py
@@ -92,13 +97,17 @@ def __init__(self, tokens):
9297
raise UnsupportedFileReferenceError()
9398

9499

100+
class Function(EvaluationNode):
101+
def __init__(self, tokens):
102+
raise UnsupportedFunctionsError()
103+
104+
95105
class Identifier(EvaluationNode):
96106
def __init__(self, mapper, tokens):
97107
self.field_name = mapper(tokens[0])
98108
if self.field_name.startswith("call_"):
99109
raise UnsupportedSampleFilteringError()
100110
logger.debug(f"Mapped {tokens[0]} to {self.field_name}")
101-
# TODO add errors for unsupported things like call_ fields etc.
102111

103112
def eval(self, data):
104113
return data[self.field_name]
@@ -250,9 +259,16 @@ def make_bcftools_filter_parser(all_fields=None, map_vcf_identifiers=True):
250259
functools.partial(Identifier, name_mapper)
251260
)
252261
indexed_identifier = indexed_identifier.set_parse_action(IndexedIdentifier)
262+
263+
expr = pp.Forward()
264+
expr_list = pp.delimited_list(pp.Group(expr))
265+
lpar, rpar = map(pp.Suppress, "()")
266+
function = pp.common.identifier() + lpar - pp.Group(expr_list) + rpar
267+
function = function.set_parse_action(Function)
268+
253269
comp_op = pp.oneOf("< = == > >= <= !=")
254270
filter_expression = pp.infix_notation(
255-
constant | indexed_identifier | identifier | file_expr,
271+
function | constant | indexed_identifier | identifier | file_expr,
256272
[
257273
("-", 1, pp.OpAssoc.RIGHT, UnaryMinus),
258274
(pp.one_of("* /"), 2, pp.OpAssoc.LEFT, BinaryOperator),
@@ -267,7 +283,8 @@ def make_bcftools_filter_parser(all_fields=None, map_vcf_identifiers=True):
267283
(pp.one_of("~ !~"), 2, pp.OpAssoc.LEFT, RegexOperator),
268284
],
269285
)
270-
return filter_expression
286+
expr <<= filter_expression
287+
return expr
271288

272289

273290
class FilterExpression:

0 commit comments

Comments
 (0)