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

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