@@ -29,15 +29,28 @@ class Constant(EvaluationNode):
29
29
def eval (self , data ):
30
30
return self .tokens
31
31
32
+ def __repr__ (self ):
33
+ return repr (self .tokens )
34
+
35
+ def referenced_fields (self ):
36
+ return frozenset ()
37
+
32
38
33
39
class Identifier (EvaluationNode ):
34
40
def __init__ (self , mapper , tokens ):
35
41
self .field_name = mapper (tokens [0 ])
36
42
logger .debug (f"Mapped { tokens [0 ]} to { self .field_name } " )
43
+ # TODO add errors for unsupported things like call_ fields etc.
37
44
38
45
def eval (self , data ):
39
46
return data [self .field_name ]
40
47
48
+ def __repr__ (self ):
49
+ return self .field_name
50
+
51
+ def referenced_fields (self ):
52
+ return frozenset ([self .field_name ])
53
+
41
54
42
55
class BinaryOperator (EvaluationNode ):
43
56
op_map = {
@@ -55,19 +68,31 @@ class BinaryOperator(EvaluationNode):
55
68
}
56
69
57
70
def eval (self , data ):
58
- # start by eval()'ing the first operand
59
- ret = self .tokens [0 ].eval (data )
60
-
61
- # get following operators and operands in pairs
71
+ # get the operators and operands in pairs
72
+ operands = self .tokens [0 ::2 ]
62
73
ops = self .tokens [1 ::2 ]
63
- operands = self .tokens [2 ::2 ]
64
- for op , operand in zip (ops , operands ):
65
- # print(f"Eval {op}, {ret}, {operand}")
66
- # update cumulative value by add/subtract/mult/divide the next operand
74
+ # start by eval()'ing the first operand
75
+ ret = operands [0 ].eval (data )
76
+ for op , operand in zip (ops , operands [1 :]):
67
77
arith_fn = self .op_map [op ]
68
78
ret = arith_fn (ret , operand .eval (data ))
69
79
return ret
70
80
81
+ def __repr__ (self ):
82
+ ops = self .tokens [1 ::2 ]
83
+ operands = self .tokens [0 ::2 ]
84
+ ret = f"({ repr (operands [0 ])} )"
85
+ for op , operand in zip (ops , operands [1 :]):
86
+ ret += f"{ op } ({ repr (operand )} )"
87
+ return ret
88
+
89
+ def referenced_fields (self ):
90
+ operands = self .tokens [0 ::2 ]
91
+ ret = operands [0 ].referenced_fields ()
92
+ for operand in operands [1 :]:
93
+ ret |= operand .referenced_fields ()
94
+ return ret
95
+
71
96
72
97
class ComparisonOperator (EvaluationNode ):
73
98
op_map = {
@@ -85,6 +110,14 @@ def eval(self, data):
85
110
comparison_fn = self .op_map [op ]
86
111
return comparison_fn (op1 .eval (data ), op2 .eval (data ))
87
112
113
+ def __repr__ (self ):
114
+ op1 , op , op2 = self .tokens
115
+ return f"({ repr (op1 )} ){ op } ({ repr (op2 )} )"
116
+
117
+ def referenced_fields (self ):
118
+ op1 , _ , op2 = self .tokens
119
+ return op1 .referenced_fields () | op2 .referenced_fields ()
120
+
88
121
89
122
def _identity (x ):
90
123
return x
@@ -110,6 +143,7 @@ def make_bcftools_filter_parser(all_fields=None, map_vcf_identifiers=True):
110
143
filter_expression = pp .infix_notation (
111
144
constant | identifier ,
112
145
[
146
+ # FIXME Does bcftools support unary minus?
113
147
# ("-", 1, pp.OpAssoc.RIGHT, ),
114
148
(pp .one_of ("* /" ), 2 , pp .OpAssoc .LEFT , BinaryOperator ),
115
149
(pp .one_of ("+ -" ), 2 , pp .OpAssoc .LEFT , BinaryOperator ),
@@ -128,6 +162,7 @@ def __init__(self, *, field_names=None, include=None, exclude=None):
128
162
if field_names is None :
129
163
field_names = set ()
130
164
self .parse_result = None
165
+ self .referenced_fields = set ()
131
166
self .invert = False
132
167
expr = None
133
168
if include is not None and exclude is not None :
@@ -144,9 +179,8 @@ def __init__(self, *, field_names=None, include=None, exclude=None):
144
179
if expr is not None :
145
180
parser = make_bcftools_filter_parser (field_names )
146
181
self .parse_result = parser .parse_string (expr , parse_all = True )
147
-
148
- # Setting to None for now so that we retrieve all fields
149
- self .referenced_fields = None
182
+ # This isn't a very good pattern, fix
183
+ self .referenced_fields = self .parse_result [0 ].referenced_fields ()
150
184
151
185
def evaluate (self , chunk_data ):
152
186
if self .parse_result is None :
0 commit comments