Skip to main content

xidl_parser/typed_ast/
annotation.rs

1use super::*;
2use serde::Serialize;
3
4#[derive(Clone, Serialize)]
5/// One annotation application, preserving its original source location.
6pub struct AnnotationAppl {
7    /// Original byte range; synthetic documentation annotations have no range.
8    #[serde(skip)]
9    pub span: Option<std::ops::Range<usize>>,
10    pub name: AnnotationName,
11    pub params: Option<AnnotationParams>,
12    pub builtin: Option<BuiltinAnnotation>,
13    pub is_extend: bool,
14    pub extra: Vec<AnnotationAppl>,
15}
16
17#[derive(Debug, Clone, Serialize)]
18pub enum AnnotationName {
19    ScopedName(ScopedName),
20    Builtin(String),
21}
22
23#[derive(Debug, Clone, Serialize)]
24pub enum AnnotationParams {
25    Params(Vec<AnnotationApplParam>),
26    Raw(String),
27}
28
29#[derive(Debug, Clone, Serialize)]
30pub enum AnnotationApplParam {
31    Positional(ConstExpr),
32    Named { ident: Identifier, value: ConstExpr },
33}
34
35impl<'a> crate::parser::FromTreeSitter<'a> for AnnotationAppl {
36    fn from_node(
37        node: tree_sitter::Node<'a>,
38        ctx: &mut crate::parser::ParseContext<'a>,
39    ) -> crate::error::ParserResult<Self> {
40        let kind_id = node.kind_id();
41        let is_extend = kind_id == xidl_parser_derive::node_id!("extend_annotation_appl");
42        if !is_extend {
43            assert_eq!(kind_id, xidl_parser_derive::node_id!("annotation_appl"));
44        }
45        let raw = ctx.node_text(&node)?.to_string();
46        let span = node
47            .children(&mut node.walk())
48            .find(|child| {
49                matches!(
50                    child.kind(),
51                    "annotation_appl_custom_body" | "annotation_appl_builtin_body"
52                )
53            })
54            .map(|body| node.start_byte()..body.end_byte());
55
56        let mut custom_body = None;
57        let mut builtin_body = None;
58        let mut extra = Vec::new();
59        for ch in node.children(&mut node.walk()) {
60            match ch.kind_id() {
61                xidl_parser_derive::node_id!("annotation_appl_custom_body") => {
62                    custom_body = Some(ch);
63                }
64                xidl_parser_derive::node_id!("annotation_appl_builtin_body") => {
65                    builtin_body = Some(ch);
66                }
67                xidl_parser_derive::node_id!("annotation_appl")
68                | xidl_parser_derive::node_id!("extend_annotation_appl") => {
69                    extra.push(AnnotationAppl::from_node(ch, ctx)?);
70                }
71                _ => {}
72            }
73        }
74
75        let body_source = custom_body
76            .or(builtin_body)
77            .map(|body| ctx.node_text(&body))
78            .transpose()?;
79        if let Some(source) = body_source.filter(|source| requires_raw_annotation_parse(source)) {
80            let mut annotation = parse_annotation_from_raw(source, is_extend, extra)?;
81            annotation.span = span;
82            return Ok(annotation);
83        }
84
85        if let Some(custom_body) = custom_body {
86            let mut scoped_name = None;
87            let mut params = None;
88            for ch in custom_body.children(&mut custom_body.walk()) {
89                match ch.kind_id() {
90                    xidl_parser_derive::node_id!("scoped_name") => {
91                        scoped_name = Some(ScopedName::from_node(ch, ctx)?);
92                    }
93                    xidl_parser_derive::node_id!("annotation_appl_params") => {
94                        params = Some(AnnotationParams::from_node(ch, ctx)?);
95                    }
96                    _ => {}
97                }
98            }
99            let scoped_name = scoped_name.ok_or_else(|| {
100                crate::error::ParseError::UnexpectedNode(
101                    "annotation_appl_custom_body missing scoped_name".to_string(),
102                )
103            })?;
104            return Ok(Self {
105                span,
106                name: AnnotationName::ScopedName(scoped_name),
107                params,
108                builtin: None,
109                is_extend,
110                extra,
111            });
112        }
113
114        let builtin = builtin_body
115            .map(|node| parse_builtin_annotation(node, ctx))
116            .transpose()?;
117        let source = builtin_body
118            .map(|node| ctx.node_text(&node))
119            .transpose()?
120            .unwrap_or(raw.as_str());
121        let source = source.trim().strip_prefix("//@").unwrap_or(source);
122        let raw = source.trim().strip_prefix('@').unwrap_or(source).trim();
123        let (name, args) = match raw.split_once('(') {
124            Some((name, rest)) => {
125                let args = rest.strip_suffix(')').unwrap_or(rest).trim();
126                (name.trim(), Some(args))
127            }
128            None => (raw, None),
129        };
130
131        Ok(Self {
132            span,
133            name: AnnotationName::Builtin(name.to_string()),
134            params: args.map(|value| AnnotationParams::Raw(value.to_string())),
135            builtin,
136            is_extend,
137            extra,
138        })
139    }
140}
141
142fn requires_raw_annotation_parse(raw: &str) -> bool {
143    let raw = crate::parser::raw::RawArguments::without_comments(raw);
144    let source = raw.trim();
145    let source = source.strip_prefix("//@").unwrap_or(source);
146    let source = source.strip_prefix('@').unwrap_or(source);
147    let name = source
148        .split_once('(')
149        .map(|(name, _)| name)
150        .unwrap_or(source);
151    name.contains('-') || crate::parser::raw::RawArguments::contains_array(source)
152}
153
154fn parse_annotation_from_raw(
155    raw: &str,
156    is_extend: bool,
157    extra: Vec<AnnotationAppl>,
158) -> crate::error::ParserResult<AnnotationAppl> {
159    let source = raw.trim();
160    let source = source.strip_prefix("//@").unwrap_or(source);
161    let source = source.strip_prefix('@').unwrap_or(source).trim();
162    let (name, args) = match source.split_once('(') {
163        Some((name, rest)) => {
164            let args = rest.strip_suffix(')').unwrap_or(rest).trim();
165            (name.trim(), Some(args))
166        }
167        None => (source, None),
168    };
169    Ok(AnnotationAppl {
170        span: None,
171        name: AnnotationName::Builtin(name.to_string()),
172        params: args.map(|value| AnnotationParams::Raw(value.to_string())),
173        builtin: None,
174        is_extend,
175        extra,
176    })
177}
178
179impl AnnotationAppl {
180    pub fn doc(text: String) -> Self {
181        let escaped = escape_doc_text(&text);
182        Self {
183            span: None,
184            name: AnnotationName::Builtin("doc".to_string()),
185            params: Some(AnnotationParams::Raw(format!("\"{}\"", escaped))),
186            builtin: None,
187            is_extend: false,
188            extra: Vec::new(),
189        }
190    }
191}
192
193fn escape_doc_text(text: &str) -> String {
194    let mut out = String::with_capacity(text.len());
195    for ch in text.chars() {
196        match ch {
197            '\\' => out.push_str("\\\\"),
198            '"' => out.push_str("\\\""),
199            '\n' => out.push_str("\\n"),
200            '\r' => {}
201            _ => out.push(ch),
202        }
203    }
204    out
205}
206
207impl<'a> crate::parser::FromTreeSitter<'a> for AnnotationParams {
208    fn from_node(
209        node: tree_sitter::Node<'a>,
210        ctx: &mut crate::parser::ParseContext<'a>,
211    ) -> crate::error::ParserResult<Self> {
212        assert_eq!(
213            node.kind_id(),
214            xidl_parser_derive::node_id!("annotation_appl_params")
215        );
216
217        let mut params = vec![];
218        for ch in node.children(&mut node.walk()) {
219            match ch.kind_id() {
220                xidl_parser_derive::node_id!("const_expr") => {
221                    return Ok(Self::Params(vec![AnnotationApplParam::Positional(
222                        ConstExpr::from_node(ch, ctx)?,
223                    )]));
224                }
225                xidl_parser_derive::node_id!("annotation_appl_param") => {
226                    params.push(AnnotationApplParam::from_node(ch, ctx)?);
227                }
228                _ => {}
229            }
230        }
231
232        if !params.is_empty() {
233            return Ok(Self::Params(params));
234        }
235
236        Err(crate::error::ParseError::UnexpectedNode(
237            "annotation_appl_params missing content".to_string(),
238        ))
239    }
240}
241
242impl<'a> crate::parser::FromTreeSitter<'a> for AnnotationApplParam {
243    fn from_node(
244        node: tree_sitter::Node<'a>,
245        ctx: &mut crate::parser::ParseContext<'a>,
246    ) -> crate::error::ParserResult<Self> {
247        assert_eq!(
248            node.kind_id(),
249            xidl_parser_derive::node_id!("annotation_appl_param")
250        );
251
252        let mut ident = None;
253        let mut value = None;
254        for ch in node.children(&mut node.walk()) {
255            match ch.kind_id() {
256                xidl_parser_derive::node_id!("identifier") => {
257                    ident = Some(Identifier::from_node(ch, ctx)?);
258                }
259                xidl_parser_derive::node_id!("const_expr") => {
260                    value = Some(ConstExpr::from_node(ch, ctx)?);
261                }
262                _ => {}
263            }
264        }
265
266        match (ident, value) {
267            (None, Some(value)) => Ok(Self::Positional(value)),
268            (Some(ident), Some(value)) => Ok(Self::Named { ident, value }),
269            _ => Err(crate::error::ParseError::UnexpectedNode(
270                "annotation_appl_param missing const_expr".to_string(),
271            )),
272        }
273    }
274}
275
276impl std::fmt::Debug for AnnotationAppl {
277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278        f.debug_struct("AnnotationAppl")
279            .field("name", &self.name)
280            .field("params", &self.params)
281            .field("builtin", &self.builtin)
282            .field("is_extend", &self.is_extend)
283            .field("extra", &self.extra)
284            .finish()
285    }
286}