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"
version = "0.1.0"
authors = ["Bodil Stokke <bodil@bodil.org>"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
quote = "0.6.8"
pom = "2.0.1"
proc-macro2 = "0.4.20"

View File

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

View File

@ -1,17 +1,16 @@
use pom::combinator::*;
use pom::Parser;
use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use quote::quote;
use std::collections::HashMap;
use proc_macro::{quote, Group, Ident, Literal, TokenStream, TokenTree};
use crate::config::global_attrs;
use crate::parser::*;
use config::global_attrs;
use map::StringyMap;
use parser::*;
// State
struct Declare {
name: Ident,
attrs: HashMap<Ident, TokenStream>,
attrs: StringyMap<Ident, TokenStream>,
req_children: Vec<Ident>,
opt_children: Option<TokenStream>,
traits: Vec<TokenStream>,
@ -28,37 +27,33 @@ impl Declare {
}
}
fn elem_name(&self) -> Ident {
fn elem_name(&self) -> TokenTree {
Ident::new(
&format!("Element_{}", self.name.to_string()),
self.name.span(),
)
.into()
}
fn attr_names(&self) -> impl Iterator<Item = Ident> + '_ {
self.attrs
.keys()
.map(|k| Ident::new(&format!("attr_{}", k.to_string()), k.span()))
fn attrs(&self) -> impl Iterator<Item = (TokenTree, TokenStream, TokenTree)> + '_ {
self.attrs.iter().map(|(key, value)| {
let attr_name: TokenTree =
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> + '_ {
self.attrs.keys().map(|k| k.to_string())
}
fn req_child_names(&self) -> impl Iterator<Item = Ident> + '_ {
self.req_children
.iter()
.map(|c| Ident::new(&format!("child_{}", c.to_string()), c.span()))
}
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 req_children(&self) -> impl Iterator<Item = (TokenTree, TokenTree, TokenTree)> + '_ {
self.req_children.iter().map(|child| {
let child_name: TokenTree =
Ident::new(&format!("child_{}", child.to_string()), child.span()).into();
let child_type: TokenTree =
Ident::new(&format!("Element_{}", child.to_string()), child.span()).into();
let child_str = Literal::string(&child.to_string()).into();
(child_name, child_type, child_str)
})
}
fn into_token_stream(self) -> TokenStream {
@ -74,46 +69,58 @@ impl Declare {
fn struct_(&self) -> TokenStream {
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 {
Some(child_constraint) => quote!(pub children: Vec<Box<#child_constraint>>),
None => TokenStream::new(),
};
let mut body = 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!(
pub struct #elem_name {
#( pub #attr_name: Option<#attr_type>, )*
pub data_attributes: std::collections::BTreeMap<String, String>,
#( pub #req_child_name: Box<#req_child_type>, )*
#children
pub struct $elem_name {
$body
}
)
}
fn impl_(&self) -> TokenStream {
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 {
Some(_) => quote!(children: Vec::new()),
None => TokenStream::new(),
};
let mut args = TokenStream::new();
for (child_name, child_type, _) in self.req_children() {
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!(
impl #elem_name {
pub fn new(#(#req_child_name: Box<#req_child_type>),*) -> Self {
#elem_name {
#( #attr_name: None, )*
data_attributes: std::collections::BTreeMap::new(),
#( #req_child_name_again, )*
#construct_children
impl $elem_name {
pub fn new($args) -> Self {
$elem_name {
$body
}
}
}
@ -123,43 +130,47 @@ impl Declare {
fn impl_node(&self) -> TokenStream {
let elem_name = self.elem_name();
quote!(
impl Node for #elem_name {}
impl ::elements::Node for $elem_name {}
)
}
fn impl_element(&self) -> TokenStream {
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!(
impl Element for #elem_name {
impl ::elements::Element for $elem_name {
fn attributes() -> &'static [&'static str] {
&[ #(#attr_name_str),* ]
&[ $attrs ]
}
fn required_children() -> &'static [&'static str] {
&[ #(#req_child_str_name),* ]
&[ $reqs ]
}
}
)
}
fn impl_marker_traits(&self) -> TokenStream {
let trait_for = std::iter::repeat(self.elem_name());
let trait_name = self.traits.iter();
quote!(
#(
impl #trait_name for #trait_for {}
)*
)
let elem_name = self.elem_name();
let mut body = TokenStream::new();
for t in &self.traits {
let name = t.clone();
body.extend(quote!(
impl $name for $elem_name {}
));
}
body
}
fn impl_display(&self) -> TokenStream {
let elem_name = self.elem_name();
let name = self.name.to_string();
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 name: TokenTree = Literal::string(&self.name.to_string()).into();
let print_opt_children = if self.opt_children.is_some() {
quote!(for child in &self.children {
@ -168,14 +179,22 @@ impl Declare {
} else {
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() {
quote!(if self.children.is_empty() {
write!(f, "/>")
} else {
write!(f, ">")?;
#print_opt_children
write!(f, "</{}>", #name)
$print_opt_children
write!(f, "</{}>", $name)
})
} else {
quote!(write!(f, "/>"))
@ -183,27 +202,30 @@ impl Declare {
} else {
quote!(
write!(f, ">")?;
#(
self.#req_child_name.fmt(f)?;
)*
#print_opt_children
write!(f, "</{}>", #name)
$print_req_children
$print_opt_children
write!(f, "</{}>", $name)
)
};
quote!(
impl std::fmt::Display for #elem_name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "<{}", #name);
#(
if let Some(ref value) = self.#attr_name {
write!(f, " {}={:?}", #attr_name_str, value.to_string())?;
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!(
impl std::fmt::Display for $elem_name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "<{}", $name);
$print_attrs
for (key, value) in &self.data_attributes {
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)>>>
{
group().map(|group: Group| {
group().map(|group: Group| -> Vec<(Ident, TokenStream)> {
let attr = ident() - punct(':') + type_spec();
let parser = attr.repeat(0..);
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()
})
}

View File

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

View File

@ -1,32 +1,34 @@
#![feature(proc_macro_hygiene)]
#![feature(proc_macro_quote)]
#![feature(proc_macro_span)]
extern crate pom;
extern crate proc_macro;
use proc_macro2::{TokenStream, TokenTree};
use proc_macro::{TokenStream, TokenTree};
mod config;
mod declare;
mod html;
mod map;
mod parser;
#[proc_macro]
pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: TokenStream = input.into();
pub fn html(input: TokenStream) -> TokenStream {
let input: Vec<TokenTree> = input.into_iter().collect();
let result = html::expand_html(&input);
match result {
Err(error) => panic!(parser::parse_error(&input, &error)),
Ok(ts) => ts.into(),
Ok(ts) => ts,
}
}
#[proc_macro]
pub fn declare_element(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: TokenStream = input.into();
pub fn declare_element(input: TokenStream) -> TokenStream {
let input: Vec<TokenTree> = input.into_iter().collect();
let result = declare::expand_declare(&input);
match result {
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::{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>> {
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 = ()>> {
comb(move |input: &[TokenTree], start| match input.get(start) {
Some(TokenTree::Ident(i)) => {
if *i == name {
if i.to_string() == name {
Ok(((), start + 1))
} else {
Err(Error::Mismatch {
@ -93,8 +93,7 @@ pub fn html_ident<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Ident>
(
match span {
None => Some(token.span()),
// FIXME: Some(span) => Some(span.join(token.span())),
span => span,
Some(span) => span.join(token.span()),
},
match token {
TokenTree::Ident(ident) => name + &ident.to_string(),

View File

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

View File

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

View File

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