# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause

from dataclasses import dataclass
from dataclasses import field
from enum import Enum
from pathlib import Path
from typing import Dict
from typing import List
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple

from Utils.lexer import Lexer


@dataclass(frozen=True)
class IDLType:
    name: str
    nullable: bool = False

    def __str__(self) -> str:
        nullable_suffix = "?" if self.nullable else ""
        return f"{self.name}{nullable_suffix}"

    def clone_with_nullable(self, nullable: bool) -> "IDLType":
        return IDLType(self.name, nullable)

    def without_nullable(self) -> "IDLType":
        return self.clone_with_nullable(False)

    def flattened_member_types(self) -> List["IDLType"]:
        return [self]

    def includes_undefined(self) -> bool:
        return self.name == "undefined"

    def includes_nullable_type(self) -> bool:
        return self.nullable

    def child_types(self) -> Tuple["IDLType", ...]:
        return ()

    def nested_types(self) -> List["IDLType"]:
        nested_types: List["IDLType"] = [self]
        for child_type in self.child_types():
            nested_types.extend(child_type.nested_types())
        return nested_types


@dataclass(init=False, frozen=True)
class IDLUnionType(IDLType):
    member_types: Tuple[IDLType, ...]

    def __init__(self, member_types: Sequence[IDLType], nullable: bool = False) -> None:
        object.__setattr__(self, "member_types", tuple(member_types))
        super().__init__(self.name_for_member_types(member_types), nullable)

    @staticmethod
    def name_for_member_types(member_types: Sequence[IDLType]) -> str:
        return f"({' or '.join(str(member_type) for member_type in member_types)})"

    def __str__(self) -> str:
        nullable_suffix = "?" if self.nullable else ""
        return f"{self.name_for_member_types(self.member_types)}{nullable_suffix}"

    def clone_with_nullable(self, nullable: bool) -> "IDLUnionType":
        return IDLUnionType(self.member_types, nullable)

    def flattened_member_types(self) -> List[IDLType]:
        flattened_member_types: List[IDLType] = []
        for member_type in self.member_types:
            flattened_member_types.extend(member_type.flattened_member_types())
        return flattened_member_types

    def includes_undefined(self) -> bool:
        return any(member_type.includes_undefined() for member_type in self.member_types)

    def includes_nullable_type(self) -> bool:
        return self.nullable or any(member_type.includes_nullable_type() for member_type in self.member_types)

    def child_types(self) -> Tuple[IDLType, ...]:
        return self.member_types


@dataclass(init=False, frozen=True)
class IDLParameterizedType(IDLType):
    parameters: Tuple[IDLType, ...]

    def __init__(self, name: str, parameters: Sequence[IDLType], nullable: bool = False) -> None:
        object.__setattr__(self, "parameters", tuple(parameters))
        super().__init__(name, nullable)

    def __str__(self) -> str:
        nullable_suffix = "?" if self.nullable else ""
        return f"{self.name}<{', '.join(str(parameter) for parameter in self.parameters)}>{nullable_suffix}"

    def clone_with_nullable(self, nullable: bool) -> "IDLParameterizedType":
        return IDLParameterizedType(self.name, self.parameters, nullable)

    def child_types(self) -> Tuple[IDLType, ...]:
        return self.parameters


@dataclass
class Constant:
    type: IDLType
    name: str
    value: str


@dataclass
class OperationParameter:
    name: str
    type: IDLType
    optional: bool = False
    default_value: Optional[str] = None
    variadic: bool = False
    extended_attributes: Dict[str, str] = field(default_factory=dict)


@dataclass
class SpecialOperation:
    identifier_type: IDLType
    return_type: IDLType
    name: str
    parameters: List[OperationParameter]
    extended_attributes: Dict[str, str] = field(default_factory=dict)


@dataclass
class Operation:
    name: str
    return_type: IDLType
    parameters: List[OperationParameter]
    extended_attributes: Dict[str, str] = field(default_factory=dict)


