diff --git a/Cargo.lock b/Cargo.lock index 397c2b5e..300b1cc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arrayref" @@ -1007,18 +1007,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" dependencies = [ "memchr", ] @@ -1352,18 +1352,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -1733,6 +1733,14 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "xml-to-wire" +version = "0.1.0" +dependencies = [ + "quick-xml", + "thiserror", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index bb3a346e..05379806 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ name = "jay" path = "src/main.rs" [workspace] -members = ["jay-config", "toml-config", "algorithms", "toml-spec", "wire-to-xml"] +members = ["jay-config", "toml-config", "algorithms", "toml-spec", "wire-to-xml", "xml-to-wire"] [profile.release] panic = "abort" diff --git a/xml-to-wire/Cargo.toml b/xml-to-wire/Cargo.toml new file mode 100644 index 00000000..055ec7d1 --- /dev/null +++ b/xml-to-wire/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "xml-to-wire" +version = "0.1.0" +edition = "2024" + +[dependencies] +quick-xml = "0.38.3" +thiserror = "2.0.16" diff --git a/xml-to-wire/src/ast.rs b/xml-to-wire/src/ast.rs new file mode 100644 index 00000000..b21b94bb --- /dev/null +++ b/xml-to-wire/src/ast.rs @@ -0,0 +1,80 @@ +pub(crate) struct Protocol { + pub(crate) _name: String, + pub(crate) _copyright: Option, + pub(crate) _description: Option, + pub(crate) interfaces: Vec, +} + +pub(crate) struct Copyright { + pub(crate) _body: String, +} + +#[derive(Debug)] +pub(crate) struct Description { + pub(crate) _summary: Option, + pub(crate) _body: String, +} + +pub(crate) struct Interface { + pub(crate) name: String, + pub(crate) _version: u32, + pub(crate) _description: Option, + pub(crate) messages: Vec, + pub(crate) _enums: Vec, +} + +#[derive(Debug)] +pub(crate) struct Arg { + pub(crate) name: String, + pub(crate) ty: ArgType, + pub(crate) _summary: Option, + pub(crate) _description: Option, + pub(crate) interface: Option, + pub(crate) allow_null: bool, + pub(crate) enum_: Option, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub(crate) enum ArgType { + NewId, + Int, + Uint, + Fixed, + String, + Object, + Array, + Fd, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub(crate) enum MessageType { + Destructor, +} + +pub(crate) struct Entry { + pub(crate) _name: String, + pub(crate) _value: String, + pub(crate) _value_u32: u32, + pub(crate) _summary: Option, + pub(crate) _since: Option, + pub(crate) _deprecated_since: Option, + pub(crate) _description: Option, +} + +pub(crate) struct Enum { + pub(crate) _name: String, + pub(crate) _since: Option, + pub(crate) _bitfield: bool, + pub(crate) _description: Option, + pub(crate) _entries: Vec, +} + +pub(crate) struct Message { + pub(crate) name: String, + pub(crate) request: bool, + pub(crate) ty: Option, + pub(crate) since: Option, + pub(crate) _deprecated_since: Option, + pub(crate) _description: Option, + pub(crate) args: Vec, +} diff --git a/xml-to-wire/src/builder.rs b/xml-to-wire/src/builder.rs new file mode 100644 index 00000000..ec7ce457 --- /dev/null +++ b/xml-to-wire/src/builder.rs @@ -0,0 +1,130 @@ +use { + crate::{ + ast::{ArgType, Message, MessageType}, + parser::{ParserError, parse}, + }, + std::{ + fs::File, + io::{self, BufWriter, Write}, + mem, + }, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum BuilderError { + #[error("Could not process {0}")] + File(String, #[source] Box), + #[error("Could not read file")] + ReadFile(#[source] io::Error), + #[error("Could not parse file")] + ParseFile(#[source] ParserError), + #[error("Could not format file")] + FormatFile(#[source] io::Error), + #[error("Could not open {0} for writing")] + OpenFile(String, #[source] io::Error), +} + +pub fn handle_file(path: &str) -> Result<(), BuilderError> { + handle_file_(path).map_err(|e| BuilderError::File(path.to_string(), Box::new(e))) +} + +fn handle_file_(path: &str) -> Result<(), BuilderError> { + let data = std::fs::read(path).map_err(BuilderError::ReadFile)?; + let protocols = parse(&data).map_err(BuilderError::ParseFile)?; + for protocol in protocols { + for i in protocol.interfaces { + let path = format!("wire/{}.txt", i.name); + let mut file = + BufWriter::new(File::create(&path).map_err(|e| BuilderError::OpenFile(path, e))?); + let mut not_first = false; + let mut handle_msg = |msg: &Message| -> Result<(), io::Error> { + if not_first { + writeln!(file)?; + } + not_first = true; + let ty = match msg.request { + true => "request", + false => "event", + }; + write!(file, "{ty} {} ", msg.name)?; + if msg.ty.is_some() || msg.since.is_some() { + write!(file, "(")?; + let mut needs_comma = false; + let handle_comma = + |needs_comma: &mut bool, file: &mut BufWriter| -> io::Result<()> { + if mem::take(needs_comma) { + write!(file, ", ")?; + } + Ok(()) + }; + if let Some(ty) = msg.ty { + match ty { + MessageType::Destructor => { + handle_comma(&mut needs_comma, &mut file)?; + write!(file, "destructor")?; + needs_comma = true; + } + } + } + if let Some(s) = msg.since { + handle_comma(&mut needs_comma, &mut file)?; + write!(file, "since = {}", s)?; + needs_comma = true; + } + let _ = needs_comma; + write!(file, ") ")?; + } + writeln!(file, "{{")?; + let mut args = msg.args.iter().peekable(); + while let Some(arg) = args.next() { + if arg.ty == ArgType::Uint + && arg.enum_.is_none() + && let Some(prefix) = arg.name.strip_suffix("_hi") + && let Some(next) = args.peek() + && next.ty == ArgType::Uint + && next.enum_.is_none() + && next.name.strip_prefix(prefix) == Some("_lo") + { + writeln!(file, " {prefix}: u64,")?; + args.next(); + continue; + } + write!(file, " {}: ", arg.name)?; + 'ty: { + let ty = match arg.ty { + ArgType::NewId | ArgType::Object => { + write!( + file, + "id({})", + arg.interface.as_deref().unwrap_or("object") + )?; + if arg.ty == ArgType::NewId { + write!(file, " (new)")?; + } + break 'ty; + } + ArgType::Int => "i32", + ArgType::Uint => "u32", + ArgType::Fixed => "fixed", + ArgType::String => match arg.allow_null { + true => "optstr", + false => "str", + }, + ArgType::Array => "array(pod(u8))", + ArgType::Fd => "fd", + }; + write!(file, "{}", ty)?; + } + writeln!(file, ",")?; + } + writeln!(file, "}}")?; + Ok(()) + }; + for m in &i.messages { + handle_msg(m).map_err(BuilderError::FormatFile)?; + } + } + } + Ok(()) +} diff --git a/xml-to-wire/src/main.rs b/xml-to-wire/src/main.rs new file mode 100644 index 00000000..f4cedfa6 --- /dev/null +++ b/xml-to-wire/src/main.rs @@ -0,0 +1,15 @@ +use { + crate::builder::{BuilderError, handle_file}, + std::env::args, +}; + +mod ast; +mod builder; +mod parser; + +fn main() -> Result<(), BuilderError> { + for xml in args().skip(1) { + handle_file(&xml)?; + } + Ok(()) +} diff --git a/xml-to-wire/src/parser.rs b/xml-to-wire/src/parser.rs new file mode 100644 index 00000000..8ecf8c55 --- /dev/null +++ b/xml-to-wire/src/parser.rs @@ -0,0 +1,577 @@ +use { + crate::ast::{ + Arg, ArgType, Copyright, Description, Entry, Enum, Interface, Message, MessageType, + Protocol, + }, + quick_xml::{ + Reader, + events::{ + Event, + attributes::{AttrError, Attribute, Attributes}, + }, + }, + std::{ + borrow::Cow, + num::ParseIntError, + str::{FromStr, ParseBoolError}, + string::FromUtf8Error, + }, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub(crate) enum ParserError { + #[error("Could not parse a protocol element")] + Protocol(#[from] ProtocolError), + #[error("Could not read the next event")] + ReadEvent(#[from] quick_xml::Error), +} + +#[derive(Debug, Error)] +pub(crate) enum ProtocolError { + #[error("Could not parse an attribute")] + Attribute(#[from] AttributeError), + #[error("Protocol does not have a name")] + MissingName, + #[error("Could not read the next event")] + ReadEvent(#[from] quick_xml::Error), + #[error("Could not the copyright element")] + Copyright(#[from] CopyrightError), + #[error("Could not parse the description element")] + Description(#[from] DescriptionError), + #[error("Could not parse an interface element")] + Interface(#[from] InterfaceError), +} + +#[derive(Debug, Error)] +pub(crate) enum CopyrightError { + #[error("Could not read the next event")] + ReadEvent(#[from] quick_xml::Error), + #[error("Could not decode the body as UTF-8")] + DecodeUtf8(#[source] FromUtf8Error), +} + +#[derive(Debug, Error)] +pub(crate) enum DescriptionError { + #[error("Could not parse an attribute")] + Attribute(#[from] AttributeError), + #[error("Could not read the next event")] + ReadEvent(#[from] quick_xml::Error), + #[error("Could not decode the body as UTF-8")] + DecodeUtf8(#[source] FromUtf8Error), +} + +#[derive(Debug, Error)] +pub(crate) enum InterfaceError { + #[error("Interface has no name")] + MissingName, + #[error("Interface has no version")] + MissingVersion, + #[error("Could not parse an attribute")] + Attribute(#[from] AttributeError), + #[error("Could not read the next event")] + ReadEvent(#[from] quick_xml::Error), + #[error("Could not parse the version")] + Version(#[source] ParseIntError), + #[error("Could not parse a request element")] + Request(#[source] MessageError), + #[error("Could not parse an event element")] + Event(#[source] MessageError), + #[error("Could not parse the description element")] + Description(#[from] DescriptionError), + #[error("Could not parse an enum element")] + Enum(#[from] EnumError), +} + +#[derive(Debug, Error)] +pub(crate) enum MessageError { + #[error("Message has no name")] + MissingName, + #[error("Could not parse an attribute")] + Attribute(#[from] AttributeError), + #[error("Could not read the next event")] + ReadEvent(#[from] quick_xml::Error), + #[error("Could not parse the since attribute")] + Since(#[source] ParseIntError), + #[error("Could not parse the deprecated-since attribute")] + DeprecatedSince(#[source] ParseIntError), + #[error("Unknown message type {}", .0)] + UnknownMessageType(String), + #[error("Could not parse an argument element")] + Arg(#[from] ArgError), + #[error("Could not the description element")] + Description(#[from] DescriptionError), +} + +#[derive(Debug, Error)] +pub(crate) enum ArgError { + #[error("Argument has no name")] + MissingName, + #[error("Argument has no type")] + MissingType, + #[error("Could not parse an attribute")] + Attribute(#[from] AttributeError), + #[error("Could not read the next event")] + ReadEvent(#[from] quick_xml::Error), + #[error("Could not parse the allow-null attribute")] + AllowNull(#[source] ParseBoolError), + #[error("Unknown arg type {}", .0)] + UnknownArgType(String), + #[error("Could not the description element")] + Description(#[from] DescriptionError), +} + +#[derive(Debug, Error)] +pub(crate) enum EnumError { + #[error("Enum has no name")] + MissingName, + #[error("Could not parse an attribute")] + Attribute(#[from] AttributeError), + #[error("Could not read the next event")] + ReadEvent(#[from] quick_xml::Error), + #[error("Could not parse the allow-null attribute")] + AllowNull(#[source] ParseBoolError), + #[error("Could not the description element")] + Description(#[from] DescriptionError), + #[error("Could not parse the since attribute")] + Since(#[source] ParseIntError), + #[error("Could not an entry element")] + Entry(#[from] EntryError), +} + +#[derive(Debug, Error)] +pub(crate) enum EntryError { + #[error("Entry has no name")] + MissingName, + #[error("Entry has no value")] + MissingValue, + #[error("Value could not be parsed")] + InvalidValue(#[source] ParseIntError), + #[error("Could not parse an attribute")] + Attribute(#[from] AttributeError), + #[error("Could not read the next event")] + ReadEvent(#[from] quick_xml::Error), + #[error("Could not the description element")] + Description(#[from] DescriptionError), + #[error("Could not parse the since attribute")] + Since(#[source] ParseIntError), + #[error("Could not parse the deprecated-since attribute")] + DeprecatedSince(#[source] ParseIntError), +} + +#[derive(Debug, Error)] +pub(crate) enum AttributeError { + #[error("quick_xml returned an error")] + QuickXml(#[from] AttrError), + #[error("Could not decode the value as UTF-8")] + DecodeUtf8(#[from] quick_xml::Error), +} + +pub(crate) fn parse(input: &[u8]) -> Result, ParserError> { + let mut reader = Reader::from_reader(input); + let mut protocols = Vec::new(); + loop { + let event = reader.read_event().map_err(ParserError::ReadEvent)?; + let (start, empty) = match event { + Event::Start(s) => (s, false), + Event::Empty(s) => (s, true), + Event::Eof => break, + _ => continue, + }; + match start.local_name().as_ref() { + b"protocol" => protocols.push(parse_protocol(&mut reader, start.attributes(), empty)?), + _ => continue, + } + } + Ok(protocols) +} + +macro_rules! parse_attr { + ($attr:expr) => { + match $attr { + Ok(ref attr) => parse_attr(attr), + Err(e) => return Err(AttributeError::QuickXml(e).into()), + } + }; +} + +fn parse_attr<'a>(attr: &'a Attribute) -> Result<(&'a [u8], Cow<'a, str>), AttributeError> { + let name = attr.key.local_name().into_inner(); + let value = attr.unescape_value().map_err(AttributeError::DecodeUtf8)?; + Ok((name, value)) +} + +fn parse_protocol( + reader: &mut Reader<&[u8]>, + attributes: Attributes, + empty: bool, +) -> Result { + let mut name = None; + for attr in attributes { + let (n, value) = parse_attr!(attr)?; + match n { + b"name" => name = Some(value.into_owned()), + _ => continue, + } + } + let mut copyright = None; + let mut description = None; + let mut interfaces = vec![]; + if !empty { + loop { + let event = reader.read_event().map_err(ProtocolError::ReadEvent)?; + let (start, empty) = match event { + Event::Start(s) => (s, false), + Event::End(_) => break, + Event::Empty(s) => (s, true), + _ => continue, + }; + match start.local_name().as_ref() { + b"copyright" => { + copyright = Some(parse_copyright(reader, start.attributes(), empty)?) + } + b"description" => { + description = Some(parse_description(reader, start.attributes(), empty)?) + } + b"interface" => { + interfaces.push(parse_interface(reader, start.attributes(), empty)?) + } + _ => continue, + } + } + } + Ok(Protocol { + _name: name.ok_or(ProtocolError::MissingName)?, + _copyright: copyright, + _description: description, + interfaces, + }) +} + +fn parse_copyright( + reader: &mut Reader<&[u8]>, + _attributes: Attributes, + empty: bool, +) -> Result { + let mut body = Vec::new(); + if !empty { + loop { + let event = reader.read_event().map_err(CopyrightError::ReadEvent)?; + match event { + Event::Text(s) => body.extend_from_slice(s.as_ref()), + Event::End(_) => break, + _ => continue, + } + } + } + Ok(Copyright { + _body: String::from_utf8(body).map_err(CopyrightError::DecodeUtf8)?, + }) +} + +fn parse_description( + reader: &mut Reader<&[u8]>, + attributes: Attributes, + empty: bool, +) -> Result { + let mut summary = None; + for attr in attributes { + let (n, value) = parse_attr!(attr)?; + match n { + b"summary" => summary = Some(value.into_owned()), + _ => continue, + } + } + let mut body = Vec::new(); + if !empty { + loop { + let event = reader.read_event().map_err(DescriptionError::ReadEvent)?; + match event { + Event::Text(s) => body.extend_from_slice(s.as_ref()), + Event::End(_) => break, + _ => continue, + } + } + } + Ok(Description { + _summary: summary, + _body: String::from_utf8(body).map_err(DescriptionError::DecodeUtf8)?, + }) +} + +fn parse_interface( + reader: &mut Reader<&[u8]>, + attributes: Attributes, + empty: bool, +) -> Result { + let mut name = None; + let mut version = None; + for attr in attributes { + let (n, value) = parse_attr!(attr)?; + match n { + b"name" => name = Some(value.into_owned()), + b"version" => version = Some(value.parse().map_err(InterfaceError::Version)?), + _ => continue, + } + } + let mut description = None; + let mut messages = Vec::new(); + let mut enums = Vec::new(); + if !empty { + loop { + let event = reader.read_event().map_err(InterfaceError::ReadEvent)?; + let (start, empty) = match event { + Event::Start(s) => (s, false), + Event::End(_) => break, + Event::Empty(s) => (s, true), + _ => continue, + }; + match start.local_name().as_ref() { + b"description" => { + description = Some(parse_description(reader, start.attributes(), empty)?) + } + b"request" => messages.push( + parse_message(reader, start.attributes(), empty, true) + .map_err(InterfaceError::Request)?, + ), + b"event" => messages.push( + parse_message(reader, start.attributes(), empty, false) + .map_err(InterfaceError::Event)?, + ), + b"enum" => enums.push(parse_enum(reader, start.attributes(), empty)?), + _ => continue, + } + } + } + Ok(Interface { + name: name.ok_or(InterfaceError::MissingName)?, + _version: version.ok_or(InterfaceError::MissingVersion)?, + _description: description, + messages, + _enums: enums, + }) +} + +fn parse_message( + reader: &mut Reader<&[u8]>, + attributes: Attributes, + empty: bool, + request: bool, +) -> Result { + let mut name = None; + let mut ty = None; + let mut since = None; + let mut deprecated_since = None; + for attr in attributes { + let (n, value) = parse_attr!(attr)?; + match n { + b"name" => name = Some(value.into_owned()), + b"type" => match value.as_ref() { + "destructor" => ty = Some(MessageType::Destructor), + _ => return Err(MessageError::UnknownMessageType(value.into_owned())), + }, + b"since" => since = Some(value.parse().map_err(MessageError::Since)?), + b"deprecated-since" => { + deprecated_since = Some(value.parse().map_err(MessageError::DeprecatedSince)?) + } + _ => continue, + } + } + let mut description = None; + let mut args = Vec::new(); + if !empty { + loop { + let event = reader.read_event().map_err(MessageError::ReadEvent)?; + let (start, empty) = match event { + Event::Start(s) => (s, false), + Event::End(_) => break, + Event::Empty(s) => (s, true), + _ => continue, + }; + match start.local_name().as_ref() { + b"description" => { + description = Some(parse_description(reader, start.attributes(), empty)?) + } + b"arg" => args.push(parse_arg(reader, start.attributes(), empty)?), + _ => continue, + } + } + } + Ok(Message { + name: name.ok_or(MessageError::MissingName)?, + request, + ty, + since, + _deprecated_since: deprecated_since, + _description: description, + args, + }) +} + +fn parse_arg( + reader: &mut Reader<&[u8]>, + attributes: Attributes, + empty: bool, +) -> Result { + let mut name = None; + let mut ty = None; + let mut summary = None; + let mut interface = None; + let mut allow_null = None; + let mut enum_ = None; + for attr in attributes { + let (n, value) = parse_attr!(attr)?; + match n { + b"name" => name = Some(value.into_owned()), + b"type" => { + ty = Some(match value.as_ref() { + "int" => ArgType::Int, + "uint" => ArgType::Uint, + "fixed" => ArgType::Fixed, + "string" => ArgType::String, + "array" => ArgType::Array, + "fd" => ArgType::Fd, + "new_id" => ArgType::NewId, + "object" => ArgType::Object, + _ => return Err(ArgError::UnknownArgType(value.into_owned())), + }) + } + b"summary" => summary = Some(value.into_owned()), + b"interface" => interface = Some(value.into_owned()), + b"allow-null" => allow_null = Some(value.parse().map_err(ArgError::AllowNull)?), + b"enum" => enum_ = Some(value.into_owned()), + _ => continue, + } + } + let mut description = None; + if !empty { + loop { + let event = reader.read_event().map_err(ArgError::ReadEvent)?; + let (start, empty) = match event { + Event::Start(s) => (s, false), + Event::End(_) => break, + Event::Empty(s) => (s, true), + _ => continue, + }; + match start.local_name().as_ref() { + b"description" => { + description = Some(parse_description(reader, start.attributes(), empty)?) + } + _ => continue, + } + } + } + Ok(Arg { + name: name.ok_or(ArgError::MissingName)?, + ty: ty.ok_or(ArgError::MissingType)?, + _summary: summary, + _description: description, + interface, + allow_null: allow_null.unwrap_or_default(), + enum_, + }) +} + +fn parse_enum( + reader: &mut Reader<&[u8]>, + attributes: Attributes, + empty: bool, +) -> Result { + let mut name = None; + let mut since = None; + let mut bitfield = None; + for attr in attributes { + let (n, v) = parse_attr!(attr)?; + match n { + b"name" => name = Some(v.into_owned()), + b"since" => since = Some(v.parse().map_err(EnumError::Since)?), + b"bitfield" => bitfield = Some(v.parse().map_err(EnumError::AllowNull)?), + _ => continue, + } + } + let mut description = None; + let mut entries = Vec::new(); + if !empty { + loop { + let event = reader.read_event().map_err(EnumError::ReadEvent)?; + let (start, empty) = match event { + Event::Start(s) => (s, false), + Event::End(_) => break, + Event::Empty(s) => (s, true), + _ => continue, + }; + match start.local_name().as_ref() { + b"description" => { + description = Some(parse_description(reader, start.attributes(), empty)?) + } + b"entry" => entries.push(parse_entry(reader, start.attributes(), empty)?), + _ => continue, + } + } + } + Ok(Enum { + _name: name.ok_or(EnumError::MissingName)?, + _since: since, + _bitfield: bitfield.unwrap_or_default(), + _description: description, + _entries: entries, + }) +} + +fn parse_entry( + reader: &mut Reader<&[u8]>, + attributes: Attributes, + empty: bool, +) -> Result { + let mut name = None; + let mut value = None; + let mut summary = None; + let mut since = None; + let mut deprecated_since = None; + for attr in attributes { + let (n, v) = parse_attr!(attr)?; + match n { + b"name" => name = Some(v.into_owned()), + b"value" => value = Some(v.into_owned()), + b"summary" => summary = Some(v.into_owned()), + b"since" => since = Some(v.parse().map_err(EntryError::Since)?), + b"deprecated-since" => { + deprecated_since = Some(v.parse().map_err(EntryError::DeprecatedSince)?) + } + _ => continue, + } + } + let mut description = None; + if !empty { + loop { + let event = reader.read_event().map_err(EntryError::ReadEvent)?; + let (start, empty) = match event { + Event::Start(s) => (s, false), + Event::End(_) => break, + Event::Empty(s) => (s, true), + _ => continue, + }; + match start.local_name().as_ref() { + b"description" => { + description = Some(parse_description(reader, start.attributes(), empty)?) + } + _ => continue, + } + } + } + let value = value.ok_or(EntryError::MissingValue)?; + let value_u32 = if let Some(value) = value.strip_prefix("0x") { + u32::from_str_radix(value, 16).map_err(EntryError::InvalidValue)? + } else { + u32::from_str(&value).map_err(EntryError::InvalidValue)? + }; + Ok(Entry { + _name: name.ok_or(EntryError::MissingName)?, + _value: value, + _value_u32: value_u32, + _summary: summary, + _since: since, + _deprecated_since: deprecated_since, + _description: description, + }) +}