moxy-template 0.1.0

Rust quasi-quoting and token templating with explicit interpolation syntax and built-in template control flow
Documentation
#![allow(unused)]

use moxy_token::parser::{ParseError, ParseStream};
use moxy_token::{Delim, Group, Parse, Span, ToTokenStream, ToTokens, Token, TokenStream, TokenTree};

use crate::Template;

#[doc = "A template if/else-if/else directive: `@if (cond) { body } @else if (cond) { body } @else { body }`."]
#[derive(Debug, Clone)]
pub struct TmplIf {
    pub span: Span,
    pub at_punct: Token![@],
    pub if_keyword: Token![if],
    pub branches: Vec<TmplIfBranch>,
    pub else_at_punct: Option<Token![@]>,
    pub else_keyword: Option<Token![else]>,
    pub else_body: Option<Box<Template>>,
}

#[doc = "A single branch of a `@if` or `@else if` directive."]
#[derive(Debug, Clone)]
pub struct TmplIfBranch {
    pub span: Span,
    pub at_punct: Option<Token![@]>,
    pub else_keyword: Option<Token![else]>,
    pub if_keyword: Token![if],
    pub cond: TokenStream,
    pub body: Template,
}

impl TmplIf {
    pub fn parse_after_keyword_if(stream: &mut ParseStream, at_punct: Token![@], if_kw: Token![if]) -> Result<Self, ParseError> {
        let span = at_punct.span();
        let first = Self::parse_branch(stream, None, None, if_kw)?;
        let mut branches = vec![first];
        let mut else_at_punct = None;
        let mut else_keyword_field = None;
        let mut else_body = None;

        loop {
            let mut fork = stream.lookahead();

            if !fork.peek::<Token![@]>() {
                break;
            }

            fork.advance();

            if !fork.peek::<Token![else]>() {
                break;
            }

            let at2 = stream.parse::<Token![@]>()?;
            let else_kw = stream.parse::<Token![else]>()?;

            if let Some(if_kw2) = stream.parse_if::<Token![if]>() {
                branches.push(Self::parse_branch(stream, Some(at2), Some(else_kw), if_kw2)?);
            } else {
                let body_stream = stream.parse_group(Delim::Brace)?;
                let mut body_ps = body_stream.parse();
                else_at_punct = Some(at2);
                else_keyword_field = Some(else_kw);
                else_body = Some(Box::new(body_ps.parse::<Template>()?));
                break;
            }
        }

        Ok(Self {
            span,
            at_punct,
            if_keyword: branches[0].if_keyword,
            branches,
            else_at_punct,
            else_keyword: else_keyword_field,
            else_body,
        })
    }

    pub fn parse_branch(
        stream: &mut ParseStream,
        at_punct: Option<Token![@]>,
        else_keyword: Option<Token![else]>,
        if_keyword: Token![if],
    ) -> Result<TmplIfBranch, ParseError> {
        let span = if_keyword.span();
        let cond = stream.parse_group(Delim::Paren)?;
        let body_stream = stream.parse_group(Delim::Brace)?;
        let mut body_ps = body_stream.parse();
        let body = body_ps.parse::<Template>()?;

        Ok(TmplIfBranch {
            span,
            at_punct,
            else_keyword,
            if_keyword,
            cond,
            body,
        })
    }
}

impl ToTokens for TmplIf {
    fn to_tokens(&self, out: &mut TokenStream) {
        for (i, branch) in self.branches.iter().enumerate() {
            if i > 0 {
                <Token![else]>::new(Span::call_site()).to_tokens(out);
            }

            <Token![if]>::new(Span::call_site()).to_tokens(out);
            branch.cond.to_tokens(out);
            out.extend_one(TokenTree::Group(Group::new(Delim::Brace, branch.body.to_token_stream())));
        }

        if let Some(else_b) = &self.else_body {
            <Token![else]>::new(Span::call_site()).to_tokens(out);
            out.extend_one(TokenTree::Group(Group::new(Delim::Brace, else_b.to_token_stream())));
        }
    }
}