@dataclass
class Constructor:
    parameters: List[OperationParameter]
    extended_attributes: Dict[str, str] = field(default_factory=dict)


@dataclass
class IterableDeclaration:
    value_type: IDLType
    key_type: Optional[IDLType] = None


@dataclass
class AsyncIterableDeclaration:
    value_type: IDLType
    parameters: List[OperationParameter] = field(default_factory=list)
    key_type: Optional[IDLType] = None


@dataclass
class MaplikeDeclaration:
    key_type: IDLType
    value_type: IDLType
    readonly: bool = False


@dataclass
class SetlikeDeclaration:
    value_type: IDLType
    readonly: bool = False


@dataclass
class Attribute:
    name: str
    type: IDLType
    readonly: bool = False
    inherit: bool = False
    extended_attributes: Dict[str, str] = field(default_factory=dict)


@dataclass
class Stringifier:
    extended_attributes: Dict[str, str] = field(default_factory=dict)
    attribute: Optional[Attribute] = None


class InterfaceKind(Enum):
    Interface = "interface"
    Namespace = "namespace"
    CallbackInterface = "callback interface"


@dataclass
class Interface:
    name: str
    path: Path
    extended_attributes: Dict[str, str] = field(default_factory=dict)
    kind: InterfaceKind = InterfaceKind.Interface
    parent_name: str = ""
    constants: List[Constant] = field(default_factory=list)
    regular_attributes: List[Attribute] = field(default_factory=list)
    static_attributes: List[Attribute] = field(default_factory=list)
    regular_operations: List[Operation] = field(default_factory=list)
    static_operations: List[Operation] = field(default_factory=list)
    constructors: List[Constructor] = field(default_factory=list)
    stringifier: Optional[Stringifier] = None
    iterable: Optional[IterableDeclaration] = None
    async_iterable: Optional[AsyncIterableDeclaration] = None
    named_property_getter: Optional[SpecialOperation] = None
    indexed_property_getter: Optional[SpecialOperation] = None
    named_property_setter: Optional[SpecialOperation] = None
    named_property_deleter: Optional[SpecialOperation] = None
    indexed_property_setter: Optional[SpecialOperation] = None
    maplike: Optional[MaplikeDeclaration] = None
    setlike: Optional[SetlikeDeclaration] = None

    @property
    def is_namespace(self) -> bool:
        return self.kind == InterfaceKind.Namespace

    @property
    def is_callback_interface(self) -> bool:
        return self.kind == InterfaceKind.CallbackInterface

    @property
    def has_special_member(self) -> bool:
        return (
            self.iterable is not None
            or self.async_iterable is not None
            or self.maplike is not None
            or self.setlike is not None
        )

    @property
    def namespaced_name(self) -> str:
        legacy_namespace = self.extended_attributes.get("LegacyNamespace")
        if legacy_namespace:
            return f"{legacy_namespace}.{self.name}"
        return self.name

    @property
    def implemented_name(self) -> str:
        return self.extended_attributes.get("ImplementedAs", self.name)

    @property
    def constructor_class(self) -> str:
        return f"{self.implemented_name}Constructor"

    @property
    def prototype_class(self) -> str:
        return f"{self.implemented_name}Prototype"

    @property
    def namespace_class(self) -> str:
        return f"{self.name}Namespace"

    @property
    def supports_named_properties(self) -> bool:
        return self.named_property_getter is not None


@dataclass
class Dictionary:
    name: str
    path: Path
    parent_name: str = ""
    members: List["DictionaryMember"] = field(default_factory=list)
    extended_attributes: Dict[str, str] = field(default_factory=dict)


@dataclass
class DictionaryMember:
    name: str
    type: IDLType
    required: bool = False
    default_value: Optional[str] = None
    extended_attributes: Dict[str, str] = field(default_factory=dict)


