Package lepl :: Package support :: Module node
[hide private]
[frames] | no frames]

Source Code for Module lepl.support.node

  1   
  2  # The contents of this file are subject to the Mozilla Public License 
  3  # (MPL) Version 1.1 (the "License"); you may not use this file except 
  4  # in compliance with the License. You may obtain a copy of the License 
  5  # at http://www.mozilla.org/MPL/ 
  6  # 
  7  # Software distributed under the License is distributed on an "AS IS" 
  8  # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
  9  # the License for the specific language governing rights and 
 10  # limitations under the License. 
 11  # 
 12  # The Original Code is LEPL (http://www.acooke.org/lepl) 
 13  # The Initial Developer of the Original Code is Andrew Cooke. 
 14  # Portions created by the Initial Developer are Copyright (C) 2009-2010 
 15  # Andrew Cooke (andrew@acooke.org). All Rights Reserved. 
 16  # 
 17  # Alternatively, the contents of this file may be used under the terms 
 18  # of the LGPL license (the GNU Lesser General Public License, 
 19  # http://www.gnu.org/licenses/lgpl.html), in which case the provisions 
 20  # of the LGPL License are applicable instead of those above. 
 21  # 
 22  # If you wish to allow use of your version of this file only under the 
 23  # terms of the LGPL License and not to allow others to use your version 
 24  # of this file under the MPL, indicate your decision by deleting the 
 25  # provisions above and replace them with the notice and other provisions 
 26  # required by the LGPL License.  If you do not delete the provisions 
 27  # above, a recipient may use your version of this file under either the 
 28  # MPL or the LGPL License. 
 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   
39 -class NodeException(Exception):
40 ''' 41 Exception raised when we have problems dynamically creating nodes. 42 '''
43 44
45 -def is_named(arg):
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
53 -def new_named_node(name, node):
54 ''' 55 Generate a sub-class of Node, with the given name as type, as long as 56 it is not already a subclass. 57 ''' 58 if type(node) != Node: 59 raise NodeException( 60 fmt('Will not coerce a node subclass ({0}) to {1}', 61 type(node), name)) 62 class_ = type(name, (Node,), {}) 63 (args, kargs) = node._constructor_args() 64 return class_(*args, **kargs)
65 66
67 -def coerce(arg):
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 # pylint: disable-msg=R0903 78 # it's not supposed to have public attributes, because it exposes contents
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
128 - def __init__(self, *args):
129 ''' 130 Expects a single list of arguments, as will be received if invoked with 131 the ``>`` operator. 132 ''' 133 super(Node, self).__init__() 134 self.__postorder = ConstructorWalker(self, Node) 135 self.__children = [] 136 self.__paths = [] 137 self.__names = set() 138 for arg in map(coerce, args): 139 if is_named(arg): 140 self.__add_named_child(arg[0], arg[1]) 141 elif isinstance(arg, Node): 142 self.__add_named_child(arg.__class__.__name__, arg) 143 else: 144 self.__add_anon_child(arg)
145
146 - def __add_named_child(self, name, value):
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
155 - def __add_anon_child(self, value):
156 ''' 157 Add a nameless value. 158 ''' 159 index = len(self.__children) 160 self.__children.append(value) 161 self.__paths.append(index)
162
163 - def __add_attribute(self, name, value):
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
175 - def __dir__(self):
176 ''' 177 The names of all the attributes constructed from the results. 178 ''' 179 # this must return a list, not an iterator (Python requirement) 180 return list(self.__names)
181
182 - def __getitem__(self, index):
183 return self.__children[index]
184
185 - def __iter__(self):
186 return iter(self.__children)
187
188 - def __str__(self):
189 visitor = NodeTreeStr() 190 return self.__postorder(visitor)
191
192 - def __repr__(self):
193 return self.__class__.__name__ + '(...)'
194
195 - def __len__(self):
196 return len(self.__children)
197
198 - def __bool__(self):
199 return bool(self.__children)
200 201 # Python 2.6
202 - def __nonzero__(self):
203 return self.__bool__()
204
205 - def _recursively_eq(self, other):
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 # pylint: disable-msg=W0212 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
233 - def _constructor_args(self):
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 # pylint: disable-msg=R0903 251 # __ method
252 -class MutableNode(Node):
253 ''' 254 Extend `Node` to allow children to be set. 255 ''' 256
257 - def __setitem__(self, index, value):
258 self.__children[index] = value
259 260
261 -class NodeTreeStr(GraphStr):
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
278 -def node_throw(node):
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 # Below unrelated to nodes - move? 290
291 -def make_dict(contents):
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
303 -def join_with(separator=''):
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