1 import re
2 import string
3 from .pathfeatures import PathFeature, PATH_PATTERN
4 from .util import ReadableException
8
11 if not isinstance(other, LogicNode):
12 return NotImplemented
13 else:
14 return LogicGroup(self, 'AND', other)
16 if not isinstance(other, LogicNode):
17 return NotImplemented
18 else:
19 return LogicGroup(self, 'AND', other)
21 if not isinstance(other, LogicNode):
22 return NotImplemented
23 else:
24 return LogicGroup(self, 'OR', other)
25
27 LEGAL_OPS = frozenset(['AND', 'OR'])
28 - def __init__(self, left, op, right, parent=None):
29 if not op in self.LEGAL_OPS:
30 raise TypeError(op + " is not a legal logical operation")
31 self.parent = parent
32 self.left = left
33 self.right = right
34 self.op = op
35 for node in [self.left, self.right]:
36 if isinstance(node, LogicGroup):
37 node.parent = self
38
40 return '<' + self.__class__.__name__ + ': ' + str(self) + '>'
42 core = ' '.join(map(str, [self.left, self.op.lower(), self.right]))
43 return '(' + core + ')' if self.parent and self.op != self.parent.op else core
45 codes = []
46 for node in [self.left, self.right]:
47 if isinstance(node, LogicGroup):
48 codes.extend(node.get_codes())
49 else:
50 codes.append(node.code)
51 return codes
52
55
57
60
63
65 return {
66 "AND": 2,
67 "OR" : 1,
68 "(" : 3,
69 ")" : 3
70 }.get(op)
71
72 ops = {
73 "AND" : "AND",
74 "&" : "AND",
75 "&&" : "AND",
76 "OR" : "OR",
77 "|" : "OR",
78 "||" : "OR",
79 "(" : "(",
80 ")" : ")"
81 }
82
83 - def parse(self, logic_str):
84 def flatten(l):
85 ret = []
86 for item in l:
87 if isinstance(item, list):
88 ret.extend(item)
89 else:
90 ret.append(item)
91 return ret
92 logic_str = logic_str.upper()
93 tokens = re.split("\s+", logic_str)
94 tokens = flatten([self.ops[x] if x in self.ops else re.split("\b", x) for x in tokens])
95 tokens = flatten([list(x) if re.search("[()]", x) else x for x in tokens])
96 self.check_syntax(tokens)
97 postfix_tokens = self.infix_to_postfix(tokens)
98 abstract_syntax_tree = self.postfix_to_tree(postfix_tokens)
99 return abstract_syntax_tree
100
102 need_an_op = False
103 need_binary_op_or_closing_bracket = False
104 processed = []
105 open_brackets = 0
106 for token in infix_tokens:
107 if token not in self.ops:
108 if need_an_op:
109 raise LogicParseError("Expected an operator after: '" + ' '.join(processed) + "'"
110 + " - but got: '" + token + "'")
111 if need_binary_op_or_closing_bracket:
112 raise LogicParseError("Logic grouping error after: '" + ' '.join(processed) + "'"
113 + " - expected an operator or a closing bracket")
114
115 need_an_op = True
116 else:
117 need_an_op = False
118 if token == "(":
119 if processed and processed[-1] not in self.ops:
120 raise LogicParseError("Logic grouping error after: '" + ' '.join(processed) + "'"
121 + " - got an unexpeced opening bracket")
122 if need_binary_op_or_closing_bracket:
123 raise LogicParseError("Logic grouping error after: '" + ' '.join(processed) + "'"
124 + " - expected an operator or a closing bracket")
125
126 open_brackets += 1
127 elif token == ")":
128 need_binary_op_or_closing_bracket = True
129 open_brackets -= 1
130 else:
131 need_binary_op_or_closing_bracket = False
132 processed.append(token)
133 if open_brackets != 0:
134 if open_brackets < 0:
135 message = "Unmatched closing bracket in: "
136 else:
137 message = "Unmatched opening bracket in: "
138 raise LogicParseError(message + '"' + ' '.join(infix_tokens) + '"')
139
140 - def infix_to_postfix(self, infix_tokens):
141 stack = []
142 postfix_tokens = []
143 for token in infix_tokens:
144 if token not in self.ops:
145 postfix_tokens.append(token)
146 else:
147 op = token
148 if op == "(":
149 stack.append(token)
150 elif op == ")":
151 while stack:
152 last_op = stack.pop()
153 if last_op == "(":
154 if stack:
155 previous_op = stack.pop()
156 if previous_op != "(": postfix_tokens.append(previous_op)
157 break
158 else:
159 postfix_tokens.append(last_op)
160 else:
161 while stack and self.get_priority(stack[-1]) <= self.get_priority(op):
162 prev_op = stack.pop()
163 if prev_op != "(": postfix_tokens.append(prev_op)
164 stack.append(op)
165 while stack: postfix_tokens.append(stack.pop())
166 return postfix_tokens
167
168 - def postfix_to_tree(self, postfix_tokens):
169 stack = []
170 for token in postfix_tokens:
171 if token not in self.ops:
172 stack.append(token)
173 else:
174 op = token
175 right = stack.pop()
176 left = stack.pop()
177 right = right if isinstance(right, LogicGroup) else self.get_constraint(right)
178 left = left if isinstance(left, LogicGroup) else self.get_constraint(left)
179 stack.append(LogicGroup(left, op, right))
180 assert len(stack) == 1, "Tree doesn't have a unique root"
181 return stack.pop()
182
184 OPS = set([])
185 - def __init__(self, path, op, code="A"):
186 if op not in self.OPS:
187 raise TypeError(op + " not in " + str(self.OPS))
188 self.op = op
189 self.code = code
190 super(CodedConstraint, self).__init__(path)
200
202 OPS = set(['IS NULL', 'IS NOT NULL'])
203
205 OPS = set(['=', '!=', '<', '>', '<=', '>=', 'LIKE', 'NOT LIKE'])
206 - def __init__(self, path, op, value, code="A"):
209
217
219 OPS = set(['IN', 'NOT IN'])
220 - def __init__(self, path, op, list_name, code="A"):
223
231
233 OPS = set(['IS', 'IS NOT'])
234 SERIALISED_OPS = {'IS':'=', 'IS NOT':'!='}
235 - def __init__(self, path, op, loopPath, code="A"):
238
246
248 OPS = set(['LOOKUP'])
249 - def __init__(self, path, op, value, extra_value=None, code="A"):
252
254 s = super(TernaryConstraint, self).to_string()
255 if self.extra_value is None:
256 return s
257 else:
258 return " ".join([s, 'IN', self.extra_value])
260 d = super(TernaryConstraint, self).to_dict()
261 if self.extra_value is not None:
262 d.update(extraValue=self.extra_value)
263 return d
264
266 OPS = set(['ONE OF', 'NONE OF'])
267 - def __init__(self, path, op, values, code="A"):
268 if not isinstance(values, list):
269 raise TypeError("values must be a list, not " + str(type(values)))
270 self.values = values
271 super(MultiConstraint, self).__init__(path, op, code)
272
280
294
297 REQUIRED = "locked"
298 OPTIONAL_ON = "on"
299 OPTIONAL_OFF = "off"
300 - def __init__(self, editable=True, optional="locked"):
313
314 @property
316 return not self.optional
317
318 @property
320 return not self.switched_on
321
323 if not self.optional:
324 return "locked"
325 else:
326 switch = "on" if self.switched_on else "off"
327 return switch
328
330 editable = "editable" if self.editable else "non-editable"
331 return '(' + editable + ", " + self.get_switchable_status() + ')'
333 c_args = {}
334 t_args = {}
335 for k, v in args.items():
336 if k == "editable":
337 t_args[k] = v == "true"
338 elif k == "optional":
339 t_args[k] = v
340 else:
341 c_args[k] = v
342 return (c_args, t_args)
343
352
361
370
379
388
397
406
408
409 CONSTRAINT_CLASSES = set([
410 UnaryConstraint, BinaryConstraint, TernaryConstraint,
411 MultiConstraint, SubClassConstraint, LoopConstraint,
412 ListConstraint])
413
415 self._codes = iter(string.ascii_uppercase)
416
418 return self._codes.next()
419
421 for CC in self.CONSTRAINT_CLASSES:
422 try:
423 c = CC(*args, **kwargs)
424 if hasattr(c, "code"): c.code = self.get_next_code()
425 return c
426 except TypeError, e:
427 pass
428 raise TypeError("No matching constraint class found for "
429 + str(args) + ", " + str(kwargs))
430
438