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 Support for S-expression ASTs using subclasses of Python's list class.
32
33 The general support works with any nested iterables (except strings).
34 '''
35
36 from functools import reduce
37
38 from lepl.support.lib import fmt, basestring
39 from lepl.support.node import Node
40
41
43 '''
44 Extend a list for use in ASTs.
45
46 Note that the argument is treated in exactly the same way as list(). That
47 means it takes a single list or generator as an argument, so to use
48 literally you might type List([1,2,3]) - note the "extra" list.
49 '''
50
52 return self.__class__.__name__ + '(...)'
53
56
57
59 '''
60 Clone a class that wraps data in an AST.
61 '''
62 if issubclass(type_, Node):
63 return type_(*list(items))
64 elif issubclass(type_, basestring):
65 return type_('').join(items)
66 else:
67 return type_(items)
68
69
70 -def sexpr_fold(per_list=None, per_item=None,
71 exclude=lambda x: isinstance(x, basestring)):
72 '''
73 We need some kind of fold-like procedure for generalising operations on
74 arbitrarily nested iterables. We can't use a normal fold because Python
75 doesn't have the equivalent of cons, etc; this tries to be more Pythonic
76 (see comments later).
77
78 We divide everything into iterables ("lists") and atomic values ("items").
79 per_list is called with a generator over the (transformed) top-most list,
80 in order. Items (ie atomic values) in that list, when requested from the
81 generator, will be processed by per_item; iterables will be processed by a
82 separate call to per_list (ie recursively).
83
84 So this is more like a recursive map than a fold, but with Python's
85 mutable state and lack of typing it appears to be equally powerful.
86 Note that per_list is passed the previous type, which can be used for
87 dispatching operations.
88 '''
89 if per_list is None:
90 per_list = clone_iterable
91 if per_item is None:
92 per_item = lambda x: x
93 def items(iterable):
94 for item in iterable:
95 try:
96 if not exclude(item):
97 if isinstance(item, dict):
98 yield per_list(type(item), items(item.items()))
99 else:
100 yield per_list(type(item), items(iter(item)))
101 continue
102 except TypeError:
103 pass
104 yield per_item(item)
105 return lambda list_: per_list(type(list_), items(iter(list_)))
106
107
108 clone_sexpr = sexpr_fold()
109 '''
110 Clone a set of listed iterables.
111 '''
112
113 count_sexpr = sexpr_fold(per_list=lambda type_, items: sum(items),
114 per_item=lambda item: 1)
115 '''
116 Count the number of value nodes in an AST.
117
118 (Note that size(List) gives the number of entries in that list, counting each
119 sublist as "1", while this descends embedded lists, counting their non-iterable
120 contents.
121 '''
122
123 join = lambda items: reduce(lambda x, y: x+y, items, [])
124 '''
125 Flatten a list of lists by one level, so [[1],[2, [3]]] becomes [1,2,[3]].
126
127 Note: this will *only* work correctly if all entries are lists.
128 '''
129
130 sexpr_flatten = sexpr_fold(per_list=lambda type_, items: join(items),
131 per_item=lambda item: [item])
132 '''
133 Flatten a list completely, so [[1],[2, [3]]] becomes [1,2,3]
134 '''
135
136 _fmt={}
137 _fmt[list] = '[{1}]'
138 _fmt[tuple] = '({1})'
139
140 sexpr_to_str = sexpr_fold(per_list=lambda type_, items:
141 fmt(_fmt.get(type_, '{0}([{1}])'),
142 type_.__name__, ','.join(items)),
143 per_item=lambda item: repr(item))
144 '''
145 A flat representation of nested lists (a set of constructors).
146 '''
147
149 '''
150 Generate a tree using the same "trick" as `GraphStr`.
151
152 The initial fold returns a function (str, str) -> list(str) at each
153 level.
154 '''
155 def per_item(item):
156 def fun(first, _rest):
157 return [first + repr(item)]
158 return fun
159 def per_list(type_, list_):
160 def fun(first, rest):
161 yield [first + str(type_.__name__)]
162 force = list(list_)
163 if force:
164 for item in force[:-1]:
165 yield item(rest + ' +- ', rest + ' | ')
166 yield force[-1](rest + ' `- ', rest + ' ')
167 return lambda first, rest: join(list(fun(first, rest)))
168 fold = sexpr_fold(per_list, per_item)
169 return '\n'.join(fold(list_)('', ''))
170
171
173 '''
174 Raise an error, if one exists in the results (AST trees are traversed).
175 Otherwise, the results are returned (invoke with ``>>``).
176 '''
177 def throw_or_copy(type_, items):
178 clone = clone_iterable(type_, items)
179 if isinstance(clone, Exception):
180 raise clone
181 else:
182 return clone
183 return sexpr_fold(per_list=throw_or_copy)(node)
184