Compare commits
5 Commits
ecb12f4358
...
64637fa92d
Author | SHA1 | Date |
---|---|---|
annieversary | 64637fa92d | |
annieversary | a67ad2d6ad | |
annieversary | 49dae204b8 | |
annieversary | 15678fe80f | |
annieversary | 224d61d4e1 |
134
README.org
134
README.org
|
@ -1,5 +1,5 @@
|
||||||
* effers
|
* effers
|
||||||
effect handlers in rust
|
ergonomic effect handlers in rust
|
||||||
|
|
||||||
** how to use
|
** how to use
|
||||||
*** defining effects
|
*** defining effects
|
||||||
|
@ -7,24 +7,30 @@ effects are defined with traits
|
||||||
|
|
||||||
#+begin_src rust
|
#+begin_src rust
|
||||||
trait Printer {
|
trait Printer {
|
||||||
fn print(&mut self, s: &str);
|
fn print(&self, s: &str);
|
||||||
|
fn available() -> bool;
|
||||||
}
|
}
|
||||||
trait Logger {
|
trait Logger {
|
||||||
fn debug(&mut self, s: &str);
|
fn debug(&mut self, s: &str);
|
||||||
fn info(&mut self, s: &str);
|
fn info(self, s: &str);
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
all the trait functions (that are used in programs) must take ~&mut self~ as a parameter
|
functions can take ~self~, ~&self~, ~&mut self~, or no ~self~ parameter. at this point ~self~ parameters with a specified type (like ~self: Box<Self>~) are not supported
|
||||||
|
|
||||||
*** defining a program
|
*** defining a program
|
||||||
|
|
||||||
programs are defined as a normal function, with the added ~program~ attribute
|
programs are defined as a normal function, with the added ~program~ attribute, which specifies (optional) a name for the program, and (required) the list of effects and corresponding functions that are used
|
||||||
|
|
||||||
#+begin_src rust
|
#+begin_src rust
|
||||||
#[program(Smth => Printer(print as p), Logger(debug, info))]
|
#[effers::program(MyCoolProgram =>
|
||||||
fn smth(val: u8) -> u8 {
|
Printer(print(&self) as p, available as printer_available),
|
||||||
|
Logger(debug(&mut self), info(self))
|
||||||
|
)]
|
||||||
|
fn my_program(val: u8) -> u8 {
|
||||||
|
if printer_available() {
|
||||||
p("hey hi hello");
|
p("hey hi hello");
|
||||||
|
}
|
||||||
|
|
||||||
debug("this is a debug-level log");
|
debug("this is a debug-level log");
|
||||||
info("this is a info-level log");
|
info("this is a info-level log");
|
||||||
|
@ -33,22 +39,47 @@ fn smth(val: u8) -> u8 {
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
the first token (~Smth~) will be the name of the program. effects are listed after the ~=>~ token
|
**** name
|
||||||
|
|
||||||
|
the first token (~MyCoolProgram~) will be the name of the program. this is optional, and can be skipped:
|
||||||
|
|
||||||
|
#+begin_src rust
|
||||||
|
#[program(
|
||||||
|
Printer(print(&self) as p, available as printer_available),
|
||||||
|
Logger(debug(&mut self), info(self))
|
||||||
|
)]
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
if skipped, the default name will be the program function's name (~my_program~) in PascalCase (~MyProgram~)
|
||||||
|
|
||||||
**** listing effects
|
**** listing effects
|
||||||
effects are listed by writing the trait's name, followed by a parenthesized list of the functions that will be used
|
effects are listed by writing the trait's name, followed by a parenthesized list of the functions that will be used
|
||||||
|
|
||||||
functions can be given an alias using the ~as~ keyword
|
***** listing effect functions
|
||||||
|
|
||||||
|
due to limitations of proc-macros, it's unknown what kind of ~self~ parameter the function takes, if any, and so it has to be explicitly specified (if you have ideas on how to fix this, please open a PR!): here's how each type is specified:
|
||||||
|
|
||||||
|
- ~fn print();~: ~print~
|
||||||
|
- ~fn print(self);~: ~print(self)~
|
||||||
|
- ~fn print(mut self);~: ~print(self)~
|
||||||
|
- ~fn print(&self);~: ~print(&self)~
|
||||||
|
- ~fn print(&mut self);~: ~print(&mut self)~
|
||||||
|
|
||||||
|
***** effect function aliases
|
||||||
|
functions can be given an alias using the ~as~ keyword (~print(&self) as p~) so that the function can be called by a different name inside the program
|
||||||
|
|
||||||
*** defining effect handlers
|
*** defining effect handlers
|
||||||
effect handlers are defined by declaring a struct, and implementing the trait on it
|
effect handlers are defined by declaring a struct, and implementing the corresponding trait on it
|
||||||
|
|
||||||
#+begin_src rust
|
#+begin_src rust
|
||||||
struct IoPrinter;
|
struct IoPrinter;
|
||||||
impl Printer for IoPrinter {
|
impl Printer for IoPrinter {
|
||||||
fn print(&mut self, s: &str) {
|
fn print(&self, s: &str) {
|
||||||
println!("{}", s)
|
println!("{}", s)
|
||||||
}
|
}
|
||||||
|
fn available() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FileLogger;
|
struct FileLogger;
|
||||||
|
@ -56,88 +87,21 @@ impl Logger for FileLogger {
|
||||||
fn debug(&mut self, s: &str) {
|
fn debug(&mut self, s: &str) {
|
||||||
println!("debug: {}", s)
|
println!("debug: {}", s)
|
||||||
}
|
}
|
||||||
fn info(&mut self, s: &str) {
|
fn info(self, s: &str) {
|
||||||
println!("info: {}", s)
|
println!("info: {}", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
*** running programs
|
*** running programs
|
||||||
programs are run by providing the corresponding handlers in order, and finally calling the ~run~ method, providing it the required parameters
|
programs are run by providing the corresponding handlers *in the order listed in the program definition*, and finally calling the ~run~ method, providing it the required parameters
|
||||||
|
|
||||||
#+begin_src rust
|
#+begin_src rust
|
||||||
let result: u8 = Smth.add(IoPrinter).add(FileLogger).run(3);
|
let result: u8 = MyCoolProgram.add(IoPrinter).add(FileLogger).run(3);
|
||||||
assert_eq!(result, 6);
|
assert_eq!(result, 6);
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** full example
|
** examples
|
||||||
#+begin_src rust
|
- [[./examples/main.rs][main: general use case]]
|
||||||
use effers::program;
|
- [[./examples/clone.rs][clone: how cloning and copying programs works]]
|
||||||
|
- [[./examples/module.rs][module: effects from other modules are supported]]
|
||||||
#[program(Smth => Printer(print as p), Logger(debug, info))]
|
|
||||||
fn smth(val: u8) -> u8 {
|
|
||||||
p("hey hi hello");
|
|
||||||
|
|
||||||
debug("this is a debug-level log");
|
|
||||||
info("this is a info-level log");
|
|
||||||
|
|
||||||
val + 3
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// maybe smth like this?
|
|
||||||
let result: u8 = Smth.add(IoPrinter).add(FileLogger).run(3);
|
|
||||||
assert_eq!(result, 6);
|
|
||||||
let other_result: u8 = Smth
|
|
||||||
.add(IoPrinter)
|
|
||||||
.add(NetworkLogger {
|
|
||||||
credentials: "secret password".to_string(),
|
|
||||||
})
|
|
||||||
.run(8);
|
|
||||||
assert_eq!(other_result, 11);
|
|
||||||
}
|
|
||||||
|
|
||||||
// effects
|
|
||||||
trait Printer {
|
|
||||||
fn print(&mut self, s: &str);
|
|
||||||
}
|
|
||||||
trait Logger {
|
|
||||||
fn debug(&mut self, s: &str);
|
|
||||||
fn info(&mut self, s: &str);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IoPrinter;
|
|
||||||
impl Printer for IoPrinter {
|
|
||||||
fn print(&mut self, s: &str) {
|
|
||||||
println!("{}", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FileLogger;
|
|
||||||
impl Logger for FileLogger {
|
|
||||||
fn debug(&mut self, s: &str) {
|
|
||||||
println!("debug: {}", s)
|
|
||||||
}
|
|
||||||
fn info(&mut self, s: &str) {
|
|
||||||
println!("info: {}", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NetworkLogger {
|
|
||||||
credentials: String,
|
|
||||||
}
|
|
||||||
impl Logger for NetworkLogger {
|
|
||||||
fn debug(&mut self, s: &str) {
|
|
||||||
println!(
|
|
||||||
"debug through network: {}; with password {}",
|
|
||||||
s, self.credentials
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn info(&mut self, s: &str) {
|
|
||||||
println!(
|
|
||||||
"info through network: {}; with password {}",
|
|
||||||
s, self.credentials
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#+end_src
|
|
||||||
|
|
|
@ -10,3 +10,4 @@ proc-macro = true
|
||||||
syn = {version = "1.0", features = ["full", "extra-traits", "visit-mut"]}
|
syn = {version = "1.0", features = ["full", "extra-traits", "visit-mut"]}
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
convert_case = "0.5.0"
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{Ident, Path};
|
||||||
|
|
||||||
|
use crate::{lette::LettersIter, Args};
|
||||||
|
|
||||||
|
pub struct IntermediateStruct {
|
||||||
|
pub tokens: TokenStream,
|
||||||
|
pub id: Ident,
|
||||||
|
pub traits: Vec<Path>,
|
||||||
|
pub letters: Vec<Ident>,
|
||||||
|
pub generics: TokenStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntermediateStruct {
|
||||||
|
pub fn new(
|
||||||
|
tokens: TokenStream,
|
||||||
|
id: Ident,
|
||||||
|
traits: Vec<Path>,
|
||||||
|
letters: Vec<Ident>,
|
||||||
|
generics: TokenStream,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
tokens,
|
||||||
|
id,
|
||||||
|
traits,
|
||||||
|
letters,
|
||||||
|
generics,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intermediate_structs(args: &Args, prog_name: &Ident) -> Vec<IntermediateStruct> {
|
||||||
|
let struct_with = format!("{}With", prog_name);
|
||||||
|
args.effects
|
||||||
|
.iter()
|
||||||
|
.fold(
|
||||||
|
(vec![], struct_with, vec![]),
|
||||||
|
|(mut structs, name, mut traits), eff| {
|
||||||
|
let name = format!("{}{}", &name, &eff.name);
|
||||||
|
|
||||||
|
traits.push(eff.path.clone());
|
||||||
|
|
||||||
|
// kinda messy
|
||||||
|
let letters = LettersIter::new()
|
||||||
|
.take(traits.len())
|
||||||
|
.map(|c| Ident::new(&c.to_string(), Span::call_site()));
|
||||||
|
let generics = traits
|
||||||
|
.iter()
|
||||||
|
.zip(letters.clone())
|
||||||
|
.map(|(t, c)| quote!(#c: #t,))
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let id = Ident::new(&name, Span::call_site());
|
||||||
|
let last = if let Some(&IntermediateStruct {
|
||||||
|
ref id,
|
||||||
|
ref letters,
|
||||||
|
..
|
||||||
|
}) = &structs.last()
|
||||||
|
{
|
||||||
|
let gen = letters.iter().map(|l| quote!(#l,)).collect::<TokenStream>();
|
||||||
|
quote!(#id<#gen>)
|
||||||
|
} else {
|
||||||
|
quote!(#prog_name)
|
||||||
|
};
|
||||||
|
let last_letter = letters.clone().last();
|
||||||
|
structs.push(IntermediateStruct::new(
|
||||||
|
quote! {
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct #id<#generics>(#last, #last_letter);
|
||||||
|
},
|
||||||
|
id,
|
||||||
|
traits.clone(),
|
||||||
|
letters.collect::<Vec<_>>(),
|
||||||
|
generics,
|
||||||
|
));
|
||||||
|
|
||||||
|
(structs, name, traits)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.0
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
const LETTERS: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct LettersIter {
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LettersIter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { idx: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Iterator for LettersIter {
|
||||||
|
type Item = String;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let l = LETTERS.chars().nth(self.idx % LETTERS.len()).unwrap();
|
||||||
|
let c = self.idx / LETTERS.len();
|
||||||
|
|
||||||
|
self.idx += 1;
|
||||||
|
|
||||||
|
Some(l.to_string().repeat(c + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iter() {
|
||||||
|
let mut i = LettersIter::new();
|
||||||
|
|
||||||
|
assert_eq!(i.next(), Some("A".to_string()));
|
||||||
|
assert_eq!(i.next(), Some("B".to_string()));
|
||||||
|
assert_eq!(i.nth(23), Some("Z".to_string()));
|
||||||
|
assert_eq!(i.next(), Some("AA".to_string()));
|
||||||
|
assert_eq!(i.next(), Some("BB".to_string()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,31 @@
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use lette::LettersIter;
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::parse::{Parse, ParseStream, Result};
|
use syn::token::{Dot, Mut, SelfValue};
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::token::{Mut, SelfValue};
|
|
||||||
use syn::visit_mut::VisitMut;
|
use syn::visit_mut::VisitMut;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input, Expr, ExprCall, FnArg, Ident, ItemFn, Path, PathSegment, Receiver, Token,
|
parse_macro_input, Expr, ExprCall, ExprField, FnArg, Ident, Index, ItemFn, Member, PathSegment,
|
||||||
Type,
|
QSelf, Receiver,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod parse;
|
||||||
|
use parse::Args;
|
||||||
|
mod intermediate_structs;
|
||||||
|
mod lette;
|
||||||
|
use intermediate_structs::*;
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn program(
|
pub fn program(
|
||||||
attr: proc_macro::TokenStream,
|
attr: proc_macro::TokenStream,
|
||||||
item: proc_macro::TokenStream,
|
item: proc_macro::TokenStream,
|
||||||
) -> proc_macro::TokenStream {
|
) -> proc_macro::TokenStream {
|
||||||
let item = parse_macro_input!(item as syn::ItemFn);
|
let item = parse_macro_input!(item as syn::ItemFn);
|
||||||
|
// dbg!(&item);
|
||||||
let mut args = parse_macro_input!(attr as Args);
|
let mut args = parse_macro_input!(attr as Args);
|
||||||
|
|
||||||
if args.name.is_none() {
|
if args.name.is_none() {
|
||||||
let i = first_letter_to_uppper_case(item.sig.ident.to_string());
|
let i = item.sig.ident.to_string().to_case(Case::Pascal);
|
||||||
args.name = Some(Ident::new(&i, Span::call_site()));
|
args.name = Some(Ident::new(&i, Span::call_site()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,122 +38,6 @@ pub fn program(
|
||||||
proc_macro::TokenStream::from(out)
|
proc_macro::TokenStream::from(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first_letter_to_uppper_case(s1: String) -> String {
|
|
||||||
let mut c = s1.chars();
|
|
||||||
match c.next() {
|
|
||||||
None => String::new(),
|
|
||||||
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Args {
|
|
||||||
name: Option<Ident>,
|
|
||||||
effects: Vec<Effect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Effect {
|
|
||||||
name: Ident,
|
|
||||||
path: Path,
|
|
||||||
functions: Vec<EffectFunction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct EffectFunction {
|
|
||||||
ident: Ident,
|
|
||||||
alias: Option<Ident>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_name_from_args(input: &mut ParseStream) -> Result<Ident> {
|
|
||||||
let name: Ident = input.parse()?;
|
|
||||||
input.parse::<Token![=>]>()?;
|
|
||||||
Ok(name)
|
|
||||||
}
|
|
||||||
impl Parse for Args {
|
|
||||||
fn parse(mut input: ParseStream) -> Result<Self> {
|
|
||||||
let name = get_name_from_args(&mut input).ok();
|
|
||||||
|
|
||||||
let effects: Vec<_> = Punctuated::<ExprCall, Token![,]>::parse_terminated(input)?
|
|
||||||
.into_iter()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let effects: Vec<Effect> = effects
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|e| {
|
|
||||||
Some(Effect {
|
|
||||||
name: name_from_expr_call(&e)?,
|
|
||||||
path: if let Expr::Path(p) = &*e.func {
|
|
||||||
Some(p.path.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}?,
|
|
||||||
functions: effects_from_expr_call(&e),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(Args { name, effects })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name_from_expr_call(e: &ExprCall) -> Option<Ident> {
|
|
||||||
if let Expr::Path(e) = &*e.func {
|
|
||||||
Some(e.path.get_ident()?.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns the list of functions, with their optional alias
|
|
||||||
fn effects_from_expr_call(e: &ExprCall) -> Vec<EffectFunction> {
|
|
||||||
e.args
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.flat_map(|p| match p {
|
|
||||||
Expr::Path(e) => Some(EffectFunction {
|
|
||||||
ident: e.path.get_ident().unwrap().clone(), // TODO remove this unwrap
|
|
||||||
alias: None,
|
|
||||||
}),
|
|
||||||
Expr::Cast(cast) => match (*cast.expr, *cast.ty) {
|
|
||||||
(Expr::Path(expr), Type::Path(ty)) => Some(EffectFunction {
|
|
||||||
ident: expr.path.get_ident().unwrap().clone(),
|
|
||||||
alias: Some(ty.path.get_ident()?.clone()),
|
|
||||||
}),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct LettersIter {
|
|
||||||
idx: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LettersIter {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
idx: 'A' as u32 - 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Iterator for LettersIter {
|
|
||||||
type Item = char;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
for _ in 0..100 {
|
|
||||||
self.idx += 1;
|
|
||||||
if let Some(c) = char::from_u32(self.idx) {
|
|
||||||
return Some(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// takes in the function contents, and returns the structs n stuff
|
/// takes in the function contents, and returns the structs n stuff
|
||||||
fn rewrite_item_into_struct(func: ItemFn, args: Args) -> TokenStream {
|
fn rewrite_item_into_struct(func: ItemFn, args: Args) -> TokenStream {
|
||||||
let prog_name = (&args.name).clone().unwrap();
|
let prog_name = (&args.name).clone().unwrap();
|
||||||
|
@ -166,6 +57,7 @@ fn rewrite_item_into_struct(func: ItemFn, args: Args) -> TokenStream {
|
||||||
let final_impl = final_impl(intermediate_structs.last().unwrap(), func, &args);
|
let final_impl = final_impl(intermediate_structs.last().unwrap(), func, &args);
|
||||||
|
|
||||||
let out = quote! {
|
let out = quote! {
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
struct #prog_name;
|
struct #prog_name;
|
||||||
|
|
||||||
#inters_tokens
|
#inters_tokens
|
||||||
|
@ -177,82 +69,6 @@ fn rewrite_item_into_struct(func: ItemFn, args: Args) -> TokenStream {
|
||||||
TokenStream::from(out)
|
TokenStream::from(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct IntermediateStruct {
|
|
||||||
tokens: TokenStream,
|
|
||||||
id: Ident,
|
|
||||||
traits: Vec<Ident>,
|
|
||||||
letters: Vec<Ident>,
|
|
||||||
generics: TokenStream,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntermediateStruct {
|
|
||||||
fn new(
|
|
||||||
tokens: TokenStream,
|
|
||||||
id: Ident,
|
|
||||||
traits: Vec<Ident>,
|
|
||||||
letters: Vec<Ident>,
|
|
||||||
generics: TokenStream,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
tokens,
|
|
||||||
id,
|
|
||||||
traits,
|
|
||||||
letters,
|
|
||||||
generics,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn intermediate_structs(args: &Args, prog_name: &Ident) -> Vec<IntermediateStruct> {
|
|
||||||
let struct_with = format!("{}With", prog_name);
|
|
||||||
args.effects
|
|
||||||
.iter()
|
|
||||||
.fold(
|
|
||||||
(vec![], struct_with, vec![]),
|
|
||||||
|(mut structs, name, mut traits), eff| {
|
|
||||||
let name = format!("{}{}", &name, &eff.name);
|
|
||||||
|
|
||||||
traits.push(eff.name.clone());
|
|
||||||
|
|
||||||
// kinda messy
|
|
||||||
let letters = LettersIter::new()
|
|
||||||
.take(traits.len())
|
|
||||||
.map(|c| Ident::new(&c.to_string(), Span::call_site()));
|
|
||||||
let generics = traits
|
|
||||||
.iter()
|
|
||||||
.zip(letters.clone())
|
|
||||||
.map(|(t, c)| quote!(#c: #t,))
|
|
||||||
.collect::<TokenStream>();
|
|
||||||
|
|
||||||
let id = Ident::new(&name, Span::call_site());
|
|
||||||
let last = if let Some(&IntermediateStruct {
|
|
||||||
ref id,
|
|
||||||
ref letters,
|
|
||||||
..
|
|
||||||
}) = &structs.last()
|
|
||||||
{
|
|
||||||
let gen = letters.iter().map(|l| quote!(#l,)).collect::<TokenStream>();
|
|
||||||
quote!(#id<#gen>)
|
|
||||||
} else {
|
|
||||||
quote!(#prog_name)
|
|
||||||
};
|
|
||||||
let last_letter = letters.clone().last();
|
|
||||||
structs.push(IntermediateStruct::new(
|
|
||||||
quote! {
|
|
||||||
struct #id<#generics>(#last, #last_letter);
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
traits.clone(),
|
|
||||||
letters.collect::<Vec<_>>(),
|
|
||||||
generics,
|
|
||||||
));
|
|
||||||
|
|
||||||
(structs, name, traits)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn impls(prog_name: &Ident, intermediate_structs: &Vec<IntermediateStruct>) -> TokenStream {
|
fn impls(prog_name: &Ident, intermediate_structs: &Vec<IntermediateStruct>) -> TokenStream {
|
||||||
let mut impls = vec![];
|
let mut impls = vec![];
|
||||||
let mut id = quote!(#prog_name);
|
let mut id = quote!(#prog_name);
|
||||||
|
@ -342,7 +158,7 @@ impl<'a> syn::visit_mut::VisitMut for FuncRewriter<'a> {
|
||||||
// check if the function name is in args
|
// check if the function name is in args
|
||||||
// if it is, replace it with the correct name
|
// if it is, replace it with the correct name
|
||||||
if let Expr::Path(path) = &mut *node.func {
|
if let Expr::Path(path) = &mut *node.func {
|
||||||
for (i, effect) in self.args.effects.iter().enumerate() {
|
for ((i, effect), l) in self.args.effects.iter().enumerate().zip(LettersIter::new()) {
|
||||||
for func in &effect.functions {
|
for func in &effect.functions {
|
||||||
let ident = func.alias.clone().unwrap_or(func.ident.clone());
|
let ident = func.alias.clone().unwrap_or(func.ident.clone());
|
||||||
if path.path.is_ident(&ident) {
|
if path.path.is_ident(&ident) {
|
||||||
|
@ -353,17 +169,61 @@ impl<'a> syn::visit_mut::VisitMut for FuncRewriter<'a> {
|
||||||
arguments: syn::PathArguments::None,
|
arguments: syn::PathArguments::None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let span = [Span::call_site()];
|
||||||
|
|
||||||
path.path = effect_path;
|
path.path = effect_path;
|
||||||
|
|
||||||
|
// qualify the trait se we get
|
||||||
|
// <A as Printer>::print
|
||||||
|
// instead of Printer::print
|
||||||
|
let ty: syn::Type = syn::parse_str(&l).unwrap();
|
||||||
|
path.qself = Some(QSelf {
|
||||||
|
lt_token: syn::token::Lt {
|
||||||
|
spans: span.clone(),
|
||||||
|
},
|
||||||
|
ty: Box::new(ty),
|
||||||
|
position: path.path.segments.len() - 1,
|
||||||
|
as_token: Some(syn::token::As { span: span[0] }),
|
||||||
|
gt_token: syn::token::Gt { spans: span },
|
||||||
|
});
|
||||||
|
|
||||||
|
// if the effect function takes a self, add it to the list of params
|
||||||
|
if let Some(mut expr) = func.self_reference.clone() {
|
||||||
// then change the parameters so the handler is the first
|
// then change the parameters so the handler is the first
|
||||||
// get the effect's index, and add the inverse num of `.0`s
|
// get the effect's index, and add the inverse num of `.0`s
|
||||||
let idx = eff_len - (i + 1);
|
let idx = eff_len - (i + 1);
|
||||||
let s = format!("&mut self{}.1", ".0".repeat(idx));
|
|
||||||
let expr: Expr = syn::parse_str(&s).unwrap();
|
for _ in 0..idx {
|
||||||
|
expr = Expr::Field(ExprField {
|
||||||
|
attrs: vec![],
|
||||||
|
base: Box::new(expr),
|
||||||
|
dot_token: Dot {
|
||||||
|
spans: [Span::call_site()],
|
||||||
|
},
|
||||||
|
member: Member::Unnamed(Index {
|
||||||
|
index: 0,
|
||||||
|
span: Span::call_site(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
expr = Expr::Field(ExprField {
|
||||||
|
attrs: vec![],
|
||||||
|
base: Box::new(expr),
|
||||||
|
dot_token: Dot {
|
||||||
|
spans: [Span::call_site()],
|
||||||
|
},
|
||||||
|
member: Member::Unnamed(Index {
|
||||||
|
index: 1,
|
||||||
|
span: Span::call_site(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
node.args.insert(0, expr);
|
node.args.insert(0, expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
use syn::parse::{Parse, ParseStream, Result};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::token::Paren;
|
||||||
|
use syn::{parenthesized, Expr, ExprPath, ExprReference, Ident, Path, Token};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Args {
|
||||||
|
pub name: Option<Ident>,
|
||||||
|
pub effects: Vec<Effect>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Effect {
|
||||||
|
pub name: Ident,
|
||||||
|
pub path: Path,
|
||||||
|
pub paren: Paren,
|
||||||
|
pub functions: Vec<EffectFunction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EffectFunction {
|
||||||
|
pub ident: Ident,
|
||||||
|
pub alias: Option<Ident>,
|
||||||
|
pub self_reference: Option<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Args {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let name = if input.peek2(Token!(=>)) {
|
||||||
|
let name: Ident = input.parse()?;
|
||||||
|
input.parse::<Token![=>]>()?;
|
||||||
|
Some(name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let effects: Vec<Effect> = Punctuated::<Effect, Token![,]>::parse_terminated(input)?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Args { name, effects })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Effect {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let path: Path = input.parse()?;
|
||||||
|
let content;
|
||||||
|
let paren = parenthesized!(content in input);
|
||||||
|
let functions = Punctuated::<EffectFunction, Token![,]>::parse_terminated(&content)?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let name = (&path)
|
||||||
|
.segments
|
||||||
|
.last()
|
||||||
|
.expect("There must be at least one PathSegment")
|
||||||
|
.ident
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
Ok(Effect {
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
functions,
|
||||||
|
paren,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Parse for EffectFunction {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let ident = input.parse()?;
|
||||||
|
|
||||||
|
let self_reference: Option<Expr> = if input.peek(Paren) {
|
||||||
|
let content;
|
||||||
|
parenthesized!(content in input);
|
||||||
|
|
||||||
|
// &mut self
|
||||||
|
if content.peek(Token![&]) && content.peek2(Token![mut]) && content.peek3(Token![self])
|
||||||
|
{
|
||||||
|
Some(Expr::Reference(content.parse::<ExprReference>()?))
|
||||||
|
} else
|
||||||
|
// &self
|
||||||
|
if content.peek(Token![&]) && content.peek2(Token![self]) {
|
||||||
|
Some(Expr::Reference(content.parse::<ExprReference>()?))
|
||||||
|
} else if content.peek(Token![self]) {
|
||||||
|
Some(Expr::Path(content.parse::<ExprPath>()?))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let alias: Option<Ident> = if input.peek(Token![as]) {
|
||||||
|
input.parse::<Token![as]>()?;
|
||||||
|
input.parse()?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EffectFunction {
|
||||||
|
ident,
|
||||||
|
alias,
|
||||||
|
self_reference,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
use effers::program;
|
||||||
|
|
||||||
|
#[program(Incrementer(increment(&self)), Printer(print(&self)))]
|
||||||
|
fn prog(val: u8) -> u8 {
|
||||||
|
let x = increment(val);
|
||||||
|
let y = increment(x);
|
||||||
|
|
||||||
|
print(x);
|
||||||
|
|
||||||
|
x + y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Incrementer {
|
||||||
|
fn increment(&self, v: u8) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct TestInc;
|
||||||
|
impl Incrementer for TestInc {
|
||||||
|
fn increment(&self, v: u8) -> u8 {
|
||||||
|
v + 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Printer {
|
||||||
|
fn print(&self, s: u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Printer1;
|
||||||
|
impl Printer for Printer1 {
|
||||||
|
fn print(&self, s: u8) {
|
||||||
|
println!("1: {}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Printer2 {
|
||||||
|
prefix: String,
|
||||||
|
}
|
||||||
|
impl Printer for Printer2 {
|
||||||
|
fn print(&self, s: u8) {
|
||||||
|
println!("2: {} {}", self.prefix, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// if a program only has Clone effects, the program also becomes clone
|
||||||
|
// same applies for Copy
|
||||||
|
|
||||||
|
// a is Copy since TestInc is Copy
|
||||||
|
let a = Prog.add(TestInc);
|
||||||
|
|
||||||
|
let b = a.add(Printer1);
|
||||||
|
let c = a.add(Printer2 {
|
||||||
|
prefix: "this is a number".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// both TestInc and Printer1 are Copy,
|
||||||
|
// therefore b is copy, and we can call it as much as we want
|
||||||
|
let first_result = b.run(0);
|
||||||
|
assert_eq!(first_result, 9);
|
||||||
|
let second_result = b.run(2);
|
||||||
|
assert_eq!(second_result, 13);
|
||||||
|
|
||||||
|
// since Printer2 is not Copy, but it is Clone,
|
||||||
|
// c is Clone but not Copy
|
||||||
|
let first_result = c.clone().run(0);
|
||||||
|
assert_eq!(first_result, 9);
|
||||||
|
let second_result = c.run(2);
|
||||||
|
assert_eq!(second_result, 13);
|
||||||
|
}
|
|
@ -1,8 +1,13 @@
|
||||||
use effers::program;
|
use effers::program;
|
||||||
|
|
||||||
#[program(Smth => Printer(print as p), Logger(debug, info))]
|
#[program(MyCoolProgram =>
|
||||||
fn smth(val: u8) -> u8 {
|
Printer(print(&self) as p, available as printer_available),
|
||||||
|
Logger(debug(&mut self), info(self))
|
||||||
|
)]
|
||||||
|
fn my_program(val: u8) -> u8 {
|
||||||
|
if printer_available() {
|
||||||
p("hey hi hello");
|
p("hey hi hello");
|
||||||
|
}
|
||||||
|
|
||||||
debug("this is a debug-level log");
|
debug("this is a debug-level log");
|
||||||
info("this is a info-level log");
|
info("this is a info-level log");
|
||||||
|
@ -10,32 +15,44 @@ fn smth(val: u8) -> u8 {
|
||||||
val + 3
|
val + 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[program(Printer(print(&self) as p))]
|
||||||
|
fn other_program() {
|
||||||
|
p("hey hi hello");
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// maybe smth like this?
|
// call the first program twice
|
||||||
let result: u8 = Smth.add(IoPrinter).add(FileLogger).run(3);
|
let result: u8 = MyCoolProgram.add(IoPrinter).add(FileLogger).run(3);
|
||||||
assert_eq!(result, 6);
|
assert_eq!(result, 6);
|
||||||
let other_result: u8 = Smth
|
let other_result: u8 = MyCoolProgram
|
||||||
.add(IoPrinter)
|
.add(IoPrinter)
|
||||||
.add(NetworkLogger {
|
.add(NetworkLogger {
|
||||||
credentials: "secret password".to_string(),
|
credentials: "secret password".to_string(),
|
||||||
})
|
})
|
||||||
.run(8);
|
.run(8);
|
||||||
assert_eq!(other_result, 11);
|
assert_eq!(other_result, 11);
|
||||||
|
|
||||||
|
// other program
|
||||||
|
OtherProgram.add(IoPrinter).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Printer {
|
trait Printer {
|
||||||
fn print(&mut self, s: &str);
|
fn print(&self, s: &str);
|
||||||
|
fn available() -> bool;
|
||||||
}
|
}
|
||||||
trait Logger {
|
trait Logger {
|
||||||
fn debug(&mut self, s: &str);
|
fn debug(&mut self, s: &str);
|
||||||
fn info(&mut self, s: &str);
|
fn info(self, s: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct IoPrinter;
|
struct IoPrinter;
|
||||||
impl Printer for IoPrinter {
|
impl Printer for IoPrinter {
|
||||||
fn print(&mut self, s: &str) {
|
fn print(&self, s: &str) {
|
||||||
println!("{}", s)
|
println!("{}", s)
|
||||||
}
|
}
|
||||||
|
fn available() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FileLogger;
|
struct FileLogger;
|
||||||
|
@ -43,7 +60,7 @@ impl Logger for FileLogger {
|
||||||
fn debug(&mut self, s: &str) {
|
fn debug(&mut self, s: &str) {
|
||||||
println!("debug: {}", s)
|
println!("debug: {}", s)
|
||||||
}
|
}
|
||||||
fn info(&mut self, s: &str) {
|
fn info(self, s: &str) {
|
||||||
println!("info: {}", s)
|
println!("info: {}", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +75,7 @@ impl Logger for NetworkLogger {
|
||||||
s, self.credentials
|
s, self.credentials
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn info(&mut self, s: &str) {
|
fn info(self, s: &str) {
|
||||||
println!(
|
println!(
|
||||||
"info through network: {}; with password {}",
|
"info through network: {}; with password {}",
|
||||||
s, self.credentials
|
s, self.credentials
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Effects can be referenced from inside a module
|
||||||
|
#[effers::program(inc::Incrementer(increment(&self)))]
|
||||||
|
fn prog(val: u8) -> u8 {
|
||||||
|
let x = increment(val);
|
||||||
|
let y = increment(x);
|
||||||
|
x + y
|
||||||
|
}
|
||||||
|
|
||||||
|
mod inc {
|
||||||
|
pub trait Incrementer {
|
||||||
|
fn increment(&self, v: u8) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TestInc;
|
||||||
|
impl Incrementer for TestInc {
|
||||||
|
fn increment(&self, v: u8) -> u8 {
|
||||||
|
v + 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Prog.add(inc::TestInc).run(1);
|
||||||
|
}
|
28
src/lib.rs
28
src/lib.rs
|
@ -4,21 +4,37 @@ pub use effers_derive::program;
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[program(Smth => Printer(print as p), Logger(debug, info))]
|
#[program(Smth => Printer(print(&self) as p), Logger(debug(self), info(&mut self)), inc::Incrementer(increment))]
|
||||||
fn smth(val: u8) -> u8 {
|
fn smth(val: u8) -> u8 {
|
||||||
p("hey hi hello");
|
let s = p("hey hi hello");
|
||||||
|
|
||||||
debug("this is a debug-level log");
|
|
||||||
info("this is a info-level log");
|
info("this is a info-level log");
|
||||||
|
debug("this is a debug-level log");
|
||||||
|
|
||||||
val + 3
|
let _s = p("hey hi hello");
|
||||||
|
|
||||||
|
dbg!(s);
|
||||||
|
|
||||||
|
let x = increment(val);
|
||||||
|
let y = increment(x);
|
||||||
|
x + y
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Printer {
|
trait Printer {
|
||||||
fn print(&mut self, s: &str);
|
fn print(&self, s: &str) -> &str;
|
||||||
}
|
}
|
||||||
trait Logger {
|
trait Logger {
|
||||||
fn debug(&mut self, s: &str);
|
fn debug(self, s: &str);
|
||||||
fn info(&mut self, s: &str);
|
fn info(&mut self, s: &str);
|
||||||
}
|
}
|
||||||
|
mod inc {
|
||||||
|
pub trait Incrementer {
|
||||||
|
fn increment(v: u8) -> u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[program(Printer(print(&self) as p))]
|
||||||
|
fn ohter() {
|
||||||
|
let _s = p("hey hi hello");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue