diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index bd5159114f..0225be1213 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -77,9 +77,9 @@ function (generate_css_implementation) arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/PseudoElements.json" ) - invoke_cpp_generator( + invoke_py_generator( "TransformFunctions.cpp" - Lagom::GenerateCSSTransformFunctions + "generate_libweb_css_transform_functions.py" "${LIBWEB_INPUT_FOLDER}/CSS/TransformFunctions.json" "CSS/TransformFunctions.h" "CSS/TransformFunctions.cpp" diff --git a/Meta/Generators/generate_libweb_css_transform_functions.py b/Meta/Generators/generate_libweb_css_transform_functions.py new file mode 100644 index 0000000000..93a688a763 --- /dev/null +++ b/Meta/Generators/generate_libweb_css_transform_functions.py @@ -0,0 +1,167 @@ +#!/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)) + + +PARAMETER_TYPE_NAMES = { + "angle": "Angle", + "length": "Length", + "length-none": "LengthNone", + "length-percentage": "LengthPercentage", + "number": "Number", + "number-percentage": "NumberPercentage", +} + + +def title_casify_transform_function(name: str) -> str: + # Transform function names look like `fooBar`, so we just have to make the first character uppercase. + return name[0].upper() + name[1:] + + +def write_header_file(out: TextIO, transforms_data: dict) -> None: + out.write(""" +#pragma once + +#include +#include +#include + +namespace Web::CSS { + +""") + + out.write("enum class TransformFunction {\n") + for name in transforms_data: + out.write(f" {title_casify_transform_function(name)},\n") + out.write("};\n") + + out.write("Optional transform_function_from_string(StringView);\n") + out.write("StringView to_string(TransformFunction);\n") + + out.write(""" +enum class TransformFunctionParameterType { + Angle, + Length, + LengthNone, + LengthPercentage, + Number, + NumberPercentage +}; + +struct TransformFunctionParameter { + TransformFunctionParameterType type; + bool required; +}; + +struct TransformFunctionMetadata { + Vector parameters; +}; +TransformFunctionMetadata transform_function_metadata(TransformFunction); + +} +""") + + +def write_implementation_file(out: TextIO, transforms_data: dict) -> None: + out.write(""" +#include +#include + +namespace Web::CSS { + +Optional transform_function_from_string(StringView name) +{ +""") + for name in transforms_data: + out.write(f""" + if (name.equals_ignoring_ascii_case("{name}"sv)) + return TransformFunction::{title_casify_transform_function(name)}; +""") + + out.write(""" + return {}; +} + +StringView to_string(TransformFunction transform_function) +{ + switch (transform_function) { +""") + for name in transforms_data: + out.write(f""" + case TransformFunction::{title_casify_transform_function(name)}: + return "{name}"sv; +""") + + out.write(""" + default: + VERIFY_NOT_REACHED(); + } +} + +TransformFunctionMetadata transform_function_metadata(TransformFunction transform_function) +{ + switch (transform_function) { +""") + for name, value in transforms_data.items(): + out.write(f""" + case TransformFunction::{title_casify_transform_function(name)}: + return TransformFunctionMetadata {{ + .parameters = {{""") + + parameters = value["parameters"] + for index, parameter in enumerate(parameters): + parameter_type_name = parameter["type"].strip("<>") + if parameter_type_name not in PARAMETER_TYPE_NAMES: + raise ValueError(f"Unknown transform function parameter type: {parameter_type_name}") + parameter_type = PARAMETER_TYPE_NAMES[parameter_type_name] + + separator = " " if index == 0 else ", " + required = "true" if parameter["required"] else "false" + out.write(f"{separator}{{ TransformFunctionParameterType::{parameter_type}, {required}}}") + + out.write(" }\n };\n") + + out.write(""" + default: + VERIFY_NOT_REACHED(); + } +} + +} +""") + + +def main(): + parser = argparse.ArgumentParser(description="Generate CSS TransformFunctions", 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 TransformFunctions header file to generate") + parser.add_argument( + "-c", "--implementation", required=True, help="Path to the TransformFunctions 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: + transforms_data = json.load(input_file) + + with open(args.header, "w", encoding="utf-8") as output_file: + write_header_file(output_file, transforms_data) + + with open(args.implementation, "w", encoding="utf-8") as output_file: + write_implementation_file(output_file, transforms_data) + + +if __name__ == "__main__": + main() diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt index 1fbfc06bfc..4a6443f571 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt @@ -8,7 +8,6 @@ lagom_tool(GenerateCSSPropertyID SOURCES GenerateCSSPropertyID.cpp LI lagom_tool(GenerateCSSPseudoClass SOURCES GenerateCSSPseudoClass.cpp LIBS LibMain) lagom_tool(GenerateCSSPseudoElement SOURCES GenerateCSSPseudoElement.cpp LIBS LibMain) lagom_tool(GenerateCSSStyleProperties SOURCES GenerateCSSStyleProperties.cpp LIBS LibMain) -lagom_tool(GenerateCSSTransformFunctions SOURCES GenerateCSSTransformFunctions.cpp LIBS LibMain) lagom_tool(GenerateCSSUnits SOURCES GenerateCSSUnits.cpp LIBS LibMain) lagom_tool(GenerateWindowOrWorkerInterfaces SOURCES GenerateWindowOrWorkerInterfaces.cpp LIBS LibMain LibIDL) lagom_tool(GenerateAriaRoles SOURCES GenerateAriaRoles.cpp LIBS LibMain) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSTransformFunctions.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSTransformFunctions.cpp deleted file mode 100644 index 569b7de110..0000000000 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSTransformFunctions.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (c) 2022-2023, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "GeneratorUtil.h" -#include -#include -#include -#include -#include - -ErrorOr generate_header_file(JsonObject& transforms_data, Core::File& file); -ErrorOr generate_implementation_file(JsonObject& transforms_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 TransformFunctions header file to generate", "generated-header-path", 'h', "generated-header-path"); - args_parser.add_option(generated_implementation_path, "Path to the TransformFunctions 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 transforms_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(transforms_data, *generated_header_file)); - TRY(generate_implementation_file(transforms_data, *generated_implementation_file)); - - return 0; -} - -static String title_casify_transform_function(StringView input) -{ - // Transform function names look like `fooBar`, so we just have to make the first character uppercase. - StringBuilder builder; - builder.append(toupper(input[0])); - builder.append(input.substring_view(1)); - return MUST(builder.to_string()); -} - -ErrorOr generate_header_file(JsonObject& transforms_data, Core::File& file) -{ - StringBuilder builder; - SourceGenerator generator { builder }; - - generator.append(R"~~~( -#pragma once - -#include -#include -#include - -namespace Web::CSS { - -)~~~"); - - generator.appendln("enum class TransformFunction {"); - transforms_data.for_each_member([&](auto& name, auto&) { - auto member_generator = generator.fork(); - member_generator.set("name:titlecase", title_casify_transform_function(name)); - member_generator.appendln(" @name:titlecase@,"); - }); - generator.appendln("};"); - - generator.appendln("Optional transform_function_from_string(StringView);"); - generator.appendln("StringView to_string(TransformFunction);"); - - generator.append(R"~~~( -enum class TransformFunctionParameterType { - Angle, - Length, - LengthNone, - LengthPercentage, - Number, - NumberPercentage -}; - -struct TransformFunctionParameter { - TransformFunctionParameterType type; - bool required; -}; - -struct TransformFunctionMetadata { - Vector parameters; -}; -TransformFunctionMetadata transform_function_metadata(TransformFunction); -)~~~"); - - generator.appendln("\n}"); - - TRY(file.write_until_depleted(generator.as_string_view().bytes())); - return {}; -} - -ErrorOr generate_implementation_file(JsonObject& transforms_data, Core::File& file) -{ - StringBuilder builder; - SourceGenerator generator { builder }; - - generator.append(R"~~~( -#include -#include - -namespace Web::CSS { -)~~~"); - - generator.append(R"~~~( -Optional transform_function_from_string(StringView name) -{ -)~~~"); - transforms_data.for_each_member([&](auto& name, auto&) { - auto member_generator = generator.fork(); - member_generator.set("name", name); - member_generator.set("name:titlecase", title_casify_transform_function(name)); - member_generator.append(R"~~~( - if (name.equals_ignoring_ascii_case("@name@"sv)) - return TransformFunction::@name:titlecase@; -)~~~"); - }); - generator.append(R"~~~( - return {}; -} -)~~~"); - - generator.append(R"~~~( -StringView to_string(TransformFunction transform_function) -{ - switch (transform_function) { -)~~~"); - transforms_data.for_each_member([&](auto& name, auto&) { - auto member_generator = generator.fork(); - member_generator.set("name", name); - member_generator.set("name:titlecase", title_casify_transform_function(name)); - member_generator.append(R"~~~( - case TransformFunction::@name:titlecase@: - return "@name@"sv; -)~~~"); - }); - generator.append(R"~~~( - default: - VERIFY_NOT_REACHED(); - } -} -)~~~"); - - generator.append(R"~~~( -TransformFunctionMetadata transform_function_metadata(TransformFunction transform_function) -{ - switch (transform_function) { -)~~~"); - transforms_data.for_each_member([&](auto& name, auto& value) { - VERIFY(value.is_object()); - - auto member_generator = generator.fork(); - member_generator.set("name:titlecase", title_casify_transform_function(name)); - member_generator.append(R"~~~( - case TransformFunction::@name:titlecase@: - return TransformFunctionMetadata { - .parameters = {)~~~"); - - JsonArray const& parameters = value.as_object().get_array("parameters"sv).value(); - bool first = true; - parameters.for_each([&](JsonValue const& value) { - GenericLexer lexer { value.as_object().get_string("type"sv).value() }; - VERIFY(lexer.consume_specific('<')); - auto parameter_type_name = lexer.consume_until('>'); - VERIFY(lexer.consume_specific('>')); - - StringView parameter_type = ""sv; - if (parameter_type_name == "angle"sv) - parameter_type = "Angle"sv; - else if (parameter_type_name == "length"sv) - parameter_type = "Length"sv; - else if (parameter_type_name == "length-none"sv) - parameter_type = "LengthNone"sv; - else if (parameter_type_name == "length-percentage"sv) - parameter_type = "LengthPercentage"sv; - else if (parameter_type_name == "number"sv) - parameter_type = "Number"sv; - else if (parameter_type_name == "number-percentage"sv) - parameter_type = "NumberPercentage"sv; - else - VERIFY_NOT_REACHED(); - - member_generator.append(first ? " "sv : ", "sv); - first = false; - - member_generator.append(MUST(String::formatted("{{ TransformFunctionParameterType::{}, {}}}", parameter_type, value.as_object().get("required"sv)->as_bool()))); - }); - - member_generator.append(R"~~~( } - }; -)~~~"); - }); - generator.append(R"~~~( - default: - VERIFY_NOT_REACHED(); - } -} -)~~~"); - - generator.appendln("\n}"); - - TRY(file.write_until_depleted(generator.as_string_view().bytes())); - return {}; -}