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

297 lines, 171 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
19from typing import Any
20from typing import List
21
22ARG_0 = os.path.basename(sys.argv[0])
23
24
25def Options():
26 # type: () -> Any
27 """Returns an option parser instance."""
28
29 p = optparse.OptionParser()
30 p.add_option('--no-pretty-print-methods',
31 dest='pretty_print_methods',
32 action='store_false',
33 default=True,
34 help='Whether to generate pretty printing methods')
35
36 # Control Python constructors
37
38 # for hnode.asdl
39 p.add_option('--py-init-N',
40 dest='py_init_n',
41 action='store_true',
42 default=False,
43 help='Generate Python __init__ that requires every field')
44
45 # The default, which matches C++
46 p.add_option(
47 '--init-zero-N',
48 dest='init_zero_n',
49 action='store_true',
50 default=True,
51 help='Generate 0 arg and N arg constructors, in Python and C++')
52
53 p.add_option('--abbrev-module',
54 dest='abbrev_module',
55 default=None,
56 help='Import this module to find abbreviations')
57
58 return p
59
60
61def main(argv):
62 # type: (List[str]) -> None
63 o = Options()
64 opts, argv = o.parse_args(argv)
65
66 try:
67 action = argv[1]
68 except IndexError:
69 raise RuntimeError('Action required')
70
71 try:
72 schema_path = argv[2]
73 except IndexError:
74 raise RuntimeError('Schema path required')
75
76 schema_filename = os.path.basename(schema_path)
77 if opts.abbrev_module:
78 # Weird Python rule for importing: fromlist needs to be non-empty.
79 abbrev_mod = __import__(opts.abbrev_module, fromlist=['.'])
80 else:
81 abbrev_mod = None
82
83 abbrev_mod_entries = dir(abbrev_mod) if abbrev_mod else []
84 # e.g. syntax_abbrev
85 abbrev_ns = opts.abbrev_module.split('.')[-1] if abbrev_mod else None
86
87 if action == 'metrics': # Sum type metrics
88 with open(schema_path) as f:
89 schema_ast, _ = front_end.LoadSchema(f)
90
91 v = metrics.MetricsVisitor(sys.stdout)
92 v.VisitModule(schema_ast)
93
94 elif action == 'closure': # count all types that command_t references
95 type_name = argv[3]
96 with open(schema_path) as f:
97 schema_ast, type_lookup = front_end.LoadSchema(f)
98
99 seen = {} # type: Dict[str, bool]
100 c = metrics.ClosureWalk(type_lookup, seen)
101 c.DoModule(schema_ast, type_name)
102 for name in sorted(seen):
103 print(name)
104 log('SHARED (%d): %s', len(c.shared), ' '.join(sorted(c.shared)))
105
106 elif action == 'c': # Generate C code for the lexer
107 with open(schema_path) as f:
108 schema_ast, _ = front_end.LoadSchema(f)
109
110 v0 = gen_cpp.CEnumVisitor(sys.stdout)
111 v0.VisitModule(schema_ast)
112
113 elif action == 'cpp': # Generate C++ code for ASDL schemas
114 out_prefix = argv[3]
115
116 with open(schema_path) as f:
117 schema_ast, _ = front_end.LoadSchema(f)
118
119 # asdl/typed_arith.asdl -> typed_arith_asdl
120 ns = os.path.basename(schema_path).replace('.', '_')
121
122 with open(out_prefix + '.h', 'w') as f:
123 guard = ns.upper()
124 f.write("""\
125// %s.h is generated by %s
126
127#ifndef %s
128#define %s
129
130#include <cstdint>
131#include "mycpp/runtime.h"
132
133""" % (out_prefix, ARG_0, guard, guard))
134
135 if opts.pretty_print_methods:
136 f.write('#include "asdl/cpp_runtime.h"\n')
137
138 for use in schema_ast.uses:
139 # HACK to work around limitation: we can't "use" simple enums
140 if use.module_parts == ['frontend', 'id_kind']:
141 f.write('namespace %s_asdl { typedef uint16_t Id_t; }\n' %
142 use.module_parts[-1])
143 continue
144
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 for n in use.type_names:
152 py_name, _ = ast.TypeNameHeuristic(n)
153 cpp_names.append('class %s;' % py_name)
154
155 f.write('namespace %s_asdl { %s }\n' %
156 (use.module_parts[-1], ' '.join(cpp_names)))
157 f.write('\n')
158
159 for extern in schema_ast.externs:
160 names = extern.names
161 type_name = names[-1]
162 cpp_namespace = names[-2]
163
164 # TODO: This 'extern' feature isn't enough for Oils
165 # I think we would have to export header to
166 # _gen/bin/oils_for_unix.mycpp.cc or something
167 # Does that create circular dependencies?
168 #
169 # Or maybe of 'extern' we can have 'include' or something?
170 # Maybe we need `.pyi` files in MyPy?
171
172 f.write("""\
173namespace %s {
174class %s {
175 public:
176 hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen);
177};
178}
179""" % (cpp_namespace, type_name))
180
181 f.write("""\
182namespace %s {
183
184// use struct instead of namespace so 'using' works consistently
185#define ASDL_NAMES struct
186
187""" % ns)
188
189 v1 = gen_cpp.ForwardDeclareVisitor(f)
190 v1.VisitModule(schema_ast)
191
192 debug_info = {} # type: Dict[str, int]
193 v2 = gen_cpp.ClassDefVisitor(
194 f,
195 pretty_print_methods=opts.pretty_print_methods,
196 debug_info=debug_info)
197 v2.VisitModule(schema_ast)
198
199 f.write("""
200} // namespace %s
201
202#endif // %s
203""" % (ns, guard))
204
205 try:
206 debug_info_path = argv[4]
207 except IndexError:
208 pass
209 else:
210 with open(debug_info_path, 'w') as f:
211 from pprint import pformat
212 f.write('''\
213cpp_namespace = %r
214tags_to_types = \\
215%s
216''' % (ns, pformat(debug_info)))
217
218 if not opts.pretty_print_methods:
219 # No .cc file at all
220 return
221
222 with open(out_prefix + '.cc', 'w') as f:
223 f.write("""\
224// %s.cc is generated by %s
225
226#include "%s.h"
227#include <assert.h>
228""" % (out_prefix, ARG_0, out_prefix))
229
230 if abbrev_mod_entries:
231 # This is somewhat hacky, works for frontend/syntax_abbrev.py and
232 # prebuilt/frontend/syntax_abbrev.mycpp.h
233 part0, part1 = opts.abbrev_module.split('.')
234 f.write("""\
235#include "prebuilt/%s/%s.mycpp.h"
236""" % (part0, part1))
237
238 f.write("""\
239#include "prebuilt/asdl/runtime.mycpp.h" // generated code uses wrappers here
240""")
241
242 # To call pretty-printing methods
243 for use in schema_ast.uses:
244 f.write('#include "_gen/%s.asdl.h" // "use" in ASDL\n' %
245 '/'.join(use.module_parts))
246 # HACK
247 if use.module_parts == ['frontend', 'id_kind']:
248 f.write('using id_kind_asdl::Id_str;\n')
249
250 f.write("""\
251
252// Generated code uses these types
253using hnode_asdl::hnode;
254using hnode_asdl::Field;
255using hnode_asdl::color_e;
256
257""")
258
259 f.write("""
260namespace %s {
261
262""" % ns)
263
264 v3 = gen_cpp.MethodDefVisitor(
265 f,
266 abbrev_ns=abbrev_ns,
267 abbrev_mod_entries=abbrev_mod_entries)
268 v3.VisitModule(schema_ast)
269
270 f.write("""
271} // namespace %s
272""" % ns)
273
274 elif action == 'mypy': # Generated typed MyPy code
275 with open(schema_path) as f:
276 schema_ast, _ = front_end.LoadSchema(f)
277
278 f = sys.stdout # type: ignore
279
280 v4 = gen_python.GenMyPyVisitor(
281 f,
282 opts.abbrev_module,
283 abbrev_mod_entries,
284 pretty_print_methods=opts.pretty_print_methods,
285 py_init_n=opts.py_init_n)
286 v4.VisitModule(schema_ast)
287
288 else:
289 raise RuntimeError('Invalid action %r' % action)
290
291
292if __name__ == '__main__':
293 try:
294 main(sys.argv)
295 except RuntimeError as e:
296 print('%s: FATAL: %s' % (ARG_0, e), file=sys.stderr)
297 sys.exit(1)