panfix 0.6.2

Panfix parsing: linear time parsing of multifix operators.
Documentation
use panfix::parsing::{Fixity, ParseError, Parser, Pattern, Visitor};

pub fn run_parser(parser: &Parser, input: &str) -> String {
    use ParseError::*;

    match parser.parse(input) {
        Err(error) => match error {
            LexError { lexeme, pos } => format!("LexErr {} {}:{}", lexeme, pos.line, pos.column),
            ExtraSeparator { separator, pos } => {
                format!("ExtraSep {} {}:{}", separator, pos.line, pos.column)
            }
            MissingSeparator {
                op_name,
                separator,
                pos,
            } => format!(
                "MissingSep {} {} {}:{}",
                op_name, separator, pos.line, pos.column
            ),
        },
        Ok(parsed) => {
            let mut expr = String::new();
            for (i, visitor) in parsed.groups().enumerate() {
                parenthesize(&mut expr, visitor, &parser);
                if i + 1 != parsed.groups().len() {
                    expr.push(' ');
                }
            }
            expr
        }
    }
}

fn parenthesize(out: &mut String, visitor: Visitor, parser: &Parser) {
    let fixity = visitor.fixity();
    let mut children = visitor.children();
    if children.len() == 0 {
        out.push_str(visitor.text());
        return;
    }
    let mut delims = visitor.op_patterns(&parser).into_iter();
    out.push('(');
    if fixity == Fixity::Infix || fixity == Fixity::Suffix {
        let child = children.next().unwrap();
        if child.name() != "$MissingAtom" {
            parenthesize(out, child, parser);
            out.push(' ');
        }
    }
    let delim = delims.next().unwrap_or(None);
    out.push_str(pattern_to_const(delim, visitor));
    while let Some(delim) = delims.next() {
        let child = children.next().unwrap();
        out.push(' ');
        parenthesize(out, child, parser);
        out.push(' ');
        out.push_str(pattern_to_const(delim, visitor));
    }
    if fixity == Fixity::Infix || fixity == Fixity::Prefix {
        let child = children.next().unwrap();
        if child.name() != "$MissingAtom" {
            out.push(' ');
            parenthesize(out, child, parser);
        }
    }
    assert!(children.next().is_none());
    out.push(')');
}

fn pattern_to_const<'a>(pattern: Option<&'a Pattern>, visitor: Visitor<'a>) -> &'a str {
    match pattern {
        None => "?",
        Some(Pattern::Constant(constant)) => &constant,
        Some(_) => visitor.text(),
    }
}