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

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