38 return rules.RELATIONS.get(relation_name) |
38 return rules.RELATIONS.get(relation_name) |
39 except KeyError: |
39 except KeyError: |
40 raise exceptions.NotSupportedException("No Relation class found for name %s" % relation_name) |
40 raise exceptions.NotSupportedException("No Relation class found for name %s" % relation_name) |
41 |
41 |
42 @ classmethod |
42 @ classmethod |
43 def get_relations(cls, configuration, relation): |
43 def get_relations(cls, relation): |
44 try: |
44 try: |
45 relations = [] |
45 relations = [] |
46 (left_expression,relation_name,right_expression) = parse_rule(relation) |
46 (left_expression,relation_name,right_expression) = parse_rule(relation) |
47 relation = cls.get_relation_by_name(relation_name)(configuration, left_expression, right_expression) |
47 relation = cls.get_relation_by_name(relation_name)(left_expression, right_expression) |
48 relations.append(relation) |
48 relations.append(relation) |
49 propagated_relations = cls.get_relations(configuration, right_expression) |
49 propagated_relations = cls.get_relations(right_expression) |
50 if propagated_relations: |
50 if propagated_relations: |
51 for relation in propagated_relations: |
51 for relation in propagated_relations: |
52 relations.append(relation) |
52 relations.append(relation) |
53 return relations |
53 return relations |
54 except exceptions.ParseError: |
54 except exceptions.ParseError: |
55 return None |
55 return None |
56 |
56 |
57 |
57 |
58 class ConfigurationContext(rules.DefaultContext): |
|
59 |
|
60 def __init__(self, data): |
|
61 rules.DefaultContext.__init__(self, data) |
|
62 |
|
63 # A note on the callback variables that follow: |
|
64 # in order to collect rule execution results for the |
|
65 # generation report, a callback system is implemented in |
|
66 # ConfigurationContext. It all boils down to ConfigureRelation |
|
67 # using configure_expression_result_callback to catch the result |
|
68 # in its execute() method. The ConfigurationContext just works as |
|
69 # a "callback hub" because a reference to the context is available |
|
70 # in all expression evaluations. |
|
71 |
|
72 # Callback called with a plugin.RelationExecutionResult object |
|
73 # when a ConfigureExpression has been evaluated |
|
74 self.configure_expression_result_callback = None |
|
75 |
|
76 # Callback called with the setting reference when a setting is dereferenced |
|
77 # as a terminal expression |
|
78 self.ref_terminal_callback = None |
|
79 |
|
80 # Callback called with the setting reference when a setting is dereferenced |
|
81 # inside an EvalExpression |
|
82 self.ref_eval_callback = None |
|
83 |
|
84 # Callback called with the setting reference when the value of a setting |
|
85 # is set inside a SetExpression |
|
86 self.ref_set_callback = None |
|
87 |
|
88 def handle_terminal(self, expression): |
|
89 try: |
|
90 value = self.data.get_default_view().get_feature(expression).get_value() |
|
91 |
|
92 # Got a valid ref, call the callback |
|
93 if self.ref_terminal_callback: |
|
94 self.ref_terminal_callback(expression) |
|
95 |
|
96 return value |
|
97 except exceptions.NotFound,e: |
|
98 """ return the expression itself if it is not a fearef """ |
|
99 #print "handle_terminal constant %s" % (expression) |
|
100 try: |
|
101 return eval(expression) |
|
102 except (NameError,SyntaxError), e: |
|
103 return expression |
|
104 |
|
105 def eval(self, ast, expression, value): |
|
106 #print "expression %s = %s" % (expression,value) |
|
107 pass |
|
108 |
|
109 class ConfigurationBaseRelation(rules.BaseRelation): |
58 class ConfigurationBaseRelation(rules.BaseRelation): |
110 def __init__(self, data, left, right): |
59 def __init__(self, left, right): |
111 self.context = ConfigurationContext(data) |
60 super(ConfigurationBaseRelation, self).__init__(None, left, right) |
112 super(ConfigurationBaseRelation, self).__init__(data, left, right) |
61 self.context = None |
113 |
62 |
114 class RequireRelation(ConfigurationBaseRelation): |
63 class RequireRelation(ConfigurationBaseRelation): |
115 KEY = 'requires' |
64 KEY = 'requires' |
116 def __init__(self, data, left, right): |
65 relation_name = 'requires' |
117 super(RequireRelation, self).__init__(data, left, right) |
66 def __init__(self, left, right): |
118 self.context = ConfigurationContext(data) |
67 super(RequireRelation, self).__init__(left, right) |
119 |
68 |
120 class ConfigureRelation(ConfigurationBaseRelation): |
69 class ConfigureRelation(ConfigurationBaseRelation): |
121 KEY = 'configures' |
70 KEY = 'configures' |
122 def __init__(self, data, left, right): |
71 relation_name = 'configures' |
123 self.context = ConfigurationContext(data) |
72 def __init__(self, left, right): |
124 super(ConfigureRelation, self).__init__(data, left, right) |
73 super(ConfigureRelation, self).__init__(left, right) |
125 |
74 |
126 # A plugin.RelationExecutionResult object is stored here |
75 # A plugin.RelationExecutionResult object is stored here |
127 self._execution_result = None |
76 self._execution_result = None |
128 |
77 |
129 |
78 |
130 def execute(self): |
79 def execute(self, context): |
131 self._execution_result = None |
80 self._execution_result = None |
132 exec_results = [] |
81 exec_results = [] |
133 |
82 |
134 # Call BaseRelation.execute() and catch any ConfigureExpression result objects |
83 result = rules.BaseRelation.execute(self, context) |
135 self.context.configure_expression_result_callback = lambda result: exec_results.append(result) |
|
136 result = rules.BaseRelation.execute(self) |
|
137 self.context.configure_expression_result_callback = None |
|
138 |
84 |
139 if len(exec_results) > 0: |
85 if len(exec_results) > 0: |
140 # There should be only one ConfigureExpression inside a ConfigureRelation |
86 # There should be only one ConfigureExpression inside a ConfigureRelation |
141 if len(exec_results) > 1: |
87 if len(exec_results) > 1: |
142 log.warning("Execution of ConfigureRelation returned more than one result, ignoring all except the first") |
88 log.warning("Execution of ConfigureRelation returned more than one result, ignoring all except the first") |
172 if pos == -1: return path |
115 if pos == -1: return path |
173 else: return path[pos + 1:] |
116 else: return path[pos + 1:] |
174 |
117 |
175 return extract_dirname(left) + extract_filename(right) |
118 return extract_dirname(left) + extract_filename(right) |
176 |
119 |
177 def handle_plus(self, left, right): |
|
178 return left + right |
|
179 |
|
180 def handle_minus(self, left, right): |
|
181 return left - right |
|
182 |
|
183 def handle_multiply(self, left, right): |
|
184 return left * right |
|
185 |
|
186 def handle_divide(self, left, right): |
|
187 return left / right |
|
188 |
120 |
189 class ConfigureExpression(rules.TwoOperatorExpression): |
121 class ConfigureExpression(rules.TwoOperatorExpression): |
190 PRECEDENCE = rules.PRECEDENCES['RELATION_OPERATORS'] |
122 PRECEDENCE = rules.PRECEDENCES['RELATION_OPERATORS'] |
191 KEY = 'configures' |
123 KEY = 'configures' |
192 OP = handle_configure |
124 OP = handle_configure |
193 |
125 |
194 def eval(self, context): |
126 def eval(self, context, **kwargs): |
195 input_refs = [] |
127 input_refs = [] |
196 affected_refs = [] |
128 affected_refs = [] |
197 |
129 |
198 # Evaluate the left-hand side expression, catching refs for the result |
130 # Evaluate the left-hand side expression |
199 try: |
131 evaluated_left = self.left.eval(context, **kwargs) |
200 context.ref_terminal_callback = lambda ref: input_refs.append(ref) |
|
201 context.ref_eval_callback = lambda ref: input_refs.append(ref) |
|
202 evaluated_left = self.left.eval(context) |
|
203 finally: |
|
204 context.ref_terminal_callback = None |
|
205 context.ref_eval_callback = None |
|
206 |
|
207 if evaluated_left: |
132 if evaluated_left: |
208 # If left evaluated to True, evaluate the right-hand side and |
133 # If left evaluated to True, evaluate the right-hand side |
209 # catch refs from SetExpression evaluations |
134 self.value = self.right.eval(context, **kwargs) |
210 try: |
|
211 context.ref_set_callback = lambda ref: affected_refs.append(ref) |
|
212 self.value = self.right.eval(context) |
|
213 finally: |
|
214 context.ref_set_callback = None |
|
215 else: |
135 else: |
216 self.value = True |
136 self.value = True |
217 |
137 |
218 if not self.value: |
138 if not self.value: |
219 left_keys = [] |
139 left_keys = [] |
220 for ref in self.ast.extract_refs(str(self.left)): |
140 for ref in self.ast.extract_refs(str(self.left)): |
221 for key in context.get_keys(ref): |
141 for key in context.get_keys(ref): |
222 left_keys.append(key) |
142 left_keys.append(key) |
223 |
|
224 for key in left_keys: |
143 for key in left_keys: |
225 self.ast.add_error(key, { 'error_string' : 'CONFIGURES right side value is "False"', |
144 self.ast.add_error(key, { 'error_string' : 'CONFIGURES right side value is "False"', |
226 'left_key' : key, |
145 'left_key' : key, |
227 'rule' : self.ast.expression |
146 'rule' : self.ast.expression |
228 }) |
147 }) |
229 |
|
230 # Return a RelationExecutionResult if necessary |
|
231 if input_refs or affected_refs: |
|
232 if context.configure_expression_result_callback: |
|
233 result = plugin.RelationExecutionResult(utils.distinct_array(input_refs), |
|
234 utils.distinct_array(affected_refs)) |
|
235 context.configure_expression_result_callback(result) |
|
236 |
|
237 return self.value |
148 return self.value |
238 |
|
239 class MultiplyExpression(rules.TwoOperatorExpression): |
|
240 expression = "multiply_operation" |
|
241 PRECEDENCE = rules.PRECEDENCES['MULDIV_OPERATORS'] |
|
242 KEY= '*' |
|
243 OP = handle_multiply |
|
244 |
|
245 class DivideExpression(rules.TwoOperatorExpression): |
|
246 expression = "divide_operation" |
|
247 PRECEDENCE = rules.PRECEDENCES['MULDIV_OPERATORS'] |
|
248 KEY= '/' |
|
249 OP = handle_divide |
|
250 |
|
251 class PlusExpression(rules.TwoOperatorExpression): |
|
252 expression = "plus_operation" |
|
253 PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS'] |
|
254 KEY= '+' |
|
255 OP = handle_plus |
|
256 |
|
257 class MinusExpression(rules.TwoOperatorExpression): |
|
258 expression = "minus_operation" |
|
259 PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS'] |
|
260 KEY= '-' |
|
261 OP = handle_minus |
|
262 |
149 |
263 class EvalExpression(rules.OneParamExpression): |
150 class EvalExpression(rules.OneParamExpression): |
264 expression = "__eval__" |
151 expression = "__eval__" |
265 PRECEDENCE = rules.PRECEDENCES['PREFIX_OPERATORS'] |
152 PRECEDENCE = rules.PRECEDENCES['PREFIX_OPERATORS'] |
266 KEY = '__eval__' |
153 KEY = '__eval__' |
267 |
154 |
268 def __init__(self, ast, expression): |
155 def __init__(self, ast, expression): |
269 super(rules.OneParamExpression, self).__init__(ast) |
156 super(EvalExpression, self).__init__(ast, expression) |
270 self.expression = expression |
157 self.expression = expression |
271 self._str_to_eval = eval(expression.expression) |
158 self._str_to_eval = eval(expression.expression) |
272 #self.default_view = default_view |
159 #self.default_view = default_view |
273 |
160 |
274 def extract_refs(self): |
161 def extract_refs(self): |
275 result = [] |
162 result = [] |
276 result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('${', '}'))) |
163 result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('${', '}'))) |
277 result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('@{', '}'))) |
164 result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('@{', '}'))) |
278 return result |
165 return result |
279 |
166 |
280 def eval(self, context): |
167 def get_refs(self): |
|
168 return self.extract_refs() |
|
169 |
|
170 def eval(self, context, **kwargs): |
281 # Using the configuration to pass the eval globals dictionary to here, |
171 # Using the configuration to pass the eval globals dictionary to here, |
282 # since there isn't any easy way to do this more elegantly |
172 # since there isn't any easy way to do this more elegantly |
283 globals_and_locals = {} |
173 globals_and_locals = {} |
284 if hasattr(context.data, '_eval_expression_globals_dict'): |
174 if hasattr(context, '_eval_expression_globals_dict'): |
285 globals_and_locals = context.data._eval_expression_globals_dict |
175 globals_and_locals = context._eval_expression_globals_dict |
286 |
176 |
287 str_to_eval = self._str_to_eval |
177 str_to_eval = self._str_to_eval |
288 |
178 |
289 def expand_feature_ref(ref, index): |
179 def expand_feature_ref(ref, index): |
290 var_name = "__fea_%05d" % index |
180 var_name = "__fea_%05d" % index |
291 globals_and_locals[var_name] = context.data.get_default_view().get_feature(ref) |
181 globals_and_locals[var_name] = context.configuration.get_default_view().get_feature(ref) |
292 if context.ref_eval_callback: |
|
293 context.ref_eval_callback(ref) |
|
294 return var_name |
182 return var_name |
|
183 |
295 def expand_value_ref(ref, index): |
184 def expand_value_ref(ref, index): |
296 var_name = "__feaval_%05d" % index |
185 var_name = "__feaval_%05d" % index |
297 globals_and_locals[var_name] = context.data.get_default_view().get_feature(ref).get_value() |
186 globals_and_locals[var_name] = context.configuration.get_default_view().get_feature(ref).get_value() |
298 if context.ref_eval_callback: |
|
299 context.ref_eval_callback(ref) |
|
300 return var_name |
187 return var_name |
301 |
188 |
302 str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_feature_ref, delimiters=('@{', '}')) |
189 str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_feature_ref, delimiters=('@{', '}')) |
303 str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_value_ref, delimiters=('${', '}')) |
190 str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_value_ref, delimiters=('${', '}')) |
304 |
191 |
315 self.ast.add_error(self.expression, { 'error_string' : 'Invalid syntax in eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression }) |
202 self.ast.add_error(self.expression, { 'error_string' : 'Invalid syntax in eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression }) |
316 except Exception, e: |
203 except Exception, e: |
317 logging.getLogger('cone.ruleml').warning("Execution failed for eval: %s %s: %s" % (str_to_eval, type(e), e) ) |
204 logging.getLogger('cone.ruleml').warning("Execution failed for eval: %s %s: %s" % (str_to_eval, type(e), e) ) |
318 self.ast.add_error(self.expression, { 'error_string' : 'Execution failed for eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression }) |
205 self.ast.add_error(self.expression, { 'error_string' : 'Execution failed for eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression }) |
319 |
206 |
320 rules.OPERATORS[EvalExpression.KEY] = EvalExpression |
|
321 |
207 |
322 class FilenamejoinExpression(rules.TwoOperatorExpression): |
208 class FilenamejoinExpression(rules.TwoOperatorExpression): |
323 expression = "filenamejoin" |
209 expression = "filenamejoin" |
324 PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS'] |
210 PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS'] |
325 KEY = 'filenamejoin' |
211 KEY = 'filenamejoin' |
326 OP = handle_filenamejoin |
212 OP = handle_filenamejoin |
327 |
213 |
|
214 # Register relations and operators to rules |
|
215 rules.RELATIONS[RequireRelation.KEY] = RequireRelation |
|
216 rules.RELATIONS[ConfigureRelation.KEY] = ConfigureRelation |
328 rules.OPERATORS[FilenamejoinExpression.KEY] = FilenamejoinExpression |
217 rules.OPERATORS[FilenamejoinExpression.KEY] = FilenamejoinExpression |
329 |
218 rules.OPERATORS[EvalExpression.KEY] = EvalExpression |
330 class SetExpression(rules.TwoOperatorExpression): |
219 rules.OPERATORS[ConfigureExpression.KEY] = ConfigureExpression |
331 PRECEDENCE = rules.PRECEDENCES['SET_OPERATORS'] |
|
332 KEY= '=' |
|
333 OP = handle_set |
|
334 |
|
335 def eval(self, context): |
|
336 try: |
|
337 variable = context.data.get_default_view().get_feature(self.left.expression) |
|
338 value = self.right.eval(context) |
|
339 variable.set_value(value) |
|
340 logging.getLogger('cone.ruleml').info("Set %r = %r from %r" % (self.left.expression, value, self.right.expression) ) |
|
341 if context.ref_set_callback: |
|
342 context.ref_set_callback(self.left.expression) |
|
343 return True |
|
344 except exceptions.NotFound,e: |
|
345 self.ast.add_error(self.left.expression, { 'error_string' : 'Setting value failed, because of %s' % e, |
|
346 'left_key' : self.left.expression, |
|
347 'rule' : self.ast.expression}) |
|
348 return False |
|
349 |
|
350 _relations_and_operators_backup = None |
|
351 |
|
352 def register(): |
|
353 """ |
|
354 Register the relations and operators to ConE rules. |
|
355 """ |
|
356 global _relations_and_operators_backup |
|
357 if _relations_and_operators_backup is None: |
|
358 # Create the backup copies of the dictionaries |
|
359 rels_backup = rules.RELATIONS.copy() |
|
360 ops_backup = rules.OPERATORS.copy() |
|
361 assert rels_backup is not rules.RELATIONS |
|
362 assert ops_backup is not rules.OPERATORS |
|
363 _relations_and_operators_backup = (rels_backup, ops_backup) |
|
364 |
|
365 # Register relations and operators to rules |
|
366 rules.RELATIONS[RequireRelation.KEY] = RequireRelation |
|
367 rules.RELATIONS[ConfigureRelation.KEY] = ConfigureRelation |
|
368 rules.OPERATORS[ConfigureExpression.KEY] = ConfigureExpression |
|
369 rules.OPERATORS[PlusExpression.KEY] = PlusExpression |
|
370 rules.OPERATORS[SetExpression.KEY] = SetExpression |
|
371 rules.OPERATORS[MinusExpression.KEY] = MinusExpression |
|
372 rules.OPERATORS[MultiplyExpression.KEY] = MultiplyExpression |
|
373 rules.OPERATORS[DivideExpression.KEY] = DivideExpression |
|
374 |
|
375 def unregister(): |
|
376 """ |
|
377 Undo the changes made by a call to register(). |
|
378 """ |
|
379 global _relations_and_operators_backup |
|
380 if _relations_and_operators_backup is not None: |
|
381 rules.RELATIONS = _relations_and_operators_backup[0] |
|
382 rules.OPERATORS = _relations_and_operators_backup[1] |
|
383 _relations_and_operators_backup = None |
|
384 |
220 |
385 def parse_rule(rulestring): |
221 def parse_rule(rulestring): |
386 """ |
222 """ |
387 Divide the given rule string into (left side, relation, right side) components. |
223 Divide the given rule string into (left side, relation, right side) components. |
388 @return: Triple (left side, relation, right side) |
224 @return: Triple (left side, relation, right side) |