OILS / asdl / asdl_main.py View on Github | oilshell.org

344 lines, 197 significant
1#!/usr/bin/env python2
2"""
3asdl_main.py - Generate Python and C from ASDL schemas.
4"""
5from __future__ import print_function
6
7import optparse
8import os
9import sys
10
11from asdl import ast
12from asdl import front_end
13from asdl import gen_cpp
14from asdl import gen_python
15#from asdl.util import log
16
17ARG_0 = os.path.basename(sys.argv[0])
18
19
20def Options():
21 """Returns an option parser instance."""
22
23 p = optparse.OptionParser()
24 p.add_option('--no-pretty-print-methods',
25 dest='pretty_print_methods',
26 action='store_false',
27 default=True,
28 help='Whether to generate pretty printing methods')
29
30 # Control Python constructors
31
32 # for hnode.asdl
33 p.add_option('--py-init-N',
34 dest='py_init_n',
35 action='store_true',
36 default=False,
37 help='Generate Python __init__ that requires every field')
38
39 # The default, which matches C++
40 p.add_option(
41 '--init-zero-N',
42 dest='init_zero_n',
43 action='store_true',
44 default=True,
45 help='Generate 0 arg and N arg constructors, in Python and C++')
46
47 p.add_option('--abbrev-module',
48 dest='abbrev_module',
49 default=None,
50 help='Import this module to find abbreviations')
51
52 return p
53
54
55class UserType(object):
56 """TODO: Delete this class after we have modules with 'use'?"""
57
58 def __init__(self, mod_name, type_name):
59 self.mod_name = mod_name
60 self.type_name = type_name
61
62 def __repr__(self):
63 return '<UserType %s %s>' % (self.mod_name, self.type_name)
64
65
66def main(argv):
67 o = Options()
68 opts, argv = o.parse_args(argv)
69
70 try:
71 action = argv[1]
72 except IndexError:
73 raise RuntimeError('Action required')
74
75 try:
76 schema_path = argv[2]
77 except IndexError:
78 raise RuntimeError('Schema path required')
79
80 schema_filename = os.path.basename(schema_path)
81 if schema_filename in ('syntax.asdl', 'runtime.asdl'):
82 app_types = {'id': UserType('id_kind_asdl', 'Id_t')}
83 else:
84 app_types = {}
85
86 if opts.abbrev_module:
87 # Weird Python rule for importing: fromlist needs to be non-empty.
88 abbrev_mod = __import__(opts.abbrev_module, fromlist=['.'])
89 else:
90 abbrev_mod = None
91
92 abbrev_mod_entries = dir(abbrev_mod) if abbrev_mod else []
93 # e.g. syntax_abbrev
94 abbrev_ns = opts.abbrev_module.split('.')[-1] if abbrev_mod else None
95
96 if action == 'c': # Generate C code for the lexer
97 with open(schema_path) as f:
98 schema_ast = front_end.LoadSchema(f, app_types)
99
100 v = gen_cpp.CEnumVisitor(sys.stdout)
101 v.VisitModule(schema_ast)
102
103 elif action == 'cpp': # Generate C++ code for ASDL schemas
104 out_prefix = argv[3]
105
106 with open(schema_path) as f:
107 schema_ast = front_end.LoadSchema(f, app_types)
108
109 # asdl/typed_arith.asdl -> typed_arith_asdl
110 ns = os.path.basename(schema_path).replace('.', '_')
111
112 with open(out_prefix + '.h', 'w') as f:
113 guard = ns.upper()
114 f.write("""\
115// %s.h is generated by %s
116
117#ifndef %s
118#define %s
119
120""" % (out_prefix, ARG_0, guard, guard))
121
122 f.write("""\
123#include <cstdint>
124""")
125 f.write("""
126#include "mycpp/runtime.h"
127""")
128 if opts.pretty_print_methods:
129 if 1:
130 # TODO: gradually migrate to this templated code, reducing code gen
131 f.write('#include "asdl/cpp_runtime.h"\n')
132 else:
133 f.write("""\
134#include "_gen/asdl/hnode.asdl.h"
135using hnode_asdl::hnode_t;
136
137""")
138
139 if app_types:
140 f.write("""\
141#include "_gen/frontend/id_kind.asdl.h"
142using id_kind_asdl::Id_t;
143
144""")
145 # Only works with gross hacks
146 if 0:
147 #if schema_path.endswith('/syntax.asdl'):
148 f.write(
149 '#include "prebuilt/frontend/syntax_abbrev.mycpp.h"\n')
150
151 for use in schema_ast.uses:
152 # Forward declarations in the header, like
153 # namespace syntax_asdl { class command_t; }
154 # must come BEFORE namespace, so it can't be in the visitor.
155
156 # assume sum type for now!
157 cpp_names = [
158 'class %s;' % ast.TypeNameHeuristic(n)
159 for n in use.type_names
160 ]
161 f.write('namespace %s_asdl { %s }\n' %
162 (use.module_parts[-1], ' '.join(cpp_names)))
163 f.write('\n')
164
165 for extern in schema_ast.externs:
166 names = extern.names
167 type_name = names[-1]
168 cpp_namespace = names[-2]
169
170 # TODO: This isn't enough for Oils
171 # I think we would have to export header to
172 # _gen/bin/oils_for_unix.mycpp.cc or something
173 # Does that create circular dependencies?
174 #
175 # Or maybe of 'extern' we can have 'include' or something?
176 # Maybe we need `.pyi` files in MyPy?
177
178 f.write("""\
179namespace %s {
180class %s {
181 public:
182 hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen);
183};
184}
185""" % (cpp_namespace, type_name))
186
187 f.write("""\
188namespace %s {
189
190// use struct instead of namespace so 'using' works consistently
191#define ASDL_NAMES struct
192
193""" % ns)
194
195 v = gen_cpp.ForwardDeclareVisitor(f)
196 v.VisitModule(schema_ast)
197
198 debug_info = {}
199 v2 = gen_cpp.ClassDefVisitor(
200 f,
201 pretty_print_methods=opts.pretty_print_methods,
202 debug_info=debug_info)
203 v2.VisitModule(schema_ast)
204
205 f.write("""
206} // namespace %s
207
208#endif // %s
209""" % (ns, guard))
210
211 try:
212 debug_info_path = argv[4]
213 except IndexError:
214 pass
215 else:
216 with open(debug_info_path, 'w') as f:
217 from pprint import pformat
218 f.write('''\
219cpp_namespace = %r
220tags_to_types = \\
221%s
222''' % (ns, pformat(debug_info)))
223
224 if not opts.pretty_print_methods:
225 # No .cc file at all
226 return
227
228 with open(out_prefix + '.cc', 'w') as f:
229 f.write("""\
230// %s.cc is generated by %s
231
232#include "%s.h"
233#include <assert.h>
234""" % (out_prefix, ARG_0, out_prefix))
235
236 if abbrev_mod_entries:
237 # This is somewhat hacky, works for frontend/syntax_abbrev.py and
238 # prebuilt/frontend/syntax_abbrev.mycpp.h
239 part0, part1 = opts.abbrev_module.split('.')
240 f.write("""\
241#include "prebuilt/%s/%s.mycpp.h"
242""" % (part0, part1))
243
244 f.write("""\
245#include "prebuilt/asdl/runtime.mycpp.h" // generated code uses wrappers here
246""")
247
248 # To call pretty-printing methods
249 for use in schema_ast.uses:
250 f.write('#include "_gen/%s.asdl.h" // "use" in ASDL \n' %
251 '/'.join(use.module_parts))
252
253 f.write("""\
254
255// Generated code uses these types
256using hnode_asdl::hnode;
257using hnode_asdl::Field;
258using hnode_asdl::color_e;
259
260""")
261
262 if app_types:
263 f.write('using id_kind_asdl::Id_str;\n')
264
265 f.write("""
266namespace %s {
267
268""" % ns)
269
270 v3 = gen_cpp.MethodDefVisitor(
271 f,
272 abbrev_ns=abbrev_ns,
273 abbrev_mod_entries=abbrev_mod_entries)
274 v3.VisitModule(schema_ast)
275
276 f.write("""
277} // namespace %s
278""" % ns)
279
280 elif action == 'mypy': # Generated typed MyPy code
281 with open(schema_path) as f:
282 schema_ast = front_end.LoadSchema(f, app_types)
283
284 f = sys.stdout
285
286 # TODO: Remove Any once we stop using it
287 f.write("""\
288from asdl import pybase
289from mycpp import mops
290from typing import Optional, List, Tuple, Dict, Any, cast, TYPE_CHECKING
291""")
292
293 if schema_ast.uses:
294 f.write('\n')
295 f.write('if TYPE_CHECKING:\n')
296 for use in schema_ast.uses:
297 py_names = [ast.TypeNameHeuristic(n) for n in use.type_names]
298 # indented
299 f.write(' from _devbuild.gen.%s_asdl import %s\n' %
300 (use.module_parts[-1], ', '.join(py_names)))
301
302 if schema_ast.externs:
303 f.write('\n')
304 f.write('if TYPE_CHECKING:\n')
305 for extern in schema_ast.externs:
306 n = extern.names
307 mod_parts = n[:-2]
308 f.write(' from %s import %s\n' % ('.'.join(mod_parts), n[-2]))
309
310 for typ in app_types.itervalues():
311 if isinstance(typ, UserType):
312 f.write('from _devbuild.gen.%s import %s\n' %
313 (typ.mod_name, typ.type_name))
314 # HACK
315 f.write('from _devbuild.gen.%s import Id_str\n' % typ.mod_name)
316
317 if opts.pretty_print_methods:
318 f.write("""
319from asdl import runtime # For runtime.NO_SPID
320from asdl.runtime import NewRecord, NewLeaf, TraversalState
321from _devbuild.gen.hnode_asdl import color_e, hnode, hnode_e, hnode_t, Field
322
323""")
324 if opts.abbrev_module:
325 f.write('from %s import *\n' % opts.abbrev_module)
326 f.write('\n')
327
328 v = gen_python.GenMyPyVisitor(
329 f,
330 abbrev_mod_entries,
331 pretty_print_methods=opts.pretty_print_methods,
332 py_init_n=opts.py_init_n)
333 v.VisitModule(schema_ast)
334
335 else:
336 raise RuntimeError('Invalid action %r' % action)
337
338
339if __name__ == '__main__':
340 try:
341 main(sys.argv)
342 except RuntimeError as e:
343 print('%s: FATAL: %s' % (ARG_0, e), file=sys.stderr)
344 sys.exit(1)