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

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