From 2e232ca5b287d8cee51eb948493ac517948b64c5 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 31 Aug 2021 16:19:29 +0200 Subject: [PATCH] Structured pattern syntax (!) --- syndicate-macros/Cargo.toml | 2 +- syndicate-macros/src/lib.rs | 35 +-- syndicate-macros/src/pat.rs | 88 +++++++ syndicate-macros/src/stx.rs | 224 ++++++++++++++++++ syndicate-macros/src/val.rs | 110 +++++++++ syndicate-server/examples/consumer.rs | 2 +- syndicate-server/examples/pingpong.rs | 2 +- syndicate-server/examples/state-consumer.rs | 2 +- syndicate-server/src/gatekeeper.rs | 2 +- .../src/services/config_watcher.rs | 2 +- .../src/services/debt_reporter.rs | 2 +- .../src/services/tcp_relay_listener.rs | 2 +- .../src/services/unix_relay_listener.rs | 2 +- 13 files changed, 450 insertions(+), 25 deletions(-) create mode 100644 syndicate-macros/src/pat.rs create mode 100644 syndicate-macros/src/stx.rs create mode 100644 syndicate-macros/src/val.rs diff --git a/syndicate-macros/Cargo.toml b/syndicate-macros/Cargo.toml index 1f9b6dc..b8fcf20 100644 --- a/syndicate-macros/Cargo.toml +++ b/syndicate-macros/Cargo.toml @@ -15,6 +15,6 @@ proc-macro = true [dependencies] syndicate = { path = "../syndicate", version = "^0.10.0"} -proc-macro2 = "^1.0" +proc-macro2 = { version = "^1.0", features = ["span-locations"] } quote = "^1.0" syn = "^1.0" diff --git a/syndicate-macros/src/lib.rs b/syndicate-macros/src/lib.rs index 4cc7173..66978ae 100644 --- a/syndicate-macros/src/lib.rs +++ b/syndicate-macros/src/lib.rs @@ -1,14 +1,13 @@ +#![feature(proc_macro_span)] + use syndicate::value::IOValue; use syndicate::value::NestedValue; use syndicate::value::Value; use syndicate::value::text::iovalue_from_str; -extern crate proc_macro; - use proc_macro2::Span; use proc_macro2::TokenStream; -use quote::ToTokens; use quote::quote; use std::convert::TryFrom; @@ -19,6 +18,12 @@ use syn::Ident; use syn::Lit; use syn::LitByteStr; +mod stx; +mod pat; +mod val; + +use pat::lit; + enum SymbolVariant<'a> { Normal(&'a str), Binder(&'a str), @@ -26,19 +31,10 @@ enum SymbolVariant<'a> { Discard, } -fn lit(e: T) -> TokenStream { - quote!( - syndicate::schemas::dataspace_patterns::Pattern::DLit(Box::new( - syndicate::schemas::dataspace_patterns::DLit { value: #e }))) -} - fn compile_sequence_members(vs: &[IOValue]) -> Vec { - let mut i = 0; - vs.iter().map(|f| { + vs.iter().enumerate().map(|(i, f)| { let p = compile_pattern(f); - let result = quote!((#i .into(), #p)); - i += 1; - result + quote!((#i .into(), #p)) }).collect::>() } @@ -177,7 +173,7 @@ fn compile_pattern(v: &IOValue) -> TokenStream { match r.label().value().as_symbol() { None => panic!("Record labels in patterns must be symbols"), Some(label) => - if label == "$" && arity == 1 { + if label.starts_with("$") && arity == 1 { let nested = compile_pattern(&r.fields()[0]); quote!(#P_::Pattern::DBind(Box::new(#P_::DBind { pattern: #nested @@ -228,7 +224,7 @@ fn compile_pattern(v: &IOValue) -> TokenStream { } #[proc_macro] -pub fn pattern(src: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn pattern_str(src: proc_macro::TokenStream) -> proc_macro::TokenStream { if let Lit::Str(s) = parse_macro_input!(src as ExprLit).lit { match iovalue_from_str(&s.value()) { Ok(v) => { @@ -258,3 +254,10 @@ pub fn template(src: proc_macro::TokenStream) -> proc_macro::TokenStream { panic!("Expected literal string containing the template and no more"); } + +//--------------------------------------------------------------------------- + +#[proc_macro] +pub fn pattern(src: proc_macro::TokenStream) -> proc_macro::TokenStream { + pat::pattern(src) +} diff --git a/syndicate-macros/src/pat.rs b/syndicate-macros/src/pat.rs new file mode 100644 index 0000000..bece22d --- /dev/null +++ b/syndicate-macros/src/pat.rs @@ -0,0 +1,88 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; + +use quote::ToTokens; +use quote::quote; + +use syn::parse_macro_input; + +use crate::stx::Stx; +use crate::val::emit_set; +use crate::val::to_value_expr; +use crate::val::value_to_value_expr; + +pub fn lit(e: T) -> TokenStream2 { + quote!( + syndicate::schemas::dataspace_patterns::Pattern::DLit(Box::new( + syndicate::schemas::dataspace_patterns::DLit { value: #e }))) +} + +fn compile_sequence_members(stxs: Vec) -> Result, &'static str> { + stxs.into_iter().enumerate().map(|(i, stx)| { + let p = to_pattern_expr(stx)?; + Ok(quote!((#i .into(), #p))) + }).collect() +} + +fn to_pattern_expr(stx: Stx) -> Result { + #[allow(non_snake_case)] + let P_: TokenStream2 = quote!(syndicate::schemas::dataspace_patterns); + #[allow(non_snake_case)] + let V_: TokenStream2 = quote!(syndicate::value); + #[allow(non_snake_case)] + let MapFromIterator_: TokenStream2 = quote!(<#V_::Map<_, _> as std::iter::FromIterator<_>>::from_iter); + + match stx { + Stx::Atom(v) => + Ok(lit(value_to_value_expr(&v))), + Stx::Binder(_, p) => { + let pe = to_pattern_expr(*p)?; + Ok(quote!(#P_::Pattern::DBind(Box::new(#P_::DBind { pattern: #pe })))) + } + Stx::Subst(e) => + Ok(lit(e)), + Stx::Discard => + Ok(quote!(#P_::Pattern::DDiscard(Box::new(#P_::DDiscard)))), + + Stx::Ctor1(_, _) => todo!(), + Stx::CtorN(_, _) => todo!(), + + Stx::Rec(l, fs) => { + let arity = fs.len() as u128; + let label = to_value_expr(*l)?; + let members = compile_sequence_members(fs)?; + Ok(quote!(#P_::Pattern::DCompound(Box::new(#P_::DCompound::Rec { + ctor: Box::new(#P_::CRec { label: #label, arity: #arity .into() }), + members: #MapFromIterator_(vec![#(#members),*]) + })))) + }, + Stx::Seq(stxs) => { + let arity = stxs.len() as u128; + let members = compile_sequence_members(stxs)?; + Ok(quote!(#P_::Pattern::DCompound(Box::new(#P_::DCompound::Arr { + ctor: Box::new(#P_::CArr { arity: #arity .into() }), + members: #MapFromIterator_(vec![#(#members),*]) + })))) + } + Stx::Set(stxs) => + Ok(lit(emit_set(&stxs.into_iter().map(to_value_expr).collect::,_>>()?))), + Stx::Dict(d) => { + let members = d.into_iter().map(|(k, v)| { + let k = to_value_expr(k)?; + let v = to_pattern_expr(v)?; + Ok(quote!((#k, #v))) + }).collect::, &'static str>>()?; + Ok(quote!(#P_::Pattern::DCompound(Box::new(#P_::DCompound::Dict { + ctor: Box::new(#P_::CDict), + members: #MapFromIterator_(vec![#(#members),*]) + })))) + } + } +} + +pub fn pattern(src: TokenStream) -> TokenStream { + let src2 = src.clone(); + let e = to_pattern_expr(parse_macro_input!(src2 as Stx)).expect("Cannot compile pattern").into(); + // println!("\n{:#} -->\n{:#}\n", &src, &e); + e +} diff --git a/syndicate-macros/src/stx.rs b/syndicate-macros/src/stx.rs new file mode 100644 index 0000000..1ae3baf --- /dev/null +++ b/syndicate-macros/src/stx.rs @@ -0,0 +1,224 @@ +use proc_macro2::Delimiter; +use proc_macro2::LineColumn; +use proc_macro2::TokenTree; + +use syn::ExprLit; +use syn::Lit; +use syn::Result; +use syn::buffer::Cursor; +use syn::parse::Error; +use syn::parse::Parse; +use syn::parse::ParseStream; + +use syndicate::value::Float; +use syndicate::value::Double; +use syndicate::value::IOValue; +use syndicate::value::NestedValue; + +#[derive(Debug, Clone)] +pub enum Stx { + Atom(IOValue), + Binder(Option, Box), + Discard, + Subst(TokenTree), + Ctor1(String, Box), + CtorN(String, Vec<(Stx, Stx)>), + Rec(Box, Vec), + Seq(Vec), + Set(Vec), + Dict(Vec<(Stx, Stx)>), +} + +impl Parse for Stx { + fn parse(input: ParseStream) -> Result { + input.step(|c| parse1(*c)) + } +} + +fn parse_id(mut c: Cursor) -> Result<(String, Cursor)> { + let mut id = String::new(); + let mut prev_pos = c.span().start(); + loop { + if c.eof() || c.span().start() != prev_pos { + return Ok((id, c)); + } else if let Some((p, next)) = c.punct() { + match p.as_char() { + '<' | '>' | '(' | ')' | '{' | '}' | '[' | ']' | ',' | ':' => return Ok((id, c)), + ch => { + id.push(ch); + prev_pos = c.span().end(); + c = next; + } + } + } else if let Some((i, next)) = c.ident() { + id.push_str(&i.to_string()); + prev_pos = i.span().end(); + c = next; + } else { + return Ok((id, c)); + } + } +} + +fn parse_seq(delim_ch: char, mut c: Cursor) -> Result<(Vec, Cursor)> { + let mut stxs = Vec::new(); + loop { + c = skip_commas(c); + + if c.eof() { + return Err(Error::new(c.span(), &format!("Expected {:?}", delim_ch))); + } + + if let Some((p, next)) = c.punct() { + if p.as_char() == delim_ch { + return Ok((stxs, next)); + } + } + + let (stx, next) = parse1(c)?; + stxs.push(stx); + c = next; + } +} + +fn skip_commas(mut c: Cursor) -> Cursor { + loop { + if let Some((p, next)) = c.punct() { + if p.as_char() == ',' { + c = next; + continue; + } + } + return c; + } +} + +fn parse_group_inner<'c, R, F: Fn(Cursor<'c>) -> Result<(R, Cursor<'c>)>>( + mut c: Cursor<'c>, + f: F, + after: Cursor<'c>, +) -> Result<(Vec, Cursor<'c>)> { + let mut stxs = Vec::new(); + loop { + c = skip_commas(c); + if c.eof() { + return Ok((stxs, after)); + } + let (stx, next) = f(c)?; + stxs.push(stx); + c = next; + } +} + +fn parse_group<'c, R, F: Fn(Cursor<'c>) -> Result<(R, Cursor<'c>)>>( + d: Delimiter, + f: F, + c: Cursor<'c>, +) -> Result<(Vec, Cursor<'c>)> { + let (inner, _, after) = c.group(d).unwrap(); + parse_group_inner(inner, f, after) +} + +fn parse_kv(c: Cursor) -> Result<((Stx, Stx), Cursor)> { + let (k, c) = parse1(c)?; + if let Some((p, c)) = c.punct() { + if p.as_char() == ':' { + let (v, c) = parse1(c)?; + return Ok(((k, v), c)); + } + } + Err(Error::new(c.span(), "Expected ':'")) +} + +fn adjacent_id(pos: LineColumn, c: Cursor) -> (Option, Cursor) { + if c.span().start() != pos { + (None, c) + } else { + let (s, next) = parse_id(c).unwrap(); + if s.is_empty() { + (None, c) + } else { + (Some(s), next) + } + } +} + +fn parse_exactly_one<'c>(c: Cursor<'c>) -> Result { + parse1(c).and_then(|(q, c)| if c.eof() { + Ok(q) + } else { + Err(Error::new(c.span(), "No more input expected")) + }) +} + +fn parse1(c: Cursor) -> Result<(Stx, Cursor)> { + if let Some((p, next)) = c.punct() { + match p.as_char() { + '<' => parse_seq('>', next).and_then(|(mut q,c)| if q.is_empty() { + Err(Error::new(c.span(), "Missing Record label")) + } else { + Ok((Stx::Rec(Box::new(q.remove(0)), q), c)) + }), + '{' => parse_group(Delimiter::Brace, parse_kv, c).map(|(q,c)| (Stx::Dict(q),c)), + '[' => parse_group(Delimiter::Bracket, parse1, c).map(|(q,c)| (Stx::Seq(q),c)), + '$' => { + let (maybe_id, next) = adjacent_id(p.span().end(), next); + if let Some((inner, _, next)) = next.group(Delimiter::Parenthesis) { + parse_exactly_one(inner).map( + |q| (Stx::Binder(maybe_id, Box::new(q)), next)) + } else { + Ok((Stx::Binder(maybe_id, Box::new(Stx::Discard)), next)) + } + } + '#' => { + if let Some((inner, _, next)) = next.group(Delimiter::Brace) { + parse_group_inner(inner, parse1, next).map(|(q,c)| (Stx::Set(q),c)) + } else if let Some((tt, next)) = next.token_tree() { + Ok((Stx::Subst(tt), next)) + } else { + Err(Error::new(c.span(), "Expected expression to substitute")) + } + } + _ => Err(Error::new(c.span(), "Unexpected punctuation")), + } + } else if let Some((i, next)) = c.ident() { + if i.to_string() == "_" { + Ok((Stx::Discard, next)) + } else { + parse_id(c).and_then(|(q,c)| { + if let Some((inner, _, next)) = c.group(Delimiter::Parenthesis) { + match parse_group_inner(inner, parse_kv, next) { + Ok((kvs, next)) => Ok((Stx::CtorN(q, kvs), next)), + Err(_) => parse_exactly_one(inner).map( + |v| (Stx::Ctor1(q, Box::new(v)), next)), + } + } else { + Ok((Stx::Atom(IOValue::symbol(&q)), c)) + } + }) + } + } else if let Some((literal, next)) = c.literal() { + let t: ExprLit = syn::parse_str(&literal.to_string())?; + let v = match t.lit { + Lit::Str(s) => IOValue::new(s.value()), + Lit::ByteStr(bs) => IOValue::new(&bs.value()[..]), + Lit::Byte(b) => IOValue::new(b.value()), + Lit::Char(_) => return Err(Error::new(c.span(), "Literal characters not supported")), + Lit::Int(i) => if i.suffix().starts_with("u") || !i.base10_digits().starts_with("-") { + IOValue::new(i.base10_parse::()?) + } else { + IOValue::new(i.base10_parse::()?) + } + Lit::Float(f) => if f.suffix() == "f32" { + IOValue::new(&Float(f.base10_parse::()?)) + } else { + IOValue::new(&Double(f.base10_parse::()?)) + } + Lit::Bool(_) => return Err(Error::new(c.span(), "Literal booleans not supported")), + Lit::Verbatim(_) => return Err(Error::new(c.span(), "Verbatim literals not supported")), + }; + Ok((Stx::Atom(v), next)) + } else { + Err(Error::new(c.span(), "Unexpected input")) + } +} diff --git a/syndicate-macros/src/val.rs b/syndicate-macros/src/val.rs new file mode 100644 index 0000000..05a12da --- /dev/null +++ b/syndicate-macros/src/val.rs @@ -0,0 +1,110 @@ +use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; + +use quote::quote; + +use std::convert::TryFrom; + +use syn::LitByteStr; + +use syndicate::value::IOValue; +use syndicate::value::NestedValue; +use syndicate::value::Value; + +use crate::stx::Stx; + +pub fn emit_record(label: TokenStream2, fs: &[TokenStream2]) -> TokenStream2 { + let arity = fs.len(); + quote!({ + let mut ___r = syndicate::value::Value::record(#label, #arity); + #(___r.fields_vec_mut().push(#fs);)* + ___r.finish().wrap() + }) +} + +pub fn emit_seq(vs: &[TokenStream2]) -> TokenStream2 { + quote!(syndicate::value::Value::from(vec![#(#vs),*]).wrap()) +} + +pub fn emit_set(vs: &[TokenStream2]) -> TokenStream2 { + quote!({ + let mut ___s = syndicate::value::Set::new(); + #(___s.insert(#vs);)* + syndicate::value::Value::from(___s).wrap() + }) +} + +pub fn emit_dict<'a, I: Iterator>(d: I) -> TokenStream2 { + let members: Vec<_> = d.map(|(k, v)| quote!(___d.insert(#k, #v))).collect(); + quote!({ + let mut ___d = syndicate::value::Map::new(); + #(#members;)* + syndicate::value::Value::from(___d).wrap() + }) +} + +pub fn value_to_value_expr(v: &IOValue) -> TokenStream2 { + #[allow(non_snake_case)] + let V_: TokenStream2 = quote!(syndicate::value); + + match v.value() { + Value::Boolean(b) => + quote!(#V_::Value::from(#b).wrap()), + Value::Float(f) => { + let f = f.0; + quote!(#V_::Value::from(#f).wrap()) + } + Value::Double(d) => { + let d = d.0; + quote!(#V_::Value::from(#d).wrap()) + } + Value::SignedInteger(i) => { + let i = i128::try_from(i).expect("Literal integer out-of-range"); + quote!(#V_::Value::from(#i).wrap()) + } + Value::String(s) => + quote!(#V_::Value::from(#s).wrap()), + Value::ByteString(bs) => { + let bs = LitByteStr::new(bs, Span::call_site()); + quote!(#V_::Value::ByteString(#bs.to_vec()).wrap()) + } + Value::Symbol(s) => + quote!(#V_::Value::symbol(#s).wrap()), + Value::Record(r) => + emit_record(value_to_value_expr(r.label()), + &r.fields().iter().map(value_to_value_expr).collect::>()), + Value::Sequence(vs) => + emit_seq(&vs.iter().map(value_to_value_expr).collect::>()), + Value::Set(vs) => + emit_set(&vs.iter().map(value_to_value_expr).collect::>()), + Value::Dictionary(d) => + emit_dict(d.into_iter().map(|(k, v)| (value_to_value_expr(k), value_to_value_expr(v)))), + Value::Embedded(_) => + panic!("Embedded values in compile-time Preserves templates not (yet?) supported"), + } +} + +pub fn to_value_expr(stx: Stx) -> Result { + match stx { + Stx::Atom(v) => Ok(value_to_value_expr(&v)), + Stx::Binder(_, _) => Err("Cannot use binder in literal value"), + Stx::Discard => Err("Cannot use discard in literal value"), + Stx::Subst(e) => Ok(e.into()), + + Stx::Ctor1(_, _) => todo!(), + Stx::CtorN(_, _) => todo!(), + + Stx::Rec(l, fs) => + Ok(emit_record(to_value_expr(*l)?, + &fs.into_iter().map(to_value_expr).collect::,_>>()?)), + Stx::Seq(vs) => + Ok(emit_seq(&vs.into_iter().map(to_value_expr).collect::,_>>()?)), + Stx::Set(vs) => + Ok(emit_set(&vs.into_iter().map(to_value_expr).collect::,_>>()?)), + Stx::Dict(kvs) => + Ok(emit_dict(kvs.into_iter() + .map(|(k, v)| Ok((to_value_expr(k)?, to_value_expr(v)?))) + .collect::,&'static str>>()? + .into_iter())), + } +} diff --git a/syndicate-server/examples/consumer.rs b/syndicate-server/examples/consumer.rs index 54dda3c..bee600f 100644 --- a/syndicate-server/examples/consumer.rs +++ b/syndicate-server/examples/consumer.rs @@ -43,7 +43,7 @@ async fn main() -> Result<(), Box> { }) .create_cap(t); ds.assert(t, &Observe { - pattern: syndicate_macros::pattern!(""), + pattern: syndicate_macros::pattern!{}, observer: Arc::clone(&consumer), }); diff --git a/syndicate-server/examples/pingpong.rs b/syndicate-server/examples/pingpong.rs index b5674ac..6cbbd67 100644 --- a/syndicate-server/examples/pingpong.rs +++ b/syndicate-server/examples/pingpong.rs @@ -171,7 +171,7 @@ async fn main() -> Result<(), Box> { ds.assert(t, &Observe { pattern: { let recv_label = AnyValue::symbol(recv_label); - syndicate_macros::pattern!("<=recv_label $ $>") + syndicate_macros::pattern!{<#(recv_label) $ $>} }, observer: Arc::clone(&consumer), }); diff --git a/syndicate-server/examples/state-consumer.rs b/syndicate-server/examples/state-consumer.rs index e3cb742..ac1d63f 100644 --- a/syndicate-server/examples/state-consumer.rs +++ b/syndicate-server/examples/state-consumer.rs @@ -64,7 +64,7 @@ async fn main() -> Result<(), Box> { }; ds.assert(t, &Observe { - pattern: syndicate_macros::pattern!(""), + pattern: syndicate_macros::pattern!{}, observer: Arc::clone(&consumer), }); diff --git a/syndicate-server/src/gatekeeper.rs b/syndicate-server/src/gatekeeper.rs index 927fbec..e5b9e01 100644 --- a/syndicate-server/src/gatekeeper.rs +++ b/syndicate-server/src/gatekeeper.rs @@ -53,7 +53,7 @@ pub fn handle_resolve( .create_cap(t); if let Some(oh) = ds.assert(t, &dataspace::Observe { // TODO: codegen plugin to generate pattern constructors - pattern: syndicate_macros::pattern!(""), + pattern: syndicate_macros::pattern!{}, observer: handler, }) { Ok(Some(Box::new(move |_ds, t| Ok(t.retract(oh))))) diff --git a/syndicate-server/src/services/config_watcher.rs b/syndicate-server/src/services/config_watcher.rs index 8d31e18..2f230da 100644 --- a/syndicate-server/src/services/config_watcher.rs +++ b/syndicate-server/src/services/config_watcher.rs @@ -42,7 +42,7 @@ pub fn on_demand(t: &mut Activation, ds: Arc) { }) .create_cap(t); ds.assert(t, &Observe { - pattern: syndicate_macros::pattern!(">>"), + pattern: syndicate_macros::pattern!{)>}, observer: monitor, }); Ok(()) diff --git a/syndicate-server/src/services/debt_reporter.rs b/syndicate-server/src/services/debt_reporter.rs index 3903edc..ff3d07a 100644 --- a/syndicate-server/src/services/debt_reporter.rs +++ b/syndicate-server/src/services/debt_reporter.rs @@ -21,7 +21,7 @@ pub fn on_demand(t: &mut Activation, ds: Arc) { .create_cap(t); let spec = from_io_value(&internal_services::DebtReporter)?; ds.assert(t, &Observe { - pattern: syndicate_macros::pattern!(""), + pattern: syndicate_macros::pattern!{}, observer: monitor, }); Ok(()) diff --git a/syndicate-server/src/services/tcp_relay_listener.rs b/syndicate-server/src/services/tcp_relay_listener.rs index d3898f2..f9aa63b 100644 --- a/syndicate-server/src/services/tcp_relay_listener.rs +++ b/syndicate-server/src/services/tcp_relay_listener.rs @@ -31,7 +31,7 @@ pub fn on_demand(t: &mut Activation, ds: Arc, gateway: Arc) { }) .create_cap(t); ds.assert(t, &Observe { - pattern: syndicate_macros::pattern!(">>>"), + pattern: syndicate_macros::pattern!{>)>}, observer: monitor, }); Ok(()) diff --git a/syndicate-server/src/services/unix_relay_listener.rs b/syndicate-server/src/services/unix_relay_listener.rs index 83b788d..617a9f6 100644 --- a/syndicate-server/src/services/unix_relay_listener.rs +++ b/syndicate-server/src/services/unix_relay_listener.rs @@ -32,7 +32,7 @@ pub fn on_demand(t: &mut Activation, ds: Arc, gateway: Arc) { }) .create_cap(t); ds.assert(t, &Observe { - pattern: syndicate_macros::pattern!(">>>"), + pattern: syndicate_macros::pattern!{>)>}, observer: monitor, }); Ok(())