From dee331c5ebec7a2389f61fbb5bc631d08f9a9b19 Mon Sep 17 00:00:00 2001 From: Bodil Stokke Date: Sat, 27 Oct 2018 16:59:30 +0100 Subject: [PATCH] Cleanup, reorg, data attributes. --- macros/src/config.rs | 18 ++ macros/src/declare.rs | 261 +++++++++++++++++++++ macros/src/html.rs | 181 +++++++++++++++ macros/src/lib.rs | 450 +------------------------------------ macros/src/parser.rs | 145 ++++++++++++ typed-html/src/bin/main.rs | 4 +- typed-html/src/elements.rs | 7 +- typed-html/src/lib.rs | 4 - typed-html/src/node.rs | 117 ---------- 9 files changed, 623 insertions(+), 564 deletions(-) create mode 100644 macros/src/config.rs create mode 100644 macros/src/declare.rs create mode 100644 macros/src/html.rs create mode 100644 macros/src/parser.rs delete mode 100644 typed-html/src/node.rs diff --git a/macros/src/config.rs b/macros/src/config.rs new file mode 100644 index 0000000..26b1111 --- /dev/null +++ b/macros/src/config.rs @@ -0,0 +1,18 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use std::collections::HashMap; + +pub fn required_children(element: &str) -> &[&str] { + match element { + "html" => &["head", "body"], + "head" => &["title"], + _ => &[], + } +} + +pub fn global_attrs(span: Span) -> HashMap { + let mut attrs = HashMap::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 +} diff --git a/macros/src/declare.rs b/macros/src/declare.rs new file mode 100644 index 0000000..afee7cc --- /dev/null +++ b/macros/src/declare.rs @@ -0,0 +1,261 @@ +use pom::combinator::*; +use pom::Parser; +use proc_macro2::{Group, Ident, TokenStream, TokenTree}; +use quote::quote; +use std::collections::HashMap; + +use crate::config::global_attrs; +use crate::parser::*; + +// State + +struct Declare { + name: Ident, + attrs: HashMap, + req_children: Vec, + opt_children: Option, + traits: Vec, +} + +impl Declare { + fn new(name: Ident) -> Self { + Declare { + attrs: global_attrs(name.span()), + req_children: Vec::new(), + opt_children: None, + traits: Vec::new(), + name, + } + } + + fn elem_name(&self) -> Ident { + Ident::new( + &format!("Element_{}", self.name.to_string()), + self.name.span(), + ) + } + + fn attr_names(&self) -> impl Iterator + '_ { + self.attrs + .keys() + .map(|k| Ident::new(&format!("attr_{}", k.to_string()), k.span())) + } + + fn attr_names_str(&self) -> impl Iterator + '_ { + self.attrs.keys().map(|k| k.to_string()) + } + + fn req_child_names(&self) -> impl Iterator + '_ { + self.req_children + .iter() + .map(|c| Ident::new(&format!("child_{}", c.to_string()), c.span())) + } + + fn req_child_names_str(&self) -> impl Iterator + '_ { + self.req_children.iter().map(|i| i.to_string()) + } + + fn req_child_types(&self) -> impl Iterator + '_ { + self.req_children + .iter() + .map(|c| Ident::new(&format!("Element_{}", c.to_string()), c.span())) + } + + fn into_token_stream(self) -> TokenStream { + let mut stream = TokenStream::new(); + stream.extend(self.struct_()); + stream.extend(self.impl_()); + stream.extend(self.impl_node()); + stream.extend(self.impl_element()); + stream.extend(self.impl_marker_traits()); + stream.extend(self.impl_display()); + stream + } + + 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>), + None => TokenStream::new(), + }; + + quote!( + pub struct #elem_name { + #( pub #attr_name: Option<#attr_type>, )* + pub data_attributes: std::collections::BTreeMap, + #( pub #req_child_name: #req_child_type, )* + #children + } + ) + } + + 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(), + }; + + quote!( + impl #elem_name { + pub fn new(#(#req_child_name: #req_child_type),*) -> Self { + #elem_name { + #( #attr_name: None, )* + data_attributes: std::collections::BTreeMap::new(), + #( #req_child_name_again, )* + #construct_children + } + } + } + ) + } + + fn impl_node(&self) -> TokenStream { + let elem_name = self.elem_name(); + quote!( + impl 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(); + quote!( + impl Element for #elem_name { + fn attributes() -> &'static [&'static str] { + &[ #(#attr_name_str),* ] + } + + fn required_children() -> &'static [&'static str] { + &[ #(#req_child_str_name),* ] + } + } + ) + } + + 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 {} + )* + ) + } + + 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 print_opt_children = if self.opt_children.is_some() { + quote!(for child in &self.children { + child.fmt(f)?; + }) + } else { + TokenStream::new() + }; + let print_children = if req_child_name.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) + }) + } else { + quote!(write!(f, "/>")) + } + } else { + quote!( + write!(f, ">")?; + #( + self.#req_child_name.fmt(f)?; + )* + #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())?; + } + )* + for (key, value) in &self.data_attributes { + write!(f, " data-{}={:?}", key, value)?; + } + #print_children + } + } + ) + } +} + +// Parser + +fn declare_attrs<'a>() -> Combinator>> +{ + group().map(|group: Group| { + let attr = ident() - punct(':') + type_spec(); + let input: Vec = group.stream().into_iter().collect(); + let result = attr.repeat(0..).parse(&input); + result.unwrap() + }) +} + +fn declare_children<'a>() -> Combinator>> { + group().map(|group: Group| { + let input: Vec = group.stream().into_iter().collect(); + let children = (ident() - punct(',').opt()).repeat(0..); + let result = children.parse(&input); + result.unwrap() + }) +} + +fn declare_traits<'a>() -> Combinator>> { + group().map(|group: Group| { + let input: Vec = group.stream().into_iter().collect(); + let traits = (type_spec() - punct(',').opt()).repeat(0..); + let result = traits.parse(&input); + result.unwrap() + }) +} + +fn declare<'a>() -> Combinator> { + (ident() + declare_attrs() + declare_children() + declare_traits().opt() + type_spec().opt()) + .map(|((((name, attrs), children), traits), child_type)| { + let mut declare = Declare::new(name); + for (key, value) in attrs { + declare.attrs.insert(key, value); + } + for child in children { + declare.req_children.push(child); + } + declare.opt_children = child_type; + declare.traits = traits.unwrap_or_default(); + declare + }) +} + +pub fn expand_declare(input: &[TokenTree]) -> pom::Result { + declare().parse(input).map(|decl| decl.into_token_stream()) +} diff --git a/macros/src/html.rs b/macros/src/html.rs new file mode 100644 index 0000000..1ba900b --- /dev/null +++ b/macros/src/html.rs @@ -0,0 +1,181 @@ +use pom::combinator::*; +use pom::Parser; +use proc_macro2::{Group, Ident, Literal, TokenStream, TokenTree}; +use quote::quote; +use std::collections::BTreeMap; + +use crate::config::required_children; +use crate::parser::*; + +#[derive(Clone)] +enum Node { + Element(Element), + Text(Literal), + Block(Group), +} + +impl Node { + fn into_token_stream(self) -> TokenStream { + match self { + Node::Element(el) => el.into_token_stream(), + Node::Text(text) => quote!(typed_html::elements::TextNode::new(#text.to_string())), + Node::Block(_) => panic!("cannot have a block in this position"), + } + } + + fn into_child_stream(self) -> TokenStream { + match self { + Node::Element(el) => { + let el = el.into_token_stream(); + quote!( + element.children.push(Box::new(#el)); + ) + } + tx @ Node::Text(_) => { + let tx = tx.into_token_stream(); + quote!( + element.children.push(Box::new(#tx)); + ) + } + Node::Block(group) => quote!({ + let iter = #group.into_iter(); + for child in iter { + element.children.push(Box::new(child)); + } + }), + } + } +} + +#[derive(Clone)] +struct Element { + name: Ident, + attributes: BTreeMap, + children: Vec, +} + +fn extract_data_attrs(attrs: &mut BTreeMap) -> BTreeMap { + let mut data = BTreeMap::new(); + let keys: Vec = attrs.keys().cloned().collect(); + for key in keys { + let key_name = key.to_string(); + let prefix = "data_"; + if key_name.starts_with(prefix) { + let value = attrs.remove(&key).unwrap(); + data.insert(key_name[prefix.len()..].to_string(), value); + } + } + data +} + +impl Element { + fn new(name: Ident) -> Self { + Element { + name, + attributes: BTreeMap::new(), + children: Vec::new(), + } + } + + 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 req_names = required_children(&name_str); + if req_names.len() > self.children.len() { + panic!( + "<{}> requires {} children but found only {}", + name_str, + req_names.len(), + self.children.len() + ); + } + 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 = self.attributes.values().cloned().collect(); + 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); + 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 + )* + element + } + ) + } +} + +fn element_start<'a>() -> Combinator> { + (punct('<') * html_ident()).map(Element::new) +} + +fn attr_value<'a>() -> Combinator> { + literal().map(TokenTree::Literal) | ident().map(TokenTree::Ident) +} + +fn attr<'a>() -> Combinator> { + html_ident() + (punct('=') * attr_value()) +} + +fn element_with_attrs<'a>() -> Combinator> { + (element_start() + attr().repeat(0..)).map(|(mut el, attrs)| { + for (name, value) in attrs { + el.attributes.insert(name, value); + } + el + }) +} + +fn element_single<'a>() -> Combinator> { + element_with_attrs() - punct('/') - punct('>') +} + +fn element_open<'a>() -> Combinator> { + element_with_attrs() - punct('>') +} + +fn element_close<'a>(name: &str) -> Combinator> { + let name = name.to_lowercase(); + // TODO make this return an error message containing the tag name + punct('<') * punct('/') * ident_match(name) * punct('>').discard() +} + +fn element_with_children<'a>() -> Combinator> { + (element_open() + comb(node).repeat(0..)).map(|(mut el, children)| { + el.children.extend(children.into_iter()); + el + }) >> |el: Element| element_close(&el.name.to_string()).expect("closing tag") * unit(el) +} + +fn node(input: &[TokenTree], start: usize) -> pom::Result<(Node, usize)> { + (element_single().map(Node::Element) + | element_with_children().map(Node::Element) + | literal().map(Node::Text) + | group().map(Node::Block)) + .0 + .parse(input, start) +} + +pub fn expand_html(input: &[TokenTree]) -> pom::Result { + comb(node).parse(input).map(|el| el.into_token_stream()) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index e58a081..584e629 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,451 +1,21 @@ -#![recursion_limit = "32768"] +#![feature(proc_macro_span)] extern crate proc_macro; -use pom::combinator::*; -use pom::{Error, Parser}; -use proc_macro2::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree}; -use quote::quote; -use std::collections::HashMap; +use proc_macro2::{TokenStream, TokenTree}; -fn required_children(element: &str) -> &[&str] { - match element { - "html" => &["head", "body"], - "head" => &["title"], - _ => &[], - } -} - -fn global_attrs(span: Span) -> HashMap { - let mut attrs = HashMap::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 -} - -#[derive(Clone)] -enum Node { - Element(Element), - Text(Literal), - Block(Group), -} - -impl Node { - fn into_token_stream(self) -> TokenStream { - match self { - Node::Element(el) => el.into_token_stream(), - Node::Text(text) => quote!(typed_html::elements::TextNode::new(#text.to_string())), - Node::Block(_) => panic!("cannot have a block in this position"), - } - } - - fn into_child_stream(self) -> TokenStream { - match self { - Node::Element(el) => { - let el = el.into_token_stream(); - quote!( - element.children.push(Box::new(#el)); - ) - } - tx @ Node::Text(_) => { - let tx = tx.into_token_stream(); - quote!( - element.children.push(Box::new(#tx)); - ) - } - Node::Block(group) => quote!({ - let iter = #group.into_iter(); - for child in iter { - element.children.push(Box::new(child)); - } - }), - } - } -} - -#[derive(Clone)] -struct Element { - name: Ident, - attributes: HashMap, - children: Vec, -} - -impl Element { - fn new(name: Ident) -> Self { - Element { - name, - attributes: HashMap::new(), - children: Vec::new(), - } - } - - 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 req_names = required_children(&name_str); - if req_names.len() > self.children.len() { - panic!( - "<{}> requires {} children but found only {}", - name_str, - req_names.len(), - self.children.len() - ); - } - let keys: Vec<_> = self - .attributes - .keys() - .map(|key| Ident::new(&format!("attr_{}", key), key.span())) - .collect(); - let values: Vec = self.attributes.values().cloned().collect(); - let opt_children = self - .children - .split_off(req_names.len()) - .into_iter() - .map(Node::into_child_stream); - for (index, child) in self.children.iter().enumerate() { - match child { - Node::Element(_) => (), - _ => panic!( - "child #{} of {} must be a {} element", - index + 1, - &name, - req_names[index] - ), - } - } - let req_children = self.children.into_iter().map(Node::into_token_stream); - quote!( - { - let mut element = typed_html::elements::#typename::new( - #({ #req_children }),* - ); - #( - element.#keys = Some(#values.into()); - )* - #( - #opt_children - )* - element - } - ) - } -} - -fn unit<'a, I: 'a, A: Clone>(value: A) -> Combinator> { - comb(move |_, start| Ok((value.clone(), start))) -} - -fn punct<'a>(punct: char) -> Combinator> { - comb(move |input: &[TokenTree], start| match input.get(start) { - Some(TokenTree::Punct(p)) if p.as_char() == punct => Ok((p.clone(), start + 1)), - _ => Err(Error::Mismatch { - message: format!("expected {:?}", punct), - position: start, - }), - }) -} - -fn ident<'a>() -> Combinator> { - comb(|input: &[TokenTree], start| match input.get(start) { - Some(TokenTree::Ident(i)) => Ok((i.clone(), start + 1)), - _ => Err(Error::Mismatch { - message: "expected identifier".to_string(), - position: start, - }), - }) -} - -fn ident_match<'a>(name: String) -> Combinator> { - comb(move |input: &[TokenTree], start| match input.get(start) { - Some(TokenTree::Ident(i)) => { - if *i == name { - Ok(((), start + 1)) - } else { - Err(Error::Mismatch { - message: format!("expected '', found ''", name, i.to_string()), - position: start, - }) - } - } - _ => Err(Error::Mismatch { - message: "expected identifier".to_string(), - position: start, - }), - }) -} - -fn literal<'a>() -> Combinator> { - comb(|input: &[TokenTree], start| match input.get(start) { - Some(TokenTree::Literal(l)) => Ok((l.clone(), start + 1)), - _ => Err(Error::Mismatch { - message: "expected literal".to_string(), - position: start, - }), - }) -} - -fn group<'a>() -> Combinator> { - comb(|input: &[TokenTree], start| match input.get(start) { - Some(TokenTree::Group(g)) => Ok((g.clone(), start + 1)), - _ => Err(Error::Mismatch { - message: "expected group".to_string(), - position: start, - }), - }) -} - -fn element_start<'a>() -> Combinator> { - (punct('<') * ident()).map(Element::new) -} - -fn attr_value<'a>() -> Combinator> { - literal().map(TokenTree::Literal) | ident().map(TokenTree::Ident) -} - -fn attr<'a>() -> Combinator> { - ident() + (punct('=') * attr_value()) -} - -fn element_with_attrs<'a>() -> Combinator> { - (element_start() + attr().repeat(0..)).map(|(mut el, attrs)| { - for (name, value) in attrs { - el.attributes.insert(name, value); - } - el - }) -} - -fn element_single<'a>() -> Combinator> { - element_with_attrs() - punct('/') - punct('>') -} - -fn element_open<'a>() -> Combinator> { - element_with_attrs() - punct('>') -} - -fn element_close<'a>(name: &str) -> Combinator> { - let name = name.to_lowercase(); - // TODO make this return an error message containing the tag name - punct('<') * punct('/') * ident_match(name) * punct('>').discard() -} - -fn element_with_children<'a>() -> Combinator> { - (element_open() + comb(node).repeat(0..)).map(|(mut el, children)| { - el.children.extend(children.into_iter()); - el - }) >> |el: Element| element_close(&el.name.to_string()).expect("closing tag") * unit(el) -} - -fn node(input: &[TokenTree], start: usize) -> pom::Result<(Node, usize)> { - (element_single().map(Node::Element) - | element_with_children().map(Node::Element) - | literal().map(Node::Text) - | group().map(Node::Block)) - .0 - .parse(input, start) -} - -fn expand_html(input: &[TokenTree]) -> pom::Result { - comb(node).parse(input).map(|el| el.into_token_stream()) -} - -struct Declare { - name: Ident, - attrs: HashMap, - req_children: Vec, - opt_children: Option, - traits: Vec, -} - -impl Declare { - fn new(name: Ident) -> Self { - Declare { - attrs: global_attrs(name.span()), - req_children: Vec::new(), - opt_children: None, - traits: Vec::new(), - name, - } - } - - fn into_token_stream(self) -> TokenStream { - let elem_name = Ident::new( - &format!("Element_{}", self.name.to_string()), - self.name.span(), - ); - let name = self.name.to_string(); - let attr_name: Vec = self - .attrs - .keys() - .map(|k| Ident::new(&format!("attr_{}", k.to_string()), k.span())) - .collect(); - let attr_name_2 = attr_name.clone(); - let attr_name_3 = attr_name.clone(); - let attr_name_str = self.attrs.keys().map(|k| k.to_string()); - let attr_type = self.attrs.values().cloned(); - let req_child_name: Vec = self - .req_children - .iter() - .map(|c| Ident::new(&format!("child_{}", c.to_string()), c.span())) - .collect(); - let req_child_name_2 = req_child_name.clone(); - let req_child_name_3 = req_child_name.clone(); - let req_child_name_4 = req_child_name.clone(); - let req_child_type: Vec = self - .req_children - .iter() - .map(|c| Ident::new(&format!("Element_{}", c.to_string()), c.span())) - .collect(); - let req_child_type_2 = req_child_type.clone(); - let construct_children = match self.opt_children { - Some(_) => quote!(children: Vec::new()), - None => TokenStream::new(), - }; - let print_opt_children = if self.opt_children.is_some() { - quote!(for child in &self.children { - child.fmt(f)?; - }) - } else { - TokenStream::new() - }; - let print_children = if req_child_name_2.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) - }) - } else { - quote!(write!(f, "/>")) - } - } else { - quote!( - write!(f, ">")?; - #( - self.#req_child_name_2.fmt(f)?; - )* - #print_opt_children - write!(f, "", #name) - ) - }; - let children = match self.opt_children { - Some(child_constraint) => quote!(pub children: Vec>), - None => TokenStream::new(), - }; - let trait_for = std::iter::repeat(elem_name.clone()); - let trait_name = self.traits.into_iter(); - - quote!( - pub struct #elem_name { - #( pub #attr_name: Option<#attr_type>, )* - #( pub #req_child_name: #req_child_type, )* - #children - } - - impl #elem_name { - pub fn new(#(#req_child_name_3: #req_child_type_2),*) -> Self { - #elem_name { - #( #attr_name_2: None, )* - #( #req_child_name_4, )* - #construct_children - } - } - } - - impl Node for #elem_name {} - impl Element for #elem_name {} - #( - impl #trait_name for #trait_for {} - )* - - 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_3 { - write!(f, " {}={:?}", #attr_name_str, value.to_string())?; - } - )* - #print_children - } - } - ) - } -} - -fn type_spec<'a>() -> Combinator> { - let valid = ident().map(TokenTree::Ident) - | punct(':').map(TokenTree::Punct) - | punct('<').map(TokenTree::Punct) - | punct('>').map(TokenTree::Punct) - | punct('&').map(TokenTree::Punct) - | punct('\'').map(TokenTree::Punct); - valid.repeat(1..).map(|tokens| { - let mut stream = TokenStream::new(); - stream.extend(tokens); - stream - }) -} - -fn declare_attrs<'a>() -> Combinator>> -{ - group().map(|group: Group| { - let attr = ident() - punct(':') + type_spec(); - let input: Vec = group.stream().into_iter().collect(); - let result = attr.repeat(0..).parse(&input); - result.unwrap() - }) -} - -fn declare_children<'a>() -> Combinator>> { - group().map(|group: Group| { - let input: Vec = group.stream().into_iter().collect(); - let children = (ident() - punct(',').opt()).repeat(0..); - let result = children.parse(&input); - result.unwrap() - }) -} - -fn declare_traits<'a>() -> Combinator>> { - group().map(|group: Group| { - let input: Vec = group.stream().into_iter().collect(); - let traits = (type_spec() - punct(',').opt()).repeat(0..); - let result = traits.parse(&input); - result.unwrap() - }) -} - -fn declare<'a>() -> Combinator> { - (ident() + declare_attrs() + declare_children() + declare_traits().opt() + type_spec().opt()) - .map(|((((name, attrs), children), traits), child_type)| { - let mut declare = Declare::new(name); - for (key, value) in attrs { - declare.attrs.insert(key, value); - } - for child in children { - declare.req_children.push(child); - } - declare.opt_children = child_type; - declare.traits = traits.unwrap_or_default(); - declare - }) -} - -fn expand_declare(input: &[TokenTree]) -> pom::Result { - declare().parse(input).map(|decl| decl.into_token_stream()) -} +mod config; +mod declare; +mod html; +mod parser; #[proc_macro] pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input: TokenStream = input.into(); let input: Vec = input.into_iter().collect(); - let result = expand_html(&input); + let result = html::expand_html(&input); match result { - Err(error) => panic!("error: {:?}", error), + Err(error) => panic!(parser::parse_error(&input, &error)), Ok(ts) => ts.into(), } } @@ -454,9 +24,9 @@ pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn declare_element(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input: TokenStream = input.into(); let input: Vec = input.into_iter().collect(); - let result = expand_declare(&input); + let result = declare::expand_declare(&input); match result { - Err(error) => panic!("error: {:?}", error), + Err(error) => panic!(parser::parse_error(&input, &error)), Ok(ts) => ts.into(), } } diff --git a/macros/src/parser.rs b/macros/src/parser.rs new file mode 100644 index 0000000..347368f --- /dev/null +++ b/macros/src/parser.rs @@ -0,0 +1,145 @@ +use pom::combinator::*; +use pom::{Error, Parser}; +use proc_macro2::{Group, Ident, Literal, Punct, TokenStream, TokenTree}; + +pub fn unit<'a, I: 'a, A: Clone>(value: A) -> Combinator> { + comb(move |_, start| Ok((value.clone(), start))) +} + +pub fn punct<'a>(punct: char) -> Combinator> { + comb(move |input: &[TokenTree], start| match input.get(start) { + Some(TokenTree::Punct(p)) if p.as_char() == punct => Ok((p.clone(), start + 1)), + _ => Err(Error::Mismatch { + message: format!("expected {:?}", punct), + position: start, + }), + }) +} + +pub fn ident<'a>() -> Combinator> { + comb(|input: &[TokenTree], start| match input.get(start) { + Some(TokenTree::Ident(i)) => Ok((i.clone(), start + 1)), + _ => Err(Error::Mismatch { + message: "expected identifier".to_string(), + position: start, + }), + }) +} + +pub fn ident_match<'a>(name: String) -> Combinator> { + comb(move |input: &[TokenTree], start| match input.get(start) { + Some(TokenTree::Ident(i)) => { + if *i == name { + Ok(((), start + 1)) + } else { + Err(Error::Mismatch { + message: format!("expected '', found ''", name, i.to_string()), + position: start, + }) + } + } + _ => Err(Error::Mismatch { + message: "expected identifier".to_string(), + position: start, + }), + }) +} + +pub fn literal<'a>() -> Combinator> { + comb(|input: &[TokenTree], start| match input.get(start) { + Some(TokenTree::Literal(l)) => Ok((l.clone(), start + 1)), + _ => Err(Error::Mismatch { + message: "expected literal".to_string(), + position: start, + }), + }) +} + +pub fn group<'a>() -> Combinator> { + comb(|input: &[TokenTree], start| match input.get(start) { + Some(TokenTree::Group(g)) => Ok((g.clone(), start + 1)), + _ => Err(Error::Mismatch { + message: "expected group".to_string(), + position: start, + }), + }) +} + +fn to_stream<'a, I: IntoIterator>(tokens: I) -> TokenStream { + let mut stream = TokenStream::new(); + stream.extend(tokens.into_iter().cloned()); + stream +} + +pub fn type_spec<'a>() -> Combinator> { + let valid = ident().map(TokenTree::Ident) + | punct(':').map(TokenTree::Punct) + | punct('<').map(TokenTree::Punct) + | punct('>').map(TokenTree::Punct) + | punct('&').map(TokenTree::Punct) + | punct('\'').map(TokenTree::Punct); + valid.repeat(1..).collect().map(to_stream) +} + +/// Read a sequence of idents and dashes, and merge them into a single ident +/// with the dashes replaced by underscores. +pub fn html_ident<'a>() -> Combinator> { + let start = ident(); + let next = punct('-') * ident(); + (start * next.repeat(0..)).collect().map(|stream| { + let (span, name) = stream + .into_iter() + .fold((None, String::new()), |(span, name), token| { + ( + match span { + None => Some(token.span()), + // FIXME: Some(span) => Some(span.join(token.span())), + span => span, + }, + match token { + TokenTree::Ident(ident) => name + &ident.to_string(), + TokenTree::Punct(_) => name + "_", + _ => unreachable!(), + }, + ) + }); + Ident::new(&name, span.unwrap()) + }) +} + +fn error_location(input: &[TokenTree], position: usize) -> String { + format!("{:?}", input[position].span()) +} + +pub fn parse_error(input: &[TokenTree], error: &pom::Error) -> String { + match error { + pom::Error::Incomplete => "Incomplete token stream".to_string(), + pom::Error::Mismatch { message, position } => { + format!("{}: {}", error_location(input, *position), message) + } + pom::Error::Conversion { message, position } => { + format!("{}: {}", error_location(input, *position), message) + } + pom::Error::Expect { + message, + position, + inner, + } => format!( + "{}: {}\n{}", + error_location(input, *position), + message, + parse_error(input, &inner) + ), + pom::Error::Custom { + message, + position, + inner, + } => { + let mut out = format!("{}: {}", error_location(input, *position), message); + if let Some(error) = inner { + out += &format!("\n{}", parse_error(input, error)); + } + out + } + } +} diff --git a/typed-html/src/bin/main.rs b/typed-html/src/bin/main.rs index 26f8e81..a5eba2a 100644 --- a/typed-html/src/bin/main.rs +++ b/typed-html/src/bin/main.rs @@ -12,12 +12,12 @@ fn main() { "Hello Kitty!" -

