blob: 46096e84a03afd5fdd544579fc54acc445827a34 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (c) 2022 Project CHIP Authors
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# Execute as `python scripts/error_table.py > docs/ERROR_CODES.md` from root of repos
#
# This script uses heuristics scraping of the headers to generate nice tables
#
import re
from dataclasses import dataclass
from enum import IntEnum
from pathlib import Path
from operator import attrgetter
from xml.etree.ElementInclude import include
@dataclass
class ErrorCode:
code: int
name: str
description: str
@dataclass
class ErrorDescriptor:
section: str
code_range: int
# `macro_regex` needs to have `code` and `name` named groups.
macro_regex: str
include_description: bool = False
class CommentState(IntEnum):
WAIT_START_COMMENT = 0
ACCUMULATE_COMMENT = 1
class ErrorCodeLoader:
def __init__(self) -> None:
self.reset()
def reset(self):
self._comment_state = CommentState.WAIT_START_COMMENT
self._last_comment = []
self._error_codes: list[ErrorCode] = []
def _process_comment_extract(self, line):
if self._comment_state == CommentState.WAIT_START_COMMENT:
if "/**" in line:
self._last_comment = []
self._comment_state = CommentState.ACCUMULATE_COMMENT
elif self._comment_state == CommentState.ACCUMULATE_COMMENT:
if "*/" in line:
self._comment_state = CommentState.WAIT_START_COMMENT
else:
self._last_comment.append(line)
def _process_error_extract(self, descriptor: ErrorDescriptor, line: str):
match = re.search(descriptor.macro_regex, line)
if match is None:
return
last_comment = "".join(self._last_comment).replace(" ", " ").replace(" ", " ").replace("*", "").replace(".", "")
last_comment = last_comment.split("@brief")[-1].strip()
code = int(match.group("code"), 0)
code = descriptor.code_range | code
name = match.group("name")
description = last_comment if descriptor.include_description else ""
self._error_codes.append(ErrorCode(code=code, name=name, description=description))
def load_error_header(self, filename: Path, descriptor: ErrorDescriptor) -> list[ErrorCode]:
self.reset()
lines = filename.read_text().split("\n")
for line in lines:
line = line.strip()
self._process_comment_extract(line)
self._process_error_extract(descriptor, line)
return self._error_codes
def get_section_title(section: str) -> tuple[str, str]:
markdown_title = f"{section} errors"
anchor_name = f'#{markdown_title.lower().replace(" ","-").replace(".","-")}'
return markdown_title, anchor_name
def dump_table(header_path: Path, descriptor: ErrorDescriptor):
loader = ErrorCodeLoader()
codes_for_section = loader.load_error_header(header_path, descriptor)
markdown_title, _ = get_section_title(descriptor.section)
print(f"## {markdown_title}")
print()
if descriptor.include_description:
print("| Decimal | Hex | Name | Description |")
print("| --- | --- | --- | --- |")
else:
print("| Decimal | Hex | Name |")
print("| --- | --- | --- |")
for code in sorted(codes_for_section, key=attrgetter("code")):
if descriptor.include_description:
print(f"| {code.code} | 0x{code.code:02X} | `{code.name}` | {code.description} |")
else:
print(f"| {code.code} | 0x{code.code:02X} | `{code.name}` |")
print()
def main():
descriptors = {
"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+)[)]"),
"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+)[)]"),
"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+)[)]"),
"src/lib/asn1/ASN1Error.h": ErrorDescriptor(section="ASN.1 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+)[)]"),
"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+)[)]"),
"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+)[)]"),
}
print()
print("[//]: # (start_ignore_spellcheck)")
print()
print("# Matter SDK `CHIP_ERROR` enums values")
print()
print("This file was **AUTOMATICALLY** generated by `python scripts/error_table.py > docs/ERROR_CODES.md`.")
print("DO NOT EDIT BY HAND!")
print()
print("## Table of contents")
for descriptor in descriptors.values():
markdown_title, anchor_name = get_section_title(descriptor.section)
print(f"- [{markdown_title}: range `0x{descriptor.code_range:03X}..0x{descriptor.code_range | 0xFF:03X}`]({anchor_name})")
print()
for filename, descriptor in descriptors.items():
dump_table(Path(filename), descriptor)
print("[//]: # (end_ignore_spellcheck)")
if __name__ == "__main__":
main()