#!/usr/bin/env python3
#
# Copyright (C) 2022-2023 Apple Inc. All rights reserved.
# Copyright (C) 2024-2025 Samuel Weinig <sam@webkit.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.

import argparse
import collections
import enum
import functools
import itertools
import json
import os
import re
import subprocess
import sys
import textwrap


def quote_iterable(iterable, *, mark='"', suffix=''):
    return (f'{mark}{x}{mark}{suffix}' for x in iterable)


def count_iterable(iterable):
    return sum(1 for _ in iterable)


def compact(iterable):
    return filter(lambda value: value is not None, iterable)


def compact_map(function, iterable):
    return compact(map(function, iterable))


def flatten(list_to_flatten):
    flattened_list = []
    for element in list_to_flatten:
        if type(element) is list:
            flattened_list += element
        else:
            flattened_list += [element]
    return flattened_list


class Writer:
    def __init__(self, output):
        self.output = output
        self._indentation_level = 0

    TAB_SIZE = 4

    @property
    def _current_indent(self):
        return (self._indentation_level * Writer.TAB_SIZE) * ' '

    def write(self, text):
        self.output.write(self._current_indent)
        self.output.write(text)
        return self.newline()

    def write_block(self, text):
        self.output.write(textwrap.indent(textwrap.dedent(text), self._current_indent))
        return self.newline()

    def write_lines(self, iterable):
        for line in iterable:
            self.write(line)
        return self

    def newline(self):
        self.output.write(f'\n')
        return self

    class Indent:
        def __init__(self, writer):
            self.writer = writer

        def __enter__(self):
            self.writer._indentation_level += 1

        def __exit__(self, exc_type, exc_value, traceback):
            self.writer._indentation_level -= 1

    def indent(self):
        return Writer.Indent(self)


class Schema:
    class Entry:
        def __init__(self, key, *, allowed_types, default_value=None, required=False, convert_to=None):
            if default_value and required:
                raise Exception(f"Invalid Schema.Entry for '{key}'. Cannot specify both 'default_value' and 'required'.")

            self.key = key
            self.allowed_types = allowed_types
            self.default_value = default_value
            self.required = required
            self.convert_to = convert_to

    def __init__(self, *entries):
        self.entries = {}

        # Give the schema an implement entry called `comment`, allowing each group to have a top level comment.
        self.entries["comment"] = Schema.Entry("comment", allowed_types=[str])

        # For each entry, add it to the entries dictionary along with a support entry with key `*-comment` to
        # support all fields having an implicit `*-comment` field to go along with it.
        for entry in entries:
            self.entries[entry.key] = entry
            self.entries[entry.key + "-comment"] = Schema.Entry(entry.key + "-comment", allowed_types=[str])

    def __add__(self, other):
        return Schema(*list({**self.entries, **other.entries}.values()))

    def set_attributes_from_dictionary(self, dictionary, *, instance):
        for entry in self.entries.values():
            setattr(instance, entry.key.replace("-", "_"), dictionary.get(entry.key, entry.default_value))

    def validate_keys(self, parsing_context, key_path, dictionary, *, label):
        invalid_keys = list(filter(lambda key: key not in self.entries.keys(), dictionary.keys()))
        if len(invalid_keys) == 1:
            raise Exception(f"Invalid key for '{label} - {key_path}': {invalid_keys[0]}")
        if len(invalid_keys) > 1:
            raise Exception(f"Invalid keys for '{label} - {key_path}': {invalid_keys}")

    def validate_types(self, parsing_context, key_path, dictionary, *, label):
        for key, value in dictionary.items():
            if type(value) not in self.entries[key].allowed_types:
                raise Exception(f"Invalid type '{type(value)}' for key '{key}' in '{label} - {key_path}'. Expected type in set '{self.entries[key].allowed_types}'.")

    def validate_requirements(self, parsing_context, key_path, dictionary, *, label):
        for key, entry in self.entries.items():
            if entry.required and key not in dictionary:
                raise Exception(f"Required key '{key}' not found in '{label} - {key_path}'.")

    def apply_conversions(self, parsing_context, key_path, dictionary, *, label):
        for key, entry in self.entries.items():
            if entry.convert_to and key in dictionary:
                dictionary[key] = entry.convert_to.from_json(parsing_context, key_path, dictionary[key])

    def validate_dictionary(self, parsing_context, key_path, dictionary, *, label):
        self.validate_keys(parsing_context, key_path, dictionary, label=label)
        self.validate_types(parsing_context, key_path, dictionary, label=label)
        self.validate_requirements(parsing_context, key_path, dictionary, label=label)
        self.apply_conversions(parsing_context, key_path, dictionary, label=label)


class Name(object):
    special_case_name_to_id = {
        "url": "URL",
    }

    def __init__(self, name):
        self.name = name
        self.id_without_prefix = Name.convert_name_to_id(self.name)

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def convert_name_to_id(name):
        return Name.special_case_name_to_id.get(name) or re.sub(r'(^[^-])|-(.)', lambda m: (m[1] or m[2]).upper(), name)

    @property
    def id_without_prefix_with_lowercase_first_letter(self):
        return self.id_without_prefix[0].lower() + self.id_without_prefix[1:]


class PropertyName(Name):
    def __init__(self, name, *, name_for_methods=None):
        super().__init__(name)
        self.name_for_methods = PropertyName._compute_name_for_methods(name_for_methods, self.id_without_prefix)

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def _compute_name_for_methods(name_for_methods, id_without_prefix):
        if name_for_methods:
            return name_for_methods
        return id_without_prefix.replace("Webkit",  "")

    @property
    def id_without_scope(self):
        return f"CSSProperty{self.id_without_prefix}"

    @property
    def id(self):
        return f"CSSPropertyID::CSSProperty{self.id_without_prefix}"


class ValueKeywordName(Name):
    def __init__(self, name):
        super().__init__(name)

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.__str__()

    def from_json(parsing_context, key_path, json_value):
        assert(type(json_value) is str)
        return ValueKeywordName(json_value)

    @property
    def id_without_scope(self):
        return f"CSSValue{self.id_without_prefix}"

    @property
    def id(self):
        return f"CSSValueID::CSSValue{self.id_without_prefix}"


class Status:
    schema = Schema(
        Schema.Entry("enabled-by-default", allowed_types=[bool]),
        Schema.Entry("status", allowed_types=[str]),
    )

    def __init__(self, **dictionary):
        Status.schema.set_attributes_from_dictionary(dictionary, instance=self)

    def __str__(self):
        return f"Status {vars(self)}"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, json_value):
        if type(json_value) is str:
            return Status(status=json_value)

        assert(type(json_value) is dict)
        Status.schema.validate_dictionary(parsing_context, f"{key_path}.status", json_value, label=f"Status")

        return Status(**json_value)


class Specification:
    schema = Schema(
        Schema.Entry("category", allowed_types=[str]),
        Schema.Entry("description", allowed_types=[str]),
        Schema.Entry("documentation-url", allowed_types=[str]),
        Schema.Entry("keywords", allowed_types=[list], default_value=[]),
        Schema.Entry("non-canonical-url", allowed_types=[str]),
        Schema.Entry("obsolete-category", allowed_types=[str]),
        Schema.Entry("obsolete-url", allowed_types=[str]),
        Schema.Entry("url", allowed_types=[str]),
    )

    def __init__(self, **dictionary):
        Specification.schema.set_attributes_from_dictionary(dictionary, instance=self)

    def __str__(self):
        return f"Specification {vars(self)}"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, json_value):
        assert(type(json_value) is dict)
        Specification.schema.validate_dictionary(parsing_context, f"{key_path}.specification", json_value, label=f"Specification")
        return Specification(**json_value)


class Value:
    schema = Schema(
        Schema.Entry("enable-if", allowed_types=[str]),
        Schema.Entry("settings-flag", allowed_types=[str]),
        Schema.Entry("status", allowed_types=[str]),
        Schema.Entry("url", allowed_types=[str]),
        Schema.Entry("value", allowed_types=[str], required=True),
    )

    def __init__(self, **dictionary):
        Value.schema.set_attributes_from_dictionary(dictionary, instance=self)
        self.value_keyword_name = ValueKeywordName(self.value)
        self.keyword_term = self._build_keyword_term()

    def _build_keyword_term(self):
        return KeywordTerm(self.value_keyword_name, comment=self.comment, settings_flag=self.settings_flag, status=self.status)

    def __str__(self):
        return f"Value {vars(self)}"

    def __repr__(self):
        return self.__str__()

    def __eq__(self, other):
        return self.value == other.value and self.settings_flag == other.settings_flag

    def __lt__(self, other):
        return self.value < other.value

    @staticmethod
    def from_json(parsing_context, key_path, json_value):
        if type(json_value) is str:
            return Value.from_json(parsing_context, key_path, {"value": json_value})

        assert(type(json_value) is dict)
        Value.schema.validate_dictionary(parsing_context, f"{key_path}.values", json_value, label=f"Value")

        if "enable-if" in json_value and not parsing_context.is_enabled(conditional=json_value["enable-if"]):
            if parsing_context.verbose:
                print(f"SKIPPED value {json_value['value']} in {key_path} due to failing to satisfy 'enable-if' condition, '{json_value['enable-if']}', with active macro set")
            return None

        if "status" in json_value and (json_value["status"] == "unimplemented" or json_value["status"] == "removed" or json_value["status"] == "not considering"):
            if parsing_context.verbose:
                print(f"SKIPPED value {json_value['value']} in {key_path} due to '{json_value['status']}' status designation.")
            return None

        if json_value.get("status", None) != "internal" and json_value["value"].startswith("-internal-"):
            raise Exception(f'Value "{json_value["value"]}" starts with "-internal-" but does not have "status": "internal" set.')

        return Value(**json_value)

    @property
    def id_without_prefix(self):
        return self.value_keyword_name.id_without_prefix

    @property
    def id_without_prefix_with_lowercase_first_letter(self):
        return self.value_keyword_name.id_without_prefix_with_lowercase_first_letter

    @property
    def id_without_scope(self):
        return self.value_keyword_name.id_without_scope

    @property
    def id(self):
        return self.value_keyword_name.id

    @property
    def name_for_methods(self):
        return self.value_keyword_name.name_for_methods

    @property
    def name(self):
        return self.value_keyword_name.name


class LogicalPropertyGroup:
    schema = Schema(
        Schema.Entry("name", allowed_types=[str], required=True),
        Schema.Entry("resolver", allowed_types=[str], required=True),
    )

    logical_property_group_resolvers = {
        "logical": {
            # Order matches LogicalBoxAxis enum in Source/WebCore/platform/BoxSides.h.
            "axis": ["inline", "block"],
            # Order matches LogicalBoxSide enum in Source/WebCore/platform/BoxSides.h.
            "side": ["block-start", "inline-end", "block-end", "inline-start"],
            # Order matches LogicalBoxCorner enum in Source/WebCore/platform/BoxSides.h.
            "corner": ["start-start", "start-end", "end-start", "end-end"],
        },
        "physical": {
            # Order matches BoxAxis enum in Source/WebCore/platform/BoxSides.h.
            "axis": ["horizontal", "vertical"],
            # Order matches BoxSide enum in Source/WebCore/platform/BoxSides.h.
            "side": ["top", "right", "bottom", "left"],
            # Order matches BoxCorner enum in Source/WebCore/platform/BoxSides.h.
            "corner": ["top-left", "top-right", "bottom-left", "bottom-right"],
        },
    }

    def __init__(self, **dictionary):
        LogicalPropertyGroup.schema.set_attributes_from_dictionary(dictionary, instance=self)
        self._update_kind_and_logic()

    def __str__(self):
        return f"LogicalPropertyGroup {vars(self)}"

    def __repr__(self):
        return self.__str__()

    def _update_kind_and_logic(self):
        for current_logic, current_resolvers_for_logic in LogicalPropertyGroup.logical_property_group_resolvers.items():
            for current_kind, resolver_list in current_resolvers_for_logic.items():
                for current_resolver in resolver_list:
                    if current_resolver == self.resolver:
                        self.kind = current_kind
                        self.logic = current_logic
                        return
        raise Exception(f"Unrecognized resolver \"{self.resolver}\"")

    @staticmethod
    def from_json(parsing_context, key_path, json_value):
        assert(type(json_value) is dict)
        LogicalPropertyGroup.schema.validate_dictionary(parsing_context, f"{key_path}.logical-property-group", json_value, label=f"LogicalPropertyGroup")
        return LogicalPropertyGroup(**json_value)


class Longhand:
    schema = Schema(
        Schema.Entry("enable-if", allowed_types=[str]),
        Schema.Entry("value", allowed_types=[str], required=True),
    )

    def __init__(self, **dictionary):
        Longhand.schema.set_attributes_from_dictionary(dictionary, instance=self)

    def __str__(self):
        return f"Longhand {vars(self)}"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, json_value):
        if type(json_value) is str:
            return Longhand.from_json(parsing_context, key_path, {"value": json_value})

        assert(type(json_value) is dict)
        Longhand.schema.validate_dictionary(parsing_context, f"{key_path}.longhands", json_value, label=f"Longhand")

        if "enable-if" in json_value and not parsing_context.is_enabled(conditional=json_value["enable-if"]):
            if parsing_context.verbose:
                print(f"SKIPPED longhand {json_value['value']} in {key_path} due to failing to satisfy 'enable-if' condition, '{json_value['enable-if']}', with active macro set")
            return None

        return Longhand(**json_value)


class StylePropertyCodeGenProperties:
    schema = Schema(
        Schema.Entry("aliases", allowed_types=[list], default_value=[]),
        Schema.Entry("animation-property", allowed_types=[bool], default_value=False),
        Schema.Entry("auto-functions", allowed_types=[bool], default_value=False),
        Schema.Entry("cascade-alias", allowed_types=[str]),
        Schema.Entry("color-property", allowed_types=[bool], default_value=False),
        Schema.Entry("computable", allowed_types=[bool]),
        Schema.Entry("disables-native-appearance", allowed_types=[bool], default_value=False),
        Schema.Entry("enable-if", allowed_types=[str]),
        Schema.Entry("fast-path-inherited", allowed_types=[bool], default_value=False),
        Schema.Entry("fill-layer-property", allowed_types=[bool], default_value=False),
        Schema.Entry("font-property", allowed_types=[bool], default_value=False),
        Schema.Entry("high-priority", allowed_types=[bool], default_value=False),
        Schema.Entry("internal-only", allowed_types=[bool], default_value=False),
        Schema.Entry("logical-property-group", allowed_types=[dict]),
        Schema.Entry("longhands", allowed_types=[list]),
        Schema.Entry("parser-exported", allowed_types=[bool]),
        Schema.Entry("parser-function", allowed_types=[str]),
        Schema.Entry("parser-function-allows-number-or-integer-input", allowed_types=[bool], default_value=False),
        Schema.Entry("parser-function-requires-additional-parameters", allowed_types=[list], default_value=[]),
        Schema.Entry("parser-function-requires-current-property", allowed_types=[bool], default_value=False),
        Schema.Entry("parser-function-requires-current-shorthand", allowed_types=[bool], default_value=False),
        Schema.Entry("parser-function-requires-value-pool", allowed_types=[bool], default_value=False),
        Schema.Entry("parser-grammar", allowed_types=[str]),
        Schema.Entry("parser-grammar-unused", allowed_types=[str]),
        Schema.Entry("parser-grammar-unused-reason", allowed_types=[str]),
        Schema.Entry("render-style-getter", allowed_types=[str]),
        Schema.Entry("render-style-initial", allowed_types=[str]),
        Schema.Entry("render-style-name-for-methods", allowed_types=[str]),
        Schema.Entry("render-style-setter", allowed_types=[str]),
        Schema.Entry("separator", allowed_types=[str]),
        Schema.Entry("settings-flag", allowed_types=[str]),
        Schema.Entry("sink-priority", allowed_types=[bool], default_value=False),
        Schema.Entry("skip-codegen", allowed_types=[bool], default_value=False),
        Schema.Entry("skip-parser", allowed_types=[bool], default_value=False),
        Schema.Entry("skip-style-builder", allowed_types=[bool], default_value=False),
        Schema.Entry("status", allowed_types=[str]),
        Schema.Entry("style-builder-conditional-converter", allowed_types=[str]),
        Schema.Entry("style-builder-converter", allowed_types=[str]),
        Schema.Entry("style-builder-custom", allowed_types=[str]),
        Schema.Entry("svg", allowed_types=[bool], default_value=False),
        Schema.Entry("top-priority", allowed_types=[bool], default_value=False),
        Schema.Entry("top-priority-reason", allowed_types=[str]),
        Schema.Entry("url", allowed_types=[str]),
        Schema.Entry("visited-link-color-support", allowed_types=[bool], default_value=False),
    )

    def __init__(self, property_name, **dictionary):
        StylePropertyCodeGenProperties.schema.set_attributes_from_dictionary(dictionary, instance=self)
        self.property_name = property_name

    def __str__(self):
        return f"StylePropertyCodeGenProperties {vars(self)}"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, name, json_value):
        if type(json_value) is list:
            json_value = parsing_context.select_enabled_variant(json_value, label=f"{key_path}.codegen-properties")

        assert(type(json_value) is dict)
        StylePropertyCodeGenProperties.schema.validate_dictionary(parsing_context, f"{key_path}.codegen-properties", json_value, label=f"StylePropertyCodeGenProperties")

        property_name = PropertyName(name, name_for_methods=json_value.get("render-style-name-for-methods"))

        if "render-style-getter" not in json_value:
            json_value["render-style-getter"] = property_name.name_for_methods[0].lower() + property_name.name_for_methods[1:]

        if "render-style-setter" not in json_value:
            json_value["render-style-setter"] = f"set{property_name.name_for_methods}"

        if "render-style-initial" not in json_value:
            if "fill-layer-property" in json_value:
                json_value["render-style-initial"] = f"initialFill{property_name.name_for_methods}"
            else:
                json_value["render-style-initial"] = f"initial{property_name.name_for_methods}"

        if "style-builder-custom" not in json_value:
            json_value["style-builder-custom"] = ""
        elif json_value["style-builder-custom"] == "All":
            json_value["style-builder-custom"] = "Initial|Inherit|Value"
        json_value["style-builder-custom"] = frozenset(json_value["style-builder-custom"].split("|"))

        if "logical-property-group" in json_value:
            if json_value.get("longhands"):
                raise Exception(f"{key_path} is a shorthand, but belongs to a logical property group.")
            json_value["logical-property-group"] = LogicalPropertyGroup.from_json(parsing_context, f"{key_path}.codegen-properties", json_value["logical-property-group"])

        if "longhands" in json_value:
            json_value["longhands"] = list(compact_map(lambda value: Longhand.from_json(parsing_context, f"{key_path}.codegen-properties", value), json_value["longhands"]))
            if not json_value["longhands"]:
                del json_value["longhands"]

        if "computable" in json_value:
            if json_value["computable"]:
                if json_value.get("internal-only", False):
                    raise Exception(f"{key_path} can't be both internal-only and computable.")
        else:
            if json_value.get("internal-only", False):
                json_value["computable"] = False
            else:
                json_value["computable"] = True

        if json_value.get("top-priority", False):
            if json_value.get("top-priority-reason") is None:
                raise Exception(f"{key_path} has top priority, but no reason justifying it.")
            if json_value.get("longhands"):
                raise Exception(f"{key_path} is a shorthand, but has top priority.")
            if json_value.get("high-priority", False):
                raise Exception(f"{key_path} can't have conflicting top/high priority.")

        if json_value.get("high-priority", False):
            if json_value.get("longhands"):
                raise Exception(f"{key_path} is a shorthand, but has high priority.")

        if json_value.get("sink-priority", False):
            if json_value.get("longhands") is not None:
                raise Exception(f"{key_path} is a shorthand, but has sink priority.")

        if json_value.get("cascade-alias"):
            if json_value.get("cascade-alias") == name:
                raise Exception(f"{key_path} can't have itself as a cascade alias property.")
            if json_value.get("longhands"):
                raise Exception(f"{key_path} can't be both a cascade alias and a shorthand.")

        if json_value.get("parser-grammar"):
            for entry_name in ["parser-function", "parser-function-requires-additional-parameters", "parser-function-requires-current-shorthand", "parser-function-requires-current-property", "parser-function-requires-value-pool", "skip-parser", "longhands"]:
                if entry_name in json_value:
                    raise Exception(f"{key_path} can't have both 'parser-grammar' and '{entry_name}'.")
            grammar = Grammar.from_string(parsing_context, f"{key_path}", name, json_value["parser-grammar"])
            grammar.perform_fixups(parsing_context.parsed_shared_grammar_rules)
            json_value["parser-grammar"] = grammar

        if json_value.get("parser-grammar-unused"):
            if "parser-grammar-unused-reason" not in json_value:
                raise Exception(f"{key_path} must have 'parser-grammar-unused-reason' specified when using 'parser-grammar-unused'.")
            # If we have a "parser-grammar-unused" specified, we still process it to ensure that at least it is syntatically valid, we just
            # won't actually use it for generation.
            grammar = Grammar.from_string(parsing_context, f"{key_path}", name, json_value["parser-grammar-unused"])
            grammar.perform_fixups(parsing_context.parsed_shared_grammar_rules)
            json_value["parser-grammar-unused"] = grammar

        if json_value.get("parser-grammar-unused-reason"):
            if "parser-grammar-unused" not in json_value:
                raise Exception(f"{key_path} must have 'parser-grammar-unused' specified when using 'parser-grammar-unused-reason'.")

        if json_value.get("parser-function"):
            if "parser-grammar-unused" not in json_value:
                raise Exception(f"{key_path} must have 'parser-grammar-unused' specified when using 'parser-function'.")
            for entry_name in ["skip-parser", "longhands", "parser-grammar"]:
                if entry_name in json_value:
                    raise Exception(f"{key_path} can't have both 'parser-function' and '{entry_name}'.")

        return StylePropertyCodeGenProperties(property_name, **json_value)

    @property
    def is_logical(self):
        if not self.logical_property_group:
            return False

        resolver = self.logical_property_group.resolver
        for logical_resolvers in LogicalPropertyGroup.logical_property_group_resolvers["logical"].values():
            for logical_resolver in logical_resolvers:
                if resolver == logical_resolver:
                    return True
        return False

class StyleProperty:
    schema = Schema(
        Schema.Entry("animation-type", allowed_types=[str]),
        Schema.Entry("codegen-properties", allowed_types=[dict, list]),
        Schema.Entry("inherited", allowed_types=[bool], default_value=False),
        Schema.Entry("initial", allowed_types=[str]),
        Schema.Entry("specification", allowed_types=[dict], convert_to=Specification),
        Schema.Entry("status", allowed_types=[dict, str], convert_to=Status),
        Schema.Entry("values", allowed_types=[list]),
    )

    def __init__(self, **dictionary):
        StyleProperty.schema.set_attributes_from_dictionary(dictionary, instance=self)
        self.property_name = self.codegen_properties.property_name

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, name, json_value):
        assert(type(json_value) is dict)
        StyleProperty.schema.validate_dictionary(parsing_context, f"{key_path}.{name}", json_value, label=f"Property")

        codegen_properties = StylePropertyCodeGenProperties.from_json(parsing_context, f"{key_path}.{name}", name, json_value.get("codegen-properties", {}))
        json_value["codegen-properties"] = codegen_properties

        if codegen_properties.enable_if is not None and not parsing_context.is_enabled(conditional=codegen_properties.enable_if):
            if parsing_context.verbose:
                print(f"SKIPPED {name} due to failing to satisfy 'enable-if' condition, '{json_value['codegen-properties'].enable_if}', with active macro set")
            return None

        if codegen_properties.skip_codegen is not None and codegen_properties.skip_codegen:
            if parsing_context.verbose:
                print(f"SKIPPED {name} due to 'skip-codegen'")
            return None

        if "animation-type" in json_value:
            VALID_ANIMATION_TYPES = [
                'discrete',
                'by computed value',
                'repeatable list',
                'see prose',
                'not animatable',
                'not animatable (needs triage)',
                'not animatable (legacy)',
                'not animatable (internal)'
            ]
            if json_value["animation-type"] not in VALID_ANIMATION_TYPES:
                raise Exception(f"'{name}' specified invalid animation type '{json_value['animation-type']}'. Must specify an animation type from {VALID_ANIMATION_TYPES}.")
        else:
            if not (codegen_properties.longhands or codegen_properties.cascade_alias):
                raise Exception(f"'{name}' must specify an 'animation-type'.")

        if "initial" not in json_value:
            if not (codegen_properties.longhands or codegen_properties.cascade_alias or codegen_properties.skip_style_builder):
                raise Exception(f"'{name}' must specify 'initial'.")

        if "values" in json_value:
            if not (codegen_properties.parser_grammar or codegen_properties.skip_parser or codegen_properties.parser_function or codegen_properties.longhands):
                raise Exception(f"'{name}' must specify a 'parser-grammar', 'skip-parser', 'parser-function' or 'longhands' when specifying a 'values' array.")

            json_value["values"] = list(filter(lambda value: value is not None, map(lambda value: Value.from_json(parsing_context, f"{key_path}.{name}", value), json_value["values"])))

            if codegen_properties.parser_grammar:
                codegen_properties.parser_grammar.perform_fixups_for_values_references(json_value["values"])
            elif codegen_properties.parser_grammar_unused:
                codegen_properties.parser_grammar_unused.perform_fixups_for_values_references(json_value["values"])

            if codegen_properties.parser_grammar:
                codegen_properties.parser_grammar.check_against_values(json_value.get("values", []))
            elif codegen_properties.parser_grammar_unused:
                if parsing_context.check_unused_grammars_values or parsing_context.verbose:
                    codegen_properties.parser_grammar_unused.check_against_values(json_value.get("values", []))

        return StyleProperty(**json_value)

    def perform_fixups_for_longhands(self, all_properties):
        # If 'longhands' was specified, replace the names with references to the Property objects.
        if self.codegen_properties.longhands:
            self.codegen_properties.longhands = [all_properties.all_by_name[longhand.value] for longhand in self.codegen_properties.longhands]

    def perform_fixups_for_cascade_alias_properties(self, all_properties):
        if self.codegen_properties.cascade_alias:
            if self.codegen_properties.cascade_alias not in all_properties.all_by_name:
                raise Exception(f"Property {self.name} is a cascade alias for an unknown property: {self.codegen_properties.cascade_alias}.")

            if not self.codegen_properties.skip_style_builder:
                raise Exception(f"Property {self.name} is a cascade alias and should also set 'skip-style-builder'.")

            self.codegen_properties.cascade_alias = all_properties.all_by_name[self.codegen_properties.cascade_alias]

    def perform_fixups_for_logical_property_groups(self, all_properties):
        if self.codegen_properties.logical_property_group:
            group_name = self.codegen_properties.logical_property_group.name
            resolver = self.codegen_properties.logical_property_group.resolver
            kind = self.codegen_properties.logical_property_group.kind
            logic = self.codegen_properties.logical_property_group.logic

            all_properties.logical_property_groups.setdefault(group_name, {})

            existing_kind = all_properties.logical_property_groups[group_name].get("kind")
            if existing_kind and existing_kind != kind:
                raise Exception(f"Logical property group \"{group_name}\" has resolvers of different kinds: {kind} and {existing_kind}.")

            all_properties.logical_property_groups[group_name]["kind"] = kind

            existing_logic = all_properties.logical_property_groups[group_name].get(logic)
            if existing_logic:
                existing_property = existing_logic.get(resolver)
                if existing_property:
                    raise Exception(f"Logical property group \"{group_name}\" has multiple \"{resolver}\" properties: {self.name} and {existing_property.name}.")
            all_properties.logical_property_groups[group_name].setdefault(logic, {})
            all_properties.logical_property_groups[group_name][logic][resolver] = self

    def perform_fixups(self, all_properties):
        self.perform_fixups_for_longhands(all_properties)
        self.perform_fixups_for_cascade_alias_properties(all_properties)
        self.perform_fixups_for_logical_property_groups(all_properties)

    @property
    def id_without_prefix(self):
        return self.property_name.id_without_prefix

    @property
    def id_without_prefix_with_lowercase_first_letter(self):
        return self.property_name.id_without_prefix_with_lowercase_first_letter

    @property
    def id_without_scope(self):
        return self.property_name.id_without_scope

    @property
    def id(self):
        return self.property_name.id

    # Used for parsing and consume methods. It is prefixed with a 'kind' for descriptors, and left unprefixed for style properties.
    # Examples:
    #       style property 'column-width' would generate a consume method called `consumeColumnWidth`
    #       @font-face descriptor 'font-display' would generate a consume method called `consumeFontFaceFontDisplay`
    @property
    def name_for_parsing_methods(self):
        return self.id_without_prefix

    @property
    def name_for_methods(self):
        return self.property_name.name_for_methods

    @property
    def name(self):
        return self.property_name.name

    @property
    def aliases(self):
        return self.codegen_properties.aliases

    @property
    def is_skipped_from_computed_style(self):
        if self.codegen_properties.internal_only:
            return True

        if not self.codegen_properties.computable:
            return True

        if self.codegen_properties.skip_style_builder and not self.codegen_properties.is_logical and not self.codegen_properties.cascade_alias:
            return True

        if self.codegen_properties.longhands is not None:
            for longhand in self.codegen_properties.longhands:
                if not longhand.is_skipped_from_computed_style:
                    return True

        return False

    # Specialized properties to compute method names.

    @property
    def method_name_for_ensure_animations_or_transitions(self):
        if "animation-" in self.name:
            return "ensureAnimations"
        if "transition-" in self.name:
            return "ensureTransitions"
        raise Exception(f"Unrecognized animation or transition property name: '{self.name}")

    @property
    def method_name_for_animations_or_transitions(self):
        if "animation-" in self.name:
            return "animations"
        if "transition-" in self.name:
            return "transitions"
        raise Exception(f"Unrecognized animation or transition property name: '{self.name}")

    @property
    def method_name_for_ensure_layers(self):
        if "background-" in self.name:
            return "ensureBackgroundLayers"
        if "mask-" in self.name:
            return "ensureMaskLayers"
        raise Exception(f"Unrecognized FillLayer property name: '{self.name}")

    @property
    def method_name_for_layers(self):
        if "background-" in self.name:
            return "backgroundLayers"
        if "mask-" in self.name:
            return "maskLayers"
        raise Exception(f"Unrecognized FillLayer property name: '{self.name}")

    @property
    def enum_name_for_layers_type(self):
        if "background-" in self.name:
            return "FillLayerType::Background"
        if "mask-" in self.name:
            return "FillLayerType::Mask"
        raise Exception(f"Unrecognized FillLayer property name: '{self.name}")


