use syn::parse::{ParseStream, Result};
use syn::{Expr, Pat, Token, token};
use crate::diag;
use crate::ir::{TeksiElse, TeksiFor, TeksiIf, TeksiMatch, TeksiMatchArm};
use super::parse_element;
pub(crate) fn parse_if(input: ParseStream) -> Result<TeksiIf> {
let if_token: Token![if] = input.parse()?;
let span = if_token.span;
parse_if_tail(input, span)
}
fn parse_if_tail(input: ParseStream, span: proc_macro2::Span) -> Result<TeksiIf> {
let cond = Expr::parse_without_eager_brace(input)?;
let then_content;
let then_brace = syn::braced!(then_content in input);
let then = parse_element(&then_content)?;
if !then_content.is_empty() {
return Err(diag::error(
then_content.span(),
"if-body must contain exactly one element — wrap multiple in a container like VStack",
));
}
let body_close = then_brace.span.close();
let else_branch = if input.peek(Token![else]) {
let _else_token: Token![else] = input.parse()?;
if input.peek(Token![if]) {
let next_if_token: Token![if] = input.parse()?;
let next = parse_if_tail(input, next_if_token.span)?;
Some(Box::new(TeksiElse::ElseIf(next)))
} else {
let else_content;
let else_brace = syn::braced!(else_content in input);
let element = parse_element(&else_content)?;
if !else_content.is_empty() {
return Err(diag::error(
else_content.span(),
"else-body must contain exactly one element",
));
}
Some(Box::new(TeksiElse::Element {
element,
body_close: else_brace.span.close(),
}))
}
} else {
None
};
Ok(TeksiIf {
cond,
then,
else_branch,
span,
body_close,
})
}
pub(crate) fn parse_match(input: ParseStream) -> Result<TeksiMatch> {
let match_token: Token![match] = input.parse()?;
let span = match_token.span;
let scrutinee = Expr::parse_without_eager_brace(input)?;
let content;
let brace = syn::braced!(content in input);
let mut arms = Vec::new();
while !content.is_empty() {
arms.push(parse_match_arm(&content)?);
}
Ok(TeksiMatch {
scrutinee,
arms,
span,
body_close: brace.span.close(),
})
}
fn parse_match_arm(input: ParseStream) -> Result<TeksiMatchArm> {
let pat = Pat::parse_multi_with_leading_vert(input)?;
let guard = if input.peek(Token![if]) {
let if_token: Token![if] = input.parse()?;
let cond: Expr = input.parse()?;
Some((if_token, cond))
} else {
None
};
let _fat_arrow: Token![=>] = input.parse()?;
let element = parse_element(input)?;
if input.peek(Token![,]) {
let _comma: Token![,] = input.parse()?;
}
Ok(TeksiMatchArm {
pat,
guard,
element,
})
}
pub(crate) fn parse_for(input: ParseStream) -> Result<TeksiFor> {
let for_token: Token![for] = input.parse()?;
let span = for_token.span;
let pat = Pat::parse_multi_with_leading_vert(input)?;
let _in: Token![in] = input.parse()?;
let iter = Expr::parse_without_eager_brace(input)?;
let content;
let brace = syn::braced!(content in input);
let mut lets = Vec::new();
while content.peek(Token![let]) {
let stmt: syn::Stmt = content.parse()?;
match stmt {
syn::Stmt::Local(local) => lets.push(local),
other => {
return Err(diag::error(
syn::spanned::Spanned::span(&other),
"expected `let` binding in for-body",
));
}
}
}
let element = parse_element(&content)?;
if !content.is_empty() {
return Err(diag::error(
content.span(),
"for-body may contain `let` bindings followed by exactly one element",
));
}
Ok(TeksiFor {
pat,
iter,
lets,
element,
span,
body_close: brace.span.close(),
})
}
pub(crate) fn parse_spread(input: ParseStream) -> Result<(Expr, proc_macro2::Span)> {
let dot_dot: Token![..] = input.parse()?;
let span = dot_dot.spans[0];
let expr: Expr = input.parse()?;
Ok((expr, span))
}
pub(crate) fn peek_spread(input: ParseStream) -> bool {
input.peek(Token![..]) && !input.peek(Token![..=]) && !input.peek2(token::Brace)
}