Drop the bad Rust 2018 Cargo forces on you. Rewrite to work with actual native proc_macros.

This commit is contained in:
Bodil Stokke 2018-10-27 19:49:52 +01:00
parent f7ce896ca3
commit 858b16cf34
10 changed files with 224 additions and 150 deletions

View File

@ -2,12 +2,9 @@
name = "typed-html-macros" name = "typed-html-macros"
version = "0.1.0" version = "0.1.0"
authors = ["Bodil Stokke <bodil@bodil.org>"] authors = ["Bodil Stokke <bodil@bodil.org>"]
edition = "2018"
[lib] [lib]
proc-macro = true proc-macro = true
[dependencies] [dependencies]
quote = "0.6.8"
pom = "2.0.1" pom = "2.0.1"
proc-macro2 = "0.4.20"

View File

@ -1,5 +1,6 @@
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro::{Ident, Span, TokenStream};
use std::collections::HashMap;
use map::StringyMap;
pub fn required_children(element: &str) -> &[&str] { pub fn required_children(element: &str) -> &[&str] {
match element { match element {
@ -9,10 +10,13 @@ pub fn required_children(element: &str) -> &[&str] {
} }
} }
pub fn global_attrs(span: Span) -> HashMap<Ident, TokenStream> { pub fn global_attrs(span: Span) -> StringyMap<Ident, TokenStream> {
let mut attrs = HashMap::new(); let mut attrs = StringyMap::new();
let mut insert = |key, value: &str| attrs.insert(Ident::new(key, span), value.parse().unwrap()); {
insert("id", "crate::elements::CssId"); let mut insert =
insert("class", "crate::elements::CssClass"); |key, value: &str| attrs.insert(Ident::new(key, span), value.parse().unwrap());
insert("id", "crate::elements::CssId");
insert("class", "crate::elements::CssClass");
}
attrs attrs
} }

View File

@ -1,17 +1,16 @@
use pom::combinator::*; use pom::combinator::*;
use pom::Parser; use pom::Parser;
use proc_macro2::{Group, Ident, TokenStream, TokenTree}; use proc_macro::{quote, Group, Ident, Literal, TokenStream, TokenTree};
use quote::quote;
use std::collections::HashMap;
use crate::config::global_attrs; use config::global_attrs;
use crate::parser::*; use map::StringyMap;
use parser::*;
// State // State
struct Declare { struct Declare {
name: Ident, name: Ident,
attrs: HashMap<Ident, TokenStream>, attrs: StringyMap<Ident, TokenStream>,
req_children: Vec<Ident>, req_children: Vec<Ident>,
opt_children: Option<TokenStream>, opt_children: Option<TokenStream>,
traits: Vec<TokenStream>, traits: Vec<TokenStream>,
@ -28,37 +27,33 @@ impl Declare {
} }
} }
fn elem_name(&self) -> Ident { fn elem_name(&self) -> TokenTree {
Ident::new( Ident::new(
&format!("Element_{}", self.name.to_string()), &format!("Element_{}", self.name.to_string()),
self.name.span(), self.name.span(),
) )
.into()
} }
fn attr_names(&self) -> impl Iterator<Item = Ident> + '_ { fn attrs(&self) -> impl Iterator<Item = (TokenTree, TokenStream, TokenTree)> + '_ {
self.attrs self.attrs.iter().map(|(key, value)| {
.keys() let attr_name: TokenTree =
.map(|k| Ident::new(&format!("attr_{}", k.to_string()), k.span())) Ident::new(&format!("attr_{}", key.to_string()), key.span()).into();
let attr_type = value.clone();
let attr_str = Literal::string(&key.to_string()).into();
(attr_name, attr_type, attr_str)
})
} }
fn attr_names_str(&self) -> impl Iterator<Item = String> + '_ { fn req_children(&self) -> impl Iterator<Item = (TokenTree, TokenTree, TokenTree)> + '_ {
self.attrs.keys().map(|k| k.to_string()) self.req_children.iter().map(|child| {
} let child_name: TokenTree =
Ident::new(&format!("child_{}", child.to_string()), child.span()).into();
fn req_child_names(&self) -> impl Iterator<Item = Ident> + '_ { let child_type: TokenTree =
self.req_children Ident::new(&format!("Element_{}", child.to_string()), child.span()).into();
.iter() let child_str = Literal::string(&child.to_string()).into();
.map(|c| Ident::new(&format!("child_{}", c.to_string()), c.span())) (child_name, child_type, child_str)
} })
fn req_child_names_str(&self) -> impl Iterator<Item = String> + '_ {
self.req_children.iter().map(|i| i.to_string())
}
fn req_child_types(&self) -> impl Iterator<Item = Ident> + '_ {
self.req_children
.iter()
.map(|c| Ident::new(&format!("Element_{}", c.to_string()), c.span()))
} }
fn into_token_stream(self) -> TokenStream { fn into_token_stream(self) -> TokenStream {
@ -74,46 +69,58 @@ impl Declare {
fn struct_(&self) -> TokenStream { fn struct_(&self) -> TokenStream {
let elem_name = self.elem_name(); let elem_name = self.elem_name();
let attr_name = self.attr_names();
let attr_type = self.attrs.values();
let req_child_name = self.req_child_names();
let req_child_type = self.req_child_types();
let children = match &self.opt_children { let mut body = TokenStream::new();
Some(child_constraint) => quote!(pub children: Vec<Box<#child_constraint>>),
None => TokenStream::new(), for (attr_name, attr_type, _) in self.attrs() {
}; body.extend(quote!( pub $attr_name: Option<$attr_type>, ));
}
body.extend(quote!(
pub data_attributes: std::collections::BTreeMap<String, String>,
));
for (child_name, child_type, _) in self.req_children() {
body.extend(quote!( pub $child_name: Box<$child_type>, ));
}
if let Some(child_constraint) = &self.opt_children {
let child_constraint = child_constraint.clone();
body.extend(quote!(pub children: Vec<Box<$child_constraint>>,));
}
quote!( quote!(
pub struct #elem_name { pub struct $elem_name {
#( pub #attr_name: Option<#attr_type>, )* $body
pub data_attributes: std::collections::BTreeMap<String, String>,
#( pub #req_child_name: Box<#req_child_type>, )*
#children
} }
) )
} }
fn impl_(&self) -> TokenStream { fn impl_(&self) -> TokenStream {
let elem_name = self.elem_name(); let elem_name = self.elem_name();
let req_child_name = self.req_child_names();
let req_child_type = self.req_child_types();
let req_child_name_again = self.req_child_names();
let attr_name = self.attr_names();
let construct_children = match self.opt_children { let mut args = TokenStream::new();
Some(_) => quote!(children: Vec::new()), for (child_name, child_type, _) in self.req_children() {
None => TokenStream::new(), args.extend(quote!( $child_name: Box<$child_type>, ));
}; }
let mut body = TokenStream::new();
for (attr_name, _, _) in self.attrs() {
body.extend(quote!( $attr_name: None, ));
}
body.extend(quote!(data_attributes: std::collections::BTreeMap::new(),));
for (child_name, _, _) in self.req_children() {
body.extend(quote!( $child_name, ));
}
if self.opt_children.is_some() {
body.extend(quote!(children: Vec::new()));
}
quote!( quote!(
impl #elem_name { impl $elem_name {
pub fn new(#(#req_child_name: Box<#req_child_type>),*) -> Self { pub fn new($args) -> Self {
#elem_name { $elem_name {
#( #attr_name: None, )* $body
data_attributes: std::collections::BTreeMap::new(),
#( #req_child_name_again, )*
#construct_children
} }
} }
} }
@ -123,43 +130,47 @@ impl Declare {
fn impl_node(&self) -> TokenStream { fn impl_node(&self) -> TokenStream {
let elem_name = self.elem_name(); let elem_name = self.elem_name();
quote!( quote!(
impl Node for #elem_name {} impl ::elements::Node for $elem_name {}
) )
} }
fn impl_element(&self) -> TokenStream { fn impl_element(&self) -> TokenStream {
let elem_name = self.elem_name(); let elem_name = self.elem_name();
let attr_name_str = self.attr_names_str();
let req_child_str_name = self.req_child_names_str(); let attrs: TokenStream = self.attrs().map(|(_, _, name)| quote!( $name, )).collect();
let reqs: TokenStream = self
.req_children()
.map(|(_, _, name)| quote!( $name, ))
.collect();
quote!( quote!(
impl Element for #elem_name { impl ::elements::Element for $elem_name {
fn attributes() -> &'static [&'static str] { fn attributes() -> &'static [&'static str] {
&[ #(#attr_name_str),* ] &[ $attrs ]
} }
fn required_children() -> &'static [&'static str] { fn required_children() -> &'static [&'static str] {
&[ #(#req_child_str_name),* ] &[ $reqs ]
} }
} }
) )
} }
fn impl_marker_traits(&self) -> TokenStream { fn impl_marker_traits(&self) -> TokenStream {
let trait_for = std::iter::repeat(self.elem_name()); let elem_name = self.elem_name();
let trait_name = self.traits.iter(); let mut body = TokenStream::new();
quote!( for t in &self.traits {
#( let name = t.clone();
impl #trait_name for #trait_for {} body.extend(quote!(
)* impl $name for $elem_name {}
) ));
}
body
} }
fn impl_display(&self) -> TokenStream { fn impl_display(&self) -> TokenStream {
let elem_name = self.elem_name(); let elem_name = self.elem_name();
let name = self.name.to_string(); let name: TokenTree = Literal::string(&self.name.to_string()).into();
let attr_name = self.attr_names();
let attr_name_str = self.attr_names_str();
let req_child_name: Vec<_> = self.req_child_names().collect();
let print_opt_children = if self.opt_children.is_some() { let print_opt_children = if self.opt_children.is_some() {
quote!(for child in &self.children { quote!(for child in &self.children {
@ -168,14 +179,22 @@ impl Declare {
} else { } else {
TokenStream::new() TokenStream::new()
}; };
let print_children = if req_child_name.is_empty() {
let mut print_req_children = TokenStream::new();
for (child_name, _, _) in self.req_children() {
print_req_children.extend(quote!(
self.$child_name.fmt(f)?;
));
}
let print_children = if self.req_children.is_empty() {
if self.opt_children.is_some() { if self.opt_children.is_some() {
quote!(if self.children.is_empty() { quote!(if self.children.is_empty() {
write!(f, "/>") write!(f, "/>")
} else { } else {
write!(f, ">")?; write!(f, ">")?;
#print_opt_children $print_opt_children
write!(f, "</{}>", #name) write!(f, "</{}>", $name)
}) })
} else { } else {
quote!(write!(f, "/>")) quote!(write!(f, "/>"))
@ -183,27 +202,30 @@ impl Declare {
} else { } else {
quote!( quote!(
write!(f, ">")?; write!(f, ">")?;
#( $print_req_children
self.#req_child_name.fmt(f)?; $print_opt_children
)* write!(f, "</{}>", $name)
#print_opt_children
write!(f, "</{}>", #name)
) )
}; };
let mut print_attrs = TokenStream::new();
for (attr_name, _, attr_str) in self.attrs() {
print_attrs.extend(quote!(
if let Some(ref value) = self.$attr_name {
write!(f, " {}={:?}", $attr_str, value.to_string())?;
}
));
}
quote!( quote!(
impl std::fmt::Display for #elem_name { impl std::fmt::Display for $elem_name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "<{}", #name); write!(f, "<{}", $name);
#( $print_attrs
if let Some(ref value) = self.#attr_name {
write!(f, " {}={:?}", #attr_name_str, value.to_string())?;
}
)*
for (key, value) in &self.data_attributes { for (key, value) in &self.data_attributes {
write!(f, " data-{}={:?}", key, value)?; write!(f, " data-{}={:?}", key, value)?;
} }
#print_children $print_children
} }
} }
) )
@ -214,10 +236,13 @@ impl Declare {
fn declare_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<(Ident, TokenStream)>>> fn declare_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<(Ident, TokenStream)>>>
{ {
group().map(|group: Group| { group().map(|group: Group| -> Vec<(Ident, TokenStream)> {
let attr = ident() - punct(':') + type_spec(); let attr = ident() - punct(':') + type_spec();
let parser = attr.repeat(0..);
let input: Vec<TokenTree> = group.stream().into_iter().collect(); let input: Vec<TokenTree> = group.stream().into_iter().collect();
let result = attr.repeat(0..).parse(&input); // FIXME the borrow checker won't let me use plain &input, it seems like a bug.
// It works in Rust 2018, so please get rid of this unsafe block when it stabilises.
let result = parser.parse(unsafe { &*(input.as_slice() as *const _) });
result.unwrap() result.unwrap()
}) })
} }

View File

@ -1,11 +1,10 @@
use pom::combinator::*; use pom::combinator::*;
use pom::Parser; use pom::Parser;
use proc_macro2::{Group, Ident, Literal, TokenStream, TokenTree}; use proc_macro::{quote, Group, Ident, Literal, TokenStream, TokenTree};
use quote::quote;
use std::collections::BTreeMap;
use crate::config::required_children; use config::required_children;
use crate::parser::*; use map::StringyMap;
use parser::*;
#[derive(Clone)] #[derive(Clone)]
enum Node { enum Node {
@ -19,7 +18,8 @@ impl Node {
match self { match self {
Node::Element(el) => el.into_token_stream(), Node::Element(el) => el.into_token_stream(),
Node::Text(text) => { Node::Text(text) => {
quote!(Box::new(typed_html::elements::TextNode::new(#text.to_string()))) let text = TokenTree::Literal(text);
quote!(Box::new(typed_html::elements::TextNode::new($text.to_string())))
} }
Node::Block(_) => panic!("cannot have a block in this position"), Node::Block(_) => panic!("cannot have a block in this position"),
} }
@ -30,20 +30,23 @@ impl Node {
Node::Element(el) => { Node::Element(el) => {
let el = el.into_token_stream(); let el = el.into_token_stream();
quote!( quote!(
element.children.push(#el); element.children.push($el);
) )
} }
tx @ Node::Text(_) => { tx @ Node::Text(_) => {
let tx = tx.into_token_stream(); let tx = tx.into_token_stream();
quote!( quote!(
element.children.push(#tx); element.children.push($tx);
) )
} }
Node::Block(group) => quote!( Node::Block(group) => {
for child in #group.into_iter() { let group: TokenTree = group.into();
quote!(
for child in $group.into_iter() {
element.children.push(child); element.children.push(child);
} }
), )
}
} }
} }
} }
@ -51,12 +54,12 @@ impl Node {
#[derive(Clone)] #[derive(Clone)]
struct Element { struct Element {
name: Ident, name: Ident,
attributes: BTreeMap<Ident, TokenTree>, attributes: StringyMap<Ident, TokenTree>,
children: Vec<Node>, children: Vec<Node>,
} }
fn extract_data_attrs(attrs: &mut BTreeMap<Ident, TokenTree>) -> BTreeMap<String, TokenTree> { fn extract_data_attrs(attrs: &mut StringyMap<Ident, TokenTree>) -> StringyMap<String, TokenTree> {
let mut data = BTreeMap::new(); let mut data = StringyMap::new();
let keys: Vec<Ident> = attrs.keys().cloned().collect(); let keys: Vec<Ident> = attrs.keys().cloned().collect();
for key in keys { for key in keys {
let key_name = key.to_string(); let key_name = key.to_string();
@ -73,7 +76,7 @@ impl Element {
fn new(name: Ident) -> Self { fn new(name: Ident) -> Self {
Element { Element {
name, name,
attributes: BTreeMap::new(), attributes: StringyMap::new(),
children: Vec::new(), children: Vec::new(),
} }
} }
@ -81,7 +84,7 @@ impl Element {
fn into_token_stream(mut self) -> TokenStream { fn into_token_stream(mut self) -> TokenStream {
let name = self.name; let name = self.name;
let name_str = name.to_string(); let name_str = name.to_string();
let typename = Ident::new(&format!("Element_{}", &name_str), name.span()); let typename: TokenTree = Ident::new(&format!("Element_{}", &name_str), name.span()).into();
let req_names = required_children(&name_str); let req_names = required_children(&name_str);
if req_names.len() > self.children.len() { if req_names.len() > self.children.len() {
panic!( panic!(
@ -92,34 +95,44 @@ impl Element {
); );
} }
let data_attrs = extract_data_attrs(&mut self.attributes); let data_attrs = extract_data_attrs(&mut self.attributes);
let data_keys = data_attrs.keys().cloned(); let attrs = self.attributes.iter().map(|(key, value)| {
let data_values = data_attrs.values().cloned(); (
let keys: Vec<_> = self TokenTree::Ident(Ident::new(&format!("attr_{}", key), key.span())),
.attributes value.clone(),
.keys() )
.map(|key| Ident::new(&format!("attr_{}", key), key.span())) });
.collect();
let values: Vec<TokenTree> = self.attributes.values().cloned().collect();
let opt_children = self let opt_children = self
.children .children
.split_off(req_names.len()) .split_off(req_names.len())
.into_iter() .into_iter()
.map(Node::into_child_stream); .map(Node::into_child_stream);
let req_children = self.children.into_iter().map(Node::into_token_stream); let req_children = self.children.into_iter().map(Node::into_token_stream);
let mut body = TokenStream::new();
for (key, value) in attrs {
body.extend(quote!(
element.$key = Some($value.into());
));
}
for (key, value) in data_attrs
.iter()
.map(|(k, v)| (TokenTree::from(Literal::string(&k)), v.clone()))
{
body.extend(quote!(
element.data_attributes.insert($key.into(), $value.into());
));
}
body.extend(opt_children);
let mut args = TokenStream::new();
for arg in req_children {
args.extend(quote!( $arg, ));
}
quote!( quote!(
{ {
let mut element = typed_html::elements::#typename::new( let mut element = typed_html::elements::$typename::new($args);
#({ #req_children }),* $body
);
#(
element.#keys = Some(#values.into());
)*
#(
element.data_attributes.insert(#data_keys.into(), #data_values.into());
)*
#(
#opt_children
)*
Box::new(element) Box::new(element)
} }
) )

View File

@ -1,32 +1,34 @@
#![feature(proc_macro_hygiene)]
#![feature(proc_macro_quote)]
#![feature(proc_macro_span)] #![feature(proc_macro_span)]
extern crate pom;
extern crate proc_macro; extern crate proc_macro;
use proc_macro2::{TokenStream, TokenTree}; use proc_macro::{TokenStream, TokenTree};
mod config; mod config;
mod declare; mod declare;
mod html; mod html;
mod map;
mod parser; mod parser;
#[proc_macro] #[proc_macro]
pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn html(input: TokenStream) -> TokenStream {
let input: TokenStream = input.into();
let input: Vec<TokenTree> = input.into_iter().collect(); let input: Vec<TokenTree> = input.into_iter().collect();
let result = html::expand_html(&input); let result = html::expand_html(&input);
match result { match result {
Err(error) => panic!(parser::parse_error(&input, &error)), Err(error) => panic!(parser::parse_error(&input, &error)),
Ok(ts) => ts.into(), Ok(ts) => ts,
} }
} }
#[proc_macro] #[proc_macro]
pub fn declare_element(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn declare_element(input: TokenStream) -> TokenStream {
let input: TokenStream = input.into();
let input: Vec<TokenTree> = input.into_iter().collect(); let input: Vec<TokenTree> = input.into_iter().collect();
let result = declare::expand_declare(&input); let result = declare::expand_declare(&input);
match result { match result {
Err(error) => panic!(parser::parse_error(&input, &error)), Err(error) => panic!(parser::parse_error(&input, &error)),
Ok(ts) => ts.into(), Ok(ts) => ts,
} }
} }

31
macros/src/map.rs Normal file
View File

@ -0,0 +1,31 @@
use std::collections::BTreeMap;
#[derive(Clone)]
pub struct StringyMap<K, V>(BTreeMap<String, (K, V)>);
impl<K, V> StringyMap<K, V>
where
K: ToString,
{
pub fn new() -> Self {
StringyMap(BTreeMap::new())
}
pub fn insert(&mut self, k: K, v: V) -> Option<V> {
let s = k.to_string();
self.0.insert(s, (k, v)).map(|(_, v)| v)
}
pub fn remove(&mut self, k: &K) -> Option<V> {
let s = k.to_string();
self.0.remove(&s).map(|(_, v)| v)
}
pub fn iter(&self) -> impl Iterator<Item = &(K, V)> {
self.0.values()
}
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.0.values().map(|(k, _)| k)
}
}

View File

@ -1,6 +1,6 @@
use pom::combinator::*; use pom::combinator::*;
use pom::{Error, Parser}; use pom::{Error, Parser};
use proc_macro2::{Group, Ident, Literal, Punct, TokenStream, TokenTree}; use proc_macro::{Group, Ident, Literal, Punct, TokenStream, TokenTree};
pub fn unit<'a, I: 'a, A: Clone>(value: A) -> Combinator<impl Parser<'a, I, Output = A>> { pub fn unit<'a, I: 'a, A: Clone>(value: A) -> Combinator<impl Parser<'a, I, Output = A>> {
comb(move |_, start| Ok((value.clone(), start))) comb(move |_, start| Ok((value.clone(), start)))
@ -29,7 +29,7 @@ pub fn ident<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Ident>> {
pub fn ident_match<'a>(name: String) -> Combinator<impl Parser<'a, TokenTree, Output = ()>> { pub fn ident_match<'a>(name: String) -> Combinator<impl Parser<'a, TokenTree, Output = ()>> {
comb(move |input: &[TokenTree], start| match input.get(start) { comb(move |input: &[TokenTree], start| match input.get(start) {
Some(TokenTree::Ident(i)) => { Some(TokenTree::Ident(i)) => {
if *i == name { if i.to_string() == name {
Ok(((), start + 1)) Ok(((), start + 1))
} else { } else {
Err(Error::Mismatch { Err(Error::Mismatch {
@ -93,8 +93,7 @@ pub fn html_ident<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Ident>
( (
match span { match span {
None => Some(token.span()), None => Some(token.span()),
// FIXME: Some(span) => Some(span.join(token.span())), Some(span) => span.join(token.span()),
span => span,
}, },
match token { match token {
TokenTree::Ident(ident) => name + &ident.to_string(), TokenTree::Ident(ident) => name + &ident.to_string(),

View File

@ -2,7 +2,6 @@
name = "typed-html" name = "typed-html"
version = "0.1.0" version = "0.1.0"
authors = ["Bodil Stokke <bodil@bodil.org>"] authors = ["Bodil Stokke <bodil@bodil.org>"]
edition = "2018"
[dependencies] [dependencies]
typed-html-macros = { path = "../macros" } typed-html-macros = { path = "../macros" }

View File

@ -2,6 +2,7 @@
#[macro_use] #[macro_use]
extern crate typed_html; extern crate typed_html;
extern crate typed_html_macros;
use typed_html_macros::html; use typed_html_macros::html;

View File

@ -1 +1,4 @@
extern crate http;
extern crate typed_html_macros;
pub mod elements; pub mod elements;