217 lines
5.1 KiB
Rust
217 lines
5.1 KiB
Rust
use syn::braced;
|
|
use syn::parse::discouraged::Speculative;
|
|
use syn::{
|
|
Expr, Ident, LitStr, Pat, Path, Result, Token,
|
|
parse::{Parse, ParseStream},
|
|
token::Brace,
|
|
};
|
|
|
|
pub struct RsxRoot {
|
|
pub nodes: Vec<RsxNode>,
|
|
}
|
|
|
|
impl Parse for RsxRoot {
|
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
let mut nodes = Vec::new();
|
|
while !input.is_empty() {
|
|
nodes.push(input.parse()?);
|
|
}
|
|
Ok(Self { nodes })
|
|
}
|
|
}
|
|
|
|
/// A single prop: `ident: expr`.
|
|
pub struct RsxProp {
|
|
pub name: Ident,
|
|
pub value: Expr,
|
|
}
|
|
|
|
pub enum RsxNode {
|
|
Text(LitStr),
|
|
Expr(Expr),
|
|
Component {
|
|
path: Path,
|
|
props: Vec<RsxProp>,
|
|
children: Vec<RsxNode>,
|
|
},
|
|
Mount(Ident),
|
|
If {
|
|
deps: Vec<Dep>,
|
|
cond: Expr,
|
|
children: Vec<RsxNode>,
|
|
},
|
|
For {
|
|
deps: Vec<Dep>,
|
|
pat: Pat,
|
|
expr: Expr,
|
|
children: Vec<RsxNode>,
|
|
},
|
|
}
|
|
|
|
pub struct Dep {
|
|
pub ident: Ident,
|
|
pub pat: Option<Pat>,
|
|
}
|
|
|
|
fn parse_deps(input: ParseStream) -> Result<Vec<Dep>> {
|
|
let mut deps = Vec::new();
|
|
|
|
if input.peek(Token![%]) {
|
|
input.parse::<Token![%]>()?;
|
|
loop {
|
|
let ident: Ident = input.parse()?;
|
|
let pat = if input.peek(Token![as]) {
|
|
input.parse::<Token![as]>()?;
|
|
// Patterns in syn 2.0 are parsed via `Pat::parse_multi`
|
|
// instead of implementing the `Parse` trait directly.
|
|
Some(Pat::parse_multi(input)?)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
deps.push(Dep { ident, pat });
|
|
|
|
if !input.peek(Token![,]) {
|
|
break;
|
|
}
|
|
input.parse::<Token![,]>()?;
|
|
}
|
|
}
|
|
|
|
Ok(deps)
|
|
}
|
|
|
|
impl Parse for RsxNode {
|
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
// @{ $expr }
|
|
if input.peek(Token![@]) {
|
|
input.parse::<Token![@]>()?;
|
|
|
|
let content;
|
|
braced!(content in input);
|
|
|
|
let expr: Expr = content.parse()?;
|
|
return Ok(RsxNode::Expr(expr));
|
|
}
|
|
|
|
// !$ident
|
|
if input.peek(Token![!]) {
|
|
input.parse::<Token![!]>()?;
|
|
let mount: Ident = input.parse()?;
|
|
return Ok(RsxNode::Mount(mount));
|
|
}
|
|
|
|
let deps = parse_deps(input)?;
|
|
|
|
// %$dep if $expr { $rsx }
|
|
if input.peek(Token![if]) {
|
|
input.parse::<Token![if]>()?;
|
|
let cond: Expr = input.parse()?;
|
|
let content;
|
|
braced!(content in input);
|
|
let children = parse_children(&content)?;
|
|
return Ok(RsxNode::If {
|
|
deps,
|
|
cond,
|
|
children,
|
|
});
|
|
}
|
|
|
|
// %$dep for $pat in $expr { $rsx }
|
|
if input.peek(Token![for]) {
|
|
input.parse::<Token![for]>()?;
|
|
let pat: Pat = Pat::parse_multi(input)?;
|
|
input.parse::<Token![in]>()?;
|
|
let expr: Expr = input.parse()?;
|
|
let content;
|
|
braced!(content in input);
|
|
let children = parse_children(&content)?;
|
|
return Ok(RsxNode::For {
|
|
deps,
|
|
pat,
|
|
expr,
|
|
children,
|
|
});
|
|
}
|
|
|
|
// "%$dep for $pat in $expr { $rsx }"
|
|
if input.peek(LitStr) {
|
|
return Ok(RsxNode::Text(input.parse()?));
|
|
}
|
|
|
|
parse_component_invocation(input)
|
|
}
|
|
}
|
|
|
|
/// Parses Path or Path { ... } into RsxNode::Component.
|
|
fn parse_component_invocation(input: ParseStream) -> Result<RsxNode> {
|
|
let path: Path = input.parse()?;
|
|
|
|
if !input.peek(Brace) {
|
|
return Ok(RsxNode::Component {
|
|
path,
|
|
props: Vec::new(),
|
|
children: Vec::new(),
|
|
});
|
|
}
|
|
|
|
let content;
|
|
braced!(content in input);
|
|
|
|
let (props, children) = parse_props_and_children(&content)?;
|
|
|
|
Ok(RsxNode::Component {
|
|
path,
|
|
props,
|
|
children,
|
|
})
|
|
}
|
|
|
|
fn parse_props_and_children(input: ParseStream) -> Result<(Vec<RsxProp>, Vec<RsxNode>)> {
|
|
Ok((parse_props(input)?, RsxRoot::parse(input)?.nodes))
|
|
}
|
|
|
|
fn parse_props(input: ParseStream) -> Result<Vec<RsxProp>> {
|
|
let mut props = Vec::new();
|
|
|
|
while !input.is_empty() {
|
|
let fork = input.fork();
|
|
|
|
let path: Path = match fork.parse() {
|
|
Ok(p) => p,
|
|
Err(_) => break,
|
|
};
|
|
|
|
if fork.peek(Token![:]) {
|
|
let name = path
|
|
.get_ident()
|
|
.cloned()
|
|
.ok_or_else(|| fork.error("prop name must be a single identifier"))?;
|
|
|
|
fork.parse::<Token![:]>()?;
|
|
let value: Expr = fork.parse()?;
|
|
|
|
input.advance_to(&fork);
|
|
|
|
props.push(RsxProp { name, value });
|
|
|
|
if input.peek(Token![,]) {
|
|
input.parse::<Token![,]>()?;
|
|
}
|
|
} else {
|
|
// ❌ do not commit → nothing consumed
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok(props)
|
|
}
|
|
|
|
fn parse_children(input: ParseStream) -> Result<Vec<RsxNode>> {
|
|
let mut nodes = Vec::new();
|
|
while !input.is_empty() {
|
|
nodes.push(input.parse()?);
|
|
}
|
|
Ok(nodes)
|
|
}
|