drought_macros 0.0.2

Macros supporting The Directive Roguter.
Documentation
use std::{collections::HashMap, fs::read_to_string};

use proc_macro::SourceFile;
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{Path, parse_file, visit::Visit, Macro, parse::Parse, Token, Error, Ident, ext::IdentExt, parse, LitStr};

use crate::droughter::{Verb, Block, Handle};


pub enum Ext {
    Verb(String, Path, Path),
    Block(String, Path, Path),
    Handle(String, Path, Path)
}
impl Parse for Ext
{
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self>
    {
        let cur = input.cursor();
        if let Some((id, _)) = cur.ident()
        {
            let errspan = input.call(Ident::parse_any)?.span();

            let name = input.parse::<LitStr>()?.value();
            input.parse::<Token![:]>()?;
            let check = input.parse()?;
            input.parse::<Token![+]>()?;
            let call = input.parse()?;
            
            match id.to_string().as_str()
            {
                "verb" => Ok(Self::Verb(name, check, call)),
                "block" => Ok(Self::Block(name, check, call)),
                "handle" => Ok(Self::Handle(name, check, call)),
                
                _ => {
                    return Err(Error::new(errspan, "Extension type must be one of `verb`, `block`, or `handle`."))
                }
            }
            
        }
        else {
            input.call(Ident::parse_any)
                .and(Err(Error::new(proc_macro2::Span::call_site(), "Unreachable!")))
        }
    }
}
impl ToTokens for Ext
{
    fn to_tokens(&self, _: &mut TokenStream) {}
}

pub fn nocheck(_: &mut TokenStream, _: &Vec<Verb>) {}

mod print_verb
{
    use proc_macro2::{TokenStream, Ident};
    use quote::{quote, ToTokens};
    use crate::droughter::Verb;
    
    pub fn call(out: &mut TokenStream, verb: &Verb, _: &Ident)
    {
        let mut argstream = TokenStream::new();

        for arg in verb.args.iter()
        {
            arg.value.to_tokens(&mut argstream);
        }
        
        quote!(
            println!(#argstream);
        ).to_tokens(out);
    }
}
mod loop_block
{
    use proc_macro2::{TokenStream, Ident};
    use quote::{quote, ToTokens};
    use crate::droughter::Block;
    
    pub fn call(out: &mut TokenStream, verb: &Block, _: &Ident)
    {
        let body = &verb.block;
        quote!(
            for _ in 0..10 {
                #body
            }
        ).to_tokens(out);
    }
}

pub type InternalCheckFn = fn (&mut TokenStream, &Vec<Verb>);
pub type InternalVerbCallFn = fn (&mut TokenStream, &Verb, &Ident);

pub type InternalBlockCallFn = fn (&mut TokenStream, &Block, &Ident);

pub type InternalHandleCallFn = fn (&mut TokenStream, &Handle, &Ident);

pub enum VerbGenerator
{
    Internal{check: InternalCheckFn, call: InternalVerbCallFn},
    Extension{check: Path, call: Path}
}
pub enum BlockGenerator
{
    Internal{check: InternalCheckFn, call: InternalBlockCallFn},
    Extension{check: Path, call: Path}
}
pub enum HandleGenerator
{
    Internal{check: InternalCheckFn, call: InternalHandleCallFn},
    Extension{check: Path, call: Path}
}

#[derive(Default)]
struct MacroVisitor
{
    verbs: HashMap<String, VerbGenerator>,
    blocks: HashMap<String, BlockGenerator>,
    handles: HashMap<String, HandleGenerator>
}
impl<'ast> Visit<'ast> for MacroVisitor
{
    fn visit_macro(&mut self, mac: &'ast Macro)
    {
        if mac.path.segments.last().map(|a| a.ident.to_string().as_str() == "droughter_ext").unwrap_or(false)
        {
            if let Ok(ext) = parse::<Ext>(mac.tokens.clone().into())
            {
                match ext
                {
                    Ext::Verb   (name, check, call) => { self.verbs.insert   (name, VerbGenerator::Extension   { check, call }); },
                    Ext::Block  (name, check, call) => { self.blocks.insert  (name, BlockGenerator::Extension  { check, call }); },
                    Ext::Handle (name, check, call) => { self.handles.insert (name, HandleGenerator::Extension { check, call }); },
                }
            }
        }
    }
}

pub fn get_exts(from: SourceFile) -> (HashMap<String, VerbGenerator>, HashMap<String, BlockGenerator>, HashMap<String, HandleGenerator>)
{
    let mut visitor = MacroVisitor::default();

    visitor.verbs.insert("print".into(), VerbGenerator::Internal { check: nocheck, call: print_verb::call });
    visitor.blocks.insert("loop10".into(), BlockGenerator::Internal { check: nocheck, call: loop_block::call });
    
    if from.is_real() {
        if let Ok(file) = parse_file(
            &read_to_string(&from.path())
                .expect(&format!("Couldn't read source file {}", from.path().to_string_lossy())))
        {
            visitor.visit_file(&file);
        }
    }

    (visitor.verbs, visitor.blocks, visitor.handles)
}