use syn::parse::{ParseStream, Result};
use syn::{Block, Expr, Local, Stmt, Token};
use crate::diag;
use crate::ir::{BodyItem, RustShape};
use super::{
parse_element, parse_for, parse_if, parse_match, parse_property_args, parse_spread,
peek_binding, peek_escape, peek_spread,
};
pub(crate) fn parse_body(input: ParseStream) -> Result<Vec<BodyItem>> {
let mut items = Vec::new();
while !input.is_empty() {
let item = parse_body_item(input)?;
items.push(item);
while input.peek(Token![,]) {
let _comma: Token![,] = input.parse()?;
}
}
Ok(items)
}
fn parse_body_item(input: ParseStream) -> Result<BodyItem> {
if peek_escape(input) {
let pound_span = input.span();
let _pound: Token![#] = input.parse()?;
let content;
let _brace = syn::braced!(content in input);
let expr: Expr = content.parse()?;
if !content.is_empty() {
return Err(diag::error(
content.span(),
"expected a single expression inside `#{ ... }`",
));
}
return Ok(BodyItem::Escape {
expr,
span: pound_span,
});
}
if peek_binding(input) {
let name: syn::Ident = input.parse()?;
let _eq: Token![=] = input.parse()?;
let element = parse_element(input)?;
return Ok(BodyItem::Binding { name, element });
}
if input.peek(Token![let]) {
let local = parse_let_local(input)?;
return Ok(BodyItem::Let(local));
}
if input.peek(Token![if]) {
return parse_if(input).map(BodyItem::If);
}
if input.peek(Token![match]) {
return parse_match(input).map(BodyItem::Match);
}
if input.peek(Token![for]) {
return parse_for(input).map(BodyItem::For);
}
if peek_spread(input) {
let (expr, span) = parse_spread(input)?;
return Ok(BodyItem::Spread { expr, span });
}
if input.peek(syn::Ident) {
let ahead: syn::Ident = input.fork().parse()?;
if ahead == "rust" && input.peek2(syn::token::Brace) {
return parse_rust_block(input);
}
}
if input.peek(Token![self])
|| input.peek(Token![Self])
|| input.peek(Token![crate])
|| input.peek(Token![super])
{
let expr: Expr = input.parse()?;
let span = syn::spanned::Spanned::span(&expr);
return Ok(BodyItem::ExprChild { expr, span });
}
if !input.peek(syn::Ident) {
let span = input.span();
return Err(diag::error(
span,
"expected a property name, child element, binding, or `#{ expr }` escape",
));
}
if input.peek2(Token![:]) && !input.peek2(Token![::]) {
return parse_property(input).map(BodyItem::Property);
}
let ident: syn::Ident = input.fork().parse()?;
if super::cursor::ident_starts_upper(&ident) {
let element = parse_element(input)?;
return Ok(BodyItem::Child(element));
}
if !is_bare_no_arg_property(input) {
let expr: Expr = input.parse()?;
let span = syn::spanned::Spanned::span(&expr);
return Ok(BodyItem::ExprChild { expr, span });
}
let property = super::property::parse_property_no_args(input)?;
Ok(BodyItem::Property(property))
}
fn is_bare_no_arg_property(input: ParseStream) -> bool {
let fork = input.fork();
if fork.parse::<syn::Ident>().is_err() {
return false;
}
fork.is_empty()
|| fork.peek(Token![,])
|| (fork.peek(syn::Ident) && !fork.peek(Token![as]))
|| fork.peek(Token![if])
|| fork.peek(Token![for])
|| fork.peek(Token![match])
|| fork.peek(Token![let])
|| fork.peek(Token![#])
}
fn parse_property(input: ParseStream) -> Result<crate::ir::TeksiProperty> {
let name: syn::Ident = input.parse()?;
let _colon: Token![:] = input.parse()?;
let args = parse_property_args(input)?;
Ok(crate::ir::TeksiProperty { name, args })
}
fn parse_let_local(input: ParseStream) -> Result<Local> {
let stmt: Stmt = input.parse()?;
match stmt {
Stmt::Local(local) => Ok(local),
other => Err(diag::error(
syn::spanned::Spanned::span(&other),
"expected a `let` binding at this body position",
)),
}
}
fn parse_rust_block(input: ParseStream) -> Result<BodyItem> {
let ident: syn::Ident = input.parse()?;
let span = ident.span();
let block: Block = input.parse()?;
let shape = classify_block_shape(&block);
Ok(BodyItem::Rust { block, span, shape })
}
fn classify_block_shape(block: &Block) -> RustShape {
match block.stmts.last() {
Some(Stmt::Expr(_, None)) => RustShape::Expression,
_ => RustShape::SideEffect,
}
}