Skip to main content

xidl_parser/
parser.rs

1use crate::error::ParserResult;
2use std::collections::{HashMap, HashSet};
3use tree_sitter::Node;
4
5pub struct ParseContext<'a> {
6    pub source: &'a [u8],
7    pub symbols: HashMap<String, String>,
8    doc_consumed: HashSet<usize>,
9}
10
11impl<'a> ParseContext<'a> {
12    pub fn new(source: &'a [u8]) -> Self {
13        Self {
14            source,
15            symbols: HashMap::new(),
16            doc_consumed: HashSet::new(),
17        }
18    }
19
20    pub fn node_text(&self, node: &Node) -> ParserResult<&str> {
21        Ok(node.utf8_text(self.source)?)
22    }
23
24    pub fn take_doc_comment(&mut self, node: &Node) -> Option<String> {
25        let start = node.start_byte();
26        if self.doc_consumed.contains(&start) {
27            return None;
28        }
29        let doc = self.extract_doc_comment(start);
30        if doc.is_some() {
31            self.doc_consumed.insert(start);
32        }
33        doc
34    }
35
36    fn extract_doc_comment(&self, start: usize) -> Option<String> {
37        if start == 0 {
38            return None;
39        }
40        let src = self.source;
41        let mut line_end = if start > 0 && src[start - 1] == b'\n' {
42            start - 1
43        } else {
44            start
45        };
46        let mut lines = Vec::new();
47        let mut first = true;
48        loop {
49            let mut line_start = 0;
50            if line_end > 0 {
51                let mut i = line_end;
52                while i > 0 && src[i - 1] != b'\n' {
53                    i -= 1;
54                }
55                line_start = i;
56            }
57            let mut line = &src[line_start..line_end];
58            if line.ends_with(b"\r") {
59                line = &line[..line.len() - 1];
60            }
61            if line.iter().all(|b| b.is_ascii_whitespace()) {
62                if first {
63                    if line_start == 0 {
64                        break;
65                    }
66                    line_end = line_start - 1;
67                    first = false;
68                    continue;
69                }
70                break;
71            }
72            first = false;
73            let mut idx = 0;
74            while idx < line.len() && line[idx].is_ascii_whitespace() {
75                idx += 1;
76            }
77            let trimmed = &line[idx..];
78            if trimmed.starts_with(b"///") {
79                let mut content = &trimmed[3..];
80                if content.first() == Some(&b' ') {
81                    content = &content[1..];
82                }
83                lines.push(String::from_utf8_lossy(content).to_string());
84                if line_start == 0 {
85                    break;
86                }
87                line_end = line_start - 1;
88                continue;
89            }
90            break;
91        }
92        if lines.is_empty() {
93            None
94        } else {
95            lines.reverse();
96            Some(lines.join("\n"))
97        }
98    }
99}
100
101pub trait FromTreeSitter<'a>: Sized {
102    fn from_node(node: Node<'a>, context: &mut ParseContext<'a>) -> ParserResult<Self>;
103}
104
105impl<'a> FromTreeSitter<'a> for String {
106    fn from_node(node: Node<'a>, context: &mut ParseContext<'a>) -> ParserResult<Self> {
107        Ok(context.node_text(&node)?.to_string())
108    }
109}
110
111impl<'a, T> FromTreeSitter<'a> for Box<T>
112where
113    T: FromTreeSitter<'a>,
114{
115    fn from_node(node: Node<'a>, context: &mut ParseContext<'a>) -> ParserResult<Self> {
116        Ok(Box::new(T::from_node(node, context)?))
117    }
118}
119
120pub fn parser_text(text: &str) -> ParserResult<crate::typed_ast::Specification> {
121    use crate::typed_ast::Specification;
122
123    let mut parser = tree_sitter::Parser::new();
124    parser.set_language(&tree_sitter_idl::language()).unwrap();
125
126    let tree = parser.parse(text, None).ok_or_else(|| {
127        crate::error::ParseError::TreeSitterError("Failed to parse text".to_string())
128    })?;
129
130    let root_node = tree.root_node();
131    if root_node.has_error() {
132        return Err(crate::error::ParseError::TreeSitterError(
133            "Failed to parse text".to_string(),
134        ));
135    }
136    let mut context = ParseContext::new(text.as_bytes());
137
138    Specification::from_node(root_node, &mut context)
139}
140
141#[cfg(test)]
142mod tests {
143    use super::parser_text;
144    use crate::typed_ast::{
145        AnnotationAppl, AnnotationName, AnnotationParams, Definition, TemplateTypeSpec,
146        TypeDclInner, TypeDeclaratorInner, TypeSpec,
147    };
148
149    #[test]
150    fn parse_template_type_spec() {
151        let typed = parser_text(
152            r#"
153            module m {
154                typedef Vec<long> MyVec;
155            };
156            "#,
157        )
158        .expect("parse should succeed");
159
160        let module = match &typed.0[0] {
161            Definition::ModuleDcl(module) => module,
162            other => panic!("expected module, got {other:?}"),
163        };
164        let type_dcl = match &module.definition[0] {
165            Definition::TypeDcl(type_dcl) => type_dcl,
166            other => panic!("expected type declaration, got {other:?}"),
167        };
168        let typedef = match &type_dcl.decl {
169            TypeDclInner::TypedefDcl(typedef) => typedef,
170            other => panic!("expected typedef, got {other:?}"),
171        };
172        let template = match &typedef.decl.ty {
173            TypeDeclaratorInner::TemplateTypeSpec(TemplateTypeSpec::TemplateType(template)) => {
174                template
175            }
176            other => panic!("expected template_type, got {other:?}"),
177        };
178        assert_eq!(template.ident.0, "Vec");
179        assert_eq!(template.args.len(), 1);
180        assert!(matches!(
181            template.args[0],
182            TypeSpec::SimpleTypeSpec(crate::typed_ast::SimpleTypeSpec::BaseTypeSpec(
183                crate::typed_ast::BaseTypeSpec::IntegerType(_)
184            ))
185        ));
186    }
187
188    #[test]
189    fn parse_doc_comments_as_doc_annotation() {
190        let typed = parser_text(
191            r#"
192            /// module doc
193            module m {
194                /// struct doc
195                struct S {
196                    /// field doc
197                    long x;
198                };
199            };
200            "#,
201        )
202        .expect("parse should succeed");
203
204        let module = match &typed.0[0] {
205            Definition::ModuleDcl(module) => module,
206            other => panic!("expected module, got {other:?}"),
207        };
208        assert_has_doc(&module.annotations, "\"module doc\"");
209
210        let type_dcl = match &module.definition[0] {
211            Definition::TypeDcl(type_dcl) => type_dcl,
212            other => panic!("expected type declaration, got {other:?}"),
213        };
214        assert_has_doc(&type_dcl.annotations, "\"struct doc\"");
215
216        let struct_def = match &type_dcl.decl {
217            TypeDclInner::ConstrTypeDcl(crate::typed_ast::ConstrTypeDcl::StructDcl(
218                crate::typed_ast::StructDcl::StructDef(def),
219            )) => def,
220            other => panic!("expected struct def, got {other:?}"),
221        };
222        let member = &struct_def.member[0];
223        assert_has_doc(&member.annotations, "\"field doc\"");
224    }
225
226    fn assert_has_doc(annotations: &[AnnotationAppl], expected: &str) {
227        let doc = annotations.iter().find_map(|anno| match &anno.name {
228            AnnotationName::Builtin(name) if name == "doc" => match &anno.params {
229                Some(AnnotationParams::Raw(raw)) => Some(raw.as_str()),
230                _ => None,
231            },
232            _ => None,
233        });
234        assert_eq!(doc, Some(expected));
235    }
236}