diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 463039d2b20..2ca37098b8e 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -10,10 +10,10 @@ function (generate_css_implementation) "CSS/DescriptorID.cpp" arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Descriptors.json" ) - - invoke_cpp_generator( + + invoke_py_generator( "Enums.cpp" - Lagom::GenerateCSSEnums + "generate_libweb_css_enums.py" "${LIBWEB_INPUT_FOLDER}/CSS/Enums.json" "CSS/Enums.h" "CSS/Enums.cpp" diff --git a/Meta/Generators/generate_libweb_css_enums.py b/Meta/Generators/generate_libweb_css_enums.py new file mode 100755 index 00000000000..66cb04fde79 --- /dev/null +++ b/Meta/Generators/generate_libweb_css_enums.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2022-2026, Sam Atkins +# Copyright (c) 2026-present, the Ladybird developers. +# +# SPDX-License-Identifier: BSD-2-Clause + +import argparse +import json +import sys + +from pathlib import Path +from typing import TextIO + +sys.path.append(str(Path(__file__).resolve().parent.parent)) + +from Utils.utils import snake_casify +from Utils.utils import title_casify +from Utils.utils import underlying_type_for_enum + + +def write_header_file(out: TextIO, enums_data: dict) -> None: + out.write(""" +#pragma once + +#include +#include + +namespace Web::CSS { +""") + + for name, value in enums_data.items(): + out.write(f"enum class {title_casify(name)} : {underlying_type_for_enum(len(value))} {{\n") + for member_name in value: + # Don't include aliases in the enum. + if "=" in member_name: + continue + out.write(f" {title_casify(member_name)},\n") + + out.write(f"""}}; +Optional<{title_casify(name)}> keyword_to_{snake_casify(name)}(Keyword); +Keyword to_keyword({title_casify(name)}); +StringView to_string({title_casify(name)}); + +""") + + out.write("}\n") + + +def write_implementation_file(out: TextIO, enums_data: dict) -> None: + out.write(""" +#include +#include + +namespace Web::CSS { +""") + + for name, value in enums_data.items(): + name_titlecase = title_casify(name) + name_snakecase = snake_casify(name) + + out.write(f""" +Optional<{name_titlecase}> keyword_to_{name_snakecase}(Keyword keyword) +{{ + switch (keyword) {{ +""") + for member_name in value: + if "=" in member_name: + parts = member_name.split("=") + valueid_titlecase = title_casify(parts[0]) + member_titlecase = title_casify(parts[1]) + else: + valueid_titlecase = title_casify(member_name) + member_titlecase = title_casify(member_name) + out.write(f""" case Keyword::{valueid_titlecase}: + return {name_titlecase}::{member_titlecase}; +""") + out.write(""" default: + return {}; + } +} +""") + + out.write(f""" +Keyword to_keyword({name_titlecase} {name_snakecase}_value) +{{ + switch ({name_snakecase}_value) {{ +""") + for member_name in value: + if "=" in member_name: + continue + member_titlecase = title_casify(member_name) + out.write(f""" case {name_titlecase}::{member_titlecase}: + return Keyword::{member_titlecase}; +""") + out.write(""" default: + VERIFY_NOT_REACHED(); + } +} +""") + + out.write(f""" +StringView to_string({name_titlecase} value) +{{ + switch (value) {{ +""") + for member_name in value: + if "=" in member_name: + continue + member_titlecase = title_casify(member_name) + out.write(f""" case {name_titlecase}::{member_titlecase}: + return "{member_name}"sv; +""") + out.write(""" default: + VERIFY_NOT_REACHED(); + } +} +""") + + out.write("}\n") + + +def main(): + parser = argparse.ArgumentParser(description="Generate CSS Enums", add_help=False) + parser.add_argument("--help", action="help", help="Show this help message and exit") + parser.add_argument("-h", "--header", required=True, help="Path to the Enums header file to generate") + parser.add_argument( + "-c", "--implementation", required=True, help="Path to the Enums implementation file to generate" + ) + parser.add_argument("-j", "--json", required=True, help="Path to the JSON file to read from") + args = parser.parse_args() + + with open(args.json, "r", encoding="utf-8") as input_file: + enums_data = json.load(input_file) + + with open(args.header, "w", encoding="utf-8") as output_file: + write_header_file(output_file, enums_data) + + with open(args.implementation, "w", encoding="utf-8") as output_file: + write_implementation_file(output_file, enums_data) + + +if __name__ == "__main__": + main() diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt index c5738957cab..6e06d2b44fa 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt @@ -1,7 +1,6 @@ set(SOURCES "") # avoid pulling SOURCES from parent scope lagom_tool(GenerateCSSDescriptors SOURCES GenerateCSSDescriptors.cpp LIBS LibMain) -lagom_tool(GenerateCSSEnums SOURCES GenerateCSSEnums.cpp LIBS LibMain) lagom_tool(GenerateCSSEnvironmentVariable SOURCES GenerateCSSEnvironmentVariable.cpp LIBS LibMain) lagom_tool(GenerateCSSKeyword SOURCES GenerateCSSKeyword.cpp LIBS LibMain) lagom_tool(GenerateCSSMathFunctions SOURCES GenerateCSSMathFunctions.cpp LIBS LibMain) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEnums.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEnums.cpp deleted file mode 100644 index 2b634b5be0e..00000000000 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEnums.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (c) 2022-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "GeneratorUtil.h" -#include -#include -#include -#include - -ErrorOr generate_header_file(JsonObject& enums_data, Core::File& file); -ErrorOr generate_implementation_file(JsonObject& enums_data, Core::File& file); - -ErrorOr ladybird_main(Main::Arguments arguments) -{ - StringView generated_header_path; - StringView generated_implementation_path; - StringView json_path; - - Core::ArgsParser args_parser; - args_parser.add_option(generated_header_path, "Path to the Enums header file to generate", "generated-header-path", 'h', "generated-header-path"); - args_parser.add_option(generated_implementation_path, "Path to the Enums implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path"); - args_parser.add_option(json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path"); - args_parser.parse(arguments); - - auto json = TRY(read_entire_file_as_json(json_path)); - VERIFY(json.is_object()); - auto enums_data = json.as_object(); - - auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write)); - auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write)); - - TRY(generate_header_file(enums_data, *generated_header_file)); - TRY(generate_implementation_file(enums_data, *generated_implementation_file)); - - return 0; -} - -ErrorOr generate_header_file(JsonObject& enums_data, Core::File& file) -{ - StringBuilder builder; - SourceGenerator generator { builder }; - - generator.append(R"~~~( -#pragma once - -#include -#include - -namespace Web::CSS { - -)~~~"); - - enums_data.for_each_member([&](auto& name, auto& value) { - VERIFY(value.is_array()); - auto& members = value.as_array(); - - auto enum_generator = generator.fork(); - enum_generator.set("name:titlecase", title_casify(name)); - enum_generator.set("name:snakecase", snake_casify(name)); - enum_generator.set("enum_type", underlying_type_for_enum(members.size())); - - enum_generator.appendln("enum class @name:titlecase@ : @enum_type@ {"); - - for (auto& member : members.values()) { - auto member_name = member.as_string(); - // Don't include aliases in the enum. - if (member_name.contains('=')) - continue; - auto member_generator = enum_generator.fork(); - member_generator.set("member:titlecase", title_casify(member_name)); - member_generator.appendln(" @member:titlecase@,"); - } - - enum_generator.appendln("};"); - enum_generator.appendln("Optional<@name:titlecase@> keyword_to_@name:snakecase@(Keyword);"); - enum_generator.appendln("Keyword to_keyword(@name:titlecase@);"); - enum_generator.appendln("StringView to_string(@name:titlecase@);"); - enum_generator.append("\n"); - }); - - generator.appendln("}"); - - TRY(file.write_until_depleted(generator.as_string_view().bytes())); - return {}; -} - -ErrorOr generate_implementation_file(JsonObject& enums_data, Core::File& file) -{ - StringBuilder builder; - SourceGenerator generator { builder }; - - generator.append(R"~~~( -#include -#include - -namespace Web::CSS { -)~~~"); - - enums_data.for_each_member([&](String const& name, JsonValue const& value) { - VERIFY(value.is_array()); - auto& members = value.as_array(); - - auto enum_generator = generator.fork(); - enum_generator.set("name:titlecase", title_casify(name)); - enum_generator.set("name:snakecase", snake_casify(name)); - - enum_generator.append(R"~~~( -Optional<@name:titlecase@> keyword_to_@name:snakecase@(Keyword keyword) -{ - switch (keyword) {)~~~"); - - for (auto& member : members.values()) { - auto member_generator = enum_generator.fork(); - auto member_name = member.as_string(); - if (member_name.contains('=')) { - auto parts = MUST(member_name.split('=')); - member_generator.set("valueid:titlecase", title_casify(parts[0])); - member_generator.set("member:titlecase", title_casify(parts[1])); - } else { - member_generator.set("valueid:titlecase", title_casify(member_name)); - member_generator.set("member:titlecase", title_casify(member_name)); - } - member_generator.append(R"~~~( - case Keyword::@valueid:titlecase@: - return @name:titlecase@::@member:titlecase@;)~~~"); - } - - enum_generator.append(R"~~~( - default: - return {}; - } -} -)~~~"); - - enum_generator.append(R"~~~( -Keyword to_keyword(@name:titlecase@ @name:snakecase@_value) -{ - switch (@name:snakecase@_value) {)~~~"); - - for (auto& member : members.values()) { - auto member_generator = enum_generator.fork(); - auto member_name = member.as_string(); - if (member_name.contains('=')) - continue; - member_generator.set("member:titlecase", title_casify(member_name)); - - member_generator.append(R"~~~( - case @name:titlecase@::@member:titlecase@: - return Keyword::@member:titlecase@;)~~~"); - } - - enum_generator.append(R"~~~( - default: - VERIFY_NOT_REACHED(); - } -} -)~~~"); - - enum_generator.append(R"~~~( -StringView to_string(@name:titlecase@ value) -{ - switch (value) {)~~~"); - - for (auto& member : members.values()) { - auto member_generator = enum_generator.fork(); - auto member_name = member.as_string(); - if (member_name.contains('=')) - continue; - member_generator.set("member:css", member_name); - member_generator.set("member:titlecase", title_casify(member_name)); - - member_generator.append(R"~~~( - case @name:titlecase@::@member:titlecase@: - return "@member:css@"sv;)~~~"); - } - - enum_generator.append(R"~~~( - default: - VERIFY_NOT_REACHED(); - } -} -)~~~"); - }); - - generator.appendln("}"); - - TRY(file.write_until_depleted(generator.as_string_view().bytes())); - return {}; -} diff --git a/Meta/Utils/utils.py b/Meta/Utils/utils.py index 91cbd194102..509ba890e30 100644 --- a/Meta/Utils/utils.py +++ b/Meta/Utils/utils.py @@ -57,3 +57,24 @@ def string_hash(string: str) -> int: h = (h + (h << 15)) & 0xFFFFFFFF return h + + +def title_casify(dashy_name: str) -> str: + return "".join(part[0].upper() + part[1:] for part in dashy_name.split("-") if part) + + +def snake_casify(dashy_name: str, trim_leading_underscores: bool = False) -> str: + snake_case = dashy_name.replace("-", "_") + if trim_leading_underscores: + snake_case = snake_case.lstrip("_") + return snake_case + + +def underlying_type_for_enum(member_count: int) -> str: + if member_count <= 0xFF: + return "u8" + if member_count <= 0xFFFF: + return "u16" + if member_count <= 0xFFFFFFFF: + return "u32" + return "u64"