panfix 0.6.2

Panfix parsing: linear time parsing of multifix operators.
Documentation
use panfix::implementation::op_resolver::{resolve, OpResolverError};
use panfix::{Lexeme, Position, Token, TOKEN_ERROR};
use std::iter;

type OpToken = Token;

#[test]
fn test_op_resolver() {
    // SYNTAX:
    //
    // Sort Expr:
    //   _ < _         -- less than
    //   _ > _         -- greater than
    //   _ ? Expr : _  -- ternary expression
    //   _ : Type ?    -- is of type?

    const TOKEN_ID: Token = 3;
    const TOKEN_LANGLE: Token = 4;
    const TOKEN_RANGLE: Token = 5;
    const TOKEN_QMARK: Token = 6;
    const TOKEN_COLON: Token = 7;
    const NUM_TOKENS: usize = 8;

    const OP_ID: Token = 3;
    const OP_TERNARY_1: Token = 4;
    const OP_TERNARY_2: Token = 5;
    const OP_HASTYPE_1: Token = 6;
    const OP_HASTYPE_2: Token = 7;
    const OP_LT: Token = 8;
    const OP_GT: Token = 9;
    const NUM_OP_TOKENS: usize = 10;

    fn lex(src: &str) -> Vec<Lexeme> {
        let mut lexemes = vec![];
        let mut pos = Position::start();
        for i in 0..src.len() {
            let ch = src[i..i + 1].chars().next().unwrap();
            if ch == ' ' {
                pos = pos.advance_by_char(' ');
                continue;
            }
            let token = match ch {
                'a'..='z' => TOKEN_ID,
                '<' => TOKEN_LANGLE,
                '>' => TOKEN_RANGLE,
                '?' => TOKEN_QMARK,
                ':' => TOKEN_COLON,
                _ => TOKEN_ERROR,
            };
            let start_pos = pos;
            pos = pos.advance_by_char(ch);
            let end_pos = pos;
            lexemes.push(Lexeme::new(token, start_pos, end_pos));
        }
        lexemes
    }

    // SortId -> Token -> op starting with that token, if any.
    let mut op_table = vec![];
    for _ in 0..NUM_TOKENS {
        op_table.push(None);
    }
    op_table[TOKEN_ID] = Some(OP_ID);
    op_table[TOKEN_LANGLE] = Some(OP_LT);
    op_table[TOKEN_RANGLE] = Some(OP_GT);
    op_table[TOKEN_QMARK] = Some(OP_TERNARY_1);
    op_table[TOKEN_COLON] = Some(OP_HASTYPE_1);

    // OpToken -> next follower, if any.
    let mut follower_table = iter::repeat(None)
        .take(NUM_OP_TOKENS)
        .collect::<Vec<Option<(Token, OpToken)>>>();
    follower_table[OP_TERNARY_1] = Some((TOKEN_COLON, OP_TERNARY_2));
    follower_table[OP_HASTYPE_1] = Some((TOKEN_QMARK, OP_HASTYPE_2));

    fn to_ops(
        lexemes: Result<Vec<Lexeme>, OpResolverError>,
    ) -> Result<Vec<OpToken>, OpResolverError> {
        lexemes.map(|lexemes| lexemes.into_iter().map(|lex| lex.token).collect())
    }

    let src = "a";
    let lexemes = resolve(&op_table, &follower_table, lex(src));
    assert_eq!(to_ops(lexemes), Ok(vec![OP_ID]));

    let src = "a < b";
    let lexemes = resolve(&op_table, &follower_table, lex(src));
    assert_eq!(to_ops(lexemes), Ok(vec![OP_ID, OP_LT, OP_ID]));

    let src = "a ? b > c : d > e";
    let lexemes = resolve(&op_table, &follower_table, lex(src));
    assert_eq!(
        to_ops(lexemes),
        Ok(vec![
            OP_ID,
            OP_TERNARY_1,
            OP_ID,
            OP_GT,
            OP_ID,
            OP_TERNARY_2,
            OP_ID,
            OP_GT,
            OP_ID
        ])
    );
}