mod tokenizer;
use proc_macro2::{
TokenStream,
TokenTree,
Ident,
};
use crate::tokenizer::{
HtmlToken,
HtmlOpenToken,
TokenizeError,
parse_html_token,
};
#[derive(Debug)]
pub enum SnaxAttribute {
Simple {
name: Ident,
value: TokenTree,
},
}
impl PartialEq for SnaxAttribute {
fn eq(&self, other: &Self) -> bool {
use SnaxAttribute::*;
match (self, other) {
(
Simple { name, value },
Simple { name: other_name, value: other_value },
) => {
name == other_name
&& value.to_string() == other_value.to_string()
},
}
}
}
#[derive(Debug)]
pub enum SnaxItem {
Tag(SnaxTag),
SelfClosingTag(SnaxSelfClosingTag),
Fragment(SnaxFragment),
Content(TokenTree),
}
impl PartialEq for SnaxItem {
fn eq(&self, other: &Self) -> bool {
use SnaxItem::*;
match (self, other) {
(Tag(this), Tag(other)) => this == other,
(SelfClosingTag(this), SelfClosingTag(other)) => this == other,
(Fragment(this), Fragment(other)) => this == other,
(Content(this), Content(other)) => {
this.to_string() == other.to_string()
},
_ => false,
}
}
}
#[derive(Debug, PartialEq)]
pub struct SnaxTag {
pub name: Ident,
pub attributes: Vec<SnaxAttribute>,
pub children: Vec<SnaxItem>,
}
#[derive(Debug, PartialEq)]
pub struct SnaxSelfClosingTag {
pub name: Ident,
pub attributes: Vec<SnaxAttribute>,
}
#[derive(Debug, PartialEq)]
pub struct SnaxFragment {
pub children: Vec<SnaxItem>,
}
#[derive(Debug)]
pub enum ParseError {
UnexpectedEnd,
UnexpectedItem(HtmlToken),
UnexpectedToken(TokenTree),
}
impl From<TokenizeError> for ParseError {
fn from(error: TokenizeError) -> ParseError {
match error {
TokenizeError::UnexpectedEnd => ParseError::UnexpectedEnd,
TokenizeError::UnexpectedToken(token) => ParseError::UnexpectedToken(token),
}
}
}
macro_rules! expect_end {
($iterator: expr) => {
match $iterator.next() {
None => {},
Some(unexpected) => return Err(ParseError::UnexpectedToken(unexpected)),
}
};
}
#[derive(Debug)]
enum OpenToken {
Tag(HtmlOpenToken),
Fragment,
}
pub fn parse(input_stream: TokenStream) -> Result<SnaxItem, ParseError> {
let mut input = input_stream.into_iter();
let mut tag_stack: Vec<(OpenToken, Vec<SnaxItem>)> = Vec::new();
loop {
match parse_html_token(&mut input)? {
HtmlToken::OpenTag(opening_tag) => {
tag_stack.push((OpenToken::Tag(opening_tag), Vec::new()));
},
HtmlToken::CloseTag(closing_tag) => {
let (open_token, children) = tag_stack.pop()
.ok_or_else(|| ParseError::UnexpectedItem(HtmlToken::CloseTag(closing_tag.clone())))?;
let opening_tag = match open_token {
OpenToken::Tag(tag) => tag,
OpenToken::Fragment => return Err(ParseError::UnexpectedItem(HtmlToken::CloseTag(closing_tag.clone()))),
};
assert_eq!(opening_tag.name, closing_tag.name);
let tag = SnaxTag {
name: opening_tag.name,
attributes: opening_tag.attributes,
children,
};
match tag_stack.last_mut() {
None => {
expect_end!(input);
return Ok(SnaxItem::Tag(tag));
},
Some((_, parent_children)) => {
parent_children.push(SnaxItem::Tag(tag));
},
}
},
HtmlToken::OpenFragment => {
tag_stack.push((OpenToken::Fragment, Vec::new()));
},
HtmlToken::CloseFragment => {
let (open_token, children) = tag_stack.pop()
.ok_or_else(|| ParseError::UnexpectedItem(HtmlToken::CloseFragment))?;
match open_token {
OpenToken::Fragment => {},
OpenToken::Tag(_) => return Err(ParseError::UnexpectedItem(HtmlToken::CloseFragment)),
}
let fragment = SnaxFragment {
children,
};
match tag_stack.last_mut() {
None => {
expect_end!(input);
return Ok(SnaxItem::Fragment(fragment));
},
Some((_, parent_children)) => {
parent_children.push(SnaxItem::Fragment(fragment));
},
}
},
HtmlToken::SelfClosingTag(self_closing_tag) => {
let tag = SnaxSelfClosingTag {
name: self_closing_tag.name,
attributes: self_closing_tag.attributes,
};
match tag_stack.last_mut() {
None => {
expect_end!(input);
return Ok(SnaxItem::SelfClosingTag(tag));
},
Some((_, parent_children)) => {
parent_children.push(SnaxItem::SelfClosingTag(tag));
},
}
},
HtmlToken::Textish(textish) => {
match tag_stack.last_mut() {
None => {
expect_end!(input);
return Ok(SnaxItem::Content(textish.content));
},
Some((_, parent_children)) => {
parent_children.push(SnaxItem::Content(textish.content));
},
}
},
}
}
}