class StyleProperties:
    def __init__(self, properties):
        self.properties = properties
        self.properties_by_name = {property.name: property for property in properties}
        self.logical_property_groups = {}
        self._all = None
        self._all_computed = None
        self._settings_flags = None

        self._perform_fixups()

        self.shorthand_by_longhand = {}
        for shorthand in self.all_shorthands:
            for longhand in shorthand.codegen_properties.longhands:
                self.shorthand_by_longhand[longhand] = shorthand

    def __str__(self):
        return "StyleProperties"

    def __repr__(self):
        return self.__str__()

    @property
    def id(self):
        return 'StyleProperty'

    @property
    def name(self):
        return 'style'

    @property
    def noun(self):
        return 'property'

    @property
    def supports_current_shorthand(self):
        return True

    @staticmethod
    def from_json(parsing_context, key_path, json_value):
        return StyleProperties(list(compact_map(lambda item: StyleProperty.from_json(parsing_context, key_path, item[0], item[1]), json_value.items())))

    # Updates any references to other properties that were by name (e.g. string) with a direct
    # reference to the property object.
    def _perform_fixups(self):
        for property in self.properties:
            property.perform_fixups(self)

    # Returns the set of all properties. Default decreasing priority and name sorting.
    @property
    def all(self):
        if not self._all:
            self._all = sorted(self.properties, key=functools.cmp_to_key(StyleProperties._sort_by_descending_priority_and_name))
        return self._all

    # Returns the map of property names to properties.
    @property
    def all_by_name(self):
        return self.properties_by_name

    # Returns the set of all properties that are included in computed styles. Sorted lexically by name with prefixed properties last.
    @property
    def all_computed(self):
        if not self._all_computed:
            self._all_computed = sorted([property for property in self.all if not property.is_skipped_from_computed_style], key=functools.cmp_to_key(StyleProperties._sort_with_prefixed_properties_last))
        return self._all_computed

    # Returns a generator for the set of properties that have an associate longhand, the so-called shorthands. Default decreasing priority and name sorting.
    @property
    def all_shorthands(self):
        return (property for property in self.all if property.codegen_properties.longhands)

    # Returns a generator for the set of properties that do not have an associate longhand. Default decreasing priority and name sorting.
    @property
    def all_non_shorthands(self):
        return (property for property in self.all if not property.codegen_properties.longhands)

    # Returns a generator for the set of properties that are direction-aware (aka flow-sensitive). Sorted first by property group name and then by property name.
    @property
    def all_direction_aware_properties(self):
        for group_name, property_group in sorted(self.logical_property_groups.items(), key=lambda x: x[0]):
            for resolver, property in sorted(property_group["logical"].items(), key=lambda x: x[1].name):
                yield property

    # Returns a generator for the set of properties that are in a logical property group, either logical or physical. Sorted first by property group name, then logical/physical, and then property name.
    @property
    def all_in_logical_property_group(self):
        for group_name, property_group in sorted(self.logical_property_groups.items(), key=lambda x: x[0]):
            for kind in ["logical", "physical"]:
                for resolver, property in sorted(property_group[kind].items(), key=lambda x: x[1].name):
                    yield property

    # Default sorting algorithm for properties.
    def _sort_by_descending_priority_and_name(a, b):
        # Sort shorthands to the back
        a_is_shorthand = a.codegen_properties.longhands is not None
        b_is_shorthand = b.codegen_properties.longhands is not None
        if a_is_shorthand and not b_is_shorthand:
            return 1
        if not a_is_shorthand and b_is_shorthand:
            return -1

        # Sort longhands with top priority to the front
        a_is_top_priority = a.codegen_properties.top_priority
        b_is_top_priority = b.codegen_properties.top_priority
        if a_is_top_priority and not b_is_top_priority:
            return -1
        if not a_is_top_priority and b_is_top_priority:
            return 1

        # Sort longhands with high priority to the front
        a_is_high_priority = a.codegen_properties.high_priority
        b_is_high_priority = b.codegen_properties.high_priority
        if a_is_high_priority and not b_is_high_priority:
            return -1
        if not a_is_high_priority and b_is_high_priority:
            return 1

        # Sort longhands in a logical property group to the back, before shorthands.
        a_is_in_logical_property_group = a.codegen_properties.logical_property_group
        b_is_in_logical_property_group = b.codegen_properties.logical_property_group
        if a_is_in_logical_property_group and not b_is_in_logical_property_group:
            return 1
        if not a_is_in_logical_property_group and b_is_in_logical_property_group:
            return -1

        # Sort sunken names at the end of their priority bucket.
        a_is_sink_priority = a.codegen_properties.sink_priority
        b_is_sink_priority = b.codegen_properties.sink_priority
        if a_is_sink_priority and not b_is_sink_priority:
            return 1
        if not a_is_sink_priority and b_is_sink_priority:
            return -1

        return StyleProperties._sort_with_prefixed_properties_last(a, b)

    def _sort_with_prefixed_properties_last(a, b):
        # Sort prefixed names to the back.
        a_starts_with_prefix = a.name[0] == "-"
        b_starts_with_prefix = b.name[0] == "-"
        if a_starts_with_prefix and not b_starts_with_prefix:
            return 1
        if not a_starts_with_prefix and b_starts_with_prefix:
            return -1

        # Finally, sort by name.
        if a.name < b.name:
            return -1
        elif a.name > b.name:
            return 1
        return 0


class DescriptorCodeGenProperties:
    schema = Schema(
        Schema.Entry("aliases", allowed_types=[list], default_value=[]),
        Schema.Entry("enable-if", allowed_types=[str]),
        Schema.Entry("internal-only", allowed_types=[bool], default_value=False),
        Schema.Entry("longhands", allowed_types=[list]),
        Schema.Entry("parser-exported", allowed_types=[bool]),
        Schema.Entry("parser-function", allowed_types=[str]),
        Schema.Entry("parser-function-allows-number-or-integer-input", allowed_types=[bool], default_value=False),
        Schema.Entry("parser-function-requires-additional-parameters", allowed_types=[list], default_value=[]),
        Schema.Entry("parser-function-requires-current-shorthand", allowed_types=[bool], default_value=False),
        Schema.Entry("parser-function-requires-current-property", allowed_types=[bool], default_value=False),
        Schema.Entry("parser-function-requires-value-pool", allowed_types=[bool], default_value=False),
        Schema.Entry("parser-grammar", allowed_types=[str]),
        Schema.Entry("parser-grammar-unused", allowed_types=[str]),
        Schema.Entry("parser-grammar-unused-reason", allowed_types=[str]),
        Schema.Entry("settings-flag", allowed_types=[str]),
        Schema.Entry("skip-codegen", allowed_types=[bool], default_value=False),
        Schema.Entry("skip-parser", allowed_types=[bool], default_value=False),
    )

    def __init__(self, descriptor_name, **dictionary):
        DescriptorCodeGenProperties.schema.set_attributes_from_dictionary(dictionary, instance=self)
        self.descriptor_name = descriptor_name

        # By defining these to None, we can utilize the shared sorting method, StyleProperties._sort_by_descending_priority_and_name.
        self.top_priority = None
        self.high_priority = None
        self.sink_priority = None
        self.logical_property_group = None

    def __str__(self):
        return f"DescriptorCodeGenProperties {vars(self)}"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, name, json_value):
        if type(json_value) is list:
            json_value = parsing_context.select_enabled_variant(json_value, label=f"{key_path}.codegen-properties")

        assert(type(json_value) is dict)
        DescriptorCodeGenProperties.schema.validate_dictionary(parsing_context, f"{key_path}.codegen-properties", json_value, label=f"DescriptorCodeGenProperties")

        descriptor_name = PropertyName(name)

        if "longhands" in json_value:
            json_value["longhands"] = list(compact_map(lambda value: Longhand.from_json(parsing_context, f"{key_path}.codegen-properties", value), json_value["longhands"]))
            if not json_value["longhands"]:
                del json_value["longhands"]

        if json_value.get("parser-grammar"):
            for entry_name in ["parser-function", "parser-function-requires-additional-parameters", "parser-function-requires-current-shorthand", "parser-function-requires-current-property", "parser-function-requires-value-pool", "skip-parser", "longhands"]:
                if entry_name in json_value:
                    raise Exception(f"{key_path} can't have both 'parser-grammar' and '{entry_name}.")
            grammar = Grammar.from_string(parsing_context, f"{key_path}", name, json_value["parser-grammar"])
            grammar.perform_fixups(parsing_context.parsed_shared_grammar_rules)
            json_value["parser-grammar"] = grammar

        if json_value.get("parser-grammar-unused"):
            if "parser-grammar-unused-reason" not in json_value:
                raise Exception(f"{key_path} must have 'parser-grammar-unused-reason' specified when using 'parser-grammar-unused'.")
            # If we have a "parser-grammar-unused" specified, we still process it to ensure that at least it is syntatically valid, we just
            # won't actually use it for generation.
            grammar = Grammar.from_string(parsing_context, f"{key_path}", name, json_value["parser-grammar-unused"])
            grammar.perform_fixups(parsing_context.parsed_shared_grammar_rules)
            json_value["parser-grammar-unused"] = grammar

        if json_value.get("parser-grammar-unused-reason"):
            if "parser-grammar-unused" not in json_value:
                raise Exception(f"{key_path} must have 'parser-grammar-unused' specified when using 'parser-grammar-unused-reason'.")

        if json_value.get("parser-function"):
            if "parser-grammar-unused" not in json_value:
                raise Exception(f"{key_path} must have 'parser-grammar-unused' specified when using 'parser-function'.")
            for entry_name in ["skip-parser", "longhands"]:
                if entry_name in json_value:
                    raise Exception(f"{key_path} can't have both 'parser-function' and '{entry_name}'.")

        return DescriptorCodeGenProperties(descriptor_name, **json_value)


class Descriptor:
    schema = Schema(
        Schema.Entry("codegen-properties", allowed_types=[dict, list]),
        Schema.Entry("specification", allowed_types=[dict], convert_to=Specification),
        Schema.Entry("status", allowed_types=[dict, str], convert_to=Status),
        Schema.Entry("values", allowed_types=[list]),
    )

    def __init__(self, descriptor_set_name, **dictionary):
        Descriptor.schema.set_attributes_from_dictionary(dictionary, instance=self)
        self.descriptor_set_name = descriptor_set_name
        self.descriptor_name = self.codegen_properties.descriptor_name

    def __str__(self):
        return f"{self.name} ({self.descriptor_set_name})"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, name, json_value, descriptor_set_name):
        assert(type(json_value) is dict)
        Descriptor.schema.validate_dictionary(parsing_context, f"{key_path}.{name}", json_value, label=f"Descriptor")

        codegen_properties = DescriptorCodeGenProperties.from_json(parsing_context, f"{key_path}.{name}", name, json_value.get("codegen-properties", {}))
        json_value["codegen-properties"] = codegen_properties

        if codegen_properties.enable_if is not None and not parsing_context.is_enabled(conditional=codegen_properties.enable_if):
            if parsing_context.verbose:
                print(f"SKIPPED {name} due to failing to satisfy 'enable-if' condition, '{json_value['codegen-properties'].enable_if}', with active macro set")
            return None

        if codegen_properties.skip_codegen is not None and codegen_properties.skip_codegen:
            if parsing_context.verbose:
                print(f"SKIPPED {name} due to 'skip-codegen'")
            return None

        if "values" in json_value:
            if not (codegen_properties.parser_grammar or codegen_properties.skip_parser or codegen_properties.parser_function or codegen_properties.longhands):
                raise Exception(f"'{name}' must specify a 'parser-grammar', 'skip-parser', 'parser-function' or 'longhands' when specifying a 'values' array.")

            json_value["values"] = list(filter(lambda value: value is not None, map(lambda value: Value.from_json(parsing_context, f"{key_path}.{name}", value), json_value["values"])))

            if codegen_properties.parser_grammar:
                codegen_properties.parser_grammar.perform_fixups_for_values_references(json_value["values"])
            elif codegen_properties.parser_grammar_unused:
                codegen_properties.parser_grammar_unused.perform_fixups_for_values_references(json_value["values"])

            if codegen_properties.parser_grammar:
                codegen_properties.parser_grammar.check_against_values(json_value.get("values", []))
            elif codegen_properties.parser_grammar_unused:
                if parsing_context.check_unused_grammars_values or parsing_context.verbose:
                    codegen_properties.parser_grammar_unused.check_against_values(json_value.get("values", []))

        return Descriptor(descriptor_set_name, **json_value)

    def perform_fixups_for_longhands(self, all_descriptors):
        # If 'longhands' was specified, replace the names with references to the Descriptor objects.
        if self.codegen_properties.longhands:
            self.codegen_properties.longhands = [all_descriptors.all_by_name[longhand.value] for longhand in self.codegen_properties.longhands]

    def perform_fixups(self, all_descriptors):
        self.perform_fixups_for_longhands(all_descriptors)

    @property
    def id_without_prefix(self):
        return self.descriptor_name.id_without_prefix

    # Used for parsing and consume methods. It is prefixed with the rule type for descriptors, and left unprefixed for style properties.
    # Examples:
    #       style property 'column-width' would generate a consume method called `consumeColumnWidth`
    #       @font-face descriptor 'font-display' would generate a consume method called `consumeFontFaceFontDisplay`
    @property
    def name_for_parsing_methods(self):
        return Name.convert_name_to_id(self.descriptor_set_name[1:]) + self.descriptor_name.id_without_prefix

    @property
    def id_without_prefix_with_lowercase_first_letter(self):
        return self.descriptor_name.id_without_prefix_with_lowercase_first_letter

    @property
    def id_without_scope(self):
        return self.descriptor_name.id_without_scope

    @property
    def id(self):
        return self.descriptor_name.id

    @property
    def name(self):
        return self.descriptor_name.name

    @property
    def aliases(self):
        return self.codegen_properties.aliases


# Provides access to each descriptor in a grouped set of descriptor (e.g. @font-face, @counter-styles, etc.) There is
# one of these per rule type, e.g. @font-face, @counter-styles, etc.
class DescriptorSet:
    def __init__(self, name, descriptors):
        self.name = name
        self.descriptors = descriptors
        self.descriptors_by_name = {descriptor.name: descriptor for descriptor in descriptors}
        self._all = None
        self._perform_fixups()

    @staticmethod
    def from_json(parsing_context, key_path, name, json_value):
        return DescriptorSet(name, list(compact_map(lambda item: Descriptor.from_json(parsing_context, f"{key_path}.{name}", item[0], item[1], name), json_value.items())))

    def _perform_fixups(self):
        for descriptor in self.descriptors:
            descriptor.perform_fixups(self)

    @property
    def id(self):
        return f'{Name.convert_name_to_id(self.name[1:])}Descriptor'

    @property
    def noun(self):
        return 'descriptor'

    @property
    def supports_current_shorthand(self):
        return False

    @property
    def all(self):
        if not self._all:
            self._all = sorted(self.descriptors, key=functools.cmp_to_key(StyleProperties._sort_by_descending_priority_and_name))
        return self._all

    @property
    def all_by_name(self):
        return self.descriptors_by_name


# Provides access to each of the grouped sets of descriptor (e.g. @font-face, @counter-styles, etc. which are
# stored as DescriptorSet instances) via either the `descriptor_sets` list or by name as dynamic attributes.
#
# e.g. font_face_descriptor_set = descriptors.font_face
#
class Descriptors:
    def __init__(self, descriptor_sets):
        self.descriptor_sets = descriptor_sets
        for descriptor_set in descriptor_sets:
            setattr(self, descriptor_set.name.replace('@', 'at-').replace('-', '_'), descriptor_set)

    def __str__(self):
        return f"Descriptors"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, json_value):
        return Descriptors([DescriptorSet.from_json(parsing_context, key_path, name, descriptors) for (name, descriptors) in json_value.items()])

    # Returns a generator for the set of descriptors.
    @property
    def all(self):
        return itertools.chain.from_iterable(descriptor_set.all for descriptor_set in self.descriptor_sets)


class PropertiesAndDescriptors:
    def __init__(self, style_properties, descriptors):
        self.style_properties = style_properties
        self.descriptors = descriptors
        self._all_grouped_by_name = None
        self._all_by_name = None
        self._all_unique = None
        self._settings_flags = None

    def __str__(self):
        return "PropertiesAndDescriptors"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, *, properties_json_value, descriptors_json_value):
        return PropertiesAndDescriptors(
            StyleProperties.from_json(parsing_context, "properties", properties_json_value),
            Descriptors.from_json(parsing_context, "descriptors", descriptors_json_value),
        )

    def _compute_all_grouped_by_name(self):
        return [self.all_by_name[property.name] for property in self.all_unique]

    def _compute_all_by_name(self):
        result = {}
        for property in self.all_properties_and_descriptors:
            result.setdefault(property.name, []).append(property)
        return result

    def _compute_all_unique(self):
        # NOTE: This is computes the ordered set of properties and descriptors that correspond to the CSSPropertyID
        # enumeration and related lookup tables and functions.

        result = list(self.style_properties.all)
        name_set = set(self.style_properties.all_by_name.keys())

        for descriptor in self.descriptors.all:
            if descriptor.name in name_set:
                continue
            result.append(descriptor)
            name_set.add(descriptor.name)

        # FIXME: It doesn't make a lot of sense to sort the descriptors like this, but this maintains
        # the current behavior and has no negative side effect. In the future, we should either separate
        # the descriptors out of CSSPropertyID or the descriptor-only ones together in some fashion.
        return sorted(result, key=functools.cmp_to_key(StyleProperties._sort_by_descending_priority_and_name))

    # Returns a generator for the set of all properties and descriptors.
    @property
    def all_properties_and_descriptors(self):
        return itertools.chain(self.style_properties.all, self.descriptors.all)

    # Returns a list of all the property or descriptor sets (e.g. 'style', '@counter-style', '@font-face', etc.).
    @property
    def all_sets(self):
        return [self.style_properties] + self.descriptors.descriptor_sets

    # Returns the set of properties and descriptors that have unique names, preferring style properties when
    # there is a conflict. This set corresponds one-to-one in membership and order with CSSPropertyID.
    @property
    def all_unique(self):
        if not self._all_unique:
            self._all_unique = self._compute_all_unique()
        return self._all_unique

    # Returns a parallel list to `all_unique`, but rather than containing the canonical property, each entry
    # in this list is a list of all properties or descriptors with the unique name.
    @property
    def all_grouped_by_name(self):
        if not self._all_grouped_by_name:
            self._all_grouped_by_name = self._compute_all_grouped_by_name()
        return self._all_grouped_by_name

    # Returns a map of names to lists of the properties or descriptors with that name.
    @property
    def all_by_name(self):
        if not self._all_by_name:
            self._all_by_name = self._compute_all_by_name()
        return self._all_by_name

    # Returns a generator for the set of properties and descriptors that are conditionally included depending on settings. If two properties
    # or descriptors have the same name, we only return the canonical one and only if all the variants have settings flags.
    #
    # For example, there are two "speak-as" entries. One is a style property and the other is @counter-style descriptor. Only the one of the
    # two, the @counter-style descriptor, has settings_flags set, so we don't return anything for that name.
    @property
    def all_unique_with_settings_flag(self):
        return (property_set[0] for property_set in self.all_grouped_by_name if all(property.codegen_properties.settings_flag for property in property_set))

    # Returns a generator for the subset of `self.all_unique` that are marked internal-only.
    @property
    def all_unique_internal_only(self):
        return (property for property in self.all_unique if property.codegen_properties.internal_only)

    # Returns a generator for the subset of `self.all_unique` that are NOT marked internal.
    @property
    def all_unique_non_internal_only(self):
        return (property for property in self.all_unique if not property.codegen_properties.internal_only)

    @property
    def all_descriptor_only(self):
        return (descriptor for descriptor in self.descriptors.all if descriptor.name not in self.style_properties.all_by_name)

    # Returns the set of settings-flags used by any property or descriptor. Uniqued and sorted lexically.
    @property
    def settings_flags(self):
        if not self._settings_flags:
            self._settings_flags = sorted(list(set([property.codegen_properties.settings_flag for property in self.all_properties_and_descriptors if property.codegen_properties.settings_flag])))
        return self._settings_flags


# MARK: - Property Parsing

class Term:
    @staticmethod
    def wrap_with_multiplier(multiplier, term):
        if multiplier.kind == BNFNodeMultiplier.Kind.ZERO_OR_ONE:
            return OptionalTerm.wrapping_term(term, annotation=multiplier.annotation)
        elif multiplier.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_ZERO_OR_MORE:
            return UnboundedRepetitionTerm.wrapping_term(term, variation=' ', min=0, annotation=multiplier.annotation)
        elif multiplier.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_ONE_OR_MORE:
            return UnboundedRepetitionTerm.wrapping_term(term, variation=' ', min=1, annotation=multiplier.annotation)
        elif multiplier.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_EXACT:
            return FixedSizeRepetitionTerm.wrapping_term(term, variation=' ', size=multiplier.range.min, annotation=multiplier.annotation)
        elif multiplier.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_AT_LEAST:
            return UnboundedRepetitionTerm.wrapping_term(term, variation=' ', min=multiplier.range.min, annotation=multiplier.annotation)
        elif multiplier.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_BETWEEN:
            return BoundedRepetitionTerm.wrapping_term(term, variation=' ', min=multiplier.range.min, max=multiplier.range.max, annotation=multiplier.annotation)
        elif multiplier.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_ONE_OR_MORE:
            return UnboundedRepetitionTerm.wrapping_term(term, variation=',', min=1, annotation=multiplier.annotation)
        elif multiplier.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_EXACT:
            return FixedSizeRepetitionTerm.wrapping_term(term, variation=',', size=multiplier.range.min, annotation=multiplier.annotation)
        elif multiplier.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_AT_LEAST:
            return UnboundedRepetitionTerm.wrapping_term(term, variation=',', min=multiplier.range.min, annotation=multiplier.annotation)
        elif multiplier.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_BETWEEN:
            return BoundedRepetitionTerm.wrapping_term(term, variation=',', min=multiplier.range.min, max=multiplier.range.max, annotation=multiplier.annotation)

    @staticmethod
    def from_node(node):
        if isinstance(node, BNFGroupingNode):
            if node.kind == BNFGroupingNode.Kind.MATCH_ALL_ORDERED:
                # FIXME: This should be part of the GroupTerm's simplification.
                if len(node.members) == 1:
                    term = Term.from_node(node.members[0])
                else:
                    term = GroupTerm.from_node(node)
            elif node.kind == BNFGroupingNode.Kind.MATCH_ONE:
                term = MatchOneTerm.from_node(node)
            elif node.kind == BNFGroupingNode.Kind.MATCH_ALL_ANY_ORDER:
                term = GroupTerm.from_node(node)
            elif node.kind == BNFGroupingNode.Kind.MATCH_ONE_OR_MORE_ANY_ORDER:
                term = GroupTerm.from_node(node)
            else:
                raise Exception(f"Unknown grouping kind '{node.kind}' in BNF parse tree node '{node}'")
        elif isinstance(node, BNFReferenceNode):
            term = ReferenceTerm.from_node(node)
        elif isinstance(node, BNFFunctionNode):
            term = FunctionTerm.from_node(node)
        elif isinstance(node, BNFKeywordNode):
            term = KeywordTerm.from_node(node)
        elif isinstance(node, BNFLiteralNode):
            term = LiteralTerm.from_node(node)
        else:
            raise Exception(f"Unknown node '{node}' in BNF parse tree")

        # If the node has an attached multiplier, wrap the node in
        # a term created from that multiplier.
        if node.multiplier.kind:
            term = Term.wrap_with_multiplier(node.multiplier, term)

        return term


class BuiltinSchema:
    class OptionalParameter:
        def __init__(self, name, values, default):
            self.name = name
            self.values = values
            self.default = default

    class RequiredParameter:
        def __init__(self, name, values):
            self.name = name
            self.values = values

    class Entry:
        def __init__(self, name, consume_function_name, *parameter_descriptors):
            self.name = Name(name)
            self.consume_function_name = consume_function_name

            # Mapping of descriptor name (e.g. 'value_range' or 'mode') to OptionalParameter descriptor.
            self.optionals = {}

            # Mapping of descriptor name (e.g. 'value_range' or 'mode') to RequiredParameter descriptor.
            self.requireds = {}

            # Mapping from all the potential values (e.g. 'svg', 'unitless-allowed') to the parameter descriptor (e.g. OptionalParameter/RequiredParameter instances).
            self.value_to_descriptor = {}

            for parameter_descriptor in parameter_descriptors:
                if isinstance(parameter_descriptor, BuiltinSchema.OptionalParameter):
                    self.optionals[parameter_descriptor.name] = parameter_descriptor
                    for value in parameter_descriptor.values.keys():
                        self.value_to_descriptor[value] = parameter_descriptor
                if isinstance(parameter_descriptor, BuiltinSchema.RequiredParameter):
                    self.requireds[parameter_descriptor.name] = parameter_descriptor
                    for value in parameter_descriptor.values.keys():
                        self.value_to_descriptor[value] = parameter_descriptor

            def builtin_schema_type_init(self, parameters):
                # Map from descriptor name (e.g. 'value_range' or 'mode') to mapped value (e.g. `ValueRange::NonNegative` or `HTMLStandardMode`) for all of the parameters.
                self.parameter_map = {}

                # Map from descriptor name (e.g. 'value_range' or 'mode') to parameter value (e.g. `[0,inf]` or `strict`) for all of the parameters.
                descriptors_used = {}

                # Example parameters is ['svg', 'unitless-allowed'].
                for parameter in parameters:
                    if parameter not in self.entry.value_to_descriptor:
                        raise Exception(f"Unknown parameter '{parameter}' passed to <{self.entry.name.name}>. Supported parameters are {', '.join(quote_iterable(self.entry.value_to_descriptor.keys()))}.")

                    descriptor = self.entry.value_to_descriptor[parameter]
                    if descriptor.name in descriptors_used:
                        raise Exception(f"More than one parameter of type '{descriptor.name}` passed to <{self.entry.name.name}>, pick one: {descriptors_used[descriptor.name]}, {parameter}.")
                    descriptors_used[descriptor.name] = parameter

                    self.parameter_map[descriptor.name] = descriptor.values[parameter]

                # Fill `results` with mappings from `names` (e.g. `value_range`) to mapped to value (e.g. `ValueRange::NonNegative`)
                self.results = {}
                for descriptor in self.entry.optionals.values():
                    self.results[descriptor.name] = self.parameter_map.get(descriptor.name, descriptor.default)
                for descriptor in self.entry.requireds.values():
                    if descriptor.name not in self.parameter_map:
                        raise Exception(f"Required parameter of type '{descriptor.name}` not passed to <{self.entry.name.name}>. Pick one of {', '.join(quote_iterable(descriptor.values.keys()))}.")
                    self.results[descriptor.name] = self.parameter_map.get(descriptor.name)

            def builtin_schema_type_parameter_string_getter(name, self):
                return self.results[name]

            # Dynamically generate a class that can handle validation and generation.
            class_name = f"Builtin{self.name.id_without_prefix}Consumer"
            class_attributes = {
                "__init__": builtin_schema_type_init,
                "consume_function_name": self.consume_function_name,
                "entry": self,
            }

            for name in itertools.chain(self.optionals.keys(), self.requireds.keys()):
                class_attributes[name.replace('-', '_')] = property(functools.partial(builtin_schema_type_parameter_string_getter, name))

            self.constructor = type(class_name, (), class_attributes)

            # Also add the type to the global scope for use in other classes.
            globals()[class_name] = self.constructor

    def __init__(self, *entries):
        self.entries = {entry.name.name: entry for entry in entries}

    def validate_and_construct_if_builtin(self, name, parameters):
        if name.name in self.entries:
            return self.entries[name.name].constructor(parameters)
        return None


