wayland_scanner/
parse.rs

1use super::protocol::*;
2use std::{
3    io::{BufRead, BufReader, Read},
4    str::FromStr,
5};
6
7use quick_xml::{
8    events::{attributes::Attributes, Event},
9    Reader,
10};
11
12pub fn parse<S: Read>(stream: S) -> Protocol {
13    let mut reader = Reader::from_reader(BufReader::new(stream));
14    let reader_config = reader.config_mut();
15    reader_config.trim_text(true);
16    reader_config.expand_empty_elements = true;
17    parse_protocol(reader)
18}
19
20fn decode_utf8_or_panic(txt: Vec<u8>) -> String {
21    match String::from_utf8(txt) {
22        Ok(txt) => txt,
23        Err(e) => panic!("Invalid UTF8: '{}'", String::from_utf8_lossy(&e.into_bytes())),
24    }
25}
26
27fn parse_or_panic<T: FromStr>(txt: &[u8]) -> T {
28    match std::str::from_utf8(txt).ok().and_then(|val| val.parse().ok()) {
29        Some(version) => version,
30        None => panic!(
31            "Invalid value '{}' for parsing type '{}'",
32            String::from_utf8_lossy(txt),
33            std::any::type_name::<T>()
34        ),
35    }
36}
37
38fn init_protocol<R: BufRead>(reader: &mut Reader<R>) -> Protocol {
39    // Check two firsts lines for protocol tag
40    for _ in 0..3 {
41        match reader.read_event_into(&mut Vec::new()) {
42            Ok(Event::Decl(_) | Event::DocType(_)) => {
43                continue;
44            }
45            Ok(Event::Start(bytes)) => {
46                assert!(bytes.name().into_inner() == b"protocol", "Missing protocol toplevel tag");
47                if let Some(attr) = bytes
48                    .attributes()
49                    .filter_map(|res| res.ok())
50                    .find(|attr| attr.key.into_inner() == b"name")
51                {
52                    return Protocol::new(decode_utf8_or_panic(attr.value.into_owned()));
53                } else {
54                    panic!("Protocol must have a name");
55                }
56            }
57            _ => panic!("Ill-formed protocol file"),
58        }
59    }
60    panic!("Ill-formed protocol file");
61}
62
63fn parse_protocol<R: BufRead>(mut reader: Reader<R>) -> Protocol {
64    let mut protocol = init_protocol(&mut reader);
65
66    loop {
67        match reader.read_event_into(&mut Vec::new()) {
68            Ok(Event::Start(bytes)) => {
69                match bytes.name().into_inner() {
70                    b"copyright" => {
71                        // parse the copyright
72                        let mut copyright = String::new();
73                        loop {
74                            match reader.read_event_into(&mut Vec::new()) {
75                                Ok(Event::Text(text)) => {
76                                    if let Ok(text) = text.decode() {
77                                        copyright.push_str(&text);
78                                    }
79                                }
80                                Ok(Event::CData(cdata)) => {
81                                    if let Ok(cdata) = String::from_utf8(cdata.into_inner().into())
82                                    {
83                                        copyright.push_str(&cdata);
84                                    }
85                                }
86                                Ok(Event::GeneralRef(byte_ref)) => {
87                                    if let Ok(Some(c)) = byte_ref.resolve_char_ref() {
88                                        copyright.push(c);
89                                    } else if let Ok(content) = byte_ref.xml_content() {
90                                        if let Some(s) =
91                                            quick_xml::escape::resolve_xml_entity(&content)
92                                        {
93                                            copyright.push_str(s);
94                                        }
95                                    }
96                                }
97                                Ok(Event::End(bytes)) => {
98                                    assert!(
99                                        bytes.name().into_inner() == "copyright".as_bytes(),
100                                        "Ill-formed protocol file"
101                                    );
102                                    break;
103                                }
104                                e => {
105                                    panic!("Ill-formed protocol file: {e:?}");
106                                }
107                            }
108                        }
109
110                        protocol.copyright = Some(copyright)
111                    }
112                    b"interface" => {
113                        protocol.interfaces.push(parse_interface(&mut reader, bytes.attributes()));
114                    }
115                    b"description" => {
116                        protocol.description =
117                            Some(parse_description(&mut reader, bytes.attributes()));
118                    }
119                    name => panic!(
120                        "Ill-formed protocol file: unexpected token `{}` in protocol {}",
121                        String::from_utf8_lossy(name),
122                        protocol.name
123                    ),
124                }
125            }
126            Ok(Event::End(bytes)) => {
127                let name = bytes.name().into_inner();
128                assert!(
129                    name == b"protocol",
130                    "Unexpected closing token `{}`",
131                    String::from_utf8_lossy(name)
132                );
133                break;
134            }
135            // ignore comments
136            Ok(Event::Comment(_)) => {}
137            e => panic!("Ill-formed protocol file: unexpected token {e:?}"),
138        }
139    }
140
141    protocol
142}
143
144fn parse_interface<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Interface {
145    let mut interface = Interface::new();
146    for attr in attrs.filter_map(|res| res.ok()) {
147        match attr.key.into_inner() {
148            b"name" => interface.name = decode_utf8_or_panic(attr.value.into_owned()),
149            b"version" => interface.version = parse_or_panic(&attr.value),
150            _ => {}
151        }
152    }
153
154    loop {
155        match reader.read_event_into(&mut Vec::new()) {
156            Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
157                b"description" => {
158                    interface.description = Some(parse_description(reader, bytes.attributes()))
159                }
160                b"request" => interface.requests.push(parse_request(reader, bytes.attributes())),
161                b"event" => interface.events.push(parse_event(reader, bytes.attributes())),
162                b"enum" => interface.enums.push(parse_enum(reader, bytes.attributes())),
163                name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
164            },
165            Ok(Event::End(bytes)) if bytes.name().into_inner() == b"interface" => break,
166            _ => {}
167        }
168    }
169
170    interface
171}
172
173fn parse_description<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> (String, String) {
174    let mut summary = String::new();
175    for attr in attrs.filter_map(|res| res.ok()) {
176        if attr.key.into_inner() == b"summary" {
177            summary = String::from_utf8_lossy(&attr.value)
178                .split_whitespace()
179                .collect::<Vec<_>>()
180                .join(" ");
181        }
182    }
183
184    let mut description = String::new();
185    // Some protocols have comments inside their descriptions, so we need to parse them in a loop and
186    // concatenate the parts into a single block of text
187    loop {
188        match reader.read_event_into(&mut Vec::new()) {
189            Ok(Event::Text(bytes)) => {
190                if !description.is_empty() {
191                    description.push_str("\n\n");
192                }
193                description.push_str(&bytes.decode().unwrap_or_default())
194            }
195            Ok(Event::End(bytes)) if bytes.name().into_inner() == b"description" => break,
196            Ok(Event::Comment(_)) => {}
197            e => panic!("Ill-formed protocol file: {e:?}"),
198        }
199    }
200
201    (summary, description)
202}
203
204fn parse_request<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Message {
205    let mut request = Message::new();
206    for attr in attrs.filter_map(|res| res.ok()) {
207        match attr.key.into_inner() {
208            b"name" => request.name = decode_utf8_or_panic(attr.value.into_owned()),
209            b"type" => request.typ = Some(parse_type(&attr.value)),
210            b"since" => request.since = parse_or_panic(&attr.value),
211            _ => {}
212        }
213    }
214
215    loop {
216        match reader.read_event_into(&mut Vec::new()) {
217            Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
218                b"description" => {
219                    request.description = Some(parse_description(reader, bytes.attributes()))
220                }
221                b"arg" => request.args.push(parse_arg(reader, bytes.attributes())),
222                name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
223            },
224            Ok(Event::End(bytes)) if bytes.name().into_inner() == b"request" => break,
225            _ => {}
226        }
227    }
228
229    request
230}
231
232fn parse_enum<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Enum {
233    let mut enu = Enum::new();
234    for attr in attrs.filter_map(|res| res.ok()) {
235        match attr.key.into_inner() {
236            b"name" => enu.name = decode_utf8_or_panic(attr.value.into_owned()),
237            b"since" => enu.since = parse_or_panic(&attr.value),
238            b"bitfield" => {
239                if &attr.value[..] == b"true" {
240                    enu.bitfield = true
241                }
242            }
243            _ => {}
244        }
245    }
246
247    loop {
248        match reader.read_event_into(&mut Vec::new()) {
249            Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
250                b"description" => {
251                    enu.description = Some(parse_description(reader, bytes.attributes()))
252                }
253                b"entry" => enu.entries.push(parse_entry(reader, bytes.attributes())),
254                name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
255            },
256            Ok(Event::End(bytes)) if bytes.name().into_inner() == b"enum" => break,
257            _ => {}
258        }
259    }
260
261    enu
262}
263
264fn parse_event<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Message {
265    let mut event = Message::new();
266    for attr in attrs.filter_map(|res| res.ok()) {
267        match attr.key.into_inner() {
268            b"name" => event.name = decode_utf8_or_panic(attr.value.into_owned()),
269            b"type" => event.typ = Some(parse_type(&attr.value)),
270            b"since" => event.since = parse_or_panic(&attr.value),
271            _ => {}
272        }
273    }
274
275    loop {
276        match reader.read_event_into(&mut Vec::new()) {
277            Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
278                b"description" => {
279                    event.description = Some(parse_description(reader, bytes.attributes()))
280                }
281                b"arg" => event.args.push(parse_arg(reader, bytes.attributes())),
282                name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
283            },
284            Ok(Event::End(bytes)) if bytes.name().into_inner() == b"event" => break,
285            _ => {}
286        }
287    }
288
289    event
290}
291
292fn parse_arg<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Arg {
293    let mut arg = Arg::new();
294    for attr in attrs.filter_map(|res| res.ok()) {
295        match attr.key.into_inner() {
296            b"name" => arg.name = decode_utf8_or_panic(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            }
306            b"interface" => arg.interface = Some(parse_or_panic(&attr.value)),
307            b"allow-null" => {
308                if &*attr.value == b"true" {
309                    arg.allow_null = true
310                }
311            }
312            b"enum" => arg.enum_ = Some(decode_utf8_or_panic(attr.value.into_owned())),
313            _ => {}
314        }
315    }
316
317    loop {
318        match reader.read_event_into(&mut Vec::new()) {
319            Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
320                b"description" => {
321                    arg.description = Some(parse_description(reader, bytes.attributes()))
322                }
323                name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
324            },
325            Ok(Event::End(bytes)) if bytes.name().into_inner() == b"arg" => break,
326            _ => {}
327        }
328    }
329
330    arg
331}
332
333fn parse_type(txt: &[u8]) -> Type {
334    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 => panic!("Unexpected type: {}", String::from_utf8_lossy(e)),
345    }
346}
347
348fn parse_entry<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Entry {
349    let mut entry = Entry::new();
350    for attr in attrs.filter_map(|res| res.ok()) {
351        match attr.key.into_inner() {
352            b"name" => entry.name = decode_utf8_or_panic(attr.value.into_owned()),
353            b"value" => {
354                entry.value = if attr.value.starts_with(b"0x") {
355                    if let Some(val) = std::str::from_utf8(&attr.value[2..])
356                        .ok()
357                        .and_then(|s| u32::from_str_radix(s, 16).ok())
358                    {
359                        val
360                    } else {
361                        panic!("Invalid number: {}", String::from_utf8_lossy(&attr.value))
362                    }
363                } else {
364                    parse_or_panic(&attr.value)
365                };
366            }
367            b"since" => entry.since = parse_or_panic(&attr.value),
368            b"summary" => {
369                entry.summary = Some(
370                    String::from_utf8_lossy(&attr.value)
371                        .split_whitespace()
372                        .collect::<Vec<_>>()
373                        .join(" "),
374                )
375            }
376            _ => {}
377        }
378    }
379
380    loop {
381        match reader.read_event_into(&mut Vec::new()) {
382            Ok(Event::Start(bytes)) => match bytes.name().into_inner() {
383                b"description" => {
384                    entry.description = Some(parse_description(reader, bytes.attributes()))
385                }
386                name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)),
387            },
388            Ok(Event::End(bytes)) if bytes.name().into_inner() == b"entry" => break,
389            _ => {}
390        }
391    }
392
393    entry
394}
395
396#[cfg(test)]
397mod tests {
398    #[test]
399    fn xml_parse() {
400        let protocol_file =
401            std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap();
402        let _ = crate::parse::parse(protocol_file);
403    }
404
405    #[test]
406    fn headerless_xml_parse() {
407        let protocol_file =
408            std::fs::File::open("./tests/scanner_assets/test-headerless-protocol.xml").unwrap();
409        let _ = crate::parse::parse(protocol_file);
410    }
411}