lbnf 0.1.0

A crate for parsing LBNF grammar
Documentation
use crate::grammar::{Cat, Def, Exp, Grammar, Ident, Item, Label};

fn escape_quotes(s: &str) -> String {
    s.replace('"', r#"\""#)
}

fn join_slice<T: std::fmt::Display>(v: &[T], sep: &str) -> String {
    v.iter()
        .map(std::string::ToString::to_string)
        .collect::<Vec<_>>()
        .join(sep)
}

impl std::fmt::Display for Grammar {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for def in &self.definitions {
            def.fmt(f)?;
        }

        Ok(())
    }
}

impl std::fmt::Display for Def {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Rule(ident, cat, items) => {
                write!(f, "{ident}. {cat} ::= {} ;", join_slice(items, " "))?;
            }
            Self::Macro(ident, exp) => write!(f, "{ident} {} ;", join_slice(exp, " "))?,
        }

        if f.alternate() {
            writeln!(f)?;
        }

        Ok(())
    }
}

impl std::fmt::Display for Item {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match *self {
            Self::Terminal(ref s) => write!(f, "\"{}\"", escape_quotes(s)),
            Self::NTerminal(ref cat) => cat.fmt(f),
        }
    }
}

impl std::fmt::Display for Cat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ListCat(cat) => write!(f, "[{cat}]"),
            Self::IdCat(ident) => ident.fmt(f),
        }
    }
}

impl std::fmt::Display for Label {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Id(id) => id.fmt(f),
            Self::Wild => f.write_str("_"),
            Self::ListE => f.write_str("[]"),
            Self::ListCons => f.write_str("(:)"),
            Self::ListOne => f.write_str("(:[])"),
        }
    }
}

impl std::fmt::Display for Ident {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

fn format_double(d: f64) -> String {
    let d = d.to_string();
    if d.contains(|c| c == '.') {
        d
    } else {
        d + ".0"
    }
}

impl std::fmt::Display for Exp {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match *self {
            Self::Var(ref ident) => ident.fmt(f),
            Self::LitInt(i) => i.fmt(f),
            Self::LitChar(c) => write!(f, "'{c}'"),
            Self::LitString(ref s) => write!(f, r#""{}""#, escape_quotes(s)),
            Self::LitDouble(d) => format_double(d).fmt(f),
        }
    }
}