# Reference terms look like keyword terms, but are surrounded by '<' and '>' characters (i.e. "<number>").
# They can either reference a rule from the grammar-rules set, in which case they will be replaced by
# the real term during fixup, or a builtin rule, in which case they will inform the generator to call
# out to a handwritten consumer. Example:
#
#   e.g. "<length unitless-allowed>"
#
class ReferenceTerm:
    builtins = BuiltinSchema(
        BuiltinSchema.Entry("angle", "consumeAngle",
            BuiltinSchema.OptionalParameter("unitless", values={"unitless-allowed": "UnitlessQuirk::Allow"}, default="UnitlessQuirk::Forbid"),
            BuiltinSchema.OptionalParameter("unitless-zero", values={"unitless-zero-allowed": "UnitlessZeroQuirk::Allow"}, default="UnitlessZeroQuirk::Forbid")),
        BuiltinSchema.Entry("length", "consumeLength",
            BuiltinSchema.OptionalParameter("value_range", values={"[0,inf]": "ValueRange::NonNegative"}, default="ValueRange::All"),
            BuiltinSchema.OptionalParameter("mode", values={"svg": "SVGAttributeMode", "strict": "HTMLStandardMode"}, default=None),
            BuiltinSchema.OptionalParameter("unitless", values={"unitless-allowed": "UnitlessQuirk::Allow"}, default="UnitlessQuirk::Forbid")),
        BuiltinSchema.Entry("length-percentage", "consumeLengthPercentage",
            BuiltinSchema.OptionalParameter("value_range", values={"[0,inf]": "ValueRange::NonNegative"}, default="ValueRange::All"),
            BuiltinSchema.OptionalParameter("unitless", values={"unitless-allowed": "UnitlessQuirk::Allow"}, default="UnitlessQuirk::Forbid"),
            BuiltinSchema.OptionalParameter("unitless_zero", values={"unitless-zero-forbidden": "UnitlessZeroQuirk::Forbid"}, default="UnitlessZeroQuirk::Allow"),
            BuiltinSchema.OptionalParameter("anchor", values={"anchor-allowed": "AnchorPolicy::Allow"}, default="AnchorPolicy::Forbid"),
            BuiltinSchema.OptionalParameter("anchor_size", values={"anchor-size-allowed": "AnchorSizePolicy::Allow"}, default="AnchorSizePolicy::Forbid")),
        BuiltinSchema.Entry("time", "consumeTime",
            BuiltinSchema.OptionalParameter("value_range", values={"[0,inf]": "ValueRange::NonNegative"}, default="ValueRange::All")),
        BuiltinSchema.Entry("integer", "consumeInteger",
            BuiltinSchema.OptionalParameter("value_range", values={"[0,inf]": "CSS::Range{0, CSS::Range::infinity}", "[1,inf]": "CSS::Range{1, CSS::Range::infinity}"}, default="CSS::Range{-CSS::Range::infinity, CSS::Range::infinity}")),
        BuiltinSchema.Entry("number", "consumeNumber",
            # FIXME: "FontWeight" is not real. Add support for arbitrary ranges.
            BuiltinSchema.OptionalParameter("value_range", values={"[0,inf]": "ValueRange::NonNegative", "[1,1000]": "ValueRange::FontWeight"}, default="ValueRange::All")),
        BuiltinSchema.Entry("percentage", "consumePercentage",
            BuiltinSchema.OptionalParameter("value_range", values={"[0,inf]": "ValueRange::NonNegative"}, default="ValueRange::All")),
        BuiltinSchema.Entry("position", "consumePosition",
            BuiltinSchema.OptionalParameter("unitless", values={"unitless-allowed": "UnitlessQuirk::Allow"}, default="UnitlessQuirk::Forbid")),
        BuiltinSchema.Entry("color", "consumeColor",
            BuiltinSchema.OptionalParameter("quirky_colors", values={"accept-quirky-colors-in-quirks-mode": True}, default=False)),
        BuiltinSchema.Entry("resolution", "consumeResolution"),
        BuiltinSchema.Entry("string", "consumeString"),
        BuiltinSchema.Entry("custom-ident", "consumeCustomIdent"),
        BuiltinSchema.Entry("dashed-ident", "consumeDashedIdent"),
        BuiltinSchema.Entry("url", "consumeURL"),
        BuiltinSchema.Entry("feature-tag-value", "consumeFeatureTagValue"),
        BuiltinSchema.Entry("variation-tag-value", "consumeVariationTagValue"),
    )

    def __init__(self, name, is_internal, is_function_reference, parameters):
        # Store the first (and perhaps only) part as the reference's name (e.g. for <length-percentage [0,inf] unitless-allowed> store 'length-percentage').
        self.name = Name(name)

        # Store whether this is an 'internal' reference (e.g. as indicated by the double angle brackets <<values>>).
        self.is_internal = is_internal

        # Store whether this is a function reference (e.g. as indicated by function notation <rect()>).
        self.is_function_reference = is_function_reference

        # Store any remaining parts as the parameters (e.g. for <length-percentage [0,inf] unitless-allowed> store ['[0,inf]', 'unitless-allowed']).
        self.parameters = parameters

        # Check name and parameters against the builtins schemas to verify if they are well formed.
        self.builtin = ReferenceTerm.builtins.validate_and_construct_if_builtin(self.name, self.parameters)

    def __str__(self):
        if self.is_function_reference:
            name = self.name.name + '()'
        else:
            name = self.name.name
        base = ' '.join([name] + self.parameters)
        if self.is_internal:
            return f"<<{base}>>"
        return f"<{base}>"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_node(node):
        assert(type(node) is BNFReferenceNode)
        return ReferenceTerm(node.name, node.is_internal, node.is_function_reference, [str(attribute) for attribute in node.attributes])

    def perform_fixups(self, all_rules):
        # Replace a reference with the term it references if it can be found.
        name_for_lookup = str(self)
        if name_for_lookup in all_rules.rules_by_name:
            return all_rules.rules_by_name[name_for_lookup].grammar.root_term.perform_fixups(all_rules)
        return self

    def perform_fixups_for_values_references(self, values):
        # NOTE: The actual name in the grammar is "<<values>>", which we store as is_internal + 'values'.
        if self.is_internal and self.name.name == "values":
            return MatchOneTerm.from_values(values)
        return self

    @property
    def is_builtin(self):
        return self.builtin is not None

    @property
    def supported_keywords(self):
        return set()

    @property
    def has_non_builtin_reference_terms(self):
        return not self.is_builtin


# LiteralTerm represents a direct match of a literal character or string. The
# syntax in the CSS specifications is either a bare delimiter character or a
# string surrounded by single quotes.
#
#   e.g. "'['" or ","
#
class LiteralTerm:
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return f"'{self.value}'"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_node(node):
        assert(type(node) is BNFLiteralNode)
        return LiteralTerm(node.value)

    def perform_fixups(self, all_rules):
        return self

    def perform_fixups_for_values_references(self, values):
        return self

    @property
    def supported_keywords(self):
        return set()

    @property
    def has_non_builtin_reference_terms(self):
        return False


# KeywordTerm represents a direct keyword match. The syntax in the CSS specifications
# is a bare string.
#
#   e.g. "auto" or "box"
#
class KeywordTerm:
    def __init__(self, value, *, aliased_to=None, comment=None, settings_flag=None, status=None):
        self.value = value
        self.aliased_to = aliased_to
        self.comment = comment
        self.settings_flag = settings_flag
        self.status = status

    def __str__(self):
        return self.value.name

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_node(node):
        assert(type(node) is BNFKeywordNode)
        return KeywordTerm(ValueKeywordName(node.keyword))

    def perform_fixups(self, all_rules):
        return self

    def perform_fixups_for_values_references(self, values):
        return self

    @property
    def supported_keywords(self):
        return {self.value.name}

    @property
    def has_non_builtin_reference_terms(self):
        return False

    @property
    def requires_context(self):
        return self.settings_flag or self.status == "internal"

    @property
    def is_eligible_for_fast_path(self):
        # Keyword terms that are aliased as not eligable for the fast path as the fast
        # path can only support a basic predicate.
        return not self.aliased_to

    @property
    def name(self):
        return self.value.name


# MatchOneTerm represents a set of terms, only one of which can match. The
# syntax in the CSS specifications is a '|' between terms.
#
#   e.g. "auto" | "reverse" | "<angle unitless-allowed unitless-zero-allowed>"
#
class MatchOneTerm:
    def __init__(self, terms):
        self.terms = terms

    def __str__(self):
        return f"[ {' | '.join(str(term) for term in self.terms)} ]"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_node(node):
        assert(type(node) is BNFGroupingNode)
        assert(node.kind is BNFGroupingNode.Kind.MATCH_ONE)

        return MatchOneTerm(list(compact_map(lambda member: Term.from_node(member), node.members)))

    @staticmethod
    def from_values(values):
        return MatchOneTerm(list(compact_map(lambda value: value.keyword_term, values)))

    def perform_fixups(self, all_rules):
        self.terms = MatchOneTerm.simplify(term.perform_fixups(all_rules) for term in self.terms)

        if len(self.terms) == 1:
            return self.terms[0]
        return self

    def perform_fixups_for_values_references(self, values):
        self.terms = MatchOneTerm.simplify(term.perform_fixups_for_values_references(values) for term in self.terms)

        if len(self.terms) == 1:
            return self.terms[0]
        return self

    @staticmethod
    def simplify(terms):
        simplified_terms = []
        for term in terms:
            if isinstance(term, MatchOneTerm):
                simplified_terms += term.terms
            else:
                simplified_terms += [term]
        return simplified_terms

    @property
    def has_keyword_term(self):
        return any(isinstance(term, KeywordTerm) for term in self.terms)

    @property
    def has_only_keyword_terms(self):
        return all(isinstance(term, KeywordTerm) for term in self.terms)

    @property
    def keyword_terms(self):
        return (term for term in self.terms if isinstance(term, KeywordTerm))

    @property
    def fast_path_keyword_terms(self):
        return (term for term in self.keyword_terms if term.is_eligible_for_fast_path)

    @property
    def has_fast_path_keyword_terms(self):
        return any(term.is_eligible_for_fast_path for term in self.keyword_terms)

    @property
    def has_only_fast_path_keyword_terms(self):
        return all(isinstance(term, KeywordTerm) and term.is_eligible_for_fast_path for term in self.terms)

    @property
    def supported_keywords(self):
        result = set()
        for term in self.terms:
            result.update(term.supported_keywords)
        return result

    @property
    def has_non_builtin_reference_terms(self):
        return any(term.has_non_builtin_reference_terms for term in self.terms)


# GroupTerm represents matching a list of provided terms with
# options for whether the matches are ordered and whether all
# terms must be matched. The syntax in the CSS specifications
# uses space separtion with square brackets (these can be ellided
# at the root level) as the base syntax for an match all ordered
# group, and adds '||' and '&&' combinators to indicate 'match
# one or more + any order' and 'match all + any order' respectively.
#
#   e.g. "[ <length> <length> ]" or "[ <length> && <string> && <number> ]"
#
class GroupTerm:
    def __init__(self, subterms, kind, annotation):
        self.subterms = subterms
        self.kind = kind

        self._process_annotation(annotation)

    def __str__(self):
        return '[ ' + self.stringified_without_brackets + ' ]'

    def __repr__(self):
        return self.__str__()

    @property
    def stringified_without_brackets(self):
        if self.kind != BNFGroupingNode.Kind.MATCH_ALL_ORDERED:
            join_string = ' ' + str(self.kind.value) + ' '
        else:
            join_string = ' '
        return join_string.join(str(subterm) for subterm in self.subterms)

    def _process_annotation(self, annotation):
        if not annotation:
            return
        # FIXME: Add initialization of annotation state here if/when group specific annotations are needed.
        for directive in annotation.directives:
            raise Exception(f"Unknown grouping annotation directive '{directive}'.")

    @staticmethod
    def from_node(node):
        assert(type(node) is BNFGroupingNode)
        return GroupTerm(list(compact_map(lambda member: Term.from_node(member), node.members)), node.kind, node.annotation)

    def perform_fixups(self, all_rules):
        self.subterms = [subterm.perform_fixups(all_rules) for subterm in self.subterms]

        if len(self.subterms) == 1:
            return self.subterms[0]
        return self

    def perform_fixups_for_values_references(self, values):
        self.subterms = [subterm.perform_fixups_for_values_references(values) for subterm in self.subterms]

        if len(self.subterms) == 1:
            return self.subterms[0]
        return self

    @property
    def supported_keywords(self):
        result = set()
        for subterm in self.subterms:
            result.update(subterm.supported_keywords)
        return result

    @property
    def has_non_builtin_reference_terms(self):
        return any(term.has_non_builtin_reference_terms for term in self.subterms)


# OptionalTerm represents matching a term that is allowed to
# be ommited. The syntax in the CSS specifications uses a
# trailing '?'.
#
#   e.g. "<length>?" or "[ <length> <string> ]?"
#
class OptionalTerm:
    def __init__(self, subterm, *, annotation):
        self.subterm = subterm

        self._process_annotation(annotation)

    def __str__(self):
        return f"{str(self.subterm)}?"

    def __repr__(self):
        return self.__str__()

    def _process_annotation(self, annotation):
        if not annotation:
            return
        for directive in annotation.directives:
            raise Exception(f"Unknown optional term annotation directive '{directive}'.")

    @staticmethod
    def wrapping_term(subterm, *, annotation):
        return OptionalTerm(subterm, annotation=annotation)

    def perform_fixups(self, all_rules):
        self.subterm = self.subterm.perform_fixups(all_rules)
        return self

    def perform_fixups_for_values_references(self, values):
        self.subterm = self.subterm.perform_fixups_for_values_references(values)
        return self

    @property
    def supported_keywords(self):
        return self.subterm.supported_keywords

    @property
    def has_non_builtin_reference_terms(self):
        return self.subterm.has_non_builtin_reference_terms


# UnboundedRepetitionTerm represents matching a list of terms
# separated by either spaces or commas. The syntax in the CSS
# specifications uses a trailing 'multiplier' such as '#', '*',
# '+', and '{A,}'.
#
#   e.g. "<length>#" or "<length>+"
#
class UnboundedRepetitionTerm:
    def __init__(self, repeated_term, *, variation, min, annotation):
        self.repeated_term = repeated_term
        self.variation = variation
        self.min = min

        self.single_value_optimization = True
        self._process_annotation(annotation)

    def __str__(self):
        return str(self.repeated_term) + self.stringified_suffix + self.stringified_annotation

    def __repr__(self):
        return self.__str__()

    @property
    def stringified_suffix(self):
        if self.variation == ' ':
            if self.min == 0:
                return '*'
            elif self.min == 1:
                return '+'
            else:
                return '{' + str(self.min) + ',}'
        if self.variation == ',':
            if self.min == 1:
                return '#'
            else:
                return '#{' + str(self.min) + ',}'
        raise Exception(f"Unknown UnboundedRepetitionTerm variation '{self.variation}'")

    @property
    def stringified_annotation(self):
        if not self.single_value_optimization:
            return '@(no-single-item-opt)'
        return ''

    def _process_annotation(self, annotation):
        if not annotation:
            return
        for directive in annotation.directives:
            if directive == 'no-single-item-opt':
                self.single_value_optimization = False
            else:
                raise Exception(f"Unknown multiplier annotation directive '{directive}'.")

    @staticmethod
    def wrapping_term(term, *, variation, min, annotation):
        return UnboundedRepetitionTerm(term, variation=variation, min=min, annotation=annotation)

    def perform_fixups(self, all_rules):
        self.repeated_term = self.repeated_term.perform_fixups(all_rules)
        return self

    def perform_fixups_for_values_references(self, values):
        self.repeated_term = self.repeated_term.perform_fixups_for_values_references(values)
        return self

    @property
    def supported_keywords(self):
        return self.repeated_term.supported_keywords

    @property
    def has_non_builtin_reference_terms(self):
        return self.repeated_term.has_non_builtin_reference_terms


# BoundedRepetitionTerm represents matching a list of terms
# separated by either spaces or commas where the list of terms
# has a length between provided upper and lower bounds . The
# syntax in the CSS specifications uses a trailing 'multiplier'
# range '{A,B}' with a '#' prefix for comma speparation.
#
#   e.g. "<length>{1,2}" or "<length>#{3,5}"
#
class BoundedRepetitionTerm:
    def __init__(self, repeated_term, *, variation, min, max, annotation):
        self.repeated_term = repeated_term
        self.variation = variation
        self.min = min
        self.max = max

        self._process_annotation(annotation)

    def __str__(self):
        return str(self.repeated_term) + self.stringified_suffix

    def __repr__(self):
        return self.__str__()

    @property
    def stringified_suffix(self):
        if self.variation == ' ':
            return '{' + str(self.min) + ',' + str(self.max) + '}'
        if self.variation == ',':
            return '#{' + str(self.min) + ',' + str(self.max) + '}'
        raise Exception(f"Unknown BoundedRepetitionTerm variation '{self.variation}'")

    def _process_annotation(self, annotation):
        if not annotation:
            return
        for directive in annotation.directives:
            raise Exception(f"Unknown bounded repetition term annotation directive '{directive}'.")

    @staticmethod
    def wrapping_term(term, *, variation, min, max, annotation):
        return BoundedRepetitionTerm(term, variation=variation, min=min, max=max, annotation=annotation)

    def perform_fixups(self, all_rules):
        self.repeated_term = self.repeated_term.perform_fixups(all_rules)
        return self

    def perform_fixups_for_values_references(self, values):
        self.repeated_term = self.repeated_term.perform_fixups_for_values_references(values)
        return self

    @property
    def supported_keywords(self):
        return self.repeated_term.supported_keywords

    @property
    def has_non_builtin_reference_terms(self):
        return self.repeated_term.has_non_builtin_reference_terms


# FixedSizeRepetitionTerm represents matching a list of terms
# separated by either spaces or commas where the list of terms
# has a length that is exactly provided length. The syntax in
# the CSS specifications uses a trailing 'multiplier' length
# '{A}' with a '#' prefix for comma separation.
#
#   e.g. "<length>{2}" or "<length>#{4}"
#
class FixedSizeRepetitionTerm:
    def __init__(self, repeated_term, *, variation, size, annotation):
        self.repeated_term = repeated_term
        self.variation = variation
        self.size = size

        self._process_annotation(annotation)

    def __str__(self):
        return str(self.repeated_term) + self.stringified_suffix

    def __repr__(self):
        return self.__str__()

    @property
    def stringified_suffix(self):
        if self.variation == ' ':
            return '{' + str(self.size) + '}'
        if self.variation == ',':
            return '#{' + str(self.size) + '}'
        raise Exception(f"Unknown FixedSizeRepetitionTerm variation '{self.variation}'")

    def _process_annotation(self, annotation):
        if not annotation:
            return
        for directive in annotation.directives:
            raise Exception(f"Unknown fixed size repetition term annotation directive '{directive}'.")

    @staticmethod
    def wrapping_term(term, *, variation, size, annotation):
        return FixedSizeRepetitionTerm(term, variation=variation, size=size, annotation=annotation)

    def perform_fixups(self, all_rules):
        self.repeated_term = self.repeated_term.perform_fixups(all_rules)
        return self

    def perform_fixups_for_values_references(self, values):
        self.repeated_term = self.repeated_term.perform_fixups_for_values_references(values)
        return self

    @property
    def supported_keywords(self):
        return self.repeated_term.supported_keywords

    @property
    def has_non_builtin_reference_terms(self):
        return self.repeated_term.has_non_builtin_reference_terms


# FunctionTerm represents matching a use of the CSS function call syntax
# which provides a way for specifications to differentiate groups by
# name. The syntax in the CSS specifications is an identifier followed
# by parenthesis with an optional group term inside the parenthesis.
#
#   e.g. "rect(<length>#{4})" or "ray()"
#
class FunctionTerm:
    def __init__(self, name, parameter_group_term):
        self.name = name
        self.parameter_group_term = parameter_group_term

    def __str__(self):
        return self.name + '(' + str(self.parameter_group_term) + ')'

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_node(node):
        assert(type(node) is BNFFunctionNode)
        return FunctionTerm(node.name, Term.from_node(node.parameter_group))

    def perform_fixups(self, all_rules):
        self.parameter_group_term = self.parameter_group_term.perform_fixups(all_rules)
        return self

    def perform_fixups_for_values_references(self, values):
        self.parameter_group_term = self.parameter_group_term.perform_fixups_for_values_references(values)
        return self

    @property
    def supported_keywords(self):
        return self.parameter_group_term.supported_keywords

    @property
    def has_non_builtin_reference_terms(self):
        return self.parameter_group_term.has_non_builtin_reference_terms


# Container for the name and root term for a grammar. Used for both shared rules and property specific grammars.
class Grammar:
    def __init__(self, name, root_term):
        self.name = name
        self.root_term = root_term
        self._fast_path_keyword_terms_sorted_by_name = None

    def __str__(self):
        return f"{self.name} {self.root_term}"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_string(parsing_context, key_path, name, string):
        assert(type(string) is str)
        return Grammar(name, Term.from_node(BNFParser(parsing_context, key_path, string).parse()))

    def perform_fixups(self, all_rules):
        self.root_term = self.root_term.perform_fixups(all_rules)

    def perform_fixups_for_values_references(self, values):
        self.root_term = self.root_term.perform_fixups_for_values_references(values)

    def check_against_values(self, values):
        if self.has_non_builtin_reference_terms:
            # If the grammar has any  non-builtin references, the grammar is incomplete and this check cannot be performed.
            return

        keywords_supported_by_grammar = self.supported_keywords
        keywords_listed_as_values = frozenset(value.name for value in values)

        mark = "'"
        keywords_only_in_grammar = keywords_supported_by_grammar - keywords_listed_as_values
        if keywords_only_in_grammar:
            print(f"WARNING: '{self.name}' Found some keywords in parser grammar not list in 'values' array: ({ ', '.join(quote_iterable((keyword for keyword in keywords_only_in_grammar), mark=mark)) })")
        keywords_only_in_values = keywords_listed_as_values - keywords_supported_by_grammar
        if keywords_only_in_values:
            print(f"WARNING: '{self.name}' Found some keywords in 'values' array not supported by the parser grammar: ({ ', '.join(quote_iterable((keyword for keyword in keywords_only_in_values), mark=mark)) })")

    @property
    def has_fast_path_keyword_terms(self):
        if isinstance(self.root_term, MatchOneTerm) and self.root_term.has_fast_path_keyword_terms:
            return True
        return False

    @property
    def has_only_keyword_terms(self):
        if isinstance(self.root_term, MatchOneTerm) and self.root_term.has_only_keyword_terms:
            return True
        return False

    @property
    def has_only_fast_path_keyword_terms(self):
        if isinstance(self.root_term, MatchOneTerm) and self.root_term.has_only_fast_path_keyword_terms:
            return True
        return False

    @property
    def fast_path_keyword_terms(self):
        if isinstance(self.root_term, MatchOneTerm):
            return self.root_term.fast_path_keyword_terms
        return []

    @property
    def fast_path_keyword_terms_sorted_by_name(self):
        if not self._fast_path_keyword_terms_sorted_by_name:
            self._fast_path_keyword_terms_sorted_by_name = sorted(self.fast_path_keyword_terms, key=functools.cmp_to_key(StyleProperties._sort_with_prefixed_properties_last))
        return self._fast_path_keyword_terms_sorted_by_name

    @property
    def supported_keywords(self):
        return self.root_term.supported_keywords

    @property
    def has_non_builtin_reference_terms(self):
        return self.root_term.has_non_builtin_reference_terms


# A shared grammar rule and metadata describing it. Part of the set of rules tracked by SharedGrammarRules.
class SharedGrammarRule:
    schema = Schema(
        Schema.Entry("aliased-to", allowed_types=[str], convert_to=ValueKeywordName),
        Schema.Entry("exported", allowed_types=[bool], default_value=False),
        Schema.Entry("grammar", allowed_types=[str], required=True),
        Schema.Entry("specification", allowed_types=[dict], convert_to=Specification),
        Schema.Entry("status", allowed_types=[dict, str], convert_to=Status),
    )

    def __init__(self, name, **dictionary):
        SharedGrammarRule.schema.set_attributes_from_dictionary(dictionary, instance=self)
        self.name = name
        self.name_for_methods = Name(name[1:-1])

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, name, json_value):
        assert(type(json_value) is dict)
        SharedGrammarRule.schema.validate_dictionary(parsing_context, f"{key_path}.{name}", json_value, label=f"SharedGrammarRule")

        grammar = Grammar.from_string(parsing_context, f"{key_path}.{name}", name, json_value["grammar"])

        if "aliased-to" in json_value:
            if not isinstance(grammar.root_term, KeywordTerm):
                raise Exception(f"Invalid use of 'aliased-to' found at '{key_path}'. 'aliased-to' can only be used with grammars that consist of a single keyword term.")
            grammar.root_term.aliased_to = json_value["aliased-to"]

        json_value["grammar"] = grammar

        return SharedGrammarRule(name, **json_value)

    def perform_fixups(self, all_rules):
        self.grammar.perform_fixups(all_rules)


# Shared grammar rules used to aid in defining property specific grammars.
class SharedGrammarRules:
    def __init__(self, rules):
        self.rules = rules
        self.rules_by_name = {rule.name: rule for rule in rules}
        self._all = None

        self._perform_fixups()

    def __str__(self):
        return "SharedGrammarRules"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def from_json(parsing_context, key_path, json_value):
        return SharedGrammarRules(list(compact_map(lambda item: SharedGrammarRule.from_json(parsing_context, key_path, item[0], item[1]), json_value.items())))

    # Updates any references to other rules with a direct reference to the rule object.
    def _perform_fixups(self):
        for rule in self.rules:
            rule.perform_fixups(self)

    # Returns the set of all shared property rules sorted by name.
    @property
    def all(self):
        if not self._all:
            self._all = sorted(self.rules, key=lambda rule: rule.name)
        return self._all


class ParsingContext:
    class TopLevelObject:
        schema = Schema(
            Schema.Entry("categories", allowed_types=[dict], required=True),
            Schema.Entry("instructions", allowed_types=[list], required=True),
            Schema.Entry("properties", allowed_types=[dict], required=True),
            Schema.Entry("descriptors", allowed_types=[dict], required=True),
            Schema.Entry("shared-grammar-rules", allowed_types=[dict], required=True),
        )

    def __init__(self, json_value, *, defines_string, parsing_for_codegen, check_unused_grammars_values, verbose):
        ParsingContext.TopLevelObject.schema.validate_dictionary(self, "$", json_value, label="top level object")

        self.json_value = json_value
        self.conditionals = frozenset((defines_string or '').split(' '))
        self.parsing_for_codegen = parsing_for_codegen
        self.check_unused_grammars_values = check_unused_grammars_values
        self.verbose = verbose
        self.parsed_shared_grammar_rules = None
        self.parsed_properties_and_descriptors = None

    def parse_shared_grammar_rules(self):
        self.parsed_shared_grammar_rules = SharedGrammarRules.from_json(self, "$shared-grammar-rules", self.json_value["shared-grammar-rules"])

    def parse_properties_and_descriptors(self):
        self.parsed_properties_and_descriptors = PropertiesAndDescriptors.from_json(self, properties_json_value=self.json_value["properties"], descriptors_json_value=self.json_value["descriptors"])

    def is_enabled(self, *, conditional):
        if conditional[0] == '!':
            return conditional[1:] not in self.conditionals
        return conditional in self.conditionals

    def select_enabled_variant(self, variants, *, label):
        for variant in variants:
            if "enable-if" not in variant:
                raise Exception(f"Invalid conditional definition for '{label}'. No 'enable-if' property found.")

            if self.is_enabled(conditional=variant["enable-if"]):
                return variant

        raise Exception(f"Invalid conditional definition for '{label}'. No 'enable-if' property matched the active set.")


# MARK: - Code Generation

class GenerationContext:
    def __init__(self, properties_and_descriptors, shared_grammar_rules, *, verbose, gperf_executable):
        self.properties_and_descriptors = properties_and_descriptors
        self.shared_grammar_rules = shared_grammar_rules
        self.verbose = verbose
        self.gperf_executable = gperf_executable

    # Shared generation constants.

    number_of_predefined_properties = 2

    # Shared generator templates.

    def generate_heading(self, *, to):
        to.write("// This file is automatically generated from CSSProperties.json by the process-css-properties.py script. Do not edit it.")
        to.newline()

    def generate_required_header_pragma(self, *, to):
        to.write(f"#pragma once")
        to.newline()

    def generate_open_namespaces(self, *, to, namespaces):
        for namespace in namespaces:
            if not namespace:
                to.write(f"namespace {{")
            else:
                to.write(f"namespace {namespace} {{")
        to.newline()

    def generate_close_namespaces(self, *, to, namespaces):
        for namespace in namespaces:
            if not namespace:
                to.write(f"}} // namespace (anonymous)")
            else:
                to.write(f"}} // namespace {namespace}")
        to.newline()

    def generate_open_namespace(self, *, to, namespace):
        self.generate_open_namespaces(to=to, namespaces=[namespace])

    def generate_close_namespace(self, *, to, namespace):
        self.generate_close_namespaces(to=to, namespaces=[namespace])

    class Namespaces:
        def __init__(self, generation_context, to, namespaces):
            self.generation_context = generation_context
            self.to = to
            self.namespaces = namespaces

        def __enter__(self):
            self.generation_context.generate_open_namespaces(to=self.to, namespaces=self.namespaces)

        def __exit__(self, exc_type, exc_value, traceback):
            self.generation_context.generate_close_namespaces(to=self.to, namespaces=self.namespaces)

    def namespace(self, namespace, *, to):
        return GenerationContext.Namespaces(self, to, [namespace])

    def namespaces(self, namespaces, *, to):
        return GenerationContext.Namespaces(self, to, namespaces)

    def generate_using_namespace_declarations(self, *, to, namespaces):
        for namespace in namespaces:
            to.write(f"using namespace {namespace};")
        to.newline()

    def generate_includes(self, *, to, headers=[], system_headers=[]):
        for header in headers:
            to.write(f"#include \"{header}\"")
        for header in system_headers:
            to.write(f"#include {header}")
        to.newline()

    def generate_cpp_required_includes(self, *, to, header):
        self.generate_includes(to=to, headers=["config.h", header])

    def generate_forward_declarations(self, *, to, structs=[], classes=[]):
        for struct in structs:
            to.write(f"struct {struct};")
        for class_ in classes:
            to.write(f"class {class_};")
        to.newline()

    def generate_property_id_switch_function(self, *, to, signature, iterable, mapping, default, mapping_to_property=lambda p: p, prologue=None, epilogue=None):
        to.write(f"{signature}")
        to.write(f"{{")

        with to.indent():
            if prologue:
                to.write(prologue)

            to.write(f"switch (id) {{")

            for item in iterable:
                to.write(f"case {mapping_to_property(item).id}:")
                with to.indent():
                    to.write(f"{mapping(item)}")

            to.write(f"default:")
            with to.indent():
                to.write(f"{default}")
            to.write(f"}}")

            if epilogue:
                to.write(epilogue)

        to.write(f"}}")
        to.newline()

    def generate_property_id_switch_function_bool(self, *, to, signature, iterable, mapping_to_property=lambda p: p):
        to.write(f"{signature}")
        to.write(f"{{")

        with to.indent():
            to.write(f"switch (id) {{")

            for item in iterable:
                to.write(f"case {mapping_to_property(item).id}:")

            with to.indent():
                to.write(f"return true;")

            to.write(f"default:")
            with to.indent():
                to.write(f"return false;")

            to.write(f"}}")
        to.write(f"}}")
        to.newline()

    def generate_property_id_bit_set(self, *, to, name, iterable, mapping_to_property=lambda p: p):
        to.write(f"const WTF::BitSet<cssPropertyIDEnumValueCount> {name} = ([]() -> WTF::BitSet<cssPropertyIDEnumValueCount> {{")

        with to.indent():
            to.write(f"WTF::BitSet<cssPropertyIDEnumValueCount> result;")

            for item in iterable:
                to.write(f"result.set({mapping_to_property(item).id});")

            to.write(f"return result;")
        to.write(f"}})();")
        to.newline()

