gen_rtti.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. #!/usr/bin/env python3
  2. """Generates C++ header to support LLVM-style RTTI for a class hierarchy.
  3. # Background
  4. A C++ class hierarchy supported by this script consists of *abstract* classes,
  5. which can be inherited from but can't be instantiated, and *concrete* classes,
  6. which can be instantiated but can't be inherited from. Classes can inherit from
  7. at most one other class in the hierarchy; a class that doesn't inherit from
  8. any other class is called a *root* class, and it cannot be concrete.
  9. # Input format
  10. This script's input file declares every class in the hierarchy, and specifies
  11. the parent of each non-root class. The input file consists of comment lines
  12. starting with `#`, whitespace lines, and one `;`-terminated line for each class.
  13. The core of a line is `class` followed by the class name. `class` can be
  14. prefixed with `root` or `abstract` to specify the corresponding kind of class;
  15. if there is no prefix, the class is concrete. If the class is not a root class,
  16. the name is followed by `:` and then the name of the class it inherits from. A
  17. class cannot inherit from a class defined later in the file.
  18. For example:
  19. root class R;
  20. abstract class A : R;
  21. abstract class B : R;
  22. class C : A;
  23. class D : B;
  24. class E : A;
  25. # Output
  26. For each abstract class `Foo`, the generated header file will contain
  27. `enum class FooKind`, which has an enumerator for each concrete class derived
  28. from `Foo`, with a name that matches the concrete class name.
  29. For each non-root class `Foo` whose root class is `Root`, the generated header
  30. file will also contain a function `bool InheritsFromFoo(RootKind kind)`, which
  31. returns true if the value of `kind` corresponds to a class that is derived from
  32. `Foo`. This function can be used to implement `Foo::classof`.
  33. All enumerators that represent the same concrete class will have the same
  34. numeric value, so you can use `static_cast` to convert between the enum types
  35. for different classes that have a common root, so long as the enumerator value
  36. is present in both types. As a result, `InheritsFromFoo` can be used to
  37. determine whether casting to `FooKind` is safe.
  38. """
  39. __copyright__ = """
  40. Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  41. Exceptions. See /LICENSE for license information.
  42. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  43. """
  44. import enum
  45. import re
  46. import sys
  47. class Class:
  48. """Metadata about a class from the input file.
  49. This consists of information
  50. Attributes set at construction:
  51. name: The class name.
  52. kind: The class kind (root, abstract, or concrete)
  53. ancestors: A list of Class objects representing the class's ancestors,
  54. starting with the root and ending with the current class's parent.
  55. _children: A list of Class objects representing the classes that are
  56. derived directly from this one.
  57. Attributes set by Finalize():
  58. id (CONCRETE only): The class's numeric ID, which will become its
  59. enumerator value in the generated C++ code.
  60. id_range (ROOT and ABSTRACT only): A pair such that a Class
  61. object `c` represents a concrete class derived from `self` if and only
  62. if c.id >= self.id_range[0] and c.id < self.id_range[1].
  63. leaves (ROOT only): A list of all concrete classes derived from this one,
  64. indexed by their IDs.
  65. """
  66. Kind = enum.Enum("Kind", "ROOT ABSTRACT CONCRETE")
  67. def __init__(self, name, kind, parent):
  68. self.name = name
  69. self.kind = kind
  70. assert (parent is None) == (kind == Class.Kind.ROOT)
  71. if parent is None:
  72. self.ancestors = []
  73. else:
  74. self.ancestors = parent.ancestors + [parent]
  75. if self.kind == Class.Kind.ROOT:
  76. self.leaves = []
  77. self.id_range = None
  78. elif self.kind == Class.Kind.ABSTRACT:
  79. self.id_range = None
  80. else:
  81. self.id = None
  82. if self.kind != Class.Kind.CONCRETE:
  83. self._children = []
  84. if parent:
  85. parent._children.append(self)
  86. def Parent(self):
  87. """Returns this Class's parent."""
  88. return self.ancestors[-1]
  89. def Root(self):
  90. """Returns the root Class of this hierarchy."""
  91. if self.kind == Class.Kind.ROOT:
  92. return self
  93. else:
  94. return self.ancestors[0]
  95. def _RegisterLeaf(self, leaf):
  96. """Records that `leaf` is derived from self.
  97. Also recursively updates the parent of self. leaf.id must already be
  98. populated, and leaves must be registered in order of ID. This operation
  99. is idempotent."""
  100. already_visited = False
  101. if self.kind == Class.Kind.ROOT:
  102. if leaf.id == len(self.leaves):
  103. self.leaves.append(leaf)
  104. else:
  105. assert leaf.id + 1 == len(self.leaves)
  106. assert self.leaves[leaf.id] == leaf
  107. already_visited = True
  108. if self.kind in [Class.Kind.ROOT, Class.Kind.ABSTRACT]:
  109. if self not in leaf.ancestors:
  110. sys.exit(
  111. f"{leaf.name} derived from {self.name}, but has a"
  112. + " different root"
  113. )
  114. if not self.id_range:
  115. self.id_range = (leaf.id, leaf.id + 1)
  116. elif self.id_range[1] == leaf.id:
  117. self.id_range = (self.id_range[0], self.id_range[1] + 1)
  118. else:
  119. assert self.id_range[1] == leaf.id + 1
  120. already_visited = True
  121. if not already_visited:
  122. if self.kind != Class.Kind.ROOT:
  123. self.Parent()._RegisterLeaf(leaf)
  124. def Finalize(self):
  125. """Populates additional attributes for `self` and derived Classes.
  126. Each Class can only be finalized once, after which no additional Classes
  127. can be derived from it.
  128. """
  129. if self.kind == Class.Kind.CONCRETE:
  130. self.id = len(self.Root().leaves)
  131. self._RegisterLeaf(self)
  132. else:
  133. for child in self._children:
  134. child.Finalize()
  135. _LINE_PATTERN = r"""(?P<prefix> \w*) \s*
  136. class \s+
  137. (?P<name> \w+)
  138. (?: \s*:\s* (?P<parent> \w+)
  139. )?
  140. ;$"""
  141. def main():
  142. input_filename = sys.argv[1]
  143. with open(input_filename) as file:
  144. lines = file.readlines()
  145. classes = dict()
  146. for line_num, line in enumerate(lines, 1):
  147. if line.startswith("#") or line.strip() == "":
  148. continue
  149. match_result = re.match(_LINE_PATTERN, line.strip(), re.VERBOSE)
  150. if not match_result:
  151. sys.exit(f"Invalid format on line {line_num}")
  152. prefix = match_result.group("prefix")
  153. if prefix == "":
  154. kind = Class.Kind.CONCRETE
  155. elif prefix == "root":
  156. kind = Class.Kind.ROOT
  157. elif prefix == "abstract":
  158. kind = Class.Kind.ABSTRACT
  159. else:
  160. sys.exit(f"Unrecognized class prefix '{prefix}' on line {line_num}")
  161. parent = None
  162. if match_result.group("parent"):
  163. if kind == Class.Kind.ROOT:
  164. sys.exit(f"Root class cannot have parent on line {line_num}")
  165. parent_name = match_result.group("parent")
  166. parent = classes[parent_name]
  167. if not parent:
  168. sys.exit(f"Unknown class '{parent_name}' on line {line_num}")
  169. if parent.kind == Class.Kind.CONCRETE:
  170. sys.exit(f"{parent.name} cannot be a parent on line {line_num}")
  171. else:
  172. if kind != Class.Kind.ROOT:
  173. sys.exit(
  174. f"Non-root class must have a parent on line {line_num}"
  175. )
  176. classes[match_result.group("name")] = Class(
  177. match_result.group("name"), kind, parent
  178. )
  179. for node in classes.values():
  180. if node.kind == Class.Kind.ROOT:
  181. node.Finalize()
  182. print(
  183. f"// Generated from {input_filename} by"
  184. + " executable_semantics/gen_rtti.py\n"
  185. )
  186. guard_macro = (
  187. input_filename.upper().translate(str.maketrans({"/": "_", ".": "_"}))
  188. + "_"
  189. )
  190. print(f"#ifndef {guard_macro}")
  191. print(f"#define {guard_macro}")
  192. print("\nnamespace Carbon {\n")
  193. for node in classes.values():
  194. if node.kind != Class.Kind.CONCRETE:
  195. ids = range(node.id_range[0], node.id_range[1])
  196. print(f"enum class {node.name}Kind {{")
  197. for id in ids:
  198. print(f" {node.Root().leaves[id].name} = {id},")
  199. print("};\n")
  200. if node.kind in [Class.Kind.ABSTRACT, Class.Kind.CONCRETE]:
  201. print(
  202. f"inline bool InheritsFrom{node.name}({node.Root().name}Kind"
  203. + " kind) {"
  204. )
  205. if node.kind == Class.Kind.ABSTRACT:
  206. if node.id_range[0] == node.id_range[1]:
  207. print(" return false;")
  208. else:
  209. range_begin = node.Root().leaves[node.id_range[0]].name
  210. print(
  211. f" return kind >= {node.Root().name}Kind"
  212. + f"::{range_begin}"
  213. )
  214. if node.id_range[1] < len(node.Root().leaves):
  215. range_end = node.Root().leaves[node.id_range[1]].name
  216. print(
  217. f" && kind < {node.Root().name}Kind"
  218. + f"::{range_end}"
  219. )
  220. print(" ;")
  221. elif node.kind == Class.Kind.CONCRETE:
  222. print(
  223. f" return kind == {node.Root().name}Kind::{node.name};"
  224. )
  225. print("}\n")
  226. print("} // namespace Carbon\n")
  227. print(f"#endif // {guard_macro}")
  228. if __name__ == "__main__":
  229. main()