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

335 lines, 195 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 opts.abbrev_module:
72 # Weird Python rule for importing: fromlist needs to be non-empty.
73 abbrev_mod = __import__(opts.abbrev_module, fromlist=['.'])
74 else:
75 abbrev_mod = None
76
77 abbrev_mod_entries = dir(abbrev_mod) if abbrev_mod else []
78 # e.g. syntax_abbrev
79 abbrev_ns = opts.abbrev_module.split('.')[-1] if abbrev_mod else None
80
81 # TODO: remove this
82 app_types = {}
83
84 if action == 'metrics': # Sum type metrics
85 with open(schema_path) as f:
86 schema_ast = front_end.LoadSchema(f, app_types)
87
88 v = metrics.MetricsVisitor(sys.stdout)
89 v.VisitModule(schema_ast)
90
91 elif action == 'closure': # count all types that command_t references
92 type_name = argv[3]
93 with open(schema_path) as f:
94 schema_ast = front_end.LoadSchema(f,
95 app_types,
96 do_closure=type_name)
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 for use in schema_ast.uses:
134 # HACK to work around limitation: we can't "use" simple enums
135 if use.module_parts == ['frontend', 'id_kind']:
136 f.write('namespace %s_asdl { typedef uint16_t Id_t; }\n' %
137 use.module_parts[-1])
138 continue
139
140 # Forward declarations in the header, like
141 # namespace syntax_asdl { class command_t; }
142 # must come BEFORE namespace, so it can't be in the visitor.
143
144 # assume sum type for now!
145 cpp_names = []
146 for n in use.type_names:
147 py_name, _ = ast.TypeNameHeuristic(n)
148 cpp_names.append('class %s;' % py_name)
149
150 f.write('namespace %s_asdl { %s }\n' %
151 (use.module_parts[-1], ' '.join(cpp_names)))
152 f.write('\n')
153
154 for extern in schema_ast.externs:
155 names = extern.names
156 type_name = names[-1]
157 cpp_namespace = names[-2]
158
159 # TODO: This 'extern' feature isn't enough for Oils
160 # I think we would have to export header to
161 # _gen/bin/oils_for_unix.mycpp.cc or something
162 # Does that create circular dependencies?
163 #
164 # Or maybe of 'extern' we can have 'include' or something?
165 # Maybe we need `.pyi` files in MyPy?
166
167 f.write("""\
168namespace %s {
169class %s {
170 public:
171 hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen);
172};
173}
174""" % (cpp_namespace, type_name))
175
176 f.write("""\
177namespace %s {
178
179// use struct instead of namespace so 'using' works consistently
180#define ASDL_NAMES struct
181
182""" % ns)
183
184 v = gen_cpp.ForwardDeclareVisitor(f)
185 v.VisitModule(schema_ast)
186
187 debug_info = {}
188 v2 = gen_cpp.ClassDefVisitor(
189 f,
190 pretty_print_methods=opts.pretty_print_methods,
191 debug_info=debug_info)
192 v2.VisitModule(schema_ast)
193
194 f.write("""
195} // namespace %s
196
197#endif // %s
198""" % (ns, guard))
199
200 try:
201 debug_info_path = argv[4]
202 except IndexError:
203 pass
204 else:
205 with open(debug_info_path, 'w') as f:
206 from pprint import pformat
207 f.write('''\
208cpp_namespace = %r
209tags_to_types = \\
210%s
211''' % (ns, pformat(debug_info)))
212
213 if not opts.pretty_print_methods:
214 # No .cc file at all
215 return
216
217 with open(out_prefix + '.cc', 'w') as f:
218 f.write("""\
219// %s.cc is generated by %s
220
221#include "%s.h"
222#include <assert.h>
223""" % (out_prefix, ARG_0, out_prefix))
224
225 if abbrev_mod_entries:
226 # This is somewhat hacky, works for frontend/syntax_abbrev.py and
227 # prebuilt/frontend/syntax_abbrev.mycpp.h
228 part0, part1 = opts.abbrev_module.split('.')
229 f.write("""\
230#include "prebuilt/%s/%s.mycpp.h"
231""" % (part0, part1))
232
233 f.write("""\
234#include "prebuilt/asdl/runtime.mycpp.h" // generated code uses wrappers here
235""")
236
237 # To call pretty-printing methods
238 for use in schema_ast.uses:
239 f.write('#include "_gen/%s.asdl.h" // "use" in ASDL\n' %
240 '/'.join(use.module_parts))
241 # HACK
242 if use.module_parts == ['frontend', 'id_kind']:
243 f.write('using id_kind_asdl::Id_str;\n')
244
245 f.write("""\
246
247// Generated code uses these types
248using hnode_asdl::hnode;
249using hnode_asdl::Field;
250using hnode_asdl::color_e;
251
252""")
253
254 f.write("""
255namespace %s {
256
257""" % ns)
258
259 v3 = gen_cpp.MethodDefVisitor(
260 f,
261 abbrev_ns=abbrev_ns,
262 abbrev_mod_entries=abbrev_mod_entries)
263 v3.VisitModule(schema_ast)
264
265 f.write("""
266} // namespace %s
267""" % ns)
268
269 elif action == 'mypy': # Generated typed MyPy code
270 with open(schema_path) as f:
271 schema_ast = front_end.LoadSchema(f, app_types)
272
273 f = sys.stdout
274
275 # TODO: Remove Any once we stop using it
276 f.write("""\
277from asdl import pybase
278from mycpp import mops
279from typing import Optional, List, Tuple, Dict, Any, cast, TYPE_CHECKING
280""")
281
282 for use in schema_ast.uses:
283 # HACK
284 if use.module_parts == ['frontend', 'id_kind']:
285 f.write('from _devbuild.gen.id_kind_asdl import Id_str\n')
286
287 if schema_ast.uses:
288 f.write('\n')
289 f.write('if TYPE_CHECKING:\n')
290 for use in schema_ast.uses:
291 py_names = []
292 for n in use.type_names:
293 py_name, _ = ast.TypeNameHeuristic(n)
294 py_names.append(py_name)
295
296 # indented
297 f.write(' from _devbuild.gen.%s_asdl import %s\n' %
298 (use.module_parts[-1], ', '.join(py_names)))
299
300 if schema_ast.externs:
301 f.write('\n')
302 f.write('if TYPE_CHECKING:\n')
303 for extern in schema_ast.externs:
304 n = extern.names
305 mod_parts = n[:-2]
306 f.write(' from %s import %s\n' % ('.'.join(mod_parts), n[-2]))
307
308 if opts.pretty_print_methods:
309 f.write("""
310from asdl import runtime # For runtime.NO_SPID
311from asdl.runtime import NewRecord, NewLeaf, TraversalState
312from _devbuild.gen.hnode_asdl import color_e, hnode, hnode_e, hnode_t, Field
313
314""")
315 if opts.abbrev_module:
316 f.write('from %s import *\n' % opts.abbrev_module)
317 f.write('\n')
318
319 v = gen_python.GenMyPyVisitor(
320 f,
321 abbrev_mod_entries,
322 pretty_print_methods=opts.pretty_print_methods,
323 py_init_n=opts.py_init_n)
324 v.VisitModule(schema_ast)
325
326 else:
327 raise RuntimeError('Invalid action %r' % action)
328
329
330if __name__ == '__main__':
331 try:
332 main(sys.argv)
333 except RuntimeError as e:
334 print('%s: FATAL: %s' % (ARG_0, e), file=sys.stderr)
335 sys.exit(1)