Package gavo :: Package adql :: Module morphhelpers
[frames] | no frames]

Source Code for Module gavo.adql.morphhelpers

  1  """ 
  2  Helpers for morphing modules 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  from gavo.adql import nodes 
 12   
 13   
14 -class State(object):
15 """is a scratchpad for morphers to communicate state among 16 themselves. 17 18 Append to warnings a necessary. Also, traverse keeps an attribute 19 nodeStack in here letting elements look up its parent chain. 20 """
21 - def __init__(self):
22 self.warnings = [] 23 self.nodeStack = []
24 25 26 _BOOLEANIZER_TABLE = { 27 ('=', '0'): "NOT ", 28 ('!=', '1'): "NOT ", 29 ('=', '1'): "", 30 ('!=', '0'): "",} 31
32 -def addNotToBooleanized(expr, operator, operand):
33 """prepends a NOT to expr if operator and operand suggest there should 34 be one for ADQL integerized boolean expressions. 35 36 The function will return None for unknown combinations of operator and 37 operand, and it will simply hand through Nones for expr, so calling 38 functions can just return addNotToBooleanized(...). 39 """ 40 if expr is None: 41 return expr 42 43 prefix = _BOOLEANIZER_TABLE.get((operator, operand), None) 44 if prefix is None: 45 # weird non-boolean-looking condition 46 return None 47 48 return prefix+expr
49 50 51 # Handler functions for booleanizeComparisons 52 _BOOLEANOID_FUNCTIONS = {} 53
54 -def registerBooleanizer(funcName, handler):
55 """registers handler as a booleanizer for ADQL functions called 56 funcName. 57 58 funcName must be all-uppercase for this to work. handler(node, 59 operand, operator) is a function that receives a function node 60 and the operand and operator of the comparison and either returns 61 None to say it can't handle it, or something else; that something 62 else is what the entire comparison node is morphed into. 63 64 You can call multiple booleanizers for the same function; they will 65 be tried in sequence. Hence, make sure you get your import sequences 66 right if you do this. 67 """ 68 _BOOLEANOID_FUNCTIONS.setdefault(funcName, []).append(handler)
69 70
71 -def booleanizeComparisons(node, state):
72 """turns a comparison expression that's really a boolean 73 expression into a boolean expression. 74 75 New-style: handler functions of children of this just set an 76 OVERRIDE_RESULT attribute, and this just returns it. The DISTANCE 77 MORPHER already does this. 78 79 Old-style functionality (should be moved to new-style at some point): 80 81 This is for things like the geometry predicates (CONTAINS, 82 INTERSECTS) or stuff like ivo_hasword and such. Embedding these 83 as booleans helps the query planner a lot (though it might change 84 semantics slightly in the presence of NULLs). 85 86 The way this works is that morphing code can call 87 registerBooleanizer with the function name and callable 88 that receives the function node, the operator, and the operand. 89 If that function returns non-None, that result is used instead 90 of the current node. 91 """ 92 if isinstance(node.op1, nodes.FunctionNode): 93 fCall, opd = node.op1, node.op2 94 elif isinstance(node.op2, nodes.FunctionNode): 95 fCall, opd = node.op2, node.op1 96 else: 97 # no function call, leave things alone 98 return node 99 100 for morpher in _BOOLEANOID_FUNCTIONS.get(fCall.funName, []): 101 res = morpher(fCall, node.opr, nodes.flatten(opd)) 102 if res is not None: 103 node = res 104 break 105 return node
106 107
108 -class Morpher(object):
109 """A class managing the process of morphing an ADQL expression. 110 111 It is constructed with a a dictionary of morphers; the keys are node 112 types, the values morphing functions. 113 114 Morphing functions have the signature m(node, state) -> node. They 115 should return the node if they do not with to change it. 116 state is a State instance. 117 118 The main entry point is morph(origTree) -> state, tree. origTree is not 119 modified, the return value can be flattened but can otherwise be severely 120 damaged. 121 122 For special effects, there's also earlyMorphers. These will be called 123 when traversal reaches the node for the first time. If these return 124 None, traversal continues as usual, if not, their result will be 125 added to the tree and *not* further traversed. 126 """
127 - def __init__(self, morphers, earlyMorphers={}):
128 self.morphers = morphers 129 self.earlyMorphers = earlyMorphers
130
131 - def _getChangedForSeq(self, value, state):
132 newVal, changed = [], False 133 for child in value: 134 if isinstance(child, nodes.ADQLNode): 135 newVal.append(self._traverse(child, state)) 136 else: 137 newVal.append(child) 138 if newVal[-1]!=child: 139 changed = True 140 if changed: 141 return tuple(newVal)
142
143 - def _getChangedForNode(self, value, state):
144 newVal = self._traverse(value, state) 145 if not newVal is value: 146 return newVal
147
148 - def _getChanges(self, name, value, state):
149 """iterates over key/value pairs changed by morphing value under 150 the key name. 151 """ 152 if isinstance(value, (list, tuple)): 153 meth = self._getChangedForSeq 154 elif isinstance(value, nodes.ADQLNode): 155 meth = self._getChangedForNode 156 else: 157 return 158 newVal = meth(value, state) 159 if newVal is not None: 160 yield name, newVal
161
162 - def _traverse(self, node, state):
163 if node.type in self.earlyMorphers: 164 res = self.earlyMorphers[node.type](node, state) 165 if res is not None: 166 return res 167 168 state.nodeStack.append(node) 169 changes = [] 170 for name, value in node.iterAttributes(): 171 changes.extend(self._getChanges(name, value, state)) 172 173 if changes: 174 newNode = node.change(**dict(changes)) 175 newNode.original = node 176 else: 177 newNode = node 178 179 # let handlers down the tree determine the total result (this 180 # is mainly for when comparisons become boolean funciton calls, 181 # but who knows?) 182 if hasattr(node, "OVERRIDE_RESULT"): 183 newNode = node.OVERRIDE_RESULT 184 185 popped = state.nodeStack.pop() 186 assert popped==node, "ADQL morphing node stack corruption" 187 188 curType = getattr(newNode, "type", None) 189 if curType in self.morphers: 190 handlerResult = self.morphers[curType](newNode, state) 191 assert handlerResult is not None, "ADQL morph handler returned None" 192 return handlerResult 193 return newNode
194
195 - def morph(self, tree):
196 state = State() 197 res = self._traverse(tree, state) 198 return state, res
199