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

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