From d3d124b1d867331eec2fe2b8e9a6395737aea594 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 24 Apr 2026 15:23:36 +0100 Subject: [PATCH] Meta: Replace GenerateCSSNumericFactoryMethods with a python generator Adds a new `invoke_py_idl_generator` CMake helper for python scripts that also produce an IDL file. --- Meta/CMake/libweb_generators.cmake | 4 +- Meta/CMake/utils.cmake | 23 +++ ...rate_libweb_css_numeric_factory_methods.py | 147 +++++++++++++++ .../CodeGenerators/LibWeb/CMakeLists.txt | 1 - .../GenerateCSSNumericFactoryMethods.cpp | 178 ------------------ Meta/Utils/utils.py | 6 + 6 files changed, 178 insertions(+), 181 deletions(-) create mode 100644 Meta/Generators/generate_libweb_css_numeric_factory_methods.py delete mode 100644 Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSNumericFactoryMethods.cpp diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 4362a855515..d729ee5c81a 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -104,10 +104,10 @@ function (generate_css_implementation) arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Keywords.json" ) - invoke_idl_generator( + invoke_py_idl_generator( "GeneratedCSSNumericFactoryMethods.cpp" "GeneratedCSSNumericFactoryMethods.idl" - Lagom::GenerateCSSNumericFactoryMethods + "generate_libweb_css_numeric_factory_methods.py" "${LIBWEB_INPUT_FOLDER}/CSS/Units.json" "CSS/GeneratedCSSNumericFactoryMethods.h" "CSS/GeneratedCSSNumericFactoryMethods.cpp" diff --git a/Meta/CMake/utils.cmake b/Meta/CMake/utils.cmake index b727bc4aaa9..d9ec3a8cabf 100644 --- a/Meta/CMake/utils.cmake +++ b/Meta/CMake/utils.cmake @@ -135,6 +135,29 @@ function(invoke_idl_generator cpp_name idl_name generator primary_source header set(CURRENT_LIB_GENERATED ${CURRENT_LIB_GENERATED} PARENT_SCOPE) endfunction() +function(invoke_py_idl_generator cpp_name idl_name script primary_source header implementation idl) + cmake_parse_arguments(invoke_py_idl_generator "" "" "arguments;dependencies" ${ARGN}) + + set(script_path "${LADYBIRD_SOURCE_DIR}/Meta/Generators/${script}") + add_custom_command( + OUTPUT "${header}" "${implementation}" "${idl}" + COMMAND ${Python3_EXECUTABLE} "${script_path}" -h "${header}.tmp" -c "${implementation}.tmp" -i "${idl}.tmp" ${invoke_py_idl_generator_arguments} + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${header}.tmp" "${header}" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${implementation}.tmp" "${implementation}" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${idl}.tmp" "${idl}" + COMMAND "${CMAKE_COMMAND}" -E remove "${header}.tmp" "${implementation}.tmp" "${idl}.tmp" + VERBATIM + DEPENDS "${script_path}" ${invoke_py_idl_generator_dependencies} "${primary_source}" + ) + + add_custom_target("generate_${cpp_name}" DEPENDS "${header}" "${implementation}" "${idl}") + add_custom_target("generate_${idl_name}" DEPENDS "generate_${cpp_name}") + add_dependencies(ladybird_codegen_accumulator "generate_${cpp_name}") + add_dependencies(ladybird_codegen_accumulator "generate_${idl_name}") + list(APPEND CURRENT_LIB_GENERATED "${name}") + set(CURRENT_LIB_GENERATED ${CURRENT_LIB_GENERATED} PARENT_SCOPE) +endfunction() + function(download_file_multisource urls path) cmake_parse_arguments(DOWNLOAD "" "SHA256" "" ${ARGN}) diff --git a/Meta/Generators/generate_libweb_css_numeric_factory_methods.py b/Meta/Generators/generate_libweb_css_numeric_factory_methods.py new file mode 100644 index 00000000000..3f35b737fe2 --- /dev/null +++ b/Meta/Generators/generate_libweb_css_numeric_factory_methods.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2025-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 make_name_acceptable_cpp +from Utils.utils import snake_casify + + +def write_header_file(out: TextIO, units_data: dict) -> None: + out.write(""" +#pragma once + +#include +#include +#include +#include + +// https://drafts.css-houdini.org/css-typed-om-1/#numeric-factory +namespace Web::CSS { + +GC::Ref number(JS::VM&, WebIDL::Double value); +GC::Ref percent(JS::VM&, WebIDL::Double value); +""") + + for dimension_name, units in units_data.items(): + dimension_acceptable_cpp = make_name_acceptable_cpp(snake_casify(dimension_name, trim_leading_underscores=True)) + out.write(f"\n// <{dimension_acceptable_cpp}>\n") + for unit_name in units: + unit_acceptable_cpp = make_name_acceptable_cpp(unit_name.lower()) + out.write(f"GC::Ref {unit_acceptable_cpp}(JS::VM&, WebIDL::Double value);\n") + + out.write(""" +} +""") + + +def write_implementation_file(out: TextIO, units_data: dict) -> None: + out.write(""" +#include +#include +#include +#include + +namespace Web::CSS { + +// https://drafts.css-houdini.org/css-typed-om-1/#numeric-factory +inline GC::Ref numeric_factory(JS::VM& vm, WebIDL::Double value, FlyString unit) +{ + // All of the above methods must, when called with a double value, return a new CSSUnitValue whose value internal + // slot is set to value and whose unit internal slot is set to the name of the method as defined here. + return CSSUnitValue::create(*vm.current_realm(), value, move(unit)); +} + +GC::Ref number(JS::VM& vm, WebIDL::Double value) +{ + return numeric_factory(vm, value, "number"_fly_string); +} + +GC::Ref percent(JS::VM& vm, WebIDL::Double value) +{ + return numeric_factory(vm, value, "percent"_fly_string); +} + +""") + + for dimension_name, units in units_data.items(): + dimension_acceptable_cpp = make_name_acceptable_cpp(snake_casify(dimension_name, trim_leading_underscores=True)) + out.write(f"\n// <{dimension_acceptable_cpp}>\n") + for unit_name in units: + unit_acceptable_cpp = make_name_acceptable_cpp(unit_name.lower()) + out.write(f""" +GC::Ref {unit_acceptable_cpp}(JS::VM& vm, WebIDL::Double value) +{{ + return numeric_factory(vm, value, "{unit_name}"_fly_string); +}} +""") + + out.write(""" +} +""") + + +def write_idl_file(out: TextIO, units_data: dict) -> None: + out.write(""" +partial namespace CSS { + CSSUnitValue number(double value); + CSSUnitValue percent(double value); + +""") + + for dimension_name, units in units_data.items(): + dimension_acceptable_cpp = make_name_acceptable_cpp(snake_casify(dimension_name, trim_leading_underscores=True)) + out.write(f""" + // <{dimension_acceptable_cpp}> +""") + for unit_name in units: + unit_acceptable_cpp = make_name_acceptable_cpp(unit_name.lower()) + out.write(f" [ImplementedAs={unit_acceptable_cpp}] CSSUnitValue {unit_name}(double value);\n") + + out.write(""" +}; +""") + + +def main(): + parser = argparse.ArgumentParser(description="Generate CSS NumericFactoryMethods", 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 CSSNumericFactoryMethods header file to generate" + ) + parser.add_argument( + "-c", + "--implementation", + required=True, + help="Path to the CSSNumericFactoryMethods implementation file to generate", + ) + parser.add_argument("-i", "--idl", required=True, help="Path to the CSSNumericFactoryMethods IDL 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: + units_data = json.load(input_file) + + with open(args.header, "w", encoding="utf-8") as output_file: + write_header_file(output_file, units_data) + + with open(args.implementation, "w", encoding="utf-8") as output_file: + write_implementation_file(output_file, units_data) + + with open(args.idl, "w", encoding="utf-8") as output_file: + write_idl_file(output_file, units_data) + + +if __name__ == "__main__": + main() diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt index 4af9729a6e4..aad88567085 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt @@ -1,6 +1,5 @@ set(SOURCES "") # avoid pulling SOURCES from parent scope -lagom_tool(GenerateCSSNumericFactoryMethods SOURCES GenerateCSSNumericFactoryMethods.cpp LIBS LibMain) lagom_tool(GenerateCSSPropertyID SOURCES GenerateCSSPropertyID.cpp LIBS LibMain) lagom_tool(GenerateCSSStyleProperties SOURCES GenerateCSSStyleProperties.cpp LIBS LibMain) lagom_tool(GenerateWindowOrWorkerInterfaces SOURCES GenerateWindowOrWorkerInterfaces.cpp LIBS LibMain LibIDL) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSNumericFactoryMethods.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSNumericFactoryMethods.cpp deleted file mode 100644 index 5307340c0cb..00000000000 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSNumericFactoryMethods.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2025, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "GeneratorUtil.h" -#include -#include -#include -#include - -ErrorOr generate_header_file(JsonObject& units_data, Core::File& file); -ErrorOr generate_implementation_file(JsonObject& units_data, Core::File& file); -ErrorOr generate_idl_file(JsonObject& units_data, Core::File& file); - -ErrorOr ladybird_main(Main::Arguments arguments) -{ - StringView generated_header_path; - StringView generated_implementation_path; - StringView generated_idl_path; - StringView units_json_path; - - Core::ArgsParser args_parser; - args_parser.add_option(generated_header_path, "Path to the CSSNumericFactoryMethods header file to generate", "generated-header-path", 'h', "generated-header-path"); - args_parser.add_option(generated_implementation_path, "Path to the CSSNumericFactoryMethods implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path"); - args_parser.add_option(generated_idl_path, "Path to the CSSNumericFactoryMethods IDL file to generate", "generated-idl-path", 'i', "generated-idl-path"); - args_parser.add_option(units_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(units_json_path)); - VERIFY(json.is_object()); - auto units_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)); - auto generated_idl_file = TRY(Core::File::open(generated_idl_path, Core::File::OpenMode::Write)); - - TRY(generate_header_file(units_data, *generated_header_file)); - TRY(generate_implementation_file(units_data, *generated_implementation_file)); - TRY(generate_idl_file(units_data, *generated_idl_file)); - - return 0; -} - -ErrorOr generate_header_file(JsonObject& units_data, Core::File& file) -{ - StringBuilder builder; - SourceGenerator generator { builder }; - - generator.append(R"~~~( -#pragma once - -#include -#include -#include -#include - -// https://drafts.css-houdini.org/css-typed-om-1/#numeric-factory -namespace Web::CSS { - -GC::Ref number(JS::VM&, WebIDL::Double value); -GC::Ref percent(JS::VM&, WebIDL::Double value); -)~~~"); - - units_data.for_each_member([&](auto& dimension_name, JsonValue const& dimension) { - auto dimension_generator = generator.fork(); - dimension_generator.set("dimension:acceptable_cpp", make_name_acceptable_cpp(snake_casify(dimension_name, TrimLeadingUnderscores::Yes))); - dimension_generator.appendln("\n// <@dimension:acceptable_cpp@>"); - - dimension.as_object().for_each_member([&](auto& unit_name, JsonValue const&) { - auto unit_generator = dimension_generator.fork(); - unit_generator.set("unit:acceptable_cpp", make_name_acceptable_cpp(unit_name.to_ascii_lowercase())); - unit_generator.appendln("GC::Ref @unit:acceptable_cpp@(JS::VM&, WebIDL::Double value);"); - }); - }); - - generator.append(R"~~~( -} -)~~~"); - - TRY(file.write_until_depleted(generator.as_string_view().bytes())); - return {}; -} - -ErrorOr generate_implementation_file(JsonObject& units_data, Core::File& file) -{ - StringBuilder builder; - SourceGenerator generator { builder }; - - generator.append(R"~~~( -#include -#include -#include -#include - -namespace Web::CSS { - -// https://drafts.css-houdini.org/css-typed-om-1/#numeric-factory -inline GC::Ref numeric_factory(JS::VM& vm, WebIDL::Double value, FlyString unit) -{ - // All of the above methods must, when called with a double value, return a new CSSUnitValue whose value internal - // slot is set to value and whose unit internal slot is set to the name of the method as defined here. - return CSSUnitValue::create(*vm.current_realm(), value, move(unit)); -} - -GC::Ref number(JS::VM& vm, WebIDL::Double value) -{ - return numeric_factory(vm, value, "number"_fly_string); -} - -GC::Ref percent(JS::VM& vm, WebIDL::Double value) -{ - return numeric_factory(vm, value, "percent"_fly_string); -} - -)~~~"); - - units_data.for_each_member([&](auto& dimension_name, JsonValue const& dimension) { - auto dimension_generator = generator.fork(); - dimension_generator.set("dimension:acceptable_cpp", make_name_acceptable_cpp(snake_casify(dimension_name, TrimLeadingUnderscores::Yes))); - dimension_generator.appendln("\n// <@dimension:acceptable_cpp@>"); - - dimension.as_object().for_each_member([&](auto& unit_name, JsonValue const&) { - auto unit_generator = dimension_generator.fork(); - unit_generator.set("unit:name", unit_name); - unit_generator.set("unit:acceptable_cpp", make_name_acceptable_cpp(unit_name.to_ascii_lowercase())); - unit_generator.append(R"~~~( -GC::Ref @unit:acceptable_cpp@(JS::VM& vm, WebIDL::Double value) -{ - return numeric_factory(vm, value, "@unit:name@"_fly_string); -} -)~~~"); - }); - }); - - generator.append(R"~~~( -} -)~~~"); - - TRY(file.write_until_depleted(generator.as_string_view().bytes())); - return {}; -} - -ErrorOr generate_idl_file(JsonObject& units_data, Core::File& file) -{ - StringBuilder builder; - SourceGenerator generator { builder }; - - generator.append(R"~~~( -partial namespace CSS { - CSSUnitValue number(double value); - CSSUnitValue percent(double value); - -)~~~"); - - units_data.for_each_member([&](auto& dimension_name, JsonValue const& dimension) { - auto dimension_generator = generator.fork(); - dimension_generator.set("dimension:acceptable_cpp", make_name_acceptable_cpp(snake_casify(dimension_name, TrimLeadingUnderscores::Yes))); - dimension_generator.append(R"~~~( - // <@dimension:acceptable_cpp@> -)~~~"); - - dimension.as_object().for_each_member([&](auto& unit_name, JsonValue const&) { - auto unit_generator = dimension_generator.fork(); - unit_generator.set("unit:name", unit_name); - unit_generator.set("unit:acceptable_cpp", make_name_acceptable_cpp(unit_name.to_ascii_lowercase())); - unit_generator.appendln(" [ImplementedAs=@unit:acceptable_cpp@] CSSUnitValue @unit:name@(double value);"); - }); - }); - - generator.append(R"~~~( -}; -)~~~"); - - TRY(file.write_until_depleted(generator.as_string_view().bytes())); - return {}; -} diff --git a/Meta/Utils/utils.py b/Meta/Utils/utils.py index 509ba890e30..f19c9901792 100644 --- a/Meta/Utils/utils.py +++ b/Meta/Utils/utils.py @@ -78,3 +78,9 @@ def underlying_type_for_enum(member_count: int) -> str: if member_count <= 0xFFFFFFFF: return "u32" return "u64" + + +def make_name_acceptable_cpp(name: str) -> str: + if name == "float": + return "float_" + return name