Basic functionality of the crate is now present.

This commit is contained in:
Thelie 2022-03-26 23:06:38 +01:00
parent 616ab17008
commit ec7ba974da
12 changed files with 773 additions and 666 deletions

View file

@ -13,13 +13,7 @@ edition = "2021"
[lib] [lib]
bench = false 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] [dependencies]
serde = { version = "1.0", features = ["derive"] } sing_macros = { path = "./sing_macros", version = "0.1.0" }
syn = { version = "1.0", features = ["full"] } sing_parse = { path = "./sing_parse", version = "0.1.0" }
proc-macro2 = "1.0" sing_util = { path = "./sing_util", version = " 0.1.0" }

View file

@ -1,5 +1,5 @@
[package] [package]
name = "sing_derive" name = "sing_macros"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"

View file

@ -2,3 +2,5 @@ use syn::custom_keyword;
custom_keyword!(ioe); custom_keyword!(ioe);
custom_keyword!(msg); custom_keyword!(msg);
custom_keyword!(equal);
custom_keyword!(different);

View file

@ -1,40 +1,32 @@
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, spanned::Spanned, ToTokens};
use sing_util::{DeduplicatedFunctionProfile, TokenStreamWrapper, TraitProfile};
use std::{ use std::{
collections::HashMap, collections::HashMap,
env, env,
fs,
error::Error, error::Error,
io::{Write, Read}, fs,
io::{Read, Write},
}; };
use proc_macro::TokenStream;
use proc_macro2::{TokenStream as TokenStream2, Span};
use sing_util::{DeduplicatedFunctionProfile, TraitProfile};
use syn::{ use syn::{
parse_macro_input, bracketed, parenthesized,
AttributeArgs,
Ident,
ItemImpl,
ImplItem,
NestedMeta,
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
Token, parse2, parse_macro_input, Arm, AttributeArgs, FnArg, Ident, ImplItem, ItemImpl, LitStr, Meta,
parenthesized, NestedMeta, Path, Signature, Stmt, Token,
Arm,
parse2,
LitStr,
Signature,
FnArg,
Path,
Stmt,
}; };
use quote::{quote, spanned::Spanned, ToTokens};
extern crate sing_util;
extern crate proc_macro; extern crate proc_macro;
extern crate sing_util;
mod keyword; 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] #[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 input = parse_macro_input!(input as ItemImpl);
let annotated_item = parse_macro_input!(annotated_item as AttributeArgs); let annotated_item = parse_macro_input!(annotated_item as AttributeArgs);
@ -43,51 +35,72 @@ pub fn sing_add_trait(input: TokenStream, annotated_item: TokenStream) -> Token
TokenStream::from(output) TokenStream::from(output)
} }
/// Used internally by the sing_add_trait macro.
fn add_trait_inner(input: ItemImpl, annotated_item: Vec<NestedMeta>) -> TokenStream2 { fn add_trait_inner(input: ItemImpl, annotated_item: Vec<NestedMeta>) -> TokenStream2 {
let trait_name; let trait_name = match &input.trait_ {
match &input.trait_ { Some((_b, p, _f)) => p.into_token_stream().to_string(),
Some((_b, p, _f)) => {
trait_name = p.into_token_stream().to_string();
},
None => { None => {
compile_error_panic(input.__span(), "This attribute can only be used on trait impl blocks."); compile_error_panic(
trait_name = String::from(""); input.__span(),
}, "This attribute can only be used on trait impl blocks.",
);
String::from("")
}
}; };
// collect all the functions of this block into a hashmap with name -> function mapping
let mut trait_functions = HashMap::new(); let mut trait_functions = HashMap::new();
for fun in &input.items { for fun in &input.items {
match fun { match fun {
ImplItem::Method(m) => {trait_functions.insert( ImplItem::Method(m) => {
m.sig.ident.to_string(), trait_functions.insert(m.sig.ident.to_string(), m.sig.to_token_stream());
m.sig.to_token_stream() }
);},
_ => { _ => {
compile_error_panic(input.__span(), "Found unexpected item that is not a method."); 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(); let mut trait_state = load_trait_state().unwrap();
// Add this trait to it
let profile = TraitProfile::new(trait_functions); let profile = TraitProfile::new(trait_functions);
if trait_state.contains_key(trait_name.as_str()) { if trait_state.contains_key(trait_name.as_str()) {
let err = format!("Trait {} is implemented twice!", trait_name); let err = format!("Trait {} is implemented twice!", trait_name);
syn::Error::new(input.__span(), err.as_str()) syn::Error::new(input.__span(), err.as_str()).to_compile_error();
.to_compile_error();
} else { } else {
trait_state.insert(trait_name, profile); trait_state.insert(trait_name, profile);
} }
save_trait_state(trait_state).unwrap(); save_trait_state(trait_state).unwrap();
// Build the output, possibly using quasi-quotation let gobble_ident = parse2::<Ident>(quote! {gobble}).unwrap();
// TODO: add gobble
quote! { 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;
}
} }
} }
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] #[proc_macro]
pub fn sing_loop(input: TokenStream) -> TokenStream { pub fn sing_loop(input: TokenStream) -> TokenStream {
let span = TokenStream2::from(input.clone()).__span(); let span = TokenStream2::from(input.clone()).__span();
@ -98,152 +111,207 @@ pub fn sing_loop(input: TokenStream) -> TokenStream {
Err(e) => { Err(e) => {
compile_error_panic(span, &e.to_string()); compile_error_panic(span, &e.to_string());
TokenStream2::new() TokenStream2::new()
}, }
}; };
TokenStream::from(output) TokenStream::from(output)
} }
/// Used internally in the sing_loop macro
fn loop_inner(input: LoopParams, span: Span) -> Result<TokenStream2, Box<dyn Error>> { fn loop_inner(input: LoopParams, span: Span) -> Result<TokenStream2, Box<dyn Error>> {
let LoopParams { let LoopParams {
trait_obj, trait_obj,
ser_fun, arg_ser_fun,
de_fun, arg_de_fun,
msg_ser_fun,
msg_de_fun,
reader, reader,
writer, writer,
err_writer, err_writer,
message_type message_type,
arg_type,
} = input; } = input;
let mut ioe_initializers = vec!(); // 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::<Path>(quote! {
sing::callobj_to_string
})?
}
};
// Do the same for the deserializer function.
let msg_de_fun = match msg_de_fun {
Some(f) => f,
None => {
parse2::<Path>(quote! {
sing::callobj_from_string
})?
}
};
// 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![];
// Then, push the statement and name for stdin…
let reader = match reader { let reader = match reader {
Some(r) => r, Some(r) => r,
None => { None => {
ioe_initializers.push(parse2::<Stmt>(quote!{ initializations.push(parse2::<Stmt>(quote! {
let reader = std::io::stdin().lock(); let mut reader = std::io::stdin().lock();
})?); })?);
parse2::<Ident>(quote! { parse2::<Ident>(quote! {
reader reader
})?}, })?
}
}; };
// for stdout…
let writer = match writer { let writer = match writer {
Some(w) => w, Some(w) => w,
None => { None => {
ioe_initializers.push(parse2::<Stmt>(quote!{ initializations.push(parse2::<Stmt>(quote! {
let writer = std::io::stdout().lock(); let mut writer = std::io::stdout().lock();
})?); })?);
parse2::<Ident>(quote! { parse2::<Ident>(quote! {
writer writer
})?}, })?
}
}; };
// and stderr
let err_writer = match err_writer { let err_writer = match err_writer {
Some(w) => w, Some(w) => w,
None => { None => {
ioe_initializers.push(parse2::<Stmt>(quote!{ initializations.push(parse2::<Stmt>(quote! {
let err_writer = std::io::stdin().lock(); let mut err_writer = std::io::stderr().lock();
})?); })?);
parse2::<Ident>(quote! { parse2::<Ident>(quote! {
err_writer err_writer
})?}, })?
}
}; };
let ioe_initializers = ioe_initializers; let initializations = initializations;
// If no message type is given, use default Type
let message_type = match message_type { let message_type = match message_type {
Some(r) => r, Some(m) => m,
None => parse2::<Path>(quote! { None => parse2::<Path>(quote! {
todo!(); sing::CallObj
})?, })?,
}; };
// If no argument type is given, use default Type
let arg_type = match arg_type {
Some(a) => a,
None => parse2::<Path>(quote! {
String
})?,
};
// TODO: Make it possible to choose a subset of the provided traits
let traits = load_trait_state().unwrap(); 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(); let mut funs = HashMap::new();
for (t_name, t_funs) in &traits { for (t_name, t_funs) in &traits {
for (f_name, fun) in t_funs.get_funs()? { for (f_name, fun) in t_funs.get_funs()? {
if funs.contains_key(&f_name) { if funs.contains_key(&f_name) {
funs.insert(f_name.clone(), match &funs[&f_name] { funs.insert(
DeduplicatedFunctionProfile::Single(t, _) => DeduplicatedFunctionProfile::Multiple(vec!(t_name.clone(), t.clone())), f_name.clone(),
match &funs[&f_name] {
DeduplicatedFunctionProfile::Single(t, _) => {
DeduplicatedFunctionProfile::Multiple(vec![t_name.clone(), t.clone()])
}
DeduplicatedFunctionProfile::Multiple(v) => { DeduplicatedFunctionProfile::Multiple(v) => {
let mut v = v.clone(); let mut v = v.clone();
v.push(t_name.clone()); v.push(t_name.clone());
DeduplicatedFunctionProfile::Multiple(v) DeduplicatedFunctionProfile::Multiple(v)
}
}, },
}); );
} else {
funs.insert(
f_name.clone(),
DeduplicatedFunctionProfile::Single(f_name, TokenStreamWrapper::new(fun)),
);
} }
} }
} }
let mut fun_arms = vec!(); // Construct the match arms for the functions
let mut fun_arms = vec![];
for (f_name, f_profile) in funs { 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_lit = LitStr::new(&f_name, span);
let fn_ident = Ident::new(&f_name, span); let fn_ident = Ident::new(&f_name, span);
let arm;
match f_profile { let arm = match f_profile {
DeduplicatedFunctionProfile::Multiple(v) => { DeduplicatedFunctionProfile::Multiple(v) => {
arm = quote!{ quote! {
#fn_lit => { #fn_lit => {
let traits = vec!( #( #v ),* ); let traits = vec!( #( #v ),* );
let message = format!("The function {} is found in the following traits: {}. return Err( format!("The function {} is found in the following traits: {}.
Plese specify which traits function you want to use.", Plese specify which traits function you want to use.",
#f_name, #f_name,
traits, traits,
); ))
<#err_writer as Write>.write_all(message.as_bytes())?;
}, },
}; }
}, }
DeduplicatedFunctionProfile::Single(tr, s) => { DeduplicatedFunctionProfile::Single(_tr, s) => {
let tr = Ident::new(&tr, span); get_fun_arm(
arm = get_fun_arm(
fn_lit, fn_lit,
fn_ident, fn_ident,
ser_fun.clone(), arg_ser_fun.clone(),
de_fun.clone(), arg_de_fun.clone(),
writer.clone(),
trait_obj.clone(), trait_obj.clone(),
tr, s.get_inner()?,
s.get_inner()? )?
)?;
}
} }
};
let arm = parse2::<Arm>(arm)?; let arm = parse2::<Arm>(arm)?;
fun_arms.push(arm); fun_arms.push(arm);
} }
fun_arms.push( // Add two special cases for the function match statement
parse2::<Arm>(quote! { fun_arms.push(parse2::<Arm>(quote! {
"" => { "" => {
let message = String::from("No function name was given! Aborting."); return Err(String::from("No function name was given! Aborting."))
<#err_writer as Write>.write_all(message.as_bytes())?;
}, },
})? })?);
);
fun_arms.push( fun_arms.push(parse2::<Arm>(quote! {
parse2::<Arm>(quote! {
s => { s => {
let message = format!("The function {} is not known! Aborting.", s); return Err(format!("The function {} is not known! Aborting.", s))
<#err_writer as Write>.write_all(message.as_bytes())?;
}, },
})? })?);
);
let mut trait_arms = vec!(); let mut trait_arms = vec![];
for (t_name, t_profile) in traits.clone() { // 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 t_lit = LitStr::new(&t_name, span);
let t_ident = Ident::new(&t_name, span);
let mut f_arms = vec!(); // 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()? { for (f_name, f_profile) in t_profile.get_funs()? {
let fn_lit = LitStr::new(&f_name, span); let fn_lit = LitStr::new(&f_name, span);
let fn_ident = Ident::new(&f_name, span); let fn_ident = Ident::new(&f_name, span);
@ -251,38 +319,33 @@ Plese specify which traits function you want to use.",
let f_arm = get_fun_arm( let f_arm = get_fun_arm(
fn_lit, fn_lit,
fn_ident, fn_ident,
ser_fun.clone(), arg_ser_fun.clone(),
de_fun.clone(), arg_de_fun.clone(),
writer.clone(),
trait_obj.clone(), trait_obj.clone(),
t_ident.clone(), f_profile,
f_profile
)?; )?;
f_arms.push(parse2::<Arm>(f_arm)?); f_arms.push(parse2::<Arm>(f_arm)?);
} }
f_arms.push( // Add two special cases for the function match statement
parse2::<Arm>(quote! { f_arms.push(parse2::<Arm>(quote! {
"" => { "" => {
let message = String::from("No function name was given! Aborting."); return Err(String::from("No function name was given! Aborting."))
<#err_writer as Write>.write_all(message.as_bytes())?;
}, },
})? })?);
);
f_arms.push( f_arms.push(
parse2::<Arm>(quote! { parse2::<Arm>(quote! {
s => { s => {
let message = format!("The function {} was not found in the trait {}! Aborting.", s, #t_lit ); return Err(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! { let t_arm = quote! {
#t_lit => { #t_lit => {
match <message as TraitCallMessage>.get_fun_name().as_str() { match message .get_fun_name().as_str() {
#( #f_arms )* #( #f_arms )*
} }
} }
@ -291,66 +354,188 @@ Plese specify which traits function you want to use.",
trait_arms.push(parse2::<Arm>(t_arm)?); trait_arms.push(parse2::<Arm>(t_arm)?);
} }
Ok(quote!{ trait_arms.push(parse2::<Arm>(quote! {
#( #ioe_initializers )* s => {
'main: loop { return Err(format!("The trait {} is not known! Aborting.", s))
let mut buf = String::new(); },
<#reader as BufRead>.read_line(buf); })?);
match #de_fun ::< #message_type >(buf) { // 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<dyn TraitCallMessage < Representation = #arg_type > >| -> Result<Vec< #arg_type >, 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<dyn std::io::BufRead>, mut inner_writer: Box<dyn std::io::Write>, mut inner_err_writer: Box<dyn std::io::Write>| {
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) => { Ok(message) => {
let mut message = message; let mut message = message;
match <message as TraitCallMessage>.get_trait_name().as_str() { match sing_do_fn_call(Box::new(message.clone())) {
#( #trait_arms )* Ok(res) => message.new_params(res),
} Err(e) => inner_err_writer.write_all(e.as_bytes()).unwrap(),
};
match #ser_fun(message) { match #msg_ser_fun(message) {
Ok(s) => <#writer as Write>.write_all(s), Ok(s) => {
Err(e) => <#err_writer as Write>.write_all("Could not encode call result: {}", e), 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) => <#err_writer as Write>.write_all("Could not decode {} as trait message: {}", buf, e), Err(e) => {
let message = format!("Could not decode {} as trait message: {}", buf, e);
inner_err_writer.write_all(message.as_bytes()).unwrap();
},
} }
} }
};
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 { struct LoopParams {
trait_obj: Ident, trait_obj: Ident,
ser_fun: Path, arg_ser_fun: Path,
de_fun: Path, arg_de_fun: Path,
msg_ser_fun: Option<Path>,
msg_de_fun: Option<Path>,
reader: Option<Ident>, reader: Option<Ident>,
writer: Option<Ident>, writer: Option<Ident>,
err_writer: Option<Ident>, err_writer: Option<Ident>,
message_type: Option<Path>, message_type: Option<Path>,
arg_type: Option<Path>,
} }
impl Parse for LoopParams { 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<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
let trait_obj = input.parse()?; let trait_obj = input.parse()?;
input.parse::<Token![,]>()?; input.parse::<Token![,]>()?;
let ser_fun = input.parse()?; let content;
input.parse::<Token![,]>()?; 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::<Token![,]>()?;
let arg_ser_fun: Path = content.parse()?;
content.parse::<Token![,]>()?;
let arg_de_fun: Path = content.parse()?;
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::<Token![,]>()?;
msg_ser_fun = Some(content.parse()?);
content.parse::<Token![,]>()?;
msg_de_fun = Some(content.parse()?);
}
}
}
let de_fun = input.parse()?;
let lookahead = input.lookahead1(); let lookahead = input.lookahead1();
let mut reader = None; let mut reader = None;
let mut writer = None; let mut writer = None;
let mut err_writer = None; let mut err_writer = None;
let mut message_type = None; let mut message_type = None;
let mut arg_type = None;
'parse: loop { 'parse: loop {
input.parse::<Token![,]>()?; input.parse::<Token![,]>()?;
if input.is_empty() {break 'parse} if input.is_empty() {
break 'parse;
}
if lookahead.peek(keyword::msg) { if lookahead.peek(keyword::msg) {
input.parse::<keyword::msg>()?; input.parse::<keyword::msg>()?;
input.parse::<Token![:]>()?; input.parse::<Token![:]>()?;
let ty = input.parse()?;
message_type = Some(ty); let content;
parenthesized!(content in input);
let m_a_type_err_generic = "The msg keyword expects two variables in the following order: (MessageType, ArgumentType),";
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
}
};
input.parse::<Token![,]>()?;
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) { } else if lookahead.peek(keyword::ioe) {
input.parse::<keyword::ioe>()?; input.parse::<keyword::ioe>()?;
@ -367,58 +552,82 @@ impl Parse for LoopParams {
} else { } else {
compile_error_panic( compile_error_panic(
content.span(), content.span(),
format!("{}, but the Input variable was not supplied.", ioe_err_generic).as_str() format!(
"{}, but the Input variable was not supplied.",
ioe_err_generic
)
.as_str(),
); );
} }
input.parse::<Token![,]>()?;
if lh_inner.peek(Ident) { if lh_inner.peek(Ident) {
let w = content.parse()?; let w = content.parse()?;
writer = Some(w); writer = Some(w);
} else { } else {
compile_error_panic( compile_error_panic(
content.span(), content.span(),
format!("{}, but the Output variable was not supplied.", ioe_err_generic).as_str(), format!(
"{}, but the Output variable was not supplied.",
ioe_err_generic
)
.as_str(),
); );
} }
input.parse::<Token![,]>()?;
if lh_inner.peek(Ident) { if lh_inner.peek(Ident) {
let e = content.parse()?; let e = content.parse()?;
err_writer = Some(e); err_writer = Some(e);
} else { } else {
compile_error_panic( compile_error_panic(
content.span(), content.span(),
format!("{}, but the Error variable was not supplied.", ioe_err_generic).as_str(), format!(
"{}, but the Error variable was not supplied.",
ioe_err_generic
)
.as_str(),
); );
} }
} }
if input.is_empty() {break 'parse} if input.is_empty() {
break 'parse;
} }
}
Ok(Self { Ok(Self {
trait_obj, trait_obj,
ser_fun, arg_ser_fun,
de_fun, arg_de_fun,
msg_ser_fun,
msg_de_fun,
reader, reader,
writer, writer,
err_writer, err_writer,
message_type, message_type,
arg_type,
}) })
} }
} }
/// Construct the match arm for a given trait function as a Tokenstream
fn get_fun_arm( fn get_fun_arm(
fn_lit: LitStr, fn_lit: LitStr,
fn_ident: Ident, fn_ident: Ident,
ser_fun: Path, arg_ser_fun: Path,
de_fun: Path, arg_de_fun: Path,
writer: Ident,
trait_obj: Ident, trait_obj: Ident,
tr: Ident,
s: TokenStream2, s: TokenStream2,
) -> Result<TokenStream2, Box<dyn Error>> { ) -> Result<TokenStream2, Box<dyn Error>> {
let sig = parse2::<Signature>(s)?; let sig = parse2::<Signature>(s)?;
let mut args = vec!(); let mut args = vec![];
let mut parse_lets = vec!(); let mut parse_lets = vec![];
let mut lets_index: usize = 0;
// 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 { for a in sig.inputs {
match a { match a {
// This is just a "self" value which does not need to be in the call // This is just a "self" value which does not need to be in the call
@ -430,32 +639,47 @@ fn get_fun_arm(
args.push(pat.clone()); args.push(pat.clone());
parse_lets.push(parse2::<Stmt>(quote! { parse_lets.push(parse2::<Stmt>(quote! {
let #pat : #ty = #de_fun ( #pat ); let #pat = params[ #lets_index ].clone();
})?); })?);
},
parse_lets.push(parse2::<Stmt>(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! { Ok(quote! {
#fn_lit => { #fn_lit => {
let params = <message as TraitCallMessage>.get_params(); let params = message.get_params();
let ( #( #args ),* ) = params;
#( #parse_lets )* #( #parse_lets )*
let result = <#trait_obj as #tr>. #fn_ident ( #( #args ),* ); let result = #trait_obj . #fn_ident ( #( #args ),* );
<message as TraitCallMessage>.new_params( match #arg_ser_fun (&result) {
vec!(<#ser_fun as Serializer>.serialize(result)) Ok(r) => {
); return Ok(vec!(r))
}
<#writer as Write>.write_all(message.as_bytes())?; Err(e) => {
return Err( format!("Could not serialize result of function call {}: {}", #fn_lit, e))
}
}
}, },
}) })
} }
/// 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<usize, Box<dyn Error>> { fn get_run_id() -> Result<usize, Box<dyn Error>> {
// 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") { match env::var("COMP_RUN_ID") {
Ok(v) => Ok(v.parse()?), Ok(v) => Ok(v.parse()?),
Err(_) => { Err(_) => {
@ -467,13 +691,20 @@ fn get_run_id () -> Result<usize, Box<dyn Error>> {
} }
} }
/// 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<String, Box<dyn Error>> { fn get_state_file_path() -> Result<String, Box<dyn Error>> {
let compilation_run_identifier = get_run_id()?; let compilation_run_identifier = get_run_id()?;
Ok(format!("/tmp/sing-trait-store-{}", compilation_run_identifier)) Ok(format!(
"/tmp/sing-trait-store-{}",
compilation_run_identifier
))
} }
fn save_trait_state(map: HashMap<String, sing_util::TraitProfile>) -> Result<usize, Box<dyn Error>> { /// Saves the trait state constructed in the sing_add_trait macro
fn save_trait_state(
map: HashMap<String, sing_util::TraitProfile>,
) -> Result<usize, Box<dyn Error>> {
let state_file_path = get_state_file_path()?; let state_file_path = get_state_file_path()?;
let mut state_file = fs::File::create(state_file_path)?; let mut state_file = fs::File::create(state_file_path)?;
@ -481,40 +712,60 @@ fn save_trait_state(map: HashMap<String, sing_util::TraitProfile>) -> Result<usi
Ok(state_file.write(state_string.as_bytes())?) 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<HashMap<String, sing_util::TraitProfile>, Box<dyn Error>> { fn load_trait_state() -> Result<HashMap<String, sing_util::TraitProfile>, Box<dyn Error>> {
let stat_file_path = get_state_file_path()?; let state_file_path = get_state_file_path()?;
let mut state_file = fs::File::open(stat_file_path)?; let state_file_path = std::path::Path::new(&state_file_path);
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(""); let mut state_string = String::from("");
state_file.read_to_string(&mut state_string)?; state_file.read_to_string(&mut state_string)?;
Ok(ron::from_str(&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) { fn compile_error_panic(span: Span, msg: &str) {
syn::Error::new(span, msg) syn::Error::new(span, msg).to_compile_error();
.to_compile_error();
panic!("{}", msg) panic!("{}", msg)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{str::FromStr, collections::HashMap}; use std::{collections::HashMap, str::FromStr};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use sing_util::TraitProfile; 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() { fn save_tokenstream() {
let mut orig = HashMap::new(); 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<dyn Error>, String]) -> String {} fn test(a: Astruct, b: [Box<dyn Error>, 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<String, Box<dyn Error>> {} fn other_test(a: Trait1 + Trait2) -> Result<String, Box<dyn Error>> {}
").unwrap(), ",
)
.unwrap(),
); );
let orig_name = String::from("Test"); let orig_name = String::from("Test");
@ -533,23 +784,30 @@ fn other_test(a: Trait1 + Trait2) -> Result<String, Box<dyn Error>> {}
#[test] #[test]
fn trait_call() { fn trait_call() {
let call = TokenStream::from_str(" let call = TokenStream::from_str(
"
impl FruitTree for BananaTree { impl FruitTree for BananaTree {
fn shake(&self) -> Box<dyn Fruit> { fn shake(&self) -> Box<dyn Fruit> {
Box::new(self.bananas.pop()) Box::new(self.bananas.pop())
} }
} }
").unwrap(); ",
)
.unwrap();
let call = syn::parse2(call).unwrap(); let call = syn::parse2(call).unwrap();
add_trait_inner(call, vec!()); add_trait_inner(call, vec![]);
let new_state = load_trait_state().unwrap(); let new_state = load_trait_state().unwrap();
assert_eq!( assert_eq!(
TokenStream::from_str(" TokenStream::from_str(
"
fn shake(&self) -> Box<dyn Fruit> fn shake(&self) -> Box<dyn Fruit>
").unwrap().to_string(), "
)
.unwrap()
.to_string(),
new_state["FruitTree"].get_funs().unwrap()["shake"].to_string() new_state["FruitTree"].get_funs().unwrap()["shake"].to_string()
) )
} }
@ -557,15 +815,18 @@ fn shake(&self) -> Box<dyn Fruit>
#[test] #[test]
#[should_panic] #[should_panic]
fn non_trait_call() { fn non_trait_call() {
let call = TokenStream::from_str(" let call = TokenStream::from_str(
"
impl Banana { impl Banana {
pub fn peel(&self) -> Food { pub fn peel(&self) -> Food {
self.inner.clone() self.inner.clone()
} }
} }
").unwrap(); ",
)
.unwrap();
let call = syn::parse2(call).unwrap(); let call = syn::parse2(call).unwrap();
add_trait_inner(call, vec!()); add_trait_inner(call, vec![]);
} }
} }

View file

@ -17,3 +17,5 @@ syn = "1"
regex = "1" regex = "1"
lalrpop-util = "0.19" lalrpop-util = "0.19"
lalrpop = "0.19" lalrpop = "0.19"
sing_util = { path = "../sing_util" }
serde = { version = "1.0", features = ["derive"] }

View file

@ -1,5 +1,8 @@
use std::fmt; use std::fmt;
use sing_util::TraitCallMessage;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallObj { pub struct CallObj {
trait_string: Option<String>, trait_string: Option<String>,
fun_string: String, 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<String> {
self.trait_string.clone()
}
fn get_params(&self) -> Vec<Self::Representation> {
self.data.clone()
}
fn new_params(&mut self, p: Vec<Self::Representation>) {
self.data = p;
}
}
impl fmt::Display for CallObj { impl fmt::Display for CallObj {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut repr = String::new(); let mut repr = String::new();

View file

@ -13,10 +13,10 @@ Descriptor: (Option<String>, String, usize) = {
<t: (<Identifier> ">")?> <f:Identifier> => (t, f, usize::MAX), <t: (<Identifier> ">")?> <f:Identifier> => (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(); Index: usize = r"[1-9][0-9]*" => usize::from_str(<>).unwrap();
Data: Vec<String> = Value+; Data: Vec<String> = Value*;
Value: String = r" [0-9a-zA-Z]*" => (<>[1..<>.chars().count()]).to_string(); Value: String = r" [0-9a-zA-Z]*" => (<>[1..<>.chars().count()]).to_string();

View file

@ -1,3 +1,7 @@
use std::{error::Error};
pub use callobj::CallObj;
#[macro_use] extern crate lalrpop_util; #[macro_use] extern crate lalrpop_util;
extern crate lalrpop; extern crate lalrpop;
@ -5,6 +9,15 @@ mod callobj;
lalrpop_mod!(fun_parser); lalrpop_mod!(fun_parser);
pub fn callobj_to_string(o: CallObj) -> Result<String, Box<dyn Error>> {
Ok(o.to_string())
}
pub fn callobj_from_string(s: String) -> Result<CallObj, Box<dyn Error>> {
// TODO: This should use a "?", but for some reason the error references s
Ok(fun_parser::CallParser::new().parse(&s).unwrap())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::fun_parser; use crate::fun_parser;

View file

@ -3,10 +3,11 @@ use std::{error::Error, collections::HashMap};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use serde_wrapper::TokenStreamWrapper; pub use serde_wrapper::TokenStreamWrapper;
mod serde_wrapper; mod serde_wrapper;
/// Represents a trait as Function names associated with tokenstreams.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraitProfile{ pub struct TraitProfile{
functions: HashMap<String, TokenStreamWrapper>, functions: HashMap<String, TokenStreamWrapper>,
@ -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)] #[derive(Debug, Clone)]
pub enum DeduplicatedFunctionProfile { pub enum DeduplicatedFunctionProfile {
Single(String, TokenStreamWrapper), Single(String, TokenStreamWrapper),
Multiple(Vec<String>), Multiple(Vec<String>),
} }
/// 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 { pub trait TraitCallMessage {
fn get_fun_name() -> String; type Representation;
fn get_trait_name() -> String;
fn get_params() -> Vec<String>; fn get_fun_name(&self) -> String;
fn new_params(p: Vec<String>); fn get_trait_name(&self) -> Option<String>;
fn get_params(&self) -> Vec<Self::Representation>;
fn new_params(&mut self, p: Vec<Self::Representation>);
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,8 +1,10 @@
use std::{str::FromStr, fmt::{self, format}, error::Error}; use std::{str::FromStr, error::Error};
use proc_macro2::TokenStream; 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)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenStreamWrapper(String); pub struct TokenStreamWrapper(String);
@ -14,62 +16,3 @@ impl TokenStreamWrapper {
Ok(TokenStream::from_str(&self.0.clone())?) Ok(TokenStream::from_str(&self.0.clone())?)
} }
} }
/*
impl Serialize for TokenStreamWrapper {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer
{
let inner = &self.0;
serializer.serialize_newtype_struct("Lifetime", &inner.to_string())
}
}
impl<'de> Deserialize<'de> for TokenStreamWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(value.to_string())
}
fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(value.to_string())
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(value)
}
}
*/

View file

@ -1,70 +1,3 @@
use core::fmt::Debug; pub use sing_macros::*;
pub use sing_parse::*;
use proc_macro2::TokenStream; pub use sing_util::TraitCallMessage;
use serde::{Serialize, Deserialize};
use serde_wrapper::TokenStreamWrapper;
mod serde_wrapper;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TraitProfile{
//functions: HashMap<String, FunctionProfile>,
functions: Vec<TokenStreamWrapper>,
}
impl TraitProfile{
pub fn new(streams: Vec<TokenStream>) -> 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<String>, TokenStreamWrapper),
}
/*impl<T> Evaluator<T> {
fn deduplicate_functions(&self) -> Self<T> {
let mut self = self.clone();
let mut functions: HashMap<String, DeduplicatedFunctionProfile>;
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<String>;
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);
}
}

View file

@ -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<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer
{
let inner = &self.0;
serializer.serialize_newtype_struct("Lifetime", &inner.to_string())
}
}
impl<'de> Deserialize<'de> for TokenStreamWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(value.to_string())
}
fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(value.to_string())
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(value)
}
}