ThirdParty/Jasy/jasy/parse/AbstractNode.py

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

eric ide

mercurial