pretty_printing.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. #!/usr/bin/env python
  2. # Copyright 2019 Google
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import textwrap
  16. from google.protobuf.descriptor_pb2 import FieldDescriptorProto
  17. import nanopb_generator as nanopb
  18. LINE_WIDTH = 80
  19. def _indent(level):
  20. """Returns leading whitespace corresponding to the given indentation `level`.
  21. """
  22. indent_per_level = 4
  23. return ' ' * (indent_per_level * level)
  24. class FilePrettyPrinting:
  25. """Allows generating pretty-printing support for a proto file.
  26. Because two files (header and source) are generated for each proto file, this
  27. class doesn't generate the necessary code directly. Use `messages` and `enums`
  28. properties to generate the declarations and definitions separately and insert
  29. them to the appropriate locations within the generated files.
  30. """
  31. def __init__(self, file_desc):
  32. """Args:
  33. file_desc: nanopb.ProtoFile describing this proto file.
  34. """
  35. self.messages = [MessagePrettyPrinting(m) for m in
  36. file_desc.messages]
  37. self.enums = [EnumPrettyPrinting(e) for e in file_desc.enums]
  38. class MessagePrettyPrinting:
  39. """Generates pretty-printing support for a message.
  40. Adds the following member function to the Nanopb generated class:
  41. std::string ToString(int indent = 0) const;
  42. Because the generated code is split between a header and a source, the
  43. declaration and definition are generated separately. Definition has the
  44. out-of-class form.
  45. The output of the generated function represents the proto in its text form,
  46. suitable for parsing, and with proper indentation. The top-level message
  47. additionally displays message name and the value of the pointer to `this`.
  48. """
  49. def __init__(self, message_desc):
  50. """Args:
  51. message_desc: nanopb.Message describing this message.
  52. """
  53. self.full_classname = str(message_desc.name)
  54. self._short_classname = message_desc.name.parts[-1]
  55. self._fields = [self._create_field(f, message_desc) for f in
  56. message_desc.fields]
  57. # Make sure fields are printed ordered by tag, for consistency with official
  58. # proto libraries.
  59. self._fields.sort(key=lambda f: f.tag)
  60. def _create_field(self, field_desc, message_desc):
  61. if isinstance(field_desc, nanopb.OneOf):
  62. return OneOfPrettyPrinting(field_desc, message_desc)
  63. else:
  64. return FieldPrettyPrinting(field_desc, message_desc)
  65. def generate_declaration(self):
  66. """Generates the declaration of a `ToString()` member function.
  67. """
  68. return '\n' + _indent(1) + 'std::string ToString(int indent = 0) const;\n'
  69. def generate_definition(self):
  70. """Generates the out-of-class definition of a `ToString()` member function.
  71. """
  72. result = '''\
  73. std::string %s::ToString(int indent) const {
  74. std::string header = PrintHeader(indent, "%s", this);
  75. std::string result;\n\n''' % (
  76. self.full_classname, self._short_classname)
  77. for field in self._fields:
  78. result += str(field)
  79. can_be_empty = all(f.is_primitive or f.is_repeated for f in self._fields)
  80. if can_be_empty:
  81. result += '''
  82. bool is_root = indent == 0;
  83. if (!result.empty() || is_root) {
  84. std::string tail = PrintTail(indent);
  85. return header + result + tail;
  86. } else {
  87. return "";
  88. }
  89. }\n\n'''
  90. else:
  91. result += '''
  92. std::string tail = PrintTail(indent);
  93. return header + result + tail;
  94. }\n\n'''
  95. return result
  96. class FieldPrettyPrinting:
  97. """Generates pretty-printing support for a field.
  98. The generated C++ code will output the field name and value; the output format
  99. is the proto text format, suitable for parsing. Unset fields are not printed.
  100. Repeated and optional fields are supported.
  101. Oneofs are not supported; use `OneOfPrettyPrinting` instead.
  102. The actual output will be delegated to a C++ function called
  103. `PrintPrimitiveField()`, `PrintEnumField()`, or `PrintMessageField()`,
  104. according to the field type; the function is expected to be visible at the
  105. point of definition.
  106. """
  107. def __init__(self, field_desc, message_desc):
  108. """Args:
  109. field_desc: nanopb.Field describing this field.
  110. message_desc: nanopb.Message describing the message containing this field.
  111. """
  112. self.name = field_desc.name
  113. self.tag = field_desc.tag
  114. self.is_optional = (field_desc.rules == 'OPTIONAL' and field_desc.allocation == 'STATIC')
  115. self.is_repeated = field_desc.rules == 'REPEATED'
  116. self.is_primitive = field_desc.pbtype != 'MESSAGE'
  117. self.is_enum = field_desc.pbtype in ['ENUM', 'UENUM']
  118. def __str__(self):
  119. """Generates a C++ statement that prints the field according to its type.
  120. """
  121. if self.is_optional:
  122. return self._generate_for_optional()
  123. elif self.is_repeated:
  124. return self._generate_for_repeated()
  125. else:
  126. return self._generate_for_leaf()
  127. def _generate_for_repeated(self):
  128. """Generates a C++ statement that prints the repeated field, if non-empty.
  129. """
  130. count = self.name + '_count'
  131. result = '''\
  132. for (pb_size_t i = 0; i != %s; ++i) {\n''' % count
  133. # If the repeated field is non-empty, print all its members, even if they are
  134. # zero or empty (otherwise, an array of zeroes would be indistinguishable from
  135. # an empty array).
  136. result += self._generate_for_leaf(indent=2, always_print=True)
  137. result += '''\
  138. }\n'''
  139. return result
  140. def _generate_for_optional(self):
  141. """Generates a C++ statement that prints the optional field, if set.
  142. """
  143. name = self.name
  144. result = '''\
  145. if (has_%s) {\n''' % name
  146. # If an optional field is set, always print the value, even if it's zero or
  147. # empty.
  148. result += self._generate_for_leaf(indent=2, always_print=True)
  149. result += '''\
  150. }\n'''
  151. return result
  152. def _generate_for_leaf(self, indent=1, always_print=False, parent_oneof=None):
  153. """Generates a C++ statement that prints the "leaf" field.
  154. "Leaf" is to indicate that this function is non-recursive. If the field is
  155. a message type, the generated code will delegate printing to its
  156. `ToString()` member function.
  157. Args:
  158. indent: The indentation level of the generated statement.
  159. always_print: If `False`, the field will not be printed if it has the
  160. default value, or for a message, if each field it contains has the
  161. default value.
  162. parent_oneof: If the field is a member of a oneof, a reference to the
  163. corresponding `OneOfPrettyPrinting`
  164. """
  165. always_print = 'true' if always_print else 'false'
  166. display_name = self.name
  167. if self.is_primitive:
  168. display_name += ':'
  169. cc_name = self._get_cc_name(parent_oneof)
  170. function_name = self._get_printer_function_name()
  171. return self._generate(indent, display_name, cc_name, function_name,
  172. always_print)
  173. def _get_cc_name(self, parent_oneof):
  174. """Gets the name of the field to use in the generated C++ code:
  175. - for repeated fields, appends indexing in the form of `[i]`;
  176. - for named union members, prepends the name of the enclosing union;
  177. - ensures the name isn't a C++ keyword by appending an underscore
  178. (currently, only for keyword `delete`).
  179. """
  180. cc_name = self.name
  181. # If a proto field is named `delete`, it is renamed to `delete_` by our script
  182. # because `delete` is a keyword in C++. Unfortunately, the renaming mechanism
  183. # runs separately from generating pretty printers; consequently, when pretty
  184. # printers are being generated, all proto fields still have their unmodified
  185. # names.
  186. if cc_name == 'delete':
  187. cc_name = 'delete_'
  188. if self.is_repeated:
  189. cc_name += '[i]'
  190. if parent_oneof and not parent_oneof.is_anonymous:
  191. cc_name = parent_oneof.name + '.' + cc_name
  192. return cc_name
  193. def _get_printer_function_name(self):
  194. """Gets the name of the C++ function to delegate printing to.
  195. """
  196. if self.is_enum:
  197. return 'PrintEnumField'
  198. elif self.is_primitive:
  199. return 'PrintPrimitiveField'
  200. else:
  201. return 'PrintMessageField'
  202. def _generate(self, indent_level, display_name, cc_name, function_name,
  203. always_print):
  204. """Generates the C++ statement that prints the field.
  205. Args:
  206. indent_level: The indentation level of the generated statement.
  207. display_name: The name of the field to display in the output.
  208. cc_name: The name of the field to use in the generated C++ code; may
  209. differ from `display_name`.
  210. function_name: The C++ function to delegate printing the value to.
  211. always_print: Whether to print the field if it has its default value.
  212. """
  213. format_str = '%sresult += %s("%s ",%s%s, indent + 1, %s);\n'
  214. for maybe_linebreak in [' ', '\n' + _indent(indent_level + 1)]:
  215. args = (
  216. _indent(indent_level), function_name, display_name, maybe_linebreak,
  217. cc_name,
  218. always_print)
  219. result = format_str % args
  220. # Best-effort attempt to fit within the expected line width.
  221. if len(result) <= LINE_WIDTH:
  222. break
  223. return result
  224. class OneOfPrettyPrinting(FieldPrettyPrinting):
  225. """Generates pretty-printing support for a oneof field.
  226. This class represents the "whole" oneof, with all of its members nested, not
  227. a single oneof member.
  228. Note that all members of the oneof are nested (in `_fields` property).
  229. """
  230. def __init__(self, field_desc, message_desc):
  231. """Args:
  232. field_desc: nanopb.Field describing this oneof field.
  233. message_desc: nanopb.Message describing the message containing this field.
  234. """
  235. FieldPrettyPrinting.__init__(self, field_desc, message_desc)
  236. self._full_classname = str(message_desc.name)
  237. self._which = 'which_' + field_desc.name
  238. self.is_anonymous = field_desc.anonymous
  239. self._fields = [FieldPrettyPrinting(f, message_desc) for f in
  240. field_desc.fields]
  241. def __str__(self):
  242. """Generates a C++ statement that prints the oneof field, if it is set.
  243. """
  244. which = self._which
  245. result = '''\
  246. switch (%s) {\n''' % which
  247. for f in self._fields:
  248. tag_name = '%s_%s_tag' % (self._full_classname, f.name)
  249. result += '''\
  250. case %s:\n''' % tag_name
  251. # If oneof is set, always print that member, even if it's zero or empty.
  252. result += f._generate_for_leaf(indent=2, parent_oneof=self,
  253. always_print=True)
  254. result += '''\
  255. break;\n'''
  256. result += '''\
  257. }\n'''
  258. return result
  259. class EnumPrettyPrinting:
  260. """Generates pretty-printing support for an enumeration.
  261. Adds the following free function to the file:
  262. const char* EnumToString(SomeEnumType value);
  263. Because the generated code is split between a header and a source, the
  264. declaration and definition are generated separately.
  265. The output of the generated function represents the string value of the given
  266. enum constant. If the given value is not part of the enum, a string
  267. representing an error is returned.
  268. """
  269. def __init__(self, enum_desc):
  270. """Args:
  271. enum_desc: nanopb.Enum describing this enumeration.
  272. """
  273. self.name = str(enum_desc.names)
  274. self._members = [str(n) for n in enum_desc.value_longnames]
  275. def generate_declaration(self):
  276. """Generates the declaration of a `EnumToString()` free function.
  277. """
  278. # Best-effort attempt to fit within the expected line width.
  279. format_str = 'const char* EnumToString(%s%s value);\n'
  280. for maybe_linebreak in ['', '\n' + _indent(1)]:
  281. args = (maybe_linebreak, self.name)
  282. result = format_str % args
  283. # Best-effort attempt to fit within the expected line width.
  284. if len(result) <= LINE_WIDTH:
  285. break
  286. return result
  287. def generate_definition(self):
  288. """Generates the definition of a `EnumToString()` free function.
  289. """
  290. result = '''\
  291. const char* EnumToString(
  292. %s value) {
  293. switch (value) {\n''' % self.name
  294. for full_name in self._members:
  295. short_name = full_name.replace('%s_' % self.name, '')
  296. result += '''\
  297. case %s:
  298. return "%s";\n''' % (full_name, short_name)
  299. result += '''\
  300. }
  301. return "<unknown enum value>";
  302. }\n\n'''
  303. return result