1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 '''
31 Base classes for AST nodes (and associated functions).
32 '''
33
34 from lepl.support.graph import GraphStr, ConstructorGraphNode, ConstructorWalker,\
35 postorder
36 from lepl.support.lib import LogMixin, basestring, fmt
37
38
40 '''
41 Exception raised when we have problems dynamically creating nodes.
42 '''
43
44
46 '''
47 Is this is "named tuple"?
48 '''
49 return (isinstance(arg, tuple) or isinstance(arg, list)) \
50 and len(arg) == 2 and isinstance(arg[0], basestring)
51
52
65
66
68 '''
69 Convert named nodes to nodes with that name.
70 '''
71 if is_named(arg) and isinstance(arg[1], Node):
72 return new_named_node(arg[0], arg[1])
73 else:
74 return arg
75
76
77
78
79 -class Node(LogMixin, ConstructorGraphNode):
80 '''
81 A base class for AST nodes.
82
83 It is designed to be applied to a list of results, via ``>``.
84
85 Nodes support both simple list--like behaviour::
86
87 >>> abc = Node('a', 'b', 'c')
88 >>> abc[1]
89 'b'
90 >>> abc[1:]
91 ['b', 'c']
92 >>> abc[:-1]
93 ['a', 'b']
94
95 and dict--like behaviour through attributes::
96
97 >>> fb = Node(('foo', 23), ('bar', 'baz'))
98 >>> fb.foo
99 [23]
100 >>> fb.bar
101 ['baz']
102
103 Both mixed together::
104
105 >>> fb = Node(('foo', 23), ('bar', 'baz'), 43, 'zap', ('foo', 'again'))
106 >>> fb[:]
107 [23, 'baz', 43, 'zap', 'again']
108 >>> fb.foo
109 [23, 'again']
110
111 Note how ``('name', value)`` pairs have a special meaning in the constructor.
112 This is supported by the creation of "named pairs"::
113
114 >>> letter = Letter() > 'letter'
115 >>> digit = Digit() > 'digit'
116 >>> example = (letter | digit)[:] > Node
117 >>> n = example.parse('abc123d45e')[0]
118 >>> n.letter
119 ['a', 'b', 'c', 'd', 'e']
120 >>> n.digit
121 ['1', '2', '3', '4', '5']
122
123 However, a named pair with a Node as a value is coerced into a subclass of
124 Node with the given name (this keeps Nodes connected into a single tree and
125 so simplifies traversal).
126 '''
127
145
147 '''
148 Add a value associated with a name (either a named pair or the class
149 of a Node subclass).
150 '''
151 index = self.__add_attribute(name, value)
152 self.__children.append(value)
153 self.__paths.append((name, index))
154
156 '''
157 Add a nameless value.
158 '''
159 index = len(self.__children)
160 self.__children.append(value)
161 self.__paths.append(index)
162
164 '''
165 Attributes are associated with lists of (named) values.
166 '''
167 if name not in self.__names:
168 self.__names.add(name)
169 setattr(self, name, [])
170 attr = getattr(self, name)
171 index = len(attr)
172 attr.append(value)
173 return index
174
176 '''
177 The names of all the attributes constructed from the results.
178 '''
179
180 return list(self.__names)
181
183 return self.__children[index]
184
186 return iter(self.__children)
187
189 visitor = NodeTreeStr()
190 return self.__postorder(visitor)
191
193 return self.__class__.__name__ + '(...)'
194
196 return len(self.__children)
197
199 return bool(self.__children)
200
201
204
206 '''
207 This compares two nodes by recursively comparing their contents.
208 It may be useful for testing, for example, but care should be taken
209 to avoid its use on cycles of objects.
210 '''
211 try:
212 siblings = iter(other)
213 except TypeError:
214 return False
215 for child in self:
216 try:
217 sibling = next(siblings)
218 try:
219
220 if not child._recursively_eq(sibling):
221 return False
222 except AttributeError:
223 if child != sibling:
224 return False
225 except StopIteration:
226 return False
227 try:
228 next(siblings)
229 return False
230 except StopIteration:
231 return True
232
234 '''
235 Regenerate the constructor arguments (returns (args, kargs)).
236 '''
237 args = []
238 for (path, value) in zip(self.__paths, self.__children):
239 if isinstance(path, int):
240 args.append(value)
241 else:
242 name = path[0]
243 if name == value.__class__.__name__:
244 args.append(value)
245 else:
246 args.append((name, value))
247 return (args, {})
248
249
250
251
253 '''
254 Extend `Node` to allow children to be set.
255 '''
256
258 self.__children[index] = value
259
260
262 '''
263 Extend `GraphStr` to handle named pairs - this generates an 'ASCII tree'
264 representation of the node graph.
265 '''
266
267 - def leaf(self, arg):
268 '''
269 Leaf nodes are either named or simple values.
270 '''
271 if is_named(arg):
272 return lambda first, rest, name_: \
273 [first + arg[0] + (' ' if arg[0] else '') + repr(arg[1])]
274 else:
275 return super(NodeTreeStr, self).leaf(arg)
276
277
279 '''
280 Raise an error, if one exists in the results (AST trees are traversed).
281 Otherwise, the results are returned (invoke with ``>>``).
282 '''
283 for child in postorder(node, Node):
284 if isinstance(child, Exception):
285 raise child
286 return node
287
288
289
290
292 '''
293 Construct a dict from a list of named pairs (other values in the list
294 will be discarded). Invoke with ``>`` after creating named pairs with
295 ``> string``.
296 '''
297 return dict(entry for entry in contents
298 if isinstance(entry, tuple)
299 and len(entry) == 2
300 and isinstance(entry[0], basestring))
301
302
304 '''
305 Join results together (via separator.join()) into a single string.
306
307 Invoke as ``> join_with(',')``, for example.
308 '''
309 def fun(results):
310 '''
311 Delay evaluation.
312 '''
313 return separator.join(results)
314 return fun
315