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

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