Bulk of the code is in place, no debugging done yet.

This commit is contained in:
Thelie 2022-03-10 13:59:51 +01:00
parent cb3bbdbb78
commit 616ab17008
4 changed files with 552 additions and 91 deletions

View file

@ -0,0 +1,4 @@
use syn::custom_keyword;
custom_keyword!(ioe);
custom_keyword!(msg);

View file

@ -5,14 +5,454 @@ use std::{
error::Error, error::Error,
io::{Write, Read}, io::{Write, Read},
}; };
use sing_util::TraitProfile; use proc_macro::TokenStream;
use syn::{parse_macro_input, AttributeArgs, ItemImpl, ImplItem}; use proc_macro2::{TokenStream as TokenStream2, Span};
use sing_util::{DeduplicatedFunctionProfile, TraitProfile};
use syn::{
parse_macro_input,
AttributeArgs,
Ident,
ItemImpl,
ImplItem,
NestedMeta,
parse::{Parse, ParseStream},
Token,
parenthesized,
Arm,
parse2,
LitStr,
Signature,
FnArg,
Path,
Stmt,
};
use quote::{quote, spanned::Spanned, ToTokens}; use quote::{quote, spanned::Spanned, ToTokens};
extern crate sing_util; extern crate sing_util;
extern crate proc_macro; extern crate proc_macro;
mod keyword;
#[proc_macro_attribute]
pub fn sing_add_trait(input: TokenStream, annotated_item: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemImpl);
let annotated_item = parse_macro_input!(annotated_item as AttributeArgs);
let output = add_trait_inner(input, annotated_item);
TokenStream::from(output)
}
fn add_trait_inner(input: ItemImpl, annotated_item: Vec<NestedMeta>) -> TokenStream2 {
let trait_name;
match &input.trait_ {
Some((_b, p, _f)) => {
trait_name = p.into_token_stream().to_string();
},
None => {
compile_error_panic(input.__span(), "This attribute can only be used on trait impl blocks.");
trait_name = String::from("");
},
};
let mut trait_functions = HashMap::new();
for fun in &input.items {
match fun {
ImplItem::Method(m) => {trait_functions.insert(
m.sig.ident.to_string(),
m.sig.to_token_stream()
);},
_ => {
compile_error_panic(input.__span(), "Found unexpected item that is not a method.");
},
}
}
let mut trait_state = load_trait_state().unwrap();
let profile = TraitProfile::new(trait_functions);
if trait_state.contains_key(trait_name.as_str()) {
let err = format!("Trait {} is implemented twice!", trait_name);
syn::Error::new(input.__span(), err.as_str())
.to_compile_error();
} else {
trait_state.insert(trait_name, profile);
}
save_trait_state(trait_state).unwrap();
// Build the output, possibly using quasi-quotation
// TODO: add gobble
quote! {
// ...
}
}
#[proc_macro]
pub fn sing_loop(input: TokenStream) -> TokenStream {
let span = TokenStream2::from(input.clone()).__span();
let input = parse_macro_input!(input as LoopParams);
let output = match loop_inner(input, span) {
Ok(s) => s,
Err(e) => {
compile_error_panic(span, &e.to_string());
TokenStream2::new()
},
};
TokenStream::from(output)
}
fn loop_inner(input: LoopParams, span: Span) -> Result<TokenStream2, Box<dyn Error>> {
let LoopParams {
trait_obj,
ser_fun,
de_fun,
reader,
writer,
err_writer,
message_type
} = input;
let mut ioe_initializers = vec!();
let reader = match reader {
Some(r) => r,
None => {
ioe_initializers.push(parse2::<Stmt>(quote!{
let reader = std::io::stdin().lock();
})?);
parse2::<Ident>(quote!{
reader
})?},
};
let writer = match writer {
Some(w) => w,
None => {
ioe_initializers.push(parse2::<Stmt>(quote!{
let writer = std::io::stdout().lock();
})?);
parse2::<Ident>(quote!{
writer
})?},
};
let err_writer = match err_writer {
Some(w) => w,
None => {
ioe_initializers.push(parse2::<Stmt>(quote!{
let err_writer = std::io::stdin().lock();
})?);
parse2::<Ident>(quote!{
err_writer
})?},
};
let ioe_initializers = ioe_initializers;
let message_type = match message_type {
Some(r) => r,
None => parse2::<Path>(quote!{
todo!();
})?,
};
let traits = load_trait_state().unwrap();
let mut funs = HashMap::new();
for (t_name, t_funs) in &traits {
for (f_name, fun) in t_funs.get_funs()? {
if funs.contains_key(&f_name) {
funs.insert(f_name.clone(), match &funs[&f_name] {
DeduplicatedFunctionProfile::Single(t, _) => DeduplicatedFunctionProfile::Multiple(vec!(t_name.clone(), t.clone())),
DeduplicatedFunctionProfile::Multiple(v) => {
let mut v = v.clone();
v.push(t_name.clone());
DeduplicatedFunctionProfile::Multiple(v)
},
});
}
}
}
let mut fun_arms = vec!();
for (f_name, f_profile) in funs {
let fn_lit = LitStr::new(&f_name, span);
let fn_ident = Ident::new(&f_name, span);
let arm;
match f_profile {
DeduplicatedFunctionProfile::Multiple(v) => {
arm = quote!{
#fn_lit => {
let traits = vec!( #( #v ),* );
let message = format!("The function {} is found in the following traits: {}.
Plese specify which traits function you want to use.",
#f_name,
traits,
);
<#err_writer as Write>.write_all(message.as_bytes())?;
},
};
},
DeduplicatedFunctionProfile::Single(tr, s) => {
let tr = Ident::new(&tr, span);
arm = get_fun_arm(
fn_lit,
fn_ident,
ser_fun.clone(),
de_fun.clone(),
writer.clone(),
trait_obj.clone(),
tr,
s.get_inner()?
)?;
}
}
let arm = parse2::<Arm>(arm)?;
fun_arms.push(arm);
}
fun_arms.push(
parse2::<Arm>(quote! {
"" => {
let message = String::from("No function name was given! Aborting.");
<#err_writer as Write>.write_all(message.as_bytes())?;
},
})?
);
fun_arms.push(
parse2::<Arm>(quote! {
s => {
let message = format!("The function {} is not known! Aborting.", s);
<#err_writer as Write>.write_all(message.as_bytes())?;
},
})?
);
let mut trait_arms = vec!();
for (t_name, t_profile) in traits.clone() {
let t_lit = LitStr::new(&t_name, span);
let t_ident = Ident::new(&t_name, span);
let mut f_arms = vec!();
for (f_name, f_profile) in t_profile.get_funs()? {
let fn_lit = LitStr::new(&f_name, span);
let fn_ident = Ident::new(&f_name, span);
let f_arm = get_fun_arm(
fn_lit,
fn_ident,
ser_fun.clone(),
de_fun.clone(),
writer.clone(),
trait_obj.clone(),
t_ident.clone(),
f_profile
)?;
f_arms.push(parse2::<Arm>(f_arm)?);
}
f_arms.push(
parse2::<Arm>(quote! {
"" => {
let message = String::from("No function name was given! Aborting.");
<#err_writer as Write>.write_all(message.as_bytes())?;
},
})?
);
f_arms.push(
parse2::<Arm>(quote! {
s => {
let message = format!("The function {} was not found in the trait {}! Aborting.", s, #t_lit );
<#err_writer as Write>.write_all(message.as_bytes())?;
}
})?
);
let t_arm = quote! {
#t_lit => {
match <message as TraitCallMessage>.get_fun_name().as_str() {
#( #f_arms )*
}
}
};
trait_arms.push(parse2::<Arm>(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 <message as TraitCallMessage>.get_trait_name().as_str() {
#( #trait_arms )*
}
match #ser_fun(message) {
Ok(s) => <#writer as Write>.write_all(s),
Err(e) => <#err_writer as Write>.write_all("Could not encode call result: {}", e),
}
},
Err(e) => <#err_writer as Write>.write_all("Could not decode {} as trait message: {}", buf, e),
}
}
})
}
struct LoopParams {
trait_obj: Ident,
ser_fun: Path,
de_fun: Path,
reader: Option<Ident>,
writer: Option<Ident>,
err_writer: Option<Ident>,
message_type: Option<Path>,
}
impl Parse for LoopParams {
fn parse(input: ParseStream) -> syn::Result<Self> {
let trait_obj = input.parse()?;
input.parse::<Token![,]>()?;
let ser_fun = input.parse()?;
input.parse::<Token![,]>()?;
let de_fun = input.parse()?;
let lookahead = input.lookahead1();
let mut reader = None;
let mut writer = None;
let mut err_writer = None;
let mut message_type = None;
'parse: loop {
input.parse::<Token![,]>()?;
if input.is_empty() {break 'parse}
if lookahead.peek(keyword::msg) {
input.parse::<keyword::msg>()?;
input.parse::<Token![:]>()?;
let ty = input.parse()?;
message_type = Some(ty);
} else if lookahead.peek(keyword::ioe) {
input.parse::<keyword::ioe>()?;
input.parse::<Token![:]>()?;
let content;
parenthesized!(content in input);
let lh_inner = content.lookahead1();
let ioe_err_generic = "The ioe keyword expects three variables in the following order: (Input, Output, Error),";
if lh_inner.peek(Ident) {
let r = content.parse()?;
reader = Some(r);
} else {
compile_error_panic(
content.span(),
format!("{}, but the Input variable was not supplied.", ioe_err_generic).as_str()
);
}
if lh_inner.peek(Ident) {
let w = content.parse()?;
writer = Some(w);
} else {
compile_error_panic(
content.span(),
format!("{}, but the Output variable was not supplied.", ioe_err_generic).as_str(),
);
}
if lh_inner.peek(Ident) {
let e = content.parse()?;
err_writer = Some(e);
} else {
compile_error_panic(
content.span(),
format!("{}, but the Error variable was not supplied.", ioe_err_generic).as_str(),
);
}
}
if input.is_empty() {break 'parse}
}
Ok(Self {
trait_obj,
ser_fun,
de_fun,
reader,
writer,
err_writer,
message_type,
})
}
}
fn get_fun_arm(
fn_lit: LitStr,
fn_ident: Ident,
ser_fun: Path,
de_fun: Path,
writer: Ident,
trait_obj: Ident,
tr: Ident,
s: TokenStream2,
) -> Result<TokenStream2, Box<dyn Error>> {
let sig = parse2::<Signature>(s)?;
let mut args = vec!();
let mut parse_lets = vec!();
for a in sig.inputs {
match a {
// This is just a "self" value which does not need to be in the call
FnArg::Receiver(_) => (),
FnArg::Typed(t) => {
let pat = *t.pat;
let ty = t.ty;
args.push(pat.clone());
parse_lets.push(parse2::<Stmt>(quote!{
let #pat : #ty = #de_fun ( #pat );
})?);
},
}
}
Ok(quote!{
#fn_lit => {
let params = <message as TraitCallMessage>.get_params();
let ( #( #args ),* ) = params;
#( #parse_lets )*
let result = <#trait_obj as #tr>. #fn_ident ( #( #args ),* );
<message as TraitCallMessage>.new_params(
vec!(<#ser_fun as Serializer>.serialize(result))
);
<#writer as Write>.write_all(message.as_bytes())?;
},
})
}
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. // Get a random uid for this compiler run and save it as an environment variable.
// Load it if it's already there. // Load it if it's already there.
@ -30,6 +470,7 @@ fn get_run_id () -> Result<usize, Box<dyn Error>> {
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>> { fn save_trait_state(map: HashMap<String, sing_util::TraitProfile>) -> Result<usize, Box<dyn Error>> {
@ -49,62 +490,82 @@ fn load_trait_state() -> Result<HashMap<String, sing_util::TraitProfile>, Box<dy
Ok(ron::from_str(&state_string)?) Ok(ron::from_str(&state_string)?)
} }
#[proc_macro_attribute] fn compile_error_panic(span: Span, msg: &str) {
pub fn sing_add_trait(input: proc_macro::TokenStream, annotated_item: proc_macro::TokenStream) -> proc_macro::TokenStream { syn::Error::new(span, msg)
let input = parse_macro_input!(input as ItemImpl);
let annotated_item = parse_macro_input!(annotated_item as AttributeArgs);
let trait_name;
match &input.trait_ {
Some((_b, p, _f)) => {
trait_name = p.into_token_stream().to_string();
},
None => {
syn::Error::new(input.__span(), "This attribute can only be used on trait impl blocks.")
.to_compile_error(); .to_compile_error();
trait_name = String::from(""); panic!("{}", msg)
},
};
let mut trait_functions = vec!();
for fun in &input.items {
match fun {
ImplItem::Method(m) => trait_functions.push(m.sig.to_token_stream()),
_ => {
syn::Error::new(input.__span(), "Found unexpected item that is not a method.")
.to_compile_error();
},
}
}
let mut trait_state = load_trait_state().unwrap();
let profile = TraitProfile::new(trait_functions);
if trait_state.contains_key(trait_name.as_str()) {
let err = format!("Trait {} is implemented twice!", trait_name);
syn::Error::new(input.__span(), err.as_str())
.to_compile_error();
} else {
trait_state.insert(trait_name, profile);
}
save_trait_state(trait_state).unwrap();
// Build the output, possibly using quasi-quotation
let output = quote! {
// ...
};
proc_macro::TokenStream::from(output)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{str::FromStr, collections::HashMap};
use proc_macro2::TokenStream;
use sing_util::TraitProfile;
use crate::{save_trait_state, load_trait_state, add_trait_inner};
#[test] #[test]
fn it_works() { fn save_tokenstream() {
let result = 2 + 2; let mut orig = HashMap::new();
assert_eq!(result, 4);
orig.insert(String::from("test"), TokenStream::from_str("
fn test(a: Astruct, b: [Box<dyn Error>, String]) -> String {}
").unwrap());
orig.insert(String::from("other_test"), TokenStream::from_str("
fn other_test(a: Trait1 + Trait2) -> Result<String, Box<dyn Error>> {}
").unwrap(),
);
let orig_name = String::from("Test");
let mut orig_state = HashMap::new();
orig_state.insert(orig_name.clone(), TraitProfile::new(orig));
save_trait_state(orig_state.clone()).unwrap();
let new_state = load_trait_state().unwrap();
assert_eq!(
orig_state[&orig_name].get_funs().unwrap()["test"].to_string(),
new_state[&orig_name].get_funs().unwrap()["test"].to_string()
)
} }
#[test]
fn trait_call() {
let call = TokenStream::from_str("
impl FruitTree for BananaTree {
fn shake(&self) -> Box<dyn Fruit> {
Box::new(self.bananas.pop())
}
}
").unwrap();
let call = syn::parse2(call).unwrap();
add_trait_inner(call, vec!());
let new_state = load_trait_state().unwrap();
assert_eq!(
TokenStream::from_str("
fn shake(&self) -> Box<dyn Fruit>
").unwrap().to_string(),
new_state["FruitTree"].get_funs().unwrap()["shake"].to_string()
)
}
#[test]
#[should_panic]
fn non_trait_call() {
let call = TokenStream::from_str("
impl Banana {
pub fn peel(&self) -> Food {
self.inner.clone()
}
}
").unwrap();
let call = syn::parse2(call).unwrap();
add_trait_inner(call, vec!());
}
} }

View file

@ -1,4 +1,5 @@
use core::fmt::Debug; use core::fmt::Debug;
use std::{error::Error, collections::HashMap};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
@ -8,55 +9,46 @@ mod serde_wrapper;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraitProfile{ pub struct TraitProfile{
//functions: HashMap<String, FunctionProfile>, functions: HashMap<String, TokenStreamWrapper>,
functions: Vec<TokenStreamWrapper>,
} }
impl TraitProfile{ impl TraitProfile{
pub fn new(streams: Vec<TokenStream>) -> Self { pub fn new(funs: HashMap<String, TokenStream>) -> Self {
let mut inner = vec!(); let mut inner = HashMap::new();
for stream in streams { for (name, stream) in funs {
inner.push(TokenStreamWrapper::new(stream)); inner.insert(name, TokenStreamWrapper::new(stream));
} }
Self{ Self{
functions: inner functions: inner
} }
} }
pub fn get_funs(&self) -> Result<HashMap<String, TokenStream>, Box<dyn Error>> {
let wrappers = self.functions.clone();
let mut funs = HashMap::new();
for (name, wrapper) in wrappers {
funs.insert(name, wrapper.get_inner()?);
}
Ok(funs)
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum DeduplicatedFunctionProfile { pub enum DeduplicatedFunctionProfile {
Single(String, TokenStreamWrapper), Single(String, TokenStreamWrapper),
Multiple(Vec<String>, TokenStreamWrapper), Multiple(Vec<String>),
} }
/*impl<T> Evaluator<T> { pub trait TraitCallMessage {
fn deduplicate_functions(&self) -> Self<T> { fn get_fun_name() -> String;
let mut self = self.clone(); fn get_trait_name() -> String;
let mut functions: HashMap<String, DeduplicatedFunctionProfile>; fn get_params() -> Vec<String>;
fn new_params(p: Vec<String>);
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)] #[cfg(test)]
mod tests { mod tests {

View file

@ -1,17 +1,20 @@
use std::{str::FromStr, fmt::{self, format}}; use std::{str::FromStr, fmt::{self, format}, error::Error};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use serde::{Serialize, Serializer, Deserialize, Deserializer, de::{Visitor, self}}; use serde::{Serialize, Serializer, Deserialize, Deserializer, de::{Visitor, self}};
#[derive(Debug, Clone)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenStreamWrapper(TokenStream); pub struct TokenStreamWrapper(String);
impl TokenStreamWrapper { impl TokenStreamWrapper {
pub fn new(stream: TokenStream) -> Self { pub fn new(stream: TokenStream) -> Self {
Self(stream) Self(stream.to_string())
}
pub fn get_inner(&self) -> Result<TokenStream, Box<dyn Error>> {
Ok(TokenStream::from_str(&self.0.clone())?)
} }
} }
/*
impl Serialize for TokenStreamWrapper { impl Serialize for TokenStreamWrapper {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where where
@ -29,7 +32,7 @@ impl<'de> Deserialize<'de> for TokenStreamWrapper {
{ {
let tok_str = deserializer.deserialize_string(TokenStreamVisitor)?; let tok_str = deserializer.deserialize_string(TokenStreamVisitor)?;
match TokenStream::from_str(&tok_str.as_str()) { match TokenStream::from_str(tok_str.as_str()) {
Ok(t) => Ok(Self(t)), Ok(t) => Ok(Self(t)),
Err(e) => Err(de::Error::custom( Err(e) => Err(de::Error::custom(
format!("string does not represent a valid TokenStream: {}", e) format!("string does not represent a valid TokenStream: {}", e)
@ -69,3 +72,4 @@ impl<'de> Visitor<'de> for TokenStreamVisitor {
} }
} }
*/