truthful 0.1.1

A logical expression parser, optimizer and evaluator.
Documentation
use super::Instruction;
use core::fmt;
use inner::Rule;
use pest::{
    iterators::{Pair, Pairs},
    Parser,
};

#[cfg(test)]
mod tests;

mod inner {
    #[derive(pest_derive::Parser)]
    #[grammar = "./src/grammar/vco.pest"]
    pub struct Parser;
}

impl TryFrom<&str> for Instruction {
    type Error = String;

    fn try_from(program: &str) -> Result<Self, Self::Error> {
        let mut pairs =
            inner::Parser::parse(Rule::statement, program).map_err(|e| e.to_string())?;

        let pair = fetch_pair(&mut pairs)?;
        let expr = fetch_pair(&mut pair.into_inner())?;

        fetch_expr(&mut expr.into_inner()).map(Self::optimize)
    }
}

impl fmt::Display for Instruction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use Instruction::*;
        match self {
            True => write!(f, "1"),
            False => write!(f, "0"),
            Argument(a) => write!(f, "\"{a}\""),
            Not(i) => write!(f, "!{i}"),
            And(l, r) => write!(f, "({l} ^ {r})"),
            Or(l, r) => write!(f, "({l} v {r})"),
            Xor(l, r) => write!(f, "({l} + {r})"),
            Conditional(l, r) => write!(f, "({l} -> {r})"),
            Biconditional(l, r) => write!(f, "({l} <-> {r})"),
            Equals(l, r) => write!(f, "({l} = {r})"),
        }
    }
}

fn fetch_pair<'a>(pairs: &mut Pairs<'a, Rule>) -> Result<Pair<'a, Rule>, String> {
    pairs
        .next()
        .ok_or_else(|| "the grammar is inconsistent: a pair was expected".to_string())
}

fn fetch_term(pairs: &mut Pairs<'_, Rule>) -> Result<Instruction, String> {
    let mut pair = fetch_pair(pairs)?;

    let mut unary_not = false;
    while let Rule::not = pair.as_rule() {
        unary_not = !unary_not;
        pair = fetch_pair(pairs)?
    }

    let instruction = match pair.as_rule() {
        Rule::identifier => {
            let mut identifier = pair.as_str().to_string();
            if identifier.starts_with('"') {
                identifier = identifier
                    .strip_prefix('"')
                    .unwrap_or(identifier.as_str())
                    .strip_suffix('"')
                    .unwrap_or(identifier.as_str())
                    .to_string();
            } else if identifier.starts_with('\'') {
                identifier = identifier
                    .strip_prefix('\'')
                    .unwrap_or(identifier.as_str())
                    .strip_suffix('\'')
                    .unwrap_or(identifier.as_str())
                    .to_string();
            }
            Instruction::Argument(identifier)
        }
        Rule::expr => fetch_expr(&mut pair.into_inner())?,
        _ => return Err("inconsistent grammar".to_string()),
    };

    if unary_not {
        Ok(Instruction::Not(Box::new(instruction)))
    } else {
        Ok(instruction)
    }
}

fn fetch_infix_rule(pairs: &mut Pairs<'_, Rule>) -> Result<Option<Rule>, String> {
    let pair = match pairs.next() {
        Some(p) => p,
        None => return Ok(None),
    };

    let rule = pair.as_rule();
    use Rule::*;
    match rule {
        and | or | xor | cond | bicond | equals => Ok(Some(rule)),
        _ => Err("invalid grammar: expected infix rule".to_string()),
    }
}

fn fetch_expr(pairs: &mut Pairs<'_, Rule>) -> Result<Instruction, String> {
    let pair = fetch_pair(pairs)?;
    let mut term = fetch_term(&mut pair.into_inner())?;

    while let Some(infix) = fetch_infix_rule(pairs)? {
        let lhs = Box::new(term);

        let rhs = fetch_pair(pairs)?;
        let rhs = fetch_term(&mut rhs.into_inner())?;
        let rhs = Box::new(rhs);

        use Instruction::*;
        term = match infix {
            Rule::and => And(lhs, rhs),
            Rule::or => Or(lhs, rhs),
            Rule::xor => Xor(lhs, rhs),
            Rule::cond => Conditional(lhs, rhs),
            Rule::bicond => Biconditional(lhs, rhs),
            Rule::equals => Equals(lhs, rhs),
            _ => return Err("internal error computing infix".to_string()),
        };
    }

    Ok(term)
}