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

303 lines, 178 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 return p
48
49
50class UserType(object):
51 """TODO: Delete this class after we have modules with 'use'?"""
52
53 def __init__(self, mod_name, type_name):
54 self.mod_name = mod_name
55 self.type_name = type_name
56
57 def __repr__(self):
58 return '<UserType %s %s>' % (self.mod_name, self.type_name)
59
60
61def main(argv):
62 o = Options()
63 opts, argv = o.parse_args(argv)
64
65 try:
66 action = argv[1]
67 except IndexError:
68 raise RuntimeError('Action required')
69
70 try:
71 schema_path = argv[2]
72 except IndexError:
73 raise RuntimeError('Schema path required')
74
75 schema_filename = os.path.basename(schema_path)
76 if schema_filename in ('syntax.asdl', 'runtime.asdl'):
77 app_types = {'id': UserType('id_kind_asdl', 'Id_t')}
78 else:
79 app_types = {}
80
81 if action == 'c': # Generate C code for the lexer
82 with open(schema_path) as f:
83 schema_ast = front_end.LoadSchema(f, app_types)
84
85 v = gen_cpp.CEnumVisitor(sys.stdout)
86 v.VisitModule(schema_ast)
87
88 elif action == 'cpp': # Generate C++ code for ASDL schemas
89 out_prefix = argv[3]
90
91 with open(schema_path) as f:
92 schema_ast = front_end.LoadSchema(f, app_types)
93
94 # asdl/typed_arith.asdl -> typed_arith_asdl
95 ns = os.path.basename(schema_path).replace('.', '_')
96
97 with open(out_prefix + '.h', 'w') as f:
98 guard = ns.upper()
99 f.write("""\
100// %s.h is generated by %s
101
102#ifndef %s
103#define %s
104
105""" % (out_prefix, ARG_0, guard, guard))
106
107 f.write("""\
108#include <cstdint>
109""")
110 f.write("""
111#include "mycpp/runtime.h"
112""")
113 if opts.pretty_print_methods:
114 if 0:
115 # TODO: gradually migrate to this templated code, reducing code gen
116 f.write('#include "asdl/cpp_runtime.h"\n')
117 else:
118 f.write("""\
119#include "_gen/asdl/hnode.asdl.h"
120using hnode_asdl::hnode_t;
121
122 """)
123
124 if app_types:
125 f.write("""\
126#include "_gen/frontend/id_kind.asdl.h"
127using id_kind_asdl::Id_t;
128
129""")
130
131 for use in schema_ast.uses:
132 # Forward declarations in the header, like
133 # namespace syntax_asdl { class command_t; }
134 # must come BEFORE namespace, so it can't be in the visitor.
135
136 # assume sum type for now!
137 cpp_names = [
138 'class %s;' % ast.TypeNameHeuristic(n)
139 for n in use.type_names
140 ]
141 f.write('namespace %s_asdl { %s }\n' %
142 (use.module_parts[-1], ' '.join(cpp_names)))
143 f.write('\n')
144
145 f.write("""\
146namespace %s {
147
148// use struct instead of namespace so 'using' works consistently
149#define ASDL_NAMES struct
150
151""" % ns)
152
153 v = gen_cpp.ForwardDeclareVisitor(f)
154 v.VisitModule(schema_ast)
155
156 debug_info = {}
157 v2 = gen_cpp.ClassDefVisitor(
158 f,
159 pretty_print_methods=opts.pretty_print_methods,
160 debug_info=debug_info)
161 v2.VisitModule(schema_ast)
162
163 f.write("""
164} // namespace %s
165
166#endif // %s
167""" % (ns, guard))
168
169 try:
170 debug_info_path = argv[4]
171 except IndexError:
172 pass
173 else:
174 with open(debug_info_path, 'w') as f:
175 from pprint import pformat
176 f.write('''\
177cpp_namespace = %r
178tags_to_types = \\
179%s
180''' % (ns, pformat(debug_info)))
181
182 if not opts.pretty_print_methods:
183 return
184
185 with open(out_prefix + '.cc', 'w') as f:
186 f.write("""\
187// %s.cc is generated by %s
188
189#include "%s.h"
190#include <assert.h>
191""" % (out_prefix, ARG_0, out_prefix))
192
193 f.write("""\
194#include "prebuilt/asdl/runtime.mycpp.h" // generated code uses wrappers here
195""")
196
197 # To call pretty-printing methods
198 for use in schema_ast.uses:
199 f.write('#include "_gen/%s.asdl.h" // "use" in ASDL \n' %
200 '/'.join(use.module_parts))
201
202 f.write("""\
203
204// Generated code uses these types
205using hnode_asdl::hnode;
206using hnode_asdl::Field;
207using hnode_asdl::color_e;
208
209""")
210
211 if app_types:
212 f.write('using id_kind_asdl::Id_str;\n')
213
214 f.write("""
215namespace %s {
216
217""" % ns)
218
219 v3 = gen_cpp.MethodDefVisitor(f)
220 v3.VisitModule(schema_ast)
221
222 f.write("""
223} // namespace %s
224""" % ns)
225
226 elif action == 'mypy': # Generated typed MyPy code
227 with open(schema_path) as f:
228 schema_ast = front_end.LoadSchema(f, app_types)
229
230 try:
231 abbrev_module_name = argv[3]
232 except IndexError:
233 abbrev_mod = None
234 else:
235 # Weird Python rule for importing: fromlist needs to be non-empty.
236 abbrev_mod = __import__(abbrev_module_name, fromlist=['.'])
237
238 f = sys.stdout
239
240 # TODO: Remove Any once we stop using it
241 f.write("""\
242from asdl import pybase
243from mycpp import mops
244from typing import Optional, List, Tuple, Dict, Any, cast, TYPE_CHECKING
245""")
246
247 if schema_ast.uses:
248 f.write('\n')
249 f.write('if TYPE_CHECKING:\n')
250 for use in schema_ast.uses:
251 py_names = [ast.TypeNameHeuristic(n) for n in use.type_names]
252 # indented
253 f.write(' from _devbuild.gen.%s_asdl import %s\n' %
254 (use.module_parts[-1], ', '.join(py_names)))
255 f.write('\n')
256
257 for typ in app_types.itervalues():
258 if isinstance(typ, UserType):
259 f.write('from _devbuild.gen.%s import %s\n' %
260 (typ.mod_name, typ.type_name))
261 # HACK
262 f.write('from _devbuild.gen.%s import Id_str\n' % typ.mod_name)
263 f.write('\n')
264
265 if opts.pretty_print_methods:
266 f.write("""
267from asdl import runtime # For runtime.NO_SPID
268from asdl.runtime import NewRecord, NewLeaf, TraversalState
269from _devbuild.gen.hnode_asdl import color_e, hnode, hnode_e, hnode_t, Field
270
271""")
272
273 abbrev_mod_entries = dir(abbrev_mod) if abbrev_mod else []
274 v = gen_python.GenMyPyVisitor(
275 f,
276 abbrev_mod_entries,
277 pretty_print_methods=opts.pretty_print_methods,
278 py_init_n=opts.py_init_n)
279 v.VisitModule(schema_ast)
280
281 if abbrev_mod:
282 f.write("""\
283#
284# CONCATENATED FILE
285#
286
287""")
288 first, module = abbrev_module_name.rsplit('.', 1)
289 dir_name = first.replace('.', '/')
290 path = os.path.join(dir_name, module + '.py')
291 with open(path) as in_f:
292 f.write(in_f.read())
293
294 else:
295 raise RuntimeError('Invalid action %r' % action)
296
297
298if __name__ == '__main__':
299 try:
300 main(sys.argv)
301 except RuntimeError as e:
302 print('%s: FATAL: %s' % (ARG_0, e), file=sys.stderr)
303 sys.exit(1)