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