"Hello Kitty!"

+

"Hello Kitty!"

"She is not a cat. She is a human girl."

{the_big_question}

{ (1..4).map(|i| { - html!(

{ TextNode::new(format!("Generated paragraph {}", i)) }

) + html!(

{ TextNode::new(format!("{}. Ceci n'est pas une chatte.", i)) }

) }) } diff --git a/typed-html/src/elements.rs b/typed-html/src/elements.rs index 68efc21..1d9266f 100644 --- a/typed-html/src/elements.rs +++ b/typed-html/src/elements.rs @@ -9,7 +9,12 @@ pub type CssId = String; pub type CssClass = String; pub trait Node: Display {} -pub trait Element: Node {} + +pub trait Element: Node { + fn attributes() -> &'static [&'static str]; + fn required_children() -> &'static [&'static str]; +} + pub trait MetadataContent: Node {} pub trait FlowContent: Node {} pub trait PhrasingContent: Node {} diff --git a/typed-html/src/lib.rs b/typed-html/src/lib.rs index c517512..2da6a8c 100644 --- a/typed-html/src/lib.rs +++ b/typed-html/src/lib.rs @@ -1,5 +1 @@ -pub mod node; - -pub use crate::node::{Element, Node}; - pub mod elements; diff --git a/typed-html/src/node.rs b/typed-html/src/node.rs deleted file mode 100644 index 2994ae8..0000000 --- a/typed-html/src/node.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::collections::HashMap; -use std::fmt::{Debug, Display, Error, Formatter}; - -#[derive(PartialEq, Eq, Clone)] -pub enum Node { - Element(Element), - Text(String), -} - -impl Node { - pub fn text>(t: S) -> Self { - Node::Text(t.into()) - } -} - -impl Display for Node { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - match self { - Node::Element(el) => (el as &Display).fmt(f), - Node::Text(tx) => (tx as &Display).fmt(f), - } - } -} - -impl Debug for Node { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - (self as &Display).fmt(f) - } -} - -impl IntoIterator for Node { - type Item = Node; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - vec![self].into_iter() - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct Element { - name: String, - attributes: HashMap, - children: Vec, -} - -impl Element { - pub fn new>(name: S) -> Self { - Element { - name: name.into(), - attributes: HashMap::new(), - children: Vec::new(), - } - } - - pub fn set_attr, S2: Into>(&mut self, attr: S1, value: S2) { - self.attributes.insert(attr.into(), value.into()); - } - - pub fn append_child(&mut self, child: Node) { - self.children.push(child) - } -} - -impl Display for Element { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - write!(f, "<{}", self.name)?; - for (attr, value) in &self.attributes { - write!(f, " {}={:?}", attr, value)?; - } - if self.children.is_empty() { - write!(f, "/>") - } else { - write!(f, ">")?; - for child in &self.children { - (child as &Display).fmt(f)?; - } - write!(f, "", self.name) - } - } -} - -impl Debug for Element { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - (self as &Display).fmt(f) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn construct() { - let el1 = Element::new("html"); - let el2 = Element::new("html".to_string()); - assert_eq!(el1, el2); - } - - #[test] - fn to_string() { - let mut doc = Element::new("html"); - doc.set_attr("version", "1.0"); - let mut head = Element::new("head"); - let mut style = Element::new("style"); - style.set_attr("src", "lol.css"); - let mut title = Element::new("title"); - title.append_child(Node::Text("Hello kitty!".to_string())); - head.append_child(Node::Element(title)); - head.append_child(Node::Element(style)); - doc.append_child(Node::Element(head)); - assert_eq!( - "Hello kitty!