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

339 lines, 200 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
16from asdl.util import log
17
18from typing import Dict
19
20ARG_0 = os.path.basename(sys.argv[0])
21
22
23def Options():
24 """Returns an option parser instance."""
25
26 p = optparse.OptionParser()
27 p.add_option('--no-pretty-print-methods',
28 dest='pretty_print_methods',
29 action='store_false',
30 default=True,
31 help='Whether to generate pretty printing methods')
32
33 # Control Python constructors
34
35 # for hnode.asdl
36 p.add_option('--py-init-N',
37 dest='py_init_n',
38 action='store_true',
39 default=False,
40 help='Generate Python __init__ that requires every field')
41
42 # The default, which matches C++
43 p.add_option(
44 '--init-zero-N',
45 dest='init_zero_n',
46 action='store_true',
47 default=True,
48 help='Generate 0 arg and N arg constructors, in Python and C++')
49
50 p.add_option('--abbrev-module',
51 dest='abbrev_module',
52 default=None,
53 help='Import this module to find abbreviations')
54
55 return p
56
57
58def main(argv):
59 o = Options()
60 opts, argv = o.parse_args(argv)
61
62 try:
63 action = argv[1]
64 except IndexError:
65 raise RuntimeError('Action required')
66
67 try:
68 schema_path = argv[2]
69 except IndexError:
70 raise RuntimeError('Schema path required')
71
72 schema_filename = os.path.basename(schema_path)
73 if opts.abbrev_module:
74 # Weird Python rule for importing: fromlist needs to be non-empty.
75 abbrev_mod = __import__(opts.abbrev_module, fromlist=['.'])
76 else:
77 abbrev_mod = None
78
79 abbrev_mod_entries = dir(abbrev_mod) if abbrev_mod else []
80 # e.g. syntax_abbrev
81 abbrev_ns = opts.abbrev_module.split('.')[-1] if abbrev_mod else None
82
83 if action == 'metrics': # Sum type metrics
84 with open(schema_path) as f:
85 schema_ast, _ = front_end.LoadSchema(f)
86
87 v = metrics.MetricsVisitor(sys.stdout)
88 v.VisitModule(schema_ast)
89
90 elif action == 'closure': # count all types that command_t references
91 type_name = argv[3]
92 with open(schema_path) as f:
93 schema_ast, type_lookup = front_end.LoadSchema(f)
94
95 seen = {} # type: Dict[str, bool]
96 c = metrics.ClosureWalk(type_lookup, seen)
97 c.DoModule(schema_ast, type_name)
98 for name in sorted(seen):
99 print(name)
100 log('SHARED (%d): %s', len(c.shared), ' '.join(sorted(c.shared)))
101
102 elif action == 'c': # Generate C code for the lexer
103 with open(schema_path) as f:
104 schema_ast, _ = front_end.LoadSchema(f)
105
106 v = gen_cpp.CEnumVisitor(sys.stdout)
107 v.VisitModule(schema_ast)
108
109 elif action == 'cpp': # Generate C++ code for ASDL schemas
110 out_prefix = argv[3]
111
112 with open(schema_path) as f:
113 schema_ast, _ = front_end.LoadSchema(f)
114
115 # asdl/typed_arith.asdl -> typed_arith_asdl
116 ns = os.path.basename(schema_path).replace('.', '_')
117
118 with open(out_prefix + '.h', 'w') as f:
119 guard = ns.upper()
120 f.write("""\
121// %s.h is generated by %s
122
123#ifndef %s
124#define %s
125
126""" % (out_prefix, ARG_0, guard, guard))
127
128 f.write("""\
129#include <cstdint>
130""")
131 f.write("""
132#include "mycpp/runtime.h"
133""")
134 if opts.pretty_print_methods:
135 f.write('#include "asdl/cpp_runtime.h"\n')
136
137 for use in schema_ast.uses:
138 # HACK to work around limitation: we can't "use" simple enums
139 if use.module_parts == ['frontend', 'id_kind']:
140 f.write('namespace %s_asdl { typedef uint16_t Id_t; }\n' %
141 use.module_parts[-1])
142 continue
143
144 # Forward declarations in the header, like
145 # namespace syntax_asdl { class command_t; }
146 # must come BEFORE namespace, so it can't be in the visitor.
147
148 # assume sum type for now!
149 cpp_names = []
150 for n in use.type_names:
151 py_name, _ = ast.TypeNameHeuristic(n)
152 cpp_names.append('class %s;' % py_name)
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 # HACK
246 if use.module_parts == ['frontend', 'id_kind']:
247 f.write('using id_kind_asdl::Id_str;\n')
248
249 f.write("""\
250
251// Generated code uses these types
252using hnode_asdl::hnode;
253using hnode_asdl::Field;
254using hnode_asdl::color_e;
255
256""")
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)
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 for use in schema_ast.uses:
287 # HACK
288 if use.module_parts == ['frontend', 'id_kind']:
289 f.write('from _devbuild.gen.id_kind_asdl import Id_str\n')
290
291 if schema_ast.uses:
292 f.write('\n')
293 f.write('if TYPE_CHECKING:\n')
294 for use in schema_ast.uses:
295 py_names = []
296 for n in use.type_names:
297 py_name, _ = ast.TypeNameHeuristic(n)
298 py_names.append(py_name)
299
300 # indented
301 f.write(' from _devbuild.gen.%s_asdl import %s\n' %
302 (use.module_parts[-1], ', '.join(py_names)))
303
304 if schema_ast.externs:
305 f.write('\n')
306 f.write('if TYPE_CHECKING:\n')
307 for extern in schema_ast.externs:
308 n = extern.names
309 mod_parts = n[:-2]
310 f.write(' from %s import %s\n' % ('.'.join(mod_parts), n[-2]))
311
312 if opts.pretty_print_methods:
313 f.write("""
314from asdl import runtime # For runtime.NO_SPID
315from asdl.runtime import NewRecord, NewLeaf, TraversalState
316from _devbuild.gen.hnode_asdl import color_e, hnode, hnode_e, hnode_t, Field
317
318""")
319 if opts.abbrev_module:
320 f.write('from %s import *\n' % opts.abbrev_module)
321 f.write('\n')
322
323 v = gen_python.GenMyPyVisitor(
324 f,
325 abbrev_mod_entries,
326 pretty_print_methods=opts.pretty_print_methods,
327 py_init_n=opts.py_init_n)
328 v.VisitModule(schema_ast)
329
330 else:
331 raise RuntimeError('Invalid action %r' % action)
332
333
334if __name__ == '__main__':
335 try:
336 main(sys.argv)
337 except RuntimeError as e:
338 print('%s: FATAL: %s' % (ARG_0, e), file=sys.stderr)
339 sys.exit(1)