xmacro_lib 0.5.1

macro engine for producing multiple expansions
Documentation
//! This module defines the tokens xmacros use, only parsers and totokens but no logic is
//! defined here.

use std::borrow::Cow;

#[allow(clippy::wildcard_imports)]
use unsynn::*;

unsynn! {
    pub operator IndexOf = "$?";
    pub operator LengthOf = "$#";
    pub operator Directive = "$:";
    pub operator ConcatIdent = "$+";
    pub operator EscapedDollar = "$$";

    // Similar to proc_macro2::TokenTree, but adding scopes, substitution and directive types for expansion.
    pub enum XToken {
        // nested scopes expand (ScopeInner->TokenStream) directly
        Scope(Dollar, BraceGroup),

        // Definitions
        Definition(Dollar, ParenthesisGroupContaining<DefinitionContent>),
        // IDEA:  $(b=a) DefinitionAlias(Dollar, ParenthesisGroupContaining<DefinitionAlias>),
        DefinitionError(Dollar, Either<ParenthesisGroup, BracketGroup>),

        // Tokens that need special expansion
        IndexedSubstitution(Dollar, usize),
        NamedSubstitution(Dollar, CachedIdent),
        IndexOfIndexed(IndexOf, usize),
        IndexOfNamed(IndexOf, CachedIdent),
        LengthOfIndexed(LengthOf, usize),
        LengthOfNamed(LengthOf, CachedIdent),
        EscapedDollar(EscapedDollar),

        // directives will be instantly evaluated
        // IDEA: $:(table_sort row ord|alpha|alnum|num|semver|... rev)
        //       stable sort on row, can be applied multiple times
        Directive(Directive, CachedIdent),

        //Note: In future we may implement `$+(list of many things to concat)`
        ConcatIdent(ConcatIdent, CachedIdent, Dollar, Either<usize, CachedIdent>),

        // When we want to expand per items then we need to break after semicolons (and braces)
        BreakSemicolon(Semicolon),

        // Any Punct that is followed by a '$' must be set to Spacing::Alone
        BreakPunct(Punct, Expect<Dollar>),

        // Tokens from proc_macro2
        // Groups need special handling for recursion
        Group(Group),

        // normal TokenTree objects stay verbatim
        TokenTree(TokenTree),

        // We must match the EndOfStream here, otherwise the TokenStream below would match anything empty.
        EndOfStream(EndOfStream),

        // expand will insert complete TokenStreams there is no recursive expansion (yet?).
        TokenStream(TokenStream),

        // XGroup are intermediate forms not parsed
        XGroup(GroupContaining<Vec<XToken>>),
    }

    // $name:
    pub(crate) struct Named {
        pub inplace: Option<Dollar>,
        pub name: CachedIdent,
        // RESERVED: name@matches: this is not yet implemented
        pub matches: Option<Cons<At, TokenTree>>,
        _colon: Colon,
    }
}

#[derive(Debug)]
pub(crate) struct DefinitionContent {
    pub names: Vec<Named>,
    //TODO: is 'static correct?
    pub definitions: Vec<Cow<'static, [TokenStream]>>,
}

impl Parser for DefinitionContent {
    fn parser(tokens: &mut TokenIter) -> Result<Self> {
        // the headers
        let names = Vec::<Named>::parse(tokens)?;

        let len = 1.max(names.len());
        let mut definitions: Vec<Cow<'static, [TokenStream]>> = Vec::with_capacity(len);
        definitions.resize(len, Cow::Owned(Vec::new()));

        // disallow empty table definitions
        if Expect::<TokenTree>::parse(tokens).is_err() {
            return Error::unexpected_end();
        }

        // the values
        while Expect::<TokenTree>::parse(tokens).is_ok() {
            for def in definitions.iter_mut().take(len) {
                def.to_mut().push(
                    Either::<ParenthesisGroupContaining<TokenStream>, TokenTree>::parse(tokens)?
                        .fold2(|pcontent| pcontent.content, |tt| tt.to_token_stream()),
                );
            }
        }

        Ok(Self { names, definitions })
    }
}

impl ToTokens for DefinitionContent {
    fn to_tokens(&self, _tokens: &mut TokenStream) {
        unimplemented!();
    }
}

// since this crate is developed along unsynn we doing a bit more extensive testing here
#[test]
fn test_xtoken_parse() {
    // Scope(Dollar, BraceGroup),
    let parsed = "${something}"
        .to_token_iter()
        .parse_all::<XToken>()
        .unwrap();
    assert!(matches!(parsed, XToken::Scope(..)));
    assert_eq!(parsed.tokens_to_string(), "${something}".tokens_to_string());

    // Definition(Dollar, Definition),
    let parsed = "$(foo: foo)".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::Definition(..)));

    // IndexedSubstitution(Dollar, usize),
    let parsed = "$123".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::IndexedSubstitution(..)));
    assert_eq!(parsed.tokens_to_string(), "$123".tokens_to_string());

    // NamedSubstitution(Dollar, CachedIdent),
    let parsed = "$name".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::NamedSubstitution(..)));
    assert_eq!(parsed.tokens_to_string(), "$name".tokens_to_string());

    // IndexOfIndexed(IndexOf, usize),
    let parsed = "$?1".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::IndexOfIndexed(..)));
    assert_eq!(parsed.tokens_to_string(), "$?1".tokens_to_string());

    // IndexOfNamed(IndexOf, CachedIdent),
    let parsed = "$?foo".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::IndexOfNamed(..)));
    assert_eq!(parsed.tokens_to_string(), "$?foo".tokens_to_string());

    // LengthOfIndexed(LengthOf, usize),
    let parsed = "$#0".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::LengthOfIndexed(..)));
    assert_eq!(parsed.tokens_to_string(), "$#0".tokens_to_string());

    // LengthOfNamed(LengthOf, CachedIdent),
    let parsed = "$#foo".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::LengthOfNamed(..)));
    assert_eq!(parsed.tokens_to_string(), "$#foo".tokens_to_string());

    // EscapedDollar(EscapedDollar),
    let parsed = "$$".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::EscapedDollar(..)));
    assert_eq!(parsed.tokens_to_string(), "$$".tokens_to_string());

    // Directive(Directive, CachedIdent),
    let parsed = "$:directive".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::Directive(..)));
    assert_eq!(parsed.tokens_to_string(), "$:directive".tokens_to_string());

    // BreakPunct(Punct, Expect<Dollar>),
    let parsed = "+$".to_token_iter().parse::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::BreakPunct(..)));
    assert_eq!(parsed.tokens_to_string(), "+".tokens_to_string());

    // Group(Group),
    let parsed = "[anything else]"
        .to_token_iter()
        .parse_all::<XToken>()
        .unwrap();
    assert!(matches!(parsed, XToken::Group(..)));
    assert_eq!(
        parsed.tokens_to_string(),
        "[anything else]".tokens_to_string()
    );

    // TokenTree(TokenTree)
    let parsed = "something".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::TokenTree(..)));
    assert_eq!(parsed.tokens_to_string(), "something".tokens_to_string());

    // EndOfStream(EndOfStream),
    let parsed = "".to_token_iter().parse_all::<XToken>().unwrap();
    assert!(matches!(parsed, XToken::EndOfStream(..)));

    // The following cant be parsed, they are generated content
    // TokenStream(TokenStream), can't be parsed
    // XGroup(GroupContaining<Vec<XToken>>),
}