use proc_macro2::Span;
use syn::{
Expr, Ident, LitBool, LitStr, Pat, Path, Result, Token, braced,
parse::{Parse, ParseStream},
token::Brace,
};
use crate::{
attributes::{AttrName, AttrValue, Attribute, ForExpr, InterpolatedSegment, RegularAttribute},
nodes::{Component, Node, NodeCollection, Tag},
utils::{extract_ident_from_path, is_path_pascal_case, path_to_string},
};
impl Parse for Node {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Token![<]) && input.peek2(Token![!]) {
let _: Token![<] = input.parse()?;
let _: Token![!] = input.parse()?;
let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
let ident: Ident = input.parse()?;
if ident.to_string().to_lowercase() == "doctype" {
let html_ident: Ident = input.parse()?;
if html_ident.to_string().to_lowercase() == "html" {
let _: Token![>] = input.parse()?;
return Ok(Node::DocType);
}
}
}
}
if input.peek(Token![<]) {
let _: Token![<] = input.parse()?;
let tag_name: Path = input.parse()?;
let mut attributes = Vec::new();
let mut end_of_regular_tag = input.peek(Token![>]);
let mut end_of_self_closing_tag = input.peek(Token![/]) && input.peek2(Token![>]);
let mut end_of_tag = end_of_regular_tag || end_of_self_closing_tag;
while !end_of_tag {
let attribute: Attribute = input.parse()?;
attributes.push(attribute);
end_of_regular_tag = input.peek(Token![>]);
end_of_self_closing_tag = input.peek(Token![/]) && input.peek2(Token![>]);
end_of_tag = end_of_regular_tag || end_of_self_closing_tag;
}
let is_component = is_path_pascal_case(&tag_name);
if input.peek(Token![/]) && input.peek2(Token![>]) {
let _: Token![/] = input.parse()?;
let _: Token![>] = input.parse()?;
if is_component {
return Ok(Node::Component(Component {
name: tag_name,
props: attributes,
children: Vec::new(),
}));
}
return Ok(Node::Element(Tag {
tag_name: extract_ident_from_path(&tag_name),
attributes,
children: Vec::new(),
self_closing: true,
}));
}
let _: Token![>] = input.parse()?;
let mut children: Vec<Node> = Vec::new();
while input.peek(Token![<]) && input.peek2(Ident)
|| input.peek(LitStr)
|| input.peek(Brace)
{
let child: Node = input.parse()?;
children.push(child);
}
let element = Tag {
tag_name: extract_ident_from_path(&tag_name),
attributes: attributes.clone(),
children: children.clone(),
self_closing: false,
};
if input.peek(Token![<]) && input.peek2(Token![/]) && input.peek3(Ident) {
let _: Token![<] = input.parse()?;
let _: Token![/] = input.parse()?;
let closing_tag_name: Path = input.parse()?;
if closing_tag_name != tag_name {
Err(input.error(format!(
"Expected closing tag {}, found {}",
path_to_string(&tag_name),
path_to_string(&closing_tag_name)
)))
} else {
let _: Token![>] = input.parse()?;
if is_component {
return Ok(Node::Component(Component {
name: tag_name,
props: attributes,
children,
}));
}
Ok(Node::Element(element))
}
} else {
Ok(Node::Text(input.parse()?))
}
} else if input.peek(LitStr) {
let content: LitStr = input.parse()?;
Ok(Node::Text(content))
} else if input.peek(Brace) {
let content_brackets;
braced!(content_brackets in input);
if content_brackets.peek(Brace) {
let inner_brackets;
braced!(inner_brackets in content_brackets);
let content_expr: Expr = inner_brackets.parse()?;
Ok(Node::UnescapedExpression(content_expr))
} else {
let content_expr: Expr = content_brackets.parse()?;
Ok(Node::Expression(content_expr))
}
} else {
Err(input.error("Expected a node"))
}
}
}
impl Parse for Attribute {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Token![:]) {
if input.peek2(Token![for]) {
let _: Token![:] = input.parse()?;
let _: Token![for] = input.parse()?;
let _: Token![=] = input.parse()?;
let content;
braced!(content in input);
return Ok(Attribute::For(content.parse()?));
} else if input.peek2(Token![if]) {
let _: Token![:] = input.parse()?;
let _: Token![if] = input.parse()?;
let _: Token![=] = input.parse()?;
let content;
braced!(content in input);
return Ok(Attribute::If(content.parse()?));
}
}
let name: AttrName = input.parse()?;
let value = if input.peek(Token![=]) {
let _: Token![=] = input.parse()?;
Some(input.parse()?)
} else {
None
};
Ok(Attribute::RegularAttribute(RegularAttribute {
name,
value,
}))
}
}
macro_rules! match_keyword {
($input:expr, $keyword:ident, $name:expr, $saw_word:expr) => {
if $input.peek(Token![$keyword]) {
if $saw_word {
break;
}
let _: Token![$keyword] = $input.parse()?;
$name.push_str(stringify!($keyword));
$saw_word = true;
continue;
}
};
}
macro_rules! match_keywords {
($input:expr, $name:expr, $saw_word:expr, [$($keyword:ident),*]) => {
$(
match_keyword!($input, $keyword, $name, $saw_word);
)*
};
}
impl Parse for AttrName {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Brace) {
let content_brackets;
braced!(content_brackets in input);
let content_expr: Expr = content_brackets.parse()?;
return Ok(AttrName::Expression(content_expr));
}
let span = input.span();
let mut name = String::new();
let mut saw_word = false;
loop {
let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
if saw_word {
break;
}
let ident: Ident = input.parse()?;
name.push_str(&ident.to_string());
saw_word = true;
} else if lookahead.peek(Token![-]) {
let _: Token![-] = input.parse()?;
name.push('-');
saw_word = false;
} else if lookahead.peek(Token![:]) {
let _: Token![:] = input.parse()?;
name.push(':');
saw_word = false;
} else if lookahead.peek(LitBool) {
let token: LitBool = input.parse()?;
name.push_str(&token.value.to_string());
saw_word = true;
} else {
match_keywords!(input, name, saw_word, [
as,
break,
const,
continue,
crate,
else,
enum,
extern,
fn,
for,
if,
impl,
in,
let,
loop,
match,
mod,
move,
mut,
pub,
ref,
return,
self,
Self,
static,
struct,
super,
trait,
type,
unsafe,
use,
where,
while,
async,
await,
dyn,
abstract,
become,
box,
do,
final,
macro,
override,
priv,
typeof,
unsized,
virtual,
yield,
try,
union
]);
break;
}
}
if !name.is_empty() {
Ok(AttrName::Literal(LitStr::new(&name, span)))
} else {
Err(input.error("Expected a valid attribute name"))
}
}
}
impl Parse for AttrValue {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Brace) {
let content_brackets;
braced!(content_brackets in input);
let content_expr: Expr = content_brackets.parse()?;
Ok(AttrValue::Expression(content_expr))
} else {
let lit_str: LitStr = input.parse()?;
if lit_str.value().contains('{') && lit_str.value().contains('}') {
let segments = parse_interpolated_string(&lit_str.value())?;
Ok(AttrValue::Interpolated(segments))
} else {
Ok(AttrValue::Literal(lit_str))
}
}
}
}
impl Parse for ForExpr {
fn parse(input: ParseStream) -> Result<Self> {
let pat: Pat = Pat::parse_single(input)?;
let _: Token![in] = input.parse()?;
let collection: Expr = input.parse()?;
Ok(ForExpr { pat, collection })
}
}
impl Parse for NodeCollection {
fn parse(input: ParseStream) -> Result<Self> {
let mut nodes = Vec::new();
while !input.is_empty() {
nodes.push(input.parse()?);
}
Ok(NodeCollection::Nodes(nodes))
}
}
fn parse_interpolated_string(s: &str) -> Result<Vec<InterpolatedSegment>> {
let mut segments = Vec::new();
let mut start = 0;
while let Some(open) = s[start..].find('{') {
if start != open {
segments.push(InterpolatedSegment::Str(LitStr::new(
&s[start..start + open],
Span::call_site(),
)));
}
let close = s[start + open..].find('}').ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"Unmatched opening brace in interpolated string",
)
})?;
let expr_str = &s[start + open + 1..start + open + close];
let expr: Expr = syn::parse_str(expr_str)?;
segments.push(InterpolatedSegment::Expr(expr));
start = start + open + close + 1;
}
if start != s.len() {
segments.push(InterpolatedSegment::Str(LitStr::new(
&s[start..],
Span::call_site(),
)));
}
Ok(segments)
}