xee-xpath-macros 0.1.4

Proc macros for xee-interpreter function library
Documentation
use syn::{
    parse::{Parse, ParseStream},
    token::Comma,
    LitStr, Result,
};

use xee_xpath_ast::ast::Signature;
use xee_xpath_ast::Namespaces;

#[derive(Debug)]
pub(crate) struct XPathFnOptions {
    pub(crate) signature: Signature,
    pub(crate) kind: Option<String>,
    pub(crate) signature_string: String,
}

mod kw {
    syn::custom_keyword!(context_first);
    syn::custom_keyword!(context_last);
    syn::custom_keyword!(context_last_optional);
    syn::custom_keyword!(position);
    syn::custom_keyword!(size);
    syn::custom_keyword!(collation);
    syn::custom_keyword!(anonymous_closure);
}

impl Parse for XPathFnOptions {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut options = Vec::new();
        while !input.is_empty() {
            options.push(input.parse()?);
            if !input.is_empty() {
                let _: Comma = input.parse()?;
            }
        }

        let mut signature: Option<String> = None;
        let mut kind: Option<String> = None;
        for option in options {
            match option {
                XPathFnOption::Signature(signature_option) => {
                    signature = Some(signature_option);
                }
                XPathFnOption::Kind(kind_option) => {
                    kind = Some(kind_option);
                }
            }
        }
        let signature_string = signature.expect("Signature not found");
        let namespaces = Namespaces::default();
        let signature = Signature::parse(&signature_string, &namespaces)
            .map_err(|e| input.error(format!("{:?}", e)))?;
        Ok(Self {
            signature,
            kind,
            signature_string,
        })
    }
}

enum XPathFnOption {
    Signature(String),
    Kind(String),
}

impl Parse for XPathFnOption {
    fn parse(input: ParseStream) -> Result<Self> {
        let lookahead = input.lookahead1();
        Ok(if lookahead.peek(kw::context_first) {
            let _eat: kw::context_first = input.parse()?;
            XPathFnOption::Kind("context_first".to_string())
        } else if lookahead.peek(kw::context_last) {
            let _eat: kw::context_last = input.parse()?;
            XPathFnOption::Kind("context_last".to_string())
        } else if lookahead.peek(kw::context_last_optional) {
            let _eat: kw::context_last_optional = input.parse()?;
            XPathFnOption::Kind("context_last_optional".to_string())
        } else if lookahead.peek(kw::position) {
            let _eat: kw::position = input.parse()?;
            XPathFnOption::Kind("position".to_string())
        } else if lookahead.peek(kw::size) {
            let _eat: kw::size = input.parse()?;
            XPathFnOption::Kind("size".to_string())
        } else if lookahead.peek(kw::collation) {
            let _eat: kw::collation = input.parse()?;
            XPathFnOption::Kind("collation".to_string())
        } else if lookahead.peek(kw::anonymous_closure) {
            let _eat: kw::anonymous_closure = input.parse()?;
            XPathFnOption::Kind("anonymous_closure".to_string())
        } else if lookahead.peek(LitStr) {
            let string_literal: LitStr = input.parse()?;
            let signature = string_literal.value();
            XPathFnOption::Signature(signature)
        } else {
            bail_spanned!(
                input.span() => "Expected a string literal or a context keyword"
            );
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use insta::assert_debug_snapshot;

    #[test]
    fn test_parse_signature() {
        assert_debug_snapshot!(syn::parse_str::<XPathFnOptions>(
            r#""fn:foo() as xs:string""#
        ));
    }

    #[test]
    fn test_parse_signature_with_kind() {
        assert_debug_snapshot!(syn::parse_str::<XPathFnOptions>(
            r#""fn:foo() as xs:string", context_first"#
        ));
    }

    #[test]
    fn test_parse_signature_parse_error() {
        assert_debug_snapshot!(syn::parse_str::<XPathFnOptions>(r#""wrong wrong""#));
    }

    #[test]
    fn test_parse_not_a_signature_string() {
        assert_debug_snapshot!(syn::parse_str::<XPathFnOptions>(r#"wrong"#));
    }

    #[test]
    fn test_parse_signature_unknown_kind() {
        assert_debug_snapshot!(syn::parse_str::<XPathFnOptions>(
            r#""fn:foo() as xs:string",blah"#
        ));
    }
}