xidl-parser 0.71.1

A IDL codegen.
Documentation
use super::*;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnnotationAppl {
    pub name: AnnotationName,
    pub params: Option<AnnotationParams>,
    pub builtin: Option<BuiltinAnnotation>,
    pub is_extend: bool,
    pub extra: Vec<AnnotationAppl>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnnotationName {
    ScopedName(ScopedName),
    Builtin(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnnotationParams {
    Params(Vec<AnnotationApplParam>),
    Raw(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnnotationApplParam {
    Positional(ConstExpr),
    Named { ident: Identifier, value: ConstExpr },
}

impl<'a> crate::parser::FromTreeSitter<'a> for AnnotationAppl {
    fn from_node(
        node: tree_sitter::Node<'a>,
        ctx: &mut crate::parser::ParseContext<'a>,
    ) -> crate::error::ParserResult<Self> {
        let kind_id = node.kind_id();
        let is_extend = kind_id == xidl_parser_derive::node_id!("extend_annotation_appl");
        if !is_extend {
            assert_eq!(kind_id, xidl_parser_derive::node_id!("annotation_appl"));
        }
        let raw = ctx.node_text(&node)?.to_string();

        let mut custom_body = None;
        let mut builtin_body = None;
        let mut extra = Vec::new();
        for ch in node.children(&mut node.walk()) {
            match ch.kind_id() {
                xidl_parser_derive::node_id!("annotation_appl_custom_body") => {
                    custom_body = Some(ch);
                }
                xidl_parser_derive::node_id!("annotation_appl_builtin_body") => {
                    builtin_body = Some(ch);
                }
                xidl_parser_derive::node_id!("annotation_appl")
                | xidl_parser_derive::node_id!("extend_annotation_appl") => {
                    extra.push(AnnotationAppl::from_node(ch, ctx)?);
                }
                _ => {}
            }
        }

        if requires_raw_annotation_parse(&raw) {
            return parse_annotation_from_raw(&raw, is_extend, extra);
        }

        if let Some(custom_body) = custom_body {
            let mut scoped_name = None;
            let mut params = None;
            for ch in custom_body.children(&mut custom_body.walk()) {
                match ch.kind_id() {
                    xidl_parser_derive::node_id!("scoped_name") => {
                        scoped_name = Some(ScopedName::from_node(ch, ctx)?);
                    }
                    xidl_parser_derive::node_id!("annotation_appl_params") => {
                        params = Some(AnnotationParams::from_node(ch, ctx)?);
                    }
                    _ => {}
                }
            }
            let scoped_name = scoped_name.ok_or_else(|| {
                crate::error::ParseError::UnexpectedNode(
                    "annotation_appl_custom_body missing scoped_name".to_string(),
                )
            })?;
            return Ok(Self {
                name: AnnotationName::ScopedName(scoped_name),
                params,
                builtin: None,
                is_extend,
                extra,
            });
        }

        let builtin = builtin_body
            .map(|node| parse_builtin_annotation(node, ctx))
            .transpose()?;
        let source = builtin_body
            .map(|node| ctx.node_text(&node))
            .transpose()?
            .unwrap_or(raw.as_str());
        let source = source.trim().strip_prefix("//@").unwrap_or(source);
        let raw = source.trim().strip_prefix('@').unwrap_or(source).trim();
        let (name, args) = match raw.split_once('(') {
            Some((name, rest)) => {
                let args = rest.strip_suffix(')').unwrap_or(rest).trim();
                (name.trim(), Some(args))
            }
            None => (raw, None),
        };

        Ok(Self {
            name: AnnotationName::Builtin(name.to_string()),
            params: args.map(|value| AnnotationParams::Raw(value.to_string())),
            builtin,
            is_extend,
            extra,
        })
    }
}

fn requires_raw_annotation_parse(raw: &str) -> bool {
    let source = raw.trim();
    let source = source.strip_prefix("//@").unwrap_or(source);
    let source = source.strip_prefix('@').unwrap_or(source);
    let name = source
        .split_once('(')
        .map(|(name, _)| name)
        .unwrap_or(source);
    name.contains('-') || source.contains('[') || source.contains(']')
}

fn parse_annotation_from_raw(
    raw: &str,
    is_extend: bool,
    extra: Vec<AnnotationAppl>,
) -> crate::error::ParserResult<AnnotationAppl> {
    let source = raw.trim();
    let source = source.strip_prefix("//@").unwrap_or(source);
    let source = source.strip_prefix('@').unwrap_or(source).trim();
    let (name, args) = match source.split_once('(') {
        Some((name, rest)) => {
            let args = rest.strip_suffix(')').unwrap_or(rest).trim();
            (name.trim(), Some(args))
        }
        None => (source, None),
    };
    Ok(AnnotationAppl {
        name: AnnotationName::Builtin(name.to_string()),
        params: args.map(|value| AnnotationParams::Raw(value.to_string())),
        builtin: None,
        is_extend,
        extra,
    })
}

impl AnnotationAppl {
    pub fn doc(text: String) -> Self {
        let escaped = escape_doc_text(&text);
        Self {
            name: AnnotationName::Builtin("doc".to_string()),
            params: Some(AnnotationParams::Raw(format!("\"{}\"", escaped))),
            builtin: None,
            is_extend: false,
            extra: Vec::new(),
        }
    }
}

fn escape_doc_text(text: &str) -> String {
    let mut out = String::with_capacity(text.len());
    for ch in text.chars() {
        match ch {
            '\\' => out.push_str("\\\\"),
            '"' => out.push_str("\\\""),
            '\n' => out.push_str("\\n"),
            '\r' => {}
            _ => out.push(ch),
        }
    }
    out
}

impl<'a> crate::parser::FromTreeSitter<'a> for AnnotationParams {
    fn from_node(
        node: tree_sitter::Node<'a>,
        ctx: &mut crate::parser::ParseContext<'a>,
    ) -> crate::error::ParserResult<Self> {
        assert_eq!(
            node.kind_id(),
            xidl_parser_derive::node_id!("annotation_appl_params")
        );

        let mut params = vec![];
        for ch in node.children(&mut node.walk()) {
            match ch.kind_id() {
                xidl_parser_derive::node_id!("const_expr") => {
                    return Ok(Self::Params(vec![AnnotationApplParam::Positional(
                        ConstExpr::from_node(ch, ctx)?,
                    )]));
                }
                xidl_parser_derive::node_id!("annotation_appl_param") => {
                    params.push(AnnotationApplParam::from_node(ch, ctx)?);
                }
                _ => {}
            }
        }

        if !params.is_empty() {
            return Ok(Self::Params(params));
        }

        Err(crate::error::ParseError::UnexpectedNode(
            "annotation_appl_params missing content".to_string(),
        ))
    }
}

impl<'a> crate::parser::FromTreeSitter<'a> for AnnotationApplParam {
    fn from_node(
        node: tree_sitter::Node<'a>,
        ctx: &mut crate::parser::ParseContext<'a>,
    ) -> crate::error::ParserResult<Self> {
        assert_eq!(
            node.kind_id(),
            xidl_parser_derive::node_id!("annotation_appl_param")
        );

        let mut ident = None;
        let mut value = None;
        for ch in node.children(&mut node.walk()) {
            match ch.kind_id() {
                xidl_parser_derive::node_id!("identifier") => {
                    ident = Some(Identifier::from_node(ch, ctx)?);
                }
                xidl_parser_derive::node_id!("const_expr") => {
                    value = Some(ConstExpr::from_node(ch, ctx)?);
                }
                _ => {}
            }
        }

        match (ident, value) {
            (None, Some(value)) => Ok(Self::Positional(value)),
            (Some(ident), Some(value)) => Ok(Self::Named { ident, value }),
            _ => Err(crate::error::ParseError::UnexpectedNode(
                "annotation_appl_param missing const_expr".to_string(),
            )),
        }
    }
}