Skip to main content

microcom_codegen/
parser.rs

1//! Hand-written recursive-descent parser for MicroCom IDL.
2
3use std::fmt;
4
5use crate::ast::*;
6
7#[derive(Debug, Clone, PartialEq)]
8pub struct ParseError {
9    pub line: usize,
10    pub column: usize,
11    pub message: String,
12}
13
14impl fmt::Display for ParseError {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        write!(f, "{}:{}: {}", self.line, self.column, self.message)
17    }
18}
19
20impl std::error::Error for ParseError {}
21
22pub fn parse(source: &str) -> Result<Idl, ParseError> {
23    let mut p = Parser { src: source, pos: 0 };
24    p.parse_file()
25}
26
27struct Parser<'a> {
28    src: &'a str,
29    pos: usize,
30}
31
32fn is_ident_char(c: char) -> bool {
33    c.is_ascii_alphanumeric() || c == '_'
34}
35
36impl<'a> Parser<'a> {
37    fn rest(&self) -> &'a str {
38        &self.src[self.pos..]
39    }
40
41    fn peek(&self) -> Option<char> {
42        self.rest().chars().next()
43    }
44
45    fn eof(&self) -> bool {
46        self.pos >= self.src.len()
47    }
48
49    fn error<T>(&self, message: impl Into<String>) -> Result<T, ParseError> {
50        let before = &self.src[..self.pos.min(self.src.len())];
51        let line = before.matches('\n').count() + 1;
52        let column = before.rfind('\n').map_or(self.pos, |i| self.pos - i - 1) + 1;
53        Err(ParseError { line, column, message: message.into() })
54    }
55
56    /// Skips whitespace, `// ...` and `/* ... */`.
57    fn skip_trivia(&mut self) -> Result<(), ParseError> {
58        loop {
59            let rest = self.rest();
60            if let Some(c) = rest.chars().next() {
61                if c.is_whitespace() {
62                    self.pos += c.len_utf8();
63                    continue;
64                }
65            }
66            if rest.starts_with("//") {
67                self.pos += rest.find('\n').unwrap_or(rest.len());
68                continue;
69            }
70            if rest.starts_with("/*") {
71                match rest.find("*/") {
72                    Some(end) => self.pos += end + 2,
73                    None => return self.error("unterminated comment"),
74                }
75                continue;
76            }
77            return Ok(());
78        }
79    }
80
81    fn eat(&mut self, c: char) -> Result<bool, ParseError> {
82        self.skip_trivia()?;
83        if self.peek() == Some(c) {
84            self.pos += c.len_utf8();
85            Ok(true)
86        } else {
87            Ok(false)
88        }
89    }
90
91    fn expect(&mut self, c: char) -> Result<(), ParseError> {
92        if self.eat(c)? {
93            Ok(())
94        } else {
95            let found = self.peek().map_or("end of file".to_string(), |c| format!("'{c}'"));
96            self.error(format!("expected '{c}', found {found}"))
97        }
98    }
99
100    fn try_ident(&mut self) -> Result<Option<String>, ParseError> {
101        self.skip_trivia()?;
102        let rest = self.rest();
103        let len = rest.find(|c| !is_ident_char(c)).unwrap_or(rest.len());
104        if len == 0 {
105            return Ok(None);
106        }
107        self.pos += len;
108        Ok(Some(rest[..len].to_string()))
109    }
110
111    fn ident(&mut self, what: &str) -> Result<String, ParseError> {
112        match self.try_ident()? {
113            Some(i) => Ok(i),
114            None => self.error(format!("expected {what}")),
115        }
116    }
117
118    fn parse_file(&mut self) -> Result<Idl, ParseError> {
119        let mut idl = Idl::default();
120        loop {
121            self.skip_trivia()?;
122            if self.eof() {
123                return Ok(idl);
124            }
125            if self.peek() == Some('@') {
126                idl.directives.push(self.parse_directive()?);
127                continue;
128            }
129            let attributes = self.parse_attributes()?;
130            let keyword = self.ident("'enum', 'struct' or 'interface'")?;
131            match keyword.as_str() {
132                "enum" => idl.enums.push(self.parse_enum(attributes)?),
133                "struct" => idl.structs.push(self.parse_struct(attributes)?),
134                "interface" => idl.interfaces.push(self.parse_interface(attributes)?),
135                other => return self.error(format!("unexpected '{other}'")),
136            }
137        }
138    }
139
140    /// `@name value-to-end-of-line` or `@name @@ multi-line text @@`.
141    fn parse_directive(&mut self) -> Result<Directive, ParseError> {
142        self.pos += 1; // '@'
143        let rest = self.rest();
144        let name_len = rest.find(|c: char| c.is_whitespace()).unwrap_or(rest.len());
145        let name = rest[..name_len].to_string();
146        if name.is_empty() {
147            return self.error("expected directive name after '@'");
148        }
149        self.pos += name_len;
150        let line_end = self.rest().find('\n').unwrap_or(self.rest().len());
151        let line = &self.rest()[..line_end];
152        if let Some(start) = line.find("@@") {
153            self.pos += start + 2;
154            let Some(end) = self.rest().find("@@") else {
155                return self.error("unterminated '@@' block");
156            };
157            let value = self.rest()[..end].trim().replace("\r\n", "\n");
158            self.pos += end + 2;
159            Ok(Directive { name, value })
160        } else {
161            let value = line.trim().to_string();
162            self.pos += line_end;
163            Ok(Directive { name, value })
164        }
165    }
166
167    /// Zero or more `[a, b(value)]` groups.
168    fn parse_attributes(&mut self) -> Result<Vec<Attribute>, ParseError> {
169        let mut out = Vec::new();
170        while self.eat('[')? {
171            let rest = self.rest();
172            let Some(end) = rest.find(']') else {
173                return self.error("unterminated attribute list");
174            };
175            let body = &rest[..end];
176            self.pos += end + 1;
177            // Split on commas that are not inside parentheses.
178            let mut depth = 0;
179            let mut start = 0;
180            let mut parts = Vec::new();
181            for (i, c) in body.char_indices() {
182                match c {
183                    '(' => depth += 1,
184                    ')' => depth -= 1,
185                    ',' if depth == 0 => {
186                        parts.push(&body[start..i]);
187                        start = i + 1;
188                    }
189                    _ => {}
190                }
191            }
192            parts.push(&body[start..]);
193            for part in parts {
194                let part = part.trim();
195                if part.is_empty() {
196                    continue;
197                }
198                match part.find('(') {
199                    Some(open) if part.ends_with(')') => out.push(Attribute {
200                        name: part[..open].trim().to_string(),
201                        value: Some(part[open + 1..part.len() - 1].trim().to_string()),
202                    }),
203                    _ => out.push(Attribute { name: part.to_string(), value: None }),
204                }
205            }
206        }
207        Ok(out)
208    }
209
210    fn parse_enum(&mut self, attributes: Vec<Attribute>) -> Result<Enum, ParseError> {
211        let name = self.ident("enum name")?;
212        self.expect('{')?;
213        let mut members = Vec::new();
214        loop {
215            if self.eat('}')? {
216                break;
217            }
218            let member = self.ident("enum member name")?;
219            let value = if self.eat('=')? {
220                self.skip_trivia()?;
221                let rest = self.rest();
222                let Some(end) = rest.find([',', '}']) else {
223                    return self.error("unterminated enum");
224                };
225                let text = rest[..end].trim();
226                if text.is_empty() {
227                    return self.error("expected enum member value");
228                }
229                self.pos += end;
230                Some(text.to_string())
231            } else {
232                None
233            };
234            members.push(EnumMember { name: member, value });
235            if !self.eat(',')? {
236                self.expect('}')?;
237                break;
238            }
239        }
240        self.eat(';')?;
241        Ok(Enum { attributes, name, members })
242    }
243
244    fn parse_struct(&mut self, attributes: Vec<Attribute>) -> Result<Struct, ParseError> {
245        let name = self.ident("struct name")?;
246        self.expect('{')?;
247        let mut fields = Vec::new();
248        while !self.eat('}')? {
249            // `type name[, name...];`
250            let (ty, first) = self.parse_type_and_name("field")?;
251            fields.push(Field { ty: ty.clone(), name: first });
252            while self.eat(',')? {
253                let n = self.ident("field name")?;
254                fields.push(Field { ty: ty.clone(), name: n });
255            }
256            self.expect(';')?;
257        }
258        self.eat(';')?;
259        Ok(Struct { attributes, name, fields })
260    }
261
262    /// Parses `ident+ ('*')* ('&')? ident` where the final identifier is the
263    /// declared name and everything before it is the type.
264    fn parse_type_and_name(&mut self, what: &str) -> Result<(TypeRef, String), ParseError> {
265        let mut words = vec![self.ident(&format!("{what} type"))?];
266        let mut pointers = 0;
267        let mut reference = false;
268        loop {
269            if self.eat('*')? {
270                if reference {
271                    return self.error("pointer to reference is not supported");
272                }
273                pointers += 1;
274            } else if self.eat('&')? {
275                reference = true;
276            } else if let Some(word) = self.try_ident()? {
277                if pointers > 0 || reference {
278                    // First identifier after the pointer/reference markers is the name.
279                    let ty = TypeRef { name: words.join(" "), pointers, reference };
280                    return Ok((ty, word));
281                }
282                words.push(word);
283            } else {
284                break;
285            }
286        }
287        if pointers > 0 || reference || words.len() < 2 {
288            return self.error(format!("expected {what} name"));
289        }
290        let name = words.pop().unwrap();
291        Ok((TypeRef { name: words.join(" "), pointers, reference }, name))
292    }
293
294    fn parse_interface(&mut self, attributes: Vec<Attribute>) -> Result<Interface, ParseError> {
295        let name = self.ident("interface name")?;
296        let base = if self.eat(':')? { Some(self.ident("base interface name")?) } else { None };
297        self.expect('{')?;
298        let mut methods = Vec::new();
299        while !self.eat('}')? {
300            let method_attrs = self.parse_attributes()?;
301            let (return_type, method_name) = self.parse_type_and_name("method")?;
302            self.expect('(')?;
303            let mut params = Vec::new();
304            if !self.eat(')')? {
305                loop {
306                    let param_attrs = self.parse_attributes()?;
307                    let (ty, pname) = self.parse_type_and_name("parameter")?;
308                    params.push(Param { attributes: param_attrs, ty, name: pname });
309                    if self.eat(',')? {
310                        continue;
311                    }
312                    self.expect(')')?;
313                    break;
314                }
315            }
316            self.expect(';')?;
317            methods.push(Method { attributes: method_attrs, return_type, name: method_name, params });
318        }
319        self.eat(';')?;
320        Ok(Interface { attributes, name, base, methods })
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    const SAMPLE: &str = r#"
329@clr-namespace Foo.Bar
330@clr-map bool int
331@clr-map long IntPtr
332@cpp-preamble @@
333#pragma once
334#include "com.h"
335@@
336
337enum E { A, B = 5, C = A | B, }
338[class-enum]
339enum F { X = -1, Y }
340
341struct S { double X, Y; void* Data; byte B; }
342
343// line comment
344[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)]
345interface IRoot : IUnknown
346{
347    /** doc
348     * comment */
349    HRESULT Get(IRoot**ppv);
350    [intptr]void* Handle([intptr] void* a, [const] char* text, [const] S& s);
351    bool Flag();
352}
353
354[uuid(e5aca675-02b7-4129-aa79-d6e417210bda), cpp-virtual-inherits]
355interface IChild : IRoot { }
356
357[uuid(722aad20-a87b-4ce5-b50f-f05cfa4cda39)]
358interface INoBase { unsigned int Count(); }
359"#;
360
361    #[test]
362    fn parses_sample() {
363        let idl = parse(SAMPLE).unwrap();
364        assert_eq!(idl.directive("clr-namespace"), Some("Foo.Bar"));
365        assert_eq!(idl.directive_values("clr-map").collect::<Vec<_>>(), ["bool int", "long IntPtr"]);
366        assert_eq!(idl.directive("cpp-preamble"), Some("#pragma once\n#include \"com.h\""));
367
368        assert_eq!(idl.enums.len(), 2);
369        assert_eq!(idl.enums[0].members[1].value.as_deref(), Some("5"));
370        assert_eq!(idl.enums[0].members[2].value.as_deref(), Some("A | B"));
371        assert!(idl.enums[1].is_class_enum());
372        assert_eq!(idl.enums[1].members[0].value.as_deref(), Some("-1"));
373
374        let s = &idl.structs[0];
375        assert_eq!(s.fields.iter().map(|f| f.name.as_str()).collect::<Vec<_>>(), ["X", "Y", "Data", "B"]);
376        assert_eq!(s.fields[2].ty, TypeRef { name: "void".into(), pointers: 1, reference: false });
377
378        let root = &idl.interfaces[0];
379        assert_eq!(root.uuid(), Some("809c652e-7396-11d2-9771-00a0c9b4d50c"));
380        assert_eq!(root.methods.len(), 3);
381        assert_eq!(root.methods[0].params[0].ty.pointers, 2);
382        assert_eq!(root.methods[0].params[0].name, "ppv");
383        let handle = &root.methods[1];
384        assert!(has_attr(&handle.attributes, "intptr"));
385        assert_eq!(handle.return_type.pointers, 1);
386        assert!(handle.params[1].is_const());
387        assert!(handle.params[2].ty.reference);
388
389        assert!(idl.interfaces[1].cpp_virtual_inherits());
390        assert_eq!(idl.interfaces[1].base.as_deref(), Some("IRoot"));
391        assert_eq!(idl.interfaces[2].base, None);
392        assert_eq!(idl.interfaces[2].methods[0].return_type.name, "unsigned int");
393    }
394
395    #[test]
396    fn reports_position() {
397        let err = parse("enum E {\n  A = ,\n}").unwrap_err();
398        assert_eq!(err.line, 2);
399    }
400}