| #!/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() |