blob: 00c300b1830bd9ab09f5f3ef8258c2199045b130 [file] [log] [blame]
#!/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()