# Generates `CSSPropertyNames.h` and `CSSPropertyNames.cpp`.
class GenerateCSSPropertyNames:
    def __init__(self, generation_context):
        self.generation_context = generation_context

    @property
    def properties_and_descriptors(self):
        return self.generation_context.properties_and_descriptors

    @property
    def properties(self):
        return self.generation_context.properties_and_descriptors.style_properties

    def generate(self):
        self.generate_css_property_names_h()
        self.generate_css_property_names_gperf()
        self.run_gperf()

    # Runs `gperf` on the output of the generated file CSSPropertyNames.gperf
    def run_gperf(self):
        gperf_command = self.generation_context.gperf_executable or os.environ['GPERF']

        gperf_result_code = subprocess.call([gperf_command, '--key-positions=*', '-D', '-n', '-s', '2', 'CSSPropertyNames.gperf', '--output-file=CSSPropertyNames.cpp'])
        if gperf_result_code != 0:
            raise Exception(f"Error when generating CSSPropertyNames.cpp from CSSPropertyNames.gperf: {gperf_result_code}")

    # MARK: - Helper generator functions for CSSPropertyNames.h

    def _generate_css_property_names_gperf_prelude(self, *, to):
        to.write("%{")

        self.generation_context.generate_heading(
            to=to
        )

        self.generation_context.generate_cpp_required_includes(
            to=to,
            header="CSSPropertyNames.h"
        )

        self.generation_context.generate_includes(
            to=to,
            headers=[
                "BoxSides.h",
                "CSSProperty.h",
                "Settings.h",
            ],
            system_headers=[
                "<string.h>",
                "<wtf/ASCIICType.h>",
                "<wtf/Hasher.h>",
                "<wtf/text/AtomString.h>",
            ]
        )

        to.write_block("""
            IGNORE_WARNINGS_BEGIN("implicit-fallthrough")
            WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN

            // Older versions of gperf like to use the `register` keyword.
            #define register
            """)

        self.generation_context.generate_open_namespace(
            to=to,
            namespace="WebCore"
        )

        to.write_block("""\
            static_assert(cssPropertyIDEnumValueCount <= (std::numeric_limits<uint16_t>::max() + 1), "CSSPropertyID should fit into uint16_t.");
            """)

        all_computed_property_ids = (f"{property.id}," for property in self.properties_and_descriptors.style_properties.all_computed)
        to.write(f"const std::array<CSSPropertyID, {count_iterable(self.properties_and_descriptors.style_properties.all_computed)}> computedPropertyIDs {{")
        with to.indent():
            to.write_lines(all_computed_property_ids)
        to.write("};")
        to.newline()

        all_property_name_strings = quote_iterable((f"{property.name}" for property in self.properties_and_descriptors.all_unique), suffix="_s,")
        to.write(f"constexpr ASCIILiteral propertyNameStrings[numCSSProperties] = {{")
        with to.indent():
            to.write_lines(all_property_name_strings)
        to.write("};")
        to.newline()

        to.write("%}")

    def _generate_css_property_names_gperf_footing(self, *, to):
        self.generation_context.generate_close_namespace(
            to=to,
            namespace="WebCore"
        )

        to.write("WTF_ALLOW_UNSAFE_BUFFER_USAGE_END")
        to.write("IGNORE_WARNINGS_END")

    def _generate_gperf_declarations(self, *, to):
        to.write_block("""\
            %struct-type
            struct CSSPropertyHashTableEntry {
                const char* name;
                uint16_t id;
            };
            %language=C++
            %readonly-tables
            %global-table
            %7bit
            %compare-strncmp
            %define class-name CSSPropertyNamesHash
            %enum
            """)

    def _generate_gperf_keywords(self, *, to):
        # Concatenates a list of unique 'propererty-name, property-id' strings with a second list of all 'property-alias, property-id' strings.
        all_property_names_and_aliases_with_ids = itertools.chain(
              [f'{property.name}, {property.id}'                        for property in self.properties_and_descriptors.all_unique],
            *[[f'{alias}, {property.id}' for alias in property.aliases] for property in self.properties_and_descriptors.all_properties_and_descriptors]
        )

        to.write("%%")
        to.write_lines(all_property_names_and_aliases_with_ids)
        to.write("%%")

    def _generate_lookup_functions(self, *, to):
        to.write_block("""
            CSSPropertyID findCSSProperty(const char* characters, unsigned length)
            {
                auto* value = CSSPropertyNamesHash::in_word_set(characters, length);
                return value ? static_cast<CSSPropertyID>(value->id) : CSSPropertyID::CSSPropertyInvalid;
            }

            ASCIILiteral nameLiteral(CSSPropertyID id)
            {
                if (id < firstCSSProperty)
                    return { };
                unsigned index = id - firstCSSProperty;
                if (index >= numCSSProperties)
                    return { };
                return propertyNameStrings[index];
            }

            const AtomString& nameString(CSSPropertyID id)
            {
                if (id < firstCSSProperty)
                    return nullAtom();
                unsigned index = id - firstCSSProperty;
                if (index >= numCSSProperties)
                    return nullAtom();

                static NeverDestroyed<std::array<AtomString, numCSSProperties>> atomStrings;
                auto& string = atomStrings.get()[index];
                if (string.isNull())
                    string = propertyNameStrings[index];
                return string;
            }

            String nameForIDL(CSSPropertyID id)
            {
                LChar characters[maxCSSPropertyNameLength];
                const char* nameForCSS = nameLiteral(id);
                if (!nameForCSS)
                    return emptyString();

                auto* propertyNamePointer = nameForCSS;
                auto* nextCharacter = characters;
                while (char character = *propertyNamePointer++) {
                    if (character == '-') {
                        char nextCharacter = *propertyNamePointer++;
                        if (!nextCharacter)
                            break;
                        character = (propertyNamePointer - 2 != nameForCSS) ? toASCIIUpper(nextCharacter) : nextCharacter;
                    }
                    *nextCharacter++ = character;
                }
                return std::span<const LChar> { characters, nextCharacter };
            }

            """)

    def _generate_physical_logical_conversion_function(self, *, to, signature, source, destination, resolver_enum_prefix):
        source_as_id = PropertyName.convert_name_to_id(source)
        destination_as_id = PropertyName.convert_name_to_id(destination)

        to.write(f"{signature}")
        to.write(f"{{")
        with to.indent():
            to.write(f"switch (id) {{")

            for group_name, property_group in sorted(self.properties_and_descriptors.style_properties.logical_property_groups.items(), key=lambda x: x[0]):
                kind = property_group["kind"]
                kind_as_id = PropertyName.convert_name_to_id(kind)

                destinations = LogicalPropertyGroup.logical_property_group_resolvers[destination][kind]
                properties = [property_group[destination][a_destination].id for a_destination in destinations]

                for resolver, property in sorted(property_group[source].items(), key=lambda x: x[0]):
                    resolver_as_id = PropertyName.convert_name_to_id(resolver)
                    resolver_enum = f"{resolver_enum_prefix}{kind_as_id}::{resolver_as_id}"

                    to.write(f"case {property.id}: {{")
                    with to.indent():
                        to.write(f"static constexpr CSSPropertyID properties[{len(properties)}] = {{ {', '.join(properties)} }};")
                        to.write(f"return properties[static_cast<size_t>(map{kind_as_id}{source_as_id}To{destination_as_id}(writingMode, {resolver_enum}))];")
                    to.write(f"}}")

            to.write(f"default:")
            with to.indent():
                to.write(f"return id;")

            to.write(f"}}")
        to.write(f"}}")
        to.newline()

    def _generate_is_exposed_functions(self, *, to):
        self.generation_context.generate_property_id_switch_function(
            to=to,
            signature="static bool isExposedNotInvalidAndNotInternal(CSSPropertyID id, const CSSPropertySettings& settings)",
            iterable=self.properties_and_descriptors.all_unique_with_settings_flag,
            mapping=lambda p: f"return settings.{p.codegen_properties.settings_flag};",
            default="return true;"
        )

        self.generation_context.generate_property_id_switch_function(
            to=to,
            signature="static bool isExposedNotInvalidAndNotInternal(CSSPropertyID id, const Settings& settings)",
            iterable=self.properties_and_descriptors.all_unique_with_settings_flag,
            mapping=lambda p: f"return settings.{p.codegen_properties.settings_flag}();",
            default="return true;"
        )

        to.write_block("""\
            bool isExposed(CSSPropertyID id, const CSSPropertySettings* settings)
            {
                if (id == CSSPropertyID::CSSPropertyInvalid || isInternal(id))
                    return false;
                if (!settings)
                    return true;
                return isExposedNotInvalidAndNotInternal(id, *settings);
            }

            bool isExposed(CSSPropertyID id, const CSSPropertySettings& settings)
            {
                if (id == CSSPropertyID::CSSPropertyInvalid || isInternal(id))
                    return false;
                return isExposedNotInvalidAndNotInternal(id, settings);
            }

            bool isExposed(CSSPropertyID id, const Settings* settings)
            {
                if (id == CSSPropertyID::CSSPropertyInvalid || isInternal(id))
                    return false;
                if (!settings)
                    return true;
                return isExposedNotInvalidAndNotInternal(id, *settings);
            }

            bool isExposed(CSSPropertyID id, const Settings& settings)
            {
                if (id == CSSPropertyID::CSSPropertyInvalid || isInternal(id))
                    return false;
                return isExposedNotInvalidAndNotInternal(id, settings);
            }
        """)

    def _generate_is_inherited_property(self, *, to):
        all_inherited_and_ids = (f'{"true " if hasattr(property, "inherited") and property.inherited else "false"}, // {property.id}' for property in self.properties_and_descriptors.all_unique)

        to.write(f"constexpr bool isInheritedPropertyTable[cssPropertyIDEnumValueCount] = {{")
        with to.indent():
            to.write(f"false, // CSSPropertyID::CSSPropertyInvalid")
            to.write(f"true , // CSSPropertyID::CSSPropertyCustom")
            to.write_lines(all_inherited_and_ids)
        to.write(f"}};")

        to.write_block("""
            bool CSSProperty::isInheritedProperty(CSSPropertyID id)
            {
                ASSERT(id < cssPropertyIDEnumValueCount);
                ASSERT(id != CSSPropertyID::CSSPropertyInvalid);
                return isInheritedPropertyTable[id];
            }
            """)

    def _generate_are_in_same_logical_property_group_with_different_mappings_logic(self, *, to):
        to.write(f"bool CSSProperty::areInSameLogicalPropertyGroupWithDifferentMappingLogic(CSSPropertyID id1, CSSPropertyID id2)")
        to.write(f"{{")
        with to.indent():
            to.write(f"switch (id1) {{")

            for group_name, property_group in sorted(self.properties_and_descriptors.style_properties.logical_property_groups.items(), key=lambda x: x[0]):
                logical = property_group["logical"]
                physical = property_group["physical"]
                for first in [logical, physical]:
                    second = physical if first is logical else logical
                    for resolver, property in sorted(first.items(), key=lambda x: x[1].name):
                        to.write(f"case {property.id}:")

                    with to.indent():
                        to.write(f"switch (id2) {{")
                        to.write_lines((f"case {property.id}:" for _, property in sorted(second.items(), key=lambda x: x[1].name)))

                        with to.indent():
                            to.write(f"return true;")
                        to.write(f"default:")
                        with to.indent():
                            to.write(f"return false;")
                        to.write(f"}}")

            to.write(f"default:")
            with to.indent():
                to.write(f"return false;")
            to.write(f"}}")
        to.write(f"}}")
        to.newline()

    def _generate_css_property_settings_constructor(self, *, to):
        first_settings_initializer, *remaining_settings_initializers = [f"{flag} {{ settings.{flag}() }}" for flag in self.properties_and_descriptors.settings_flags]

        to.write(f"CSSPropertySettings::CSSPropertySettings(const Settings& settings)")
        with to.indent():
            to.write(f": {first_settings_initializer}")
            to.write_lines((f", {initializer}" for initializer in remaining_settings_initializers))

        to.write(f"{{")
        to.write(f"}}")
        to.newline()

    def _generate_css_property_settings_operator_equal(self, *, to):
        first, *middle, last = (f"a.{flag} == b.{flag}" for flag in self.properties_and_descriptors.settings_flags)

        to.write(f"bool operator==(const CSSPropertySettings& a, const CSSPropertySettings& b)")
        to.write(f"{{")
        with to.indent():
            to.write(f"return {first}")
            with to.indent():
                to.write_lines((f"&& {expression}" for expression in middle))
                to.write(f"&& {last};")

        to.write(f"}}")
        to.newline()

    def _generate_css_property_settings_hasher(self, *, to):
        first, *middle, last = (f'{"(uint64_t) " if i >= 32 else ""}settings.{flag} << {i}' for (i, flag) in enumerate(self.properties_and_descriptors.settings_flags))

        to.write(f"void add(Hasher& hasher, const CSSPropertySettings& settings)")
        to.write(f"{{")
        with to.indent():
            to.write(f"uint64_t bits = {first}")
            with to.indent():
                to.write_lines((f"| {expression}" for expression in middle))
                to.write(f"| {last};")

            to.write(f"add(hasher, bits);")
        to.write(f"}}")
        to.newline()

    def _term_matches_number_or_integer(self, term):
        if isinstance(term, ReferenceTerm):
            if term.name.name == "number" or term.name.name == "integer":
                return True
        elif isinstance(term, MatchOneTerm):
            for inner_term in term.terms:
                if self._term_matches_number_or_integer(inner_term):
                    return True
        elif isinstance(term, UnboundedRepetitionTerm):
            return self._term_matches_number_or_integer(term.repeated_term)
        return False

    def _property_matches_number_or_integer(self, p):
        if p.codegen_properties.parser_function_allows_number_or_integer_input:
            return True
        if not p.codegen_properties.parser_grammar:
            return False
        return self._term_matches_number_or_integer(p.codegen_properties.parser_grammar.root_term)

    def generate_css_property_names_gperf(self):
        with open('CSSPropertyNames.gperf', 'w') as output_file:
            writer = Writer(output_file)

            self._generate_css_property_names_gperf_prelude(
                to=writer
            )

            self._generate_gperf_declarations(
                to=writer
            )

            self._generate_gperf_keywords(
                to=writer
            )

            self._generate_lookup_functions(
                to=writer
            )

            self.generation_context.generate_property_id_switch_function_bool(
                to=writer,
                signature="bool isInternal(CSSPropertyID id)",
                iterable=(p for p in self.properties_and_descriptors.all_unique if p.codegen_properties.internal_only)
            )

            self._generate_is_exposed_functions(
                to=writer
            )

            self._generate_is_inherited_property(
                to=writer
            )

            self.generation_context.generate_property_id_switch_function(
                to=writer,
                signature="CSSPropertyID cascadeAliasProperty(CSSPropertyID id)",
                iterable=(p for p in self.properties_and_descriptors.style_properties.all if p.codegen_properties.cascade_alias),
                mapping=lambda p: f"return {p.codegen_properties.cascade_alias.id};",
                default="return id;"
            )

            self.generation_context.generate_property_id_switch_function(
                to=writer,
                signature="Vector<String> CSSProperty::aliasesForProperty(CSSPropertyID id)",
                iterable=(p for p in self.properties_and_descriptors.style_properties.all if p.codegen_properties.aliases),
                mapping=lambda p: f"return {{ {', '.join(quote_iterable(p.codegen_properties.aliases, suffix='_s'))} }};",
                default="return { };"
            )

            self.generation_context.generate_property_id_bit_set(
                to=writer,
                name="CSSProperty::colorProperties",
                iterable=(p for p in self.properties_and_descriptors.style_properties.all if p.codegen_properties.color_property)
            )

            physical_properties = []
            for _, property_group in sorted(self.generation_context.properties_and_descriptors.style_properties.logical_property_groups.items(), key=lambda x: x[0]):
                kind = property_group["kind"]
                destinations = LogicalPropertyGroup.logical_property_group_resolvers["physical"][kind]
                for property in [property_group["physical"][a_destination] for a_destination in destinations]:
                    physical_properties.append(property)

            self.generation_context.generate_property_id_bit_set(
                to=writer,
                name="CSSProperty::physicalProperties",
                iterable=sorted(list(set(physical_properties)), key=lambda x: x.id)
            )

            self.generation_context.generate_property_id_switch_function(
                to=writer,
                signature="UChar CSSProperty::listValuedPropertySeparator(CSSPropertyID id)",
                iterable=(p for p in self.properties_and_descriptors.style_properties.all if p.codegen_properties.separator),
                mapping=lambda p: f"return '{ p.codegen_properties.separator[0] }';",
                default="break;",
                epilogue="return '\\0';"
            )

            self.generation_context.generate_property_id_switch_function_bool(
                to=writer,
                signature="bool CSSProperty::allowsNumberOrIntegerInput(CSSPropertyID id)",
                iterable=(p for p in self.properties_and_descriptors.style_properties.all if self._property_matches_number_or_integer(p))
            )

            self.generation_context.generate_property_id_switch_function_bool(
                to=writer,
                signature="bool CSSProperty::disablesNativeAppearance(CSSPropertyID id)",
                iterable=(p for p in self.properties_and_descriptors.style_properties.all if p.codegen_properties.disables_native_appearance)
            )

            self.generation_context.generate_property_id_switch_function_bool(
                to=writer,
                signature="bool CSSProperty::isDirectionAwareProperty(CSSPropertyID id)",
                iterable=self.properties_and_descriptors.style_properties.all_direction_aware_properties
            )

            for group_name, property_group in sorted(self.generation_context.properties_and_descriptors.style_properties.logical_property_groups.items(), key=lambda x: x[0]):
                properties = set()
                for kind in ["logical", "physical"]:
                    for property in property_group[kind].values():
                        properties.add(property)
                        if property in self.generation_context.properties_and_descriptors.style_properties.shorthand_by_longhand:
                            properties.add(self.generation_context.properties_and_descriptors.style_properties.shorthand_by_longhand[property])

                group_id = PropertyName.convert_name_to_id(group_name)
                self.generation_context.generate_property_id_switch_function_bool(
                    to=writer,
                    signature=f"bool CSSProperty::is{group_id}Property(CSSPropertyID id)",
                    iterable=sorted(properties, key=lambda x: x.name)
                )

            self.generation_context.generate_property_id_switch_function_bool(
                to=writer,
                signature="bool CSSProperty::isInLogicalPropertyGroup(CSSPropertyID id)",
                iterable=self.properties_and_descriptors.style_properties.all_in_logical_property_group
            )

            self._generate_are_in_same_logical_property_group_with_different_mappings_logic(
                to=writer
            )

            self._generate_physical_logical_conversion_function(
                to=writer,
                signature="CSSPropertyID CSSProperty::resolveDirectionAwareProperty(CSSPropertyID id, WritingMode writingMode)",
                source="logical",
                destination="physical",
                resolver_enum_prefix="LogicalBox"
            )

            self._generate_physical_logical_conversion_function(
                to=writer,
                signature="CSSPropertyID CSSProperty::unresolvePhysicalProperty(CSSPropertyID id, WritingMode writingMode)",
                source="physical",
                destination="logical",
                resolver_enum_prefix="Box"
            )

            self.generation_context.generate_property_id_switch_function_bool(
                to=writer,
                signature="bool CSSProperty::isDescriptorOnly(CSSPropertyID id)",
                iterable=self.properties_and_descriptors.all_descriptor_only
            )

            self._generate_css_property_settings_constructor(
                to=writer
            )

            self._generate_css_property_settings_operator_equal(
                to=writer
            )

            self._generate_css_property_settings_hasher(
                to=writer
            )

            self._generate_css_property_names_gperf_footing(
                to=writer
            )

    # MARK: - Helper generator functions for CSSPropertyNames.h

    def _generate_css_property_names_h_property_constants(self, *, to):
        to.write(f"enum CSSPropertyID : uint16_t {{")
        with to.indent():
            to.write(f"CSSPropertyInvalid = 0,")
            to.write(f"CSSPropertyCustom = 1,")

            first = GenerationContext.number_of_predefined_properties
            count = GenerationContext.number_of_predefined_properties
            max_length = 0
            first_shorthand_property = None
            last_shorthand_property = None
            first_top_priority_property = None
            last_top_priority_property = None
            first_high_priority_property = None
            last_high_priority_property = None
            first_low_priority_property = None
            last_low_priority_property = None
            first_logical_group_property = None
            last_logical_group_property = None

            for property in self.properties_and_descriptors.all_unique:
                if property.codegen_properties.longhands:
                    if not first_shorthand_property:
                        first_shorthand_property = property
                    last_shorthand_property = property
                elif property.codegen_properties.top_priority:
                    if not first_top_priority_property:
                        first_top_priority_property = property
                    last_top_priority_property = property
                elif property.codegen_properties.high_priority:
                    if not first_high_priority_property:
                        first_high_priority_property = property
                    last_high_priority_property = property
                elif not property.codegen_properties.logical_property_group:
                    if not first_low_priority_property:
                        first_low_priority_property = property
                    last_low_priority_property = property
                else:
                    if not first_logical_group_property:
                        first_logical_group_property = property
                    last_logical_group_property = property

                to.write(f"{property.id_without_scope} = {count},")

                count += 1
                max_length = max(len(property.name), max_length)

            num = count - first

        to.write(f"}};")
        to.newline()

        to.write(f"// Enum value of the first \"real\" CSS property, which excludes")
        to.write(f"// CSSPropertyInvalid and CSSPropertyCustom.")
        to.write(f"constexpr uint16_t firstCSSProperty = {first};")

        to.write(f"// Total number of enum values in the CSSPropertyID enum. If making an array")
        to.write(f"// that can be indexed into using the enum value, use this as the size.")
        to.write(f"constexpr uint16_t cssPropertyIDEnumValueCount = {count};")

        to.write(f"// Number of \"real\" CSS properties. This differs from cssPropertyIDEnumValueCount,")
        to.write(f"// as this doesn't consider CSSPropertyInvalid and CSSPropertyCustom.")
        to.write(f"constexpr uint16_t numCSSProperties = {num};")

        to.write(f"constexpr unsigned maxCSSPropertyNameLength = {max_length};")
        to.write(f"constexpr auto firstTopPriorityProperty = {first_top_priority_property.id};")
        to.write(f"constexpr auto lastTopPriorityProperty = {last_top_priority_property.id};")
        to.write(f"constexpr auto firstHighPriorityProperty = {first_high_priority_property.id};")
        to.write(f"constexpr auto lastHighPriorityProperty = {last_high_priority_property.id};")
        to.write(f"constexpr auto firstLowPriorityProperty = {first_low_priority_property.id};")
        to.write(f"constexpr auto lastLowPriorityProperty = {last_low_priority_property.id};")
        to.write(f"constexpr auto firstLogicalGroupProperty = {first_logical_group_property.id};")
        to.write(f"constexpr auto lastLogicalGroupProperty = {last_logical_group_property.id};")
        to.write(f"constexpr auto firstShorthandProperty = {first_shorthand_property.id};")
        to.write(f"constexpr auto lastShorthandProperty = {last_shorthand_property.id};")
        to.write(f"constexpr uint16_t numCSSPropertyLonghands = firstShorthandProperty - firstCSSProperty;")

        to.write(f"extern const std::array<CSSPropertyID, {count_iterable(self.properties_and_descriptors.style_properties.all_computed)}> computedPropertyIDs;")
        to.newline()

    def _generate_css_property_names_h_property_settings(self, *, to):
        settings_variable_declarations = (f"bool {flag} : 1 {{ false }};" for flag in self.properties_and_descriptors.settings_flags)

        to.write(f"struct CSSPropertySettings {{")
        with to.indent():
            to.write(f"WTF_MAKE_STRUCT_FAST_ALLOCATED;")
            to.newline()

            to.write_lines(settings_variable_declarations)
            to.newline()

            to.write(f"CSSPropertySettings() = default;")
            to.write(f"explicit CSSPropertySettings(const Settings&);")
        to.write(f"}};")
        to.newline()

        to.write(f"bool operator==(const CSSPropertySettings&, const CSSPropertySettings&);")
        to.write(f"void add(Hasher&, const CSSPropertySettings&);")
        to.newline()

    def _generate_css_property_names_h_declarations(self, *, to):
        to.write_block("""\
            constexpr bool isLonghand(CSSPropertyID);
            bool isInternal(CSSPropertyID);
            bool isExposed(CSSPropertyID, const Settings*);
            bool isExposed(CSSPropertyID, const Settings&);
            bool isExposed(CSSPropertyID, const CSSPropertySettings*);
            bool isExposed(CSSPropertyID, const CSSPropertySettings&);

            CSSPropertyID findCSSProperty(const char* characters, unsigned length);
            ASCIILiteral nameLiteral(CSSPropertyID);
            const AtomString& nameString(CSSPropertyID);
            String nameForIDL(CSSPropertyID);

            CSSPropertyID cascadeAliasProperty(CSSPropertyID);

            template<CSSPropertyID first, CSSPropertyID last> struct CSSPropertiesRange {
                struct Iterator {
                    uint16_t index { static_cast<uint16_t>(first) };
                    constexpr CSSPropertyID operator*() const { return static_cast<CSSPropertyID>(index); }
                    constexpr Iterator& operator++() { ++index; return *this; }
                    constexpr bool operator==(std::nullptr_t) const { return index > static_cast<uint16_t>(last); }
                };
                static constexpr Iterator begin() { return { }; }
                static constexpr std::nullptr_t end() { return nullptr; }
                static constexpr uint16_t size() { return last - first + 1; }
            };
            using AllCSSPropertiesRange = CSSPropertiesRange<static_cast<CSSPropertyID>(firstCSSProperty), lastShorthandProperty>;
            using AllLonghandCSSPropertiesRange = CSSPropertiesRange<static_cast<CSSPropertyID>(firstCSSProperty), lastLogicalGroupProperty>;
            constexpr AllCSSPropertiesRange allCSSProperties() { return { }; }
            constexpr AllLonghandCSSPropertiesRange allLonghandCSSProperties() { return { }; }

            constexpr bool isLonghand(CSSPropertyID property)
            {
                return static_cast<uint16_t>(property) >= firstCSSProperty && static_cast<uint16_t>(property) < static_cast<uint16_t>(firstShorthandProperty);
            }
            constexpr bool isShorthand(CSSPropertyID property)
            {
                return static_cast<uint16_t>(property) >= static_cast<uint16_t>(firstShorthandProperty) && static_cast<uint16_t>(property) <= static_cast<uint16_t>(lastShorthandProperty);
            }
            """)

    def _generate_css_property_names_h_hash_traits(self, *, to):
        with self.generation_context.namespace("WTF", to=to):
            to.write_block("""\
                template<> struct DefaultHash<WebCore::CSSPropertyID> : IntHash<unsigned> { };

                template<> struct HashTraits<WebCore::CSSPropertyID> : GenericHashTraits<WebCore::CSSPropertyID> {
                    static const bool emptyValueIsZero = true;
                    static void constructDeletedValue(WebCore::CSSPropertyID& slot) { slot = static_cast<WebCore::CSSPropertyID>(std::numeric_limits<uint16_t>::max()); }
                    static bool isDeletedValue(WebCore::CSSPropertyID value) { return static_cast<uint16_t>(value) == std::numeric_limits<uint16_t>::max(); }
                };
                """)

    def _generate_css_property_names_h_iterator_traits(self, *, to):
        with self.generation_context.namespace("std", to=to):
            to.write_block("""\
                template<> struct iterator_traits<WebCore::AllCSSPropertiesRange::Iterator> { using value_type = WebCore::CSSPropertyID; };
                template<> struct iterator_traits<WebCore::AllLonghandCSSPropertiesRange::Iterator> { using value_type = WebCore::CSSPropertyID; };
                """)

    def generate_css_property_names_h(self):
        with open('CSSPropertyNames.h', 'w') as output_file:
            writer = Writer(output_file)

            self.generation_context.generate_heading(
                to=writer
            )

            self.generation_context.generate_required_header_pragma(
                to=writer
            )

            self.generation_context.generate_includes(
                to=writer,
                system_headers=[
                    "<array>",
                    "<wtf/HashFunctions.h>",
                    "<wtf/HashTraits.h>",
                ]
            )

            with self.generation_context.namespace("WebCore", to=writer):
                self.generation_context.generate_forward_declarations(
                    to=writer,
                    classes=["Settings"]
                )

                self._generate_css_property_names_h_property_constants(
                    to=writer
                )

                self._generate_css_property_names_h_property_settings(
                    to=writer
                )

                self._generate_css_property_names_h_declarations(
                    to=writer
                )

            self._generate_css_property_names_h_hash_traits(
                to=writer
            )

            self._generate_css_property_names_h_iterator_traits(
                to=writer
            )


# Generates `CSSStyleDeclaration+PropertyNames.idl`.
class GenerateCSSStyleDeclarationPropertyNames:
    def __init__(self, generation_context):
        self.generation_context = generation_context

    @property
    def properties_and_descriptors(self):
        return self.generation_context.properties_and_descriptors

    def generate(self):
        self.generate_css_style_declaration_property_names_idl()

    # MARK: - Helper generator functions for CSSStyleDeclaration+PropertyNames.idl

    def _generate_css_style_declaration_property_names_idl_typedefs(self, *, to):
        to.write_block("""\
            typedef USVString CSSOMString;
            """)

    def _generate_css_style_declaration_property_names_idl_open_interface(self, *, to):
        to.write("partial interface CSSStyleDeclaration {")

    def _generate_css_style_declaration_property_names_idl_close_interface(self, *, to):
        to.write("};")

    def _convert_css_property_to_idl_attribute(name, *, lowercase_first):
        # https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
        output = ""
        uppercase_next = False

        if lowercase_first:
            name = name[1:]

        for character in name:
            if character == "-":
                uppercase_next = True
            elif uppercase_next:
                uppercase_next = False
                output += character.upper()
            else:
                output += character

        return output

    def _generate_css_style_declaration_property_names_idl_section(self, *, to, comment, names_and_aliases_with_properties, variant, convert_to_idl_attribute, lowercase_first=None):
        to.write_block(comment)

        for name_or_alias, property in names_and_aliases_with_properties:
            if convert_to_idl_attribute:
                idl_attribute_name = GenerateCSSStyleDeclarationPropertyNames._convert_css_property_to_idl_attribute(name_or_alias, lowercase_first=lowercase_first)
            else:
                idl_attribute_name = name_or_alias

            extended_attributes_values = [f"DelegateToSharedSyntheticAttribute=propertyValueFor{variant}IDLAttribute", "CallWith=PropertyName"]
            if property.codegen_properties.settings_flag:
                extended_attributes_values += [f"EnabledBySetting={property.codegen_properties.settings_flag}"]

            to.write(f"[CEReactions=Needed, {', '.join(extended_attributes_values)}] attribute [LegacyNullToEmptyString] CSSOMString {idl_attribute_name};")

    def generate_css_style_declaration_property_names_idl(self):
        with open('CSSStyleDeclaration+PropertyNames.idl', 'w') as output_file:
            writer = Writer(output_file)

            self.generation_context.generate_heading(
                to=writer
            )

            name_or_alias_to_property = {}
            for property in self.properties_and_descriptors.all_unique_non_internal_only:
                name_or_alias_to_property[property.name] = property
                for alias in property.aliases:
                    name_or_alias_to_property[alias] = property

            names_and_aliases_with_properties = sorted(list(name_or_alias_to_property.items()), key=lambda x: x[0])

            self._generate_css_style_declaration_property_names_idl_typedefs(
                to=writer
            )

            self._generate_css_style_declaration_property_names_idl_open_interface(
                to=writer
            )

            with writer.indent():
                self._generate_css_style_declaration_property_names_idl_section(
                    to=writer,
                    comment="""\
                        // For each CSS property property that is a supported CSS property, the following
                        // partial interface applies where camel-cased attribute is obtained by running the
                        // CSS property to IDL attribute algorithm for property.
                        // Example: font-size -> element.style.fontSize
                        // Example: -webkit-transform -> element.style.WebkitTransform
                        // [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString _camel_cased_attribute;
                        """,
                    names_and_aliases_with_properties=names_and_aliases_with_properties,
                    variant="CamelCased",
                    convert_to_idl_attribute=True,
                    lowercase_first=False
                )

                self._generate_css_style_declaration_property_names_idl_section(
                    to=writer,
                    comment="""
                        // For each CSS property property that is a supported CSS property and that begins
                        // with the string -webkit-, the following partial interface applies where webkit-cased
                        // attribute is obtained by running the CSS property to IDL attribute algorithm for
                        // property, with the lowercase first flag set.
                        // Example: -webkit-transform -> element.style.webkitTransform
                        // [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString _webkit_cased_attribute;
                        """,
                    names_and_aliases_with_properties=filter(lambda item: item[0].startswith("-webkit-"), names_and_aliases_with_properties),
                    variant="WebKitCased",
                    convert_to_idl_attribute=True,
                    lowercase_first=True
                )

                self._generate_css_style_declaration_property_names_idl_section(
                    to=writer,
                    comment="""
                        // For each CSS property property that is a supported CSS property, except for
                        // properties that have no "-" (U+002D) in the property name, the following partial
                        // interface applies where dashed attribute is property.
                        // Example: font-size -> element.style['font-size']
                        // Example: -webkit-transform -> element.style.['-webkit-transform']
                        // [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString _dashed_attribute;
                        """,
                    names_and_aliases_with_properties=filter(lambda item: "-" in item[0], names_and_aliases_with_properties),
                    variant="Dashed",
                    convert_to_idl_attribute=False
                )

                self._generate_css_style_declaration_property_names_idl_section(
                    to=writer,
                    comment="""
                        // Non-standard. Special case properties starting with -epub- like is done for
                        // -webkit-, where attribute is obtained by running the CSS property to IDL attribute
                        // algorithm for property, with the lowercase first flag set.
                        // Example: -epub-caption-side -> element.style.epubCaptionSide
                        """,
                    names_and_aliases_with_properties=filter(lambda item: item[0].startswith("-epub-"), names_and_aliases_with_properties),
                    variant="EpubCased",
                    convert_to_idl_attribute=True,
                    lowercase_first=True
                )

            self._generate_css_style_declaration_property_names_idl_close_interface(
                to=writer
            )


