configurationengine/source/plugins/common/ConeLegacyRulePlugin/legacyruleplugin/rules.py
changeset 3 e7e0ae78773e
equal deleted inserted replaced
2:87cfa131b535 3:e7e0ae78773e
       
     1 #
       
     2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     3 # All rights reserved.
       
     4 # This component and the accompanying materials are made available
       
     5 # under the terms of "Eclipse Public License v1.0"
       
     6 # which accompanies this distribution, and is available
       
     7 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 #
       
     9 # Initial Contributors:
       
    10 # Nokia Corporation - initial contribution.
       
    11 #
       
    12 # Contributors:
       
    13 #
       
    14 # Description: 
       
    15 # 
       
    16 
       
    17 import operator as ops
       
    18 import logging
       
    19 import tokenize
       
    20 from token import ENDMARKER, NAME, ERRORTOKEN
       
    21 import StringIO
       
    22 
       
    23 from cone.public import container
       
    24 
       
    25 RELATIONS = {}
       
    26 
       
    27 def abstract():
       
    28     import inspect
       
    29     caller = inspect.getouterframes(inspect.currentframe())[1][3]
       
    30     raise NotImplementedError(caller + ' needs to be implemented')
       
    31 
       
    32 def get_tokens(tokenstr):
       
    33     result = []
       
    34     tokens = []
       
    35     tokenstr = tokenstr.replace('\r', '')
       
    36     name_buffer = [] # Temp buffer for reading name tokens
       
    37     last_epos = None
       
    38     for toknum, tokval, spos, epos, _  in tokenize.generate_tokens(StringIO.StringIO(unicode(tokenstr)).readline):
       
    39         #print "toknum: %r, tokval: %r, spos: %r, epos: %r" % (toknum, tokval, spos, epos)
       
    40         val = tokval.strip('\r\n\t ')
       
    41         
       
    42         if toknum == ENDMARKER and name_buffer:
       
    43             tokens.append(''.join(name_buffer))
       
    44         
       
    45         # Ignore whitespace (this ignores also the end marker,
       
    46         # since its value is empty)
       
    47         if val == '': continue
       
    48         
       
    49         # Put NAME, and ERRORTOKEN tokens through the temp
       
    50         # buffer
       
    51         if toknum in (NAME, ERRORTOKEN):
       
    52             # If this and the previous token in the temp buffer are not adjacent,
       
    53             # they belong to separate tokens
       
    54             if name_buffer and spos[1] != last_epos[1]:
       
    55                 tokens.append(''.join(name_buffer))
       
    56                 name_buffer = []
       
    57             
       
    58             name_buffer.append(val)
       
    59             last_epos = epos
       
    60         # Other tokens can just go directly to the token list
       
    61         else:
       
    62             if name_buffer:
       
    63                 tokens.append(''.join(name_buffer))
       
    64                 name_buffer = []
       
    65             tokens.append(val)
       
    66     
       
    67     while len(tokens) > 0:
       
    68         val = tokens.pop(0)
       
    69         # Join the refs with dot in between them to make them dotted refs
       
    70         if val == '.':
       
    71             newval = ".".join([result.pop(),tokens.pop(0)])
       
    72             result.append( newval )
       
    73         else:
       
    74             result.append( val )
       
    75 
       
    76     return result
       
    77 
       
    78 class RelationException(Exception):
       
    79     pass
       
    80 
       
    81 #### The containers are here ####
       
    82 
       
    83 
       
    84 class RelationBase(object):
       
    85     """
       
    86     RelationBase defines a base class for all named relations that can be applied between objects. 
       
    87     e.g. Relation depends, that can be applied in Rule
       
    88     """
       
    89     relation_name = "RelationBase"
       
    90     def __init__(self, data, left, right):
       
    91         self.description = ""
       
    92         self.data = data or container.DataContainer()
       
    93         self.left = left
       
    94         self.right = right
       
    95 
       
    96     def __str__(self):
       
    97         """
       
    98         @return: A string presentation of the relation object
       
    99         """
       
   100         return "%s %s %s" % (self.left,self.relation_name,self.right)
       
   101     
       
   102     def __repr__(self):
       
   103         return "%s(ref=%r, lineno=%r)" % (self.__class__.__name__,
       
   104                                           getattr(self, 'ref', None),
       
   105                                           getattr(self, 'lineno', None))
       
   106 
       
   107     def get_name(self):
       
   108         """
       
   109         @return: The relation name.
       
   110         """
       
   111         return self.relation_name
       
   112 
       
   113     def get_description(self):
       
   114         """
       
   115         @return: a possible description of the relation.
       
   116         """
       
   117         return self.description
       
   118         
       
   119     def execute(self):
       
   120         """
       
   121         Execute the relation object.
       
   122         """
       
   123         pass
       
   124 
       
   125 
       
   126 class RelationContainer(RelationBase, list):
       
   127     """
       
   128     This class provides the RelationContainer interface for collecting
       
   129     relation sets into one place which can be executed through the container.
       
   130     """
       
   131     def __init__(self, data=None):
       
   132         super(RelationContainer, self).__init__(data, 'LContainer', 'RContainer')
       
   133         self.value_list = list()
       
   134 
       
   135     def append(self, value):
       
   136         self.value_list.append(value)
       
   137 
       
   138     def __iter__(self):
       
   139         return self.value_list.__iter__()
       
   140 
       
   141     def __len__(self):
       
   142         return len(self.value_list)
       
   143 
       
   144     def __or__(self, other):
       
   145         """
       
   146         This function adds two RelationContainers to each other and removes
       
   147         duplicates from the result.
       
   148         The modification is inplace and the returned value is called object.
       
   149         """
       
   150         self.value_list = set(self.value_list) | set(other.value_list)
       
   151         return self
       
   152 
       
   153     def __unicode__(self):
       
   154         if self:
       
   155             ret = ''
       
   156             for value in self:
       
   157                 ret += unicode(value)
       
   158         else:
       
   159             ret = 'No relations'
       
   160         return ret
       
   161 
       
   162     def find_relations(self, refs): abstract()
       
   163     def add_relation(self, relation): abstract()
       
   164     def has_errors(self): abstract()
       
   165 
       
   166 class RelationContainerImpl(RelationContainer):
       
   167     """ Base implementation for RelationContainer to use in ConE rules
       
   168     """
       
   169     def execute(self):
       
   170         ret = True
       
   171         i = 0
       
   172         for relation in self:
       
   173             i += 1
       
   174             r = relation.execute()
       
   175             ret = ret and r
       
   176         return ret
       
   177 
       
   178     def find_relations(self, refs):
       
   179         relations = []
       
   180         for ref in refs:
       
   181             for relation in self:
       
   182                 if relation.has_ref(ref):
       
   183                     relations.append(relation)
       
   184         return relations
       
   185 
       
   186     def add_relation(self, relation):
       
   187         self.append(relation)
       
   188 
       
   189     def has_ref(self, refs):
       
   190         for ref in refs:
       
   191             for relation in self:
       
   192                 if relation.has_ref(ref):
       
   193                     return True
       
   194         return False
       
   195 
       
   196     def has_errors(self):
       
   197         for relation in self:
       
   198             if relation.has_errors():
       
   199                 return True
       
   200         return False
       
   201             
       
   202     def get_errors(self):
       
   203         errors = []
       
   204         for relation in self:
       
   205             errors += relation.get_errors()
       
   206         return errors
       
   207 
       
   208 #### The relations are here ####
       
   209 
       
   210 class BaseRelation(RelationBase):
       
   211     """ BaseRelation implements the basic evaluation logic for relations
       
   212     This class abstract and should be extended for concrete implementation of
       
   213     relation type.
       
   214 
       
   215     Subclasses need to set their own context in their constructor before this
       
   216     class's constructor is called if custom context is needed. If context not
       
   217     set then DefaultContext is used.
       
   218     """
       
   219     KEY = 'base_relation'
       
   220 
       
   221     def __init__(self, data, left, right):
       
   222         # Context needs to be overridden for special purposes
       
   223         try:
       
   224             self.__getattribute__('context')
       
   225         except AttributeError:
       
   226             self.context = DefaultContext(data)
       
   227 
       
   228         left = self.expand_rule_elements(left)
       
   229         right = self.expand_rule_elements(right)
       
   230         super(BaseRelation, self).__init__(data, left, right)
       
   231         self.interpreter = ASTInterpreter(context=self.context)
       
   232 
       
   233     def execute(self, context=None):
       
   234         """
       
   235         @return Returns error dictionary
       
   236 
       
   237         In the client code proper way to check if the rule applies:
       
   238         info = relation.execute()
       
   239         if not info.has_errors():
       
   240         else: HANDLE ERRORS
       
   241         """
       
   242         # logger.debug("Interpreter context %s" % self.interpreter.context)
       
   243         self.interpreter.create_ast('%s %s %s' % (self.left, self.KEY, self.right))
       
   244         ret = self.interpreter.eval(context, relation=self)
       
   245         return ret
       
   246 
       
   247     def get_keys(self):
       
   248         """ Returns the references from this relation.
       
   249         """
       
   250         refs = ASTInterpreter.extract_refs(self.left)
       
   251         refs += ASTInterpreter.extract_refs(self.right)
       
   252         return refs
       
   253 
       
   254     def has_ref(self, ref):
       
   255         """ Returns if the 'ref' is included in this relation
       
   256         """
       
   257         return ref in self.get_keys()
       
   258 
       
   259     def has_errors(self):
       
   260         return bool(self.interpreter.errors)
       
   261 
       
   262     def get_refs(self):
       
   263         extracted_refs = ASTInterpreter.extract_refs(self.left)
       
   264         
       
   265         # Handle eval expressions (a somewhat ugly hack,
       
   266         # but this is a legacy plug-in so...)
       
   267         interpreter = ASTInterpreter(self.left)
       
   268         for expression in interpreter.parse_tree:
       
   269             if hasattr(expression, 'extract_refs'):
       
   270                 extracted_refs.extend(expression.extract_refs())
       
   271         
       
   272         # Filter out entries that evaluate successfully as Python code
       
   273         # e.g. True or 123
       
   274         result = []
       
   275         for item in extracted_refs:
       
   276             try:    eval(item)
       
   277             except: result.append(item)
       
   278             else:   pass
       
   279         return result
       
   280 
       
   281     def _eval_rside_value(self, value): abstract()
       
   282     def _compare_value(self, value): abstract()
       
   283 
       
   284     def extract_erroneus_features_with_values(self):
       
   285         """
       
   286         Extract references who have errors.
       
   287 
       
   288         Returns dictionary { 'reference' : 'value' }
       
   289         """
       
   290         data_dict = {}
       
   291         for ref in ASTInterpreter.extract_refs(self.right):
       
   292             value = self.data.get_feature(ref)
       
   293             if self._compare_value(value):
       
   294                 data_dict[ref] = value
       
   295             elif value == None:
       
   296                 data_dict[ref] = None
       
   297         return data_dict
       
   298 
       
   299     def get_errors(self):
       
   300         return self.interpreter.errors
       
   301 
       
   302     def expand_rule_elements(self, rule):
       
   303         """ Expans rule elements base on the reference.
       
   304         Context is used for fetching the child elements for parent references
       
   305         which uses asterisk identifier for selecting all child features: 
       
   306         'parent_feature.*' -> 'child_fea_1 and child_fea_2'.
       
   307         """
       
   308         tokens = get_tokens(rule) # [token for token in rule.split()]
       
   309 
       
   310         expanded_rule = ""
       
   311         for token in tokens:
       
   312             if token.endswith('.*'):
       
   313                 index = token.index('.*')
       
   314                 parent_ref = token[:index]
       
   315                 children = self.context.get_children_for_reference(parent_ref)
       
   316                 expanded_element = ' and '.join([child.reference for child in children])
       
   317                 if expanded_rule:
       
   318                     expanded_rule = '%s and %s' % (expanded_rule, expanded_element.rstrip())
       
   319                 else:
       
   320                     expanded_rule = expanded_element.rstrip()
       
   321             elif token.lower() in OPERATORS:
       
   322                 expanded_rule += ' %s ' % token
       
   323             else:
       
   324                 if expanded_rule:
       
   325                     expanded_rule += '%s'% token
       
   326                 else:
       
   327                     expanded_rule = token
       
   328         return expanded_rule.strip()
       
   329 
       
   330 class RequireRelation(BaseRelation):
       
   331     KEY = 'requires'
       
   332 RELATIONS[RequireRelation.KEY] = RequireRelation
       
   333 
       
   334 class ExcludesRelation(BaseRelation):
       
   335     KEY = 'excludes'
       
   336 
       
   337 RELATIONS['excludes'] = ExcludesRelation
       
   338 
       
   339 ################################
       
   340 # Abstract syntax tree builder #
       
   341 ################################
       
   342 
       
   343 def nor(expression, a, b):
       
   344     return not ops.or_(a, b)
       
   345 
       
   346 def nand(expression, a, b):
       
   347     return not ops.and_(a, b)
       
   348 
       
   349 def truth_and(expression, a, b):
       
   350     return ops.truth(a) and ops.truth(b)
       
   351 
       
   352 class DefaultContext(object):
       
   353     """ DefaultContext implements ConE specific context for handling rules
       
   354     """
       
   355     def __init__(self, data):
       
   356         self.data = data
       
   357 
       
   358     def eval(self, ast, expression, value):
       
   359         pass
       
   360 
       
   361     def get_keys(self, refs):
       
   362         return ASTInterpreter.extract_refs(refs)
       
   363 
       
   364     def get_children_for_reference(self, reference):
       
   365         # implement ConE specific children expansion
       
   366         pass
       
   367 
       
   368     def handle_terminal(self, expression):
       
   369         try:
       
   370             return int(expression)
       
   371         except:
       
   372             return expression
       
   373 
       
   374 PRECEDENCES = {
       
   375     'PREFIX_OPERATORS' : 10,
       
   376     'MULDIV_OPERATORS' : 8,
       
   377     'ADDSUB_OPERATORS' : 7,
       
   378     'SHIFT_OPERATORS' : 6,
       
   379     'BITWISE_OPERATORS' : 5,
       
   380     'COMPARISON_OPERATORS' : 4,
       
   381     'SET_OPERATORS' : 3,
       
   382     'BOOLEAN_OPERATORS' : 2, 
       
   383     'RELATION_OPERATORS' : 1,
       
   384     'NOT_DEFINED' : 0
       
   385 }
       
   386 
       
   387 class Expression(object):
       
   388     PRECEDENCE = PRECEDENCES['NOT_DEFINED']
       
   389     KEY = 'base_expression'
       
   390 
       
   391     def __init__(self, ast):
       
   392         self.ast = ast
       
   393         self.value = None
       
   394 
       
   395     def get_title(self):
       
   396         return self.KEY
       
   397 
       
   398     def eval(self, context, **kwargs): pass
       
   399 
       
   400 class OneParamExpression(Expression):
       
   401     PARAM_COUNT = 1
       
   402     def __init__(self, ast, expression):
       
   403         super(OneParamExpression, self).__init__(ast)
       
   404         self.expression = expression
       
   405 
       
   406     def __unicode__(self):
       
   407         return u'%s %s' % (self.KEY, self.expression)
       
   408 
       
   409     def eval(self, context, **kwargs):
       
   410         self.value = self.OP(self.expression.eval(context, **kwargs))
       
   411         context.eval(self.ast, self, self.value)
       
   412         return self.value
       
   413 
       
   414 class TwoOperatorExpression(Expression):
       
   415     PARAM_COUNT = 2
       
   416     OP = None
       
   417     EVAL_AS_BOOLS = True
       
   418 
       
   419     def __init__(self, ast, left, right):
       
   420         super(TwoOperatorExpression, self).__init__(ast)
       
   421         self.left = left
       
   422         self.right = right
       
   423 
       
   424     def __unicode__(self):
       
   425         return u'%s %s %s' % (self.left, self.KEY, self.right)
       
   426 
       
   427     def eval(self, context, **kwargs):
       
   428         self.value = self.OP(self.left.eval(context, **kwargs), self.right.eval(context, **kwargs))
       
   429         context.eval(self.ast, self, self.value)
       
   430         return self.value
       
   431 
       
   432 class TwoOperatorBooleanExpression(TwoOperatorExpression):
       
   433     def eval(self, context, **kwargs):
       
   434         self.value = self.OP(bool(self.left.eval(context, **kwargs)), bool(self.right.eval(context, **kwargs)))
       
   435         context.eval(self.ast, self, self.value)
       
   436         return self.value         
       
   437 
       
   438 class TerminalExpression(Expression):
       
   439     KEY = 'terminal'
       
   440 
       
   441     def __init__(self, ast, expression):
       
   442         super(TerminalExpression, self).__init__(ast)
       
   443         self.expression = expression
       
   444 
       
   445     def eval(self, context, **kwargs):
       
   446         """ Use context to eval the value
       
   447         Expression on TerminalExpression is feature reference or value
       
   448         context should handle the reference conversion to correct value
       
   449         """
       
   450         from cone.public import exceptions
       
   451         try:
       
   452             context.configuration.get_default_view().get_feature(self.expression)
       
   453             self.value = context.handle_terminal(self.expression)
       
   454         except exceptions.NotFound:
       
   455             self.value = context.convert_value(self.expression)
       
   456             
       
   457         return self.value
       
   458 
       
   459     def __unicode__(self):
       
   460         return self.expression
       
   461     
       
   462     def __repr__(self):
       
   463         return self.expression
       
   464 
       
   465     def get_ref(self):
       
   466         """
       
   467         @return: The setting reference, e.g. 'MyFeature.MySetting'
       
   468         """
       
   469         return self.expression
       
   470 
       
   471 
       
   472 class NegExpression(OneParamExpression):
       
   473     PRECEDENCE = PRECEDENCES['PREFIX_OPERATORS']
       
   474     KEY= '-'
       
   475     OP = ops.neg
       
   476 
       
   477 class AndExpression(TwoOperatorBooleanExpression):
       
   478     PRECEDENCE = PRECEDENCES['BOOLEAN_OPERATORS']
       
   479     KEY= 'and'
       
   480     OP = truth_and
       
   481 
       
   482 class NandExpression(TwoOperatorBooleanExpression):
       
   483     PRECEDENCE = PRECEDENCES['BOOLEAN_OPERATORS']
       
   484     KEY = 'nand'
       
   485     OP = nand
       
   486 
       
   487 class OrExpression(TwoOperatorBooleanExpression):
       
   488     PRECEDENCE = PRECEDENCES['BOOLEAN_OPERATORS']
       
   489     KEY = 'or'
       
   490     OP = ops.or_
       
   491 
       
   492 class XorExpression(TwoOperatorBooleanExpression):
       
   493     PRECEDENCE = PRECEDENCES['BOOLEAN_OPERATORS']
       
   494     KEY = 'xor'
       
   495     OP = ops.xor
       
   496 
       
   497 class NorExpression(TwoOperatorBooleanExpression):
       
   498     PRECEDENCE = PRECEDENCES['BOOLEAN_OPERATORS']
       
   499     KEY = 'nor'
       
   500     OP = nor
       
   501 
       
   502 class EqualExpression(TwoOperatorExpression):
       
   503     PRECEDENCE = PRECEDENCES['COMPARISON_OPERATORS']
       
   504     KEY = '=='
       
   505     OP = ops.eq
       
   506 
       
   507 class NotEqualExpression(TwoOperatorExpression):
       
   508     PRECEDENCE = PRECEDENCES['COMPARISON_OPERATORS']
       
   509     KEY = '!='
       
   510     OP = ops.ne
       
   511 
       
   512 class LessThanExpression(TwoOperatorExpression):
       
   513     PRECEDENCE = PRECEDENCES['COMPARISON_OPERATORS']
       
   514     KEY = '<'
       
   515     OP = ops.lt
       
   516 
       
   517 class GreaterThanExpression(TwoOperatorExpression):
       
   518     PRECEDENCE = PRECEDENCES['COMPARISON_OPERATORS']
       
   519     KEY = '>'
       
   520     OP = ops.gt
       
   521 
       
   522 class LessThanEqualExpression(TwoOperatorExpression):
       
   523     PRECEDENCE = PRECEDENCES['COMPARISON_OPERATORS']
       
   524     KEY = '<='
       
   525     OP = ops.le
       
   526 
       
   527 class GreaterThanEqualExpression(TwoOperatorExpression):
       
   528     PRECEDENCE = PRECEDENCES['COMPARISON_OPERATORS']
       
   529     KEY = '>='
       
   530     OP = ops.ge
       
   531 
       
   532 
       
   533 def handle_require(expression, left, right):
       
   534     if left and right:
       
   535         return True
       
   536     elif not left:
       
   537         return True
       
   538     return False
       
   539 
       
   540 class RequireExpression(TwoOperatorExpression):
       
   541     PRECEDENCE = PRECEDENCES['RELATION_OPERATORS']
       
   542     KEY = 'requires'
       
   543     OP = handle_require
       
   544 
       
   545     def eval(self, context, **kwargs):
       
   546         super(RequireExpression, self).eval(context, **kwargs)
       
   547         if not self.value:
       
   548             left_keys = []
       
   549             for ref in self.ast.extract_refs(unicode(self.left)):
       
   550                 for key in context.get_keys(ref):
       
   551                     left_keys.append(key)
       
   552 
       
   553             for key in left_keys:
       
   554                 self.ast.add_error(key, { 'error_string' : 'REQUIRES right side value is "False"',
       
   555                                           'left_key' : key,
       
   556                                           'rule' : self.ast.expression
       
   557                                           })
       
   558         return self.value
       
   559 
       
   560 def handle_exclude(expression, left, right):
       
   561     if left and not right:
       
   562         return True
       
   563     elif not left:
       
   564         return True
       
   565     return False
       
   566 
       
   567 class ExcludeExpression(TwoOperatorExpression):
       
   568     PRECEDENCE = PRECEDENCES['RELATION_OPERATORS']
       
   569     KEY = 'excludes'
       
   570     OP = handle_exclude
       
   571 
       
   572     def eval(self, context, **kwargs):
       
   573         super(ExcludeExpression, self).eval(context, **kwargs)
       
   574         if not self.value:
       
   575             left_keys = []
       
   576             for ref in self.ast.extract_refs(unicode(self.left)):
       
   577                 for key in context.get_keys(ref):
       
   578                     left_keys.append(key)
       
   579                     
       
   580             for key in left_keys:
       
   581                 self.ast.add_error(key, { 'error_string' : 'EXCLUDE right side value is "True"',
       
   582                                           'left_key' : key,
       
   583                                           'rule' : self.ast.expression
       
   584                                           })
       
   585         return self.value
       
   586 
       
   587 
       
   588 class NotExpression(OneParamExpression):
       
   589     PRECEDENCE = PRECEDENCES['PREFIX_OPERATORS']
       
   590     KEY = 'not'
       
   591     OP = ops.not_
       
   592 
       
   593 class TruthExpression(OneParamExpression):
       
   594     PRECEDENCE = PRECEDENCES['PREFIX_OPERATORS']
       
   595     KEY = 'truth'
       
   596     OP = ops.truth
       
   597 
       
   598 LEFT_PARENTHESIS = '('
       
   599 RIGHT_PARENTHESIS = ')'
       
   600 class SimpleCondition(EqualExpression):
       
   601     """
       
   602     A simple condition object that can refer to a model object and evaluate if the value matches  
       
   603     """
       
   604     def __init__(self, left, right):
       
   605         lterm = TerminalExpression(None, left)
       
   606         rterm = TerminalExpression(None, right)
       
   607         EqualExpression.__init__(self, None, lterm, rterm)
       
   608 
       
   609 
       
   610 # in format KEY : OPERATOR CLASS
       
   611 OPERATORS = {
       
   612     'and' : AndExpression,
       
   613     'nand' : NandExpression,
       
   614     'or' : OrExpression,
       
   615     'xor' : XorExpression,
       
   616     'nor' : NorExpression,
       
   617     'not' : NotExpression,
       
   618     'truth' : TruthExpression,
       
   619     '==' : EqualExpression,
       
   620     '!=' : NotEqualExpression,
       
   621     '<' : LessThanExpression,
       
   622     '>' : GreaterThanExpression,
       
   623     '<=' : LessThanEqualExpression,
       
   624     '>=' : GreaterThanEqualExpression,
       
   625     'requires' : RequireExpression,
       
   626     'excludes' : ExcludeExpression,
       
   627     '-' : NegExpression
       
   628     }
       
   629 
       
   630 def add_operator(key, operator_class=None, baseclass=RequireExpression):
       
   631     """
       
   632     Add new operator key and operator class.
       
   633     If operator class isn't provided the baseclass parameter is used as
       
   634     operator base. The baseclass parameter is RequireExpression by default
       
   635     which has success condition left_rule=True and right_rule=True
       
   636     
       
   637     """
       
   638     OPERATORS[key] = operator_class or create_new_class(key, baseclass)
       
   639 
       
   640 def create_new_class(key, baseclass):
       
   641     ns = baseclass.__dict__.copy()
       
   642     ns['KEY'] = key
       
   643     key_pieces = key.split('_')
       
   644     class_prefix = ''.join([key_piece.capitalize() for key_piece in key_pieces])
       
   645     new_class = type(class_prefix + 'Expression', (baseclass,), ns)
       
   646     return new_class
       
   647 
       
   648 class ParseException(Exception): pass
       
   649 
       
   650 class ASTInterpreter(object):
       
   651     def __init__(self, infix_expression=None, context=None):
       
   652         """ Takes infix expression as string """
       
   653         self.context = context or DefaultContext(None)
       
   654         # logger.debug("AST init context: %s" % self.context)
       
   655         self._init_locals(infix_expression)
       
   656         if infix_expression:
       
   657             self.create_ast()
       
   658 
       
   659     def _init_locals(self, infix_expression):
       
   660         # The result value of full eval of the parse_tree
       
   661         self.value = None
       
   662         self.warnings = {}
       
   663         self.errors = {}
       
   664         self.postfix_array = []
       
   665         self.parse_tree = []
       
   666         self.expression = infix_expression
       
   667 
       
   668     def __unicode__(self):
       
   669         s = ''
       
   670         for expr in self.parse_tree:
       
   671             s += unicode(expr)
       
   672         return s
       
   673 
       
   674     def add_error(self, key, error_dict):
       
   675         if self.errors.has_key(key):
       
   676             self.errors[key].append(error_dict)
       
   677         else:
       
   678             self.errors[key] = [error_dict]
       
   679 
       
   680     def create_ast(self, infix_expression=None):
       
   681         if infix_expression:
       
   682             self._init_locals(infix_expression)
       
   683         self._infix_to_postfix()
       
   684         self._create_parse_tree()
       
   685         return self.parse_tree
       
   686 
       
   687     def _infix_to_postfix(self):
       
   688         """
       
   689         Shunting yard algorithm used to convert infix presentation to postfix.
       
   690         """
       
   691         if not self.expression:
       
   692             raise ParseException('Expression is None')
       
   693         tokens = get_tokens(self.expression) # [token for token in self.expression.split()]
       
   694         stack = []
       
   695         # logger.debug('TOKENS: %s' % tokens)
       
   696         for token in tokens:
       
   697             # logger.debug('TOKEN: %s' % token)
       
   698             if token.lower() in OPERATORS:
       
   699                 op_class = OPERATORS.get(token)
       
   700                 if stack:
       
   701                     while len(stack) != 0:
       
   702                         top = stack[-1]
       
   703                         if top in OPERATORS:
       
   704                             top_operator = OPERATORS.get(top)
       
   705                             if op_class.PRECEDENCE <= top_operator.PRECEDENCE:
       
   706                                 self.postfix_array.append(stack.pop())
       
   707                             else:
       
   708                                 # Break from loop if top operator precedence is less.
       
   709                                 break
       
   710                         else:
       
   711                             # If top not operator break from loop
       
   712                             break
       
   713                 stack.append(token)
       
   714             elif token == LEFT_PARENTHESIS:
       
   715                 # logger.debug('Left parenthesis')
       
   716                 stack.append(token)
       
   717             elif token == RIGHT_PARENTHESIS:
       
   718                 # logger.debug('Right parenthesis')
       
   719                 left_par_found = False
       
   720                 stack_token = stack.pop()
       
   721                 while stack_token:
       
   722                     if stack_token != LEFT_PARENTHESIS:
       
   723                         self.postfix_array.append(stack_token)
       
   724                     else:
       
   725                         left_par_found = True
       
   726                         break
       
   727                     if stack:
       
   728                         stack_token = stack.pop()
       
   729                     else:
       
   730                         stack_token = None
       
   731                         
       
   732                 if not left_par_found:
       
   733                     raise ParseException('Mismatched parenthesis "%s".' % LEFT_PARENTHESIS)
       
   734             else:
       
   735                 # logger.debug('Adding value to output. %s' % repr((token)))
       
   736                 self.postfix_array.append((token))
       
   737             
       
   738         # There should be only operators left in the stack
       
   739         if stack:
       
   740             # logger.debug('Operators in stack.')
       
   741             operator = stack.pop()
       
   742             while operator:
       
   743                 if operator != LEFT_PARENTHESIS:
       
   744                     self.postfix_array.append(operator)
       
   745                 else:
       
   746                     raise ParseException('Mismatched parenthesis "%s".' % LEFT_PARENTHESIS)
       
   747                 if stack:
       
   748                     operator = stack.pop()
       
   749                 else:
       
   750                     operator = None
       
   751 
       
   752         # logger.debug('Infix to postfix conversion: %s' % self.postfix_array)
       
   753         return self.postfix_array
       
   754     
       
   755     def _create_parse_tree(self):
       
   756         self.parse_tree = []
       
   757         for token in self.postfix_array:
       
   758             if token in OPERATORS:
       
   759                 # logger.debug('OP: %s' % (token))
       
   760                 expression_class = OPERATORS[token]
       
   761                 params = []
       
   762                 for i in range(expression_class.PARAM_COUNT):
       
   763                     try:
       
   764                         params.append(self.parse_tree.pop())
       
   765                     except IndexError, e:
       
   766                         raise ParseException('Syntax error: "%s"' % self.expression)
       
   767                 params.reverse()
       
   768                 expression = expression_class(self, *params)
       
   769 
       
   770                 # logger.debug('The operation: %s' % expression)
       
   771                 self.parse_tree.append(expression)
       
   772             else:
       
   773                 expression = TerminalExpression(self, token)
       
   774                 self.parse_tree.append(expression)
       
   775 
       
   776         #logger.debug('THE STACK: %s' % self.parse_tree)
       
   777         #for s in self.parse_tree:
       
   778         #    logger.debug('Stack e: %s' % str(s))
       
   779 
       
   780         return self.parse_tree
       
   781 
       
   782     def eval(self, context=None, **kwargs):
       
   783         """ Evals the AST
       
   784         If empty expression is given, None is returned
       
   785         """
       
   786         for expression in self.parse_tree:
       
   787             self.value = expression.eval(context, **kwargs)
       
   788         return self.value
       
   789 
       
   790     @staticmethod
       
   791     def extract_refs(expression):
       
   792         tokens = get_tokens(expression)
       
   793         refs = []
       
   794         for token in tokens:
       
   795             if not token.lower() in OPERATORS and token != LEFT_PARENTHESIS and token != RIGHT_PARENTHESIS:
       
   796                 refs.append(token.strip('%s%s' % (LEFT_PARENTHESIS, RIGHT_PARENTHESIS)))
       
   797         return refs
       
   798 
       
   799 ##################################################################
       
   800 # Create and configure the main level logger
       
   801 logger = logging.getLogger('cone')