Basic functionality of the crate is now present.
This commit is contained in:
parent
616ab17008
commit
ec7ba974da
12 changed files with 773 additions and 666 deletions
12
Cargo.toml
12
Cargo.toml
|
@ -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" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "sing_derive"
|
name = "sing_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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![]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"] }
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
73
src/lib.rs
73
src/lib.rs
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in a new issue