Meta+LibWeb: Initial scaffolding for CSS value type parsing code gen
In the future we should switch to using a better file format for this, i.e. one that supports directly pasting CSS grammar production blocks (https://drafts.csswg.org/css-values-4/#css-grammar-production-block) and has support for inline comments, but we use JSON for now for simplicity's sake.
This commit is contained in:
parent
324ed5de0d
commit
8849435d6f
6 changed files with 194 additions and 0 deletions
|
|
@ -415,3 +415,24 @@ The generated code provides:
|
|||
- `bool units_are_compatible(FooUnit, FooUnit)` which returns whether these are compatible - basically whether you can convert from one to the other.
|
||||
- `double ratio_between_units(FooUnit, FooUnit)` to get a multiplier for converting the first unit into the second.
|
||||
- `bool is_absolute(LengthUnit)`, `bool is_font_relative(LengthUnit)`, `bool is_viewport_relative(LengthUnit)`, and `bool is_relative(LengthUnit)` for checking the category of length units.
|
||||
|
||||
## ValueTypes.json
|
||||
|
||||
This is a JSON object with the keys being value type names, and the values being the definition of the value type.
|
||||
It generates Parser/GeneratedValueTypesParsing.h and Parser/GeneratedValueTypesParsing.cpp
|
||||
|
||||
NOTE: The generated parsing code is limited to the information given by the CSS value definition grammar, if there are
|
||||
additional requirements not representable in this grammar (e.g. bespoke resultant StyleValue types, default value
|
||||
handling, etc) parsing will need to be implemented manually.
|
||||
|
||||
Each value type has the following properties:
|
||||
| Field | Required | Description |
|
||||
|-------------|----------|-------------------------------------------------------------------|
|
||||
| `spec` | Yes | A link to the CSS specification where this value type is defined. |
|
||||
| `grammar` | Yes | The grammar of the CSS value type, as defined in the spec. |
|
||||
| `__comment` | No | Strings, for when you want to leave a note. |
|
||||
|
||||
The generated code provides:
|
||||
- A `GenerateValueTypes` enum, listing each of the generated value types.
|
||||
- For each of those...
|
||||
- A `CSS::Parser::Parser::parse_foo_value` method to parse the value type.
|
||||
|
|
|
|||
|
|
@ -1214,6 +1214,7 @@ set(GENERATED_SOURCES
|
|||
CSS/Keyword.cpp
|
||||
CSS/MathFunctions.cpp
|
||||
CSS/MediaFeatureID.cpp
|
||||
CSS/Parser/GeneratedValueTypesParsing.cpp
|
||||
CSS/PropertyID.cpp
|
||||
CSS/PseudoClass.cpp
|
||||
CSS/PseudoElement.cpp
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <LibWeb/CSS/PageSelector.h>
|
||||
#include <LibWeb/CSS/ParsedFontFace.h>
|
||||
#include <LibWeb/CSS/Parser/ComponentValue.h>
|
||||
#include <LibWeb/CSS/Parser/GeneratedValueTypesParsing.h>
|
||||
#include <LibWeb/CSS/Parser/RuleContext.h>
|
||||
#include <LibWeb/CSS/Parser/TokenStream.h>
|
||||
#include <LibWeb/CSS/Parser/Tokenizer.h>
|
||||
|
|
@ -565,6 +566,11 @@ private:
|
|||
RefPtr<StyleValue const> parse_white_space_trim_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<StyleValue const> parse_will_change_value(TokenStream<ComponentValue>&);
|
||||
|
||||
#define __ENUMERATE_GENERATED_CSS_VALUE_TYPE(value_type_name) \
|
||||
RefPtr<StyleValue const> parse_##value_type_name##_value(TokenStream<ComponentValue>& tokens);
|
||||
ENUMERATE_GENERATED_CSS_VALUE_TYPES
|
||||
#undef __ENUMERATE_GENERATED_CSS_VALUE_TYPE
|
||||
|
||||
RefPtr<CalculationNode const> convert_to_calculation_node(CalcParsing::Node const&, CalculationContext const&);
|
||||
RefPtr<CalculationNode const> parse_a_calculation(TokenStream<ComponentValue>&, CalculationContext const&);
|
||||
|
||||
|
|
|
|||
1
Libraries/LibWeb/CSS/ValueTypes.json
Normal file
1
Libraries/LibWeb/CSS/ValueTypes.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -86,6 +86,15 @@ function (generate_css_implementation)
|
|||
arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/TransformFunctions.json"
|
||||
)
|
||||
|
||||
invoke_py_generator(
|
||||
"GeneratedValueTypesParsing.cpp"
|
||||
"generate_libweb_css_value_types_parsing.py"
|
||||
"${LIBWEB_INPUT_FOLDER}/CSS/ValueTypes.json"
|
||||
"CSS/Parser/GeneratedValueTypesParsing.h"
|
||||
"CSS/Parser/GeneratedValueTypesParsing.cpp"
|
||||
arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/ValueTypes.json"
|
||||
)
|
||||
|
||||
invoke_py_generator(
|
||||
"Units.cpp"
|
||||
"generate_libweb_css_units.py"
|
||||
|
|
|
|||
156
Meta/Generators/generate_libweb_css_value_types_parsing.py
Normal file
156
Meta/Generators/generate_libweb_css_value_types_parsing.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2026, Callum Law <callumlaw1709@outlook.com>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import TextIO
|
||||
|
||||
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from Utils.utils import snake_casify
|
||||
|
||||
|
||||
def json_is_valid(value_type_data: dict[str, Any], json_path: str) -> bool:
|
||||
is_valid = True
|
||||
most_recent_value_type_name = ""
|
||||
|
||||
for value_type_name, value_type_definition in value_type_data.items():
|
||||
if value_type_name.lower() < most_recent_value_type_name.lower():
|
||||
print(
|
||||
f"{json_path}: Value type `{value_type_name}` is in the wrong position. Please keep this list alphabetical!",
|
||||
file=sys.stderr,
|
||||
)
|
||||
is_valid = False
|
||||
|
||||
most_recent_value_type_name = value_type_name
|
||||
|
||||
if not isinstance(value_type_definition, dict):
|
||||
print(f"{json_path}: Value type `{value_type_name}` is not an object", file=sys.stderr)
|
||||
is_valid = False
|
||||
continue
|
||||
|
||||
if "spec" not in value_type_definition:
|
||||
print(f"{json_path}: Value type `{value_type_name}` is missing a spec link", file=sys.stderr)
|
||||
is_valid = False
|
||||
elif not isinstance(value_type_definition["spec"], str):
|
||||
print(f"{json_path}: Value type `{value_type_name}` has a spec field that is not a string", file=sys.stderr)
|
||||
is_valid = False
|
||||
|
||||
if "grammar" not in value_type_definition:
|
||||
print(f"{json_path}: Value type `{value_type_name}` is missing a grammar", file=sys.stderr)
|
||||
is_valid = False
|
||||
elif not isinstance(value_type_definition["grammar"], str):
|
||||
print(
|
||||
f"{json_path}: Value type `{value_type_name}` has a grammar field that is not a string",
|
||||
file=sys.stderr,
|
||||
)
|
||||
is_valid = False
|
||||
|
||||
for field_name in value_type_definition:
|
||||
if field_name in ("spec", "grammar", "__comment"):
|
||||
continue
|
||||
|
||||
print(
|
||||
f"{json_path}: Value type `{value_type_name}` has an unexpected field `{field_name}`",
|
||||
file=sys.stderr,
|
||||
)
|
||||
is_valid = False
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
def value_type_name_to_snake_case(value_type_name: str) -> str:
|
||||
return snake_casify(value_type_name[1:-1])
|
||||
|
||||
|
||||
def generate_header_file(out: TextIO, value_type_data: dict[str, Any]) -> None:
|
||||
out.write("""// This file is generated by generate_libweb_css_value_types_parsing.py
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
#define ENUMERATE_GENERATED_CSS_VALUE_TYPES \\
|
||||
""")
|
||||
|
||||
for value_type_name in value_type_data:
|
||||
out.write(f" __ENUMERATE_GENERATED_CSS_VALUE_TYPE({value_type_name_to_snake_case(value_type_name)}) \\\n")
|
||||
|
||||
out.write("\n")
|
||||
out.write("}")
|
||||
|
||||
|
||||
def generate_implementation_file(out: TextIO, value_type_data: dict[str, Any]) -> None:
|
||||
out.write("""// This file is generated by generate_libweb_css_value_types_parsing.py
|
||||
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
|
||||
namespace Web::CSS::Parser {
|
||||
|
||||
""")
|
||||
|
||||
for value_type_name, value_type_definition in value_type_data.items():
|
||||
spec_link = value_type_definition["spec"]
|
||||
grammar = value_type_definition["grammar"]
|
||||
name_snake_case = value_type_name_to_snake_case(value_type_name)
|
||||
|
||||
out.write(f"""
|
||||
// {spec_link}
|
||||
RefPtr<StyleValue const> Parser::parse_{name_snake_case}_value(TokenStream<ComponentValue>& tokens)
|
||||
{{
|
||||
// {value_type_name} = {grammar}
|
||||
(void)tokens;
|
||||
// FIXME: Generate parser code for this value type.
|
||||
return nullptr;
|
||||
}}
|
||||
""")
|
||||
|
||||
out.write("}\n")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Generate CSS value types parsing methods", 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 GeneratedValueTypesParsing header file to generate",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--implementation",
|
||||
required=True,
|
||||
help="Path to the GeneratedValueTypesParsing 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 json_file:
|
||||
value_type_data = json.load(json_file)
|
||||
|
||||
if not isinstance(value_type_data, dict):
|
||||
raise RuntimeError(f"{args.json}: expected a JSON object")
|
||||
|
||||
if not json_is_valid(value_type_data, args.json):
|
||||
sys.exit(1)
|
||||
|
||||
with (
|
||||
open(args.header, "w", encoding="utf-8") as header_file,
|
||||
open(args.implementation, "w", encoding="utf-8") as implementation_file,
|
||||
):
|
||||
generate_header_file(header_file, value_type_data)
|
||||
generate_implementation_file(implementation_file, value_type_data)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Reference in a new issue