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)
}