# Generates `StyleBuilderGenerated.cpp`.
class GenerateStyleBuilderGenerated:
    def __init__(self, generation_context):
        self.generation_context = generation_context

    @property
    def properties_and_descriptors(self):
        return self.generation_context.properties_and_descriptors

    @property
    def style_properties(self):
        return self.generation_context.properties_and_descriptors.style_properties

    def generate(self):
        self.generate_style_builder_generated_cpp()

    # MARK: - Helper generator functions for StyleBuilderGenerated.cpp

    # Color property setters.

    def _generate_color_property_initial_value_setter(self, to, property):
        if property.codegen_properties.render_style_initial == "currentColor":
            initial_function = "Style::Color::currentColor"
        else:
            initial_function = "RenderStyle::" + property.codegen_properties.render_style_initial
        to.write(f"if (builderState.applyPropertyToRegularStyle())")
        to.write(f"    builderState.style().{property.codegen_properties.render_style_setter}({initial_function}());")
        to.write(f"if (builderState.applyPropertyToVisitedLinkStyle())")
        to.write(f"    builderState.style().setVisitedLink{property.name_for_methods}({initial_function}());")

    def _generate_color_property_inherit_value_setter(self, to, property):
        to.write(f"if (builderState.applyPropertyToRegularStyle())")
        to.write(f"    builderState.style().{property.codegen_properties.render_style_setter}(builderState.parentStyle().{property.codegen_properties.render_style_getter}());")
        to.write(f"if (builderState.applyPropertyToVisitedLinkStyle())")
        to.write(f"    builderState.style().setVisitedLink{property.name_for_methods}(builderState.parentStyle().{property.codegen_properties.render_style_getter}());")

    def _generate_color_property_value_setter(self, to, property, value):
        to.write(f"if (builderState.applyPropertyToRegularStyle())")
        to.write(f"    builderState.style().{property.codegen_properties.render_style_setter}(builderState.createStyleColor({value}, ForVisitedLink::No));")
        to.write(f"if (builderState.applyPropertyToVisitedLinkStyle())")
        to.write(f"    builderState.style().setVisitedLink{property.name_for_methods}(builderState.createStyleColor({value}, ForVisitedLink::Yes));")

    # Animation property setters.

    def _generate_animation_property_initial_value_setter(self, to, property):
        to.write(f"auto& list = builderState.style().{property.method_name_for_ensure_animations_or_transitions}();")
        to.write(f"if (list.isEmpty())")
        to.write(f"    list.append(Animation::create());")
        to.write(f"list.animation(0).{property.codegen_properties.render_style_setter}(Animation::{property.codegen_properties.render_style_initial}());")
        to.write(f"for (auto& animation : list)")
        to.write(f"    animation->clear{property.name_for_methods}();")

    def _generate_animation_property_inherit_value_setter(self, to, property):
        to.write(f"auto& list = builderState.style().{property.method_name_for_ensure_animations_or_transitions}();")
        to.write(f"auto* parentList = builderState.parentStyle().{property.method_name_for_animations_or_transitions}();")
        to.write(f"size_t i = 0, parentSize = parentList ? parentList->size() : 0;")
        to.write(f"for ( ; i < parentSize && parentList->animation(i).is{property.name_for_methods}Set(); ++i) {{")
        to.write(f"    if (list.size() <= i)")
        to.write(f"        list.append(Animation::create());")
        to.write(f"    list.animation(i).{property.codegen_properties.render_style_setter}(parentList->animation(i).{property.codegen_properties.render_style_getter}());")
        to.write(f"}}")
        to.write(f"// Reset any remaining animations to not have the property set.")
        to.write(f"for ( ; i < list.size(); ++i)")
        to.write(f"    list.animation(i).clear{property.name_for_methods}();")

    def _generate_animation_property_value_setter(self, to, property):
        to.write(f"auto& list = builderState.style().{property.method_name_for_ensure_animations_or_transitions}();")
        to.write(f"size_t childIndex = 0;")
        to.write(f"if (auto* valueList = dynamicDowncast<CSSValueList>(value)) {{")
        to.write(f"    // Walk each value and put it into an animation, creating new animations as needed.")
        to.write(f"    for (auto& currentValue : *valueList) {{")
        to.write(f"        if (childIndex >= list.size())")
        to.write(f"            list.append(Animation::create());")
        to.write(f"        builderState.styleMap().mapAnimation{property.name_for_methods}(list.animation(childIndex), currentValue);")
        to.write(f"        ++childIndex;")
        to.write(f"    }}")
        to.write(f"}} else {{")
        to.write(f"    if (list.isEmpty())")
        to.write(f"        list.append(Animation::create());")
        to.write(f"    builderState.styleMap().mapAnimation{property.name_for_methods}(list.animation(childIndex), value);")
        to.write(f"    childIndex = 1;")
        to.write(f"}}")
        to.write(f"for ( ; childIndex < list.size(); ++childIndex) {{")
        to.write(f"    // Reset all remaining animations to not have the property set.")
        to.write(f"    list.animation(childIndex).clear{property.name_for_methods}();")
        to.write(f"}}")

    # Font property setters.

    def _generate_font_property_initial_value_setter(self, to, property):
        to.write(f"auto fontDescription = builderState.fontDescription();")
        to.write(f"fontDescription.{property.codegen_properties.render_style_setter}(FontCascadeDescription::{property.codegen_properties.render_style_initial}());")
        to.write(f"builderState.setFontDescription(WTFMove(fontDescription));")

    def _generate_font_property_inherit_value_setter(self, to, property):
        to.write(f"auto fontDescription = builderState.fontDescription();")
        to.write(f"auto inheritedValue = builderState.parentFontDescription().{property.codegen_properties.render_style_getter}();")
        to.write(f"fontDescription.{property.codegen_properties.render_style_setter}(WTFMove(inheritedValue));")
        to.write(f"builderState.setFontDescription(WTFMove(fontDescription));")

    def _generate_font_property_value_setter(self, to, property, value):
        to.write(f"auto fontDescription = builderState.fontDescription();")
        to.write(f"fontDescription.{property.codegen_properties.render_style_setter}({value});")
        to.write(f"builderState.setFontDescription(WTFMove(fontDescription));")

    # Fill Layer property setters.

    def _generate_fill_layer_property_initial_value_setter(self, to, property):
        initial = f"FillLayer::{property.codegen_properties.render_style_initial}({property.enum_name_for_layers_type})"
        to.write(f"// Check for (single-layer) no-op before clearing anything.")
        to.write(f"auto& layers = builderState.style().{property.method_name_for_layers}();")
        to.write(f"if (!layers.next() && (!layers.is{property.name_for_methods}Set() || layers.{property.codegen_properties.render_style_getter}() == {initial}))")
        to.write(f"    return;")
        to.write(f"auto* child = &builderState.style().{property.method_name_for_ensure_layers}();")
        to.write(f"child->{property.codegen_properties.render_style_setter}({initial});")
        to.write(f"for (child = child->next(); child; child = child->next())")
        to.write(f"    child->clear{property.name_for_methods}();")

    def _generate_fill_layer_property_inherit_value_setter(self, to, property):
        to.write(f"// Check for no-op before copying anything.")
        to.write(f"if (builderState.parentStyle().{property.method_name_for_layers}() == builderState.style().{property.method_name_for_layers}())")
        to.write(f"    return;")
        to.write(f"auto* child = &builderState.style().{property.method_name_for_ensure_layers}();")
        to.write(f"FillLayer* previousChild = nullptr;")
        to.write(f"for (auto* parent = &builderState.parentStyle().{property.method_name_for_layers}(); parent && parent->is{property.name_for_methods}Set(); parent = parent->next()) {{")
        to.write(f"    if (!child) {{")
        to.write(f"        previousChild->setNext(FillLayer::create({property.enum_name_for_layers_type}));")
        to.write(f"        child = previousChild->next();")
        to.write(f"    }}")
        to.write(f"    child->{property.codegen_properties.render_style_setter}(parent->{property.codegen_properties.render_style_getter}());")
        to.write(f"    previousChild = child;")
        to.write(f"    child = previousChild->next();")
        to.write(f"}}")
        to.write(f"for (; child; child = child->next())")
        to.write(f"    child->clear{property.name_for_methods}();")

    def _generate_fill_layer_property_value_setter(self, to, property):
        to.write(f"auto* child = &builderState.style().{property.method_name_for_ensure_layers}();")
        to.write(f"FillLayer* previousChild = nullptr;")
        to.write(f"if (auto* valueList = dynamicDowncast<CSSValueList>(value)) {{")
        to.write(f"    // Walk each value and put it into a layer, creating new layers as needed.")
        to.write(f"    for (auto& item : *valueList) {{")
        to.write(f"        if (!child) {{")
        to.write(f"            previousChild->setNext(FillLayer::create({property.enum_name_for_layers_type}));")
        to.write(f"            child = previousChild->next();")
        to.write(f"        }}")
        to.write(f"        builderState.styleMap().mapFill{property.name_for_methods}(id, *child, item);")
        to.write(f"        previousChild = child;")
        to.write(f"        child = child->next();")
        to.write(f"    }}")
        to.write(f"}} else {{")
        to.write(f"    builderState.styleMap().mapFill{property.name_for_methods}(id, *child, value);")
        to.write(f"    child = child->next();")
        to.write(f"}}")
        to.write(f"for (; child; child = child->next())")
        to.write(f"    child->clear{property.name_for_methods}();")

    # SVG property setters.

    def _generate_svg_property_initial_value_setter(self, to, property):
        to.write(f"builderState.style().accessSVGStyle().{property.codegen_properties.render_style_setter}(SVGRenderStyle::{property.codegen_properties.render_style_initial}());")

    def _generate_svg_property_inherit_value_setter(self, to, property):
        to.write(f"builderState.style().accessSVGStyle().{property.codegen_properties.render_style_setter}(forwardInheritedValue(builderState.parentStyle().svgStyle().{property.codegen_properties.render_style_getter}()));")

    def _generate_svg_property_value_setter(self, to, property, value):
        to.write(f"builderState.style().accessSVGStyle().{property.codegen_properties.render_style_setter}({value});")

    # All other property setters.

    def _generate_property_initial_value_setter(self, to, property):
        to.write(f"builderState.style().{property.codegen_properties.render_style_setter}(RenderStyle::{property.codegen_properties.render_style_initial}());")

    def _generate_property_inherit_value_setter(self, to, property):
        to.write(f"builderState.style().{property.codegen_properties.render_style_setter}(forwardInheritedValue(builderState.parentStyle().{property.codegen_properties.render_style_getter}()));")

    def _generate_property_value_setter(self, to, property, value):
        to.write(f"builderState.style().{property.codegen_properties.render_style_setter}({value});")

    # Property setter dispatch.

    def _generate_style_builder_generated_cpp_initial_value_setter(self, to, property):
        to.write(f"static void applyInitial{property.id_without_prefix}(BuilderState& builderState)")
        to.write(f"{{")

        with to.indent():
            if property.codegen_properties.auto_functions:
                to.write(f"builderState.style().setHasAuto{property.name_for_methods}();")
            elif property.codegen_properties.visited_link_color_support:
                self._generate_color_property_initial_value_setter(to, property)
            elif property.codegen_properties.animation_property:
                self._generate_animation_property_initial_value_setter(to, property)
            elif property.codegen_properties.font_property:
                self._generate_font_property_initial_value_setter(to, property)
            elif property.codegen_properties.fill_layer_property:
                self._generate_fill_layer_property_initial_value_setter(to, property)
            elif property.codegen_properties.svg:
                self._generate_svg_property_initial_value_setter(to, property)
            else:
                self._generate_property_initial_value_setter(to, property)

            if property.codegen_properties.fast_path_inherited:
                to.write(f"builderState.style().setDisallowsFastPathInheritance();")

        to.write(f"}}")

    def _generate_style_builder_generated_cpp_inherit_value_setter(self, to, property):
        to.write(f"static void applyInherit{property.id_without_prefix}(BuilderState& builderState)")
        to.write(f"{{")

        with to.indent():
            if property.codegen_properties.auto_functions:
                to.write(f"if (builderState.parentStyle().hasAuto{property.name_for_methods}()) {{")
                with to.indent():
                    to.write(f"builderState.style().setHasAuto{property.name_for_methods}();")
                    to.write(f"return;")
                to.write(f"}}")

                if property.codegen_properties.svg:
                    self._generate_svg_property_inherit_value_setter(to, property)
                else:
                    self._generate_property_inherit_value_setter(to, property)
            elif property.codegen_properties.visited_link_color_support:
                self._generate_color_property_inherit_value_setter(to, property)
            elif property.codegen_properties.animation_property:
                self._generate_animation_property_inherit_value_setter(to, property)
            elif property.codegen_properties.font_property:
                self._generate_font_property_inherit_value_setter(to, property)
            elif property.codegen_properties.fill_layer_property:
                self._generate_fill_layer_property_inherit_value_setter(to, property)
            elif property.codegen_properties.svg:
                self._generate_svg_property_inherit_value_setter(to, property)
            else:
                self._generate_property_inherit_value_setter(to, property)

            if property.codegen_properties.fast_path_inherited:
                to.write(f"builderState.style().setDisallowsFastPathInheritance();")

        to.write(f"}}")

    def _generate_style_builder_generated_cpp_value_setter(self, to, property):
        if property.codegen_properties.fill_layer_property:
            to.write(f"static void applyValue{property.id_without_prefix}(CSSPropertyID id, BuilderState& builderState, CSSValue& value)")
        else:
            to.write(f"static void applyValue{property.id_without_prefix}(BuilderState& builderState, CSSValue& value)")
        to.write(f"{{")

        with to.indent():
            def converted_value(property):
                if property.codegen_properties.style_builder_converter:
                    return f"BuilderConverter::convert{property.codegen_properties.style_builder_converter}(builderState, value)"
                elif property.codegen_properties.style_builder_conditional_converter:
                    return f"WTFMove(convertedValue.value())"
                elif property.codegen_properties.color_property and not property.codegen_properties.visited_link_color_support:
                    return f"builderState.createStyleColor(value, ForVisitedLink::No)"
                else:
                    return "fromCSSValueDeducingType(builderState, value)"

            if property in self.style_properties.all_by_name["font"].codegen_properties.longhands and "Initial" not in property.codegen_properties.style_builder_custom and not property.codegen_properties.style_builder_converter:
                to.write(f"if (CSSPropertyParserHelpers::isSystemFontShorthand(value.valueID())) {{")
                with to.indent():
                    to.write(f"applyInitial{property.id_without_prefix}(builderState);")
                    to.write(f"return;")
                to.write(f"}}")

            if property.codegen_properties.auto_functions:
                to.write(f"if (value.valueID() == CSSValueAuto) {{")
                with to.indent():
                    to.write(f"builderState.style().setHasAuto{property.name_for_methods}();")
                    to.write(f"return;")
                to.write(f"}}")
                if property.codegen_properties.svg:
                    self._generate_svg_property_value_setter(to, property, converted_value(property))
                else:
                    self._generate_property_value_setter(to, property, converted_value(property))
            elif property.codegen_properties.visited_link_color_support:
                self._generate_color_property_value_setter(to, property, converted_value(property))
            elif property.codegen_properties.animation_property:
                self._generate_animation_property_value_setter(to, property)
            elif property.codegen_properties.font_property:
                self._generate_font_property_value_setter(to, property, converted_value(property))
            elif property.codegen_properties.fill_layer_property:
                self._generate_fill_layer_property_value_setter(to, property)
            else:
                if property.codegen_properties.style_builder_conditional_converter:
                    to.write(f"auto convertedValue = BuilderConverter::convert{property.codegen_properties.style_builder_conditional_converter}(builderState, value);")
                    to.write(f"if (convertedValue)")
                    with to.indent():
                        if property.codegen_properties.svg:
                            self._generate_svg_property_value_setter(to, property, converted_value(property))
                        else:
                            self._generate_property_value_setter(to, property, converted_value(property))
                else:
                    if property.codegen_properties.svg:
                        self._generate_svg_property_value_setter(to, property, converted_value(property))
                    else:
                        self._generate_property_value_setter(to, property, converted_value(property))

            if property.codegen_properties.fast_path_inherited:
                to.write(f"builderState.style().setDisallowsFastPathInheritance();")

        to.write(f"}}")

    def _generate_style_builder_generated_cpp_builder_functions_class(self, *, to):
        to.write(f"class BuilderFunctions {{")
        to.write(f"public:")

        with to.indent():
            for property in self.style_properties.all:
                if property.codegen_properties.longhands:
                    continue
                if property.codegen_properties.skip_style_builder:
                    continue

                if property.codegen_properties.is_logical:
                    raise Exception(f"Property '{property.name}' is logical but doesn't have skip-style-builder.")

                if "Initial" not in property.codegen_properties.style_builder_custom:
                    self._generate_style_builder_generated_cpp_initial_value_setter(to, property)
                if "Inherit" not in property.codegen_properties.style_builder_custom:
                    self._generate_style_builder_generated_cpp_inherit_value_setter(to, property)
                if "Value" not in property.codegen_properties.style_builder_custom:
                    self._generate_style_builder_generated_cpp_value_setter(to, property)

        to.write(f"}};")

    def _generate_style_builder_generated_cpp_builder_generated_apply(self, *, to):
        to.write_block("""
            void BuilderGenerated::applyProperty(CSSPropertyID id, BuilderState& builderState, CSSValue& value, ApplyValueType valueType)
            {
                switch (id) {
                case CSSPropertyID::CSSPropertyInvalid:
                    break;
                case CSSPropertyID::CSSPropertyCustom:
                    ASSERT_NOT_REACHED();
                    break;""")

        with to.indent():
            def scope_for_function(property, function):
                if function in property.codegen_properties.style_builder_custom:
                    return "BuilderCustom"
                return "BuilderFunctions"

            for property in self.properties_and_descriptors.all_unique:
                if not isinstance(property, StyleProperty):
                    to.write(f"case {property.id}:")
                    with to.indent():
                        to.write(f"break;")
                    continue

                to.write(f"case {property.id}:")

                with to.indent():
                    if property.codegen_properties.longhands:
                        to.write(f"ASSERT(isShorthand(id));")
                        to.write(f"ASSERT_NOT_REACHED();")
                    elif not property.codegen_properties.skip_style_builder:
                        apply_initial_arguments = ["builderState"]
                        apply_inherit_arguments = ["builderState"]
                        apply_value_arguments = ["builderState", "value"]
                        if property.codegen_properties.fill_layer_property:
                            apply_value_arguments.insert(0, "id")

                        to.write(f"switch (valueType) {{")
                        to.write(f"case ApplyValueType::Initial:")
                        with to.indent():
                            to.write(f"{scope_for_function(property, 'Initial')}::applyInitial{property.id_without_prefix}({', '.join(apply_initial_arguments)});")
                            to.write(f"break;")
                        to.write(f"case ApplyValueType::Inherit:")
                        with to.indent():
                            to.write(f"{scope_for_function(property, 'Inherit')}::applyInherit{property.id_without_prefix}({', '.join(apply_inherit_arguments)});")
                            to.write(f"break;")
                        to.write("case ApplyValueType::Value:")
                        with to.indent():
                            to.write(f"{scope_for_function(property, 'Value')}::applyValue{property.id_without_prefix}({', '.join(apply_value_arguments)});")
                            to.write(f"break;")
                        to.write(f"}}")

                    to.write(f"break;")

            to.write(f"}}")
        to.write(f"}}")
        to.newline()

    def generate_style_builder_generated_cpp(self):
        with open('StyleBuilderGenerated.cpp', 'w') as output_file:
            writer = Writer(output_file)

            self.generation_context.generate_heading(
                to=writer
            )

            self.generation_context.generate_cpp_required_includes(
                to=writer,
                header="StyleBuilderGenerated.h"
            )

            self.generation_context.generate_includes(
                to=writer,
                headers=[
                    "CSSPrimitiveValueMappings.h",
                    "CSSProperty.h",
                    "RenderStyleSetters.h",
                    "StyleBuilderConverter.h",
                    "StyleBuilderCustom.h",
                    "StyleBuilderState.h",
                    "StylePropertyShorthand.h",
                ]
            )

            with self.generation_context.namespaces(["WebCore", "Style"], to=writer):
                self._generate_style_builder_generated_cpp_builder_functions_class(
                    to=writer
                )

                self._generate_style_builder_generated_cpp_builder_generated_apply(
                    to=writer
                )


# Generates `StylePropertyShorthandFunctions.h` and `StylePropertyShorthandFunctions.cpp`.
class GenerateStylePropertyShorthandFunctions:
    def __init__(self, generation_context):
        self.generation_context = generation_context

    @property
    def style_properties(self):
        return self.generation_context.properties_and_descriptors.style_properties

    def generate(self):
        self.generate_style_property_shorthand_functions_h()
        self.generate_style_property_shorthand_functions_cpp()

    # MARK: - Helper generator functions for StylePropertyShorthandFunctions.h

    def _generate_style_property_shorthand_functions_declarations(self, *, to):
        # Skip non-shorthand properties (aka properties WITH longhands).
        for property in self.style_properties.all_shorthands:
            to.write(f"StylePropertyShorthand {property.id_without_prefix_with_lowercase_first_letter}Shorthand();")
        to.newline()

    def generate_style_property_shorthand_functions_h(self):
        with open('StylePropertyShorthandFunctions.h', 'w') as output_file:
            writer = Writer(output_file)

            self.generation_context.generate_heading(
                to=writer
            )

            self.generation_context.generate_required_header_pragma(
                to=writer
            )

            with self.generation_context.namespace("WebCore", to=writer):
                self.generation_context.generate_forward_declarations(
                    to=writer,
                    classes=["StylePropertyShorthand"]
                )

                self._generate_style_property_shorthand_functions_declarations(
                    to=writer
                )

    # MARK: - Helper generator functions for StylePropertyShorthandFunctions.cpp

    def _generate_style_property_shorthand_functions_accessors(self, *, to, longhand_to_shorthands, shorthand_to_longhand_count):
        for property in self.style_properties.all_shorthands:
            to.write(f"StylePropertyShorthand {property.id_without_prefix_with_lowercase_first_letter}Shorthand()")
            to.write(f"{{")
            with to.indent():
                to.write(f"static const CSSPropertyID {property.id_without_prefix_with_lowercase_first_letter}Properties[] = {{")

                with to.indent():
                    shorthand_to_longhand_count[property] = 0
                    for longhand in property.codegen_properties.longhands:
                        if longhand.name == "all":
                            for inner_property in self.style_properties.all_non_shorthands:
                                if inner_property.name == "direction" or inner_property.name == "unicode-bidi":
                                    continue
                                longhand_to_shorthands.setdefault(inner_property, [])
                                longhand_to_shorthands[inner_property].append(property)
                                shorthand_to_longhand_count[property] += 1
                                to.write(f"{inner_property.id},")
                        else:
                            longhand_to_shorthands.setdefault(longhand, [])
                            longhand_to_shorthands[longhand].append(property)
                            shorthand_to_longhand_count[property] += 1
                            to.write(f"{longhand.id},")

                to.write(f"}};")
                to.write(f"return StylePropertyShorthand({property.id}, std::span {{ {property.id_without_prefix_with_lowercase_first_letter}Properties }});")
            to.write(f"}}")
            to.newline()

    def _generate_style_property_shorthand_functions_matching_shorthands_for_longhand(self, *, to, longhand_to_shorthands, shorthand_to_longhand_count):
        to.write(f"StylePropertyShorthandVector matchingShorthandsForLonghand(CSSPropertyID id)")
        to.write(f"{{")
        with to.indent():
            to.write(f"switch (id) {{")

            vector_to_longhands = {}

            # https://drafts.csswg.org/cssom/#concept-shorthands-preferred-order
            def preferred_order_for_shorthands(x):
                return (-shorthand_to_longhand_count[x], x.name.startswith("-"), not x.name.startswith("-webkit-"), x.name)

            for longhand, shorthands in sorted(list(longhand_to_shorthands.items()), key=lambda item: item[0].name):
                shorthand_calls = [f"{p.id_without_prefix_with_lowercase_first_letter}Shorthand()" for p in sorted(shorthands, key=preferred_order_for_shorthands)]
                vector = f"StylePropertyShorthandVector{{{ ', '.join(shorthand_calls) }}}"
                vector_to_longhands.setdefault(vector, [])
                vector_to_longhands[vector].append(longhand)

            for vector, longhands in sorted(list(vector_to_longhands.items()), key=lambda item: item[0]):
                for longhand in longhands:
                    to.write(f"case {longhand.id}:")
                with to.indent():
                    to.write(f"return {vector};")

            to.write(f"default:")
            with to.indent():
                to.write(f"return {{ }};")
            to.write(f"}}")
        to.write(f"}}")
        to.newline()

    def generate_style_property_shorthand_functions_cpp(self):
        with open('StylePropertyShorthandFunctions.cpp', 'w') as output_file:
            writer = Writer(output_file)

            self.generation_context.generate_heading(
                to=writer
            )

            self.generation_context.generate_cpp_required_includes(
                to=writer,
                header="StylePropertyShorthandFunctions.h"
            )

            self.generation_context.generate_includes(
                to=writer,
                headers=[
                    "StylePropertyShorthand.h",
                ]
            )

            with self.generation_context.namespace("WebCore", to=writer):
                longhand_to_shorthands = {}
                shorthand_to_longhand_count = {}

                self._generate_style_property_shorthand_functions_accessors(
                    to=writer,
                    longhand_to_shorthands=longhand_to_shorthands,
                    shorthand_to_longhand_count=shorthand_to_longhand_count
                )

                self.generation_context.generate_property_id_switch_function(
                    to=writer,
                    signature="StylePropertyShorthand shorthandForProperty(CSSPropertyID id)",
                    iterable=self.style_properties.all_shorthands,
                    mapping=lambda p: f"return {p.id_without_prefix_with_lowercase_first_letter}Shorthand();",
                    default="return { };"
                )

                self._generate_style_property_shorthand_functions_matching_shorthands_for_longhand(
                    to=writer,
                    longhand_to_shorthands=longhand_to_shorthands,
                    shorthand_to_longhand_count=shorthand_to_longhand_count
                )


