Pattern Matching

You can pattern match on each field in a struct (or enum, as we'll see later) to tell Astray that it should parse a specific instance of a Token (or type) for that specific field.

No Pattern matching

If pattern matching is not specified, then any instance of that type may be parsed. This includes Tokens:

set_token!(Token)

#[derive(SN)]
struct Pair{
    l_element: Token,
    comma: Token,
    r_element: Token,
}

fn main() {
    let tokens = lexer("a b c");
    assert_eq!(tokens, vec![
        Token::Identifier("a".to_owned())
        Token::Identifier("b".to_owned())
        Token::Identifier("c".to_owned())
    ])
    // Any three tokens will be successfully parsed. This is pretty useless
    let pair: Result<Pair, ParseError<Token>> = Pair::parse(tokens.into())
}

Of course, parsing Tokens without a specific

With pattern matching

If the user wants to parse a specific instance of a type, annotate the desired field with the pattern that field is expected to have.

set_token!(Token)

#[derive(SN)]
struct Pair{
    #[pat(Token::Identifier(_))]
    l_element: Token,
    #[pat(Token::Comma)]
    comma: Token,
    #[pat(Token::Identifier(_))]
    r_element: Token,
}

fn main() {
    let tokens = [
        Token::Identifier("a".to_owned()),
        Token::Identifier("b".to_owned()),
        Token::Identifier("c".to_owned()),
    ]
    // result is err
    let pair: Result<Pair, ParseError<Token>> = Pair::parse(tokens.into())
    assert!(pair.is_err());

    let tokens = [
        Token::Identifier("a".to_owned()),
        Token::Comma,
        Token::Identifier("c".to_owned()),
    ];
    let pair: Result<Pair, ParseError<Token>> = Pair::parse(tokens.into())
    assert_eq!(pair, Ok(Pair {
        l_element: Token::Identifier("a".to_owned()),
        comma: Token::Comma,
        r_element : Token::Identifier("c".to_owned()),
    }))
}

Of course this works for all parsable types, not just tokens:

struct Expr {
    #[pat(Token::IntLiteral(_))]
    left: Token,
    #[pat(Sign::Add)]
    sign: Sign,
    #[pat(Token::IntLiteral(_))]
    right: Token,
}

enum Sign {
    #[pat(Token::Plus)]
    Add,
    #[pat(Token::Minus)]
    Sub,
}


fn main() {
    let tokens = [
        Token::IntLiteral(3),
        Token::Plus,
        Token::IntLiteral(2),
    ]
    let expr_result = Expr::parse(tokens.into());
    assert_eq!(expr_result, Ok(
        Expr {
            left: Token::IntLiteral(3),
            sign: Sign::Add
            right: Token::IntLiteral(2),
        }
    ))

    let tokens = [
        Token::IntLiteral(3),
        Token::Minus,
        Token::IntLiteral(2),
    ]
    let expr_result = Expr::parse(tokens.into());
    // Does not parse, since Expr is expecting specifically a Sign::Add, which may not be parsed when Token::Minus is present instead of Token::Plus
    assert!(expr_result.is_err())
}

Extract values

Currently a WIP, you can extract specific values form a matched pattern, should you want to keep only the inner values of a struct / enum in your AST

set_token!(Token)

#[derive(SN)]
struct Pair{
    #[extract(Token::Identifier(l_element))]
    l_element: String,
    #[pat(Token::Comma)]
    comma: Token,
    #[extract(Token::Identifier(r_element))]
    r_element: String,
}

fn main() {
    let tokens = [
        Token::Identifier("a".to_owned()),
        Token::Comma,
        Token::Identifier("c".to_owned()),
    ];
    let pair: Result<Pair, ParseError<Token>> = Pair::parse(tokens.into())
    assert_eq!(pair, Ok(Pair {
        l_element: "a".to_owned(),
        comma: Token::Comma,
        r_element : "c".to_owned(),
    }))
}

Either this or that

As you'd expect, it's possible to use patterns with pipes to make Astray parse one possible type from a set of types. This can be a replacement for moving a type to a separate enum, and will very likely be faster. TODO: Benchmark this

set_token!(Token)

#[derive(SN)]
struct Pair{
    #[extract(Token::Identifier(l_element))]
    l_element: String,
    #[pat(Token::Comma | Token::SemiColon)]
    comma: Token,
    #[extract(Token::Identifier(r_element))]
    r_element: String,
}

fn main() {
    let tokens = [
        Token::Identifier("a".to_owned()),
        Token::Identifier(",".to_owned()),
        Token::Identifier("c".to_owned()),
    ]

    let pair: Result<Pair, ParseError<Token>> = Pair::parse(tokens.into())
    assert_eq!(pair, Ok(Pair {
        l_element: "a".to_owned(),
        comma: Token::Comma,
        r_element : "c".to_owned(),
    }))

    let tokens = [
        Token::Identifier("a".to_owned()),
        Token::Comma,
        Token::Identifier("c".to_owned()),
    ];
    let pair: Result<Pair, ParseError<Token>> = Pair::parse(tokens.into())
    assert_eq!(pair, Ok(Pair {
        l_element: "a".to_owned(),
        comma: Token::Comma,
        r_element : "c".to_owned(),
    }))
}