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

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