src/eric7/ThirdParty/Jasy/jasy/parse/AbstractNode.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8312
800c432b34c8
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 #
2 # Jasy - Web Tooling Framework
3 # Copyright 2013-2014 Sebastian Werner
4 #
5
6 import json, copy
7
8 class 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

eric ide

mercurial