diff --git a/sing_macros/src/keyword.rs b/sing_macros/src/keyword.rs new file mode 100644 index 0000000..d945e93 --- /dev/null +++ b/sing_macros/src/keyword.rs @@ -0,0 +1,4 @@ +use syn::custom_keyword; + +custom_keyword!(ioe); +custom_keyword!(msg); diff --git a/sing_macros/src/lib.rs b/sing_macros/src/lib.rs index 25021e2..b2ddfe3 100644 --- a/sing_macros/src/lib.rs +++ b/sing_macros/src/lib.rs @@ -5,14 +5,454 @@ use std::{ error::Error, io::{Write, Read}, }; -use sing_util::TraitProfile; -use syn::{parse_macro_input, AttributeArgs, ItemImpl, ImplItem}; +use proc_macro::TokenStream; +use proc_macro2::{TokenStream as TokenStream2, Span}; +use sing_util::{DeduplicatedFunctionProfile, TraitProfile}; +use syn::{ + parse_macro_input, + AttributeArgs, + Ident, + ItemImpl, + ImplItem, + NestedMeta, + parse::{Parse, ParseStream}, + Token, + parenthesized, + Arm, + parse2, + LitStr, + Signature, + FnArg, + Path, + Stmt, +}; use quote::{quote, spanned::Spanned, ToTokens}; - extern crate sing_util; extern crate proc_macro; +mod keyword; + +#[proc_macro_attribute] +pub fn sing_add_trait(input: TokenStream, annotated_item: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemImpl); + let annotated_item = parse_macro_input!(annotated_item as AttributeArgs); + + let output = add_trait_inner(input, annotated_item); + + TokenStream::from(output) +} + +fn add_trait_inner(input: ItemImpl, annotated_item: Vec) -> TokenStream2 { + let trait_name; + match &input.trait_ { + Some((_b, p, _f)) => { + trait_name = p.into_token_stream().to_string(); + }, + None => { + compile_error_panic(input.__span(), "This attribute can only be used on trait impl blocks."); + trait_name = String::from(""); + }, + }; + + let mut trait_functions = HashMap::new(); + for fun in &input.items { + match fun { + ImplItem::Method(m) => {trait_functions.insert( + m.sig.ident.to_string(), + m.sig.to_token_stream() + );}, + _ => { + compile_error_panic(input.__span(), "Found unexpected item that is not a method."); + }, + } + } + + let mut trait_state = load_trait_state().unwrap(); + + let profile = TraitProfile::new(trait_functions); + if trait_state.contains_key(trait_name.as_str()) { + let err = format!("Trait {} is implemented twice!", trait_name); + syn::Error::new(input.__span(), err.as_str()) + .to_compile_error(); + } else { + trait_state.insert(trait_name, profile); + } + + save_trait_state(trait_state).unwrap(); + + // Build the output, possibly using quasi-quotation + // TODO: add gobble + quote! { + // ... + } +} + +#[proc_macro] +pub fn sing_loop(input: TokenStream) -> TokenStream { + let span = TokenStream2::from(input.clone()).__span(); + let input = parse_macro_input!(input as LoopParams); + + let output = match loop_inner(input, span) { + Ok(s) => s, + Err(e) => { + compile_error_panic(span, &e.to_string()); + TokenStream2::new() + }, + }; + + TokenStream::from(output) +} + +fn loop_inner(input: LoopParams, span: Span) -> Result> { + let LoopParams { + trait_obj, + ser_fun, + de_fun, + reader, + writer, + err_writer, + message_type + } = input; + + let mut ioe_initializers = vec!(); + + let reader = match reader { + Some(r) => r, + None => { + ioe_initializers.push(parse2::(quote!{ + let reader = std::io::stdin().lock(); + })?); + + parse2::(quote!{ + reader + })?}, + }; + + let writer = match writer { + Some(w) => w, + None => { + ioe_initializers.push(parse2::(quote!{ + let writer = std::io::stdout().lock(); + })?); + + parse2::(quote!{ + writer + })?}, + }; + + let err_writer = match err_writer { + Some(w) => w, + None => { + ioe_initializers.push(parse2::(quote!{ + let err_writer = std::io::stdin().lock(); + })?); + + parse2::(quote!{ + err_writer + })?}, + }; + + let ioe_initializers = ioe_initializers; + + let message_type = match message_type { + Some(r) => r, + None => parse2::(quote!{ + todo!(); + })?, + }; + + let traits = load_trait_state().unwrap(); + + let mut funs = HashMap::new(); + for (t_name, t_funs) in &traits { + for (f_name, fun) in t_funs.get_funs()? { + if funs.contains_key(&f_name) { + funs.insert(f_name.clone(), match &funs[&f_name] { + DeduplicatedFunctionProfile::Single(t, _) => DeduplicatedFunctionProfile::Multiple(vec!(t_name.clone(), t.clone())), + DeduplicatedFunctionProfile::Multiple(v) => { + let mut v = v.clone(); + v.push(t_name.clone()); + DeduplicatedFunctionProfile::Multiple(v) + }, + }); + } + } + } + + let mut fun_arms = vec!(); + for (f_name, f_profile) in funs { + let fn_lit = LitStr::new(&f_name, span); + let fn_ident = Ident::new(&f_name, span); + let arm; + + match f_profile { + DeduplicatedFunctionProfile::Multiple(v) => { + arm = quote!{ + #fn_lit => { + let traits = vec!( #( #v ),* ); + let message = format!("The function {} is found in the following traits: {}. +Plese specify which traits function you want to use.", + #f_name, + traits, + ); + <#err_writer as Write>.write_all(message.as_bytes())?; + }, + }; + }, + DeduplicatedFunctionProfile::Single(tr, s) => { + let tr = Ident::new(&tr, span); + arm = get_fun_arm( + fn_lit, + fn_ident, + ser_fun.clone(), + de_fun.clone(), + writer.clone(), + trait_obj.clone(), + tr, + s.get_inner()? + )?; + } + } + + let arm = parse2::(arm)?; + fun_arms.push(arm); + } + + fun_arms.push( + parse2::(quote! { + "" => { + let message = String::from("No function name was given! Aborting."); + <#err_writer as Write>.write_all(message.as_bytes())?; + }, + })? + ); + + fun_arms.push( + parse2::(quote! { + s => { + let message = format!("The function {} is not known! Aborting.", s); + <#err_writer as Write>.write_all(message.as_bytes())?; + }, + })? + ); + + let mut trait_arms = vec!(); + + for (t_name, t_profile) in traits.clone() { + let t_lit = LitStr::new(&t_name, span); + let t_ident = Ident::new(&t_name, span); + + let mut f_arms = vec!(); + for (f_name, f_profile) in t_profile.get_funs()? { + let fn_lit = LitStr::new(&f_name, span); + let fn_ident = Ident::new(&f_name, span); + + let f_arm = get_fun_arm( + fn_lit, + fn_ident, + ser_fun.clone(), + de_fun.clone(), + writer.clone(), + trait_obj.clone(), + t_ident.clone(), + f_profile + )?; + + f_arms.push(parse2::(f_arm)?); + } + + f_arms.push( + parse2::(quote! { + "" => { + let message = String::from("No function name was given! Aborting."); + <#err_writer as Write>.write_all(message.as_bytes())?; + }, + })? + ); + + f_arms.push( + parse2::(quote! { + s => { + let message = format!("The function {} was not found in the trait {}! Aborting.", s, #t_lit ); + <#err_writer as Write>.write_all(message.as_bytes())?; + } + })? + ); + + let t_arm = quote! { + #t_lit => { + match .get_fun_name().as_str() { + #( #f_arms )* + } + } + }; + + trait_arms.push(parse2::(t_arm)?); + } + + Ok(quote!{ + #( #ioe_initializers )* + 'main: loop { + let mut buf = String::new(); + <#reader as BufRead>.read_line(buf); + + match #de_fun ::< #message_type >(buf) { + Ok(message) => { + let mut message = message; + + match .get_trait_name().as_str() { + #( #trait_arms )* + } + + match #ser_fun(message) { + Ok(s) => <#writer as Write>.write_all(s), + Err(e) => <#err_writer as Write>.write_all("Could not encode call result: {}", e), + } + }, + Err(e) => <#err_writer as Write>.write_all("Could not decode {} as trait message: {}", buf, e), + } + } + }) +} + +struct LoopParams { + trait_obj: Ident, + ser_fun: Path, + de_fun: Path, + reader: Option, + writer: Option, + err_writer: Option, + message_type: Option, +} + +impl Parse for LoopParams { + fn parse(input: ParseStream) -> syn::Result { + let trait_obj = input.parse()?; + input.parse::()?; + + let ser_fun = input.parse()?; + input.parse::()?; + + let de_fun = input.parse()?; + let lookahead = input.lookahead1(); + + let mut reader = None; + let mut writer = None; + let mut err_writer = None; + let mut message_type = None; + + 'parse: loop { + input.parse::()?; + if input.is_empty() {break 'parse} + if lookahead.peek(keyword::msg) { + input.parse::()?; + input.parse::()?; + + let ty = input.parse()?; + message_type = Some(ty); + + } else if lookahead.peek(keyword::ioe) { + input.parse::()?; + input.parse::()?; + + let content; + parenthesized!(content in input); + let lh_inner = content.lookahead1(); + let ioe_err_generic = "The ioe keyword expects three variables in the following order: (Input, Output, Error),"; + + if lh_inner.peek(Ident) { + let r = content.parse()?; + reader = Some(r); + } else { + compile_error_panic( + content.span(), + format!("{}, but the Input variable was not supplied.", ioe_err_generic).as_str() + ); + } + + if lh_inner.peek(Ident) { + let w = content.parse()?; + writer = Some(w); + } else { + compile_error_panic( + content.span(), + format!("{}, but the Output variable was not supplied.", ioe_err_generic).as_str(), + ); + } + + if lh_inner.peek(Ident) { + let e = content.parse()?; + err_writer = Some(e); + } else { + compile_error_panic( + content.span(), + format!("{}, but the Error variable was not supplied.", ioe_err_generic).as_str(), + ); + } + } + if input.is_empty() {break 'parse} + } + Ok(Self { + trait_obj, + ser_fun, + de_fun, + reader, + writer, + err_writer, + message_type, + }) + } +} + +fn get_fun_arm( + fn_lit: LitStr, + fn_ident: Ident, + ser_fun: Path, + de_fun: Path, + writer: Ident, + trait_obj: Ident, + tr: Ident, + s: TokenStream2, +) -> Result> { + let sig = parse2::(s)?; + let mut args = vec!(); + let mut parse_lets = vec!(); + + for a in sig.inputs { + match a { + // This is just a "self" value which does not need to be in the call + FnArg::Receiver(_) => (), + FnArg::Typed(t) => { + let pat = *t.pat; + let ty = t.ty; + + args.push(pat.clone()); + + parse_lets.push(parse2::(quote!{ + let #pat : #ty = #de_fun ( #pat ); + })?); + }, + } + } + + Ok(quote!{ + #fn_lit => { + let params = .get_params(); + + let ( #( #args ),* ) = params; + #( #parse_lets )* + + let result = <#trait_obj as #tr>. #fn_ident ( #( #args ),* ); + .new_params( + vec!(<#ser_fun as Serializer>.serialize(result)) + ); + + <#writer as Write>.write_all(message.as_bytes())?; + }, + }) +} + fn get_run_id () -> Result> { // Get a random uid for this compiler run and save it as an environment variable. // Load it if it's already there. @@ -30,6 +470,7 @@ fn get_run_id () -> Result> { fn get_state_file_path() -> Result> { let compilation_run_identifier = get_run_id()?; Ok(format!("/tmp/sing-trait-store-{}", compilation_run_identifier)) + } fn save_trait_state(map: HashMap) -> Result> { @@ -49,62 +490,82 @@ fn load_trait_state() -> Result, Box proc_macro::TokenStream { - let input = parse_macro_input!(input as ItemImpl); - let annotated_item = parse_macro_input!(annotated_item as AttributeArgs); - - let trait_name; - match &input.trait_ { - Some((_b, p, _f)) => { - trait_name = p.into_token_stream().to_string(); - }, - None => { - syn::Error::new(input.__span(), "This attribute can only be used on trait impl blocks.") - .to_compile_error(); - trait_name = String::from(""); - }, - }; - - let mut trait_functions = vec!(); - for fun in &input.items { - match fun { - ImplItem::Method(m) => trait_functions.push(m.sig.to_token_stream()), - _ => { - syn::Error::new(input.__span(), "Found unexpected item that is not a method.") - .to_compile_error(); - }, - } - } - - let mut trait_state = load_trait_state().unwrap(); - - let profile = TraitProfile::new(trait_functions); - if trait_state.contains_key(trait_name.as_str()) { - let err = format!("Trait {} is implemented twice!", trait_name); - syn::Error::new(input.__span(), err.as_str()) - .to_compile_error(); - } else { - trait_state.insert(trait_name, profile); - } - - save_trait_state(trait_state).unwrap(); - - // Build the output, possibly using quasi-quotation - let output = quote! { - // ... - }; - - proc_macro::TokenStream::from(output) +fn compile_error_panic(span: Span, msg: &str) { + syn::Error::new(span, msg) + .to_compile_error(); + panic!("{}", msg) } #[cfg(test)] mod tests { + use std::{str::FromStr, collections::HashMap}; + + use proc_macro2::TokenStream; + use sing_util::TraitProfile; + + use crate::{save_trait_state, load_trait_state, add_trait_inner}; #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); + fn save_tokenstream() { + let mut orig = HashMap::new(); + + orig.insert(String::from("test"), TokenStream::from_str(" +fn test(a: Astruct, b: [Box, String]) -> String {} +").unwrap()); + orig.insert(String::from("other_test"), TokenStream::from_str(" +fn other_test(a: Trait1 + Trait2) -> Result> {} +").unwrap(), + ); + + let orig_name = String::from("Test"); + let mut orig_state = HashMap::new(); + orig_state.insert(orig_name.clone(), TraitProfile::new(orig)); + + save_trait_state(orig_state.clone()).unwrap(); + + let new_state = load_trait_state().unwrap(); + + assert_eq!( + orig_state[&orig_name].get_funs().unwrap()["test"].to_string(), + new_state[&orig_name].get_funs().unwrap()["test"].to_string() + ) } + #[test] + fn trait_call() { + let call = TokenStream::from_str(" +impl FruitTree for BananaTree { + fn shake(&self) -> Box { + Box::new(self.bananas.pop()) + } +} + ").unwrap(); + let call = syn::parse2(call).unwrap(); + + add_trait_inner(call, vec!()); + + let new_state = load_trait_state().unwrap(); + + assert_eq!( + TokenStream::from_str(" +fn shake(&self) -> Box +").unwrap().to_string(), + new_state["FruitTree"].get_funs().unwrap()["shake"].to_string() + ) + } + + #[test] + #[should_panic] + fn non_trait_call() { + let call = TokenStream::from_str(" +impl Banana { + pub fn peel(&self) -> Food { + self.inner.clone() + } +} + ").unwrap(); + let call = syn::parse2(call).unwrap(); + + add_trait_inner(call, vec!()); + } } diff --git a/sing_util/src/lib.rs b/sing_util/src/lib.rs index 914ed90..4a630cd 100644 --- a/sing_util/src/lib.rs +++ b/sing_util/src/lib.rs @@ -1,4 +1,5 @@ use core::fmt::Debug; +use std::{error::Error, collections::HashMap}; use proc_macro2::TokenStream; use serde::{Serialize, Deserialize}; @@ -8,55 +9,46 @@ mod serde_wrapper; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TraitProfile{ - //functions: HashMap, - functions: Vec, + functions: HashMap, } impl TraitProfile{ - pub fn new(streams: Vec) -> Self { - let mut inner = vec!(); + pub fn new(funs: HashMap) -> Self { + let mut inner = HashMap::new(); - for stream in streams { - inner.push(TokenStreamWrapper::new(stream)); + for (name, stream) in funs { + inner.insert(name, TokenStreamWrapper::new(stream)); } Self{ functions: inner } } + + pub fn get_funs(&self) -> Result, Box> { + let wrappers = self.functions.clone(); + let mut funs = HashMap::new(); + + for (name, wrapper) in wrappers { + funs.insert(name, wrapper.get_inner()?); + } + + Ok(funs) + } } #[derive(Debug, Clone)] -enum DeduplicatedFunctionProfile { +pub enum DeduplicatedFunctionProfile { Single(String, TokenStreamWrapper), - Multiple(Vec, TokenStreamWrapper), + Multiple(Vec), } -/*impl Evaluator { - fn deduplicate_functions(&self) -> Self { - let mut self = self.clone(); - let mut functions: HashMap; - - for (t_name, t_profile) in self.traits { - for (fun_name, fun_profile) in t_profile.functions { - if !functions.contains_key(fun_name) { - self.functions.insert(fun_name, DeduplicatedFunctionProfile::Single(t_name, fun_profile)) - } else { - let other = self.functions.remove(fun_name).unwrap(); - - let traits: Vec; - match other { - DeduplicatedFunctionProfile::Single(t, _) => traits = vec!(t), - DeduplicatedFunctionProfile::Multiple(ts, _) => traits = ts, - } - - self.functions.insert(fun_name, DeduplicatedFunctionProfile::Multiple(traits, fun_profile)) - } - } - } - } +pub trait TraitCallMessage { + fn get_fun_name() -> String; + fn get_trait_name() -> String; + fn get_params() -> Vec; + fn new_params(p: Vec); } -*/ #[cfg(test)] mod tests { diff --git a/sing_util/src/serde_wrapper.rs b/sing_util/src/serde_wrapper.rs index 3075c97..b027c3a 100644 --- a/sing_util/src/serde_wrapper.rs +++ b/sing_util/src/serde_wrapper.rs @@ -1,17 +1,20 @@ -use std::{str::FromStr, fmt::{self, format}}; +use std::{str::FromStr, fmt::{self, format}, error::Error}; use proc_macro2::TokenStream; use serde::{Serialize, Serializer, Deserialize, Deserializer, de::{Visitor, self}}; -#[derive(Debug, Clone)] -pub struct TokenStreamWrapper(TokenStream); +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TokenStreamWrapper(String); impl TokenStreamWrapper { pub fn new(stream: TokenStream) -> Self { - Self(stream) + Self(stream.to_string()) + } + pub fn get_inner(&self) -> Result> { + Ok(TokenStream::from_str(&self.0.clone())?) } } - +/* impl Serialize for TokenStreamWrapper { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where @@ -29,7 +32,7 @@ impl<'de> Deserialize<'de> for TokenStreamWrapper { { let tok_str = deserializer.deserialize_string(TokenStreamVisitor)?; - match TokenStream::from_str(&tok_str.as_str()) { + match TokenStream::from_str(tok_str.as_str()) { Ok(t) => Ok(Self(t)), Err(e) => Err(de::Error::custom( format!("string does not represent a valid TokenStream: {}", e) @@ -69,3 +72,4 @@ impl<'de> Visitor<'de> for TokenStreamVisitor { } } +*/