Cleanup, reorg, data attributes.

This commit is contained in:
Bodil Stokke 2018-10-27 16:59:30 +01:00
parent 4f8b4e2b20
commit dee331c5eb
9 changed files with 623 additions and 564 deletions

18
macros/src/config.rs Normal file
View File

@ -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<Ident, TokenStream> {
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
}

261
macros/src/declare.rs Normal file
View File

@ -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<Ident, TokenStream>,
req_children: Vec<Ident>,
opt_children: Option<TokenStream>,
traits: Vec<TokenStream>,
}
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<Item = Ident> + '_ {
self.attrs
.keys()
.map(|k| Ident::new(&format!("attr_{}", k.to_string()), k.span()))
}
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 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<Box<#child_constraint>>),
None => TokenStream::new(),
};
quote!(
pub struct #elem_name {
#( pub #attr_name: Option<#attr_type>, )*
pub data_attributes: std::collections::BTreeMap<String, String>,
#( 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<impl Parser<'a, TokenTree, Output = Vec<(Ident, TokenStream)>>>
{
group().map(|group: Group| {
let attr = ident() - punct(':') + type_spec();
let input: Vec<TokenTree> = group.stream().into_iter().collect();
let result = attr.repeat(0..).parse(&input);
result.unwrap()
})
}
fn declare_children<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<Ident>>> {
group().map(|group: Group| {
let input: Vec<TokenTree> = group.stream().into_iter().collect();
let children = (ident() - punct(',').opt()).repeat(0..);
let result = children.parse(&input);
result.unwrap()
})
}
fn declare_traits<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<TokenStream>>> {
group().map(|group: Group| {
let input: Vec<TokenTree> = group.stream().into_iter().collect();
let traits = (type_spec() - punct(',').opt()).repeat(0..);
let result = traits.parse(&input);
result.unwrap()
})
}
fn declare<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Declare>> {
(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<TokenStream> {
declare().parse(input).map(|decl| decl.into_token_stream())
}

181
macros/src/html.rs Normal file
View File

@ -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<Ident, TokenTree>,
children: Vec<Node>,
}
fn extract_data_attrs(attrs: &mut BTreeMap<Ident, TokenTree>) -> BTreeMap<String, TokenTree> {
let mut data = BTreeMap::new();
let keys: Vec<Ident> = 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<TokenTree> = 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<impl Parser<'a, TokenTree, Output = Element>> {
(punct('<') * html_ident()).map(Element::new)
}
fn attr_value<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = TokenTree>> {
literal().map(TokenTree::Literal) | ident().map(TokenTree::Ident)
}
fn attr<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = (Ident, TokenTree)>> {
html_ident() + (punct('=') * attr_value())
}
fn element_with_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
(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<impl Parser<'a, TokenTree, Output = Element>> {
element_with_attrs() - punct('/') - punct('>')
}
fn element_open<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
element_with_attrs() - punct('>')
}
fn element_close<'a>(name: &str) -> Combinator<impl Parser<'a, TokenTree, Output = ()>> {
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<impl Parser<'a, TokenTree, Output = Element>> {
(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<TokenStream> {
comb(node).parse(input).map(|el| el.into_token_stream())
}

View File

@ -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<Ident, TokenStream> {
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<Ident, TokenTree>,
children: Vec<Node>,
}
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<TokenTree> = 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<impl Parser<'a, I, Output = A>> {
comb(move |_, start| Ok((value.clone(), start)))
}
fn punct<'a>(punct: char) -> Combinator<impl Parser<'a, TokenTree, Output = Punct>> {
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<impl Parser<'a, TokenTree, Output = Ident>> {
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<impl Parser<'a, TokenTree, Output = ()>> {
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<impl Parser<'a, TokenTree, Output = Literal>> {
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<impl Parser<'a, TokenTree, Output = Group>> {
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<impl Parser<'a, TokenTree, Output = Element>> {
(punct('<') * ident()).map(Element::new)
}
fn attr_value<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = TokenTree>> {
literal().map(TokenTree::Literal) | ident().map(TokenTree::Ident)
}
fn attr<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = (Ident, TokenTree)>> {
ident() + (punct('=') * attr_value())
}
fn element_with_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
(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<impl Parser<'a, TokenTree, Output = Element>> {
element_with_attrs() - punct('/') - punct('>')
}
fn element_open<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
element_with_attrs() - punct('>')
}
fn element_close<'a>(name: &str) -> Combinator<impl Parser<'a, TokenTree, Output = ()>> {
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<impl Parser<'a, TokenTree, Output = Element>> {
(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<TokenStream> {
comb(node).parse(input).map(|el| el.into_token_stream())
}
struct Declare {
name: Ident,
attrs: HashMap<Ident, TokenStream>,
req_children: Vec<Ident>,
opt_children: Option<TokenStream>,
traits: Vec<TokenStream>,
}
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<Ident> = 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<Ident> = 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<Ident> = 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<Box<#child_constraint>>),
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<impl Parser<'a, TokenTree, Output = TokenStream>> {
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<impl Parser<'a, TokenTree, Output = Vec<(Ident, TokenStream)>>>
{
group().map(|group: Group| {
let attr = ident() - punct(':') + type_spec();
let input: Vec<TokenTree> = group.stream().into_iter().collect();
let result = attr.repeat(0..).parse(&input);
result.unwrap()
})
}
fn declare_children<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<Ident>>> {
group().map(|group: Group| {
let input: Vec<TokenTree> = group.stream().into_iter().collect();
let children = (ident() - punct(',').opt()).repeat(0..);
let result = children.parse(&input);
result.unwrap()
})
}
fn declare_traits<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<TokenStream>>> {
group().map(|group: Group| {
let input: Vec<TokenTree> = group.stream().into_iter().collect();
let traits = (type_spec() - punct(',').opt()).repeat(0..);
let result = traits.parse(&input);
result.unwrap()
})
}
fn declare<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Declare>> {
(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<TokenStream> {
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<TokenTree> = 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<TokenTree> = 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(),
}
}

145
macros/src/parser.rs Normal file
View File

@ -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<impl Parser<'a, I, Output = A>> {
comb(move |_, start| Ok((value.clone(), start)))
}
pub fn punct<'a>(punct: char) -> Combinator<impl Parser<'a, TokenTree, Output = Punct>> {
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<impl Parser<'a, TokenTree, Output = Ident>> {
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<impl Parser<'a, TokenTree, Output = ()>> {
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<impl Parser<'a, TokenTree, Output = Literal>> {
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<impl Parser<'a, TokenTree, Output = Group>> {
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<Item = &'a TokenTree>>(tokens: I) -> TokenStream {
let mut stream = TokenStream::new();
stream.extend(tokens.into_iter().cloned());
stream
}
pub fn type_spec<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = TokenStream>> {
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<impl Parser<'a, TokenTree, Output = Ident>> {
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
}
}
}

View File

@ -12,12 +12,12 @@ fn main() {
<title>"Hello Kitty!"</title>
</head>
<body>
<h1>"Hello Kitty!"</h1>
<h1 data-lol="foo">"Hello Kitty!"</h1>
<p class=splain_class>"She is not a cat. She is a human girl."</p>
<p class="mind-blown">{the_big_question}</p>
{
(1..4).map(|i| {
html!(<p>{ TextNode::new(format!("Generated paragraph {}", i)) }</p>)
html!(<p>{ TextNode::new(format!("{}. Ceci n'est pas une chatte.", i)) }</p>)
})
}
</body>

View File

@ -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 {}

View File

@ -1,5 +1 @@
pub mod node;
pub use crate::node::{Element, Node};
pub mod elements;

View File

@ -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<S: Into<String>>(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<Node>;
fn into_iter(self) -> Self::IntoIter {
vec![self].into_iter()
}
}
#[derive(PartialEq, Eq, Clone)]
pub struct Element {
name: String,
attributes: HashMap<String, String>,
children: Vec<Node>,
}
impl Element {
pub fn new<S: Into<String>>(name: S) -> Self {
Element {
name: name.into(),
attributes: HashMap::new(),
children: Vec::new(),
}
}
pub fn set_attr<S1: Into<String>, S2: Into<String>>(&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!(
"<html version=\"1.0\"><head><title>Hello kitty!</title><style src=\"lol.css\"/></head></html>",
&doc.to_string()
);
}
}