error_table.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (c) 2022 Project CHIP Authors
  4. # All rights reserved.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. #
  18. #
  19. # Execute as `python scripts/error_table.py > docs/ERROR_CODES.md` from root of repos
  20. #
  21. # This script uses heuristics scraping of the headers to generate nice tables
  22. #
  23. import re
  24. from dataclasses import dataclass
  25. from enum import IntEnum
  26. from operator import attrgetter
  27. from pathlib import Path
  28. @dataclass
  29. class ErrorCode:
  30. code: int
  31. name: str
  32. description: str
  33. @dataclass
  34. class ErrorDescriptor:
  35. section: str
  36. code_range: int
  37. # `macro_regex` needs to have `code` and `name` named groups.
  38. macro_regex: str
  39. include_description: bool = False
  40. class CommentState(IntEnum):
  41. WAIT_START_COMMENT = 0
  42. ACCUMULATE_COMMENT = 1
  43. class ErrorCodeLoader:
  44. def __init__(self) -> None:
  45. self.reset()
  46. def reset(self):
  47. self._comment_state = CommentState.WAIT_START_COMMENT
  48. self._last_comment = []
  49. self._error_codes: list[ErrorCode] = []
  50. def _process_comment_extract(self, line):
  51. if self._comment_state == CommentState.WAIT_START_COMMENT:
  52. if "/**" in line:
  53. self._last_comment = []
  54. self._comment_state = CommentState.ACCUMULATE_COMMENT
  55. elif self._comment_state == CommentState.ACCUMULATE_COMMENT:
  56. if "*/" in line:
  57. self._comment_state = CommentState.WAIT_START_COMMENT
  58. else:
  59. self._last_comment.append(line)
  60. def _process_error_extract(self, descriptor: ErrorDescriptor, line: str):
  61. match = re.search(descriptor.macro_regex, line)
  62. if match is None:
  63. return
  64. last_comment = "".join(self._last_comment).replace(" ", " ").replace(" ", " ").replace("*", "").replace(".", "")
  65. last_comment = last_comment.split("@brief")[-1].strip()
  66. code = int(match.group("code"), 0)
  67. code = descriptor.code_range | code
  68. name = match.group("name")
  69. description = last_comment if descriptor.include_description else ""
  70. self._error_codes.append(ErrorCode(code=code, name=name, description=description))
  71. def load_error_header(self, filename: Path, descriptor: ErrorDescriptor) -> list[ErrorCode]:
  72. self.reset()
  73. lines = filename.read_text().split("\n")
  74. for line in lines:
  75. line = line.strip()
  76. self._process_comment_extract(line)
  77. self._process_error_extract(descriptor, line)
  78. return self._error_codes
  79. def get_section_title(section: str) -> tuple[str, str]:
  80. markdown_title = f"{section} errors"
  81. anchor_name = f'#{markdown_title.lower().replace(" ","-").replace(".","-")}'
  82. return markdown_title, anchor_name
  83. def dump_table(header_path: Path, descriptor: ErrorDescriptor):
  84. loader = ErrorCodeLoader()
  85. codes_for_section = loader.load_error_header(header_path, descriptor)
  86. markdown_title, _ = get_section_title(descriptor.section)
  87. print(f"## {markdown_title}")
  88. print()
  89. if descriptor.include_description:
  90. print("| Decimal | Hex | Name | Description |")
  91. print("| --- | --- | --- | --- |")
  92. else:
  93. print("| Decimal | Hex | Name |")
  94. print("| --- | --- | --- |")
  95. for code in sorted(codes_for_section, key=attrgetter("code")):
  96. if descriptor.include_description:
  97. print(f"| {code.code} | 0x{code.code:02X} | `{code.name}` | {code.description} |")
  98. else:
  99. print(f"| {code.code} | 0x{code.code:02X} | `{code.name}` |")
  100. print()
  101. def main():
  102. descriptors = {
  103. "src/lib/core/CHIPError.h": ErrorDescriptor(section="SDK Core", code_range=0x000, macro_regex=r"^#define\s+(?P<name>[_A-Z0-9]+)\s+CHIP(_CORE)?_ERROR[(](?P<code>(0x[a-fA-F0-9]+)|\d+)[)]"),
  104. "src/inet/InetError.h": ErrorDescriptor(section="SDK Inet Layer", code_range=0x100, macro_regex=r"^#define\s+(?P<name>[_A-Z0-9]+)\s+CHIP_INET_ERROR[(](?P<code>(0x[a-fA-F0-9]+)|\d+)[)]"),
  105. "src/include/platform/CHIPDeviceError.h": ErrorDescriptor(section="SDK Device Layer", code_range=0x200, macro_regex=r"^#define\s+(?P<name>[_A-Z0-9]+)\s+CHIP_DEVICE_ERROR[(](?P<code>(0x[a-fA-F0-9]+)|\d+)[)]"),
  106. "src/lib/asn1/ASN1Error.h": ErrorDescriptor(section="ASN1 Layer", code_range=0x300, macro_regex=r"^#define\s+(?P<name>[_A-Z0-9]+)\s+CHIP_ASN1_ERROR[(](?P<code>(0x[a-fA-F0-9]+)|\d+)[)]"),
  107. "src/ble/BleError.h": ErrorDescriptor(section="BLE Layer", code_range=0x400, macro_regex=r"^#define\s+(?P<name>[_A-Z0-9]+)\s+CHIP_BLE_ERROR[(](?P<code>(0x[a-fA-F0-9]+)|\d+)[)]"),
  108. "src/protocols/interaction_model/StatusCodeList.h": ErrorDescriptor(section="IM Global errors", code_range=0x500, macro_regex=r"^CHIP_IM_STATUS_CODE[(][A-Za-z0-9_]+\s*,\s*(?P<name>[A-Z0-9_]+)\s*,\s*(?P<code>(0x[a-fA-F0-9]+)|\d+)[)]"),
  109. }
  110. print("# Matter SDK `CHIP_ERROR` enums values")
  111. print()
  112. print("This file was **AUTOMATICALLY** generated by `python scripts/error_table.py > docs/ERROR_CODES.md`.")
  113. print("DO NOT EDIT BY HAND!")
  114. print()
  115. print("## Table of contents")
  116. for descriptor in descriptors.values():
  117. markdown_title, anchor_name = get_section_title(descriptor.section)
  118. print(f"- [{markdown_title}: range `0x{descriptor.code_range:03X}..0x{descriptor.code_range | 0xFF:03X}`]({anchor_name})")
  119. print()
  120. for filename, descriptor in descriptors.items():
  121. dump_table(Path(filename), descriptor)
  122. if __name__ == "__main__":
  123. main()