runa_wayland_spec_parser/
parse.rs

1use std::io::{BufRead, BufReader, Read};
2
3use quick_xml::{
4    events::{attributes::Attributes, Event},
5    reader::Reader,
6};
7use thiserror::Error;
8
9use super::protocol::*;
10#[derive(Error, Debug)]
11pub enum Error {
12    #[error("Ill-formed protocol file")]
13    IllFormed,
14    #[error("Protocol must have a name")]
15    NoName,
16    #[error("Invalid UTF-8: {0}")]
17    FromUtf8(#[from] std::string::FromUtf8Error),
18    #[error("Invalid UTF-8: {0}")]
19    Utf8(#[from] std::str::Utf8Error),
20    #[error("Invalid number: {0}")]
21    ParseInt(#[from] std::num::ParseIntError),
22    #[error("Invalid XML")]
23    Xml(#[from] quick_xml::Error),
24    #[error("Unexpected token: `{0}`")]
25    UnexpectedToken(String),
26    #[error("Unexpected type: `{0}`")]
27    UnexpectedType(String),
28    #[error("Unexpected end tag, expected: `{expected}`, got: `{actual}`")]
29    UnexpectedEndTag { expected: String, actual: String },
30}
31type Result<T, E = Error> = std::result::Result<T, E>;
32
33fn extract_end_tag<R: BufRead>(reader: &mut Reader<R>, tag: &[u8]) -> Result<()> {
34    match reader.read_event_into(&mut Vec::new())? {
35        Event::End(bytes) =>
36            if bytes.name().local_name().as_ref() != tag {
37                return Err(Error::UnexpectedEndTag {
38                    expected: String::from_utf8_lossy(tag).into_owned(),
39                    actual:   String::from_utf8_lossy(bytes.name().local_name().as_ref())
40                        .into_owned(),
41                })
42            },
43        other => return Err(Error::UnexpectedToken(format!("{other:?}"))),
44    }
45    Ok(())
46}
47pub fn parse<S: Read>(stream: S) -> Result<Protocol> {
48    let mut reader = Reader::from_reader(BufReader::new(stream));
49    reader.trim_text(true).expand_empty_elements(true);
50    // Skip first <?xml ... ?> event
51    let _ = reader.read_event_into(&mut Vec::new());
52    parse_protocol(reader)
53}
54
55fn parse_protocol<R: BufRead>(mut reader: Reader<R>) -> Result<Protocol> {
56    let mut protocol = match reader.read_event_into(&mut Vec::new())? {
57        Event::Start(bytes) => {
58            assert!(
59                bytes.local_name().as_ref() == b"protocol",
60                "Missing protocol toplevel tag"
61            );
62            if let Some(attr) = bytes
63                .attributes()
64                .filter_map(|res| res.ok())
65                .find(|attr| attr.key.local_name().as_ref() == b"name")
66            {
67                Protocol::new(String::from_utf8(attr.value.into_owned())?)
68            } else {
69                return Err(Error::NoName)
70            }
71        },
72        other => return Err(Error::UnexpectedToken(format!("{other:?}"))),
73    };
74
75    loop {
76        match reader.read_event_into(&mut Vec::new()) {
77            Ok(Event::Start(bytes)) => {
78                match bytes.local_name().as_ref() {
79                    b"copyright" => {
80                        // parse the copyright
81                        let copyright = match reader.read_event_into(&mut Vec::new()) {
82                            Ok(Event::Text(copyright)) =>
83                                copyright.unescape().ok().map(std::borrow::Cow::into_owned),
84                            Ok(Event::CData(copyright)) =>
85                                String::from_utf8(copyright.into_inner().into()).ok(),
86                            Err(e) => return Err(e.into()),
87                            _ => return Err(Error::IllFormed),
88                        };
89
90                        extract_end_tag(&mut reader, b"copyright")?;
91                        protocol.copyright = copyright
92                    },
93                    b"interface" => {
94                        protocol
95                            .interfaces
96                            .push(parse_interface(&mut reader, bytes.attributes())?);
97                    },
98                    b"description" => {
99                        protocol.description =
100                            Some(parse_description(&mut reader, bytes.attributes())?);
101                    },
102                    _ =>
103                        return Err(Error::UnexpectedToken(String::from_utf8(
104                            bytes.local_name().as_ref().to_owned(),
105                        )?)),
106                }
107            },
108            Ok(Event::End(bytes)) => {
109                assert!(
110                    bytes.local_name().as_ref() == b"protocol",
111                    "Unexpected closing token `{}`",
112                    String::from_utf8_lossy(bytes.local_name().as_ref())
113                );
114                break
115            },
116            // ignore comments
117            Ok(Event::Comment(_)) => {},
118            e => return Err(Error::UnexpectedToken(format!("{e:?}"))),
119        }
120    }
121
122    Ok(protocol)
123}
124
125fn parse_interface<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Result<Interface> {
126    let mut interface = Interface::new();
127    for attr in attrs.filter_map(|res| res.ok()) {
128        match attr.key.local_name().as_ref() {
129            b"name" => interface.name = String::from_utf8(attr.value.into_owned())?,
130            b"version" => interface.version = std::str::from_utf8(attr.value.as_ref())?.parse()?,
131            _ => {},
132        }
133    }
134
135    loop {
136        match reader.read_event_into(&mut Vec::new()) {
137            Ok(Event::Start(bytes)) => match bytes.local_name().as_ref() {
138                b"description" =>
139                    interface.description = Some(parse_description(reader, bytes.attributes())?),
140                b"request" => interface.requests.push(parse_event_or_request(
141                    reader,
142                    bytes.attributes(),
143                    b"request",
144                )?),
145                b"event" => interface.events.push(parse_event_or_request(
146                    reader,
147                    bytes.attributes(),
148                    b"event",
149                )?),
150                b"enum" => interface
151                    .enums
152                    .push(parse_enum(reader, bytes.attributes())?),
153                _ =>
154                    return Err(Error::UnexpectedToken(String::from_utf8(
155                        bytes.local_name().as_ref().to_owned(),
156                    )?)),
157            },
158            Ok(Event::End(bytes)) if bytes.local_name().as_ref() == b"interface" => break,
159            _ => {},
160        }
161    }
162
163    Ok(interface)
164}
165
166fn parse_description<R: BufRead>(
167    reader: &mut Reader<R>,
168    attrs: Attributes,
169) -> Result<(String, String)> {
170    let mut summary = String::new();
171    for attr in attrs.filter_map(|res| res.ok()) {
172        if attr.key.local_name().as_ref() == b"summary" {
173            summary = String::from_utf8_lossy(&attr.value)
174                .split_whitespace()
175                .collect::<Vec<_>>()
176                .join(" ");
177        }
178    }
179
180    let mut description = String::new();
181    // Some protocols have comments inside their descriptions, so we need to parse
182    // them in a loop and concatenate the parts into a single block of text
183    loop {
184        match reader.read_event_into(&mut Vec::new()) {
185            Ok(Event::Text(bytes)) => {
186                if !description.is_empty() {
187                    description.push_str("\n\n");
188                }
189                description.push_str(&bytes.unescape().unwrap_or_default())
190            },
191            Ok(Event::End(bytes)) if bytes.local_name().as_ref() == b"description" => break,
192            Ok(Event::Comment(_)) => {},
193            Err(e) => return Err(e.into()),
194            e => return Err(Error::UnexpectedToken(format!("{e:?}"))),
195        }
196    }
197
198    Ok((summary, description))
199}
200
201fn parse_enum<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Result<Enum> {
202    let mut enu = Enum::new();
203    for attr in attrs.filter_map(|res| res.ok()) {
204        match attr.key.local_name().as_ref() {
205            b"name" => enu.name = String::from_utf8(attr.value.into_owned())?,
206            b"since" => enu.since = std::str::from_utf8(&attr.value)?.parse()?,
207            b"bitfield" =>
208                if &attr.value[..] == b"true" {
209                    enu.bitfield = true
210                },
211            _ => {},
212        }
213    }
214
215    loop {
216        match reader.read_event_into(&mut Vec::new()) {
217            Ok(Event::Start(bytes)) => match bytes.local_name().as_ref() {
218                b"description" =>
219                    enu.description = Some(parse_description(reader, bytes.attributes())?),
220                b"entry" => enu.entries.push(parse_entry(reader, bytes.attributes())?),
221                _ =>
222                    return Err(Error::UnexpectedToken(String::from_utf8(
223                        bytes.local_name().as_ref().to_owned(),
224                    )?)),
225            },
226            Ok(Event::End(bytes)) if bytes.local_name().as_ref() == b"enum" => break,
227            _ => {},
228        }
229    }
230
231    Ok(enu)
232}
233
234fn parse_event_or_request<R: BufRead>(
235    reader: &mut Reader<R>,
236    attrs: Attributes,
237    tag: &[u8],
238) -> Result<Message> {
239    let mut message = Message::new();
240    for attr in attrs.filter_map(|res| res.ok()) {
241        match attr.key.local_name().as_ref() {
242            b"name" => message.name = String::from_utf8(attr.value.into_owned())?,
243            b"type" => message.typ = Some(parse_type(&attr.value)?),
244            b"since" => message.since = std::str::from_utf8(&attr.value)?.parse()?,
245            _ => {},
246        }
247    }
248
249    loop {
250        match reader.read_event_into(&mut Vec::new()) {
251            Ok(Event::Start(bytes)) => match bytes.local_name().as_ref() {
252                b"description" =>
253                    message.description = Some(parse_description(reader, bytes.attributes())?),
254                b"arg" => {
255                    let arg = parse_arg(reader, bytes.attributes())?;
256                    if arg.typ == Type::NewId && arg.interface.is_none() {
257                        // Push implicit parameter: interface and version
258                        message.args.push(Arg {
259                            name:        "interface".to_string(),
260                            typ:         Type::String,
261                            interface:   None,
262                            summary:     Some("interface of the bound object".into()),
263                            description: None,
264                            allow_null:  false,
265                            enum_:       None,
266                        });
267                        message.args.push(Arg {
268                            name:        "version".to_string(),
269                            typ:         Type::Uint,
270                            interface:   None,
271                            summary:     Some("interface version of the bound object".into()),
272                            description: None,
273                            allow_null:  false,
274                            enum_:       None,
275                        });
276                    }
277                    message.args.push(arg);
278                },
279                _ =>
280                    return Err(Error::UnexpectedToken(String::from_utf8(
281                        bytes.local_name().as_ref().to_owned(),
282                    )?)),
283            },
284            Ok(Event::End(bytes)) if bytes.local_name().as_ref() == tag => break,
285            _ => {},
286        }
287    }
288
289    Ok(message)
290}
291
292fn parse_arg<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Result<Arg> {
293    let mut arg = Arg::new();
294    for attr in attrs.filter_map(|res| res.ok()) {
295        match attr.key.local_name().as_ref() {
296            b"name" => arg.name = String::from_utf8(attr.value.into_owned())?,
297            b"type" => arg.typ = parse_type(&attr.value)?,
298            b"summary" =>
299                arg.summary = Some(
300                    String::from_utf8_lossy(&attr.value)
301                        .split_whitespace()
302                        .collect::<Vec<_>>()
303                        .join(" "),
304                ),
305            b"interface" => arg.interface = Some(String::from_utf8(attr.value.into_owned())?),
306            b"allow-null" =>
307                if &*attr.value == b"true" {
308                    arg.allow_null = true
309                },
310            b"enum" => arg.enum_ = Some(String::from_utf8(attr.value.into_owned())?),
311            _ => {},
312        }
313    }
314
315    loop {
316        match reader.read_event_into(&mut Vec::new()) {
317            Ok(Event::Start(bytes)) => match bytes.local_name().as_ref() {
318                b"description" =>
319                    arg.description = Some(parse_description(reader, bytes.attributes())?),
320                _ =>
321                    return Err(Error::UnexpectedToken(String::from_utf8(
322                        bytes.local_name().as_ref().to_owned(),
323                    )?)),
324            },
325            Ok(Event::End(bytes)) if bytes.local_name().as_ref() == b"arg" => break,
326            _ => {},
327        }
328    }
329
330    Ok(arg)
331}
332
333fn parse_type(txt: &[u8]) -> Result<Type> {
334    Ok(match txt {
335        b"int" => Type::Int,
336        b"uint" => Type::Uint,
337        b"fixed" => Type::Fixed,
338        b"string" => Type::String,
339        b"object" => Type::Object,
340        b"new_id" => Type::NewId,
341        b"array" => Type::Array,
342        b"fd" => Type::Fd,
343        b"destructor" => Type::Destructor,
344        e =>
345            return Err(Error::UnexpectedType(
346                String::from_utf8_lossy(e).into_owned(),
347            )),
348    })
349}
350
351fn parse_entry<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Result<Entry> {
352    let mut entry = Entry::new();
353    for attr in attrs.filter_map(|res| res.ok()) {
354        match attr.key.local_name().as_ref() {
355            b"name" => entry.name = String::from_utf8(attr.value.into_owned())?,
356            b"value" => {
357                entry.value = if attr.value.starts_with(b"0x") {
358                    u32::from_str_radix(std::str::from_utf8(&attr.value[2..])?, 16)?
359                } else {
360                    std::str::from_utf8(&attr.value)?.parse()?
361                };
362            },
363            b"since" => entry.since = std::str::from_utf8(&attr.value)?.parse()?,
364            b"summary" =>
365                entry.summary = Some(
366                    String::from_utf8_lossy(&attr.value)
367                        .split_whitespace()
368                        .collect::<Vec<_>>()
369                        .join(" "),
370                ),
371            _ => {},
372        }
373    }
374
375    loop {
376        match reader.read_event_into(&mut Vec::new()) {
377            Ok(Event::Start(bytes)) => match bytes.local_name().as_ref() {
378                b"description" =>
379                    entry.description = Some(parse_description(reader, bytes.attributes())?),
380                _ =>
381                    return Err(Error::UnexpectedToken(
382                        String::from_utf8_lossy(bytes.local_name().as_ref()).into_owned(),
383                    )),
384            },
385            Ok(Event::End(bytes)) if bytes.local_name().as_ref() == b"entry" => break,
386            _ => {},
387        }
388    }
389
390    Ok(entry)
391}