| #!/usr/bin/env python3 | |
| # Copyright (c) 2023, Alliance for Open Media. All rights reserved | |
| ## | |
| # This source code is subject to the terms of the BSD 3-Clause Clear License and the | |
| # Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear License was | |
| # not distributed with this source code in the LICENSE file, you can obtain it | |
| # at aomedia.org/license/software-license/bsd-3-c-c/. If the Alliance for Open Media Patent | |
| # License 1.0 was not distributed with this source code in the PATENTS file, you | |
| # can obtain it at aomedia.org/license/patent-license/. | |
| ## | |
| """Creates enum value to name mapping header for AVM. | |
| Walks through the enum definitions in av1/common/enums.h and creates a header that | |
| maps individual enum variants to their (string) names. | |
| """ | |
| import argparse | |
| from collections.abc import Sequence | |
| import re | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--enums_header", action="append", required=True) | |
| parser.add_argument("--output", required=True) | |
| # AVM enum definitions start with an optional typedef, the enum keyword, and | |
| # then optionally `ATTRIBUTE_PACKED`. | |
| ENUM_START_REGEX = re.compile(r"(?:typedef )?enum (?:ATTRIBUTE_PACKED )?\{") | |
| # AVM enum definitions optionally end with the macro UENUM1BYTE or SENUM1BYTE | |
| # and the enum name. | |
| ENUM_END_REGEX = re.compile(r"} ([US]ENUM[12]BYTE\()?(\w+)(?(1)\)|);") | |
| # Some enum variants are defined based on another variant; we don't care about | |
| # these ones, and only want to handle the ones with unique values. These variants | |
| # typically appear as actual symbols in the stream; the others are defined just | |
| # for convenience to mark the start and endpoints of meaningful groups of enums, | |
| # e.g. INTRA_MODE_START / INTRA_MODE_END are used to mark which prediction modes | |
| # are intra (rather than inter). | |
| ENUM_VARIANT_REGEX = re.compile(r"(\w+)(=(\d+))?,") | |
| # String template for each enum mapping. | |
| ENUM_MAP_TEMPLATE = """static absl::flat_hash_map<int, std::string_view> k{name_camel}Map = {{""" | |
| # These preprocessor directives are replaced with dummy conditions to simplify | |
| # handling of their corresponding #endif. #ifdef/#ifndef are only used for | |
| # include guards and #ifdef __cplusplus, which we can ignore for the generated | |
| # header. | |
| SKIP_DIRECTIVES = ("#ifdef", "#ifndef") | |
| # These preprocessor directives are relevant for enum definitions, so pass them | |
| # through to the generated header as-is. Ignore all other preprocessor | |
| # directives. | |
| PASSTHROUGH_DIRECTIVES = ("#if ", "#elif", "#else", "#endif") | |
| # Template for the generated header. | |
| ENUM_HEADER_TEMPLATE = """ | |
| /* | |
| * Copyright (c) 2023, Alliance for Open Media. All rights reserved | |
| * | |
| * This source code is subject to the terms of the BSD 3-Clause Clear License | |
| * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear | |
| * License was not distributed with this source code in the LICENSE file, you | |
| * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. If the | |
| * Alliance for Open Media Patent License 1.0 was not distributed with this | |
| * source code in the PATENTS file, you can obtain it at | |
| * aomedia.org/license/patent-license/. | |
| */ | |
| // Auto-generated by generate_enum_mappings.py. | |
| #ifndef AOM_TOOLS_EXTRACT_PROTO_ENUM_MAPPINGS_H_ | |
| #define AOM_TOOLS_EXTRACT_PROTO_ENUM_MAPPINGS_H_ | |
| #include <string_view> | |
| #include "av1/common/av1_common_int.h" | |
| #include "av1/common/blockd.h" | |
| #include "av1/common/enums.h" | |
| #include "av1/common/filter.h" | |
| #include "av1/common/mv.h" | |
| #include "av1/decoder/accounting.h" | |
| #include "config/aom_config.h" | |
| #include "absl/container/flat_hash_map.h" | |
| {enum_definitions} | |
| #endif // AOM_TOOLS_EXTRACT_PROTO_ENUM_MAPPINGS_H_ | |
| """ | |
| def snake_to_camel_case(s: str) -> str: | |
| # Some enum names are already camel case, so return as-is | |
| if "_" not in s or not s.isupper(): | |
| return s | |
| return "".join(c.capitalize() for c in s.lower().split("_")) | |
| def strip_comments(s: str) -> str: | |
| # Note: Assumes no nested comments | |
| s = re.sub(r"\/\*.+?\*\/", "", s, flags=re.DOTALL) | |
| s = re.sub(r"//.*", "", s) | |
| return s | |
| def remove_backslashes(s: str) -> str: | |
| s = re.sub(r"\\\n", "", s, flags=re.DOTALL) | |
| return s | |
| # TODO(comc) Unit test this, and its helper functions. | |
| def create_enum_mapping(input_header_paths: Sequence[str], output_header_path: str): | |
| enum_definitions = [] | |
| for input_header_path in input_header_paths: | |
| with open(input_header_path, "r") as f: | |
| header = f.read() | |
| header = strip_comments(header) | |
| header = remove_backslashes(header) | |
| output_lines = [] | |
| current_enum_index = None | |
| for line in header.split("\n"): | |
| line = line.strip() | |
| if any(line.startswith(s) for s in PASSTHROUGH_DIRECTIVES): | |
| output_lines.append(line) | |
| elif any(line.startswith(s) for s in SKIP_DIRECTIVES): | |
| # To simplify parsing, when we encounter #ifdef / #ifndef, just replace it | |
| # with `#if 1` so the corresponding #endif still works. | |
| output_lines.append("#if 1") | |
| elif re.fullmatch(ENUM_START_REGEX, line): | |
| current_enum_index = len(output_lines) | |
| output_lines.append(ENUM_MAP_TEMPLATE) | |
| elif current_enum_index is not None: | |
| if variant := re.fullmatch(ENUM_VARIANT_REGEX, line.replace(" ", "")): | |
| name = variant.group(1) | |
| output_lines.append(f' {{{name}, "{name}"}},') | |
| elif enum_name := re.fullmatch(ENUM_END_REGEX, line): | |
| output_lines[current_enum_index] = ENUM_MAP_TEMPLATE.format( | |
| name_camel=snake_to_camel_case(enum_name.group(2)) | |
| ) | |
| current_enum_index = None | |
| output_lines.append("};") | |
| enum_definitions.append("\n".join(output_lines)) | |
| enum_header = ENUM_HEADER_TEMPLATE.format( | |
| enum_definitions="\n".join(enum_definitions)) | |
| with open(output_header_path, "w") as f: | |
| f.write(enum_header) | |
| def main() -> None: | |
| args = parser.parse_args() | |
| create_enum_mapping(args.enums_header, args.output) | |
| if __name__ == "__main__": | |
| main() |