2026-04-26 07:28:56 -03:00
|
|
|
# Copyright (c) 2026-present, the Ladybird developers.
|
|
|
|
|
#
|
|
|
|
|
# SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from dataclasses import field
|
2026-06-06 11:56:13 -03:00
|
|
|
from enum import Enum
|
2026-04-26 07:28:56 -03:00
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Dict
|
|
|
|
|
from typing import List
|
2026-04-29 23:37:35 -03:00
|
|
|
from typing import NoReturn
|
2026-04-26 07:28:56 -03:00
|
|
|
from typing import Optional
|
2026-06-06 11:56:13 -03:00
|
|
|
from typing import Sequence
|
|
|
|
|
from typing import Tuple
|
2026-04-26 07:28:56 -03:00
|
|
|
|
|
|
|
|
from Utils.lexer import Lexer
|
|
|
|
|
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
@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
|
|
|
|
|
|
|
|
|
|
|
2026-04-26 07:28:56 -03:00
|
|
|
@dataclass
|
|
|
|
|
class Constant:
|
2026-06-06 11:56:13 -03:00
|
|
|
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)
|
2026-04-26 07:28:56 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class SpecialOperation:
|
2026-06-06 11:56:13 -03:00
|
|
|
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"
|
2026-04-26 07:28:56 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class Interface:
|
|
|
|
|
name: str
|
|
|
|
|
path: Path
|
|
|
|
|
extended_attributes: Dict[str, str] = field(default_factory=dict)
|
2026-06-06 11:56:13 -03:00
|
|
|
kind: InterfaceKind = InterfaceKind.Interface
|
2026-04-26 07:28:56 -03:00
|
|
|
parent_name: str = ""
|
|
|
|
|
constants: List[Constant] = field(default_factory=list)
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
2026-04-26 07:28:56 -03:00
|
|
|
named_property_getter: Optional[SpecialOperation] = None
|
|
|
|
|
indexed_property_getter: Optional[SpecialOperation] = None
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
|
|
|
|
)
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
@property
|
|
|
|
|
def namespaced_name(self) -> str:
|
2026-04-26 07:28:56 -03:00
|
|
|
legacy_namespace = self.extended_attributes.get("LegacyNamespace")
|
|
|
|
|
if legacy_namespace:
|
2026-06-06 11:56:13 -03:00
|
|
|
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"
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
@property
|
|
|
|
|
def supports_named_properties(self) -> bool:
|
|
|
|
|
return self.named_property_getter is not None
|
2026-04-26 07:28:56 -03:00
|
|
|
|
|
|
|
|
|
2026-05-15 04:28:23 -03:00
|
|
|
@dataclass
|
|
|
|
|
class Dictionary:
|
|
|
|
|
name: str
|
|
|
|
|
path: Path
|
2026-06-06 11:56:13 -03:00
|
|
|
parent_name: str = ""
|
|
|
|
|
members: List["DictionaryMember"] = field(default_factory=list)
|
|
|
|
|
extended_attributes: Dict[str, str] = field(default_factory=dict)
|
2026-05-15 04:28:23 -03:00
|
|
|
|
|
|
|
|
|
2026-04-26 07:28:56 -03:00
|
|
|
@dataclass
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
2026-04-26 07:28:56 -03:00
|
|
|
path: Path
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
2026-04-26 07:28:56 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2026-06-06 11:56:13 -03:00
|
|
|
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)
|
2026-04-26 07:28:56 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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():
|
2026-06-06 11:56:13 -03:00
|
|
|
self.parse_module_declaration(module, self.parse_leading_extended_attributes())
|
2026-04-26 07:28:56 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
|
|
|
|
|
return module
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
if self.consume_optional_keyword("partial dictionary"):
|
|
|
|
|
module.partial_dictionaries.append(self.parse_dictionary(extended_attributes))
|
|
|
|
|
return
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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()
|
2026-04-26 07:28:56 -03:00
|
|
|
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()
|
2026-06-06 11:56:13 -03:00
|
|
|
mixin_name = self.parse_identifier()
|
2026-04-26 07:28:56 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
self.assert_specific(";")
|
2026-06-06 11:56:13 -03:00
|
|
|
return IncludedMixin(interface_name=interface_name, mixin_name=mixin_name)
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
def parse_interface_declaration(
|
|
|
|
|
self, extended_attributes: Dict[str, str], kind: InterfaceKind = InterfaceKind.Interface
|
2026-04-26 07:28:56 -03:00
|
|
|
) -> Interface:
|
2026-06-06 11:56:13 -03:00
|
|
|
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:
|
2026-04-26 07:28:56 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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("=")
|
2026-04-26 07:28:56 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
return_type = self.parse_type()
|
|
|
|
|
self.consume_whitespace()
|
|
|
|
|
parameters = self.parse_parenthesized_parameters()
|
|
|
|
|
self.assert_specific(";")
|
|
|
|
|
|
|
|
|
|
return CallbackFunction(
|
|
|
|
|
name=name,
|
2026-04-26 07:28:56 -03:00
|
|
|
path=self.path,
|
2026-06-06 11:56:13 -03:00
|
|
|
return_type=return_type,
|
|
|
|
|
parameters=parameters,
|
2026-04-26 07:28:56 -03:00
|
|
|
extended_attributes=extended_attributes,
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
def parse_typedef(self) -> Typedef:
|
|
|
|
|
typedef_type = self.parse_type()
|
2026-04-26 07:28:56 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
name = self.parse_identifier()
|
2026-04-26 07:28:56 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
self.assert_specific(";")
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
return Typedef(name=name, path=self.path, type=typedef_type)
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
def parse_type(self) -> IDLType:
|
|
|
|
|
if self.lexer.consume_specific("("):
|
|
|
|
|
return self.parse_union_type()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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)
|
2026-05-15 04:28:23 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
def parse_union_type(self) -> IDLUnionType:
|
|
|
|
|
member_types = [self.parse_type()]
|
|
|
|
|
self.consume_whitespace()
|
|
|
|
|
self.consume_keyword("or")
|
2026-05-15 04:28:23 -03:00
|
|
|
self.consume_whitespace()
|
2026-06-06 11:56:13 -03:00
|
|
|
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")
|
2026-05-15 04:28:23 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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()
|
2026-05-15 04:28:23 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
parent_name = ""
|
2026-05-15 04:28:23 -03:00
|
|
|
if self.lexer.consume_specific(":"):
|
|
|
|
|
self.consume_whitespace()
|
2026-06-06 11:56:13 -03:00
|
|
|
parent_name = self.parse_identifier()
|
2026-05-15 04:28:23 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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(",")
|
|
|
|
|
|
2026-05-15 04:28:23 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
self.assert_specific(";")
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
2026-05-15 04:28:23 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
2026-05-15 04:28:23 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
self.consume_member_semicolon()
|
|
|
|
|
interface.stringifier = Stringifier(extended_attributes)
|
2026-04-26 07:28:56 -03:00
|
|
|
continue
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
if self.consume_optional_keyword("inherit"):
|
|
|
|
|
interface.regular_attributes.append(self.parse_attribute(extended_attributes, inherit=True))
|
2026-04-26 07:28:56 -03:00
|
|
|
continue
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
readonly = self.consume_optional_keyword("readonly")
|
|
|
|
|
if self.next_is_keyword("attribute"):
|
|
|
|
|
interface.regular_attributes.append(
|
|
|
|
|
self.parse_attribute(
|
|
|
|
|
extended_attributes,
|
|
|
|
|
readonly=readonly,
|
|
|
|
|
)
|
|
|
|
|
)
|
2026-04-26 07:28:56 -03:00
|
|
|
continue
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
if self.next_is_keyword("maplike"):
|
|
|
|
|
interface.maplike = self.parse_maplike_declaration(readonly=readonly)
|
|
|
|
|
continue
|
2026-05-09 08:26:13 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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:
|
2026-04-26 07:28:56 -03:00
|
|
|
interface.named_property_getter = special_operation
|
2026-06-06 11:56:13 -03:00
|
|
|
elif identifier_type.name == "unsigned long" and not identifier_type.nullable:
|
2026-04-26 07:28:56 -03:00
|
|
|
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}'"
|
|
|
|
|
)
|
2026-05-09 08:26:13 -03:00
|
|
|
continue
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
if self.next_is_keyword("setter"):
|
|
|
|
|
special_operation = self.parse_special_operation("setter", extended_attributes)
|
|
|
|
|
identifier_type = special_operation.identifier_type
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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())
|
2026-04-26 07:28:56 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
self.assert_specific(">")
|
|
|
|
|
return parameters
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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)
|
2026-04-26 07:28:56 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
return_type = self.parse_type()
|
|
|
|
|
self.consume_whitespace()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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(","):
|
2026-04-26 07:28:56 -03:00
|
|
|
break
|
2026-06-06 11:56:13 -03:00
|
|
|
self.consume_whitespace()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
return parameters
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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,
|
|
|
|
|
)
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
return Constructor(
|
|
|
|
|
parameters=parameters,
|
|
|
|
|
extended_attributes=extended_attributes,
|
|
|
|
|
)
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
def parse_constant(self) -> Constant:
|
|
|
|
|
self.consume_keyword("const")
|
2026-04-26 07:28:56 -03:00
|
|
|
self.consume_whitespace()
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
constant_type = self.parse_type()
|
|
|
|
|
self.consume_whitespace()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
name = self.parse_identifier()
|
|
|
|
|
self.consume_whitespace()
|
|
|
|
|
self.assert_specific("=")
|
|
|
|
|
self.consume_whitespace()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
value = self.consume_until_top_level(";").strip()
|
|
|
|
|
self.consume_member_semicolon()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
return Constant(
|
|
|
|
|
type=constant_type,
|
|
|
|
|
name=name,
|
|
|
|
|
value=value,
|
|
|
|
|
)
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
def parse_attribute(
|
|
|
|
|
self,
|
|
|
|
|
extended_attributes: Dict[str, str],
|
|
|
|
|
readonly: bool = False,
|
|
|
|
|
inherit: bool = False,
|
|
|
|
|
) -> Attribute:
|
|
|
|
|
self.consume_keyword("attribute")
|
|
|
|
|
self.consume_whitespace()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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,
|
|
|
|
|
)
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
def parse_extended_attributes(self) -> Dict[str, str]:
|
|
|
|
|
extended_attributes: Dict[str, str] = {}
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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:
|
2026-04-26 07:28:56 -03:00
|
|
|
start = self.lexer.tell()
|
2026-06-06 11:56:13 -03:00
|
|
|
delimiter_stack: List[str] = []
|
2026-04-26 07:28:56 -03:00
|
|
|
active_quote = ""
|
2026-06-06 11:56:13 -03:00
|
|
|
matching_delimiter = {
|
|
|
|
|
"(": ")",
|
|
|
|
|
"[": "]",
|
|
|
|
|
"{": "}",
|
|
|
|
|
"<": ">",
|
|
|
|
|
}
|
2026-04-26 07:28:56 -03:00
|
|
|
|
|
|
|
|
while not self.lexer.is_eof():
|
2026-06-06 11:56:13 -03:00
|
|
|
character = self.lexer.peek()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
|
|
|
|
if active_quote:
|
2026-06-06 11:56:13 -03:00
|
|
|
self.lexer.consume()
|
2026-04-26 07:28:56 -03:00
|
|
|
if character == active_quote:
|
|
|
|
|
active_quote = ""
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if character in ('"', "'"):
|
|
|
|
|
active_quote = character
|
2026-06-06 11:56:13 -03:00
|
|
|
self.lexer.consume()
|
2026-04-26 07:28:56 -03:00
|
|
|
continue
|
|
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
if character in terminators and not delimiter_stack:
|
|
|
|
|
return self.contents[start : self.lexer.tell()]
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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()
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
return self.contents[start : self.lexer.tell()]
|
2026-04-26 07:28:56 -03:00
|
|
|
|
2026-06-06 11:56:13 -03:00
|
|
|
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")
|
2026-04-26 07:28:56 -03:00
|
|
|
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]
|
2026-06-06 11:56:13 -03:00
|
|
|
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
|
2026-04-26 07:28:56 -03:00
|
|
|
|
|
|
|
|
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}'")
|
|
|
|
|
|
2026-04-29 23:37:35 -03:00
|
|
|
def raise_parse_error(self, message: str) -> NoReturn:
|
2026-04-26 07:28:56 -03:00
|
|
|
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}")
|