OILS / opy / _regtest / src / opy / tools / astgen.py View on Github | oils.pub

295 lines, 237 significant
1from __future__ import print_function
2"""Generate ast module from specification
3
4This script generates the ast module from a simple specification,
5which makes it easy to accommodate changes in the grammar. This
6approach would be quite reasonable if the grammar changed often.
7Instead, it is rather complex to generate the appropriate code. And
8the Node interface has changed more often than the grammar.
9"""
10
11import fileinput
12import re
13import sys
14from io import StringIO
15
16SPEC = "ast.txt"
17COMMA = ", "
18
19def load_boilerplate(file):
20 f = open(file)
21 buf = f.read()
22 f.close()
23 i = buf.find('### ''PROLOGUE')
24 j = buf.find('### ''EPILOGUE')
25 pro = buf[i+12:j].strip()
26 epi = buf[j+12:].strip()
27 return pro, epi
28
29def strip_default(arg):
30 """Return the argname from an 'arg = default' string"""
31 i = arg.find('=')
32 if i == -1:
33 return arg
34 t = arg[:i].strip()
35 return t
36
37P_NODE = 1
38P_OTHER = 2
39P_NESTED = 3
40P_NONE = 4
41
42class NodeInfo:
43 """Each instance describes a specific AST node"""
44 def __init__(self, name, args):
45 self.name = name
46 self.args = args.strip()
47 self.argnames = self.get_argnames()
48 self.argprops = self.get_argprops()
49 self.nargs = len(self.argnames)
50 self.init = []
51
52 def get_argnames(self):
53 if '(' in self.args:
54 i = self.args.find('(')
55 j = self.args.rfind(')')
56 args = self.args[i+1:j]
57 else:
58 args = self.args
59 return [strip_default(arg.strip())
60 for arg in args.split(',') if arg]
61
62 def get_argprops(self):
63 """Each argument can have a property like '*' or '!'
64
65 XXX This method modifies the argnames in place!
66 """
67 d = {}
68 hardest_arg = P_NODE
69 for i in range(len(self.argnames)):
70 arg = self.argnames[i]
71 if arg.endswith('*'):
72 arg = self.argnames[i] = arg[:-1]
73 d[arg] = P_OTHER
74 hardest_arg = max(hardest_arg, P_OTHER)
75 elif arg.endswith('!'):
76 arg = self.argnames[i] = arg[:-1]
77 d[arg] = P_NESTED
78 hardest_arg = max(hardest_arg, P_NESTED)
79 elif arg.endswith('&'):
80 arg = self.argnames[i] = arg[:-1]
81 d[arg] = P_NONE
82 hardest_arg = max(hardest_arg, P_NONE)
83 else:
84 d[arg] = P_NODE
85 self.hardest_arg = hardest_arg
86
87 if hardest_arg > P_NODE:
88 self.args = self.args.replace('*', '')
89 self.args = self.args.replace('!', '')
90 self.args = self.args.replace('&', '')
91
92 return d
93
94 def gen_source(self):
95 buf = StringIO()
96 print("class %s(Node):" % self.name, file=buf)
97 self._gen_init(buf)
98 print(file=buf)
99 self._gen_getChildren(buf)
100 print(file=buf)
101 self._gen_getChildNodes(buf)
102 print(file=buf)
103 self._gen_repr(buf)
104 buf.seek(0, 0)
105 return buf.read()
106
107 def _gen_init(self, buf):
108 if self.args:
109 argtuple = '(' in self.args
110 args = self.args if not argtuple else ''.join(self.argnames)
111 print(" def __init__(self, %s, lineno=None):" % args, file=buf)
112 else:
113 print(" def __init__(self, lineno=None):", file=buf)
114 if self.argnames:
115 if argtuple:
116 for idx, name in enumerate(self.argnames):
117 print(" self.%s = %s[%s]" % (name, args, idx), file=buf)
118 else:
119 for name in self.argnames:
120 print(" self.%s = %s" % (name, name), file=buf)
121 print(" self.lineno = lineno", file=buf)
122 # Copy the lines in self.init, indented four spaces. The rstrip()
123 # business is to get rid of the four spaces if line happens to be
124 # empty, so that reindent.py is happy with the output.
125 for line in self.init:
126 print((" " + line).rstrip(), file=buf)
127
128 def _gen_getChildren(self, buf):
129 print(" def getChildren(self):", file=buf)
130 if len(self.argnames) == 0:
131 print(" return ()", file=buf)
132 else:
133 if self.hardest_arg < P_NESTED:
134 clist = COMMA.join(["self.%s" % c
135 for c in self.argnames])
136 if self.nargs == 1:
137 print(" return %s," % clist, file=buf)
138 else:
139 print(" return %s" % clist, file=buf)
140 else:
141 if len(self.argnames) == 1:
142 print(" return tuple(flatten(self.%s))" % self.argnames[0], file=buf)
143 else:
144 print(" children = []", file=buf)
145 template = " children.%s(%sself.%s%s)"
146 for name in self.argnames:
147 if self.argprops[name] == P_NESTED:
148 print(template % ("extend", "flatten(",
149 name, ")"), file=buf)
150 else:
151 print(template % ("append", "", name, ""), file=buf)
152 print(" return tuple(children)", file=buf)
153
154 def _gen_getChildNodes(self, buf):
155 print(" def getChildNodes(self):", file=buf)
156 if len(self.argnames) == 0:
157 print(" return ()", file=buf)
158 else:
159 if self.hardest_arg < P_NESTED:
160 clist = ["self.%s" % c
161 for c in self.argnames
162 if self.argprops[c] == P_NODE]
163 if len(clist) == 0:
164 print(" return ()", file=buf)
165 elif len(clist) == 1:
166 print(" return %s," % clist[0], file=buf)
167 else:
168 print(" return %s" % COMMA.join(clist), file=buf)
169 else:
170 print(" nodelist = []", file=buf)
171 template = " nodelist.%s(%sself.%s%s)"
172 for name in self.argnames:
173 if self.argprops[name] == P_NONE:
174 tmp = (" if self.%s is not None:\n"
175 " nodelist.append(self.%s)")
176 print(tmp % (name, name), file=buf)
177 elif self.argprops[name] == P_NESTED:
178 print(template % ("extend", "flatten_nodes(",
179 name, ")"), file=buf)
180 elif self.argprops[name] == P_NODE:
181 print(template % ("append", "", name, ""), file=buf)
182 print(" return tuple(nodelist)", file=buf)
183
184 def _gen_repr(self, buf):
185 print(" def __repr__(self):", file=buf)
186 if self.argnames:
187 fmt = COMMA.join(["%s"] * self.nargs)
188 if '(' in self.args:
189 fmt = '(%s)' % fmt
190 vals = ["repr(self.%s)" % name for name in self.argnames]
191 vals = COMMA.join(vals)
192 if self.nargs == 1:
193 vals = vals + ","
194 print(' return "%s(%s)" %% (%s)' % \
195 (self.name, fmt, vals), file=buf)
196 else:
197 print(' return "%s()"' % self.name, file=buf)
198
199rx_init = re.compile('init\((.*)\):')
200
201def parse_spec(file):
202 classes = {}
203 cur = None
204 for line in fileinput.input(file):
205 if line.strip().startswith('#'):
206 continue
207 mo = rx_init.search(line)
208 if mo is None:
209 if cur is None:
210 # a normal entry
211 try:
212 name, args = line.split(':')
213 except ValueError:
214 continue
215 classes[name] = NodeInfo(name, args)
216 cur = None
217 else:
218 # some code for the __init__ method
219 cur.init.append(line)
220 else:
221 # some extra code for a Node's __init__ method
222 name = mo.group(1)
223 cur = classes[name]
224 return sorted(list(classes.values()), key=lambda n: n.name)
225
226def main():
227 prologue, epilogue = load_boilerplate(sys.argv[-1])
228 print(prologue)
229 print()
230 classes = parse_spec(SPEC)
231 for info in classes:
232 print(info.gen_source())
233 print(epilogue)
234
235if __name__ == "__main__":
236 main()
237 sys.exit(0)
238
239### PROLOGUE
240"""Python abstract syntax node definitions
241
242This file is automatically generated by Tools/compiler/astgen.py
243"""
244from consts import CO_VARARGS, CO_VARKEYWORDS
245
246def flatten(seq):
247 l = []
248 for elt in seq:
249 t = type(elt)
250 if t is tuple or t is list:
251 for elt2 in flatten(elt):
252 l.append(elt2)
253 else:
254 l.append(elt)
255 return l
256
257def flatten_nodes(seq):
258 return [n for n in flatten(seq) if isinstance(n, Node)]
259
260nodes = {}
261
262class Node:
263 """Abstract base class for ast nodes."""
264 def getChildren(self):
265 pass # implemented by subclasses
266 def __iter__(self):
267 for n in self.getChildren():
268 yield n
269 def asList(self): # for backwards compatibility
270 return self.getChildren()
271 def getChildNodes(self):
272 pass # implemented by subclasses
273
274class EmptyNode(Node):
275 pass
276
277class Expression(Node):
278 # Expression is an artificial node class to support "eval"
279 nodes["expression"] = "Expression"
280 def __init__(self, node):
281 self.node = node
282
283 def getChildren(self):
284 return self.node,
285
286 def getChildNodes(self):
287 return self.node,
288
289 def __repr__(self):
290 return "Expression(%s)" % (repr(self.node))
291
292### EPILOGUE
293for name, obj in list(globals().items()):
294 if isinstance(obj, type) and issubclass(obj, Node):
295 nodes[name.lower()] = obj