diff --git a/Cargo.toml b/Cargo.toml index 6ad597c..8525a86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,7 @@ edition = "2021" [lib] bench = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -# build = "build.rs" -# -# [build-dependencies] -# lalrpop = "0.19" -# [dependencies] -serde = { version = "1.0", features = ["derive"] } -syn = { version = "1.0", features = ["full"] } -proc-macro2 = "1.0" +sing_macros = { path = "./sing_macros", version = "0.1.0" } +sing_parse = { path = "./sing_parse", version = "0.1.0" } +sing_util = { path = "./sing_util", version = " 0.1.0" } diff --git a/sing_macros/Cargo.toml b/sing_macros/Cargo.toml index a9b0e9f..638644f 100644 --- a/sing_macros/Cargo.toml +++ b/sing_macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sing_derive" +name = "sing_macros" version = "0.1.0" edition = "2021" diff --git a/sing_macros/src/keyword.rs b/sing_macros/src/keyword.rs index d945e93..6724b4c 100644 --- a/sing_macros/src/keyword.rs +++ b/sing_macros/src/keyword.rs @@ -2,3 +2,5 @@ use syn::custom_keyword; custom_keyword!(ioe); custom_keyword!(msg); +custom_keyword!(equal); +custom_keyword!(different); diff --git a/sing_macros/src/lib.rs b/sing_macros/src/lib.rs index b2ddfe3..c0047b1 100644 --- a/sing_macros/src/lib.rs +++ b/sing_macros/src/lib.rs @@ -1,571 +1,832 @@ -use std::{ - collections::HashMap, - env, - fs, - error::Error, - io::{Write, Read}, -}; 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 proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, spanned::Spanned, ToTokens}; +use sing_util::{DeduplicatedFunctionProfile, TokenStreamWrapper, TraitProfile}; +use std::{ + collections::HashMap, + env, + error::Error, + fs, + io::{Read, Write}, +}; +use syn::{ + bracketed, parenthesized, + parse::{Parse, ParseStream}, + parse2, parse_macro_input, Arm, AttributeArgs, FnArg, Ident, ImplItem, ItemImpl, LitStr, Meta, + NestedMeta, Path, Signature, Stmt, Token, +}; -extern crate sing_util; extern crate proc_macro; +extern crate sing_util; mod keyword; +/// Add the trait in this ipml block to sings trait store. +/// +/// The impl block needs to contain all functions as usual. +/// After adding a trait, it can be used by the sing_loop macro. #[proc_macro_attribute] -pub fn sing_add_trait(input: TokenStream, annotated_item: TokenStream) -> TokenStream { +pub fn sing_add_trait(annotated_item: TokenStream, input: 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); + let annotated_item = parse_macro_input!(annotated_item as AttributeArgs); + + let output = add_trait_inner(input, annotated_item); TokenStream::from(output) } +/// Used internally by the sing_add_trait macro. 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 trait_name = match &input.trait_ { + Some((_b, p, _f)) => p.into_token_stream().to_string(), + None => { + compile_error_panic( + input.__span(), + "This attribute can only be used on trait impl blocks.", + ); + 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."); - }, - } + // collect all the functions of this block into a hashmap with name -> function mapping + 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.", + ); + } + } + } + + // Get the currently saved list of traits + let mut trait_state = load_trait_state().unwrap(); + + // Add this trait to it + 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(); + + let gobble_ident = parse2::(quote! {gobble}).unwrap(); + + let mut gobble_flag = false; + + for flag in annotated_item { + if let NestedMeta::Meta(Meta::Path(p)) = flag { + if p.get_ident().unwrap() == &gobble_ident { + gobble_flag = true; + } + } } - 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! { - // ... + if gobble_flag { + quote! {} + } else { + quote! { #input } } } +/// Creates a main loop that parses stdin (or the given input stream), +/// evaluates the called function and returns the result. +/// +/// TODO: document syntax #[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() - }, - }; + 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) } +/// Used internally in the sing_loop macro 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 LoopParams { + trait_obj, + arg_ser_fun, + arg_de_fun, + msg_ser_fun, + msg_de_fun, + reader, + writer, + err_writer, + message_type, + arg_type, + } = input; - let reader = match reader { - Some(r) => r, - None => { - ioe_initializers.push(parse2::(quote!{ - let reader = std::io::stdin().lock(); - })?); - - parse2::(quote!{ - reader - })?}, - }; + // If no msg serializer function is passed, + // use the sing message serializer function as default. + let msg_ser_fun = match msg_ser_fun { + Some(f) => f, + None => { + parse2::(quote! { + sing::callobj_to_string + })? + } + }; - 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!(); - })?, - }; + // Do the same for the deserializer function. + let msg_de_fun = match msg_de_fun { + Some(f) => f, + None => { + parse2::(quote! { + sing::callobj_from_string + })? + } + }; - let traits = load_trait_state().unwrap(); + // In the case that no streams were passed, + // use stdin, stdout and stderr. + // First, create a vec for the let statements + let mut initializations = vec![]; - 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) - }, - }); - } - } - } + // Then, push the statement and name for stdin… + let reader = match reader { + Some(r) => r, + None => { + initializations.push(parse2::(quote! { + let mut reader = std::io::stdin().lock(); + })?); - 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; + parse2::(quote! { + reader + })? + } + }; - 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()? - )?; - } - } + // for stdout… + let writer = match writer { + Some(w) => w, + None => { + initializations.push(parse2::(quote! { + let mut writer = std::io::stdout().lock(); + })?); + + parse2::(quote! { + writer + })? + } + }; + + // and stderr + let err_writer = match err_writer { + Some(w) => w, + None => { + initializations.push(parse2::(quote! { + let mut err_writer = std::io::stderr().lock(); + })?); + + parse2::(quote! { + err_writer + })? + } + }; + + let initializations = initializations; + + // If no message type is given, use default Type + let message_type = match message_type { + Some(m) => m, + None => parse2::(quote! { + sing::CallObj + })?, + }; + + // If no argument type is given, use default Type + let arg_type = match arg_type { + Some(a) => a, + None => parse2::(quote! { + String + })?, + }; + + // TODO: Make it possible to choose a subset of the provided traits + let traits = load_trait_state().unwrap(); + + // Create vector of functions for trait-less calls. + // If a function name is used in more than one trait, + // the user should be alerted when trying to call the ambiguos function. + 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) + } + }, + ); + } else { + funs.insert( + f_name.clone(), + DeduplicatedFunctionProfile::Single(f_name, TokenStreamWrapper::new(fun)), + ); + } + } + } + + // Construct the match arms for the functions + let mut fun_arms = vec![]; + for (f_name, f_profile) in funs { + // The function name is needed both as a literal string and + // an ident keyword for the quote invocation. + let fn_lit = LitStr::new(&f_name, span); + let fn_ident = Ident::new(&f_name, span); - let arm = parse2::(arm)?; - fun_arms.push(arm); - } + let arm = match f_profile { + DeduplicatedFunctionProfile::Multiple(v) => { + quote! { + #fn_lit => { + let traits = vec!( #( #v ),* ); + return Err( format!("The function {} is found in the following traits: {}. + Plese specify which traits function you want to use.", + #f_name, + traits, + )) + }, + } + } + DeduplicatedFunctionProfile::Single(_tr, s) => { + get_fun_arm( + fn_lit, + fn_ident, + arg_ser_fun.clone(), + arg_de_fun.clone(), + trait_obj.clone(), + s.get_inner()?, + )? + } + }; - fun_arms.push( - parse2::(quote! { - "" => { - let message = String::from("No function name was given! Aborting."); - <#err_writer as Write>.write_all(message.as_bytes())?; - }, - })? - ); + let arm = parse2::(arm)?; + fun_arms.push(arm); + } - 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())?; - }, - })? - ); + // Add two special cases for the function match statement + fun_arms.push(parse2::(quote! { + "" => { + return Err(String::from("No function name was given! Aborting.")) + }, + })?); - let mut trait_arms = vec!(); + fun_arms.push(parse2::(quote! { + s => { + return Err(format!("The function {} is not known! Aborting.", s)) + }, + })?); + + 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); + // Create vector of match arms for calls with a trait name. + // All functions of a given trait should be represented, + // as well as The case of an empty function name, which should warn the user + for (t_name, t_profile) in traits { + // The trait name is needed both as a literal string and + // an ident keyword for the quote invocation. + let t_lit = LitStr::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 + // Create vector of all possible function calls for this trait + 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, + arg_ser_fun.clone(), + arg_de_fun.clone(), + trait_obj.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::(f_arm)?); + } - f_arms.push( + // Add two special cases for the function match statement + f_arms.push(parse2::(quote! { + "" => { + return Err(String::from("No function name was given! Aborting.")) + }, + })?); + + 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())?; + return Err(format!("The function {} was not found in the trait {}! Aborting.", s, #t_lit )) } })? ); - let t_arm = quote! { - #t_lit => { - match .get_fun_name().as_str() { - #( #f_arms )* + let t_arm = quote! { + #t_lit => { + match message .get_fun_name().as_str() { + #( #f_arms )* + } + } + }; + + trait_arms.push(parse2::(t_arm)?); + } + + trait_arms.push(parse2::(quote! { + s => { + return Err(format!("The trait {} is not known! Aborting.", s)) + }, + })?); + + // Construct the finished Tokenstream + // First, initialize the input output and error streams if needed + // Then loop by getting a call from input, + // processing it by the match statements constructed above and write to output. + Ok(quote! { + use std::io::{BufRead, Write}; + use sing::TraitCallMessage; + + #( #initializations )* + + // TODO: make this return the messages arg type vector + let mut sing_do_fn_call = |mut message: Box >| -> Result, std::string::String> { + match message.get_trait_name() { + Some(n) => match n.as_str() { + #( #trait_arms )* + } + None => match message.get_fun_name().as_str() { + #( #fun_arms )* + } + } + }; + + let mut sing_loop_inner = |mut inner_reader: Box, mut inner_writer: Box, mut inner_err_writer: Box| { + loop { + let mut buf = String::new(); + inner_reader.read_line(&mut buf); + let result: Result< #message_type, Box< dyn std::error::Error >> = #msg_de_fun (buf.clone()); + + match result { + Ok(message) => { + let mut message = message; + + match sing_do_fn_call(Box::new(message.clone())) { + Ok(res) => message.new_params(res), + Err(e) => inner_err_writer.write_all(e.as_bytes()).unwrap(), + }; + + match #msg_ser_fun(message) { + Ok(s) => { + inner_writer.write_all(s.as_bytes()).unwrap(); + inner_writer.write_all("\n".as_bytes()).unwrap(); + inner_writer.flush(); + }, + Err(e) => { + let message = format!("Could not encode call result: {}", e); + inner_err_writer.write_all(message.as_bytes()).unwrap(); + }, + } + }, + Err(e) => { + let message = format!("Could not decode {} as trait message: {}", buf, e); + inner_err_writer.write_all(message.as_bytes()).unwrap(); + }, } } }; - 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), - } - } - }) + sing_loop_inner(Box::new(#reader), Box::new(#writer), Box::new(#err_writer)); + + }) } +/// Used for parsing the ser and de function syntax +enum EqDiff { + Equal(keyword::equal), + Diff(keyword::different), +} + +/// Representation of the parsed syntax for the sing_loop macro struct LoopParams { - trait_obj: Ident, - ser_fun: Path, - de_fun: Path, - reader: Option, - writer: Option, - err_writer: Option, - message_type: Option, + trait_obj: Ident, + arg_ser_fun: Path, + arg_de_fun: Path, + msg_ser_fun: Option, + msg_de_fun: Option, + reader: Option, + writer: Option, + err_writer: Option, + message_type: Option, + arg_type: Option, } impl Parse for LoopParams { + /// Parses a given input into a Loopparams object + /// according to the syntax outlined in the documentation + /// of the sing_loop macro fn parse(input: ParseStream) -> syn::Result { - let trait_obj = input.parse()?; - input.parse::()?; + let trait_obj = input.parse()?; + input.parse::()?; - let ser_fun = input.parse()?; - input.parse::()?; + let content; + bracketed!(content in input); + let lh_inner = content.lookahead1(); + + let eq_dif_token = if lh_inner.peek(keyword::equal) { + EqDiff::Equal(content.parse()?) + } else { + EqDiff::Diff(content.parse()?) + }; + content.parse::()?; + + let arg_ser_fun: Path = content.parse()?; + content.parse::()?; + let arg_de_fun: Path = content.parse()?; - let de_fun = input.parse()?; - let lookahead = input.lookahead1(); + let mut msg_ser_fun = None; + let mut msg_de_fun = None; + + match eq_dif_token { + EqDiff::Equal(_) => { + msg_ser_fun = Some(arg_ser_fun.clone()); + msg_de_fun = Some(arg_de_fun.clone()); + } + EqDiff::Diff(_) => { + if !content.is_empty() { + content.parse::()?; + msg_ser_fun = Some(content.parse()?); + content.parse::()?; + msg_de_fun = Some(content.parse()?); + } + } + } + + let lookahead = input.lookahead1(); + + let mut reader = None; + let mut writer = None; + let mut err_writer = None; + let mut message_type = None; + let mut arg_type = None; + + 'parse: loop { + input.parse::()?; + if input.is_empty() { + break 'parse; + } + if lookahead.peek(keyword::msg) { + input.parse::()?; + input.parse::()?; - 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),"; + parenthesized!(content in input); + let m_a_type_err_generic = "The msg keyword expects two variables in the following order: (MessageType, ArgumentType),"; - 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() - ); - } + message_type = match content.parse() { + Ok(m) => Some(m), + Err(e) => { + compile_error_panic( + e.span(), + format!( + "{}, but an error occured while parsing the MessageType: {}", + m_a_type_err_generic, + e, + ).as_str(), + ); + None + } + }; - 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(), - ); - } + input.parse::()?; - 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, - }) - } + arg_type = match content.parse() { + Ok(a) => Some(a), + Err(e) => { + compile_error_panic( + e.span(), + format!( + "{}, but an error occured while parsing the ArgumentType: {}", + m_a_type_err_generic, + e, + ).as_str(), + ); + None + } + }; + + } 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(), + ); + } + + input.parse::()?; + + 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(), + ); + } + + input.parse::()?; + + 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, + arg_ser_fun, + arg_de_fun, + msg_ser_fun, + msg_de_fun, + reader, + writer, + err_writer, + message_type, + arg_type, + }) + } } - + +/// Construct the match arm for a given trait function as a Tokenstream 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, + fn_lit: LitStr, + fn_ident: Ident, + arg_ser_fun: Path, + arg_de_fun: Path, + trait_obj: Ident, + s: TokenStream2, ) -> Result> { let sig = parse2::(s)?; - let mut args = vec!(); - let mut parse_lets = vec!(); + let mut args = vec![]; + let mut parse_lets = vec![]; + let mut lets_index: usize = 0; - 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; + // Extract the types of the functions arguments and + // produce let statements that create them from Strings using the de_fun function + 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 ); + args.push(pat.clone()); + + parse_lets.push(parse2::(quote! { + let #pat = params[ #lets_index ].clone(); })?); - }, - } - } - - Ok(quote!{ + + parse_lets.push(parse2::(quote! { + let #pat : #ty = match #arg_de_fun ( & #pat ) { + Ok(p) => p, + Err(e) => return Err(format!("Could not serialize input \"{}\" : {}", #pat, e)), + }; + })?); + + lets_index += 1 + } + } + } + + // TODO: check length of params before call + + // Construct the arm, matching the function name, + // parsing the parameters and calling the function + Ok(quote! { #fn_lit => { - let params = .get_params(); + let params = message.get_params(); + + #( #parse_lets )* - 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())?; - }, + let result = #trait_obj . #fn_ident ( #( #args ),* ); + match #arg_ser_fun (&result) { + Ok(r) => { + return Ok(vec!(r)) + } + Err(e) => { + return Err( format!("Could not serialize result of function call {}: {}", #fn_lit, e)) + } + } + }, }) } -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. - match env::var("COMP_RUN_ID") { - Ok(v) => Ok(v.parse()?), - Err(_) => { - let id: usize = rand::random(); - let id_str = format!("{}", id); - env::set_var("COMP_RUN_ID", id_str); - Ok(id) - } - } +/// Retrieves the unique ID for this compilation run from the COMP_RUN_ID env variable +/// Creates a random ID if none is present and saves it +fn get_run_id() -> Result> { + match env::var("COMP_RUN_ID") { + Ok(v) => Ok(v.parse()?), + Err(_) => { + let id: usize = rand::random(); + let id_str = format!("{}", id); + env::set_var("COMP_RUN_ID", id_str); + Ok(id) + } + } } +/// Constructs and returns a path unique to this compilation run to save the trait state in +/// Utilizes the get_run_id function fn get_state_file_path() -> Result> { - let compilation_run_identifier = get_run_id()?; - Ok(format!("/tmp/sing-trait-store-{}", compilation_run_identifier)) - + let compilation_run_identifier = get_run_id()?; + Ok(format!( + "/tmp/sing-trait-store-{}", + compilation_run_identifier + )) } -fn save_trait_state(map: HashMap) -> Result> { - let state_file_path = get_state_file_path()?; - let mut state_file = fs::File::create(state_file_path)?; +/// Saves the trait state constructed in the sing_add_trait macro +fn save_trait_state( + map: HashMap, +) -> Result> { + let state_file_path = get_state_file_path()?; + let mut state_file = fs::File::create(state_file_path)?; - let state_string = ron::to_string(&map)?; - Ok(state_file.write(state_string.as_bytes())?) + let state_string = ron::to_string(&map)?; + Ok(state_file.write(state_string.as_bytes())?) } +/// Loads the trait state constructed in the sing_add_trait macro. +/// Creates new trait state file if none is found. fn load_trait_state() -> Result, Box> { - let stat_file_path = get_state_file_path()?; - let mut state_file = fs::File::open(stat_file_path)?; + let state_file_path = get_state_file_path()?; + let state_file_path = std::path::Path::new(&state_file_path); - let mut state_string= String::from(""); - state_file.read_to_string(&mut state_string)?; - Ok(ron::from_str(&state_string)?) + if !state_file_path.exists() { + return Ok(HashMap::new()) + } + + let mut state_file = fs::File::open(state_file_path)?; + + let mut state_string = String::from(""); + state_file.read_to_string(&mut state_string)?; + Ok(ron::from_str(&state_string)?) } +/// Shorthand for creating a compile error and then panicing. +/// Useful for testing macros outside of a compilation run. fn compile_error_panic(span: Span, msg: &str) { - syn::Error::new(span, msg) - .to_compile_error(); - panic!("{}", msg) + syn::Error::new(span, msg).to_compile_error(); + panic!("{}", msg) } #[cfg(test)] mod tests { - use std::{str::FromStr, collections::HashMap}; + use std::{collections::HashMap, str::FromStr}; use proc_macro2::TokenStream; use sing_util::TraitProfile; - use crate::{save_trait_state, load_trait_state, add_trait_inner}; + use crate::{add_trait_inner, load_trait_state, save_trait_state}; - #[test] + #[test] fn save_tokenstream() { let mut orig = HashMap::new(); - orig.insert(String::from("test"), TokenStream::from_str(" + 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(" +", + ) + .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)); +", + ) + .unwrap(), + ); - 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() - ) + 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(" + #[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(); + ", + ) + .unwrap(); + let call = syn::parse2(call).unwrap(); - add_trait_inner(call, vec!()); + add_trait_inner(call, vec![]); - let new_state = load_trait_state().unwrap(); - - assert_eq!( - TokenStream::from_str(" + 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() - ) - } +" + ) + .unwrap() + .to_string(), + new_state["FruitTree"].get_funs().unwrap()["shake"].to_string() + ) + } - #[test] - #[should_panic] - fn non_trait_call() { - let call = TokenStream::from_str(" + #[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(); + ", + ) + .unwrap(); + let call = syn::parse2(call).unwrap(); - add_trait_inner(call, vec!()); - } + add_trait_inner(call, vec![]); + } } diff --git a/sing_parse/Cargo.toml b/sing_parse/Cargo.toml index 4fd9ec0..cb35cd0 100644 --- a/sing_parse/Cargo.toml +++ b/sing_parse/Cargo.toml @@ -16,4 +16,6 @@ lalrpop = "0.19" syn = "1" regex = "1" lalrpop-util = "0.19" -lalrpop = "0.19" \ No newline at end of file +lalrpop = "0.19" +sing_util = { path = "../sing_util" } +serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/sing_parse/src/callobj.rs b/sing_parse/src/callobj.rs index 93c6290..95d4161 100644 --- a/sing_parse/src/callobj.rs +++ b/sing_parse/src/callobj.rs @@ -1,5 +1,8 @@ use std::fmt; +use sing_util::TraitCallMessage; +use serde::{Serialize, Deserialize}; +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct CallObj { trait_string: Option, fun_string: String, @@ -18,6 +21,26 @@ impl CallObj { } } +impl TraitCallMessage for CallObj { + type Representation = String; + + fn get_fun_name(&self) -> String { + self.fun_string.clone() + } + + fn get_trait_name(&self) -> Option { + self.trait_string.clone() + } + + fn get_params(&self) -> Vec { + self.data.clone() + } + + fn new_params(&mut self, p: Vec) { + self.data = p; + } +} + impl fmt::Display for CallObj { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut repr = String::new(); diff --git a/sing_parse/src/fun_parser.lalrpop b/sing_parse/src/fun_parser.lalrpop index 0ca72ab..1ecd659 100644 --- a/sing_parse/src/fun_parser.lalrpop +++ b/sing_parse/src/fun_parser.lalrpop @@ -13,10 +13,10 @@ Descriptor: (Option, String, usize) = { ">")?> => (t, f, usize::MAX), } -Identifier: String = r"[A-Z][a-zA-Z]*" => <>.to_string(); +Identifier: String = r"[a-zA-Z]*" => <>.to_string(); Index: usize = r"[1-9][0-9]*" => usize::from_str(<>).unwrap(); -Data: Vec = Value+; +Data: Vec = Value*; Value: String = r" [0-9a-zA-Z]*" => (<>[1..<>.chars().count()]).to_string(); diff --git a/sing_parse/src/lib.rs b/sing_parse/src/lib.rs index 3a5cf3c..7081755 100644 --- a/sing_parse/src/lib.rs +++ b/sing_parse/src/lib.rs @@ -1,3 +1,7 @@ +use std::{error::Error}; + +pub use callobj::CallObj; + #[macro_use] extern crate lalrpop_util; extern crate lalrpop; @@ -5,6 +9,15 @@ mod callobj; lalrpop_mod!(fun_parser); +pub fn callobj_to_string(o: CallObj) -> Result> { + Ok(o.to_string()) +} + +pub fn callobj_from_string(s: String) -> Result> { + // TODO: This should use a "?", but for some reason the error references s + Ok(fun_parser::CallParser::new().parse(&s).unwrap()) +} + #[cfg(test)] mod tests { use crate::fun_parser; diff --git a/sing_util/src/lib.rs b/sing_util/src/lib.rs index 4a630cd..cf33ad8 100644 --- a/sing_util/src/lib.rs +++ b/sing_util/src/lib.rs @@ -3,10 +3,11 @@ use std::{error::Error, collections::HashMap}; use proc_macro2::TokenStream; use serde::{Serialize, Deserialize}; -use serde_wrapper::TokenStreamWrapper; +pub use serde_wrapper::TokenStreamWrapper; mod serde_wrapper; +/// Represents a trait as Function names associated with tokenstreams. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TraitProfile{ functions: HashMap, @@ -37,17 +38,23 @@ impl TraitProfile{ } } +/// Represents the state of a function name, +/// with it either being only present once or existing multiple times. #[derive(Debug, Clone)] pub enum DeduplicatedFunctionProfile { Single(String, TokenStreamWrapper), Multiple(Vec), } +/// Needs to be implemented by all structs that are used as +/// function calls that are sent through a sing I/O stream pub trait TraitCallMessage { - fn get_fun_name() -> String; - fn get_trait_name() -> String; - fn get_params() -> Vec; - fn new_params(p: Vec); + type Representation; + + fn get_fun_name(&self) -> String; + fn get_trait_name(&self) -> Option; + fn get_params(&self) -> Vec; + fn new_params(&mut self, p: Vec); } #[cfg(test)] diff --git a/sing_util/src/serde_wrapper.rs b/sing_util/src/serde_wrapper.rs index b027c3a..51fe722 100644 --- a/sing_util/src/serde_wrapper.rs +++ b/sing_util/src/serde_wrapper.rs @@ -1,8 +1,10 @@ -use std::{str::FromStr, fmt::{self, format}, error::Error}; +use std::{str::FromStr, error::Error}; use proc_macro2::TokenStream; -use serde::{Serialize, Serializer, Deserialize, Deserializer, de::{Visitor, self}}; +use serde::{Serialize, Deserialize}; +/// Wraps the proc_macro2 Tokenstream type, +/// so it can be serialized and deserialized. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TokenStreamWrapper(String); @@ -14,62 +16,3 @@ impl TokenStreamWrapper { Ok(TokenStream::from_str(&self.0.clone())?) } } -/* -impl Serialize for TokenStreamWrapper { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - S: Serializer - { - let inner = &self.0; - serializer.serialize_newtype_struct("Lifetime", &inner.to_string()) - } -} - -impl<'de> Deserialize<'de> for TokenStreamWrapper { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de> - { - let tok_str = deserializer.deserialize_string(TokenStreamVisitor)?; - - 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) - )) - } - } -} - -struct TokenStreamVisitor; - -impl<'de> Visitor<'de> for TokenStreamVisitor { - type Value = String; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("A string representing a TokenStream") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - Ok(value.to_string()) - } - - fn visit_borrowed_str(self, value: &'de str) -> Result - where - E: de::Error, - { - Ok(value.to_string()) - } - - fn visit_string(self, value: String) -> Result - where - E: de::Error, - { - Ok(value) - } - -} -*/ diff --git a/src/lib.rs b/src/lib.rs index 66c12b5..69210a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,70 +1,3 @@ -use core::fmt::Debug; - -use proc_macro2::TokenStream; -use serde::{Serialize, Deserialize}; -use serde_wrapper::TokenStreamWrapper; - -mod serde_wrapper; - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct TraitProfile{ - //functions: HashMap, - functions: Vec, -} - -impl TraitProfile{ - pub fn new(streams: Vec) -> Self { - let mut inner = vec!(); - - for stream in streams { - inner.push(TokenStreamWrapper::new(stream)); - } - - Self{ - functions: inner - } - } -} - -#[derive(Debug, Clone)] -enum DeduplicatedFunctionProfile { - Single(String, TokenStreamWrapper), - Multiple(Vec, TokenStreamWrapper), -} - -/*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)) - } - } - } - } -} -*/ - -#[cfg(test)] -mod tests { - - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } - -} +pub use sing_macros::*; +pub use sing_parse::*; +pub use sing_util::TraitCallMessage; diff --git a/src/serde_wrapper.rs b/src/serde_wrapper.rs deleted file mode 100644 index 3075c97..0000000 --- a/src/serde_wrapper.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::{str::FromStr, fmt::{self, format}}; - -use proc_macro2::TokenStream; -use serde::{Serialize, Serializer, Deserialize, Deserializer, de::{Visitor, self}}; - -#[derive(Debug, Clone)] -pub struct TokenStreamWrapper(TokenStream); - -impl TokenStreamWrapper { - pub fn new(stream: TokenStream) -> Self { - Self(stream) - } -} - -impl Serialize for TokenStreamWrapper { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - S: Serializer - { - let inner = &self.0; - serializer.serialize_newtype_struct("Lifetime", &inner.to_string()) - } -} - -impl<'de> Deserialize<'de> for TokenStreamWrapper { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de> - { - let tok_str = deserializer.deserialize_string(TokenStreamVisitor)?; - - 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) - )) - } - } -} - -struct TokenStreamVisitor; - -impl<'de> Visitor<'de> for TokenStreamVisitor { - type Value = String; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("A string representing a TokenStream") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - Ok(value.to_string()) - } - - fn visit_borrowed_str(self, value: &'de str) -> Result - where - E: de::Error, - { - Ok(value.to_string()) - } - - fn visit_string(self, value: String) -> Result - where - E: de::Error, - { - Ok(value) - } - -}