gluon_codegen 0.18.3

Code generation macros for the gluon programming language
Documentation
use proc_macro2::{Group, Span, TokenStream, TokenTree};

use {
    quote::ToTokens,
    syn::{
        self, Expr, Meta,
        Meta::*,
        Path,
        parse::{self, Parse},
        punctuated::Punctuated,
        token::Comma,
    },
};

fn get_gluon_meta_items(attr: &syn::Attribute) -> Option<Vec<Meta>> {
    let path = attr.path();
    if path.segments.len() == 1
        && (path.segments[0].ident == "gluon"
            || path.segments[0].ident == "gluon_trace"
            || path.segments[0].ident == "gluon_userdata")
    {
        let parsed = attr
            .parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
            .ok()?;
        Some(parsed.into_iter().collect())
    } else {
        None
    }
}

pub enum CrateName {
    Some(syn::Path),
    GluonVm,
    None,
}

pub struct Container {
    pub crate_name: CrateName,
    pub vm_type: Option<String>,
    pub newtype: bool,
    pub skip: bool,
    pub clone: bool,
    pub ast_clone_bounds: Option<String>,
}

impl Container {
    pub fn from_ast(item: &syn::DeriveInput) -> Container {
        let mut crate_name = CrateName::None;
        let mut vm_type = None;
        let mut newtype = false;
        let mut skip = false;
        let mut clone = false;
        let mut ast_clone_bounds = None;

        for meta_items in item.attrs.iter().filter_map(get_gluon_meta_items) {
            for meta_item in meta_items {
                match meta_item {
                    // Parse `#[gluon(crate_name = "foo")]`
                    NameValue(ref m) if m.path.is_ident("crate_name") => {
                        if let Ok(path) = parse_lit_into_path(&m.path, &m.value) {
                            crate_name = CrateName::Some(path);
                        }
                    }

                    // Parse `#[gluon(gluon_vm)]`
                    Path(ref w) if w.is_ident("gluon_vm") => {
                        crate_name = CrateName::GluonVm;
                    }

                    Path(ref w) if w.is_ident("newtype") => {
                        newtype = true;
                    }

                    NameValue(ref m) if m.path.is_ident("vm_type") => {
                        vm_type = Some(get_lit_str(&m.path, &m.path, &m.value).unwrap().value())
                    }

                    Path(ref w) if w.is_ident("skip") => {
                        skip = true;
                    }

                    Path(ref w) if w.is_ident("clone") => {
                        clone = true;
                    }

                    NameValue(ref m) if m.path.is_ident("ast_clone_bounds") => {
                        ast_clone_bounds =
                            Some(get_lit_str(&m.path, &m.path, &m.value).unwrap().value())
                    }

                    meta_item => {
                        let path = meta_item
                            .path()
                            .into_token_stream()
                            .to_string()
                            .replace(' ', "");
                        panic!("unexpected gluon container attribute: `{}`", path)
                    }
                }
            }
        }

        Container {
            crate_name,
            vm_type,
            newtype,
            skip,
            clone,
            ast_clone_bounds,
        }
    }
}

fn get_lit_str<'a>(
    attr_name: &Path,
    _meta_item_name: &Path,
    expr: &'a Expr,
) -> Result<&'a syn::LitStr, ()> {
    if let Expr::Lit(expr_lit) = expr {
        if let syn::Lit::Str(ref lit) = expr_lit.lit {
            return Ok(lit);
        }
    }
    {
        panic!(
            "Expected attribute `{:?}` to be a string",
            attr_name.into_token_stream().to_string().replace(' ', "")
        )
    }
}

fn parse_lit_into_path(attr_name: &Path, expr: &Expr) -> Result<syn::Path, ()> {
    let string = get_lit_str(attr_name, attr_name, expr)?;
    parse_lit_str(string).map_err(|_| panic!("failed to parse path: {:?}", string.value()))
}

fn parse_lit_str<T>(s: &syn::LitStr) -> Result<T, parse::Error>
where
    T: Parse,
{
    let tokens = spanned_tokens(s)?;
    syn::parse2(tokens)
}

fn spanned_tokens(s: &syn::LitStr) -> Result<TokenStream, parse::Error> {
    let stream = syn::parse_str(&s.value())?;
    Ok(respan_token_stream(stream, s.span()))
}

fn respan_token_stream(stream: TokenStream, span: Span) -> TokenStream {
    stream
        .into_iter()
        .map(|token| respan_token_tree(token, span))
        .collect()
}

fn respan_token_tree(mut token: TokenTree, span: Span) -> TokenTree {
    if let TokenTree::Group(ref mut g) = token {
        *g = Group::new(g.delimiter(), respan_token_stream(g.stream().clone(), span));
    }
    token.set_span(span);
    token
}