mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2026-05-05 15:58:02 +02:00
scanner: allow for cross-referencing enums
Add another run to the XML parser so we parse enums before Arguments that cross-reference enums from other interfaces. This also fixes a type bug - the enum name string was passed to Argument.create() as Enum and no-one noticed.
This commit is contained in:
parent
eecf69af48
commit
1db37c33d5
1 changed files with 103 additions and 57 deletions
160
proto/ei-scanner
160
proto/ei-scanner
|
|
@ -299,6 +299,16 @@ class Interface:
|
|||
mode: str = attr.ib() # "ei" or "eis"
|
||||
description: Optional[Description] = attr.ib(default=None)
|
||||
|
||||
@staticmethod
|
||||
def mangle_name(name: str, component: str) -> str:
|
||||
"""
|
||||
Returns the mangled interface name with the component as prefix (e.g. eis_device).
|
||||
The XML only uses `ei_` as prefix, so let's replace that accordingly.
|
||||
"""
|
||||
if name.startswith("ei"):
|
||||
return f"{component}{name[2:]}"
|
||||
return name
|
||||
|
||||
def add_request(self, request: Request) -> None:
|
||||
if request.name in [r.name for r in self.requests]:
|
||||
raise ValueError(f"Duplicate request name '{request.name}'")
|
||||
|
|
@ -314,6 +324,12 @@ class Interface:
|
|||
raise ValueError(f"Duplicate enum name '{enum.name}'")
|
||||
self.enums.append(enum)
|
||||
|
||||
def find_enum(self, name: str) -> Optional[Enum]:
|
||||
for e in self.enums:
|
||||
if e.name == name:
|
||||
return e
|
||||
return None
|
||||
|
||||
@property
|
||||
def outgoing(self) -> list[Message]:
|
||||
"""
|
||||
|
|
@ -454,6 +470,63 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
if self._run_counter <= 1:
|
||||
return
|
||||
|
||||
if element == "enum":
|
||||
if self.current_interface is None:
|
||||
raise XmlError.create(
|
||||
f"Invalid element '{element}' outside an <interface>",
|
||||
self.location,
|
||||
)
|
||||
if self.current_message is not None:
|
||||
raise XmlError.create(
|
||||
f"Invalid element '{element}' inside '{self.current_message.name}'",
|
||||
self.location,
|
||||
)
|
||||
try:
|
||||
name = attrs["name"]
|
||||
since = attrs["since"]
|
||||
except KeyError as e:
|
||||
raise XmlError.create(
|
||||
f"Missing attribute {e} in element '{element}'",
|
||||
self.location,
|
||||
)
|
||||
|
||||
try:
|
||||
is_bitfield = {
|
||||
"true": True,
|
||||
"false": False,
|
||||
}[attrs.get("bitfield", "false")]
|
||||
except KeyError as e:
|
||||
raise XmlError.create(
|
||||
f"Invalid value {e} for boolean bitfield attribute in '{element}'",
|
||||
self.location,
|
||||
)
|
||||
|
||||
# We only create the enum on the second run, in subsequent runs
|
||||
# we re-use them so we can cross-reference correctly
|
||||
if self._run_counter > 2:
|
||||
enum = self.current_interface.find_enum(name)
|
||||
if enum is None:
|
||||
raise XmlError.create(
|
||||
f"Invalid enum {name}. This is a parser bug",
|
||||
self.location,
|
||||
)
|
||||
else:
|
||||
enum = Enum.create(
|
||||
name=name,
|
||||
since=since,
|
||||
interface=self.current_interface,
|
||||
is_bitfield=is_bitfield,
|
||||
)
|
||||
try:
|
||||
self.current_interface.add_enum(enum)
|
||||
except ValueError as e:
|
||||
raise XmlError.create(str(e), self.location)
|
||||
self.current_message = enum
|
||||
|
||||
# second run only parses enums
|
||||
if self._run_counter <= 2:
|
||||
return
|
||||
|
||||
if element == "request":
|
||||
if self.current_interface is None:
|
||||
raise XmlError.create(
|
||||
|
|
@ -517,48 +590,6 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
except ValueError as e:
|
||||
raise XmlError.create(str(e), self.location)
|
||||
self.current_message = event
|
||||
elif element == "enum":
|
||||
if self.current_interface is None:
|
||||
raise XmlError.create(
|
||||
f"Invalid element '{element}' outside an <interface>",
|
||||
self.location,
|
||||
)
|
||||
if self.current_message is not None:
|
||||
raise XmlError.create(
|
||||
f"Invalid element '{element}' inside '{self.current_message.name}'",
|
||||
self.location,
|
||||
)
|
||||
try:
|
||||
name = attrs["name"]
|
||||
since = attrs["since"]
|
||||
except KeyError as e:
|
||||
raise XmlError.create(
|
||||
f"Missing attribute {e} in element '{element}'",
|
||||
self.location,
|
||||
)
|
||||
|
||||
try:
|
||||
is_bitfield = {
|
||||
"true": True,
|
||||
"false": False,
|
||||
}[attrs.get("bitfield", "false")]
|
||||
except KeyError as e:
|
||||
raise XmlError.create(
|
||||
f"Invalid value {e} for boolean bitfield attribute in '{element}'",
|
||||
self.location,
|
||||
)
|
||||
|
||||
enum = Enum.create(
|
||||
name=name,
|
||||
since=since,
|
||||
interface=self.current_interface,
|
||||
is_bitfield=is_bitfield,
|
||||
)
|
||||
try:
|
||||
self.current_interface.add_enum(enum)
|
||||
except ValueError as e:
|
||||
raise XmlError.create(str(e), self.location)
|
||||
self.current_message = enum
|
||||
elif element == "arg":
|
||||
if self.current_interface is None:
|
||||
raise XmlError.create(
|
||||
|
|
@ -581,19 +612,28 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
summary = attrs.get("summary", "")
|
||||
interface_name = attrs.get("interface", None)
|
||||
if interface_name is not None:
|
||||
if interface_name.startswith("ei"):
|
||||
interface_name = f"{self.component}{interface_name[2:]}"
|
||||
interface = self.interface_by_name(interface_name)
|
||||
interface = self.interface_by_name(
|
||||
Interface.mangle_name(interface_name, self.component)
|
||||
)
|
||||
else:
|
||||
interface = None
|
||||
enum = attrs.get("enum", None)
|
||||
if enum is not None and enum not in [
|
||||
e.name for e in self.current_interface.enums
|
||||
]:
|
||||
raise XmlError.create(
|
||||
f"Failed to find enum '{self.current_interface.name}.{enum}'",
|
||||
self.location,
|
||||
)
|
||||
enum_name = attrs.get("enum", None)
|
||||
enum = None
|
||||
if enum_name is not None:
|
||||
if "." in enum_name:
|
||||
iname, enum_name = enum_name.split(".")
|
||||
intf = self.interface_by_name(
|
||||
Interface.mangle_name(iname, self.component)
|
||||
)
|
||||
else:
|
||||
intf = self.current_interface
|
||||
|
||||
enum = intf.find_enum(enum_name)
|
||||
if enum is None:
|
||||
raise XmlError.create(
|
||||
f"Failed to find enum '{intf.name}.{enum_name}'",
|
||||
self.location,
|
||||
)
|
||||
arg = Argument.create(
|
||||
name=name,
|
||||
protocol_type=proto_type,
|
||||
|
|
@ -647,15 +687,20 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
if self._run_counter <= 1:
|
||||
return
|
||||
|
||||
if name == "enum":
|
||||
assert isinstance(self.current_message, Enum)
|
||||
self.current_message = None
|
||||
|
||||
# second run only parses interfaces and enums
|
||||
if self._run_counter <= 2:
|
||||
return
|
||||
|
||||
if name == "request":
|
||||
assert isinstance(self.current_message, Request)
|
||||
self.current_message = None
|
||||
elif name == "event":
|
||||
assert isinstance(self.current_message, Event)
|
||||
self.current_message = None
|
||||
elif name == "enum":
|
||||
assert isinstance(self.current_message, Enum)
|
||||
self.current_message = None
|
||||
elif name == "description":
|
||||
assert self.current_description is not None
|
||||
self.current_description.text = dedent(self.current_description.text)
|
||||
|
|
@ -685,7 +730,8 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
|
|||
def parse(protofile: Path, component: str) -> Protocol:
|
||||
proto = ProtocolParser.create(component=component)
|
||||
xml.sax.parse(os.fspath(protofile), proto)
|
||||
# We parse two times, once to fetch all the interfaces, then to parse the details
|
||||
# We parse three times, once to fetch all the interfaces, one for enums, then to parse the details
|
||||
xml.sax.parse(os.fspath(protofile), proto)
|
||||
xml.sax.parse(os.fspath(protofile), proto)
|
||||
copyright = proto.copyright.text if proto.copyright else None
|
||||
return Protocol(copyright=copyright, interfaces=proto.interfaces)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue