1# 2# Jasy - Web Tooling Framework 3# Copyright 2013-2014 Sebastian Werner 4# 5 6import json, copy 7 8class AbstractNode(list): 9 10 __slots__ = [ 11 # core data 12 "line", "type", "tokenizer", "start", "end", "rel", "parent", 13 14 # dynamic added data by other modules 15 "comments", "scope", "values", 16 17 # node type specific 18 "value", "parenthesized", "fileId", "params", 19 "name", "initializer", "condition", "assignOp", 20 "thenPart", "elsePart", "statements", 21 "statement", "variables", "names", "postfix" 22 ] 23 24 25 def __init__(self, tokenizer=None, type=None, args=[]): 26 list.__init__(self) 27 28 self.start = 0 29 self.end = 0 30 self.line = None 31 32 if tokenizer: 33 token = getattr(tokenizer, "token", None) 34 if token: 35 # We may define a custom type but use the same positioning as another token 36 # e.g. transform curlys in block nodes, etc. 37 self.type = type if type else getattr(token, "type", None) 38 self.line = token.line 39 40 # Start & end are file positions for error handling. 41 self.start = token.start 42 self.end = token.end 43 44 else: 45 self.type = type 46 self.line = tokenizer.line 47 self.start = None 48 self.end = None 49 50 self.tokenizer = tokenizer 51 52 elif type: 53 self.type = type 54 55 for arg in args: 56 self.append(arg) 57 58 59 def getFileName(self): 60 """ 61 Traverses up the tree to find a node with a fileId and returns it 62 """ 63 64 node = self 65 while node: 66 fileId = getattr(node, "fileId", None) 67 if fileId is not None: 68 return fileId 69 70 node = getattr(node, "parent", None) 71 72 73 def getUnrelatedChildren(self): 74 """Collects all unrelated children""" 75 76 collection = [] 77 for child in self: 78 if not hasattr(child, "rel"): 79 collection.append(child) 80 81 return collection 82 83 84 def getChildrenLength(self, filter=True): 85 """Number of (per default unrelated) children""" 86 87 count = 0 88 for child in self: 89 if not filter or not hasattr(child, "rel"): 90 count += 1 91 return count 92 93 94 def remove(self, kid): 95 """Removes the given kid""" 96 97 if not kid in self: 98 raise Exception("Given node is no child!") 99 100 if hasattr(kid, "rel"): 101 delattr(self, kid.rel) 102 del kid.rel 103 del kid.parent 104 105 list.remove(self, kid) 106 107 108 def insert(self, index, kid): 109 """Inserts the given kid at the given index""" 110 111 if index is None: 112 return self.append(kid) 113 114 if hasattr(kid, "parent"): 115 kid.parent.remove(kid) 116 117 kid.parent = self 118 119 return list.insert(self, index, kid) 120 121 122 def insertAll(self, index, kids): 123 """Inserts all kids starting with the given index""" 124 125 if index is None: 126 for kid in list(kids): 127 self.append(kid) 128 else: 129 for pos, kid in enumerate(list(kids)): 130 self.insert(index+pos, kid) 131 132 133 def insertAllReplace(self, orig, kids): 134 """Inserts all kids at the same position as the original node (which is removed afterwards)""" 135 136 index = self.index(orig) 137 for pos, kid in enumerate(list(kids)): 138 self.insert(index+pos, kid) 139 140 self.remove(orig) 141 142 143 def append(self, kid, rel=None): 144 """Appends the given kid with an optional relation hint""" 145 146 # kid can be null e.g. [1, , 2]. 147 if kid: 148 if hasattr(kid, "parent"): 149 kid.parent.remove(kid) 150 151 # Debug 152 if not isinstance(kid, AbstractNode): 153 raise Exception("Invalid kid: %s" % kid) 154 155 if hasattr(kid, "tokenizer"): 156 if hasattr(kid, "start"): 157 if not hasattr(self, "start") or self.start == None or kid.start < self.start: 158 self.start = kid.start 159 160 if hasattr(kid, "end"): 161 if not hasattr(self, "end") or self.end == None or self.end < kid.end: 162 self.end = kid.end 163 164 kid.parent = self 165 166 # alias for function 167 if rel != None: 168 setattr(self, rel, kid) 169 setattr(kid, "rel", rel) 170 171 # Block None kids when they should be related 172 if not kid and rel: 173 return 174 175 return list.append(self, kid) 176 177 178 def replace(self, kid, repl): 179 """Replaces the given kid with a replacement kid""" 180 181 if repl in self: 182 self.remove(repl) 183 184 self[self.index(kid)] = repl 185 186 if hasattr(kid, "rel"): 187 repl.rel = kid.rel 188 setattr(self, kid.rel, repl) 189 190 # cleanup old kid 191 delattr(kid, "rel") 192 193 elif hasattr(repl, "rel"): 194 # delete old relation on new child 195 delattr(repl, "rel") 196 197 delattr(kid, "parent") 198 repl.parent = self 199 200 return kid 201 202 203 def toXml(self, format=True, indent=0, tab=" "): 204 """Converts the node to XML""" 205 206 lead = tab * indent if format else "" 207 innerLead = tab * (indent+1) if format else "" 208 lineBreak = "\n" if format else "" 209 210 relatedChildren = [] 211 attrsCollection = [] 212 213 for name in self.__slots__: 214 # "type" is used as node name - no need to repeat it as an attribute 215 # "parent" is a relation to the parent node - for serialization we ignore these at the moment 216 # "rel" is used internally to keep the relation to the parent - used by nodes which need to keep track of specific children 217 # "start" and "end" are for debugging only 218 if hasattr(self, name) and name not in ("type", "parent", "comments", "selector", "rel", "start", "end") and name[0] != "_": 219 value = getattr(self, name) 220 if isinstance(value, AbstractNode): 221 if hasattr(value, "rel"): 222 relatedChildren.append(value) 223 224 elif type(value) in (bool, int, float, str, list, set, dict): 225 if type(value) == bool: 226 value = "true" if value else "false" 227 elif type(value) in (int, float): 228 value = str(value) 229 elif type(value) in (list, set, dict): 230 if type(value) == dict: 231 value = value.keys() 232 if len(value) == 0: 233 continue 234 try: 235 value = ",".join(value) 236 except TypeError as ex: 237 raise Exception("Invalid attribute list child at: %s: %s" % (name, ex)) 238 239 attrsCollection.append('%s=%s' % (name, json.dumps(value))) 240 241 attrs = (" " + " ".join(attrsCollection)) if len(attrsCollection) > 0 else "" 242 243 comments = getattr(self, "comments", None) 244 scope = getattr(self, "scope", None) 245 selector = getattr(self, "selector", None) 246 247 if len(self) == 0 and len(relatedChildren) == 0 and (not comments or len(comments) == 0) and not scope and not selector: 248 result = "%s<%s%s/>%s" % (lead, self.type, attrs, lineBreak) 249 250 else: 251 result = "%s<%s%s>%s" % (lead, self.type, attrs, lineBreak) 252 253 if comments: 254 for comment in comments: 255 result += '%s<comment context="%s" variant="%s">%s</comment>%s' % (innerLead, comment.context, comment.variant, comment.text, lineBreak) 256 257 if scope: 258 for statKey in scope: 259 statValue = scope[statKey] 260 if statValue != None and len(statValue) > 0: 261 if type(statValue) is set: 262 statValue = ",".join(statValue) 263 elif type(statValue) is dict: 264 statValue = ",".join(statValue.keys()) 265 266 result += '%s<stat name="%s">%s</stat>%s' % (innerLead, statKey, statValue, lineBreak) 267 268 if selector: 269 for entry in selector: 270 result += '%s<selector>%s</selector>%s' % (innerLead, entry, lineBreak) 271 272 for child in self: 273 if not child: 274 result += "%s<none/>%s" % (innerLead, lineBreak) 275 elif not hasattr(child, "rel"): 276 result += child.toXml(format, indent+1) 277 elif not child in relatedChildren: 278 raise Exception("Oops, irritated by non related: %s in %s - child says it is related as %s" % (child.type, self.type, child.rel)) 279 280 for child in relatedChildren: 281 result += "%s<%s>%s" % (innerLead, child.rel, lineBreak) 282 result += child.toXml(format, indent+2) 283 result += "%s</%s>%s" % (innerLead, child.rel, lineBreak) 284 285 result += "%s</%s>%s" % (lead, self.type, lineBreak) 286 287 return result 288 289 290 def __deepcopy__(self, memo): 291 """Used by deepcopy function to clone AbstractNode instances""" 292 293 CurrentClass = self.__class__ 294 295 # Create copy 296 if hasattr(self, "tokenizer"): 297 result = CurrentClass(tokenizer=self.tokenizer) 298 else: 299 result = CurrentClass(type=self.type) 300 301 # Copy children 302 for child in self: 303 if child is None: 304 list.append(result, None) 305 else: 306 # Using simple list appends for better performance 307 childCopy = copy.deepcopy(child, memo) 308 childCopy.parent = result 309 list.append(result, childCopy) 310 311 # Sync attributes 312 # Note: "parent" attribute is handled by append() already 313 for name in self.__slots__: 314 if hasattr(self, name) and not name in ("parent", "tokenizer"): 315 value = getattr(self, name) 316 if value is None: 317 pass 318 elif type(value) in (bool, int, float, str): 319 setattr(result, name, value) 320 elif type(value) in (list, set, dict, CurrentClass): 321 setattr(result, name, copy.deepcopy(value, memo)) 322 # Scope can be assigned (will be re-created when needed for the copied node) 323 elif name == "scope": 324 result.scope = self.scope 325 326 return result 327 328 329 def getSource(self): 330 """Returns the source code of the node""" 331 332 if not self.tokenizer: 333 raise Exception("Could not find source for node '%s'" % self.type) 334 335 if getattr(self, "start", None) is not None: 336 if getattr(self, "end", None) is not None: 337 return self.tokenizer.source[self.start:self.end] 338 return self.tokenizer.source[self.start:] 339 340 if getattr(self, "end", None) is not None: 341 return self.tokenizer.source[:self.end] 342 343 return self.tokenizer.source[:] 344 345 346 # Map Python built-ins 347 __repr__ = toXml 348 __str__ = toXml 349 350 351 def __eq__(self, other): 352 return self is other 353 354 def __bool__(self): 355 return True 356