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

1100 lines, 687 significant
1"""
2gen_cpp.py - Generate C++ classes from an ASDL schema.
3"""
4from __future__ import print_function
5
6import sys
7
8from collections import defaultdict
9
10from asdl import ast
11from asdl import visitor
12from asdl.util import log
13
14from typing import List, Dict, Tuple, IO, Union, Optional, Any, cast, TYPE_CHECKING
15
16if TYPE_CHECKING:
17 from asdl.asdl_main import Abbrev
18
19_ = log
20
21
22# Used by core/asdl_gen.py to generate _devbuild/gen/osh-types.h, with
23# lex_mode__*
24class CEnumVisitor(visitor.AsdlVisitor):
25
26 def VisitSimpleSum(self, sum, name, depth):
27 # type: (ast.SimpleSum, str, int) -> None
28
29 # Just use #define, since enums aren't namespaced.
30 for i, variant in enumerate(sum.types):
31 self.Emit('#define %s__%s %d' % (name, variant.name, i + 1), depth)
32 self.Emit("", depth)
33
34
35def WriteDebugInfo(debug_info, ns, debug_info_path):
36 # type: (Dict[str, Any], str, str) -> None
37 """
38 Emit info for the devtools/oils.gdb plugin. Not used very often.
39 """
40 with open(debug_info_path, 'w') as f:
41 from pprint import pformat
42 f.write('''\
43cpp_namespace = %r
44tags_to_types = \\
45%s
46''' % (ns, pformat(debug_info)))
47
48
49def _WriteUses(f, schema_ast):
50 # type: (IO[bytes], ast.Module) -> None
51 for use in schema_ast.uses:
52 # HACK to work around limitation: we can't "use" simple enums
53 if use.module_parts == ['frontend', 'id_kind']:
54 f.write('namespace %s_asdl { typedef uint16_t Id_t; }\n' %
55 use.module_parts[-1])
56 continue
57
58 # Forward declarations in the header, like
59 # namespace syntax_asdl { class command_t; }
60 # must come BEFORE namespace, so it can't be in the visitor.
61
62 # assume sum type for now!
63 cpp_names = []
64 for n in use.type_names:
65 py_name, _ = ast.TypeNameHeuristic(n)
66 cpp_names.append('class %s;' % py_name)
67
68 f.write('namespace %s_asdl { %s }\n' %
69 (use.module_parts[-1], ' '.join(cpp_names)))
70 f.write('\n')
71
72 for extern in schema_ast.externs:
73 names = extern.names
74 type_name = names[-1]
75 cpp_namespace = names[-2]
76
77 # TODO: This 'extern' feature isn't enough for Oils
78 # I think we would have to export header to
79 # _gen/bin/oils_for_unix.mycpp.cc or something
80 # Does that create circular dependencies?
81 #
82 # Or maybe of 'extern' we can have 'include' or something?
83 # Maybe we need `.pyi` files in MyPy?
84
85 f.write("""\
86namespace %s {
87class %s {
88public:
89hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen);
90};
91}
92""" % (cpp_namespace, type_name))
93
94
95def WriteHeaderFile(schema_ast, ARG_0, ns, pretty_print_methods, out_prefix):
96 # type: (ast.Module, str, str, bool, str) -> Dict[str, int]
97 guard = ns.upper()
98 with open(out_prefix + '.h', 'w') as f:
99 f.write("""\
100// %s.h is generated by %s
101
102#ifndef %s
103#define %s
104
105#include <cstdint>
106#include "mycpp/runtime.h"
107
108""" % (out_prefix, ARG_0, guard, guard))
109
110 if pretty_print_methods:
111 f.write('#include "asdl/cpp_runtime.h"\n')
112
113 # uses, externs
114 _WriteUses(f, schema_ast)
115
116 f.write("""\
117namespace %s {
118
119// use struct instead of namespace so 'using' works consistently
120#define ASDL_NAMES struct
121
122""" % ns)
123
124 # Must be in the namespace
125 v1 = ForwardDeclareVisitor(f)
126 v1.VisitModule(schema_ast)
127
128 # Fill in debug_info
129 debug_info = {} # type: Dict[str, int]
130 v2 = ClassDefVisitor(f,
131 pretty_print_methods=pretty_print_methods,
132 debug_info=debug_info)
133 v2.VisitModule(schema_ast)
134
135 f.write("""
136} // namespace %s
137
138#endif // %s
139""" % (ns, guard))
140
141 return debug_info
142
143
144def WriteCppFile(schema_ast, ARG_0, ns, abbrev, out_prefix):
145 # type: (ast.Module, str, str, Abbrev, str) -> None
146
147 with open(out_prefix + '.cc', 'w') as f:
148 f.write("""\
149// %s.cc is generated by %s
150
151#include "%s.h"
152#include <assert.h>
153""" % (out_prefix, ARG_0, out_prefix))
154
155 if abbrev.mod_entries:
156 # This is somewhat hacky, works for frontend/syntax_abbrev.py and
157 # prebuilt/frontend/syntax_abbrev.mycpp.h
158 part0, part1 = abbrev.module.split('.')
159 f.write('#include "prebuilt/%s/%s.mycpp.h"\n' % (part0, part1))
160
161 # e.g. runtime::NewRecord() and TraversalState are from asdl/runtime.py
162 # TODO: it would be nice to get rid of this
163 f.write('#include "prebuilt/asdl/runtime.mycpp.h" // from mycpp\n')
164
165 # To call pretty-printing methods
166 for use in schema_ast.uses:
167 f.write('#include "_gen/%s.asdl.h" // "use" in ASDL\n' %
168 '/'.join(use.module_parts))
169 # HACK
170 if use.module_parts == ['frontend', 'id_kind']:
171 f.write('using id_kind_asdl::Id_str;\n')
172
173 f.write("""\
174
175// Generated code uses these types
176using hnode_asdl::hnode;
177using hnode_asdl::Field;
178using hnode_asdl::color_e;
179
180namespace %s {
181
182""" % ns)
183
184 v3 = MethodDefVisitor(f,
185 abbrev_ns=abbrev.ns,
186 abbrev_mod_entries=abbrev.mod_entries)
187 v3.VisitModule(schema_ast)
188
189 f.write("""
190} // namespace %s
191""" % ns)
192
193
194class ForwardDeclareVisitor(visitor.AsdlVisitor):
195 """Print forward declarations.
196
197 ASDL allows forward references of types, but C++ doesn't.
198 """
199
200 def VisitCompoundSum(self, sum, name, depth):
201 # type: (ast.Sum, str, int) -> None
202 self.Emit("class %(name)s_t;" % locals(), depth)
203
204 def VisitProduct(self, product, name, depth):
205 # type: (ast.Product, str, int) -> None
206 self.Emit("class %(name)s;" % locals(), depth)
207
208 def EmitFooter(self):
209 # type: () -> None
210 self.Emit("", 0) # blank line
211
212
213_PRIMITIVES = {
214 'string': 'BigStr*', # declared in containers.h
215 'int': 'int',
216 'uint16': 'uint16_t',
217 'BigInt': 'mops::BigInt',
218 'float': 'double',
219 'bool': 'bool',
220 'any': 'void*',
221}
222
223
224def _GetCppType(typ):
225 # type: (ast.type_expr_t) -> str
226 if isinstance(typ, ast.ParameterizedType):
227 type_name = typ.type_name
228
229 if type_name == 'Dict':
230 k_type = _GetCppType(typ.children[0])
231 v_type = _GetCppType(typ.children[1])
232 return 'Dict<%s, %s>*' % (k_type, v_type)
233
234 if type_name == 'List':
235 c_type = _GetCppType(typ.children[0])
236 return 'List<%s>*' % (c_type)
237
238 if type_name == 'Optional':
239 c_type = _GetCppType(typ.children[0])
240 return c_type
241
242 raise AssertionError()
243
244 elif isinstance(typ, ast.NamedType):
245
246 if typ.resolved:
247 if isinstance(typ.resolved, ast.SimpleSum):
248 return '%s_t' % typ.name
249 if isinstance(typ.resolved, ast.Sum):
250 return '%s_t*' % typ.name
251 if isinstance(typ.resolved, ast.Product):
252 return '%s*' % typ.name
253 if isinstance(typ.resolved, ast.Use):
254 py_name, is_pointer = ast.TypeNameHeuristic(typ.name)
255 star = '*' if is_pointer else ''
256 return '%s_asdl::%s%s' % (typ.resolved.module_parts[-1],
257 py_name, star)
258 if isinstance(typ.resolved, ast.Extern):
259 r = typ.resolved
260 type_name = r.names[-1]
261 cpp_namespace = r.names[-2]
262 return '%s::%s*' % (cpp_namespace, type_name)
263 raise AssertionError()
264
265 return _PRIMITIVES[typ.name]
266
267 else:
268 raise AssertionError()
269
270
271def _IsManagedType(typ):
272 # type: (ast.type_expr_t) -> bool
273 # This is a little cheesy, but works
274 return _GetCppType(typ).endswith('*')
275
276
277def _DefaultValue(typ, conditional=True):
278 # type: (ast.type_expr_t, bool) -> str
279 """Values that the ::CreateNull() constructor passes."""
280
281 if isinstance(typ, ast.ParameterizedType):
282 type_name = typ.type_name
283
284 if type_name == 'Dict': # TODO: can respect alloc_dicts=True
285 return 'nullptr'
286
287 elif type_name == 'List':
288 c_type = _GetCppType(typ.children[0])
289
290 d = 'Alloc<List<%s>>()' % (c_type)
291 if conditional:
292 return 'alloc_lists ? %s : nullptr' % d
293 else:
294 return d
295
296 elif type_name == 'Optional':
297 return 'nullptr'
298
299 else:
300 raise AssertionError(type_name)
301
302 elif isinstance(typ, ast.NamedType):
303 type_name = typ.name
304
305 if type_name in ('int', 'uint16', 'BigInt'):
306 default = '-1'
307 elif type_name == 'id': # hard-coded HACK
308 default = '-1'
309 elif type_name == 'bool':
310 default = 'false'
311 elif type_name == 'float':
312 default = '0.0' # or should it be NaN?
313 elif type_name == 'string':
314 default = 'kEmptyString'
315
316 elif typ.resolved and isinstance(typ.resolved, ast.SimpleSum):
317 sum_type = typ.resolved
318 # Just make it the first variant. We could define "Undef" for
319 # each enum, but it doesn't seem worth it.
320 default = '%s_e::%s' % (type_name, sum_type.types[0].name)
321
322 else:
323 default = 'nullptr' # Sum or Product
324 return default
325
326 else:
327 raise AssertionError()
328
329
330def _HNodeExpr(typ, var_name):
331 # type: (ast.type_expr_t, str) -> Tuple[str, bool]
332 none_guard = False
333
334 if ast.IsOptional(typ):
335 narrow = cast(ast.ParameterizedType, typ)
336 typ = narrow.children[0] # descend one level
337
338 if isinstance(typ, ast.ParameterizedType):
339 code_str = '%s->PrettyTree()' % var_name
340 none_guard = True
341
342 elif isinstance(typ, ast.NamedType):
343
344 type_name = typ.name
345
346 if type_name in ('bool', 'int', 'uint16', 'BigInt', 'float', 'string'):
347 code_str = "ToPretty(%s)" % var_name
348
349 elif type_name == 'any':
350 # This is used for _BuiltinFunc, _BuiltinProc. There is not that much to customize here.
351 code_str = 'Alloc<hnode::Leaf>(StrFromC("<extern>"), color_e::External)' # % var_name
352
353 elif type_name == 'id': # was meta.UserType
354 code_str = 'Alloc<hnode::Leaf>(Id_str(%s, false), color_e::UserType)' % var_name
355
356 elif typ.resolved and isinstance(typ.resolved, ast.SimpleSum):
357 # ASDL could generate ToPretty<T> ?
358 code_str = 'Alloc<hnode::Leaf>(%s_str(%s), color_e::TypeName)' % (
359 type_name, var_name)
360
361 else:
362 code_str = '%s->PrettyTree(do_abbrev, seen)' % var_name
363 none_guard = True
364
365 else:
366 raise AssertionError()
367
368 return code_str, none_guard
369
370
371# Variant tags are NOT unique:
372# for each sum type, they go from 0, 1, 2 ... N
373# There can be up to 64
374# Product types tags ARE unique, because any of them can be SHARED between sum types
375# They start at 64
376MAX_VARIANTS_PER_SUM = 64
377
378
379class ClassDefVisitor(visitor.AsdlVisitor):
380 """Generate C++ declarations and type-safe enums."""
381
382 def __init__(
383 self,
384 f, # type: IO[bytes]
385 pretty_print_methods=True, # type: bool
386 debug_info=None, # type: Optional[Dict[str, Any]]
387 ):
388 # type: (...) -> None
389 """
390 Args:
391 f: file to write to
392 debug_info: dictionary fill in with info for GDB
393 """
394 visitor.AsdlVisitor.__init__(self, f)
395 self.pretty_print_methods = pretty_print_methods
396 self.debug_info = debug_info if debug_info is not None else {}
397
398 self._shared_type_tags = {} # type: Dict[str, int]
399 self._product_counter = MAX_VARIANTS_PER_SUM
400
401 self._products = [] # type: List[Any]
402 self._base_classes = defaultdict(list) # type: Dict[str, List[str]]
403
404 self._subtypes = [] # type: List[Any]
405
406 def _EmitEnum(self, sum, sum_name, depth, strong=False, is_simple=False):
407 # type: (ast.Sum, str, int, bool, bool) -> Dict[int, str]
408 enum = []
409 int_to_type = {}
410 add_suffix = not ('no_namespace_suffix' in sum.generate)
411 for i, variant in enumerate(sum.types):
412 if variant.shared_type: # Copied from gen_python.py
413 tag_num = self._shared_type_tags[variant.shared_type]
414 # e.g. DoubleQuoted may have base types expr_t, word_part_t
415 base_class = sum_name + '_t'
416 bases = self._base_classes[variant.shared_type]
417 if base_class in bases:
418 raise RuntimeError(
419 "Two tags in sum %r refer to product type %r" %
420 (sum_name, variant.shared_type))
421 else:
422 bases.append(base_class)
423 type_str = variant.shared_type
424 else:
425 tag_num = i + 1
426 type_str = '%s__%s' % (sum_name, variant.name)
427 int_to_type[tag_num] = type_str
428 enum.append((variant.name, tag_num)) # zero is reserved
429
430 if strong:
431 enum_name = '%s_e' % sum_name if add_suffix else sum_name
432
433 # Simple sum types can be STRONG since there's no possibility of multiple
434 # inheritance!
435
436 self.Emit('enum class %s {' % enum_name, depth)
437 for name, tag_num in enum:
438 self.Emit('%s = %d,' % (name, tag_num), depth + 1)
439 self.Emit('};', depth)
440
441 # type alias to match Python code
442 self.Emit('typedef %s %s_t;' % (enum_name, sum_name), depth)
443 self.Emit('', depth)
444
445 if self.pretty_print_methods:
446 sum_name = ast.NameHack(sum_name)
447 self.Emit(
448 'BigStr* %s_str(%s tag, bool dot = true);' %
449 (sum_name, enum_name), depth)
450 self.Emit('', depth)
451
452 else:
453 if is_simple:
454 enum_name = '%s_i' % sum_name if add_suffix else sum_name
455 else:
456 enum_name = '%s_e' % sum_name if add_suffix else sum_name
457
458 # Awkward struct/enum C++ idiom because:
459
460 # 1. namespace can't be "imported" with 'using'
461 # 2. plain enum pollutes outer namespace
462 # 3. C++ 11 'enum class' does not allow conversion to int
463 # 4. namespace and 'static const int' or 'static constexpr int' gives
464 # weird link errors
465 # https://quuxplusone.github.io/blog/2020/09/19/value-or-pitfall/
466
467 self.Emit('ASDL_NAMES %s {' % enum_name, depth)
468 self.Emit(' enum no_name {', depth)
469 for name, tag_num in enum:
470 self.Emit('%s = %d,' % (name, tag_num), depth + 1)
471
472 if is_simple:
473 # Help in sizing array. Note that we're 1-based.
474 self.Emit('ARRAY_SIZE = %d,' % (len(enum) + 1), depth + 1)
475
476 self.Emit(' };', depth)
477 self.Emit('};', depth)
478
479 self.Emit('', depth)
480
481 if self.pretty_print_methods:
482 sum_name = ast.NameHack(sum_name)
483 self.Emit(
484 'BigStr* %s_str(int tag, bool dot = true);' % sum_name,
485 depth)
486 self.Emit('', depth)
487
488 return int_to_type
489
490 def VisitSimpleSum(self, sum, name, depth):
491 # type: (ast.SimpleSum, str, int) -> None
492 # Note: there can be more than 128 variants in a simple sum, because it's an
493 # integer and doesn't have an object header.
494
495 if 'integers' in sum.generate:
496 self._EmitEnum(sum, name, depth, strong=False, is_simple=True)
497 self.Emit('typedef int %s_t;' % name)
498 self.Emit('')
499 elif 'uint16' in sum.generate:
500 self._EmitEnum(sum, name, depth, strong=False, is_simple=True)
501 self.Emit('typedef uint16_t %s_t;' % name)
502 self.Emit('')
503 else:
504 self._EmitEnum(sum, name, depth, strong=True)
505
506 def VisitCompoundSum(self, sum, sum_name, depth):
507 # type: (ast.Sum, str, int) -> None
508 #log('%d variants in %s', len(sum.types), sum_name)
509
510 # Must fit in 7 bit Obj::type_tag
511 assert len(sum.types) < MAX_VARIANTS_PER_SUM, \
512 'sum type %r has too many variants' % sum_name
513
514 # This is a sign that Python needs string interpolation!!!
515 def Emit(s, depth=depth):
516 # type: (str, int) -> None
517 self.Emit(s % sys._getframe(1).f_locals, depth)
518
519 int_to_type = self._EmitEnum(sum, sum_name, depth)
520
521 # Only add debug info for compound sums.
522 self.debug_info['%s_t' % sum_name] = int_to_type
523
524 # This is the base class.
525 Emit('class %(sum_name)s_t {')
526
527 # to implement Walkable, we'd need all types to fit in in the GC header
528 #Emit('class %(sum_name)s_t : public Walkable {')
529
530 # Can't be constructed directly. Note: this shows up in uftrace in debug
531 # mode, e.g. when we instantiate Token. Do we need it?
532 Emit(' protected:')
533 Emit(' %s_t() {' % sum_name)
534 Emit(' }')
535 Emit(' public:')
536 Emit(' int tag() const {')
537 # There's no inheritance relationship, so we have to reinterpret_cast.
538 Emit(' return ObjHeader::FromObject(this)->type_tag;')
539 Emit(' }')
540
541 if self.pretty_print_methods:
542 self.Emit(
543 ' hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen = nullptr);'
544 )
545
546 Emit('')
547 Emit(' DISALLOW_COPY_AND_ASSIGN(%(sum_name)s_t)')
548 Emit('};')
549 Emit('')
550
551 for variant in sum.types:
552 if variant.shared_type:
553 continue # Don't generate a class.
554 super_name = '%s_t' % sum_name
555 tag = 'static_cast<uint16_t>(%s_e::%s)' % (sum_name, variant.name)
556 class_name = '%s__%s' % (sum_name, variant.name)
557 self._GenClass(variant.fields, class_name, [super_name], depth,
558 tag)
559
560 # Generate 'extern' declarations for zero arg singleton globals
561 for variant in sum.types:
562 if not variant.shared_type and len(variant.fields) == 0:
563 variant_name = variant.name
564 Emit(
565 'extern GcGlobal<%(sum_name)s__%(variant_name)s> g%(sum_name)s__%(variant_name)s;'
566 )
567
568 # Allow expr::Const in addition to expr.Const.
569 Emit('ASDL_NAMES %(sum_name)s {')
570 for variant in sum.types:
571 if variant.shared_type:
572 continue
573
574 # TODO: This produces a lint error, but IS USED via % reflection
575 variant_name = variant.name
576
577 if len(variant.fields) == 0:
578 Emit(
579 ' static %(sum_name)s__%(variant_name)s* %(variant_name)s;'
580 )
581 else:
582 Emit(
583 ' typedef %(sum_name)s__%(variant_name)s %(variant_name)s;'
584 )
585 Emit('};')
586 Emit('')
587
588 def _GenClassBegin(self, class_name, base_classes, depth):
589 # type: (str, List[str], int) -> None
590 if base_classes:
591 bases = ', '.join('public %s' % b for b in base_classes)
592 self.Emit("class %s : %s {" % (class_name, bases), depth)
593 else:
594 self.Emit("class %s {" % class_name, depth)
595 #self.Emit("class %s : public Walkable {" % class_name, depth)
596
597 self.Emit(" public:", depth)
598
599 def _GenClassEnd(self, class_name, depth):
600 # type: (str, int) -> None
601 self.Emit(' DISALLOW_COPY_AND_ASSIGN(%s)' % class_name)
602 self.Emit('};', depth)
603 self.Emit('', depth)
604
605 def _EmitMethodsInHeader(self, obj_header_str):
606 # type: (str) -> None
607 """Generate PrettyTree(), type_id(), obj_header()"""
608 if self.pretty_print_methods:
609 self.Emit(
610 'hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen = nullptr);'
611 )
612 self.Emit('')
613
614 self.Emit('static constexpr ObjHeader obj_header() {')
615 self.Emit(' return %s;' % obj_header_str)
616 self.Emit('}')
617
618 def _GenClassForList(self, class_name, base_classes, tag_num):
619 # type: (str, List[str], int) -> None
620 depth = 0
621 self._GenClassBegin(class_name, base_classes, depth)
622
623 base_type_str = [b for b in base_classes if b.startswith('List<')][0]
624
625 # Zero arg constructor
626 self.Emit(' %s() : %s() {' % (class_name, base_type_str), depth)
627 self.Emit(' }', depth)
628
629 # One arg constructor used by Take.
630 # This should be is PROTECTED, like the superclass, but Alloc<T> calls
631 # it. Hm.
632 #self.Emit(' protected:')
633 self.Emit(
634 ' %s(%s* plain_list) : %s(plain_list) {' %
635 (class_name, base_type_str, base_type_str), depth)
636 self.Emit(' }', depth)
637
638 self.Emit(' static %s* New() {' % class_name, depth)
639 self.Emit(' return Alloc<%s>();' % class_name, depth)
640 self.Emit(' }', depth)
641
642 # Take() constructor
643 self.Emit(
644 ' static %s* Take(%s* plain_list) {' %
645 (class_name, base_type_str), depth)
646 self.Emit(' auto* result = Alloc<%s>(plain_list);' % class_name,
647 depth)
648 self.Emit(' plain_list->SetTaken();', depth)
649 self.Emit(' return result;', depth)
650 self.Emit(' }', depth)
651
652 # field_mask() should call List superclass, since say word_t won't have it
653 obj_header_str = 'ObjHeader::TaggedSubtype(%d, field_mask())' % tag_num
654 self.Indent()
655 self._EmitMethodsInHeader(obj_header_str)
656 self.Dedent()
657
658 self._GenClassEnd(class_name, depth)
659
660 def _GenClass(
661 self,
662 fields, # type: List[ast.Field]
663 class_name, # type: str
664 base_classes, # type: List[str]
665 depth, # type: int
666 tag_num, # type: Union[int, str]
667 obj_header_str='', # type: str
668 ):
669 # type: (...) -> None
670 """For Product and Constructor."""
671 self._GenClassBegin(class_name, base_classes, depth)
672
673 # Ensure that the member variables are ordered such that GC managed objects
674 # come before any unmanaged ones because we use `HeapTag::Scanned`.
675 managed_fields = []
676 unmanaged_fields = []
677 for f in fields:
678 if _IsManagedType(f.typ):
679 managed_fields.append(f)
680 else:
681 unmanaged_fields.append(f)
682 all_fields = managed_fields + unmanaged_fields
683
684 def FieldInitJoin(strs):
685 # type: (List[str]) -> str
686 # reflow doesn't work well here, so do it manually
687 return ',\n '.join(strs)
688
689 # Ensure that the constructor params are listed in the same order as the
690 # equivalent python constructors for compatibility in translated code.
691 params = []
692 for f in fields:
693 params.append('%s %s' % (_GetCppType(f.typ), f.name))
694
695 # Member initializers are in the same order as the member variables to
696 # avoid compiler warnings (the order doesn't affect the semantics).
697 inits = []
698 for f in all_fields:
699 inits.append('%s(%s)' % (f.name, f.name))
700
701 # Define constructor with N args
702 if len(inits):
703 self.Emit(' %s(%s)' % (class_name, ', '.join(params)), depth)
704 self.Emit(' : %s {' % FieldInitJoin(inits),
705 depth,
706 reflow=False)
707 self.Emit(' }')
708 else:
709 self.Emit(' %s(%s) {}' % (class_name, ', '.join(params)), depth)
710 self.Emit('')
711
712 # Define static constructor with ZERO args. Don't emit for types with no
713 # fields.
714 if fields:
715 init_args = []
716 for field in fields:
717 init_args.append(_DefaultValue(field.typ))
718
719 self.Emit(
720 ' static %s* CreateNull(bool alloc_lists = false) { ' %
721 class_name, depth)
722 self.Emit(
723 ' return Alloc<%s>(%s);' %
724 (class_name, ', '.join(init_args)), depth)
725 self.Emit(' }')
726 self.Emit('')
727
728 obj_header_str = 'ObjHeader::AsdlClass(%s, %d)' % (tag_num,
729 len(managed_fields))
730 self.Indent()
731 self._EmitMethodsInHeader(obj_header_str)
732 self.Dedent()
733
734 #
735 # Members
736 #
737 for field in all_fields:
738 self.Emit(" %s %s;" % (_GetCppType(field.typ), field.name))
739 self.Emit('')
740
741 self._GenClassEnd(class_name, depth)
742
743 def VisitSubType(self, subtype):
744 # type: (ast.SubTypeDecl) -> None
745 self._shared_type_tags[subtype.name] = self._product_counter
746
747 # Also create these last. They may inherit from sum types that have yet
748 # to be defined.
749 self._subtypes.append((subtype, self._product_counter))
750 self._product_counter += 1
751
752 def VisitProduct(self, product, name, depth):
753 # type: (ast.Product, str, int) -> None
754 self._shared_type_tags[name] = self._product_counter
755 # Create a tuple of _GenClass args to create LAST. They may inherit from
756 # sum types that have yet to be defined.
757 self._products.append((product, name, depth, self._product_counter))
758 self._product_counter += 1
759
760 def EmitFooter(self):
761 # type: () -> None
762 # Now generate all the product types we deferred.
763 for args in self._products:
764 ast_node, name, depth, tag_num = args
765 # Figure out base classes AFTERWARD.
766 bases = self._base_classes[name]
767 self._GenClass(ast_node.fields, name, bases, depth, tag_num)
768
769 for args in self._subtypes:
770 subtype, tag_num = args
771 # Figure out base classes AFTERWARD.
772 bases = self._base_classes[subtype.name]
773
774 cpp_type = _GetCppType(subtype.base_class)
775 assert cpp_type.endswith('*') # hack
776 cpp_type = cpp_type[:-1]
777 bases.append(cpp_type)
778
779 t = subtype.base_class.type_name
780 if t == 'List':
781 self._GenClassForList(subtype.name, bases, tag_num)
782
783 elif t == 'Dict':
784 raise AssertionError()
785 else:
786 #obj_header_str = ''
787 raise AssertionError()
788
789
790class MethodDefVisitor(visitor.AsdlVisitor):
791 """Generate the body of pretty printing methods.
792
793 We have to do this in another pass because types and schemas have
794 circular dependencies.
795 """
796
797 def __init__(self, f, abbrev_ns=None, abbrev_mod_entries=None):
798 # type: (IO[bytes], Optional[Any], List[Any]) -> None
799 visitor.AsdlVisitor.__init__(self, f)
800 self.abbrev_ns = abbrev_ns
801 self.abbrev_mod_entries = abbrev_mod_entries or []
802
803 def _EmitList(self, list_str, item_type, out_val_name):
804 # type: (str, ast.type_expr_t, str) -> None
805 # used in format strings
806 c_item_type = _GetCppType(item_type)
807
808 def _Emit(s):
809 # type: (str) -> None
810 self.Emit(s % sys._getframe(1).f_locals, self.current_depth)
811
812 _Emit(
813 'hnode::Array* %(out_val_name)s = Alloc<hnode::Array>(Alloc<List<hnode_t*>>());'
814 )
815 _Emit(
816 'for (ListIter<%(c_item_type)s> it(%(list_str)s); !it.Done(); it.Next()) {'
817 )
818 _Emit(' %(c_item_type)s v_ = it.Value();')
819
820 child_code_str, none_guard = _HNodeExpr(item_type, 'v_')
821 if none_guard: # e.g. for List[Optional[value_t]]
822 # TODO: could consolidate this logic with asdl/runtime.py NewLeaf(), which is prebuilt/
823 child_code_str = (
824 '(v_ == nullptr) ? Alloc<hnode::Leaf>(StrFromC("_"), color_e::OtherConst) : %s'
825 % child_code_str)
826 _Emit(' hnode_t* h = %(child_code_str)s;')
827 _Emit(' %(out_val_name)s->children->append(h);')
828 _Emit('}')
829
830 def _EmitListPrettyPrint(self, field, out_val_name):
831 # type: (ast.Field, str) -> None
832 typ = field.typ
833 if ast.IsOptional(typ): # descend one level
834 typ = cast(ast.ParameterizedType, typ).children[0]
835 item_type = cast(ast.ParameterizedType, typ).children[0]
836
837 self._EmitList('this->%s' % field.name, item_type, out_val_name)
838
839 def _EmitDictPrettyPrint(self, field):
840 # type: (ast.Field) -> None
841 typ = field.typ
842 if ast.IsOptional(typ): # descend one level
843 typ = cast(ast.ParameterizedType, typ).children[0]
844
845 k_typ = cast(ast.ParameterizedType, typ).children[0]
846 v_typ = cast(ast.ParameterizedType, typ).children[1]
847
848 k_c_type = _GetCppType(k_typ)
849 v_c_type = _GetCppType(v_typ)
850
851 k_code_str, _ = _HNodeExpr(k_typ, 'k')
852 v_code_str, _ = _HNodeExpr(v_typ, 'v')
853
854 self.Emit('auto* unnamed = NewList<hnode_t*>();')
855 self.Emit(
856 'auto* hdict = Alloc<hnode::Record>(kEmptyString, StrFromC("{"), StrFromC("}"), NewList<Field*>(), unnamed);'
857 )
858 self.Emit(
859 'for (DictIter<%s, %s> it(this->%s); !it.Done(); it.Next()) {' %
860 (k_c_type, v_c_type, field.name))
861 self.Emit(' auto k = it.Key();')
862 self.Emit(' auto v = it.Value();')
863 self.Emit(' unnamed->append(%s);' % k_code_str)
864 self.Emit(' unnamed->append(%s);' % v_code_str)
865 self.Emit('}')
866 self.Emit('L->append(Alloc<Field>(StrFromC("%s"), hdict));' %
867 field.name)
868
869 def _EmitCodeForField(self, field, counter):
870 # type: (ast.Field, int) -> None
871 """Generate code that returns an hnode for a field."""
872 out_val_name = 'x%d' % counter
873
874 if ast.IsList(field.typ):
875 self.Emit('if (this->%s != nullptr) { // List' % field.name)
876 self.Indent()
877 self._EmitListPrettyPrint(field, out_val_name)
878 self.Emit('L->append(Alloc<Field>(StrFromC("%s"), %s));' %
879 (field.name, out_val_name))
880 self.Dedent()
881 self.Emit('}')
882
883 elif ast.IsDict(field.typ):
884 self.Emit('if (this->%s != nullptr) { // Dict' % field.name)
885 self.Indent()
886 self._EmitDictPrettyPrint(field)
887 self.Dedent()
888 self.Emit('}')
889
890 elif ast.IsOptional(field.typ):
891 typ = cast(ast.ParameterizedType, field.typ).children[0]
892
893 self.Emit('if (this->%s) { // Optional' % field.name)
894 child_code_str, _ = _HNodeExpr(typ, 'this->%s' % field.name)
895 self.Emit(' hnode_t* %s = %s;' % (out_val_name, child_code_str))
896 self.Emit(' L->append(Alloc<Field>(StrFromC("%s"), %s));' %
897 (field.name, out_val_name))
898 self.Emit('}')
899
900 else:
901 var_name = 'this->%s' % field.name
902 code_str, obj_none_guard = _HNodeExpr(field.typ, var_name)
903
904 depth = self.current_depth
905 if obj_none_guard: # to satisfy MyPy type system
906 pass
907 self.Emit('hnode_t* %s = %s;' % (out_val_name, code_str), depth)
908
909 self.Emit(
910 'L->append(Alloc<Field>(StrFromC("%s"), %s));' %
911 (field.name, out_val_name), depth)
912
913 def _EmitPrettyPrintMethods(self,
914 class_name,
915 all_fields,
916 sum_name=None,
917 list_item_type=None):
918 # type: (str, List[ast.Field], Optional[str], Optional[Any]) -> None
919 """
920 """
921 self.Emit('')
922 self.Emit(
923 'hnode_t* %s::PrettyTree(bool do_abbrev, Dict<int, bool>* seen) {'
924 % class_name)
925
926 # Similar to j8::HeapValueId()
927 self.Emit(' seen = seen ? seen : Alloc<Dict<int, bool>>();')
928 self.Emit(' int heap_id = ObjectId(this);')
929 self.Emit(' if (dict_contains(seen, heap_id)) {')
930 self.Emit(' return Alloc<hnode::AlreadySeen>(heap_id);')
931 self.Emit(' }')
932 self.Emit(' seen->set(heap_id, true);')
933 self.Emit('')
934
935 if list_item_type:
936 self.Indent()
937 self._EmitList('this', list_item_type, 'out_node')
938 self.Dedent()
939 else:
940 if sum_name is not None:
941 n = '%s_str(this->tag())' % sum_name
942 else:
943 n = 'StrFromC("%s")' % class_name
944
945 abbrev_name = '_%s' % class_name
946
947 if abbrev_name in self.abbrev_mod_entries:
948 self.Emit(' if (do_abbrev) {')
949 self.Emit(' auto* p = %s::%s(this);' %
950 (self.abbrev_ns, abbrev_name))
951 self.Emit(' if (p) {')
952 self.Emit(' return p;')
953 self.Emit(' }')
954 self.Emit(' }')
955 else:
956 #self.Emit(' // no abbrev %s' % abbrev_name)
957 pass
958
959 self.Emit(' hnode::Record* out_node = runtime::NewRecord(%s);' %
960 n)
961 if all_fields:
962 self.Emit(' List<Field*>* L = out_node->fields;')
963 self.Emit('')
964
965 # Use the runtime type to be more like asdl/format.py
966 for local_id, field in enumerate(all_fields):
967 #log('%s :: %s', field_name, field_desc)
968 self.Indent()
969 self._EmitCodeForField(field, local_id)
970 self.Dedent()
971 self.Emit('')
972 self.Emit(' return out_node;')
973 self.Emit('}')
974 self.Emit('')
975
976 def _EmitStrFunction(self,
977 sum,
978 sum_name,
979 depth,
980 strong=False,
981 simple=False):
982 # type: (ast.Sum, str, int, bool, bool) -> None
983 add_suffix = not ('no_namespace_suffix' in sum.generate)
984 if add_suffix:
985 if simple:
986 enum_name = '%s_i' % sum_name
987 else:
988 enum_name = '%s_e' % sum_name
989 else:
990 enum_name = sum_name
991
992 if strong:
993 self.Emit(
994 'BigStr* %s_str(%s tag, bool dot) {' % (sum_name, enum_name),
995 depth)
996 else:
997 self.Emit('BigStr* %s_str(int tag, bool dot) {' % sum_name, depth)
998
999 buf_size = 32
1000 v_max = max(len(variant.name) for variant in sum.types)
1001 s_max = v_max + 1 + len(sum_name) + 1 # for . and NUL
1002 if s_max > buf_size:
1003 raise RuntimeError('Sum name %r + variant name is too long' %
1004 sum_name)
1005
1006 self.Emit(' char buf[%d];' % buf_size, depth)
1007 self.Emit(' const char* v = nullptr;', depth)
1008 self.Emit(' switch (tag) {', depth)
1009 for variant in sum.types:
1010 self.Emit('case %s::%s:' % (enum_name, variant.name), depth + 1)
1011 self.Emit(' v = "%s"; break;' % variant.name, depth + 1)
1012
1013 self.Emit('default:', depth + 1)
1014 self.Emit(' assert(0);', depth + 1)
1015
1016 self.Emit(' }', depth)
1017 self.Emit(' if (dot) {', depth)
1018 self.Emit(' snprintf(buf, %d, "%s.%%s", v);' % (buf_size, sum_name),
1019 depth)
1020 self.Emit(' return StrFromC(buf);', depth)
1021 self.Emit(' } else {', depth)
1022 self.Emit(' return StrFromC(v);', depth)
1023 self.Emit(' }', depth)
1024 self.Emit('}', depth)
1025
1026 def VisitSimpleSum(self, sum, name, depth):
1027 # type: (ast.SimpleSum, str, int) -> None
1028 if 'integers' in sum.generate or 'uint16' in sum.generate:
1029 self._EmitStrFunction(sum, name, depth, strong=False, simple=True)
1030 else:
1031 self._EmitStrFunction(sum, name, depth, strong=True)
1032
1033 def VisitCompoundSum(self, sum, sum_name, depth):
1034 # type: (ast.Sum, str, int) -> None
1035 self._EmitStrFunction(sum, sum_name, depth)
1036
1037 # Generate definitions for the for zero arg singleton globals
1038 for variant in sum.types:
1039 if variant.shared_type:
1040 continue
1041 if len(variant.fields) == 0:
1042 variant_name = variant.name
1043 self.Emit('')
1044 self.Emit('%s__%s* %s::%s = &g%s__%s.obj;' %
1045 (sum_name, variant_name, sum_name, variant_name,
1046 sum_name, variant_name))
1047 self.Emit('')
1048 self.Emit('GcGlobal<%s__%s> g%s__%s = ' %
1049 (sum_name, variant_name, sum_name, variant_name))
1050 self.Emit(' { ObjHeader::Global(%s_e::%s) };' %
1051 (sum_name, variant_name))
1052
1053 for variant in sum.types:
1054 if variant.shared_type:
1055 continue
1056 all_fields = variant.fields
1057 class_name = '%s__%s' % (sum_name, variant.name)
1058 self._EmitPrettyPrintMethods(class_name,
1059 all_fields,
1060 sum_name=sum_name)
1061
1062 # Emit dispatch WITHOUT using 'virtual'
1063 self.Emit('')
1064 self.Emit(
1065 'hnode_t* %s_t::PrettyTree(bool do_abbrev, Dict<int, bool>* seen) {'
1066 % sum_name)
1067 self.Emit(' switch (this->tag()) {', depth)
1068
1069 for variant in sum.types:
1070 if variant.shared_type:
1071 subtype_name = variant.shared_type
1072 else:
1073 subtype_name = '%s__%s' % (sum_name, variant.name)
1074
1075 self.Emit(' case %s_e::%s: {' % (sum_name, variant.name), depth)
1076 self.Emit(
1077 ' %s* obj = static_cast<%s*>(this);' %
1078 (subtype_name, subtype_name), depth)
1079 self.Emit(' return obj->PrettyTree(do_abbrev, seen);', depth)
1080 self.Emit(' }', depth)
1081
1082 self.Emit(' default:', depth)
1083 self.Emit(' assert(0);', depth)
1084
1085 self.Emit(' }')
1086 self.Emit('}')
1087
1088 def VisitProduct(self, product, name, depth):
1089 # type: (ast.Product, str, int) -> None
1090 self._EmitPrettyPrintMethods(name, product.fields)
1091
1092 def VisitSubType(self, subtype):
1093 # type: (ast.SubTypeDecl) -> None
1094 list_item_type = None
1095 b = subtype.base_class
1096 if isinstance(b, ast.ParameterizedType):
1097 if b.type_name == 'List':
1098 list_item_type = b.children[0]
1099 self._EmitPrettyPrintMethods(subtype.name, [],
1100 list_item_type=list_item_type)