OILS / asdl / asdl_main.py View on Github | oils.pub

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