kamo-macros 0.1.2

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

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

pub fn emit_abbrev<'a>(
    mutator: syn::Ident,
    pair: Pair<'a, Rule>,
    out: &mut TokenStream,
) -> Result<(), Error<'a>> {
    if pair.as_rule() == Rule::abbrev {
        let mut pairs = pair.into_inner();
        let abbrev = pairs.next().expect("abbreviation prefix");
        let mut datum = TokenStream::new();
        let abbrev = match abbrev.as_str() {
            "'" => syn::LitStr::new("quote", Span::call_site()),
            "`" => syn::LitStr::new("quasiquote", Span::call_site()),
            "," => syn::LitStr::new("unquote", Span::call_site()),
            ",@" => syn::LitStr::new("unquote-splicing", Span::call_site()),
            _ => unreachable!("unimplemented abbreviation: {:?}", abbrev.as_str()),
        };

        emit_datum(
            Some(mutator.to_owned()),
            pairs.next().expect("abbreviation datum"),
            &mut datum,
        )?;
        out.extend(quote! {
            Value::new_list(#mutator.clone(),
                vec![Value::new_symbol(#mutator.clone(), #abbrev), #datum])
        });
        Ok(())
    } else {
        Err(Error::ExpectedAbbrev(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_abbrev_success() {
        let exprs = [
            (
                "'()",
                quote! { Value::new_list(m.clone(),
                vec![Value::new_symbol(m.clone(), "quote"), Value::new_nil()]) },
            ),
            (
                "''()",
                quote! { Value::new_list(m.clone(),
                    vec![Value::new_symbol(m.clone(), "quote"),
                    Value::new_list(m.clone(),
                vec![Value::new_symbol(m.clone(), "quote"), Value::new_nil()])]) },
            ),
            (
                ",()",
                quote! { Value::new_list(m.clone(),
                vec![Value::new_symbol(m.clone(), "unquote"), Value::new_nil()]) },
            ),
            (
                "`()",
                quote! { Value::new_list(m.clone(),
                vec![Value::new_symbol(m.clone(), "quasiquote"), Value::new_nil()]) },
            ),
            (
                ",@()",
                quote! { Value::new_list(m.clone(),
                vec![Value::new_symbol(m.clone(), "unquote-splicing"), Value::new_nil()]) },
            ),
        ];

        for (input, expected) in &exprs {
            let pair = SExpr::parse(Rule::datum, input).unwrap().next().unwrap();
            let mut out = TokenStream::new();

            emit_abbrev(syn::Ident::new("m", Span::call_site()), pair, &mut out).unwrap();
            assert_eq!(out.to_string(), expected.to_string());
        }
    }

    #[test]
    fn emit_abbrev_failure() {
        let exprs = ["#t", "#f", "#\\a"];
        for input in &exprs {
            let pair = SExpr::parse(Rule::datum, input).unwrap().next().unwrap();
            let mut out = TokenStream::new();

            assert!(emit_abbrev(syn::Ident::new("m", Span::call_site()), pair, &mut out).is_err());
        }
    }
}