OILS / frontend / flag_gen.py View on Github | oils.pub

543 lines, 338 significant
1#!/usr/bin/env python2
2""" flag_gen.py - generate Python and C++ from flag specs """
3from __future__ import print_function
4
5import itertools
6import sys
7
8from _devbuild.gen.runtime_asdl import flag_type_e
9from _devbuild.gen.value_asdl import value_e
10from frontend import args
11from frontend import flag_def # side effect: flags are defined!
12from frontend import flag_spec
13from mycpp import mops
14from mycpp.mylib import log, switch
15# This causes a circular build dependency! That is annoying.
16# builtin_comp -> core/completion -> pylib/{os_path,path_stat,...} -> posix_
17#from osh import builtin_comp
18
19_ = flag_def
20
21
22def CString(s):
23 # HACKS for now
24
25 assert '"' not in s, s
26 assert '\\' not in s, s
27
28 # For the default of write --end
29 if s == '\n':
30 return '"\\n"'
31
32 return '"%s"' % s
33
34
35def _CleanFieldName(name):
36 # Avoid C++ keyword for invoke --extern
37 if name == 'extern':
38 return 'extern_'
39 if name == 'private':
40 return 'private_'
41
42 return name.replace('-', '_')
43
44
45def _WriteStrArray(f, var_name, a):
46 c_strs = ', '.join(CString(s) for s in sorted(a))
47 f.write('const char* %s[] = {%s, nullptr};\n' % (var_name, c_strs))
48 f.write('\n')
49
50
51def _WriteActionParams(f, actions, counter):
52 param_names = []
53 for key in sorted(actions):
54 action = actions[key]
55 to_write = None
56
57 if isinstance(action, args.SetToString):
58 if action.valid:
59 to_write = action.valid
60
61 elif isinstance(action, args.SetNamedOption):
62 if action.names:
63 to_write = action.names
64
65 elif isinstance(action, args.SetNamedAction):
66 if action.names:
67 to_write = action.names
68
69 if to_write:
70 uniq = counter.next()
71 var_name = 'params_%d' % uniq
72
73 _WriteStrArray(f, var_name, to_write)
74 else:
75 var_name = None
76
77 param_names.append(var_name)
78
79 return param_names
80
81
82def _WriteActions(f, var_name, actions, counter):
83 # TODO: 'osh' and 'set' duplicate shopt params!!! Maybe we want the entire
84 # action not to be duplicated?
85 param_names = _WriteActionParams(f, actions, counter)
86
87 f.write('Action_c %s[] = {\n' % var_name)
88 for i, key in enumerate(sorted(actions)):
89 action = actions[key]
90 #log('%s %s', key, action)
91
92 name = None
93 if isinstance(action, args.SetToString):
94 action_type = 'SetToString'
95 name = action.name
96
97 elif isinstance(action, args.SetToInt):
98 action_type = 'SetToInt'
99 name = action.name
100
101 elif isinstance(action, args.SetToFloat):
102 action_type = 'SetToFloat'
103 name = action.name
104
105 elif isinstance(action, args.SetToTrue):
106 action_type = 'SetToTrue'
107 name = action.name
108
109 elif isinstance(action, args.SetAttachedBool):
110 action_type = 'SetAttachedBool'
111 name = action.name
112
113 elif isinstance(action, args.SetOption):
114 action_type = 'SetOption'
115 name = action.name
116
117 elif isinstance(action, args.SetNamedOption):
118 if action.shopt:
119 action_type = 'SetNamedOption_shopt'
120 else:
121 action_type = 'SetNamedOption'
122
123 elif isinstance(action, args.SetAction):
124 action_type = 'SetAction'
125 name = action.name
126
127 elif isinstance(action, args.SetNamedAction):
128 action_type = 'SetNamedAction'
129
130 elif isinstance(action, args.AppendEvalFlag):
131 action_type = 'AppendEvalFlag'
132 name = action.name
133
134 else:
135 raise AssertionError(action)
136
137 name_str = ('"%s"' % name) if name else 'nullptr'
138 params_str = param_names[i] or 'nullptr'
139 f.write(' {"%s", ActionType_c::%s, %s, %s},\n' %
140 (key, action_type, name_str, params_str))
141 #cc_f.write('SetToArg_c %s[] = {\n' % arity1_name)
142 f.write('''\
143 {},
144};
145
146''')
147
148
149def _WriteDefaults(cc_f, defaults_name, defaults):
150 cc_f.write('DefaultPair_c %s[] = {\n' % defaults_name)
151
152 for name in sorted(defaults):
153 val = defaults[name]
154 if val.tag() == value_e.Bool:
155 typ = 'Bool'
156 v = '{.b = %s}' % ('true' if val.b else 'false')
157 elif val.tag() == value_e.Int:
158 typ = 'Int'
159 v = '{.i = %s}' % mops.BigTruncate(val.i)
160 elif val.tag() == value_e.Float:
161 typ = 'Float'
162 # printing this to C++ is problematic
163 if val.f != -1.0:
164 raise AssertionError('Float default not supported %r' % val.f)
165 v = '{.f = -1.0}'
166 elif val.tag() == value_e.Undef:
167 typ = 'Str' # default for string
168 v = '{}'
169 elif val.tag() == value_e.Str:
170 # NOTE: 'osh' FlagSpecAndMore_ has default='nice' and default='abbrev-text'
171 typ = 'Str'
172 v = '{.s = %s}' % CString(val.s)
173
174 else:
175 raise AssertionError(val)
176
177 cc_f.write(' {%s, flag_type_e::%s, %s},\n' %
178 (CString(name), typ, v))
179
180 cc_f.write('''\
181 {},
182};
183
184''')
185
186
187def Cpp(specs, header_f, cc_f):
188 counter = itertools.count()
189
190 header_f.write("""\
191// arg_types.h is generated by frontend/flag_gen.py
192
193#ifndef ARG_TYPES_H
194#define ARG_TYPES_H
195
196#include "cpp/frontend_flag_spec.h" // for FlagSpec_c
197#include "mycpp/gc_mylib.h"
198
199using value_asdl::value;
200using value_asdl::value_e;
201
202namespace arg_types {
203""")
204 for spec_name in sorted(specs):
205 spec = specs[spec_name]
206
207 if not spec.fields:
208 continue # skip empty 'eval' spec
209
210 #
211 # Figure out how to initialize the class
212 #
213
214 init_vals = []
215 field_names = []
216 field_decls = []
217 bits = []
218 for field_name in sorted(spec.fields):
219 typ = spec.fields[field_name]
220 field_name = _CleanFieldName(field_name)
221 field_names.append(field_name)
222
223 with switch(typ) as case:
224 if case(flag_type_e.Bool):
225 init_vals.append(
226 'static_cast<value::Bool*>(attrs->at(StrFromC("%s")))->b'
227 % field_name)
228 field_decls.append('bool %s;' % field_name)
229
230 # Bug that test should find
231 #bits.append('maskbit(offsetof(%s, %s))' % (spec_name, field_name))
232
233 elif case(flag_type_e.Str):
234 # TODO: This code is ugly and inefficient! Generate something
235 # better. At least get rid of 'new' everywhere?
236 init_vals.append('''\
237attrs->at(StrFromC("%s"))->tag() == value_e::Undef
238 ? nullptr
239 : static_cast<value::Str*>(attrs->at(StrFromC("%s")))->s''' %
240 (field_name, field_name))
241
242 field_decls.append('BigStr* %s;' % field_name)
243
244 # BigStr* is a pointer type, so add a field here
245 bits.append('maskbit(offsetof(%s, %s))' %
246 (spec_name, field_name))
247
248 elif case(flag_type_e.Int):
249 init_vals.append('''\
250attrs->at(StrFromC("%s"))->tag() == value_e::Undef
251 ? -1
252 : static_cast<value::Int*>(attrs->at(StrFromC("%s")))->i''' %
253 (field_name, field_name))
254 field_decls.append('int %s;' % field_name)
255
256 elif case(flag_type_e.Float):
257 init_vals.append('''\
258attrs->at(StrFromC("%s"))->tag() == value_e::Undef
259 ? -1
260 : static_cast<value::Float*>(attrs->at(StrFromC("%s")))->f''' %
261 (field_name, field_name))
262 field_decls.append('float %s;' % field_name)
263
264 else:
265 raise AssertionError(typ)
266
267 #
268 # Now emit the class
269 #
270
271 if bits:
272 obj_tag = 'HeapTag::FixedSize'
273 mask_str = 'field_mask()'
274 else:
275 obj_tag = 'HeapTag::Opaque'
276 mask_str = 'kZeroMask'
277
278 header_f.write("""
279class %s {
280 public:
281 %s(Dict<BigStr*, value_asdl::value_t*>* attrs)""" % (spec_name, spec_name))
282
283 if field_names:
284 header_f.write('\n : ')
285 for i, field_name in enumerate(field_names):
286 if i != 0:
287 header_f.write(',\n ')
288 header_f.write('%s(%s)' % (field_name, init_vals[i]))
289 header_f.write(' {\n')
290 header_f.write(' }\n')
291 header_f.write('\n')
292
293 for decl in field_decls:
294 header_f.write(' %s\n' % decl)
295
296 header_f.write('\n')
297 header_f.write(' static constexpr ObjHeader obj_header() {\n')
298 header_f.write(' return ObjHeader::Class(%s, %s, sizeof(%s));\n' %
299 (obj_tag, mask_str, spec_name))
300 header_f.write(' }\n')
301
302 if bits:
303 header_f.write('\n')
304 header_f.write(' static constexpr uint32_t field_mask() {\n')
305 header_f.write(' return\n')
306 header_f.write(' ')
307 header_f.write('\n | '.join(bits))
308 header_f.write(';\n')
309 header_f.write(' }\n')
310 header_f.write('\n')
311
312 header_f.write("""\
313};
314""")
315
316 header_f.write("""
317extern FlagSpec_c kFlagSpecs[];
318extern FlagSpecAndMore_c kFlagSpecsAndMore[];
319
320} // namespace arg_types
321
322#endif // ARG_TYPES_H
323
324""")
325
326 cc_f.write("""\
327// arg_types.cc is generated by frontend/flag_gen.py
328
329#include "arg_types.h"
330using runtime_asdl::flag_type_e;
331
332namespace arg_types {
333
334""")
335
336 var_names = []
337 for i, spec_name in enumerate(sorted(flag_spec.FLAG_SPEC)):
338 spec = specs[spec_name]
339 arity0_name = None
340 arity1_name = None
341 actions_long_name = None
342 plus_name = None
343 defaults_name = None
344
345 if spec.arity0:
346 arity0_name = 'arity0_%d' % i
347 _WriteStrArray(cc_f, arity0_name, spec.arity0)
348
349 if spec.arity1:
350 arity1_name = 'arity1_%d' % i
351 _WriteActions(cc_f, arity1_name, spec.arity1, counter)
352
353 if spec.actions_long:
354 actions_long_name = 'actions_long_%d' % i
355 _WriteActions(cc_f, actions_long_name, spec.actions_long, counter)
356
357 if spec.plus_flags:
358 plus_name = 'plus_%d' % i
359 _WriteStrArray(cc_f, plus_name, spec.plus_flags)
360
361 if spec.defaults:
362 defaults_name = 'defaults_%d' % i
363 _WriteDefaults(cc_f, defaults_name, spec.defaults)
364
365 var_names.append((arity0_name, arity1_name, actions_long_name,
366 plus_name, defaults_name))
367
368 cc_f.write('FlagSpec_c kFlagSpecs[] = {\n')
369
370 # Now print a table
371 for i, spec_name in enumerate(sorted(flag_spec.FLAG_SPEC)):
372 spec = specs[spec_name]
373 names = var_names[i]
374 cc_f.write(' { "%s", %s, %s, %s, %s, %s },\n' % (
375 spec_name,
376 names[0] or 'nullptr',
377 names[1] or 'nullptr',
378 names[2] or 'nullptr',
379 names[3] or 'nullptr',
380 names[4] or 'nullptr',
381 ))
382
383 cc_f.write("""\
384 {},
385};
386
387""")
388
389 n = len(var_names)
390 var_names = []
391 for i, spec_name in enumerate(sorted(flag_spec.FLAG_SPEC_AND_MORE)):
392 spec = specs[spec_name]
393 actions_short_name = None
394 actions_long_name = None
395 plus_name = None
396 defaults_name = None
397
398 if spec.actions_short:
399 actions_short_name = 'short_%d' % (n + i)
400 _WriteActions(cc_f, actions_short_name, spec.actions_short,
401 counter)
402
403 #if spec.actions_long:
404 if spec.actions_long:
405 actions_long_name = 'long_%d' % (n + i)
406 _WriteActions(cc_f, actions_long_name, spec.actions_long, counter)
407
408 if spec.plus_flags:
409 plus_name = 'plus_%d' % i
410 _WriteStrArray(cc_f, plus_name, spec.plus_flags)
411
412 if spec.defaults:
413 defaults_name = 'defaults_%d' % (n + i)
414 _WriteDefaults(cc_f, defaults_name, spec.defaults)
415
416 var_names.append(
417 (actions_short_name, actions_long_name, plus_name, defaults_name))
418
419 cc_f.write('FlagSpecAndMore_c kFlagSpecsAndMore[] = {\n')
420 for i, spec_name in enumerate(sorted(flag_spec.FLAG_SPEC_AND_MORE)):
421 names = var_names[i]
422 cc_f.write(' { "%s", %s, %s, %s, %s },\n' % (
423 spec_name,
424 names[0] or 'nullptr',
425 names[1] or 'nullptr',
426 names[2] or 'nullptr',
427 names[3] or 'nullptr',
428 ))
429
430 cc_f.write("""\
431 {},
432};
433""")
434
435 cc_f.write("""\
436} // namespace arg_types
437""")
438
439
440def main(argv):
441 try:
442 action = argv[1]
443 except IndexError:
444 raise RuntimeError('Action required')
445
446 if 0:
447 for spec_name in sorted(flag_spec.FLAG_SPEC_AND_MORE):
448 log('%s', spec_name)
449
450 # Both kinds of specs have 'fields' attributes
451 specs = {}
452 specs.update(flag_spec.FLAG_SPEC)
453 specs.update(flag_spec.FLAG_SPEC_AND_MORE)
454 #log('SPECS %s', specs)
455
456 for spec_name in sorted(specs):
457 spec = specs[spec_name]
458 #spec.spec.PrettyPrint(f=sys.stderr)
459 #log('spec.arity1 %s', spec.spec.arity1)
460 #log('%s', spec_name)
461
462 #print(dir(spec))
463 #print(spec.arity0)
464 #print(spec.arity1)
465 #print(spec.options)
466 # Every flag has a default
467 #log('%s', spec.fields)
468
469 if action == 'cpp':
470 prefix = argv[2]
471
472 with open(prefix + '.h', 'w') as header_f:
473 with open(prefix + '.cc', 'w') as cc_f:
474 Cpp(specs, header_f, cc_f)
475
476 elif action == 'mypy':
477 print("""
478from _devbuild.gen.value_asdl import value, value_e, value_t
479from frontend.args import _Attributes
480from mycpp import mops
481from typing import cast, Dict, Optional
482""")
483 for spec_name in sorted(specs):
484 spec = specs[spec_name]
485
486 #log('%s spec.fields %s', spec_name, spec.fields)
487 if not spec.fields:
488 continue # skip empty specs, e.g. eval
489
490 print("""
491class %s(object):
492 def __init__(self, attrs):
493 # type: (Dict[str, value_t]) -> None
494""" % spec_name)
495
496 i = 0
497 for field_name in sorted(spec.fields):
498 typ = spec.fields[field_name]
499 field_name = _CleanFieldName(field_name)
500
501 with switch(typ) as case:
502 if case(flag_type_e.Bool):
503 print(
504 ' self.%s = cast(value.Bool, attrs[%r]).b # type: bool'
505 % (field_name, field_name))
506
507 elif case(flag_type_e.Str):
508 tmp = 'val%d' % i
509 print(' %s = attrs[%r]' % (tmp, field_name))
510 print(
511 ' self.%s = None if %s.tag() == value_e.Undef else cast(value.Str, %s).s # type: Optional[str]'
512 % (field_name, tmp, tmp))
513
514 elif case(flag_type_e.Int):
515 tmp = 'val%d' % i
516 print(' %s = attrs[%r]' % (tmp, field_name))
517 print(
518 ' self.%s = mops.BigInt(-1) if %s.tag() == value_e.Undef else cast(value.Int, %s).i # type: mops.BigInt'
519 % (field_name, tmp, tmp))
520
521 elif case(flag_type_e.Float):
522 tmp = 'val%d' % i
523 print(' %s = attrs[%r]' % (tmp, field_name))
524 print(
525 ' self.%s = -1.0 if %s.tag() == value_e.Undef else cast(value.Float, %s).f # type: float'
526 % (field_name, tmp, tmp))
527 else:
528 raise AssertionError(typ)
529
530 i += 1
531
532 print()
533
534 else:
535 raise RuntimeError('Invalid action %r' % action)
536
537
538if __name__ == '__main__':
539 try:
540 main(sys.argv)
541 except RuntimeError as e:
542 print('FATAL: %s' % e, file=sys.stderr)
543 sys.exit(1)