7 #include <unordered_set> 17 std::ofstream &stream,
18 const std::string &doc,
19 const std::string &indent =
"" 21 auto word = std::ostringstream();
22 auto line = std::ostringstream();
23 line << indent <<
"\"\"\"";
24 bool line_empty =
false;
25 bool first_word =
true;
28 if (c ==
'\n' || c ==
' ') {
42 flush = !line_empty && line.str().size() + word.str().size() > 79;
45 stream << line.str() << std::endl;
52 if (!word.str().empty()) {
53 line <<
" " << word.str();
59 if (line.str().size() + 3 > 79) {
60 stream << std::endl << indent;
62 stream <<
"\"\"\"" << std::endl;
70 std::ofstream &output,
75 output <<
" return " << node.
title_case_name <<
"._deserialize(cbor, seq_to_ob, links)" << std::endl;
77 for (
auto &derived : node.
derived) {
87 std::ofstream &output,
96 output << node.
parent->title_case_name;
100 output <<
"):" << std::endl;
101 if (!node.
doc.empty()) {
107 output <<
" __slots__ = [";
108 if (!node.
fields.empty()) {
110 for (
const auto &field : node.
fields) {
111 output <<
" '_attr_" << field.name <<
"'," << std::endl;
115 output <<
"]" << std::endl << std::endl;
118 output <<
" def __init__(";
119 if (all_fields.empty()) {
123 output <<
" self," << std::endl;
124 for (
const auto &field : all_fields) {
125 output <<
" " << field.name <<
"=None," << std::endl;
129 output <<
"):" << std::endl;
130 output <<
" super().__init__(";
133 for (
const auto &field : node.
parent->all_fields()) {
139 output << field.name <<
"=" << field.name;
142 output <<
")" << std::endl;
143 for (
const auto &field : node.
fields) {
144 output <<
" self." << field.name <<
" = " << field.name << std::endl;
149 for (
const auto &field : node.
fields) {
155 bool is_prim = field.type ==
Prim && field.ext_type ==
Prim;
156 bool is_any = field.type ==
Any || (field.type ==
Prim && field.ext_type ==
Any);
157 bool is_many = field.type ==
Many || (field.type ==
Prim && field.ext_type ==
Many);
158 bool is_any_or_many = is_any || is_many;
159 bool is_link = field.type ==
Link || (field.type ==
Prim && field.ext_type ==
Link);
160 is_link |= field.type ==
OptLink || (field.type ==
Prim && field.ext_type ==
OptLink);
161 std::string type = (field.type ==
Prim) ? field.py_prim_type : field.node_type->title_case_name;
163 if (is_any_or_many) {
164 auto split = type.rfind(
'.');
165 if (split == std::string::npos) {
166 type =
"Multi" + type;
168 type = type.substr(0, split + 1) +
"Multi" + type.substr(split + 1);
173 output <<
" @property" << std::endl;
174 output <<
" def " << field.name <<
"(self):" << std::endl;
175 if (!field.doc.empty()) {
178 output <<
" return self._attr_" << field.name << std::endl << std::endl;
181 output <<
" @" << field.name <<
".setter" << std::endl;
182 output <<
" def " << field.name <<
"(self, val):" << std::endl;
183 output <<
" if val is None:" << std::endl;
184 output <<
" del self." << field.name << std::endl;
185 output <<
" return" << std::endl;
186 output <<
" if not isinstance(val, " << type <<
"):" << std::endl;
188 output <<
" # Try to \"typecast\" if this isn't an obvious mistake." << std::endl;
189 output <<
" if isinstance(val, Node):" << std::endl;
190 output <<
" raise TypeError('" << field.name <<
" must be of type " << type <<
"')" << std::endl;
191 output <<
" val = " << type <<
"(val)" << std::endl;
194 output <<
" raise TypeError('" << field.name <<
" must be of type " << type <<
"')" << std::endl;
196 output <<
" self._attr_" << field.name <<
" = ";
197 output <<
"val" << std::endl << std::endl;
201 output <<
" @" << field.name <<
".deleter" << std::endl;
202 output <<
" def " << field.name <<
"(self):" << std::endl;
203 output <<
" self._attr_" << field.name;
204 if (is_prim || is_any_or_many) {
205 output <<
" = " << type <<
"()";
209 output << std::endl << std::endl;
215 output <<
" def __eq__(self, other):" << std::endl;
216 format_doc(output,
"Equality operator. Ignores annotations!",
" ");
217 output <<
" if not isinstance(other, " << node.
title_case_name <<
"):" << std::endl;
218 output <<
" return False" << std::endl;
219 for (
const auto &field : all_fields) {
220 EdgeType type = (field.type ==
Prim) ? field.ext_type : field.type;
227 output <<
" if self." << field.name <<
" != other." << field.name <<
":" << std::endl;
231 output <<
" if self." << field.name <<
" is not other." << field.name <<
":" << std::endl;
234 output <<
" return False" << std::endl;
236 output <<
" return True" << std::endl << std::endl;
241 output <<
" def dump(self, indent=0, annotations=None, links=1):" << std::endl;
243 "Returns a debug representation of this tree as a " 244 "multiline string. indent is the number of double spaces " 245 "prefixed before every line. annotations, if specified, " 246 "must be a set-like object containing the key strings of " 247 "the annotations that are to be printed. links specifies " 248 "the maximum link recursion depth.",
" ");
249 output <<
" s = [' '*indent]" << std::endl;
251 output <<
" if annotations is None:" << std::endl;
252 output <<
" annotations = []" << std::endl;
253 output <<
" for key in annotations:" << std::endl;
254 output <<
" if key in self:" << std::endl;
255 output <<
" s.append(' # {}: {}'.format(key, self[key]))" << std::endl;
256 output <<
" s.append('\\n')" << std::endl;
257 if (!all_fields.empty()) {
258 output <<
" indent += 1" << std::endl;
259 for (
auto &field : all_fields) {
260 EdgeType type = (field.type ==
Prim) ? field.ext_type : field.type;
261 output <<
" s.append(' '*indent)" << std::endl;
262 output <<
" s.append('" << field.name;
268 output <<
"')" << std::endl;
274 output <<
" if self." << field.name <<
" is None:" << std::endl;
275 if (type ==
One || type ==
Link) {
276 output <<
" s.append('!MISSING\\n')" << std::endl;
278 output <<
" s.append('-\\n')" << std::endl;
280 output <<
" else:" << std::endl;
281 output <<
" s.append('<\\n')" << std::endl;
282 if (field.ext_type ==
Link || field.ext_type ==
OptLink) {
283 output <<
" if links:" << std::endl;
284 output <<
" s.append(self." << field.name <<
".dump(indent + 1, annotations, links - 1) + '\\n')" << std::endl;
285 output <<
" else:" << std::endl;
286 output <<
" s.append(' '*(indent+1) + '...\\n')" << std::endl;
288 output <<
" s.append(self." << field.name <<
".dump(indent + 1, annotations, links) + '\\n')" << std::endl;
290 output <<
" s.append(' '*indent + '>\\n')" << std::endl;
295 output <<
" if not self." << field.name <<
":" << std::endl;
296 if (field.ext_type ==
Many) {
297 output <<
" s.append('!MISSING\\n')" << std::endl;
299 output <<
" s.append('-\\n')" << std::endl;
301 output <<
" else:" << std::endl;
302 output <<
" s.append('[\\n')" << std::endl;
303 output <<
" for child in self." << field.name <<
":" << std::endl;
304 output <<
" s.append(child.dump(indent + 1, annotations, links) + '\\n')" << std::endl;
305 output <<
" s.append(' '*indent + ']\\n')" << std::endl;
309 output <<
" s.append(str(self." << field.name <<
") + '\\n')" << std::endl;
314 output <<
" indent -= 1" << std::endl;
315 output <<
" s.append(' '*indent)" << std::endl;
317 output <<
" s.append(')')" << std::endl;
318 output <<
" return ''.join(s)" << std::endl << std::endl;
319 output <<
" __str__ = dump" << std::endl;
320 output <<
" __repr__ = dump" << std::endl << std::endl;
325 output <<
" def find_reachable(self, id_map=None):" << std::endl;
327 "Returns a dictionary mapping Python id() values to " 328 "stable sequence numbers for all nodes in the tree rooted " 329 "at this node. If id_map is specified, found nodes are " 330 "appended to it.",
" ");
331 output <<
" if id_map is None:" << std::endl;
332 output <<
" id_map = {}" << std::endl;
333 output <<
" if id(self) in id_map:" << std::endl;
334 output <<
" raise NotWellFormed('node {!r} with id {} occurs more than once'.format(self, id(self)))" << std::endl;
335 output <<
" id_map[id(self)] = len(id_map)" << std::endl;
336 for (
const auto &field : all_fields) {
337 EdgeType type = (field.type ==
Prim) ? field.ext_type : field.type;
341 output <<
" if self._attr_" << field.name <<
" is not None:" << std::endl;
342 output <<
" self._attr_" << field.name <<
".find_reachable(id_map)" << std::endl;
346 output <<
" for el in self._attr_" << field.name <<
":" << std::endl;
347 output <<
" el.find_reachable(id_map)" << std::endl;
355 output <<
" return id_map" << std::endl << std::endl;
360 output <<
" def check_complete(self, id_map=None):" << std::endl;
362 "Raises NotWellFormed if the tree rooted at this node " 363 "is not well-formed. If id_map is specified, this tree is " 364 "only a subtree in the context of a larger tree, and id_map " 365 "must be a dict mapping from Python id() codes to tree " 366 "indices for all reachable nodes.",
" ");
367 output <<
" if id_map is None:" << std::endl;
368 output <<
" id_map = self.find_reachable()" << std::endl;
369 for (
const auto &field : all_fields) {
370 EdgeType type = (field.type ==
Prim) ? field.ext_type : field.type;
373 output <<
" if self._attr_" << field.name <<
" is None:" << std::endl;
374 output <<
" raise NotWellFormed('";
375 output << field.name <<
" is required but not set')" << std::endl;
378 output <<
" if self._attr_" << field.name <<
" is not None:" << std::endl;
379 output <<
" self._attr_" << field.name <<
".check_complete(id_map)" << std::endl;
382 output <<
" if not self._attr_" << field.name <<
":" << std::endl;
383 output <<
" raise NotWellFormed('";
384 output << field.name <<
" needs at least one node but has zero')" << std::endl;
387 output <<
" for child in self._attr_" << field.name <<
":" << std::endl;
388 output <<
" child.check_complete(id_map)" << std::endl;
391 output <<
" if self._attr_" << field.name <<
" is None:" << std::endl;
392 output <<
" raise NotWellFormed('";
393 output << field.name <<
" is required but not set')" << std::endl;
396 output <<
" if self._attr_" << field.name <<
" is not None:" << std::endl;
397 output <<
" if id(self._attr_" << field.name <<
") not in id_map:" << std::endl;
398 output <<
" raise NotWellFormed('";
399 output << field.name <<
" links to unreachable node')" << std::endl;
410 output <<
" def copy(self):" << std::endl;
411 format_doc(output,
"Returns a shallow copy of this node.",
" ");
414 for (
const auto &field : all_fields) {
418 output <<
"," << std::endl;
420 output <<
" " << field.name <<
"=";
421 auto type = (field.type !=
Prim) ? field.type : field.ext_type;
428 output <<
"self._attr_" << field.name;
432 output <<
"self._attr_" << field.name <<
".copy()";
436 output << std::endl <<
" )" << std::endl << std::endl;
441 output <<
" def clone(self):" << std::endl;
443 "Returns a deep copy of this node. This mimics the " 444 "C++ interface, deficiencies with links included; that is, " 445 "links always point to the original tree. If you're not " 446 "cloning a subtree in a context where this is the desired " 447 "behavior, you may want to use the copy.deepcopy() from the " 448 "stdlib instead, which should copy links correctly.",
452 for (
const auto &field : all_fields) {
456 output <<
"," << std::endl;
458 output <<
" " << field.name <<
"=";
459 auto type = (field.type !=
Prim) ? field.type : field.ext_type;
466 output <<
"_cloned(self._attr_" << field.name <<
")";
470 output <<
"self._attr_" << field.name;
474 output << std::endl <<
" )" << std::endl << std::endl;
478 output <<
" @staticmethod" << std::endl;
479 output <<
" def _deserialize(cbor, seq_to_ob, links):" << std::endl;
481 "Attempts to deserialize the given cbor object (in Python " 482 "primitive representation) into a node of this type. All " 483 "(sub)nodes are added to the seq_to_ob dict, indexed by their " 484 "cbor sequence number. All links are registered in the links " 485 "list by means of a two-tuple of the setter function for the " 486 "link field and the sequence number of the target node.",
488 output <<
" if not isinstance(cbor, dict):" << std::endl;
489 output <<
" raise TypeError('node description object must be a dict')" << std::endl;
490 output <<
" typ = cbor.get('@t', None)" << std::endl;
491 output <<
" if typ is None:" << std::endl;
492 output <<
" raise ValueError('type (@t) field is missing from node serialization')" << std::endl;
495 output <<
" raise ValueError('found node serialization for ' + typ + ', but expected ";
497 if (all_fields.empty()) {
499 output <<
" # Construct the " << node.
title_case_name <<
" node." << std::endl;
502 std::vector<std::string> links;
503 for (
const auto &field : all_fields) {
505 output <<
" # Deserialize the " << field.name <<
" field." << std::endl;
506 output <<
" field = cbor.get('" << field.name <<
"', None)" << std::endl;
507 output <<
" if not isinstance(field, dict):" << std::endl;
508 output <<
" raise ValueError('missing or invalid serialization of field " << field.name <<
"')" << std::endl;
509 auto type = (field.type ==
Prim) ? field.ext_type : field.type;
510 auto type_name = (field.type ==
Prim) ? field.py_prim_type : field.node_type->title_case_name;
511 auto multi_name = (field.type ==
Prim) ? field.py_multi_type : (
"Multi" + field.node_type->title_case_name);
513 output <<
" if field.get('@T') != '";
515 case Maybe: output <<
"?";
break;
516 case One: output <<
"1";
break;
517 case Any: output <<
"*";
break;
518 case Many: output <<
"+";
break;
519 case OptLink: output <<
"@";
break;
520 case Link: output <<
"$";
break;
521 case Prim:
throw std::runtime_error(
"internal error, should be unreachable");
523 output <<
"':" << std::endl;
524 output <<
" raise ValueError('unexpected edge type for field " << field.name <<
"')" << std::endl;
529 output <<
" if field.get('@t', None) is None:" << std::endl;
530 output <<
" f_" << field.name <<
" = None" << std::endl;
531 output <<
" else:" << std::endl;
532 output <<
" f_" << field.name <<
" = " << type_name <<
"._deserialize(field, seq_to_ob, links)" << std::endl;
536 output <<
" data = field.get('@d', None)" << std::endl;
537 output <<
" if not isinstance(data, list):" << std::endl;
538 output <<
" raise ValueError('missing serialization of Any/Many contents')" << std::endl;
539 output <<
" f_" << field.name <<
" = " << multi_name <<
"()" << std::endl;
540 output <<
" for element in data:" << std::endl;
541 output <<
" if element.get('@T') != '1':" << std::endl;
542 output <<
" raise ValueError('unexpected edge type for Any/Many element')" << std::endl;
543 output <<
" f_" << field.name <<
".append(" << type_name <<
"._deserialize(element, seq_to_ob, links))" << std::endl;
547 output <<
" f_" << field.name <<
" = None" << std::endl;
548 output <<
" l_" << field.name <<
" = field.get('@l', None)" << std::endl;
549 links.push_back(field.name);
552 output <<
" if hasattr(" << field.py_prim_type <<
", 'deserialize_cbor'):" << std::endl;
553 output <<
" f_" << field.name <<
" = " << field.py_prim_type <<
".deserialize_cbor(field)" << std::endl;
554 output <<
" else:" << std::endl;
556 output <<
" raise ValueError('no deserialization function seems to exist for field type " << field.py_prim_type <<
"')" << std::endl;
558 output <<
" f_" << field.name <<
" = " << spec.
py_deserialize_fn <<
"(" << field.py_prim_type <<
", field)" << std::endl;
564 output <<
" # Construct the " << node.
title_case_name <<
" node." << std::endl;
567 for (
const auto &field : all_fields) {
573 output <<
"f_" << field.name;
575 output <<
")" << std::endl;
576 if (!links.empty()) {
578 output <<
" # Register links to be made after tree construction." << std::endl;
579 for (
const auto &link : links) {
580 output <<
" links.append((lambda val: " << node.
title_case_name <<
"." << link <<
".fset(node, val), l_" << link <<
"))" << std::endl;
585 output <<
" # Deserialize annotations." << std::endl;
586 output <<
" for key, val in cbor.items():" << std::endl;
587 output <<
" if not (key.startswith('{') and key.endswith('}')):" << std::endl;
588 output <<
" continue" << std::endl;
589 output <<
" key = key[1:-1]" << std::endl;
591 output <<
" node[key] = val" << std::endl;
593 output <<
" node[key] = " << spec.
py_deserialize_fn <<
"(key, val)" << std::endl;
596 output <<
" # Register node in sequence number lookup." << std::endl;
597 output <<
" seq = cbor.get('@i', None)" << std::endl;
598 output <<
" if not isinstance(seq, int):" << std::endl;
599 output <<
" raise ValueError('sequence number field (@i) is not an integer or missing from node serialization')" << std::endl;
600 output <<
" if seq in seq_to_ob:" << std::endl;
601 output <<
" raise ValueError('duplicate sequence number %d' % seq)" << std::endl;
602 output <<
" seq_to_ob[seq] = node" << std::endl << std::endl;
603 output <<
" return node" << std::endl;
606 output <<
" raise ValueError('unknown or unexpected type (@t) found in node serialization')" << std::endl;
611 output <<
" def _serialize(self, id_map):" << std::endl;
613 "Serializes this node to the Python primitive " 614 "representation of its CBOR serialization. The tree that the " 615 "node belongs to must be well-formed. id_map must match Python " 616 "id() calls for all nodes to unique integers, to use for the " 617 "sequence number representation of links.",
619 output <<
" cbor = {'@i': id_map[id(self)], '@t': '" << node.
title_case_name <<
"'}" << std::endl;
620 for (
const auto &field : all_fields) {
622 output <<
" # Serialize the " << field.name <<
" field." << std::endl;
623 auto type = (field.type ==
Prim) ? field.ext_type : field.type;
624 auto type_name = (field.type ==
Prim) ? field.py_prim_type : field.node_type->title_case_name;
626 output <<
" if hasattr(self._attr_" << field.name <<
", 'serialize_cbor'):" << std::endl;
627 output <<
" cbor['" << field.name <<
"'] = self._attr_" << field.name <<
".serialize_cbor()" << std::endl;
628 output <<
" else:" << std::endl;
630 output <<
" raise ValueError('no serialization function seems to exist for field type " << field.py_prim_type <<
"')" << std::endl;
632 output <<
" cbor['" << field.name <<
"'] = " << spec.
py_serialize_fn <<
"(" << field.py_prim_type <<
", self._attr_" << field.name <<
")" << std::endl;
635 output <<
" field = {'@T': '";
637 case Maybe: output <<
"?";
break;
638 case One: output <<
"1";
break;
639 case Any: output <<
"*";
break;
640 case Many: output <<
"+";
break;
641 case OptLink: output <<
"@";
break;
642 case Link: output <<
"$";
break;
643 case Prim:
throw std::runtime_error(
"internal error, should be unreachable");
645 output <<
"'}" << std::endl;
649 output <<
" if self._attr_" << field.name <<
" is None:" << std::endl;
650 output <<
" field['@t'] = None" << std::endl;
651 output <<
" else:" << std::endl;
652 output <<
" field.update(self._attr_" << field.name <<
"._serialize(id_map))" << std::endl;
656 output <<
" lst = []" << std::endl;
657 output <<
" for el in self._attr_" << field.name <<
":" << std::endl;
658 output <<
" el = el._serialize(id_map)" << std::endl;
659 output <<
" el['@T'] = '1'" << std::endl;
660 output <<
" lst.append(el)" << std::endl;
661 output <<
" field['@d'] = lst" << std::endl;
665 output <<
" if self._attr_" << field.name <<
" is None:" << std::endl;
666 output <<
" field['@l'] = None" << std::endl;
667 output <<
" else:" << std::endl;
668 output <<
" field['@l'] = id_map[id(self._attr_" << field.name <<
")]" << std::endl;
670 case Prim:
throw std::runtime_error(
"internal error, should be unreachable");
672 output <<
" cbor['" << field.name <<
"'] = field" << std::endl;
676 output <<
" # Serialize annotations." << std::endl;
677 output <<
" for key, val in self._annot.items():" << std::endl;
679 output <<
" try:" << std::endl;
680 output <<
" cbor['{%s}' % key] = _py_to_cbor(val)" << std::endl;
681 output <<
" except TypeError:" << std::endl;
682 output <<
" pass" << std::endl;
684 output <<
" cbor['{%s}' % key] = _py_to_cbor(" << spec.
py_serialize_fn <<
"(key, val))" << std::endl;
687 output <<
" return cbor" << std::endl << std::endl;
692 output <<
"class Multi" << node.
title_case_name <<
"(_Multiple):" << std::endl;
693 auto doc =
"Wrapper for an edge with multiple " + node.
title_case_name +
" objects.";
697 output << std::endl << std::endl;
708 const std::string &python_filename,
711 auto nodes = specification.
nodes;
714 auto output = std::ofstream(python_filename);
715 if (!output.is_open()) {
716 std::cerr <<
"Failed to open Python file for writing" << std::endl;
725 output <<
"import functools" << std::endl;
726 output <<
"import struct" << std::endl;
728 output << include << std::endl;
737 def _cbor_read_intlike(cbor, offset, info): 738 """Parses the additional information and reads any additional bytes it 739 specifies the existence of, and returns the encoded integer. offset 740 should point to the byte immediately following the initial byte. Returns 741 the encoded integer and the offset immediately following the object.""" 743 # Info less than 24 is a shorthand for the integer itself. 747 # 24 is 8-bit following the info byte. 749 return cbor[offset], offset + 1 751 # 25 is 16-bit following the info byte. 753 val, = struct.unpack('>H', cbor[offset:offset+2]) 754 return val, offset + 2 756 # 26 is 32-bit following the info byte. 758 val, = struct.unpack('>I', cbor[offset:offset+4]) 759 return val, offset + 4 761 # 27 is 64-bit following the info byte. 763 val, = struct.unpack('>Q', cbor[offset:offset+8]) 764 return val, offset + 8 766 # Info greater than or equal to 28 is illegal. Note that 31 is used for 767 # indefinite lengths, so this must be checked prior to calling this 769 raise ValueError("invalid CBOR: illegal additional info for integer or object length") 772 def _sub_cbor_to_py(cbor, offset): 773 """Converts the CBOR object starting at cbor[offset] to its Python 774 representation for as far as tree-gen supports CBOR. Returns this Python 775 representation and the offset immediately following the CBOR representation 776 thereof. Supported types: 778 - 0: unsigned integer (int) 779 - 1: negative integer (int) 780 - 2: byte string (bytes) 781 - 3: UTF-8 string (str) 784 - 6: semantic tag (ignored) 787 - 7.22: null (NoneType) 788 - 7.27: double-precision float (float) 790 Both definite-length and indefinite-length notation is supported for sized 791 objects (strings, arrays, maps). A ValueError is thrown if the CBOR is 792 invalid or contains unsupported structures.""" 794 # Read the initial byte. 795 initial = cbor[offset] 797 info = initial & 0x1F 800 # Handle unsigned integer (0) and negative integer (1). 802 value, offset = _cbor_read_intlike(cbor, offset, info) 807 # Handle byte string (2) and UTF-8 string (3). 810 # Gather components of the string in here. 813 # Handle indefinite length strings. These consist of a 814 # break-terminated (0xFF) list of definite-length strings of the 818 sub_initial = cbor[offset]; offset += 1 819 if sub_initial == 0xFF: 821 sub_typ = sub_initial >> 5 822 sub_info = sub_initial & 0x1F 824 raise ValueError('invalid CBOR: illegal indefinite-length string component') 826 # Seek past definite-length string component. The size in 827 # bytes is encoded as an integer. 828 size, offset = _cbor_read_intlike(cbor, offset, sub_info) 829 value.append(cbor[offset:offset + size]) 831 value = b''.join(value) 835 # Handle definite-length strings. The size in bytes is encoded as 837 size, offset = _cbor_read_intlike(cbor, offset, info) 838 value = cbor[offset:offset + size] 842 value = value.decode('UTF-8') 845 # Handle array (4) and map (5). 848 # Create result container. 849 container = [] if typ == 4 else {} 851 # Handle indefinite length arrays and maps. 854 # Read objects/object pairs until we encounter a break. 855 while cbor[offset] != 0xFF: 857 value, offset = _sub_cbor_to_py(cbor, offset) 858 container.append(value) 860 key, offset = _sub_cbor_to_py(cbor, offset) 861 if not isinstance(key, str): 862 raise ValueError('invalid CBOR: map key is not a UTF-8 string') 863 value, offset = _sub_cbor_to_py(cbor, offset) 864 container[key] = value 866 # Seek past the break. 871 # Handle definite-length arrays and maps. The amount of 872 # objects/object pairs is encoded as an integer. 873 size, offset = _cbor_read_intlike(cbor, offset, info) 874 for _ in range(size): 876 value, offset = _sub_cbor_to_py(cbor, offset) 877 container.append(value) 879 key, offset = _sub_cbor_to_py(cbor, offset) 880 if not isinstance(key, str): 881 raise ValueError('invalid CBOR: map key is not a UTF-8 string') 882 value, offset = _sub_cbor_to_py(cbor, offset) 883 container[key] = value 885 return container, offset 887 # Handle semantic tags. 890 # We don't use semantic tags for anything, but ignoring them is 891 # legal and reading past them is easy enough. 892 _, offset = _cbor_read_intlike(cbor, offset, info) 893 return _sub_cbor_to_py(cbor, offset) 895 # Handle major type 7. Here, the type is defined by the additional info. 896 # Additional info 24 is reserved for having the type specified by the 897 # next byte, but all such values are unassigned. 912 raise ValueError('invalid CBOR: undefined value is not supported') 915 # Half-precision float. 916 raise ValueError('invalid CBOR: half-precision float is not supported') 919 # Single-precision float. 920 raise ValueError('invalid CBOR: single-precision float is not supported') 923 # Double-precision float. 924 value, = struct.unpack('>d', cbor[offset:offset+8]) 925 return value, offset + 8 928 # Break value used for indefinite-length objects. 929 raise ValueError('invalid CBOR: unexpected break') 931 raise ValueError('invalid CBOR: unknown type code') 934 def _cbor_to_py(cbor): 935 """Converts the given CBOR object (bytes) to its Python representation for 936 as far as tree-gen supports CBOR. Supported types: 938 - 0: unsigned integer (int) 939 - 1: negative integer (int) 940 - 2: byte string (bytes) 941 - 3: UTF-8 string (str) 944 - 6: semantic tag (ignored) 947 - 7.22: null (NoneType) 948 - 7.27: double-precision float (float) 950 Both definite-length and indefinite-length notation is supported for sized 951 objects (strings, arrays, maps). A ValueError is thrown if the CBOR is 952 invalid or contains unsupported structures.""" 954 value, length = _sub_cbor_to_py(cbor, 0) 955 if length < len(cbor): 956 raise ValueError('invalid CBOR: garbage at the end') 961 """Marker class indicating that this bytes object represents CBOR.""" 965 def _cbor_write_intlike(value, major=0): 966 """Converts the given integer to its minimal representation in CBOR. The 967 major code can be overridden to write lengths for strings, arrays, and 970 # Negative integers use major code 1. 976 # Use the minimal representation. 978 return struct.pack('>B', initial | value) 980 return struct.pack('>BB', initial | 24, value) 982 return struct.pack('>BH', initial | 25, value) 983 if value < 0x100000000: 984 return struct.pack('>BI', initial | 26, value) 985 if value < 0x10000000000000000: 986 return struct.pack('>BQ', initial | 27, value) 988 raise ValueError('integer too large for CBOR (bigint not supported)') 991 def _py_to_cbor(value, type_converter=None): 992 """Inverse of _cbor_to_py(). type_converter optionally specifies a function 993 that takes a value and either converts it to a primitive for serialization, 994 converts it to a _Cbor object manually, or raises a TypeError if no 995 conversion is known. If no type_converter is specified, a TypeError is 996 raised in all cases the type_converter would otherwise be called. The cbor 997 serialization is returned using a _Cbor object, which is just a marker class 998 behaving just like bytes.""" 999 if isinstance(value, _Cbor): 1002 if isinstance(value, int): 1003 return _Cbor(_cbor_write_intlike(value)) 1005 if isinstance(value, float): 1006 return _Cbor(struct.pack('>Bd', 0xFB, value)) 1008 if isinstance(value, str): 1009 value = value.encode('UTF-8') 1010 return _Cbor(_cbor_write_intlike(len(value), 3) + value) 1012 if isinstance(value, bytes): 1013 return _Cbor(_cbor_write_intlike(len(value), 2) + value) 1016 return _Cbor(b'\xF4') 1019 return _Cbor(b'\xF5') 1022 return _Cbor(b'\xF6') 1024 if isinstance(value, (list, tuple)): 1025 cbor = [_cbor_write_intlike(len(value), 4)] 1027 cbor.append(_py_to_cbor(val, type_converter)) 1028 return _Cbor(b''.join(cbor)) 1030 if isinstance(value, dict): 1031 cbor = [_cbor_write_intlike(len(value), 5)] 1032 for key, val in sorted(value.items()): 1033 if not isinstance(key, str): 1034 raise TypeError('dict keys must be strings') 1035 cbor.append(_py_to_cbor(key, type_converter)) 1036 cbor.append(_py_to_cbor(val, type_converter)) 1037 return _Cbor(b''.join(cbor)) 1039 if type_converter is not None: 1040 return _py_to_cbor(type_converter(value)) 1042 raise TypeError('unsupported type for conversion to cbor: %r' % (value,)) 1045 class NotWellFormed(ValueError): 1046 """Exception class for well-formedness checks.""" 1048 def __init__(self, msg): 1049 super().__init__('not well-formed: ' + str(msg)) 1053 """Base class for nodes.""" 1055 __slots__ = ['_annot'] 1061 def __getitem__(self, key): 1062 """Returns the annotation object with the specified key, or raises 1063 KeyError if not found.""" 1064 if not isinstance(key, str): 1065 raise TypeError('indexing a node with something other than an ' 1066 'annotation key string') 1067 return self._annot[key] 1069 def __setitem__(self, key, val): 1070 """Assigns the annotation object with the specified key.""" 1071 if not isinstance(key, str): 1072 raise TypeError('indexing a node with something other than an ' 1073 'annotation key string') 1074 self._annot[key] = val 1076 def __delitem__(self, key): 1077 """Deletes the annotation object with the specified key.""" 1078 if not isinstance(key, str): 1079 raise TypeError('indexing a node with something other than an ' 1080 'annotation key string') 1081 del self._annot[key] 1083 def __contains__(self, key): 1084 """Returns whether an annotation exists for the specified key.""" 1085 return key in self._annot 1088 def find_reachable(self, id_map=None): 1089 """Returns a dictionary mapping Python id() values to stable sequence 1090 numbers for all nodes in the tree rooted at this node. If id_map is 1091 specified, found nodes are appended to it. Note that this is overridden 1092 by the actual node class implementations; this base function does very 1098 def check_complete(self, id_map=None): 1099 """Raises NotWellFormed if the tree rooted at this node is not 1100 well-formed. If id_map is specified, this tree is only a subtree in the 1101 context of a larger tree, and id_map must be a dict mapping from Python 1102 id() codes to tree indices for all reachable nodes. Note that this is 1103 overridden by the actual node class implementations; this base function 1104 always raises an exception.""" 1105 raise NotWellFormed('found node of abstract type ' + type(self).__name__) 1107 def check_well_formed(self): 1108 """Checks whether the tree starting at this node is well-formed. That 1111 - all One, Link, and Many edges have (at least) one entry; 1112 - all the One entries internally stored by Any/Many have an entry; 1113 - all Link and filled OptLink nodes link to a node that's reachable 1115 - the nodes referred to be One/Maybe only appear once in the tree 1116 (except through links). 1118 If it isn't well-formed, a NotWellFormed is thrown.""" 1119 self.check_complete() 1121 def is_well_formed(self): 1122 """Returns whether the tree starting at this node is well-formed. That 1125 - all One, Link, and Many edges have (at least) one entry; 1126 - all the One entries internally stored by Any/Many have an entry; 1127 - all Link and filled OptLink nodes link to a node that's reachable 1129 - the nodes referred to be One/Maybe only appear once in the tree 1130 (except through links).""" 1132 self.check_well_formed() 1134 except NotWellFormed: 1138 """Returns a shallow copy of this node. Note that this is overridden by 1139 the actual node class implementations; this base function always raises 1141 raise TypeError('can\'t copy node of abstract type ' + type(self).__name__) 1144 """Returns a deep copy of this node. Note that this is overridden by 1145 the actual node class implementations; this base function always raises 1147 raise TypeError('can\'t clone node of abstract type ' + type(self).__name__) 1150 def deserialize(cls, cbor): 1151 """Attempts to deserialize the given cbor object (either as bytes or as 1152 its Python primitive representation) into a node of this type.""" 1153 if isinstance(cbor, bytes): 1154 cbor = _cbor_to_py(cbor) 1157 root = cls._deserialize(cbor, seq_to_ob, links) 1158 for link_setter, seq in links: 1159 ob = seq_to_ob.get(seq, None) 1161 raise ValueError('found link to nonexistent object') 1165 def serialize(self): 1166 """Serializes this node into its cbor representation in the form of a 1168 id_map = self.find_reachable() 1169 self.check_complete(id_map) 1170 return _py_to_cbor(self._serialize(id_map)) 1173 def _deserialize(cbor, seq_to_ob, links): 1174 if not isinstance(cbor, dict): 1175 raise TypeError('node description object must be a dict') 1176 typ = cbor.get('@t', None) 1178 raise ValueError('type (@t) field is missing from node serialization') 1179 node_type = _typemap.get(cbor.get('@t'), None) 1180 if node_type is None: 1181 raise ValueError('unknown node type (@t): ' + str(cbor.get('@t'))) 1182 return node_type._deserialize(cbor, seq_to_ob, links) 1185 @functools.total_ordering 1186 class _Multiple(object): 1187 """Base class for the Any* and Many* edge helper classes. Inheriting 1188 classes must set the class constant _T to the node type they are made 1193 def __init__(self, *args, **kwargs): 1195 self._l = list(*args, **kwargs) 1196 for idx, val in enumerate(self._l): 1197 if not isinstance(val, self._T): 1199 'object {!r} at index {:d} is not an instance of {!r}' 1200 .format(val, idx, self._T)) 1203 return '{}({!r})'.format(type(self).__name__, self._l) 1206 return self.__class__(map(lambda node: node.clone(), self._l)) 1211 def __getitem__(self, idx): 1214 def __setitem__(self, idx, val): 1215 if not isinstance(val, self._T): 1217 'object {!r} is not an instance of {!r}' 1218 .format(val, idx, self._T)) 1221 def __delitem__(self, idx): 1225 return iter(self._l) 1227 def __reversed__(self): 1228 return reversed(self._l) 1230 def __contains__(self, val): 1231 return val in self._l 1233 def append(self, val): 1234 if not isinstance(val, self._T): 1236 'object {!r} is not an instance of {!r}' 1237 .format(val, self._T)) 1240 def extend(self, iterable): 1241 for val in iterable: 1244 def insert(self, idx, val): 1245 if not isinstance(val, self._T): 1247 'object {!r} is not an instance of {!r}' 1248 .format(val, self._T)) 1249 self._l.insert(idx, val) 1251 def remote(self, val): 1254 def pop(self, idx=-1): 1255 return self._l.pop(idx) 1260 def idx(self, val, start=0, end=-1): 1261 return self._l.idx(val, start, end) 1263 def count(self, val): 1264 return self._l.count(val) 1266 def sort(self, key=None, reverse=False): 1267 self._l.sort(key=key, reverse=reverse) 1273 return self.__class__(self) 1275 def __eq__(self, other): 1276 if not isinstance(other, _Multiple): 1278 return self._l == other._l 1280 def __lt__(self, other): 1281 return self._l < other._l 1283 def __iadd__(self, other): 1286 def __add__(self, other): 1291 def __imul__(self, other): 1294 def __mul__(self, other): 1299 def __rmul__(self, other): 1305 class MultiNode(_Multiple): 1306 """Wrapper for an edge with multiple Node objects.""" 1312 """Attempts to clone the given object by calling its clone() method, if it 1314 if hasattr(obj, 'clone'): 1322 std::unordered_set<std::string> generated;
1323 for (
auto node : nodes) {
1324 if (generated.count(node->snake_case_name)) {
1327 auto ancestors =
Nodes();
1329 ancestors.push_back(node);
1330 node = node->parent;
1332 for (
auto node_it = ancestors.rbegin(); node_it != ancestors.rend(); node_it++) {
1334 if (generated.count(node->snake_case_name)) {
1337 generated.insert(node->snake_case_name);
std::vector< Field > all_fields() const
Gathers all child nodes, including those in parent classes.
Header file for tree-gen-python.cpp.
std::vector< std::shared_ptr< Node > > Nodes
List of nodes.
Represents a type of AST node.
Struct containing everything needed for a complete specification.
std::vector< std::weak_ptr< Node > > derived
Node types derived from this one.
Namespace for the tree-gen program.
std::string title_case_name
Name in TitleCase.
Link to zero or one nodes elsewhere in the tree.
EdgeType
Types of edges between nodes and primitives.
void generate(const std::string &python_filename, Specification &specification)
Generates the complete Python code.
std::string doc
Class documentation.
void generate_deserialize_mux(std::ofstream &output, Node &node)
Recursive function to print a muxing if statement for all node classes derived from the given node cl...
std::vector< std::string > python_includes
The include statements to stick at the top of the Python file.
std::vector< Field > fields
Child nodes.
std::string python_doc
Python file documentation.
std::string py_deserialize_fn
Deserialization function to use when serializing primitives in the Python domain, using ...
std::shared_ptr< Node > parent
The node type this is derived from, if any.
Nodes nodes
All the nodes.
void format_doc(std::ofstream &stream, const std::string &doc, const std::string &indent="")
Formats a Python docstring.
std::string py_serialize_fn
Serialization function to use when serializing primitives in the Python domain, using ...
void generate_node_class(std::ofstream &output, Specification &spec, Node &node)
Generates the class for the given node.
Link to exactly one node elsewhere in the tree.