use syn::{Expr, Ident, Path, spanned::Spanned};
pub trait AstNode: std::fmt::Debug + Clone {
fn span(&self) -> proc_macro2::Span;
#[allow(unused)]
fn accept<V: AstVisitor>(&self, visitor: &mut V) -> syn::Result<()>;
fn validate(&self) -> syn::Result<()> {
Ok(()) }
}
#[allow(unused)]
pub trait AstVisitor {
fn visit_element(&mut self, element: &Element) -> syn::Result<()>;
fn visit_prop(&mut self, prop: &Prop) -> syn::Result<()>;
fn visit_conditional(&mut self, conditional: &ConditionalNode) -> syn::Result<()>;
fn visit_expression(&mut self, expr: &Expr) -> syn::Result<()>;
fn visit_comment(&mut self, comment: &CommentNode) -> syn::Result<()>;
fn visit_for_loop(&mut self, for_loop: &ForLoopNode) -> syn::Result<()>;
fn visit_fragment(&mut self, fragment: &FragmentNode) -> syn::Result<()>;
}
#[derive(Debug, Clone)]
pub struct Prop {
pub key: Ident,
pub value: Expr,
}
impl AstNode for Prop {
fn span(&self) -> proc_macro2::Span {
self.key.span()
}
fn accept<V: AstVisitor>(&self, visitor: &mut V) -> syn::Result<()> {
visitor.visit_prop(self)
}
fn validate(&self) -> syn::Result<()> {
if self.key.to_string().is_empty() {
return Err(syn::Error::new(self.span(), "Property key cannot be empty"));
}
Ok(())
}
}
pub struct PropFactory;
impl PropFactory {
pub fn create(key: Ident, value: Expr, _span: proc_macro2::Span) -> syn::Result<Prop> {
let prop = Prop { key, value };
prop.validate()?;
Ok(prop)
}
pub fn create_shorthand(key: Ident, _span: proc_macro2::Span) -> syn::Result<Prop> {
use syn::{Expr, Lit, LitBool};
let value = Expr::Lit(syn::ExprLit {
attrs: vec![],
lit: Lit::Bool(LitBool {
value: true,
span: key.span(),
}),
});
let prop = Prop { key, value };
prop.validate()?;
Ok(prop)
}
}
#[derive(Debug, Clone)]
pub enum Node {
Element(Element),
Expression(Expr),
Conditional(ConditionalNode),
Comment(CommentNode),
ForLoop(ForLoopNode),
Fragment(FragmentNode),
}
impl AstNode for Node {
fn span(&self) -> proc_macro2::Span {
match self {
Node::Element(e) => e.span(),
Node::Expression(e) => e.span(),
Node::Conditional(c) => c.span(),
Node::Comment(c) => c.span(),
Node::ForLoop(f) => f.span(),
Node::Fragment(f) => f.span(),
}
}
fn accept<V: AstVisitor>(&self, visitor: &mut V) -> syn::Result<()> {
match self {
Node::Element(e) => e.accept(visitor),
Node::Expression(e) => visitor.visit_expression(e),
Node::Conditional(c) => c.accept(visitor),
Node::Comment(c) => c.accept(visitor),
Node::ForLoop(f) => f.accept(visitor),
Node::Fragment(f) => f.accept(visitor),
}
}
fn validate(&self) -> syn::Result<()> {
match self {
Node::Element(e) => e.validate(),
Node::Expression(_) => Ok(()),
Node::Conditional(c) => c.validate(),
Node::Comment(c) => c.validate(),
Node::ForLoop(f) => f.validate(),
Node::Fragment(f) => f.validate(),
}
}
}
#[derive(Debug, Clone)]
pub struct Element {
pub name: Path,
pub attributes: Vec<Prop>,
pub children: Vec<Node>,
#[allow(dead_code)]
pub span: proc_macro2::Span,
}
impl AstNode for Element {
fn span(&self) -> proc_macro2::Span {
self.name.span()
}
fn accept<V: AstVisitor>(&self, visitor: &mut V) -> syn::Result<()> {
visitor.visit_element(self)?;
for attr in &self.attributes {
attr.accept(visitor)?;
}
for child in &self.children {
child.accept(visitor)?;
}
Ok(())
}
fn validate(&self) -> syn::Result<()> {
if self.name.segments.is_empty() {
return Err(syn::Error::new(self.span(), "Element name cannot be empty"));
}
for attr in &self.attributes {
attr.validate()?;
}
for child in &self.children {
child.validate()?;
}
Ok(())
}
}
pub struct ElementFactory;
impl ElementFactory {
pub fn create(
name: Path,
attributes: Vec<Prop>,
children: Vec<Node>,
span: proc_macro2::Span,
) -> syn::Result<Element> {
let element = Element {
name,
attributes,
children,
span,
};
element.validate()?;
Ok(element)
}
}
#[derive(Debug, Clone)]
pub struct CommentNode {
#[allow(dead_code)]
pub content: String,
}
#[derive(Debug, Clone)]
pub struct FragmentNode {
pub children: Vec<Node>,
pub span: proc_macro2::Span,
}
impl AstNode for CommentNode {
fn span(&self) -> proc_macro2::Span {
proc_macro2::Span::call_site()
}
fn accept<V: AstVisitor>(&self, visitor: &mut V) -> syn::Result<()> {
visitor.visit_comment(self)
}
}
impl AstNode for FragmentNode {
fn span(&self) -> proc_macro2::Span {
self.span
}
fn accept<V: AstVisitor>(&self, visitor: &mut V) -> syn::Result<()> {
visitor.visit_fragment(self)?;
for child in &self.children {
child.accept(visitor)?;
}
Ok(())
}
fn validate(&self) -> syn::Result<()> {
for child in &self.children {
child.validate()?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum ConditionalNode {
If {
condition: Expr,
then_branch: Box<Node>,
else_ifs: Vec<ElseIfBranch>,
else_branch: Option<Box<Node>>,
},
IfLet {
pattern: syn::Pat,
expr: Expr,
then_branch: Box<Node>,
else_branch: Option<Box<Node>>,
},
Match {
expr: Expr,
arms: Vec<MatchArm>,
},
LogicalAnd {
condition: Expr,
then_branch: Box<Node>,
},
}
impl AstNode for ConditionalNode {
fn span(&self) -> proc_macro2::Span {
match self {
ConditionalNode::If { condition, .. } => condition.span(),
ConditionalNode::IfLet { expr, .. } => expr.span(),
ConditionalNode::Match { expr, .. } => expr.span(),
ConditionalNode::LogicalAnd { condition, .. } => condition.span(),
}
}
fn accept<V: AstVisitor>(&self, visitor: &mut V) -> syn::Result<()> {
visitor.visit_conditional(self)?;
match self {
ConditionalNode::If {
then_branch,
else_ifs,
else_branch,
..
} => {
then_branch.accept(visitor)?;
for else_if in else_ifs {
else_if.then_branch.accept(visitor)?;
}
if let Some(else_node) = else_branch {
else_node.accept(visitor)?;
}
}
ConditionalNode::IfLet {
then_branch,
else_branch,
..
} => {
then_branch.accept(visitor)?;
if let Some(else_node) = else_branch {
else_node.accept(visitor)?;
}
}
ConditionalNode::Match { arms, .. } => {
for arm in arms {
arm.body.accept(visitor)?;
}
}
ConditionalNode::LogicalAnd { then_branch, .. } => {
then_branch.accept(visitor)?;
}
}
Ok(())
}
fn validate(&self) -> syn::Result<()> {
match self {
ConditionalNode::If {
then_branch,
else_ifs,
else_branch,
..
} => {
then_branch.validate()?;
for else_if in else_ifs {
else_if.then_branch.validate()?;
}
if let Some(else_node) = else_branch {
else_node.validate()?;
}
}
ConditionalNode::IfLet {
then_branch,
else_branch,
..
} => {
then_branch.validate()?;
if let Some(else_node) = else_branch {
else_node.validate()?;
}
}
ConditionalNode::Match { arms, .. } => {
if arms.is_empty() {
return Err(syn::Error::new(
self.span(),
"Match expression must have at least one arm",
));
}
for arm in arms {
arm.body.validate()?;
}
}
ConditionalNode::LogicalAnd { then_branch, .. } => {
then_branch.validate()?;
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ElseIfBranch {
pub condition: Expr,
pub then_branch: Box<Node>,
}
#[derive(Debug, Clone)]
pub struct MatchArm {
pub pattern: syn::Pat,
pub guard: Option<Expr>,
pub body: Box<Node>,
}
#[derive(Debug, Clone)]
pub struct ForLoopNode {
pub pattern: syn::Pat,
pub iterable: Expr,
pub preparation_stmts: Vec<syn::Stmt>,
pub body: Box<Node>,
pub span: proc_macro2::Span,
}
impl AstNode for ForLoopNode {
fn span(&self) -> proc_macro2::Span {
self.span
}
fn accept<V: AstVisitor>(&self, visitor: &mut V) -> syn::Result<()> {
visitor.visit_for_loop(self)?;
self.body.accept(visitor)
}
fn validate(&self) -> syn::Result<()> {
match &self.pattern {
syn::Pat::Ident(_) => {} syn::Pat::Tuple(_) => {} syn::Pat::Struct(_) => {} _ => {
return Err(syn::Error::new(
self.pattern.span(),
"For-loop pattern must be a simple identifier or destructuring pattern",
));
}
}
self.body.validate()
}
}
pub struct ForLoopFactory;
impl ForLoopFactory {
pub fn create(
pattern: syn::Pat,
iterable: Expr,
preparation_stmts: Vec<syn::Stmt>,
body: Box<Node>,
span: proc_macro2::Span,
) -> syn::Result<ForLoopNode> {
let for_loop = ForLoopNode {
pattern,
iterable,
preparation_stmts,
body,
span,
};
for_loop.validate()?;
Ok(for_loop)
}
}
pub struct FragmentFactory;
impl FragmentFactory {
pub fn create(children: Vec<Node>, span: proc_macro2::Span) -> syn::Result<FragmentNode> {
let fragment = FragmentNode { children, span };
fragment.validate()?;
Ok(fragment)
}
}
pub struct ConditionalFactory;
impl ConditionalFactory {
pub fn create_if(
condition: Expr,
then_branch: Box<Node>,
else_ifs: Vec<ElseIfBranch>,
else_branch: Option<Box<Node>>,
_span: proc_macro2::Span,
) -> syn::Result<ConditionalNode> {
let conditional = ConditionalNode::If {
condition,
then_branch,
else_ifs,
else_branch,
};
conditional.validate()?;
Ok(conditional)
}
pub fn create_match(
expr: Expr,
arms: Vec<MatchArm>,
_span: proc_macro2::Span,
) -> syn::Result<ConditionalNode> {
let conditional = ConditionalNode::Match { expr, arms };
conditional.validate()?;
Ok(conditional)
}
pub fn create_if_let(
pattern: syn::Pat,
expr: Expr,
then_branch: Box<Node>,
else_branch: Option<Box<Node>>,
_span: proc_macro2::Span,
) -> syn::Result<ConditionalNode> {
let conditional = ConditionalNode::IfLet {
pattern,
expr,
then_branch,
else_branch,
};
conditional.validate()?;
Ok(conditional)
}
pub fn create_logical_and(
condition: Expr,
then_branch: Box<Node>,
_span: proc_macro2::Span,
) -> syn::Result<ConditionalNode> {
let conditional = ConditionalNode::LogicalAnd {
condition,
then_branch,
};
conditional.validate()?;
Ok(conditional)
}
}