kamo-macros 0.1.2

A macro for parsing s-expressions into kamo Values.
Documentation
use pest::iterators::Pair;
use proc_macro2::TokenStream;
use quote::quote;

use crate::sexpr::{error::Error, parser::Rule};

use super::helper;

pub fn emit_string<'a>(
    mutator: syn::Ident,
    pair: Pair<'a, Rule>,
    out: &mut TokenStream,
) -> Result<(), Error<'a>> {
    if pair.as_rule() == Rule::string {
        let string = pair.as_str();

        if string.is_empty() {
            out.extend(quote! { Value::new_string("") });
            return Ok(());
        }

        let pairs = pair.into_inner();

        if pairs.peek().is_some() {
            let mut string = String::with_capacity(string.len());

            for pair in pairs {
                match pair.as_rule() {
                    Rule::string_text => string.push_str(pair.as_str()),
                    Rule::string_escape => {
                        if let Some(c) = helper::get_escape(pair)? {
                            string.push(c)
                        }
                    }
                    _ => unreachable!(),
                }
            }
            out.extend(quote! { Value::new_string(#mutator.clone(), #string) });
        } else {
            out.extend(quote! { Value::new_string(#mutator.clone(), #string) });
        }
        Ok(())
    } else {
        Err(Error::ExpectedString(pair.as_span()))
    }
}

#[cfg(test)]
mod tests {
    use pest::Parser;
    use proc_macro2::Span;
    use quote::quote;

    use crate::sexpr::parser::SExpr;

    use super::*;

    #[test]
    fn emit_string_success() {
        let exprs = [
            (
                r#""hello""#,
                quote! { Value::new_string(m.clone(), "hello") },
            ),
            (
                r#""\"hello\"""#,
                quote! { Value::new_string(m.clone(), "\"hello\"") },
            ),
            (
                r#""hello\\""#,
                quote! { Value::new_string(m.clone(), "hello\\") },
            ),
            (
                r#""hello\a""#,
                quote! { Value::new_string(m.clone(), "hello\u{7}") },
            ),
            (
                r#""hello\b""#,
                quote! { Value::new_string(m.clone(), "hello\u{8}") },
            ),
            (
                r#""hello\t""#,
                quote! { Value::new_string(m.clone(), "hello\t") },
            ),
            (
                r#""hello\n""#,
                quote! { Value::new_string(m.clone(), "hello\n") },
            ),
            (
                r#""hello\r""#,
                quote! { Value::new_string(m.clone(), "hello\r") },
            ),
            (
                r#""hello \ 
                    world""#,
                quote! { Value::new_string(m.clone(), "hello world") },
            ),
        ];

        for (input, expected) in &exprs {
            let pair = SExpr::parse(Rule::datum, input).unwrap().next().unwrap();
            let mut out = TokenStream::new();
            emit_string(syn::Ident::new("m", Span::call_site()), pair, &mut out).unwrap();
            assert_eq!(out.to_string(), expected.to_string());
        }
    }

    #[test]
    fn emit_string_failure() {
        let exprs = ["10", "hello"];

        for input in &exprs {
            let pair = SExpr::parse(Rule::datum, input).unwrap().next().unwrap();
            let mut out = TokenStream::new();
            assert!(emit_string(syn::Ident::new("m", Span::call_site()), pair, &mut out).is_err());
        }
    }
}