@dataclass
class CallbackFunction:
    name: str
    path: Path
    return_type: IDLType
    parameters: List[OperationParameter] = field(default_factory=list)
    extended_attributes: Dict[str, str] = field(default_factory=dict)


@dataclass
class Enumeration:
    name: str
    path: Path
    values: List[str]
    extended_attributes: Dict[str, str] = field(default_factory=dict)


@dataclass
class Typedef:
    name: str
    path: Path
    type: IDLType


@dataclass
class IncludedMixin:
    interface_name: str
    mixin_name: str


@dataclass
class Module:
    path: Path
    interface: Optional[Interface] = None
    partial_interfaces: List[Interface] = field(default_factory=list)
    mixins: List[Interface] = field(default_factory=list)
    partial_mixins: List[Interface] = field(default_factory=list)
    included_mixins: List[IncludedMixin] = field(default_factory=list)
    callback_functions: List[CallbackFunction] = field(default_factory=list)
    dictionaries: List[Dictionary] = field(default_factory=list)
    partial_dictionaries: List[Dictionary] = field(default_factory=list)
    enumerations: List[Enumeration] = field(default_factory=list)
    typedefs: List[Typedef] = field(default_factory=list)


class ParseError(RuntimeError):
    pass


def parse_module(path: Path, contents: str) -> Module:
    return Parser(path, contents).parse()