# Generates `CSSPropertyParsing.h` and `CSSPropertyParsing.cpp`.
class GenerateCSSPropertyParsing:
    def __init__(self, generation_context):
        self.generation_context = generation_context

        # Create a handler for each property and add it to the `property_consumers` map.
        self.property_consumers = {property: PropertyConsumer.make(property) for property in generation_context.properties_and_descriptors.all_properties_and_descriptors}
        self.shared_grammar_rule_consumers = {shared_grammar_rule: SharedGrammarRuleConsumer.make(shared_grammar_rule) for shared_grammar_rule in generation_context.shared_grammar_rules.all}

    def generate(self):
        self.generate_css_property_parsing_h()
        self.generate_css_property_parsing_cpp()

    @property
    def properties_and_descriptors(self):
        return self.generation_context.properties_and_descriptors

    @property
    def shared_grammar_rules(self):
        return self.generation_context.shared_grammar_rules

    @property
    def all_property_consumers(self):
        return (self.property_consumers[property] for property in self.properties_and_descriptors.all_properties_and_descriptors)

    @property
    def all_shared_grammar_rule_consumers(self):
        return (self.shared_grammar_rule_consumers[shared_grammar_rule] for shared_grammar_rule in self.shared_grammar_rules.all)

    @property
    def all_property_parsing_collections(self):
        ParsingCollection = collections.namedtuple('ParsingCollection', ['id', 'name', 'noun', 'supports_current_shorthand', 'consumers'])

        result = []
        for set in self.properties_and_descriptors.all_sets:
            result += [ParsingCollection(set.id, set.name, set.noun, set.supports_current_shorthand, list(self.property_consumers[property] for property in set.all))]
        return result

    @property
    def all_consumers_grouped_by_kind(self):
        ConsumerCollection = collections.namedtuple('ConsumerCollection', ['description', 'consumers'])

        return [ConsumerCollection(f'{parsing_collection.name} {parsing_collection.noun}', parsing_collection.consumers) for parsing_collection in self.all_property_parsing_collections] + [ConsumerCollection(f'shared', list(self.all_shared_grammar_rule_consumers))]

    def generate_css_property_parsing_h(self):
        with open('CSSPropertyParsing.h', 'w') as output_file:
            writer = Writer(output_file)

            self.generation_context.generate_heading(
                to=writer
            )

            self.generation_context.generate_required_header_pragma(
                to=writer
            )

            self.generation_context.generate_includes(
                to=writer,
                headers=[
                    "CSSPropertyNames.h",
                    "CSSValueKeywords.h",
                ]
            )

            with self.generation_context.namespace("WebCore", to=writer):
                self.generation_context.generate_forward_declarations(
                    to=writer,
                    classes=[
                        "CSSParserTokenRange",
                        "CSSValue"
                    ],
                    structs=[
                        "CSSParserContext"
                    ]
                )

                self._generate_css_property_parsing_h_property_parsing_declaration(
                    to=writer
                )

    def generate_css_property_parsing_cpp(self):
        with open('CSSPropertyParsing.cpp', 'w') as output_file:
            writer = Writer(output_file)

            self.generation_context.generate_heading(
                to=writer
            )

            self.generation_context.generate_cpp_required_includes(
                to=writer,
                header="CSSPropertyParsing.h"
            )

            self.generation_context.generate_includes(
                to=writer,
                headers=[
                    "CSSParserContext.h",
                    "CSSParserIdioms.h",
                    "CSSPropertyParser.h",
                    "CSSPropertyParserConsumer+Align.h",
                    "CSSPropertyParserConsumer+Anchor.h",
                    "CSSPropertyParserConsumer+Angle.h",
                    "CSSPropertyParserConsumer+Animations.h",
                    "CSSPropertyParserConsumer+AppleVisualEffect.h",
                    "CSSPropertyParserConsumer+Attr.h",
                    "CSSPropertyParserConsumer+Background.h",
                    "CSSPropertyParserConsumer+Box.h",
                    "CSSPropertyParserConsumer+Color.h",
                    "CSSPropertyParserConsumer+ColorAdjust.h",
                    "CSSPropertyParserConsumer+Conditional.h",
                    "CSSPropertyParserConsumer+Contain.h",
                    "CSSPropertyParserConsumer+Content.h",
                    "CSSPropertyParserConsumer+CounterStyles.h",
                    "CSSPropertyParserConsumer+Display.h",
                    "CSSPropertyParserConsumer+Easing.h",
                    "CSSPropertyParserConsumer+Filter.h",
                    "CSSPropertyParserConsumer+Font.h",
                    "CSSPropertyParserConsumer+Grid.h",
                    "CSSPropertyParserConsumer+Ident.h",
                    "CSSPropertyParserConsumer+Image.h",
                    "CSSPropertyParserConsumer+Inline.h",
                    "CSSPropertyParserConsumer+Inset.h",
                    "CSSPropertyParserConsumer+Integer.h",
                    "CSSPropertyParserConsumer+Length.h",
                    "CSSPropertyParserConsumer+LengthPercentage.h",
                    "CSSPropertyParserConsumer+List.h",
                    "CSSPropertyParserConsumer+Lists.h",
                    "CSSPropertyParserConsumer+Masking.h",
                    "CSSPropertyParserConsumer+Motion.h",
                    "CSSPropertyParserConsumer+Number.h",
                    "CSSPropertyParserConsumer+Overflow.h",
                    "CSSPropertyParserConsumer+Page.h",
                    "CSSPropertyParserConsumer+Percentage.h",
                    "CSSPropertyParserConsumer+PointerEvents.h",
                    "CSSPropertyParserConsumer+Position.h",
                    "CSSPropertyParserConsumer+PositionTry.h",
                    "CSSPropertyParserConsumer+Primitives.h",
                    "CSSPropertyParserConsumer+Resolution.h",
                    "CSSPropertyParserConsumer+Ruby.h",
                    "CSSPropertyParserConsumer+SVG.h",
                    "CSSPropertyParserConsumer+ScrollSnap.h",
                    "CSSPropertyParserConsumer+Scrollbars.h",
                    "CSSPropertyParserConsumer+Shapes.h",
                    "CSSPropertyParserConsumer+Sizing.h",
                    "CSSPropertyParserConsumer+Speech.h",
                    "CSSPropertyParserConsumer+String.h",
                    "CSSPropertyParserConsumer+Syntax.h",
                    "CSSPropertyParserConsumer+Text.h",
                    "CSSPropertyParserConsumer+TextDecoration.h",
                    "CSSPropertyParserConsumer+Time.h",
                    "CSSPropertyParserConsumer+Timeline.h",
                    "CSSPropertyParserConsumer+Transform.h",
                    "CSSPropertyParserConsumer+Transitions.h",
                    "CSSPropertyParserConsumer+UI.h",
                    "CSSPropertyParserConsumer+URL.h",
                    "CSSPropertyParserConsumer+ViewTransition.h",
                    "CSSPropertyParserConsumer+WillChange.h",
                    "CSSValuePool.h",
                    "DeprecatedGlobalSettings.h",
                ]
            )

            with self.generation_context.namespace("WebCore", to=writer):
                self.generation_context.generate_using_namespace_declarations(
                    to=writer,
                    namespaces=["CSSPropertyParserHelpers"]
                )

                self._generate_css_property_parsing_cpp_property_parsing_functions(
                    to=writer
                )

                for parsing_collection in self.all_property_parsing_collections:
                    self._generate_css_property_parsing_cpp_parse_property(
                        to=writer,
                        parsing_collection=parsing_collection
                    )

                    keyword_fast_path_eligible_property_consumers = [consumer for consumer in parsing_collection.consumers if consumer.keyword_fast_path_generator]

                    self._generate_css_property_parsing_cpp_is_keyword_valid_for_property(
                        to=writer,
                        parsing_collection=parsing_collection,
                        keyword_fast_path_eligible_property_consumers=keyword_fast_path_eligible_property_consumers
                    )

                    self._generate_css_property_parsing_cpp_is_keyword_fast_path_eligible_for_property(
                        to=writer,
                        parsing_collection=parsing_collection,
                        keyword_fast_path_eligible_property_consumers=keyword_fast_path_eligible_property_consumers
                    )

    # MARK: - Helper generator functions for CSSPropertyParsing.h

    def _generate_css_property_parsing_h_property_parsing_declaration(self, *, to):
        to.write(f"struct CSSPropertyParsing {{")

        with to.indent():
            for parsing_collection in self.all_property_parsing_collections:
                to.write(f"// Parse and return a single longhand {parsing_collection.name} {parsing_collection.noun}.")
                if parsing_collection.supports_current_shorthand:
                    to.write(f"static RefPtr<CSSValue> parse{parsing_collection.id}(CSSParserTokenRange&, CSSPropertyID id, CSSPropertyID currentShorthand, const CSSParserContext&);")
                else:
                    to.write(f"static RefPtr<CSSValue> parse{parsing_collection.id}(CSSParserTokenRange&, CSSPropertyID id, const CSSParserContext&);")
                to.write(f"// Fast path bare-keyword support.")
                to.write(f"static bool isKeywordValidFor{parsing_collection.id}(CSSPropertyID, CSSValueID, const CSSParserContext&);")
                to.write(f"static bool isKeywordFastPathEligible{parsing_collection.id}(CSSPropertyID);")
                to.newline()

            to.write(f"// Direct consumers.")

            for description, consumers in self.all_consumers_grouped_by_kind:
                if any(consumer.is_exported for consumer in consumers):
                    to.newline()
                    to.write(f"// Exported {description} consumers.")
                    for consumer in (consumer for consumer in consumers if consumer.is_exported):
                        consumer.generate_export_declaration(to=to)

        to.write(f"}};")
        to.newline()

    # MARK: - Helper generator functions for CSSPropertyParsing.cpp

    def _generate_css_property_parsing_cpp_is_keyword_valid_for_property(self, *, to, parsing_collection, keyword_fast_path_eligible_property_consumers):
        if not keyword_fast_path_eligible_property_consumers:
            to.write(f"bool CSSPropertyParsing::isKeywordValidFor{parsing_collection.id}(CSSPropertyID, CSSValueID, const CSSParserContext&)")
            to.write(f"{{")
            with to.indent():
                to.write(f"return false;")
            to.write(f"}}")
            to.newline()
            return

        requires_context = any(propopery_consumer.keyword_fast_path_generator.requires_context for propopery_consumer in keyword_fast_path_eligible_property_consumers)

        self.generation_context.generate_property_id_switch_function(
            to=to,
            signature=f"bool CSSPropertyParsing::isKeywordValidFor{parsing_collection.id}(CSSPropertyID id, CSSValueID keyword, const CSSParserContext&{' context' if requires_context else ''})",
            iterable=keyword_fast_path_eligible_property_consumers,
            mapping=lambda property_consumer: f"return {property_consumer.keyword_fast_path_generator.generate_call_string(keyword_string='keyword', context_string='context')};",
            default="return false;",
            mapping_to_property=lambda property_consumer: property_consumer.property
        )

    def _generate_css_property_parsing_cpp_is_keyword_fast_path_eligible_for_property(self, *, to, parsing_collection, keyword_fast_path_eligible_property_consumers):
        if not keyword_fast_path_eligible_property_consumers:
            to.write(f"bool CSSPropertyParsing::isKeywordFastPathEligible{parsing_collection.id}(CSSPropertyID)")
            to.write(f"{{")
            with to.indent():
                to.write(f"return false;")
            to.write(f"}}")
            to.newline()
            return

        self.generation_context.generate_property_id_switch_function_bool(
            to=to,
            signature=f"bool CSSPropertyParsing::isKeywordFastPathEligible{parsing_collection.id}(CSSPropertyID id)",
            iterable=keyword_fast_path_eligible_property_consumers,
            mapping_to_property=lambda property_consumer: property_consumer.property
        )

    def _generate_css_property_parsing_cpp_property_parsing_functions(self, *, to):
        # First generate definitions for all the keyword-only fast path predicate functions.
        for property_consumer in self.all_property_consumers:
            keyword_fast_path_generator = property_consumer.keyword_fast_path_generator
            if not keyword_fast_path_generator:
                continue
            keyword_fast_path_generator.generate_definition(to=to)

        # Then all the non-exported consume functions (these will be static functions).
        for property_consumer in self.all_property_consumers:
            if not property_consumer.property.codegen_properties.parser_exported:
                property_consumer.generate_definition(to=to)

        # Then all the exported consume functions (these will be static members of the CSSPropertyParsing struct).
        for property_consumer in self.all_property_consumers:
            if property_consumer.property.codegen_properties.parser_exported:
                property_consumer.generate_definition(to=to)

        # And finally all the exported shared grammar rule consumers (these will be static members of the CSSPropertyParsing struct).
        for shared_grammar_rule_consumer in self.all_shared_grammar_rule_consumers:
            shared_grammar_rule_consumer.generate_definition(to=to)

    def _generate_css_property_parsing_cpp_parse_property(self, *, to, parsing_collection):
        if parsing_collection.supports_current_shorthand:
            to.write(f"RefPtr<CSSValue> CSSPropertyParsing::parse{parsing_collection.id}(CSSParserTokenRange& range, CSSPropertyID id, CSSPropertyID currentShorthand, const CSSParserContext& context)")
            current_shorthand_string = "currentShorthand"
        else:
            to.write(f"RefPtr<CSSValue> CSSPropertyParsing::parse{parsing_collection.id}(CSSParserTokenRange& range, CSSPropertyID id, const CSSParserContext& context)")
            current_shorthand_string = None

        to.write(f"{{")
        with to.indent():
            to.write(f"if (!isExposed(id, context.propertySettings) && !isInternal(id)) {{")
            with to.indent():
                to.write(f"// Allow internal properties as we use them to parse several internal-only-shorthands (e.g. background-repeat),")
                to.write(f"// and to handle certain DOM-exposed values (e.g. -webkit-font-size-delta from execCommand('FontSizeDelta')).")
                to.write(f"ASSERT_NOT_REACHED();")
                to.write(f"return nullptr;")
            to.write(f"}}")

            # Build up a list of pairs of (property, return-expression-to-use-for-property).

            PropertyReturnExpression = collections.namedtuple('PropertyReturnExpression', ['property', 'return_expression'])
            property_and_return_expressions = []

            for consumer in parsing_collection.consumers:
                return_expression = consumer.generate_call_string(
                    range_string="range",
                    id_string="id",
                    current_shorthand_string=current_shorthand_string,
                    context_string="context")

                if return_expression is None:
                    continue

                property_and_return_expressions.append(
                    PropertyReturnExpression(consumer.property, return_expression))

            # Take the list of pairs of (value, return-expression-to-use-for-value), and
            # group them by their 'return-expression' to avoid unnecessary duplication of
            # return statements.

            PropertiesReturnExpression = collections.namedtuple('PropertiesReturnExpression', ['properties', 'return_expression'])

            property_and_return_expressions_sorted_by_expression = sorted(property_and_return_expressions, key=lambda x: x.return_expression)
            property_and_return_expressions_grouped_by_expression = []
            for return_expression, group in itertools.groupby(property_and_return_expressions_sorted_by_expression, lambda x: x.return_expression):
                properties = [property_and_return_expression.property for property_and_return_expression in group]
                property_and_return_expressions_grouped_by_expression.append(PropertiesReturnExpression(properties, return_expression))

            def _sort_by_first_property(a, b):
                return StyleProperties._sort_by_descending_priority_and_name(a.properties[0], b.properties[0])

            to.write(f"switch (id) {{")
            for properties, return_expression in sorted(property_and_return_expressions_grouped_by_expression, key=functools.cmp_to_key(_sort_by_first_property)):
                for property in properties:
                    to.write(f"case {property.id}:")

                with to.indent():
                    to.write(f"return {return_expression};")

            to.write(f"default:")
            with to.indent():
                to.write(f"return nullptr;")
            to.write(f"}}")
        to.write(f"}}")
        to.newline()


# Helper class for representing a function parameter.
class FunctionParameter:
    def __init__(self, type, name):
        self.type = type
        self.name = name

    @property
    def declaration_string(self):
        return f"{self.type}"

    @property
    def definition_string(self):
        return f"{self.type} {self.name}"


# Helper class for representing a function signature.
class FunctionSignature:
    def __init__(self, *, result_type, scope, name, parameters):
        self.result_type = result_type
        self.scope = scope
        self.name = name
        self.parameters = parameters

    @property
    def _declaration_parameters_string(self):
        return ", ".join(parameter.declaration_string for parameter in self.parameters)

    @property
    def _definition_parameters_string(self):
        return ", ".join(parameter.definition_string for parameter in self.parameters)

    @property
    def _scope_string(self):
        return f"{self.scope}::" if self.scope else ""

    @property
    def declaration_string(self):
        return f"{self.result_type} {self.name}({self._declaration_parameters_string})"

    @property
    def definition_string(self):
        return f"{self.result_type} {self._scope_string}{self.name}({self._definition_parameters_string})"

    @property
    def reference_string(self):
        return f"{self._scope_string}{self.name}"

    def generate_call_string(self, parameters):
        return f"{self._scope_string}{self.name}({', '.join(parameters)})"


# The `TermGenerator` classes help generate parser functions by providing
# generation of parsing text for a term or set of terms.
class TermGenerator(object):
    def make(term, keyword_fast_path_generator=None):
        if isinstance(term, MatchOneTerm):
            return TermGeneratorMatchOneTerm(term, keyword_fast_path_generator)
        elif isinstance(term, OptionalTerm):
            return TermGeneratorOptionalTerm(term)
        elif isinstance(term, GroupTerm):
            return TermGeneratorGroupTerm(term)
        elif isinstance(term, UnboundedRepetitionTerm):
            return TermGeneratorUnboundedRepetitionTerm(term)
        elif isinstance(term, ReferenceTerm):
            return TermGeneratorReferenceTerm(term)
        elif isinstance(term, FunctionTerm):
            return TermGeneratorFunctionTerm(term)
        elif isinstance(term, LiteralTerm):
            return TermGeneratorLiteralTerm(term)
        elif isinstance(term, KeywordTerm):
            return TermGeneratorNonFastPathKeywordTerm([term])
        else:
            raise Exception(f"Unknown term type - {type(term)} - {term}")


class TermGeneratorGroupTerm(TermGenerator):
    def __init__(self, term):
        self.term = term
        self.subterm_generators = [TermGenerator.make(subterm) for subterm in term.subterms]
        self.requires_context = any(subterm_generator.requires_context for subterm_generator in self.subterm_generators)

    def generate_conditional(self, *, to, range_string, context_string):
        # FIXME: Implement generation.
        pass

    def generate_unconditional(self, *, to, range_string, context_string):
        # FIXME: Implement generation.
        pass


class TermGeneratorOptionalTerm(TermGenerator):
    def __init__(self, optional_term):
        self.term = optional_term
        self.subterm_generator = TermGenerator.make(optional_term.subterm, None)
        self.requires_context = self.subterm_generator.requires_context

    def generate_conditional(self, *, to, range_string, context_string):
        # FIXME: Implement generation.
        pass

    def generate_unconditional(self, *, to, range_string, context_string):
        # FIXME: Implement generation.
        pass


class TermGeneratorFunctionTerm(TermGenerator):
    def __init__(self, term):
        self.term = term
        self.parameter_group_generator = TermGenerator.make(term.parameter_group_term)
        self.requires_context = self.parameter_group_generator.requires_context

    def generate_conditional(self, *, to, range_string, context_string):
        # FIXME: Implement generation.
        pass

    def generate_unconditional(self, *, to, range_string, context_string):
        # FIXME: Implement generation.
        pass


class TermGeneratorLiteralTerm(TermGenerator):
    def __init__(self, term):
        self.term = term
        self.requires_context = self.term.requires_context

    def generate_conditional(self, *, to, range_string, context_string):
        # FIXME: Implement generation.
        pass

    def generate_unconditional(self, *, to, range_string, context_string):
        # FIXME: Implement generation.
        pass


class TermGeneratorUnboundedRepetitionTerm(TermGenerator):
    def __init__(self, term):
        self.term = term
        self.repeated_term_generator = TermGenerator.make(term.repeated_term, None)
        self.requires_context = self.repeated_term_generator.requires_context

    def generate_conditional(self, *, to, range_string, context_string):
        self._generate_lambda(to=to, range_string=range_string, context_string=context_string)
        to.write(f"if (auto result = {self._generate_call_string(range_string=range_string, context_string=context_string)})")
        with to.indent():
            to.write(f"return result;")

    def generate_unconditional(self, *, to, range_string, context_string):
        self._generate_lambda(to=to, range_string=range_string, context_string=context_string)
        to.write(f"return {self._generate_call_string(range_string=range_string, context_string=context_string)};")

    def _generate_lambda(self, *, to, range_string, context_string):
        lambda_declaration_paramaters = ["CSSParserTokenRange& range"]
        if self.repeated_term_generator.requires_context:
            lambda_declaration_paramaters += ["const CSSParserContext& context"]

        to.write(f"auto lambda = []({', '.join(lambda_declaration_paramaters)}) -> RefPtr<CSSValue> {{")
        with to.indent():
            self.repeated_term_generator.generate_unconditional(to=to, range_string="range", context_string="context")
        to.write(f"}};")

    def _generate_call_string(self, *, range_string, context_string):
        parameters = [range_string, "lambda"]
        if self.repeated_term_generator.requires_context:
            parameters += [context_string]

        with_or_without = 'With' if self.term.single_value_optimization else 'Without'
        return f"consumeCommaSeparatedList{with_or_without}SingleValueOptimization({', '.join(parameters)})"


class TermGeneratorMatchOneTerm(TermGenerator):
    def __init__(self, term, keyword_fast_path_generator=None):
        self.term = term
        self.keyword_fast_path_generator = keyword_fast_path_generator
        self.term_generators = TermGeneratorMatchOneTerm._build_term_generators(term, keyword_fast_path_generator)
        self.requires_context = any(term_generator.requires_context for term_generator in self.term_generators)

    @staticmethod
    def _build_term_generators(term, keyword_fast_path_generator):
        # Partition the sub-terms into keywords and references (and eventually more things):
        fast_path_keyword_terms = []
        non_fast_path_keyword_terms = []
        reference_terms = []
        repetition_terms = []
        group_terms = []
        optional_terms = []

        for sub_term in term.terms:
            if isinstance(sub_term, KeywordTerm):
                if keyword_fast_path_generator and sub_term.is_eligible_for_fast_path:
                    fast_path_keyword_terms.append(sub_term)
                else:
                    non_fast_path_keyword_terms.append(sub_term)
            elif isinstance(sub_term, ReferenceTerm):
                reference_terms.append(sub_term)
            elif isinstance(sub_term, UnboundedRepetitionTerm):
                repetition_terms.append(sub_term)
            elif isinstance(sub_term, BoundedRepetitionTerm):
                repetition_terms.append(sub_term)
            elif isinstance(sub_term, FixedSizeRepetitionTerm):
                repetition_terms.append(sub_term)
            elif isinstance(sub_term, GroupTerm):
                group_terms.append(sub_term)
            elif isinstance(sub_term, OptionalTerm):
                optional_terms.append(sub_term)
            else:
                raise Exception(f"Only KeywordTerm, ReferenceTerm and UnboundedRepetitionTerm terms are supported inside MatchOneTerm at this time: '{term}' - {sub_term}")

        # Build a list of generators for the terms, starting with all (if any) the keywords at once.
        term_generators = []

        if fast_path_keyword_terms:
            term_generators += [TermGeneratorFastPathKeywordTerms(keyword_fast_path_generator)]
        if non_fast_path_keyword_terms:
            term_generators += [TermGeneratorNonFastPathKeywordTerm(non_fast_path_keyword_terms)]
        if reference_terms:
            term_generators += [TermGeneratorReferenceTerm(sub_term) for sub_term in reference_terms]
        if repetition_terms:
            term_generators += [TermGeneratorUnboundedRepetitionTerm(sub_term) for sub_term in repetition_terms]
        if group_terms:
            term_generators += [TermGeneratorGroupTerm(sub_term) for sub_term in group_terms]
        if repetition_terms:
            term_generators += [TermGeneratorOptionalTerm(sub_term) for sub_term in optional_terms]
        return term_generators

    def generate_conditional(self, *, to, range_string, context_string):
        # For any remaining generators, call the consume function and return the result if non-null.
        for term_generator in self.term_generators:
            term_generator.generate_conditional(to=to, range_string=range_string, context_string=context_string)

    def generate_unconditional(self, *, to, range_string, context_string):
        # Pop the last generator off, as that one will be the special, non-if case.
        *remaining_term_generators, last_term_generator = self.term_generators

        # For any remaining generators, call the consume function and return the result if non-null.
        for term_generator in remaining_term_generators:
            term_generator.generate_conditional(to=to, range_string=range_string, context_string=context_string)

        # And finally call that last generator we popped of back.
        last_term_generator.generate_unconditional(to=to, range_string=range_string, context_string=context_string)


# Generation support for a single `ReferenceTerm`.
class TermGeneratorReferenceTerm(TermGenerator):
    def __init__(self, term):
        self.term = term

    def generate_conditional(self, *, to, range_string, context_string):
        to.write(f"if (auto result = {self.generate_call_string(range_string=range_string, context_string=context_string)})")
        with to.indent():
            to.write(f"return result;")

    def generate_unconditional(self, *, to, range_string, context_string):
        to.write(f"return {self.generate_call_string(range_string=range_string, context_string=context_string)};")

    def generate_call_string(self, *, range_string, context_string):
        if self.term.is_builtin:
            builtin = self.term.builtin
            if isinstance(builtin, BuiltinAngleConsumer):
                return f"{builtin.consume_function_name}({range_string}, {context_string}, {builtin.unitless}, {builtin.unitless_zero})"
            elif isinstance(builtin, BuiltinTimeConsumer):
                return f"{builtin.consume_function_name}({range_string}, {context_string}, {builtin.value_range})"
            elif isinstance(builtin, BuiltinLengthConsumer):
                if builtin.mode:
                    return f"{builtin.consume_function_name}({range_string}, {context_string}, {builtin.mode}, {builtin.value_range}, {builtin.unitless})"
                return f"{builtin.consume_function_name}({range_string}, {context_string}, {builtin.value_range}, {builtin.unitless})"
            elif isinstance(builtin, BuiltinLengthPercentageConsumer):
                return f"{builtin.consume_function_name}({range_string}, {context_string}, {builtin.value_range}, {builtin.unitless}, {builtin.unitless_zero}, {builtin.anchor}, {builtin.anchor_size})"
            elif isinstance(builtin, BuiltinIntegerConsumer):
                return f"{builtin.consume_function_name}({range_string}, {context_string}, {builtin.value_range})"
            elif isinstance(builtin, BuiltinNumberConsumer):
                return f"{builtin.consume_function_name}({range_string}, {context_string}, {builtin.value_range})"
            elif isinstance(builtin, BuiltinPercentageConsumer):
                return f"{builtin.consume_function_name}({range_string}, {context_string}, {builtin.value_range})"
            elif isinstance(builtin, BuiltinPositionConsumer):
                return f"{builtin.consume_function_name}({range_string}, {context_string}, {builtin.unitless}, PositionSyntax::Position)"
            elif isinstance(builtin, BuiltinColorConsumer):
                if builtin.quirky_colors:
                    return f"{builtin.consume_function_name}({range_string}, {context_string}, {{ .acceptQuirkyColors = ({context_string}.mode == HTMLQuirksMode) }})"
                return f"{builtin.consume_function_name}({range_string}, {context_string})"
            elif self.requires_context:
                return f"{builtin.consume_function_name}({range_string}, {context_string})"
            else:
                return f"{builtin.consume_function_name}({range_string})"
        else:
            return f"consume{self.term.name.id_without_prefix}({range_string}, {context_string})"

    @property
    def requires_context(self):
        if self.term.is_builtin:
            builtin = self.term.builtin
            if isinstance(builtin, BuiltinAngleConsumer):
                return True
            elif isinstance(builtin, BuiltinTimeConsumer):
                return True
            elif isinstance(builtin, BuiltinLengthConsumer):
                return True
            elif isinstance(builtin, BuiltinLengthPercentageConsumer):
                return True
            elif isinstance(builtin, BuiltinIntegerConsumer):
                return True
            elif isinstance(builtin, BuiltinNumberConsumer):
                return True
            elif isinstance(builtin, BuiltinPercentageConsumer):
                return True
            elif isinstance(builtin, BuiltinPositionConsumer):
                return True
            elif isinstance(builtin, BuiltinColorConsumer):
                return True
            elif isinstance(builtin, BuiltinResolutionConsumer):
                return True
            elif isinstance(builtin, BuiltinStringConsumer):
                return False
            elif isinstance(builtin, BuiltinCustomIdentConsumer):
                return False
            elif isinstance(builtin, BuiltinDashedIdentConsumer):
                return False
            elif isinstance(builtin, BuiltinURLConsumer):
                return False
            elif isinstance(builtin, BuiltinFeatureTagValueConsumer):
                return True
            elif isinstance(builtin, BuiltinVariationTagValueConsumer):
                return True
            else:
                raise Exception(f"Unknown builtin type used: {builtin.name.name}")
        else:
            return True


# Generation support for any keyword terms that are not fast-path eligible.
class TermGeneratorNonFastPathKeywordTerm(TermGenerator):
    def __init__(self, keyword_terms):
        self.keyword_terms = keyword_terms
        self.requires_context = any(keyword_term.requires_context for keyword_term in self.keyword_terms)

    def generate_conditional(self, *, to, range_string, context_string):
        self._generate(to=to, range_string=range_string, context_string=context_string, default_string="break")

    def generate_unconditional(self, *, to, range_string, context_string):
        self._generate(to=to, range_string=range_string, context_string=context_string, default_string="return nullptr")

    def _generate(self, *, to, range_string, context_string, default_string):
        # Build up a list of pairs of (value, return-expression-to-use-for-value), taking
        # into account settings flags and mode checks for internal values. Leave the return
        # expression as an empty array for the default return expression "return true;".

        ReturnExpression = collections.namedtuple('ReturnExpression', ['conditions', 'return_value'])
        KeywordTermAndReturnExpression = collections.namedtuple('KeywordTermAndReturnExpression', ['keyword_term', 'return_expression'])
        keyword_term_and_return_expressions = []

        for keyword_term in self.keyword_terms:
            conditions = []
            if keyword_term.settings_flag:
                if keyword_term.settings_flag.startswith("DeprecatedGlobalSettings::"):
                    conditions.append(f"!{keyword_term.settings_flag}")
                else:
                    conditions.append(f"!{context_string}.{keyword_term.settings_flag}")
            if keyword_term.status == "internal":
                conditions.append(f"!isUASheetBehavior({context_string}.mode)")

            if keyword_term.aliased_to:
                return_value = keyword_term.aliased_to.id
            else:
                return_value = "keyword"

            keyword_term_and_return_expressions.append(KeywordTermAndReturnExpression(keyword_term, ReturnExpression(conditions, return_value)))

        # Take the list of pairs of (value, return-expression-to-use-for-value), and
        # group them by their 'return-expression' to avoid unnecessary duplication of
        # return statements.
        to.write(f"switch (auto keyword = {range_string}.peek().id(); keyword) {{")
        for return_expression, group in itertools.groupby(sorted(keyword_term_and_return_expressions, key=lambda x: x.return_expression), lambda x: x.return_expression):
            for keyword_term, _ in group:
                to.write(f"case {keyword_term.value.id}:")

            with to.indent():
                if return_expression.conditions:
                    to.write(f"if ({' || '.join(return_expression.conditions)})")
                    with to.indent():
                        to.write(f"{default_string};")

                to.write(f"{range_string}.consumeIncludingWhitespace();")
                to.write(f"return CSSPrimitiveValue::create({return_expression.return_value});")

        to.write(f"default:")
        with to.indent():
            to.write(f"{default_string};")

        to.write(f"}}")


# Generation support for a properties fast path eligable keyword terms.
class TermGeneratorFastPathKeywordTerms(TermGenerator):
    def __init__(self, keyword_fast_path_generator):
        self.keyword_fast_path_generator = keyword_fast_path_generator
        self.requires_context = keyword_fast_path_generator.requires_context

    def generate_conditional(self, *, to, range_string, context_string):
        to.write(f"if (auto result = {self.generate_call_string(range_string=range_string, context_string=context_string)})")
        with to.indent():
            to.write(f"return result;")

    def generate_unconditional(self, *, to, range_string, context_string):
        to.write(f"return {self.generate_call_string(range_string=range_string, context_string=context_string)};")

    def generate_call_string(self, *, range_string, context_string):
        # For root keyword terms we can utilize the `keyword-only fast path` function.
        parameters = [range_string, self.keyword_fast_path_generator.generate_reference_string()]
        if self.requires_context:
            parameters.append(context_string)
        return f"consumeIdent({', '.join(parameters)})"


