mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
Meta: Replace GenerateCSSMathFunctions with a python generator
This commit is contained in:
committed by
Shannon Booth
parent
143f1bc34d
commit
353846fe79
Notes:
github-actions[bot]
2026-04-25 10:04:09 +00:00
Author: https://github.com/AtkinsSJ Commit: https://github.com/LadybirdBrowser/ladybird/commit/353846fe79a Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/9083
@@ -29,9 +29,9 @@ function (generate_css_implementation)
|
||||
arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/EnvironmentVariables.json"
|
||||
)
|
||||
|
||||
invoke_cpp_generator(
|
||||
invoke_py_generator(
|
||||
"MathFunctions.cpp"
|
||||
Lagom::GenerateCSSMathFunctions
|
||||
"generate_libweb_css_math_functions.py"
|
||||
"${LIBWEB_INPUT_FOLDER}/CSS/MathFunctions.json"
|
||||
"CSS/MathFunctions.h"
|
||||
"CSS/MathFunctions.cpp"
|
||||
|
||||
446
Meta/Generators/generate_libweb_css_math_functions.py
Normal file
446
Meta/Generators/generate_libweb_css_math_functions.py
Normal file
@@ -0,0 +1,446 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2022-2026, Sam Atkins <sam@ladybird.org>
|
||||
# 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 title_casify
|
||||
|
||||
TYPE_CHECKS = {
|
||||
"<angle>": "matches_angle(percentages_resolve_as)",
|
||||
"<dimension>": "matches_dimension()",
|
||||
"<flex>": "matches_flex(percentages_resolve_as)",
|
||||
"<frequency>": "matches_frequency(percentages_resolve_as)",
|
||||
"<length>": "matches_length(percentages_resolve_as)",
|
||||
"<number>": "matches_number(percentages_resolve_as)",
|
||||
"<percentage>": "matches_percentage()",
|
||||
"<resolution>": "matches_resolution(percentages_resolve_as)",
|
||||
"<time>": "matches_time(percentages_resolve_as)",
|
||||
}
|
||||
|
||||
|
||||
def generate_calculation_type_check(calculation_variable_name: str, parameter_types: str) -> str:
|
||||
parts = []
|
||||
for allowed_type_name in parameter_types.split("|"):
|
||||
if allowed_type_name not in TYPE_CHECKS:
|
||||
print(f"I don't know what '{allowed_type_name}' is!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
parts.append(f"{calculation_variable_name}.{TYPE_CHECKS[allowed_type_name]}")
|
||||
return " || ".join(parts)
|
||||
|
||||
|
||||
def write_header_file(out: TextIO, functions_data: dict) -> None:
|
||||
out.write("""
|
||||
// This file is generated by GenerateCSSMathFunctions.cpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StringView.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
enum class MathFunction {
|
||||
Calc,
|
||||
""")
|
||||
|
||||
for name in functions_data:
|
||||
out.write(f" {title_casify(name)},\n")
|
||||
|
||||
out.write("""
|
||||
};
|
||||
|
||||
Optional<MathFunction> math_function_from_string(StringView);
|
||||
""")
|
||||
out.write(" \n")
|
||||
out.write("""}
|
||||
""")
|
||||
|
||||
|
||||
def write_implementation_file(out: TextIO, functions_data: dict) -> None:
|
||||
out.write("""
|
||||
// This file is generated by GenerateCSSMathFunctions.cpp
|
||||
|
||||
#include <LibWeb/CSS/Enums.h>
|
||||
#include <LibWeb/CSS/MathFunctions.h>
|
||||
#include <LibWeb/CSS/Parser/ErrorReporter.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/RandomValueSharingStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
Optional<MathFunction> math_function_from_string(StringView name)
|
||||
{
|
||||
if (name.equals_ignoring_ascii_case("calc"sv))
|
||||
return MathFunction::Calc;
|
||||
""")
|
||||
|
||||
for name in functions_data:
|
||||
out.write(f"""
|
||||
if (name.equals_ignoring_ascii_case("{name}"sv))
|
||||
return MathFunction::{title_casify(name)};
|
||||
""")
|
||||
|
||||
out.write("""
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Web::CSS::Parser {
|
||||
|
||||
static Optional<RoundingStrategy> parse_rounding_strategy(TokenStream<ComponentValue>& stream)
|
||||
{
|
||||
stream.discard_whitespace();
|
||||
if (!stream.has_next_token())
|
||||
return {};
|
||||
|
||||
auto& ident = stream.consume_a_token();
|
||||
if (!ident.is(Token::Type::Ident))
|
||||
return {};
|
||||
|
||||
stream.discard_whitespace();
|
||||
if (stream.has_next_token())
|
||||
return {};
|
||||
|
||||
auto maybe_keyword = keyword_from_string(ident.token().ident());
|
||||
if (!maybe_keyword.has_value())
|
||||
return {};
|
||||
|
||||
return keyword_to_rounding_strategy(maybe_keyword.value());
|
||||
}
|
||||
|
||||
RefPtr<CalculationNode const> Parser::parse_math_function(Function const& function, CalculationContext const& context)
|
||||
{
|
||||
TokenStream stream { function.value };
|
||||
auto arguments = parse_a_comma_separated_list_of_component_values(stream);
|
||||
auto const& percentages_resolve_as = context.percentages_resolve_as;
|
||||
""")
|
||||
|
||||
for name, function_data in functions_data.items():
|
||||
parameters = function_data["parameters"]
|
||||
parameter_validation_rule = function_data.get("parameter-validation")
|
||||
if parameter_validation_rule is not None:
|
||||
requires_same_parameters = parameter_validation_rule == "same"
|
||||
else:
|
||||
requires_same_parameters = True
|
||||
|
||||
name_titlecase = title_casify(name)
|
||||
out.write(f' if (function.name.equals_ignoring_ascii_case("{name}"sv)) {{\n')
|
||||
|
||||
if function_data.get("is-variadic", False):
|
||||
# Variadic function
|
||||
out.write(f"""
|
||||
Optional<NumericType> determined_argument_type;
|
||||
Vector<NonnullRefPtr<CalculationNode const>> parsed_arguments;
|
||||
parsed_arguments.ensure_capacity(arguments.size());
|
||||
|
||||
for (auto& argument : arguments) {{
|
||||
TokenStream<ComponentValue> tokens {{ argument }};
|
||||
auto calculation_node = parse_a_calculation(tokens, context);
|
||||
if (!calculation_node) {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{{}} is not a valid calculation.", parsed_arguments.size())),
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
|
||||
auto maybe_argument_type = calculation_node->numeric_type();
|
||||
if (!maybe_argument_type.has_value()) {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{{}} couldn't determine its type.", parsed_arguments.size())),
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
auto argument_type = maybe_argument_type.release_value();
|
||||
|
||||
""")
|
||||
assert len(parameters) == 1
|
||||
parameter_type_string = parameters[0]["type"]
|
||||
type_check = generate_calculation_type_check("argument_type", parameter_type_string)
|
||||
out.write(f"""
|
||||
if (!({type_check})) {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{{}} type ({{}}) is not an accepted type.", parsed_arguments.size(), argument_type.dump())),
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
|
||||
if (!determined_argument_type.has_value()) {{
|
||||
determined_argument_type = move(argument_type);
|
||||
}} else {{
|
||||
""")
|
||||
if requires_same_parameters:
|
||||
out.write(f"""
|
||||
if (determined_argument_type != argument_type) {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{{}} type ({{}}) doesn't match type of previous arguments ({{}}).", parsed_arguments.size(), argument_type.dump(), determined_argument_type->dump())),
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
""")
|
||||
else:
|
||||
out.write(f"""
|
||||
if (auto consistent_type = determined_argument_type->consistent_type(argument_type); consistent_type.has_value()) {{
|
||||
determined_argument_type = consistent_type.release_value();
|
||||
}} else {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{{}} type ({{}}) is not consistent with type of previous arguments ({{}}).", parsed_arguments.size(), argument_type.dump(), determined_argument_type->dump())),
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
""")
|
||||
out.write(f"""
|
||||
}}
|
||||
|
||||
parsed_arguments.append(calculation_node.release_nonnull());
|
||||
}}
|
||||
|
||||
return {name_titlecase}CalculationNode::create(move(parsed_arguments));
|
||||
}}
|
||||
""")
|
||||
|
||||
else:
|
||||
# Function with specified parameters.
|
||||
min_argument_count = sum(1 for p in parameters if p.get("required") is True)
|
||||
max_argument_count = len(parameters)
|
||||
|
||||
if name == "random":
|
||||
out.write("""
|
||||
if (!context_allows_random_functions())
|
||||
return nullptr;
|
||||
|
||||
m_random_function_index++;
|
||||
""")
|
||||
|
||||
out.write(f"""
|
||||
if (arguments.size() < {min_argument_count} || arguments.size() > {max_argument_count}) {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Wrong number of arguments {{}}, expected between {min_argument_count} and {max_argument_count} inclusive.", arguments.size())),
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
size_t argument_index = 0;
|
||||
Optional<NumericType> determined_argument_type;
|
||||
""")
|
||||
|
||||
for parameter_index, parameter in enumerate(parameters):
|
||||
parameter_type_string = parameter["type"]
|
||||
parameter_required = parameter["required"]
|
||||
parameter_name = parameter["name"]
|
||||
|
||||
if parameter_type_string == "<rounding-strategy>":
|
||||
parameter_is_calculation = False
|
||||
parameter_type = "RoundingStrategy"
|
||||
parse_function = f"parse_rounding_strategy(tokens_{parameter_index})"
|
||||
check_function = ".has_value()"
|
||||
release_function = ".release_value()"
|
||||
default_value = parameter.get("default")
|
||||
if default_value is not None:
|
||||
parameter_default = f" = RoundingStrategy::{title_casify(default_value)}"
|
||||
else:
|
||||
parameter_default = ""
|
||||
elif parameter_type_string == "<random-value-sharing>":
|
||||
parameter_is_calculation = False
|
||||
parameter_type = "RefPtr<RandomValueSharingStyleValue const>"
|
||||
parse_function = f"parse_random_value_sharing(tokens_{parameter_index})"
|
||||
check_function = " != nullptr"
|
||||
release_function = ".release_nonnull()"
|
||||
parameter_default = (
|
||||
" = RandomValueSharingStyleValue::create_auto(random_value_sharing_auto_name(), false)"
|
||||
)
|
||||
else:
|
||||
# NOTE: This assumes everything not handled above is a calculation node of some kind.
|
||||
parameter_is_calculation = True
|
||||
parameter_type = "RefPtr<CalculationNode const>"
|
||||
parse_function = f"parse_a_calculation(tokens_{parameter_index}, context)"
|
||||
check_function = " != nullptr"
|
||||
release_function = ".release_nonnull()"
|
||||
default_value = parameter.get("default")
|
||||
if default_value is not None:
|
||||
parameter_default = (
|
||||
f" = NumericCalculationNode::from_keyword(Keyword::{title_casify(default_value)}, context)"
|
||||
)
|
||||
else:
|
||||
parameter_default = ""
|
||||
|
||||
out.write(f"""
|
||||
{parameter_type} parameter_{parameter_index}{parameter_default};
|
||||
""")
|
||||
|
||||
if parameter_required:
|
||||
out.write(f"""
|
||||
if (argument_index >= arguments.size()) {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = "Missing required argument '{parameter_name}'."_string,
|
||||
}});
|
||||
return nullptr;
|
||||
}} else {{
|
||||
""")
|
||||
else:
|
||||
out.write("""
|
||||
if (argument_index < arguments.size()) {
|
||||
""")
|
||||
|
||||
out.write(f"""
|
||||
TokenStream tokens_{parameter_index} {{ arguments[argument_index] }};
|
||||
auto maybe_parsed_argument_{parameter_index} = {parse_function};
|
||||
if (maybe_parsed_argument_{parameter_index}{check_function}) {{
|
||||
parameter_{parameter_index} = maybe_parsed_argument_{parameter_index}{release_function};
|
||||
argument_index++;
|
||||
""")
|
||||
if parameter_required:
|
||||
out.write(f"""
|
||||
}} else {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = "Failed to parse required argument '{parameter_name}'."_string,
|
||||
}});
|
||||
return nullptr;
|
||||
""")
|
||||
out.write("""
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
if parameter_is_calculation:
|
||||
parameter_type_variable = f"argument_type_{parameter_index}"
|
||||
type_check = generate_calculation_type_check(parameter_type_variable, parameter_type_string)
|
||||
out.write(f"""
|
||||
if (parameter_{parameter_index}) {{
|
||||
auto maybe_argument_type_{parameter_index} = parameter_{parameter_index}->numeric_type();
|
||||
if (!maybe_argument_type_{parameter_index}.has_value()) {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = "Argument '{parameter_name}' couldn't determine its type."_string,
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
auto argument_type_{parameter_index} = maybe_argument_type_{parameter_index}.release_value();
|
||||
|
||||
if (!({type_check})) {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument '{parameter_name}' type ({{}}) is not an accepted type.", argument_type_{parameter_index}.dump())),
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
|
||||
if (!determined_argument_type.has_value()) {{
|
||||
determined_argument_type = argument_type_{parameter_index};
|
||||
}} else {{
|
||||
""")
|
||||
if requires_same_parameters:
|
||||
out.write(f"""
|
||||
if (determined_argument_type != argument_type_{parameter_index}) {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument '{parameter_name}' type ({{}}) doesn't match type of previous arguments ({{}}).", argument_type_{parameter_index}.dump(), determined_argument_type->dump())),
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
""")
|
||||
else:
|
||||
out.write(f"""
|
||||
if (auto consistent_type = determined_argument_type->consistent_type(argument_type_{parameter_index}); consistent_type.has_value()) {{
|
||||
determined_argument_type = consistent_type.release_value();
|
||||
}} else {{
|
||||
ErrorReporter::the().report(InvalidValueError {{
|
||||
.value_type = "{name}()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument '{parameter_name}' type ({{}}) is not consistent with type of previous arguments ({{}}).", argument_type_{parameter_index}.dump(), determined_argument_type->dump())),
|
||||
}});
|
||||
return nullptr;
|
||||
}}
|
||||
""")
|
||||
out.write("""
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
out.write("""
|
||||
if (argument_index < arguments.size())
|
||||
return nullptr;
|
||||
""")
|
||||
# Generate the call to the constructor
|
||||
out.write(f" return {name_titlecase}CalculationNode::create(")
|
||||
for parameter_index, parameter in enumerate(parameters):
|
||||
parameter_type_string = parameter["type"]
|
||||
if parameter_type_string == "<rounding-strategy>":
|
||||
release_value = ""
|
||||
else:
|
||||
if parameter["required"] or parameter.get("default") is not None:
|
||||
release_value = ".release_nonnull()"
|
||||
else:
|
||||
release_value = ""
|
||||
|
||||
if parameter_index == 0:
|
||||
out.write(f"parameter_{parameter_index}{release_value}")
|
||||
else:
|
||||
out.write(f", parameter_{parameter_index}{release_value}")
|
||||
out.write(""");
|
||||
}
|
||||
""")
|
||||
|
||||
out.write("""
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
""")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate CSS MathFunctions", 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 MathFunctions header file to generate")
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--implementation",
|
||||
required=True,
|
||||
help="Path to the MathFunctions 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:
|
||||
functions_data = json.load(input_file)
|
||||
|
||||
with open(args.header, "w", encoding="utf-8") as output_file:
|
||||
write_header_file(output_file, functions_data)
|
||||
|
||||
with open(args.implementation, "w", encoding="utf-8") as output_file:
|
||||
write_implementation_file(output_file, functions_data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,6 +1,5 @@
|
||||
set(SOURCES "") # avoid pulling SOURCES from parent scope
|
||||
|
||||
lagom_tool(GenerateCSSMathFunctions SOURCES GenerateCSSMathFunctions.cpp LIBS LibMain)
|
||||
lagom_tool(GenerateCSSNumericFactoryMethods SOURCES GenerateCSSNumericFactoryMethods.cpp LIBS LibMain)
|
||||
lagom_tool(GenerateCSSPropertyID SOURCES GenerateCSSPropertyID.cpp LIBS LibMain)
|
||||
lagom_tool(GenerateCSSPseudoElement SOURCES GenerateCSSPseudoElement.cpp LIBS LibMain)
|
||||
|
||||
@@ -1,514 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "GeneratorUtil.h"
|
||||
#include <AK/SourceGenerator.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibMain/Main.h>
|
||||
|
||||
namespace {
|
||||
|
||||
ErrorOr<void> generate_header_file(JsonObject& functions_data, Core::File& file)
|
||||
{
|
||||
StringBuilder builder;
|
||||
SourceGenerator generator { builder };
|
||||
|
||||
generator.append(R"~~~(
|
||||
// This file is generated by GenerateCSSMathFunctions.cpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StringView.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
enum class MathFunction {
|
||||
Calc,
|
||||
)~~~");
|
||||
|
||||
functions_data.for_each_member([&](auto& name, auto&) {
|
||||
auto member_generator = generator.fork();
|
||||
member_generator.set("name:titlecase", title_casify(name));
|
||||
member_generator.appendln(" @name:titlecase@,"sv);
|
||||
});
|
||||
|
||||
generator.append(R"~~~(
|
||||
};
|
||||
|
||||
Optional<MathFunction> math_function_from_string(StringView);
|
||||
|
||||
}
|
||||
)~~~");
|
||||
|
||||
TRY(file.write_until_depleted(generator.as_string_view().bytes()));
|
||||
return {};
|
||||
}
|
||||
|
||||
String generate_calculation_type_check(StringView calculation_variable_name, StringView parameter_types)
|
||||
{
|
||||
StringBuilder builder;
|
||||
auto allowed_types = parameter_types.split_view('|');
|
||||
bool first_type_check = true;
|
||||
for (auto const& allowed_type_name : allowed_types) {
|
||||
if (!first_type_check)
|
||||
builder.append(" || "sv);
|
||||
first_type_check = false;
|
||||
|
||||
if (allowed_type_name == "<angle>"sv) {
|
||||
builder.appendff("{}.{}", calculation_variable_name, "matches_angle(percentages_resolve_as)"sv);
|
||||
} else if (allowed_type_name == "<dimension>"sv) {
|
||||
builder.appendff("{}.{}", calculation_variable_name, "matches_dimension()"sv);
|
||||
} else if (allowed_type_name == "<flex>"sv) {
|
||||
builder.appendff("{}.{}", calculation_variable_name, "matches_flex(percentages_resolve_as)"sv);
|
||||
} else if (allowed_type_name == "<frequency>"sv) {
|
||||
builder.appendff("{}.{}", calculation_variable_name, "matches_frequency(percentages_resolve_as)"sv);
|
||||
} else if (allowed_type_name == "<length>"sv) {
|
||||
builder.appendff("{}.{}", calculation_variable_name, "matches_length(percentages_resolve_as)"sv);
|
||||
} else if (allowed_type_name == "<number>"sv) {
|
||||
builder.appendff("{}.{}", calculation_variable_name, "matches_number(percentages_resolve_as)"sv);
|
||||
} else if (allowed_type_name == "<percentage>"sv) {
|
||||
builder.appendff("{}.{}", calculation_variable_name, "matches_percentage()"sv);
|
||||
} else if (allowed_type_name == "<resolution>"sv) {
|
||||
builder.appendff("{}.{}", calculation_variable_name, "matches_resolution(percentages_resolve_as)"sv);
|
||||
} else if (allowed_type_name == "<time>"sv) {
|
||||
builder.appendff("{}.{}", calculation_variable_name, "matches_time(percentages_resolve_as)"sv);
|
||||
} else {
|
||||
dbgln("I don't know what '{}' is!", allowed_type_name);
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
ErrorOr<void> generate_implementation_file(JsonObject& functions_data, Core::File& file)
|
||||
{
|
||||
StringBuilder builder;
|
||||
SourceGenerator generator { builder };
|
||||
|
||||
generator.append(R"~~~(
|
||||
// This file is generated by GenerateCSSMathFunctions.cpp
|
||||
|
||||
#include <LibWeb/CSS/Enums.h>
|
||||
#include <LibWeb/CSS/MathFunctions.h>
|
||||
#include <LibWeb/CSS/Parser/ErrorReporter.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/RandomValueSharingStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
Optional<MathFunction> math_function_from_string(StringView name)
|
||||
{
|
||||
if (name.equals_ignoring_ascii_case("calc"sv))
|
||||
return MathFunction::Calc;
|
||||
)~~~");
|
||||
|
||||
functions_data.for_each_member([&](auto& name, auto&) {
|
||||
auto member_generator = generator.fork();
|
||||
member_generator.set("name:lowercase", name);
|
||||
member_generator.set("name:titlecase", title_casify(name));
|
||||
member_generator.append(R"~~~(
|
||||
if (name.equals_ignoring_ascii_case("@name:lowercase@"sv))
|
||||
return MathFunction::@name:titlecase@;
|
||||
)~~~");
|
||||
});
|
||||
|
||||
generator.append(R"~~~(
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Web::CSS::Parser {
|
||||
|
||||
static Optional<RoundingStrategy> parse_rounding_strategy(TokenStream<ComponentValue>& stream)
|
||||
{
|
||||
stream.discard_whitespace();
|
||||
if (!stream.has_next_token())
|
||||
return {};
|
||||
|
||||
auto& ident = stream.consume_a_token();
|
||||
if (!ident.is(Token::Type::Ident))
|
||||
return {};
|
||||
|
||||
stream.discard_whitespace();
|
||||
if (stream.has_next_token())
|
||||
return {};
|
||||
|
||||
auto maybe_keyword = keyword_from_string(ident.token().ident());
|
||||
if (!maybe_keyword.has_value())
|
||||
return {};
|
||||
|
||||
return keyword_to_rounding_strategy(maybe_keyword.value());
|
||||
}
|
||||
|
||||
RefPtr<CalculationNode const> Parser::parse_math_function(Function const& function, CalculationContext const& context)
|
||||
{
|
||||
TokenStream stream { function.value };
|
||||
auto arguments = parse_a_comma_separated_list_of_component_values(stream);
|
||||
auto const& percentages_resolve_as = context.percentages_resolve_as;
|
||||
)~~~");
|
||||
|
||||
functions_data.for_each_member([&](auto& name, JsonValue const& value) -> void {
|
||||
auto& function_data = value.as_object();
|
||||
auto& parameters = function_data.get_array("parameters"sv).value();
|
||||
auto parameter_validation_rule = function_data.get_string("parameter-validation"sv);
|
||||
bool requires_same_parameters = parameter_validation_rule.has_value() ? (parameter_validation_rule == "same"sv) : true;
|
||||
|
||||
auto function_generator = generator.fork();
|
||||
function_generator.set("name:lowercase", name);
|
||||
function_generator.set("name:titlecase", title_casify(name));
|
||||
function_generator.appendln(" if (function.name.equals_ignoring_ascii_case(\"@name:lowercase@\"sv)) {");
|
||||
if (function_data.get_bool("is-variadic"sv).value_or(false)) {
|
||||
// Variadic function
|
||||
function_generator.append(R"~~~(
|
||||
Optional<NumericType> determined_argument_type;
|
||||
Vector<NonnullRefPtr<CalculationNode const>> parsed_arguments;
|
||||
parsed_arguments.ensure_capacity(arguments.size());
|
||||
|
||||
for (auto& argument : arguments) {
|
||||
TokenStream<ComponentValue> tokens { argument };
|
||||
auto calculation_node = parse_a_calculation(tokens, context);
|
||||
if (!calculation_node) {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{} is not a valid calculation.", parsed_arguments.size())),
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto maybe_argument_type = calculation_node->numeric_type();
|
||||
if (!maybe_argument_type.has_value()) {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{} couldn't determine its type.", parsed_arguments.size())),
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
auto argument_type = maybe_argument_type.release_value();
|
||||
|
||||
)~~~");
|
||||
// Generate some type checks
|
||||
VERIFY(parameters.size() == 1);
|
||||
auto& parameter_data = parameters[0].as_object();
|
||||
auto parameter_type_string = parameter_data.get_string("type"sv).value();
|
||||
function_generator.set("type_check", generate_calculation_type_check("argument_type"sv, parameter_type_string));
|
||||
function_generator.append(R"~~~(
|
||||
if (!(@type_check@)) {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{} type ({}) is not an accepted type.", parsed_arguments.size(), argument_type.dump())),
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!determined_argument_type.has_value()) {
|
||||
determined_argument_type = move(argument_type);
|
||||
} else {
|
||||
)~~~");
|
||||
if (requires_same_parameters) {
|
||||
function_generator.append(R"~~~(
|
||||
if (determined_argument_type != argument_type) {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{} type ({}) doesn't match type of previous arguments ({}).", parsed_arguments.size(), argument_type.dump(), determined_argument_type->dump())),
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
)~~~");
|
||||
} else {
|
||||
function_generator.append(R"~~~(
|
||||
if (auto consistent_type = determined_argument_type->consistent_type(argument_type); consistent_type.has_value()) {
|
||||
determined_argument_type = consistent_type.release_value();
|
||||
} else {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument #{} type ({}) is not consistent with type of previous arguments ({}).", parsed_arguments.size(), argument_type.dump(), determined_argument_type->dump())),
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
)~~~");
|
||||
}
|
||||
function_generator.append(R"~~~(
|
||||
}
|
||||
|
||||
parsed_arguments.append(calculation_node.release_nonnull());
|
||||
}
|
||||
|
||||
return @name:titlecase@CalculationNode::create(move(parsed_arguments));
|
||||
}
|
||||
)~~~");
|
||||
|
||||
} else {
|
||||
// Function with specified parameters.
|
||||
size_t min_argument_count = 0;
|
||||
size_t max_argument_count = parameters.size();
|
||||
parameters.for_each([&](JsonValue const& parameter_value) {
|
||||
auto& parameter = parameter_value.as_object();
|
||||
if (parameter.get_bool("required"sv) == true)
|
||||
min_argument_count++;
|
||||
});
|
||||
function_generator.set("min_argument_count", String::number(min_argument_count));
|
||||
function_generator.set("max_argument_count", String::number(max_argument_count));
|
||||
|
||||
if (name == "random") {
|
||||
function_generator.append(R"~~~(
|
||||
if (!context_allows_random_functions())
|
||||
return nullptr;
|
||||
|
||||
m_random_function_index++;
|
||||
)~~~");
|
||||
}
|
||||
|
||||
function_generator.append(R"~~~(
|
||||
if (arguments.size() < @min_argument_count@ || arguments.size() > @max_argument_count@) {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Wrong number of arguments {}, expected between @min_argument_count@ and @max_argument_count@ inclusive.", arguments.size())),
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
size_t argument_index = 0;
|
||||
Optional<NumericType> determined_argument_type;
|
||||
)~~~");
|
||||
|
||||
size_t parameter_index = 0;
|
||||
parameters.for_each([&](JsonValue const& parameter_value) {
|
||||
auto& parameter = parameter_value.as_object();
|
||||
auto parameter_type_string = parameter.get_string("type"sv).value();
|
||||
auto parameter_required = parameter.get_bool("required"sv).value();
|
||||
|
||||
auto parameter_generator = function_generator.fork();
|
||||
parameter_generator.set("parameter_name", parameter.get_string("name"sv).value());
|
||||
parameter_generator.set("parameter_index", String::number(parameter_index));
|
||||
|
||||
bool parameter_is_calculation;
|
||||
if (parameter_type_string == "<rounding-strategy>") {
|
||||
parameter_is_calculation = false;
|
||||
parameter_generator.set("parameter_type", "RoundingStrategy"_string);
|
||||
parameter_generator.set("parse_function", MUST(String::formatted("parse_rounding_strategy(tokens_{})", parameter_index)));
|
||||
parameter_generator.set("check_function", ".has_value()"_string);
|
||||
parameter_generator.set("release_function", ".release_value()"_string);
|
||||
if (auto default_value = parameter.get_string("default"sv); default_value.has_value()) {
|
||||
parameter_generator.set("parameter_default", MUST(String::formatted(" = RoundingStrategy::{}", title_casify(default_value.value()))));
|
||||
} else {
|
||||
parameter_generator.set("parameter_default", ""_string);
|
||||
}
|
||||
} else if (parameter_type_string == "<random-value-sharing>") {
|
||||
parameter_is_calculation = false;
|
||||
parameter_generator.set("parameter_type", "RefPtr<RandomValueSharingStyleValue const>"_string);
|
||||
parameter_generator.set("parse_function", MUST(String::formatted("parse_random_value_sharing(tokens_{})", parameter_index)));
|
||||
parameter_generator.set("check_function", " != nullptr"_string);
|
||||
parameter_generator.set("release_function", ".release_nonnull()"_string);
|
||||
parameter_generator.set("parameter_default", MUST(String::formatted(" = RandomValueSharingStyleValue::create_auto(random_value_sharing_auto_name(), false)")));
|
||||
} else {
|
||||
// NOTE: This assumes everything not handled above is a calculation node of some kind.
|
||||
parameter_is_calculation = true;
|
||||
parameter_generator.set("parameter_type", "RefPtr<CalculationNode const>"_string);
|
||||
parameter_generator.set("parse_function", MUST(String::formatted("parse_a_calculation(tokens_{}, context)", parameter_index)));
|
||||
parameter_generator.set("check_function", " != nullptr"_string);
|
||||
parameter_generator.set("release_function", ".release_nonnull()"_string);
|
||||
|
||||
// NOTE: We have exactly one default value in the data right now, and it's a `<calc-constant>`,
|
||||
// so that's all we handle.
|
||||
if (auto default_value = parameter.get_string("default"sv); default_value.has_value()) {
|
||||
parameter_generator.set("parameter_default", MUST(String::formatted(" = NumericCalculationNode::from_keyword(Keyword::{}, context)", title_casify(default_value.value()))));
|
||||
} else {
|
||||
parameter_generator.set("parameter_default", ""_string);
|
||||
}
|
||||
}
|
||||
|
||||
parameter_generator.append(R"~~~(
|
||||
@parameter_type@ parameter_@parameter_index@@parameter_default@;
|
||||
)~~~");
|
||||
|
||||
if (parameter_required) {
|
||||
parameter_generator.append(R"~~~(
|
||||
if (argument_index >= arguments.size()) {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = "Missing required argument '@parameter_name@'."_string,
|
||||
});
|
||||
return nullptr;
|
||||
} else {
|
||||
)~~~");
|
||||
} else {
|
||||
parameter_generator.append(R"~~~(
|
||||
if (argument_index < arguments.size()) {
|
||||
)~~~");
|
||||
}
|
||||
|
||||
parameter_generator.append(R"~~~(
|
||||
TokenStream tokens_@parameter_index@ { arguments[argument_index] };
|
||||
auto maybe_parsed_argument_@parameter_index@ = @parse_function@;
|
||||
if (maybe_parsed_argument_@parameter_index@@check_function@) {
|
||||
parameter_@parameter_index@ = maybe_parsed_argument_@parameter_index@@release_function@;
|
||||
argument_index++;
|
||||
)~~~");
|
||||
if (parameter_required) {
|
||||
parameter_generator.append(R"~~~(
|
||||
} else {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = "Failed to parse required argument '@parameter_name@'."_string,
|
||||
});
|
||||
return nullptr;
|
||||
)~~~");
|
||||
}
|
||||
parameter_generator.append(R"~~~(
|
||||
}
|
||||
}
|
||||
)~~~");
|
||||
|
||||
if (parameter_is_calculation) {
|
||||
auto parameter_type_variable = MUST(String::formatted("argument_type_{}", parameter_index));
|
||||
parameter_generator.set("type_check", generate_calculation_type_check(parameter_type_variable, parameter_type_string));
|
||||
parameter_generator.append(R"~~~(
|
||||
if (parameter_@parameter_index@) {
|
||||
auto maybe_argument_type_@parameter_index@ = parameter_@parameter_index@->numeric_type();
|
||||
if (!maybe_argument_type_@parameter_index@.has_value()) {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = "Argument '@parameter_name@' couldn't determine its type."_string,
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
auto argument_type_@parameter_index@ = maybe_argument_type_@parameter_index@.release_value();
|
||||
|
||||
if (!(@type_check@)) {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument '@parameter_name@' type ({}) is not an accepted type.", argument_type_@parameter_index@.dump())),
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!determined_argument_type.has_value()) {
|
||||
determined_argument_type = argument_type_@parameter_index@;
|
||||
} else {
|
||||
)~~~");
|
||||
|
||||
if (requires_same_parameters) {
|
||||
parameter_generator.append(R"~~~(
|
||||
if (determined_argument_type != argument_type_@parameter_index@) {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument '@parameter_name@' type ({}) doesn't match type of previous arguments ({}).", argument_type_@parameter_index@.dump(), determined_argument_type->dump())),
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
)~~~");
|
||||
} else {
|
||||
parameter_generator.append(R"~~~(
|
||||
if (auto consistent_type = determined_argument_type->consistent_type(argument_type_@parameter_index@); consistent_type.has_value()) {
|
||||
determined_argument_type = consistent_type.release_value();
|
||||
} else {
|
||||
ErrorReporter::the().report(InvalidValueError {
|
||||
.value_type = "@name:lowercase@()"_fly_string,
|
||||
.value_string = stream.dump_string(),
|
||||
.description = MUST(String::formatted("Argument '@parameter_name@' type ({}) is not consistent with type of previous arguments ({}).", argument_type_@parameter_index@.dump(), determined_argument_type->dump())),
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
)~~~");
|
||||
}
|
||||
parameter_generator.append(R"~~~(
|
||||
}
|
||||
}
|
||||
)~~~");
|
||||
}
|
||||
|
||||
parameter_index++;
|
||||
});
|
||||
|
||||
function_generator.append(R"~~~(
|
||||
if (argument_index < arguments.size())
|
||||
return nullptr;
|
||||
)~~~");
|
||||
// Generate the call to the constructor
|
||||
function_generator.append(" return @name:titlecase@CalculationNode::create("sv);
|
||||
parameter_index = 0;
|
||||
parameters.for_each([&](JsonValue const& parameter_value) {
|
||||
auto& parameter = parameter_value.as_object();
|
||||
auto parameter_type_string = parameter.get_string("type"sv).value();
|
||||
|
||||
auto parameter_generator = function_generator.fork();
|
||||
parameter_generator.set("parameter_index"sv, String::number(parameter_index));
|
||||
|
||||
if (parameter_type_string == "<rounding-strategy>"sv) {
|
||||
parameter_generator.set("release_value"sv, ""_string);
|
||||
} else {
|
||||
// NOTE: This assumes everything not handled above is a calculation node of some kind.
|
||||
if (parameter.get_bool("required"sv).value() || parameter.get_string("default"sv).has_value()) {
|
||||
parameter_generator.set("release_value"sv, ".release_nonnull()"_string);
|
||||
} else {
|
||||
parameter_generator.set("release_value", ""_string);
|
||||
}
|
||||
}
|
||||
|
||||
if (parameter_index == 0) {
|
||||
parameter_generator.append("parameter_@parameter_index@@release_value@"sv);
|
||||
} else {
|
||||
parameter_generator.append(", parameter_@parameter_index@@release_value@"sv);
|
||||
}
|
||||
parameter_index++;
|
||||
});
|
||||
function_generator.append(R"~~~();
|
||||
}
|
||||
)~~~");
|
||||
}
|
||||
});
|
||||
|
||||
generator.append(R"~~~(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
)~~~");
|
||||
|
||||
TRY(file.write_until_depleted(generator.as_string_view().bytes()));
|
||||
return {};
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
ErrorOr<int> 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 MathFunctions header file to generate", "generated-header-path", 'h', "generated-header-path");
|
||||
args_parser.add_option(generated_implementation_path, "Path to the MathFunctions 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 math_functions_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(math_functions_data, *generated_header_file));
|
||||
TRY(generate_implementation_file(math_functions_data, *generated_implementation_file));
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user