class Parser:
    def __init__(self, path: Path, contents: str) -> None:
        self.path = path
        self.contents = contents
        self.lexer = Lexer(contents)

    def parse(self) -> Module:
        module = Module(path=self.path)

        self.consume_whitespace()
        while not self.lexer.is_eof():
            self.parse_module_declaration(module, self.parse_leading_extended_attributes())
            self.consume_whitespace()

        return module

    def parse_module_declaration(self, module: Module, extended_attributes: Dict[str, str]) -> None:
        if self.consume_optional_keyword("dictionary"):
            module.dictionaries.append(self.parse_dictionary(extended_attributes))
            return

        if self.consume_optional_keyword("partial dictionary"):
            module.partial_dictionaries.append(self.parse_dictionary(extended_attributes))
            return

        if self.consume_optional_keyword("enum"):
            module.enumerations.append(self.parse_enumeration(extended_attributes))
            return

        if self.consume_optional_keyword("typedef"):
            module.typedefs.append(self.parse_typedef())
            return

        if self.consume_optional_keyword("partial interface mixin"):
            module.partial_mixins.append(self.parse_interface_declaration(extended_attributes))
            return

        if self.consume_optional_keyword("partial interface"):
            module.partial_interfaces.append(self.parse_interface_declaration(extended_attributes))
            return

        if self.consume_optional_keyword("interface mixin"):
            module.mixins.append(self.parse_interface_declaration(extended_attributes))
            return

        if self.consume_optional_keyword("partial namespace"):
            module.partial_interfaces.append(
                self.parse_interface_declaration(extended_attributes, InterfaceKind.Namespace)
            )
            return

        if self.consume_optional_keyword("callback interface"):
            module.interface = self.parse_interface_declaration(extended_attributes, InterfaceKind.CallbackInterface)
            return

        if self.consume_optional_keyword("callback"):
            module.callback_functions.append(self.parse_callback_function(extended_attributes))
            return

        if self.consume_optional_keyword("namespace"):
            module.interface = self.parse_interface_declaration(extended_attributes, InterfaceKind.Namespace)
            return

        if self.consume_optional_keyword("interface"):
            module.interface = self.parse_interface_declaration(extended_attributes)
            return

        module.included_mixins.append(self.parse_includes_statement())

    def parse_includes_statement(self) -> IncludedMixin:
        interface_name = self.parse_identifier()
        self.consume_whitespace()

        if not self.next_is_keyword("includes"):
            self.raise_parse_error("expected a declaration or an includes statement")

        self.consume_keyword("includes")
        self.consume_whitespace()
        mixin_name = self.parse_identifier()
        self.consume_whitespace()
        self.assert_specific(";")
        return IncludedMixin(interface_name=interface_name, mixin_name=mixin_name)

    def parse_interface_declaration(
        self, extended_attributes: Dict[str, str], kind: InterfaceKind = InterfaceKind.Interface
    ) -> Interface:
        name, parent_name = self.parse_named_body_header()
        interface = Interface(
            name=name,
            path=self.path,
            extended_attributes=extended_attributes,
            kind=kind,
            parent_name=parent_name,
        )
        self.parse_interface_body(interface)
        self.consume_whitespace()
        self.assert_specific(";")

        return interface

    def parse_dictionary(self, extended_attributes: Dict[str, str]) -> Dictionary:
        dictionary_extended_attributes = extended_attributes
        dictionary_name, parent_name = self.parse_named_body_header()
        members: List[DictionaryMember] = []

        while True:
            self.consume_whitespace()

            if self.lexer.consume_specific("}"):
                self.consume_whitespace()
                self.assert_specific(";")
                break

            member_extended_attributes = self.parse_leading_extended_attributes()

            required = self.consume_optional_keyword("required")

            member_extended_attributes.update(self.parse_leading_extended_attributes())

            member_type = self.parse_type()
            self.consume_whitespace()
            member_name = self.parse_identifier()
            self.consume_whitespace()

            default_value: Optional[str] = None
            if self.lexer.consume_specific("="):
                if required:
                    self.raise_parse_error("required dictionary member must not have a default value")
                self.consume_whitespace()
                default_value = self.consume_until_top_level(";").strip()
                self.consume_whitespace()

            self.assert_specific(";")

            members.append(
                DictionaryMember(
                    name=member_name,
                    type=member_type,
                    required=required,
                    default_value=default_value,
                    extended_attributes=member_extended_attributes,
                )
            )

        members.sort(key=lambda member: member.name)

        return Dictionary(
            name=dictionary_name,
            path=self.path,
            parent_name=parent_name,
            members=members,
            extended_attributes=dictionary_extended_attributes,
        )

    def parse_callback_function(self, extended_attributes: Dict[str, str]) -> CallbackFunction:
        name = self.parse_identifier()
        self.consume_whitespace()
        self.assert_specific("=")
        self.consume_whitespace()

        return_type = self.parse_type()
        self.consume_whitespace()
        parameters = self.parse_parenthesized_parameters()
        self.assert_specific(";")

        return CallbackFunction(
            name=name,
            path=self.path,
            return_type=return_type,
            parameters=parameters,
            extended_attributes=extended_attributes,
        )

    def parse_typedef(self) -> Typedef:
        typedef_type = self.parse_type()
        self.consume_whitespace()

        name = self.parse_identifier()
        self.consume_whitespace()
        self.assert_specific(";")

        return Typedef(name=name, path=self.path, type=typedef_type)

    def parse_type(self) -> IDLType:
        if self.lexer.consume_specific("("):
            return self.parse_union_type()

        type_name = self.parse_type_name()
        parameters: List[IDLType] = []
        if self.lexer.next_is("<"):
            parameters = self.parse_type_parameters()

        nullable = self.lexer.consume_specific("?")
        if parameters:
            return IDLParameterizedType(type_name, parameters, nullable)
        return IDLType(type_name, nullable)

    def parse_union_type(self) -> IDLUnionType:
        member_types = [self.parse_type()]
        self.consume_whitespace()
        self.consume_keyword("or")
        self.consume_whitespace()
        member_types.append(self.parse_type())
        self.consume_whitespace()

        while self.lexer.consume_specific("or"):
            self.consume_whitespace()
            member_types.append(self.parse_type())
            self.consume_whitespace()

        self.assert_specific(")")
        return IDLUnionType(member_types, self.lexer.consume_specific("?"))

    def parse_type_name(self) -> str:
        type_words: List[str] = []
        if self.consume_optional_keyword("unsigned"):
            type_words.append("unsigned")
        if self.consume_optional_keyword("unrestricted"):
            type_words.append("unrestricted")

        name = self.lexer.consume_while(lambda character: character.isalnum() or character == "_")
        if not name:
            self.raise_parse_error("type can't be an empty string")
        type_words.append(name)

        if name.lower() == "long":
            position_before_whitespace = self.lexer.tell()
            self.consume_whitespace()
            if self.lexer.consume_specific("long"):
                type_words.append("long")
            else:
                self.lexer.position = position_before_whitespace

        return " ".join(type_words)

    def parse_named_body_header(self) -> Tuple[str, str]:
        name = self.parse_identifier()
        self.consume_whitespace()

        parent_name = ""
        if self.lexer.consume_specific(":"):
            self.consume_whitespace()
            parent_name = self.parse_identifier()
            self.consume_whitespace()

        self.assert_specific("{")
        return name, parent_name

    def parse_enumeration(self, extended_attributes: Dict[str, str]) -> Enumeration:
        name = self.parse_identifier()
        self.consume_whitespace()
        self.assert_specific("{")

        values: List[str] = []

        while not self.lexer.is_eof():
            self.consume_whitespace()
            if self.lexer.consume_specific("}"):
                break

            self.assert_specific('"')
            value = self.lexer.consume_until(lambda character: character == '"')
            self.assert_specific('"')
            self.consume_whitespace()

            values.append(value)

            self.consume_whitespace()
            if self.lexer.next_is("}"):
                continue
            self.assert_specific(",")

        self.consume_whitespace()
        self.assert_specific(";")

        return Enumeration(
            name=name,
            path=self.path,
            values=values,
            extended_attributes=extended_attributes,
        )

    def parse_interface_body(self, interface: Interface) -> None:
        while not self.lexer.is_eof():
            self.consume_whitespace()
            if self.lexer.consume_specific("}"):
                return

            extended_attributes = self.parse_leading_extended_attributes()
            self.consume_whitespace()

            if self.next_is_keyword("const"):
                interface.constants.append(self.parse_constant())
                continue

            if self.consume_optional_keyword("static"):
                readonly = self.consume_optional_keyword("readonly")
                if readonly or self.next_is_keyword("attribute"):
                    interface.static_attributes.append(self.parse_attribute(extended_attributes, readonly=readonly))
                else:
                    interface.static_operations.append(self.parse_operation(extended_attributes))
                continue

            if self.consume_optional_keyword("stringifier"):
                readonly = self.consume_optional_keyword("readonly")
                if readonly or self.next_is_keyword("attribute"):
                    attribute = self.parse_attribute(extended_attributes, readonly=readonly)
                    interface.regular_attributes.append(attribute)
                    interface.stringifier = Stringifier(extended_attributes, attribute)
                    continue

                self.consume_member_semicolon()
                interface.stringifier = Stringifier(extended_attributes)
                continue

            if self.consume_optional_keyword("inherit"):
                interface.regular_attributes.append(self.parse_attribute(extended_attributes, inherit=True))
                continue

            readonly = self.consume_optional_keyword("readonly")
            if self.next_is_keyword("attribute"):
                interface.regular_attributes.append(
                    self.parse_attribute(
                        extended_attributes,
                        readonly=readonly,
                    )
                )
                continue

            if self.next_is_keyword("maplike"):
                interface.maplike = self.parse_maplike_declaration(readonly=readonly)
                continue

            if self.next_is_keyword("setlike"):
                interface.setlike = self.parse_setlike_declaration(readonly=readonly)
                continue

            if readonly:
                interface.regular_attributes.append(self.parse_attribute(extended_attributes, readonly=True))
                continue

            if self.next_is_keyword("constructor"):
                interface.constructors.append(self.parse_constructor(extended_attributes))
                continue

            if self.next_is_keyword("iterable"):
                interface.iterable = self.parse_iterable_declaration()
                continue

            if self.next_is_keyword("async"):
                interface.async_iterable = self.parse_async_iterable_declaration()
                continue

            if self.next_is_keyword("getter"):
                special_operation = self.parse_special_operation("getter", extended_attributes)
                identifier_type = special_operation.identifier_type

                if identifier_type.name == "DOMString" and not identifier_type.nullable:
                    interface.named_property_getter = special_operation
                elif identifier_type.name == "unsigned long" and not identifier_type.nullable:
                    interface.indexed_property_getter = special_operation
                else:
                    self.raise_parse_error(
                        f"named/indexed property getter must use DOMString or unsigned long, got '{identifier_type}'"
                    )
                continue

            if self.next_is_keyword("setter"):
                special_operation = self.parse_special_operation("setter", extended_attributes)
                identifier_type = special_operation.identifier_type

                if identifier_type.name == "DOMString" and not identifier_type.nullable:
                    interface.named_property_setter = special_operation
                elif identifier_type.name == "unsigned long" and not identifier_type.nullable:
                    interface.indexed_property_setter = special_operation
                else:
                    self.raise_parse_error(
                        f"named/indexed property setter must use DOMString or unsigned long, got '{identifier_type}'"
                    )
                continue

            if self.next_is_keyword("deleter"):
                special_operation = self.parse_special_operation("deleter", extended_attributes)
                identifier_type = special_operation.identifier_type

                if identifier_type.name == "DOMString" and not identifier_type.nullable:
                    interface.named_property_deleter = special_operation
                else:
                    self.raise_parse_error(f"named property deleter must use DOMString, got '{identifier_type}'")
                continue

            interface.regular_operations.append(self.parse_operation(extended_attributes))

        self.raise_parse_error("unterminated interface body")

    def parse_iterable_declaration(self) -> IterableDeclaration:
        self.consume_keyword("iterable")
        key_type, value_type = self.parse_iterable_type_parameters()
        self.consume_member_semicolon()

        return IterableDeclaration(
            value_type=value_type,
            key_type=key_type,
        )

    def parse_async_iterable_declaration(self) -> AsyncIterableDeclaration:
        self.consume_keyword("async")
        self.consume_whitespace()
        self.consume_keyword("iterable")
        key_type, value_type = self.parse_iterable_type_parameters()
        self.consume_whitespace()

        parameters: List[OperationParameter] = []
        if self.lexer.next_is("("):
            parameters = self.parse_parenthesized_parameters()

        self.consume_member_semicolon()

        return AsyncIterableDeclaration(
            value_type=value_type,
            key_type=key_type,
            parameters=parameters,
        )

    def parse_iterable_type_parameters(self) -> Tuple[Optional[IDLType], IDLType]:
        parameters = self.parse_type_parameters()
        if len(parameters) == 1:
            return None, parameters[0]
        if len(parameters) == 2:
            return parameters[0], parameters[1]
        self.raise_parse_error("expected one or two iterable type parameters")

    def parse_type_parameters(self) -> List[IDLType]:
        self.assert_specific("<")
        parameters = [self.parse_type()]
        self.consume_whitespace()

        while self.lexer.consume_specific(","):
            self.consume_whitespace()
            parameters.append(self.parse_type())
            self.consume_whitespace()

        self.assert_specific(">")
        return parameters

    def parse_maplike_declaration(self, readonly: bool = False) -> MaplikeDeclaration:
        self.consume_keyword("maplike")
        parameters = self.parse_type_parameters()
        if len(parameters) != 2:
            self.raise_parse_error("expected two maplike type parameters")
        self.consume_member_semicolon()

        return MaplikeDeclaration(
            key_type=parameters[0],
            value_type=parameters[1],
            readonly=readonly,
        )

    def parse_setlike_declaration(self, readonly: bool = False) -> SetlikeDeclaration:
        self.consume_keyword("setlike")
        parameters = self.parse_type_parameters()
        if len(parameters) != 1:
            self.raise_parse_error("expected one setlike type parameter")
        self.consume_member_semicolon()

        return SetlikeDeclaration(
            value_type=parameters[0],
            readonly=readonly,
        )

    def parse_special_operation(
        self,
        keyword: str,
        extended_attributes: Dict[str, str],
    ) -> SpecialOperation:
        self.consume_keyword(keyword)
        self.consume_whitespace()

        return_type = self.parse_type()
        self.consume_whitespace()

        name = ""
        if not self.lexer.next_is("("):
            name = self.parse_identifier()
            self.consume_whitespace()

        parameters = self.parse_parenthesized_parameters()
        self.consume_member_semicolon()

        if not parameters:
            self.raise_parse_error("special operation must have an identifier parameter")

        return SpecialOperation(
            identifier_type=parameters[0].type,
            return_type=return_type,
            name=name,
            parameters=parameters,
            extended_attributes=extended_attributes,
        )

    def parse_parenthesized_parameters(self) -> List[OperationParameter]:
        self.assert_specific("(")
        self.consume_whitespace()

        parameters: List[OperationParameter] = []
        if not self.lexer.next_is(")"):
            parameters = self.parse_operation_parameters()
            self.consume_whitespace()

        self.assert_specific(")")
        self.consume_whitespace()
        return parameters

    def parse_operation_parameters(self) -> List[OperationParameter]:
        parameters: List[OperationParameter] = []

        self.consume_whitespace()
        while not self.lexer.next_is(")"):
            extended_attributes = self.parse_leading_extended_attributes()

            optional = self.consume_optional_keyword("optional")

            extended_attributes.update(self.parse_leading_extended_attributes())

            parameter_type = self.parse_type()
            self.consume_whitespace()

            variadic = self.lexer.consume_specific("...")
            self.consume_whitespace()

            parameter_name = self.parse_identifier()

            self.consume_whitespace()
            default_value: Optional[str] = None
            if self.lexer.consume_specific("="):
                default_value = self.consume_until_top_level(",", ")").strip()
                self.consume_whitespace()
            parameters.append(
                OperationParameter(
                    name=parameter_name,
                    type=parameter_type,
                    optional=optional,
                    default_value=default_value,
                    variadic=variadic,
                    extended_attributes=extended_attributes,
                )
            )
            if not self.lexer.consume_specific(","):
                break
            self.consume_whitespace()

        return parameters

    def parse_operation(self, extended_attributes: Dict[str, str]) -> Operation:
        return_type = self.parse_type()
        self.consume_whitespace()
        name = self.parse_identifier()
        self.consume_whitespace()
        parameters = self.parse_parenthesized_parameters()
        self.consume_member_semicolon()

        return Operation(
            name=name,
            return_type=return_type,
            parameters=parameters,
            extended_attributes=extended_attributes,
        )

    def parse_constructor(self, extended_attributes: Dict[str, str]) -> Constructor:
        self.consume_keyword("constructor")
        self.consume_whitespace()
        parameters = self.parse_parenthesized_parameters()
        self.consume_member_semicolon()

        return Constructor(
            parameters=parameters,
            extended_attributes=extended_attributes,
        )

    def parse_constant(self) -> Constant:
        self.consume_keyword("const")
        self.consume_whitespace()

        constant_type = self.parse_type()
        self.consume_whitespace()

        name = self.parse_identifier()
        self.consume_whitespace()
        self.assert_specific("=")
        self.consume_whitespace()

        value = self.consume_until_top_level(";").strip()
        self.consume_member_semicolon()

        return Constant(
            type=constant_type,
            name=name,
            value=value,
        )

    def parse_attribute(
        self,
        extended_attributes: Dict[str, str],
        readonly: bool = False,
        inherit: bool = False,
    ) -> Attribute:
        self.consume_keyword("attribute")
        self.consume_whitespace()

        attribute_type = self.parse_type()
        self.consume_whitespace()

        name = self.parse_identifier()
        self.consume_member_semicolon()

        return Attribute(
            name=name,
            type=attribute_type,
            readonly=readonly,
            inherit=inherit,
            extended_attributes=extended_attributes,
        )

    def parse_extended_attributes(self) -> Dict[str, str]:
        extended_attributes: Dict[str, str] = {}

        while True:
            self.consume_whitespace()
            if self.lexer.consume_specific("]"):
                break

            name = self.parse_identifier()
            value = ""
            if self.lexer.consume_specific("="):
                value = self.consume_until_top_level(",", "]").strip()

            extended_attributes[name] = value
            self.lexer.consume_specific(",")

        self.consume_whitespace()
        return extended_attributes

    def parse_leading_extended_attributes(self) -> Dict[str, str]:
        self.consume_whitespace()
        if self.lexer.consume_specific("["):
            return self.parse_extended_attributes()
        return {}

    def consume_member_semicolon(self) -> None:
        self.consume_whitespace()
        self.assert_specific(";")

    def consume_until_top_level(self, *terminators: str) -> str:
        start = self.lexer.tell()
        delimiter_stack: List[str] = []
        active_quote = ""
        matching_delimiter = {
            "(": ")",
            "[": "]",
            "{": "}",
            "<": ">",
        }

        while not self.lexer.is_eof():
            character = self.lexer.peek()

            if active_quote:
                self.lexer.consume()
                if character == active_quote:
                    active_quote = ""
                continue

            if character in ('"', "'"):
                active_quote = character
                self.lexer.consume()
                continue

            if character in terminators and not delimiter_stack:
                return self.contents[start : self.lexer.tell()]

            character = self.lexer.consume()
            if character in matching_delimiter:
                delimiter_stack.append(matching_delimiter[character])
            elif delimiter_stack and character == delimiter_stack[-1]:
                delimiter_stack.pop()

        return self.contents[start : self.lexer.tell()]

    def parse_identifier(self) -> str:
        identifier = self.lexer.consume_while(lambda character: character.isalnum() or character in "_-")
        if not identifier:
            self.raise_parse_error("expected identifier")
        return identifier.lstrip("_")

    def consume_whitespace(self) -> None:
        consumed = True
        while consumed:
            consumed = False

            whitespace = self.lexer.consume_while(str.isspace)
            if whitespace:
                consumed = True

            if self.lexer.consume_specific("//"):
                self.lexer.ignore_until("\n")
                if self.lexer.peek() == "\n":
                    self.lexer.ignore()
                consumed = True

    def next_is_keyword(self, keyword: str) -> bool:
        if not self.lexer.next_is(keyword):
            return False

        end = self.lexer.tell() + len(keyword)
        if end >= len(self.contents):
            return True

        trailing_character = self.contents[end]
        return trailing_character.isspace() or trailing_character in "{([;:<"

    def consume_optional_keyword(self, keyword: str) -> bool:
        if not self.next_is_keyword(keyword):
            return False

        self.consume_keyword(keyword)
        self.consume_whitespace()
        return True

    def consume_keyword(self, keyword: str) -> None:
        if not self.next_is_keyword(keyword):
            self.raise_parse_error(f"expected '{keyword}'")
        self.lexer.ignore(len(keyword))

    def assert_specific(self, expected_character: str) -> None:
        if not self.lexer.consume_specific(expected_character):
            self.raise_parse_error(f"expected '{expected_character}'")

    def raise_parse_error(self, message: str) -> NoReturn:
        line_number = 1
        column_number = 1

        for index, character in enumerate(self.contents):
            if index == self.lexer.tell():
                break
            if character == "\n":
                line_number += 1
                column_number = 1
            else:
                column_number += 1

        raise ParseError(f"{self.path}:{line_number}:{column_number}: error: {message}")