# Used by the `PropertyConsumer` classes to generate a `keyword-only fast path` function
# (e.g. `isKeywordValidFor*`) for use both in the keyword only fast path and in the main
# `parse` function.
class KeywordFastPathGenerator:
    def __init__(self, name, keyword_terms):
        self.keyword_terms = keyword_terms
        self.requires_context = any(keyword_term.requires_context for keyword_term in keyword_terms)
        self.signature = KeywordFastPathGenerator._build_signature(name, self.requires_context)

    @staticmethod
    def _build_parameters(requires_context):
        parameters = [FunctionParameter("CSSValueID", "keyword")]
        if requires_context:
            parameters += [FunctionParameter("const CSSParserContext&", "context")]
        return parameters

    @staticmethod
    def _build_signature(name, requires_context):
        return FunctionSignature(
            result_type="bool",
            scope=None,
            name=name,
            parameters=KeywordFastPathGenerator._build_parameters(requires_context))

    def generate_reference_string(self):
        return self.signature.reference_string

    def generate_call_string(self, *, keyword_string, context_string):
        parameters = [keyword_string]
        if self.requires_context:
            parameters += [context_string]

        return self.signature.generate_call_string(parameters)

    def generate_definition(self, *, to):
        to.write(f"static {self.signature.definition_string}")
        to.write(f"{{")

        with to.indent():
            # Build up a list of pairs of (value, return-expression-to-use-for-value), taking
            # into account settings flags and mode checks for internal values. Leave the return
            # expression as an empty array for the default return expression "return true;".

            KeywordTermReturnExpression = collections.namedtuple('KeywordTermReturnExpression', ['keyword_term', 'return_expression'])
            keyword_term_and_return_expressions = []

            for keyword_term in self.keyword_terms:
                return_expression = []
                if keyword_term.settings_flag:
                    if keyword_term.settings_flag.startswith("DeprecatedGlobalSettings::"):
                        return_expression.append(keyword_term.settings_flag)
                    else:
                        return_expression.append(f"context.{keyword_term.settings_flag}")
                if keyword_term.status == "internal":
                    return_expression.append("isUASheetBehavior(context.mode)")

                keyword_term_and_return_expressions.append(KeywordTermReturnExpression(keyword_term, return_expression))

            # Take the list of pairs of (value, return-expression-to-use-for-value), and
            # group them by their 'return-expression' to avoid unnecessary duplication of
            # return statements.
            to.write(f"switch (keyword) {{")
            for return_expression, group in itertools.groupby(sorted(keyword_term_and_return_expressions, key=lambda x: x.return_expression), lambda x: x.return_expression):
                for keyword_term, _ in group:
                    to.write(f"case {keyword_term.value.id}:")
                with to.indent():
                    to.write(f"return {' && '.join(return_expression or ['true'])};")

            to.write(f"default:")
            with to.indent():
                to.write(f"return false;")

            to.write(f"}}")
        to.write(f"}}")
        to.newline()


# Each shared grammar rule has a corresponding `SharedGrammarRuleConsumer` which defines how
# that rules parsing is exposed and if the parsing function for the rule should be exported in
# the header for use in other areas of WebCore. Currently, all non-exported rules are 'skipped'
# here. Note, that does not mean the rule isn't used, as a reference of that rule by a property
# or another shared rule will still use the grammar, it will just be simplified into the parents
# grammar with no explicit function being emitted. That leaves only exported rules actually
# having functions emitted. The current set of kinds of `SharedGrammarRuleConsumer` are:
#
#   - `SkipSharedGrammarRuleConsumer`:
#        Used when the shared property rule is not needed by other parts of WebCore, and therefore
#        no explicit function needs to be emitted. Used for any shared rule that is not marked
#        as 'exported`.
#
#   - `GeneratedSharedGrammarRuleConsumer`:
#        Used for all exported rules. These generate a dedicated `consume` function which is exported
#        in `CSSPropertyParser` for use by other parts of WebCore.
#
# `SharedGrammarRuleConsumer` abstract interface:
#
#   def generate_export_declaration(self, *, to):
#   def generate_definition(self, *, to):
#   var is_exported
#
class SharedGrammarRuleConsumer(object):
    @staticmethod
    def make(shared_grammar_rule):
        if not shared_grammar_rule.exported:
            return SkipSharedGrammarRuleConsumer(shared_grammar_rule)
        return GeneratedSharedGrammarRuleConsumer(shared_grammar_rule)


class SkipSharedGrammarRuleConsumer(SharedGrammarRuleConsumer):
    def __init__(self, shared_grammar_rule):
        self.shared_grammar_rule = shared_grammar_rule

    def __str__(self):
        return "SkipSharedGrammarRuleConsumer"

    def __repr__(self):
        return self.__str__()

    @property
    def is_exported(self):
        return False

    def generate_export_declaration(self, *, to):
        pass

    def generate_definition(self, *, to):
        pass


class GeneratedSharedGrammarRuleConsumer(SharedGrammarRuleConsumer):
    def __init__(self, shared_grammar_rule):
        self.term_generator = TermGenerator.make(shared_grammar_rule.grammar.root_term)
        self.requires_context = self.term_generator.requires_context
        self.signature = GeneratedSharedGrammarRuleConsumer._build_signature(shared_grammar_rule, self.requires_context)

    def __str__(self):
        return "GeneratedSharedGrammarRuleConsumer"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def _build_parameters(requires_context):
        parameters = [FunctionParameter("CSSParserTokenRange&", "range")]
        if requires_context:
            parameters += [FunctionParameter("const CSSParserContext&", "context")]
        return parameters

    @staticmethod
    def _build_signature(shared_grammar_rule, requires_context):
        return FunctionSignature(
            result_type="RefPtr<CSSValue>",
            scope="CSSPropertyParsing",
            name=f"consume{shared_grammar_rule.name_for_methods.id_without_prefix}",
            parameters=GeneratedSharedGrammarRuleConsumer._build_parameters(requires_context))

    @property
    def is_exported(self):
        return True

    def generate_export_declaration(self, *, to):
        to.write(f"static {self.signature.declaration_string};")

    def generate_definition(self, *, to):
        to.write(f"{self.signature.definition_string}")
        to.write(f"{{")
        with to.indent():
            self.term_generator.generate_unconditional(to=to, range_string='range', context_string='context')
        to.write(f"}}")
        to.newline()


# Each CSS property has a corresponding `PropertyConsumer` which defines how that property's
# parsing works, if the parsing function for the property should be exported in the header for
# use in other areas of WebCore, and what fast paths it exposes. The current set of kinds of
# `PropertyConsumer` are:
#
#   - `SkipPropertyConsumer`:
#        Used when the property is not eligable for parsing, and should be skipped. Used for
#        descriptor-only properties, shorthand properties, and properties marked 'skip-parser`.
#
#   - `CustomPropertyConsumer`:
#        Used when the property has been marked with `parser-function`. These property consumers never
#        generate a `consume` function of their own, and call the defined `consume` function declared
#        in `parser-function` directly from the main `parse` function.
#
#   - `FastPathKeywordOnlyPropertyConsumer`:
#        The only allowed values for this property are fast path eligible keyword values. These property
#        consumers always emit a `keyword-only fast path` function (e.g. `isKeywordValidFor*`) and the
#        main `parse` function uses that fast path function directly (e.g. `consumeIdent(range, isKeywordValidFor*)`
#        This allows us to avoid making a `consume` function for the property in all cases except for
#        when the property has been marked explicity with `parser-exported`, in which case we do
#        generate a `consume` function to warp that invocation above.
#
#   - `DirectPropertyConsumer`:
#        Used when a property's only term is a single non-simplifiable reference term (e.g. [ <number> ]
#        or [ <color> ]. These property consumers call the referenced term directly from the main `parse`
#        function. This allows us to avoid making a `consume` function for the property in all cases
#        except for when the property has been marked explicity with `parser-exported`, in which case
#        we do generate a `consume` function to warp that invocation above.
#
#   - `GeneratedPropertyConsumer`:
#        Used for all other properties. Requires that `parser-grammar` has been defined. These property
#        consumers use the provided parser grammer to generate a dedicated `consume` function which is
#        called from the main `parse` function. If the parser grammar allows for any keyword only valid
#        parses (e.g. for the grammar [ none | <image> ], "none" is a valid keyword only parse), these
#        property consumers will also emit a `keyword-only fast path` function (e.g. `isKeywordValidFor*`)
#        and ensure that it is called from the main `isKeywordValidForStyleProperty` function.
#
# `PropertyConsumer` abstract interface:
#
#   def generate_call_string(self, *, range_string, id_string, current_shorthand_string, context_string):
#   def generate_export_declaration(self, *, to):
#   def generate_definition(self, *, to):
#   var is_exported
#   var keyword_fast_path_generator

class PropertyConsumer(object):
    @staticmethod
    def make(property):
        if property.codegen_properties.longhands or property.codegen_properties.skip_parser:
            return SkipPropertyConsumer(property)

        if property.codegen_properties.parser_function:
            return CustomPropertyConsumer(property)

        if property.codegen_properties.parser_grammar:
            if property.codegen_properties.parser_grammar.has_only_fast_path_keyword_terms:
                return FastPathKeywordOnlyPropertyConsumer(property)
            if isinstance(property.codegen_properties.parser_grammar.root_term, ReferenceTerm):
                return DirectPropertyConsumer(property)
            return GeneratedPropertyConsumer(property)

        raise Exception(f"Invalid property definition for '{property.id}'. Style properties must either specify values or a custom parser.")


# Property consumer used for properties that should not be parsed.
class SkipPropertyConsumer(PropertyConsumer):
    def __init__(self, property):
        self.property = property

    def __str__(self):
        return f"SkipPropertyConsumer for {self.property}"

    def __repr__(self):
        return self.__str__()

    def generate_call_string(self, *, range_string, id_string, current_shorthand_string, context_string):
        return None

    def generate_export_declaration(self, *, to):
        pass

    def generate_definition(self, *, to):
        pass

    @property
    def is_exported(self):
        return False

    @property
    def keyword_fast_path_generator(self):
        return None


# Property consumer used for properties with `parser-function` defined.
class CustomPropertyConsumer(PropertyConsumer):
    def __init__(self, property):
        self.property = property

    def __str__(self):
        return f"CustomPropertyConsumer for {self.property}"

    def __repr__(self):
        return self.__str__()

    def generate_call_string(self, *, range_string, id_string, current_shorthand_string, context_string):
        parameters = [range_string, context_string]
        if self.property.codegen_properties.parser_function_requires_current_property:
            parameters.append(id_string)
        if self.property.codegen_properties.parser_function_requires_current_shorthand:
            parameters.append(current_shorthand_string)
        if self.property.codegen_properties.parser_function_requires_value_pool:
            parameters.append("CSSValuePool::singleton()")
        parameters += self.property.codegen_properties.parser_function_requires_additional_parameters

        function = self.property.codegen_properties.parser_function

        # Merge the scope, function and parameters to form the final invocation.
        return f"{function}({', '.join(parameters)})"

    def generate_export_declaration(self, *, to):
        pass

    def generate_definition(self, *, to):
        pass

    @property
    def is_exported(self):
        return False

    @property
    def keyword_fast_path_generator(self):
        return None


# Property consumer used for properties with only fast-path eligable keyword terms in its grammar.
class FastPathKeywordOnlyPropertyConsumer(PropertyConsumer):
    def __init__(self, property):
        self.property = property
        self.keyword_fast_path_generator = KeywordFastPathGenerator(f"isKeywordValidFor{property.name_for_parsing_methods}", property.codegen_properties.parser_grammar.fast_path_keyword_terms_sorted_by_name)
        self.term_generator = TermGeneratorFastPathKeywordTerms(self.keyword_fast_path_generator)
        self.signature = FastPathKeywordOnlyPropertyConsumer._build_signature(property, self.keyword_fast_path_generator)

    def __str__(self):
        return f"FastPathKeywordOnlyPropertyConsumer for {self.property}"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def _build_scope(property):
        if property.codegen_properties.parser_exported:
            return "CSSPropertyParsing"
        return None

    @staticmethod
    def _build_parameters(keyword_fast_path_generator):
        parameters = [FunctionParameter("CSSParserTokenRange&", "range")]
        if keyword_fast_path_generator.requires_context:
            parameters += [FunctionParameter("const CSSParserContext&", "context")]
        return parameters

    @staticmethod
    def _build_signature(property, keyword_fast_path_generator):
        return FunctionSignature(
            result_type="RefPtr<CSSValue>",
            scope=FastPathKeywordOnlyPropertyConsumer._build_scope(property),
            name=f"consume{property.name_for_parsing_methods}",
            parameters=FastPathKeywordOnlyPropertyConsumer._build_parameters(keyword_fast_path_generator))

    def generate_call_string(self, *, range_string, id_string=None, current_shorthand_string=None, context_string):
        # NOTE: Even in the case that we generate a `consume` function, we don't generate a call to it,
        # but rather always directly use `consumeIdent` with the keyword-only fast path predicate.
        return self.term_generator.generate_call_string(range_string=range_string, context_string=context_string)

    # For "direct" and "fast-path keyword only" consumers, we only generate the property specific
    # defintion if the property has been marked as exported.

    @property
    def is_exported(self):
        return self.property.codegen_properties.parser_exported

    def generate_export_declaration(self, *, to):
        if self.is_exported:
            to.write(f"static {self.signature.declaration_string};")

    def generate_definition(self, *, to):
        if self.is_exported:
            to.write(f"{self.signature.definition_string}")
            to.write(f"{{")
            with to.indent():
                to.write(f"return {self.generate_call_string(range_string='range', context_string='context')};")
            to.write(f"}}")
            to.newline()


# Property consumer for a property that has a `parser-grammar` that consists of only a single non-simplifiable
# reference term (e.g. [ <number> ] or [ <color> ])
class DirectPropertyConsumer(PropertyConsumer):
    def __init__(self, property):
        self.property = property
        self.term_generator = TermGeneratorReferenceTerm(self.property.codegen_properties.parser_grammar.root_term)
        self.signature = DirectPropertyConsumer._build_signature(self.property, self.term_generator)

    def __str__(self):
        return f"DirectPropertyConsumer for {self.property}"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def _build_scope(property):
        if property.codegen_properties.parser_exported:
            return "CSSPropertyParsing"
        return None

    @staticmethod
    def _build_parameters(term_generator):
        parameters = [FunctionParameter("CSSParserTokenRange&", "range")]
        if term_generator.requires_context:
            parameters += [FunctionParameter("const CSSParserContext&", "context")]
        return parameters

    @staticmethod
    def _build_signature(property, term_generator):
        return FunctionSignature(
            result_type="RefPtr<CSSValue>",
            scope=DirectPropertyConsumer._build_scope(property),
            name=f"consume{property.name_for_parsing_methods}",
            parameters=DirectPropertyConsumer._build_parameters(term_generator))

    def generate_call_string(self, *, range_string, id_string=None, current_shorthand_string=None, context_string):
        # NOTE: Even in the case that we generate a `consume` function for the case, we don't
        # generate a call to it, but rather always generate the consume function for the reference,
        # which is just as good and works in all cases.
        return self.term_generator.generate_call_string(range_string=range_string, context_string=context_string)

    # For "direct" and "fast-path keyword only" consumers, we only generate the property specific
    # defintion if the property has been marked as exported.

    @property
    def is_exported(self):
        return self.property.codegen_properties.parser_exported

    def generate_export_declaration(self, *, to):
        if self.is_exported:
            to.write(f"static {self.signature.declaration_string};")

    def generate_definition(self, *, to):
        if self.is_exported:
            to.write(f"{self.signature.definition_string}")
            to.write(f"{{")
            with to.indent():
                self.term_generator.generate_unconditional(to=to, range_string='range', context_string='context')
            to.write(f"}}")
            to.newline()

    @property
    def keyword_fast_path_generator(self):
        return None


# Default property consumer. Uses `parser-grammar` to generate a `consume` function for the property.
class GeneratedPropertyConsumer(PropertyConsumer):
    def __init__(self, property):
        self.property = property
        self.keyword_fast_path_generator = GeneratedPropertyConsumer._build_keyword_fast_path_generator(property)
        self.term_generator = TermGenerator.make(property.codegen_properties.parser_grammar.root_term, self.keyword_fast_path_generator)
        self.requires_context = self.term_generator.requires_context
        self.signature = GeneratedPropertyConsumer._build_signature(property, self.requires_context)

    def __str__(self):
        return f"GeneratedPropertyConsumer for {self.property}"

    def __repr__(self):
        return self.__str__()

    @staticmethod
    def _build_scope(property):
        if property.codegen_properties.parser_exported:
            return "CSSPropertyParsing"
        return None

    @staticmethod
    def _build_parameters(property, requires_context):
        parameters = [FunctionParameter("CSSParserTokenRange&", "range")]
        if requires_context:
            parameters += [FunctionParameter("const CSSParserContext&", "context")]
        return parameters

    @staticmethod
    def _build_signature(property, requires_context):
        return FunctionSignature(
            result_type="RefPtr<CSSValue>",
            scope=GeneratedPropertyConsumer._build_scope(property),
            name=f"consume{property.name_for_parsing_methods}",
            parameters=GeneratedPropertyConsumer._build_parameters(property, requires_context))

    @staticmethod
    def _build_keyword_fast_path_generator(property):
        if property.codegen_properties.parser_grammar.has_fast_path_keyword_terms:
            return KeywordFastPathGenerator(f"isKeywordValidFor{property.name_for_parsing_methods}", property.codegen_properties.parser_grammar.fast_path_keyword_terms_sorted_by_name)
        return None

    def generate_call_string(self, *, range_string, id_string, current_shorthand_string, context_string):
        parameters = [range_string]
        if self.requires_context:
            parameters += [context_string]
        return self.signature.generate_call_string(parameters)

    @property
    def is_exported(self):
        return self.property.codegen_properties.parser_exported

    def generate_export_declaration(self, *, to):
        if self.is_exported:
            to.write(f"static {self.signature.declaration_string};")

    def generate_definition(self, *, to):
        if self.is_exported:
            to.write(f"{self.signature.definition_string}")
        else:
            to.write(f"static {self.signature.definition_string}")
        to.write(f"{{")
        with to.indent():
            self.term_generator.generate_unconditional(to=to, range_string='range', context_string='context')
        to.write(f"}}")
        to.newline()


class StringEqualingEnum(enum.Enum):
    def __eq__(self, b):
        if isinstance(b, str):
            return self.name == b
        else:
            return self.name == b.name

    def __hash__(self):
        return id(self.name)


class BNFToken(StringEqualingEnum):
    # Numbers.
    FLOAT   = re.compile(r'\d+\.\d+')
    INT     = re.compile(r'\d+')

    # Brackets.
    LPAREN  = re.compile(r'\(')
    RPAREN  = re.compile(r'\)')
    LBRACE  = re.compile(r'\{')
    RBRACE  = re.compile(r'\}')
    LSQUARE = re.compile(r'\[')
    RSQUARE = re.compile(r'\]')
    LTLT    = re.compile(r'<<')
    GTGT    = re.compile(r'>>')
    LT      = re.compile(r'<')
    GT      = re.compile(r'>')
    SQUOTE  = re.compile(r'\'')
    ATPAREN = re.compile(r'@\(')

    # Multipliers.
    HASH    = re.compile(r'#')
    PLUS    = re.compile(r'\+')
    STAR    = re.compile(r'\*')
    NOT     = re.compile(r'!')
    QMARK   = re.compile(r'\?')

    # Combinators.
    OROR    = re.compile(r'\|\|')
    OR      = re.compile(r'\|')
    ANDAND  = re.compile(r'&&')
    COMMA   = re.compile(r',')

    # Literals
    SLASH   = re.compile(r'/')

    # Identifiers.
    FUNC    = re.compile(r'[_a-zA-Z\-][_a-zA-Z0-9\-]*\(')
    ID      = re.compile(r'[_a-zA-Z\-][_a-zA-Z0-9\-]*')

    # Whitespace.
    WHITESPACE = re.compile(r'(\t|\n|\s|\r)+')


BNF_ILLEGAL_TOKEN = 'ILLEGAL'
BNF_EOF_TOKEN     = 'EOF'

BNFTokenInfo = collections.namedtuple("BNFTokens", ["name", "value"])


def BNFLexer(data):
    position = 0
    while position < len(data):
        for token_id in BNFToken:
            match = token_id.value.match(data, position)
            if match:
                position = match.end(0)
                if token_id == BNFToken.WHITESPACE:
                    # ignore whitespace
                    break
                yield BNFTokenInfo(token_id.name, match.group(0))
                break
        else:
            # in case pattern doesn't match send the charector as illegal
            yield BNFTokenInfo(BNF_ILLEGAL_TOKEN, data[position])
            position += 1
    yield BNFTokenInfo(BNF_EOF_TOKEN, '\x00')


class BNFRepetitionModifier:
    class Kind(enum.Enum):
        EXACT       = '{A}'
        AT_LEAST    = '{A,}'
        BETWEEN     = '{A,B}'

    def __init__(self):
        self.kind = None
        self.min = None
        self.max = None

    def __str__(self):
        if self.kind is None:
            return "[UNSET RepetitionModifier]"
        elif self.kind == BNFRepetitionModifier.Kind.EXACT:
            return '{' + self.min + '}'
        elif self.kind == BNFRepetitionModifier.Kind.AT_LEAST:
            return '{' + self.min + ',}'
        elif self.kind == BNFRepetitionModifier.Kind.BETWEEN:
            return '{' + self.min + ',' + self.max + '}'
        raise Exception("Unknown repetition kind: {self.kind}")


# BNFAnnotations are introduced by trailing '@(foo-bar baz)' and are an
# extension to the syntax used by CSS, added to allow passing additional
# metadata to the generators for parser creation.
class BNFAnnotation:
    def __init__(self):
        self.directives = []

    def __str__(self):
        return '@(' + ' '.join(self.directives) + ')'

    def add_directive(self, directive):
        self.directives.append(directive)


# Node multipliers are introduced by trailing symbols like '#', '+', '*', and '{1,4}'.
# https://drafts.csswg.org/css-values-4/#component-multipliers
class BNFNodeMultiplier:
    class Kind(enum.Enum):
        ZERO_OR_ONE                     = '?'
        SPACE_SEPARATED_ZERO_OR_MORE    = '*'
        SPACE_SEPARATED_ONE_OR_MORE     = '+'
        SPACE_SEPARATED_EXACT           = '{A}'
        SPACE_SEPARATED_AT_LEAST        = '{A,}'
        SPACE_SEPARATED_BETWEEN         = '{A,B}'
        COMMA_SEPARATED_ONE_OR_MORE     = '#'
        COMMA_SEPARATED_EXACT           = '#{A}'
        COMMA_SEPARATED_AT_LEAST        = '#{A,}'
        COMMA_SEPARATED_BETWEEN         = '#{A,B}'

    def __init__(self):
        self.kind = None
        self.range = None
        self.annotation = None

    def __str__(self):
        if self.annotation:
            return self.stringified_without_annotation + str(self.annotation)
        return self.stringified_without_annotation

    @property
    def stringified_without_annotation(self):
        if self.kind == BNFNodeMultiplier.Kind.ZERO_OR_ONE:
            return '?'
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_ZERO_OR_MORE:
            return '*'
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_ONE_OR_MORE:
            return '+'
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_EXACT:
            return '{' + self.range.min + '}'
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_AT_LEAST:
            return '{' + self.range.min + ',}'
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_BETWEEN:
            return '{' + self.range.min + ',' + self.range.max + '}'
        elif self.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_ONE_OR_MORE:
            return '#'
        elif self.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_EXACT:
            return '#' + '{' + self.range.min + '}'
        elif self.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_AT_LEAST:
            return '#' + '{' + self.range.min + ',}'
        elif self.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_BETWEEN:
            return '#' + '{' + self.range.min + ',' + self.range.max + '}'
        return ''

    def add(self, multiplier):
        if self.annotation:
            raise Exception("Invalid to stack another multiplier on top of a multiplier that has already received an annotation.")

        if self.kind is None:
            if isinstance(multiplier, BNFRepetitionModifier):
                if multiplier.kind == BNFRepetitionModifier.Kind.EXACT:
                    self.kind = BNFNodeMultiplier.Kind.SPACE_SEPARATED_EXACT
                    self.range = multiplier
                elif multiplier.kind == BNFRepetitionModifier.Kind.AT_LEAST:
                    self.kind = BNFNodeMultiplier.Kind.SPACE_SEPARATED_AT_LEAST
                    self.range = multiplier
                elif multiplier.kind == BNFRepetitionModifier.Kind.BETWEEN:
                    self.kind = BNFNodeMultiplier.Kind.SPACE_SEPARATED_BETWEEN
                    self.range = multiplier
            else:
                self.kind = BNFNodeMultiplier.Kind(multiplier)
        elif self.kind == BNFNodeMultiplier.Kind.ZERO_OR_ONE:
            raise Exception("Invalid to stack another multiplier on top of '?'")
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_ZERO_OR_MORE:
            raise Exception("Invalid to stack another multiplier on top of '*'")
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_ONE_OR_MORE:
            raise Exception("Invalid to stack another multiplier on top of '+'")
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_EXACT:
            raise Exception("Invalid to stack another multiplier on top of a range.")
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_AT_LEAST:
            raise Exception("Invalid to stack another multiplier on top of a range.")
        elif self.kind == BNFNodeMultiplier.Kind.SPACE_SEPARATED_BETWEEN:
            raise Exception("Invalid to stack another multiplier on top of a range.")
        elif self.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_ONE_OR_MORE:
            if isinstance(multiplier, BNFRepetitionModifier):
                if multiplier.kind == BNFRepetitionModifier.Kind.EXACT:
                    self.kind = BNFNodeMultiplier.Kind.COMMA_SEPARATED_EXACT
                    self.range = multiplier
                elif multiplier.kind == BNFRepetitionModifier.Kind.AT_LEAST:
                    self.kind = BNFNodeMultiplier.Kind.COMMA_SEPARATED_AT_LEAST
                    self.range = multiplier
                elif multiplier.kind == BNFRepetitionModifier.Kind.BETWEEN:
                    self.kind = BNFNodeMultiplier.Kind.COMMA_SEPARATED_BETWEEN
                    self.range = multiplier
            else:
                raise Exception("Invalid to stack a non-range multiplier on top of '#'.")
        elif self.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_EXACT:
            raise Exception("Invalid to stack another multiplier on top of a comma modifier range multiplier.")
        elif self.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_AT_LEAST:
            raise Exception("Invalid to stack another multiplier on top of a comma modifier range multiplier.")
        elif self.kind == BNFNodeMultiplier.Kind.COMMA_SEPARATED_BETWEEN:
            raise Exception("Invalid to stack another multiplier on top of a comma modifier range multiplier.")

    def add_annotation(self, annotation):
        if self.annotation:
            raise Exception("Invalid to add an annotation to a multiplier node that already has an annotation.")

        SUPPORTED_DIRECTIVES = {
            'no-single-item-opt': {
                BNFNodeMultiplier.Kind.SPACE_SEPARATED_ZERO_OR_MORE,
                BNFNodeMultiplier.Kind.SPACE_SEPARATED_ONE_OR_MORE,
                BNFNodeMultiplier.Kind.SPACE_SEPARATED_AT_LEAST,
                BNFNodeMultiplier.Kind.COMMA_SEPARATED_ONE_OR_MORE,
                BNFNodeMultiplier.Kind.COMMA_SEPARATED_AT_LEAST
            },
        }

        for directive in annotation.directives:
            if directive not in SUPPORTED_DIRECTIVES:
                raise Exception(f"Unknown annotation directive '{directive}' for multiplier '{self}'.")
            if self.kind not in SUPPORTED_DIRECTIVES[directive]:
                raise Exception(f"Unsupported annotation directive '{directive}' for multiplier '{self}'.")

        self.annotation = annotation


# https://drafts.csswg.org/css-values-4/#component-combinators
class BNFGroupingNode:
    class Kind(enum.Enum):
        MATCH_ALL_ORDERED = ' '                # [ <length>    <integer>    <percentage> ]
        MATCH_ONE = '|'                        # [ <length>  | <integer>  | <percentage> ]
        MATCH_ALL_ANY_ORDER = '&&'             # [ <length> && <integer> && <percentage> ]
        MATCH_ONE_OR_MORE_ANY_ORDER = '||'     # [ <length> || <integer> || <percentage> ]

    def __init__(self, *, is_initial=False):
        self.kind = BNFGroupingNode.Kind.MATCH_ALL_ORDERED
        self.members = []
        self.multiplier = BNFNodeMultiplier()
        self.is_initial = is_initial
        self.annotation = None

    def __str__(self):
        return self.stringified_without_multipliers + str(self.multiplier)

    @property
    def stringified_without_multipliers(self):
        if self.is_initial:
            return self.stringified_without_brackets_or_multipliers
        return '[ ' + self.stringified_without_brackets_or_multipliers + ' ]'

    @property
    def stringified_without_brackets_or_multipliers(self):
        if self.kind != BNFGroupingNode.Kind.MATCH_ALL_ORDERED:
            join_string = ' ' + self.kind.value + ' '
        else:
            join_string = ' '

        return join_string.join(str(member) for member in self.members)

    def add(self, member):
        self.members.append(member)

    @property
    def last(self):
        return self.members[-1]

    def add_annotation(self, annotation):
        if self.multiplier.kind:
            self.multiplier.add_annotation(annotation)
            return

        if self.annotation:
            raise Exception("Invalid to add an annotation to a grouping node that already has an annotation.")

        SUPPORTED_DIRECTIVES = {
            'primitive-pair': {
                BNFGroupingNode.Kind.MATCH_ALL_ORDERED,
                BNFGroupingNode.Kind.MATCH_ALL_ANY_ORDER,
                BNFGroupingNode.Kind.MATCH_ONE_OR_MORE_ANY_ORDER,
            },
        }

        for directive in annotation.directives:
            if directive not in SUPPORTED_DIRECTIVES:
                raise Exception(f"Unknown annotation directive '{directive}' for grouping '{self}'.")
            if self.kind not in SUPPORTED_DIRECTIVES[directive]:
                raise Exception(f"Unsupported annotation directive '{directive}' for grouping '{self}'.")

        self.annotation = annotation


