OILS / frontend / flag_gen.py View on Github | oilshell.org

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