# https://drafts.csswg.org/css-values-4/#functional-notation
class BNFFunctionNode:
    def __init__(self, name):
        self.name = name
        self.parameter_group = BNFGroupingNode()
        self.multiplier = BNFNodeMultiplier()

    def __str__(self):
        return self.stringified_without_multipliers + str(self.multiplier)

    @property
    def stringified_without_multipliers(self):
        return self.name + '(' + self.parameter_group.stringified_without_brackets_or_multipliers + ')'

    @property
    def kind(self):
        return self.parameter_group.kind

    @kind.setter
    def kind(self, kind):
        self.parameter_group.kind = kind

    def add(self, member):
        self.parameter_group.add(member)

    @property
    def last(self):
        return self.parameter_group.last


class BNFReferenceNode:
    class RangeAttribute:
        def __init__(self):
            self.min = None
            self.max = None

        def __str__(self):
            return '[' + str(self.min) + ',' + str(self.max) + ']'

    def __init__(self, *, is_internal=False):
        self.name = None
        self.is_internal = is_internal
        self.is_function_reference = False
        self.attributes = []
        self.multiplier = BNFNodeMultiplier()
        self.annotation = None

    def __str__(self):
        return self.stringified_without_multipliers + str(self.multiplier)

    @property
    def stringified_without_multipliers(self):
        if self.is_internal:
            prefix = '<<'
            suffix = '>>'
        else:
            prefix = '<'
            suffix = '>'

        if self.is_function_reference:
            name = self.name + '()'
        else:
            name = self.name

        if self.attributes:
            return prefix + str(name) + ' ' + ' '.join(str(attribute) for attribute in self.attributes) + suffix
        return prefix + str(name) + suffix

    def add_attribute(self, attribute):
        self.attributes.append(attribute)

    def add_annotation(self, annotation):
        if self.multiplier.kind:
            self.multiplier.add_annotation(annotation)
            return

        if self.annotation:
            raise Exception("Invalid to add an annotation to a reference node that already has an annotation.")

        SUPPORTED_DIRECTIVES = {}

        for directive in annotation.directives:
            if directive not in SUPPORTED_DIRECTIVES:
                raise Exception(f"Unknown annotation directive '{directive}' for reference node '{self}'.")

        self.annotation = annotation


class BNFKeywordNode:
    def __init__(self, keyword):
        self.keyword = keyword
        self.multiplier = BNFNodeMultiplier()
        self.annotation = None

    def __str__(self):
        return self.stringified_without_multipliers + str(self.multiplier)

    @property
    def stringified_without_multipliers(self):
        return self.keyword

    def add_annotation(self, annotation):
        if self.multiplier.kind:
            self.multiplier.add_annotation(annotation)
            return

        if self.annotation:
            raise Exception("Invalid to add an annotation to a keyword node that already has an annotation.")

        SUPPORTED_DIRECTIVES = {}

        for directive in annotation.directives:
            if directive not in SUPPORTED_DIRECTIVES:
                raise Exception(f"Unknown annotation directive '{directive}' for keyword '{self}'.")

        self.annotation = annotation


class BNFLiteralNode:
    def __init__(self, value=None):
        self.value = value
        self.multiplier = BNFNodeMultiplier()
        self.annotation = None

    def __str__(self):
        return self.stringified_without_multipliers + str(self.multiplier)

    @property
    def stringified_without_multipliers(self):
        return str(self.value)

    def add_annotation(self, annotation):
        if self.multiplier.kind:
            self.multiplier.add_annotation(annotation)
            return

        if self.annotation:
            raise Exception("Invalid to add an annotation to a literal node that already has an annotation.")

        SUPPORTED_DIRECTIVES = {}

        for directive in annotation.directives:
            if directive not in SUPPORTED_DIRECTIVES:
                raise Exception(f"Unknown annotation directive '{directive}' for literal '{self}'.")

        self.annotation = annotation


class BNFParserState(enum.Enum):
    UNKNOWN_GROUPING_INITIAL = enum.auto()
    UNKNOWN_GROUPING_SEEN_TERM = enum.auto()
    KNOWN_ORDERED_GROUPING = enum.auto()
    KNOWN_COMBINATOR_GROUPING_TERM_REQUIRED = enum.auto()
    KNOWN_COMBINATOR_GROUPING_COMBINATOR_OR_CLOSE_REQUIRED = enum.auto()
    INTERNAL_REFERENCE_INITIAL = enum.auto()
    INTERNAL_REFERENCE_SEEN_ID = enum.auto()
    REFERENCE_INITIAL = enum.auto()
    REFERENCE_SEEN_FUNCTION_OPEN = enum.auto()
    REFERENCE_SEEN_ID_OR_FUNCTION = enum.auto()
    REFERENCE_RANGE_INITIAL = enum.auto()
    REFERENCE_RANGE_SEEN_MIN = enum.auto()
    REFERENCE_RANGE_SEEN_MIN_AND_COMMA = enum.auto()
    REFERENCE_RANGE_SEEN_MAX = enum.auto()
    REPETITION_MODIFIER_INITIAL = enum.auto()
    REPETITION_MODIFIER_SEEN_MIN = enum.auto()
    REPETITION_MODIFIER_SEEN_MIN_AND_COMMA = enum.auto()
    REPETITION_MODIFIER_SEEN_MAX = enum.auto()
    QUOTED_LITERAL_INITIAL = enum.auto()
    QUOTED_LITERAL_SEEN_ID = enum.auto()
    ANNOTATION_INITIAL = enum.auto()
    ANNOTATION_SEEN_ID = enum.auto()
    DONE = enum.auto()


BNFParserStateInfo = collections.namedtuple("BNFParserStates", ["state", "node"])


class BNFParser:
    COMBINATOR_FOR_TOKEN = {
        BNFToken.OR.name: BNFGroupingNode.Kind.MATCH_ONE,
        BNFToken.OROR.name: BNFGroupingNode.Kind.MATCH_ONE_OR_MORE_ANY_ORDER,
        BNFToken.ANDAND.name: BNFGroupingNode.Kind.MATCH_ALL_ANY_ORDER,
    }

    SIMPLE_MULTIPLIERS = {
        BNFToken.HASH.name,
        BNFToken.PLUS.name,
        BNFToken.STAR.name,
        BNFToken.NOT.name,
        BNFToken.QMARK.name,
    }

    SUPPORTED_UNQUOTED_LITERALS = {
        BNFToken.COMMA.name,
        BNFToken.SLASH.name
    }

    DEBUG_PRINT_STATE = 0
    DEBUG_PRINT_TOKENS = 0

    def __init__(self, parsing_context, key_path, data):
        self.parsing_context = parsing_context
        self.key_path = key_path
        self.data = data
        self.root = BNFGroupingNode(is_initial=True)
        self.state_stack = []
        self.enter_initial_grouping()

    def parse(self):
        PARSER_THUNKS = {
            BNFParserState.UNKNOWN_GROUPING_INITIAL: BNFParser.parse_UNKNOWN_GROUPING_INITIAL,
            BNFParserState.UNKNOWN_GROUPING_SEEN_TERM: BNFParser.parse_UNKNOWN_GROUPING_SEEN_TERM,
            BNFParserState.KNOWN_ORDERED_GROUPING: BNFParser.parse_KNOWN_ORDERED_GROUPING,
            BNFParserState.KNOWN_COMBINATOR_GROUPING_TERM_REQUIRED: BNFParser.parse_KNOWN_COMBINATOR_GROUPING_TERM_REQUIRED,
            BNFParserState.KNOWN_COMBINATOR_GROUPING_COMBINATOR_OR_CLOSE_REQUIRED: BNFParser.parse_KNOWN_COMBINATOR_GROUPING_COMBINATOR_OR_CLOSE_REQUIRED,
            BNFParserState.INTERNAL_REFERENCE_INITIAL: BNFParser.parse_INTERNAL_REFERENCE_INITIAL,
            BNFParserState.INTERNAL_REFERENCE_SEEN_ID: BNFParser.parse_INTERNAL_REFERENCE_SEEN_ID,
            BNFParserState.REFERENCE_INITIAL: BNFParser.parse_REFERENCE_INITIAL,
            BNFParserState.REFERENCE_SEEN_FUNCTION_OPEN: BNFParser.parse_REFERENCE_SEEN_FUNCTION_OPEN,
            BNFParserState.REFERENCE_SEEN_ID_OR_FUNCTION: BNFParser.parse_REFERENCE_SEEN_ID_OR_FUNCTION,
            BNFParserState.REFERENCE_RANGE_INITIAL: BNFParser.parse_REFERENCE_RANGE_INITIAL,
            BNFParserState.REFERENCE_RANGE_SEEN_MIN: BNFParser.parse_REFERENCE_RANGE_SEEN_MIN,
            BNFParserState.REFERENCE_RANGE_SEEN_MIN_AND_COMMA: BNFParser.parse_REFERENCE_RANGE_SEEN_MIN_AND_COMMA,
            BNFParserState.REFERENCE_RANGE_SEEN_MAX: BNFParser.parse_REFERENCE_RANGE_SEEN_MAX,
            BNFParserState.REPETITION_MODIFIER_INITIAL: BNFParser.parse_REPETITION_MODIFIER_INITIAL,
            BNFParserState.REPETITION_MODIFIER_SEEN_MIN: BNFParser.parse_REPETITION_MODIFIER_SEEN_MIN,
            BNFParserState.REPETITION_MODIFIER_SEEN_MIN_AND_COMMA: BNFParser.parse_REPETITION_MODIFIER_SEEN_MIN_AND_COMMA,
            BNFParserState.REPETITION_MODIFIER_SEEN_MAX: BNFParser.parse_REPETITION_MODIFIER_SEEN_MAX,
            BNFParserState.QUOTED_LITERAL_INITIAL: BNFParser.parse_QUOTED_LITERAL_INITIAL,
            BNFParserState.QUOTED_LITERAL_SEEN_ID: BNFParser.parse_QUOTED_LITERAL_SEEN_ID,
            BNFParserState.ANNOTATION_INITIAL: BNFParser.parse_ANNOTATION_INITIAL,
            BNFParserState.ANNOTATION_SEEN_ID: BNFParser.parse_ANNOTATION_SEEN_ID,
        }

        for token in BNFLexer(self.data):
            if token.name == BNF_ILLEGAL_TOKEN:
                raise Exception(f"Illegal token found while parsing grammar definition: {token}")

            state = self.state_stack[-1]

            if BNFParser.DEBUG_PRINT_STATE:
                print("STATE: " + state.state.name + " " + str(state.node))
            if BNFParser.DEBUG_PRINT_TOKENS:
                print("TOKEN: " + str(token))
            PARSER_THUNKS[state.state](self, token, state)

        if self.state_stack[-1].state != BNFParserState.DONE:
            raise Exception(f"Unexpected state '{state.state.name}' after processing all tokens")

        return self.root

    def transition_top(self, *, to):
        self.state_stack[-1] = BNFParserStateInfo(to, self.state_stack[-1].node)

    def push(self, new_state, new_node):
        self.state_stack.append(BNFParserStateInfo(new_state, new_node))

    def pop(self):
        return self.state_stack.pop()

    @property
    def top(self):
        return self.state_stack[-1]

    def unexpected(self, token, state):
        return Exception(f"Unexpected token '{token}' found while in state '{state.state.name}'")

    # COMMON ACTIONS.

    # Root BNFGroupingNode. Syntatically isn't surrounded by square brackets.
    def enter_initial_grouping(self):
        self.push(BNFParserState.UNKNOWN_GROUPING_INITIAL, self.root)

    def exit_initial_grouping(self, token, state):
        if isinstance(state.node, BNFGroupingNode) and state.node.is_initial:
            self.transition_top(to=BNFParserState.DONE)
            return
        raise self.unexpected(token, state)

    # Non-initial BNFGroupingNode. e.g. "[foo bar]", "[foo | bar]", etc.
    def enter_new_grouping(self, token, state):
        self.push(BNFParserState.UNKNOWN_GROUPING_INITIAL, BNFGroupingNode())

    def exit_grouping(self, token, state):
        if isinstance(state.node, BNFGroupingNode) and not state.node.is_initial:
            self.pop()
            self.top.node.add(state.node)
            return
        raise self.unexpected(token, state)

    # BNFFunctionNode. e.g. "foo(<bar>)"
    def enter_new_function(self, token, state):
        self.push(BNFParserState.UNKNOWN_GROUPING_INITIAL, BNFFunctionNode(token.value[:-1]))

    def exit_function(self, token, state):
        if isinstance(state.node, BNFFunctionNode):
            self.pop()
            self.top.node.add(state.node)
            return
        raise self.unexpected(token, state)

    # Internal BNFReferenceNodes. e.g. "<<values>>"
    def enter_new_internal_reference(self, token, state):
        self.push(BNFParserState.INTERNAL_REFERENCE_INITIAL, BNFReferenceNode(is_internal=True))

    def exit_internal_reference(self, token, state):
        self.pop()
        self.top.node.add(state.node)

    # Non-internal BNFReferenceNodes. e.g. "<length>"
    def enter_new_reference(self, token, state):
        new_reference = BNFReferenceNode()
        state.node.add(new_reference)
        self.push(BNFParserState.REFERENCE_INITIAL, new_reference)

    def exit_reference(self, token, state):
        self.pop()

    # BNFRepetitionModifier. e.g. {A,B}
    def enter_new_repetition_modifier(self, token, state):
        self.push(BNFParserState.REPETITION_MODIFIER_INITIAL, BNFRepetitionModifier())

    def exit_repetition_modifier(self, token, state):
        self.pop()
        self.top.node.last.multiplier.add(state.node)

    # BNFReferenceNode.RangeAttribute. e.g. [0,inf]
    def enter_range_attribute(self, token, state):
        self.push(BNFParserState.REFERENCE_RANGE_INITIAL, BNFReferenceNode.RangeAttribute())

    def exit_range_attribute(self, token, state):
        self.pop()
        self.top.node.add_attribute(state.node)

    # BNFLiteralNode. e.g. '['
    def enter_new_quoted_literal(self, token, state):
        self.push(BNFParserState.QUOTED_LITERAL_INITIAL, BNFLiteralNode())

    def exit_quoted_literal(self, token, state):
        self.pop()
        self.top.node.add(state.node)

    # BNFAnnotation. e.g. @(foo-bar baz)
    def enter_new_annotation(self, token, state):
        self.push(BNFParserState.ANNOTATION_INITIAL, BNFAnnotation())

    def exit_annotation(self, token, state):
        self.pop()
        self.top.node.last.add_annotation(state.node)

    # MARK: Parsing Thunks.

    def parse_UNKNOWN_GROUPING_INITIAL(self, token, state):
        if token.name == BNFToken.LSQUARE:
            self.transition_top(to=BNFParserState.UNKNOWN_GROUPING_SEEN_TERM)
            self.enter_new_grouping(token, state)
            return

        if token.name == BNFToken.LTLT:
            self.transition_top(to=BNFParserState.UNKNOWN_GROUPING_SEEN_TERM)
            self.enter_new_internal_reference(token, state)
            return

        if token.name == BNFToken.LT:
            self.transition_top(to=BNFParserState.UNKNOWN_GROUPING_SEEN_TERM)
            self.enter_new_reference(token, state)
            return

        if token.name == BNFToken.FUNC:
            self.transition_top(to=BNFParserState.UNKNOWN_GROUPING_SEEN_TERM)
            self.enter_new_function(token, state)
            return

        if token.name == BNFToken.ID:
            self.transition_top(to=BNFParserState.UNKNOWN_GROUPING_SEEN_TERM)
            state.node.add(BNFKeywordNode(token.value))
            return

        if token.name == BNFToken.SQUOTE:
            self.transition_top(to=BNFParserState.UNKNOWN_GROUPING_SEEN_TERM)
            self.enter_new_quoted_literal(token, state)
            return

        if token.name in BNFParser.SUPPORTED_UNQUOTED_LITERALS:
            self.transition_top(to=BNFParserState.UNKNOWN_GROUPING_SEEN_TERM)
            state.node.add(BNFLiteralNode(token.value))
            return

        if token.name == BNFToken.RPAREN:
            self.transition_top(to=BNFParserState.UNKNOWN_GROUPING_SEEN_TERM)
            self.exit_function(token, state)
            return

        raise self.unexpected(token, state)

    def parse_UNKNOWN_GROUPING_SEEN_TERM(self, token, state):
        if token.name == BNFToken.RSQUARE:
            self.exit_grouping(token, state)
            return

        if token.name == BNF_EOF_TOKEN:
            self.exit_initial_grouping(token, state)
            return

        if token.name == BNFToken.RPAREN:
            self.exit_function(token, state)
            return

        if token.name == BNFToken.LSQUARE:
            self.transition_top(to=BNFParserState.KNOWN_ORDERED_GROUPING)
            self.enter_new_grouping(token, state)
            return

        if token.name == BNFToken.LTLT:
            self.transition_top(to=BNFParserState.KNOWN_ORDERED_GROUPING)
            self.enter_new_internal_reference(token, state)
            return

        if token.name == BNFToken.LT:
            self.transition_top(to=BNFParserState.KNOWN_ORDERED_GROUPING)
            self.enter_new_reference(token, state)
            return

        if token.name == BNFToken.FUNC:
            self.transition_top(to=BNFParserState.KNOWN_ORDERED_GROUPING)
            self.enter_new_function(token, state)
            return

        if token.name == BNFToken.ID:
            self.transition_top(to=BNFParserState.KNOWN_ORDERED_GROUPING)
            state.node.add(BNFKeywordNode(token.value))
            return

        if token.name == BNFToken.SQUOTE:
            self.transition_top(to=BNFParserState.KNOWN_ORDERED_GROUPING)
            self.enter_new_quoted_literal(token, state)
            return

        if token.name in BNFParser.SUPPORTED_UNQUOTED_LITERALS:
            self.transition_top(to=BNFParserState.KNOWN_ORDERED_GROUPING)
            state.node.add(BNFLiteralNode(token.value))
            return

        if token.name in BNFParser.COMBINATOR_FOR_TOKEN:
            self.transition_top(to=BNFParserState.KNOWN_COMBINATOR_GROUPING_TERM_REQUIRED)
            state.node.kind = BNFParser.COMBINATOR_FOR_TOKEN[token.name]
            return

        if token.name in BNFParser.SIMPLE_MULTIPLIERS:
            state.node.last.multiplier.add(token.value)
            return

        if token.name == BNFToken.ATPAREN:
            self.enter_new_annotation(token, state)
            return

        if token.name == BNFToken.LBRACE:
            self.enter_new_repetition_modifier(token, state)
            return

        raise self.unexpected(token, state)

    def parse_KNOWN_ORDERED_GROUPING(self, token, state):
        if token.name == BNFToken.RSQUARE:
            self.exit_grouping(token, state)
            return

        if token.name == BNF_EOF_TOKEN:
            self.exit_initial_grouping(token, state)
            return

        if token.name == BNFToken.RPAREN:
            self.exit_function(token, state)
            return

        if token.name == BNFToken.LSQUARE:
            self.enter_new_grouping(token, state)
            return

        if token.name == BNFToken.LTLT:
            self.enter_new_internal_reference(token, state)
            return

        if token.name == BNFToken.LT:
            self.enter_new_reference(token, state)
            return

        if token.name == BNFToken.ID:
            state.node.add(BNFKeywordNode(token.value))
            return

        if token.name == BNFToken.SQUOTE:
            self.enter_new_quoted_literal(token, state)
            return

        if token.name in BNFParser.SUPPORTED_UNQUOTED_LITERALS:
            state.node.add(BNFLiteralNode(token.value))
            return

        if token.name == BNFToken.FUNC:
            self.enter_new_function(token, state)
            return

        if token.name in BNFParser.SIMPLE_MULTIPLIERS:
            state.node.last.multiplier.add(token.value)
            return

        if token.name == BNFToken.ATPAREN:
            self.enter_new_annotation(token, state)
            return

        raise self.unexpected(token, state)

    def parse_KNOWN_COMBINATOR_GROUPING_TERM_REQUIRED(self, token, state):
        if token.name == BNFToken.LSQUARE:
            self.transition_top(to=BNFParserState.KNOWN_COMBINATOR_GROUPING_COMBINATOR_OR_CLOSE_REQUIRED)
            self.enter_new_grouping(token, state)
            return

        if token.name == BNFToken.LTLT:
            self.transition_top(to=BNFParserState.KNOWN_COMBINATOR_GROUPING_COMBINATOR_OR_CLOSE_REQUIRED)
            self.enter_new_internal_reference(token, state)
            return

        if token.name == BNFToken.LT:
            self.transition_top(to=BNFParserState.KNOWN_COMBINATOR_GROUPING_COMBINATOR_OR_CLOSE_REQUIRED)
            self.enter_new_reference(token, state)
            return

        if token.name == BNFToken.FUNC:
            self.transition_top(to=BNFParserState.KNOWN_COMBINATOR_GROUPING_COMBINATOR_OR_CLOSE_REQUIRED)
            self.enter_new_function(token, state)
            return

        if token.name == BNFToken.ID:
            self.transition_top(to=BNFParserState.KNOWN_COMBINATOR_GROUPING_COMBINATOR_OR_CLOSE_REQUIRED)
            state.node.add(BNFKeywordNode(token.value))
            return

        # FIXME: Does it make any sense to support literals here? e.g. [ <foo> && , ]

        if token.name == BNFToken.RSQUARE or token.name == BNFToken.FUNC or token.name == EOF:
            raise Exception(f"Unexpected token '{token}'. Groupings can't end in a combinator.")
        raise self.unexpected(token, state)

    def parse_KNOWN_COMBINATOR_GROUPING_COMBINATOR_OR_CLOSE_REQUIRED(self, token, state):
        if token.name == BNFToken.RSQUARE:
            self.exit_grouping(token, state)
            return

        if token.name == BNF_EOF_TOKEN:
            self.exit_initial_grouping(token, state)
            return

        if token.name == BNFToken.RPAREN:
            self.exit_function(token, state)
            return

        if token.name in BNFParser.COMBINATOR_FOR_TOKEN:
            if state.node.kind == BNFParser.COMBINATOR_FOR_TOKEN[token.name]:
                self.transition_top(to=BNFParserState.KNOWN_COMBINATOR_GROUPING_TERM_REQUIRED)
                return
            raise Exception(f"Unexpected token '{token}'. Did you mean '{state.node.kind.name}'?.")

        if token.name in BNFParser.SIMPLE_MULTIPLIERS:
            state.node.last.multiplier.add(token.value)
            return

        if token.name == BNFToken.ATPAREN:
            self.enter_new_annotation(token, state)
            return

        if token.name == BNFToken.LBRACE:
            self.enter_new_repetition_modifier(token, state)
            return

        raise self.unexpected(token, state)

    def parse_REFERENCE_INITIAL(self, token, state):
        if token.name == BNFToken.FUNC:
            self.transition_top(to=BNFParserState.REFERENCE_SEEN_FUNCTION_OPEN)
            state.node.is_function_reference = True
            state.node.name = token.value[:-1]
            return

        if token.name == BNFToken.ID:
            self.transition_top(to=BNFParserState.REFERENCE_SEEN_ID_OR_FUNCTION)
            state.node.name = token.value
            return

        raise self.unexpected(token, state)

    def parse_REFERENCE_SEEN_FUNCTION_OPEN(self, token, state):
        if token.name == BNFToken.RPAREN:
            self.transition_top(to=BNFParserState.REFERENCE_SEEN_ID_OR_FUNCTION)
            return

        raise self.unexpected(token, state)

    def parse_REFERENCE_SEEN_ID_OR_FUNCTION(self, token, state):
        if token.name == BNFToken.ID:
            state.node.add_attribute(token.value)
            # Remain in BNFParserState.REFERENCE_SEEN_ID_OR_FUNCTION.
            return

        if token.name == BNFToken.LSQUARE:
            self.enter_range_attribute(token, state)
            return

        if token.name == BNFToken.GT:
            self.exit_reference(token, state)
            return

        raise self.unexpected(token, state)

    def parse_INTERNAL_REFERENCE_INITIAL(self, token, state):
        if token.name == BNFToken.ID:
            self.transition_top(to=BNFParserState.INTERNAL_REFERENCE_SEEN_ID)
            state.node.name = token.value
            return

        raise self.unexpected(token, state)

    def parse_INTERNAL_REFERENCE_SEEN_ID(self, token, state):
        if token.name == BNFToken.ID:
            state.node.add_attribute(token.value)
            # Remain in BNFParserState.INTERNAL_REFERENCE_SEEN_ID.
            return

        if token.name == BNFToken.LSQUARE:
            self.enter_range_attribute(token, state)
            return

        if token.name == BNFToken.GTGT:
            self.exit_internal_reference(token, state)
            return

        raise self.unexpected(token, state)

    def parse_REFERENCE_RANGE_INITIAL(self, token, state):
        if token.name == BNFToken.INT or token.name == BNFToken.FLOAT or (token.name == BNFToken.ID and token.value == "inf"):
            self.transition_top(to=BNFParserState.REFERENCE_RANGE_SEEN_MIN)
            state.node.min = token.value
            return

        raise self.unexpected(token, state)

    def parse_REFERENCE_RANGE_SEEN_MIN(self, token, state):
        if token.name == BNFToken.COMMA:
            self.transition_top(to=BNFParserState.REFERENCE_RANGE_SEEN_MIN_AND_COMMA)
            return

        raise self.unexpected(token, state)

    def parse_REFERENCE_RANGE_SEEN_MIN_AND_COMMA(self, token, state):
        if token.name == BNFToken.INT or token.name == BNFToken.FLOAT or (token.name == BNFToken.ID and token.value == "inf"):
            self.transition_top(to=BNFParserState.REFERENCE_RANGE_SEEN_MAX)
            state.node.max = token.value
            return

        raise self.unexpected(token, state)

    def parse_REFERENCE_RANGE_SEEN_MAX(self, token, state):
        if token.name == BNFToken.RSQUARE:
            self.exit_range_attribute(token, state)
            return

        raise self.unexpected(token, state)

    def parse_REPETITION_MODIFIER_INITIAL(self, token, state):
        if token.name == BNFToken.INT:
            self.transition_top(to=BNFParserState.REPETITION_MODIFIER_SEEN_MIN)
            state.node.kind = BNFRepetitionModifier.Kind.EXACT
            state.node.min = token.value
            return

        raise self.unexpected(token, state)

    def parse_REPETITION_MODIFIER_SEEN_MIN(self, token, state):
        if token.name == BNFToken.COMMA:
            self.transition_top(to=BNFParserState.REPETITION_MODIFIER_SEEN_MIN_AND_COMMA)
            state.node.kind = BNFRepetitionModifier.Kind.AT_LEAST
            return

        if token.name == BNFToken.RBRACE:
            self.exit_repetition_modifier(token, state)
            return

        raise self.unexpected(token, state)

    def parse_REPETITION_MODIFIER_SEEN_MIN_AND_COMMA(self, token, state):
        if token.name == BNFToken.INT:
            self.transition_top(to=BNFParserState.REPETITION_MODIFIER_SEEN_MAX)
            state.node.kind = BNFRepetitionModifier.Kind.BETWEEN
            state.node.max = token.value
            return

        if token.name == BNFToken.RBRACE:
            self.exit_repetition_modifier(token, state)
            return

        raise self.unexpected(token, state)

    def parse_REPETITION_MODIFIER_SEEN_MAX(self, token, state):
        if token.name == BNFToken.RBRACE:
            self.exit_repetition_modifier(token, state)
            return

        raise self.unexpected(token, state)

    def parse_QUOTED_LITERAL_INITIAL(self, token, state):
        # Take the value regardless of token name.
        self.transition_top(to=BNFParserState.QUOTED_LITERAL_SEEN_ID)
        state.node.value = token.value

    def parse_QUOTED_LITERAL_SEEN_ID(self, token, state):
        if token.name == BNFToken.SQUOTE:
            self.exit_quoted_literal(token, state)
            return

        # Append the value regardles of the token value.
        state.node.value = state.node.value + token.value

    def parse_ANNOTATION_INITIAL(self, token, state):
        if token.name == BNFToken.ID:
            self.transition_top(to=BNFParserState.ANNOTATION_SEEN_ID)
            state.node.add_directive(token.value)
            return

        raise self.unexpected(token, state)

    def parse_ANNOTATION_SEEN_ID(self, token, state):
        if token.name == BNFToken.ID:
            state.node.add_directive(token.value)
            return

        if token.name == BNFToken.RPAREN:
            self.exit_annotation(token, state)
            return

        raise self.unexpected(token, state)


def main():
    parser = argparse.ArgumentParser(description='Process CSS property definitions.')
    parser.add_argument('--properties', default="CSSProperties.json")
    parser.add_argument('--defines')
    parser.add_argument('--gperf-executable')
    parser.add_argument('-v', '--verbose', action='store_true')
    parser.add_argument('--dump-unused-grammars', action='store_true')
    parser.add_argument('--check-unused-grammars-values', action='store_true')
    args = parser.parse_args()

    with open(args.properties, "r", encoding="utf-8") as properties_file:
        properties_json = json.load(properties_file)

    parsing_context = ParsingContext(properties_json, defines_string=args.defines, parsing_for_codegen=True, check_unused_grammars_values=args.check_unused_grammars_values, verbose=args.verbose)
    parsing_context.parse_shared_grammar_rules()
    parsing_context.parse_properties_and_descriptors()

    if args.verbose:
        print(f"{len(parsing_context.parsed_shared_grammar_rules.rules)} shared grammar rules active for code generation")
        for set in parsing_context.parsed_properties_and_descriptors.all_sets:
            print(f"{len(set.all)} {set.name} {set.noun} active for code generation")
        print(f"{len(parsing_context.parsed_properties_and_descriptors.all_unique)} uniquely named properties and descriptors active for code generation")

    if args.dump_unused_grammars:
        for property in parsing_context.parsed_properties_and_descriptors.all_properties_and_descriptors:
            if property.codegen_properties.parser_grammar_unused:
                print(str(property).rjust(40, " ") + "  " + str(property.codegen_properties.parser_grammar_unused.root_term))
                print("           ".rjust(40, " ") + "  " + str(property.codegen_properties.parser_grammar_unused_reason))

    generation_context = GenerationContext(parsing_context.parsed_properties_and_descriptors, parsing_context.parsed_shared_grammar_rules, verbose=args.verbose, gperf_executable=args.gperf_executable)

    generators = [
        GenerateCSSPropertyNames,
        GenerateCSSPropertyParsing,
        GenerateCSSStyleDeclarationPropertyNames,
        GenerateStyleBuilderGenerated,
        GenerateStylePropertyShorthandFunctions,
    ]

    for generator in generators:
        generator(generation_context).generate()


if __name__ == "__main__":
    main()
