infoterm 0.1.1

ncurses-compatible terminfo parsing library
Documentation
use nom::branch::alt;
use nom::bytes::complete::{is_not, tag};
use nom::character::complete::{anychar, i32, one_of, u32};
use nom::combinator::{map, opt};
use nom::multi::{fold_many1, many0};
use nom::sequence::{delimited, preceded, tuple};
use nom::IResult;

use super::{op::*, Error};

pub fn parse(input: &[u8]) -> Result<Vec<Op>, Error> {
    let r = string(input);

    match r {
        Ok((_, v)) => Ok(v),
        Err(_) => Err(Error::ParseFailed),
    }
}

fn string(input: &[u8]) -> IResult<&[u8], Vec<Op>> {
    many0(alt((plain, percent_op)))(input)
}

fn plain(input: &[u8]) -> IResult<&[u8], Op> {
    map(is_not("%"), Op::OutputBytes)(input)
}

fn percent_op(input: &[u8]) -> IResult<&[u8], Op> {
    preceded(
        tag("%"),
        alt((simple_op, format_op, char_op, int_op, param_op, var_op)),
    )(input)
}

fn simple_op(input: &[u8]) -> IResult<&[u8], Op> {
    map(one_of("%cl+-*/m&|^=><AO!~i?te;"), |c| match c {
        '%' => Op::OutputPercent,
        'c' => Op::OutputChar,
        'l' => Op::PushStringLength,
        '+' => Op::Add,
        '-' => Op::Sub,
        '*' => Op::Mul,
        '/' => Op::Div,
        'm' => Op::Mod,
        '&' => Op::BitAnd,
        '|' => Op::BitOr,
        '^' => Op::BitXor,
        '=' => Op::Eq,
        '>' => Op::Gt,
        '<' => Op::Lt,
        'A' => Op::BoolAnd,
        'O' => Op::BoolOr,
        '!' => Op::BoolNot,
        '~' => Op::BitNot,
        'i' => Op::AnsiIncrement,
        '?' => Op::IfStart,
        't' => Op::Then,
        'e' => Op::Else,
        ';' => Op::IfEnd,
        _ => unreachable!(),
    })(input)
}

fn format_op(input: &[u8]) -> IResult<&[u8], Op> {
    map(
        tuple((
            opt(preceded(opt(tag(":")), fold_many1(one_of("-+# "), FormatFlags::default, |mut f, c| {
                match c {
                    '-' => f.minus = true,
                    '+' => f.plus = true,
                    '#' => f.hash = true,
                    ' ' => f.space = true,
                    _ => {}
                };
                f
            }))),
            opt(u32),
            opt(preceded(tag("."), u32)),
            map(one_of("doxXs"), |c| match c {
                'd' => FormatSpec::Decimal,
                'o' => FormatSpec::Octal,
                'x' => FormatSpec::LowerHex,
                'X' => FormatSpec::UpperHex,
                's' => FormatSpec::String,
                _ => unreachable!(),
            }),
        )),
        |(flags, width, precision, spec)| {
            Op::OutputFormatted(Format {
                flags: flags.unwrap_or_default(),
                width,
                precision,
                spec,
            })
        }
    )(input)
}

fn param_op(input: &[u8]) -> IResult<&[u8], Op> {
    map(preceded(tag("p"), one_of("123456789")), |x| {
        Op::PushParameter(x as usize - '1' as usize)
    })(input)
}

fn var_op(input: &[u8]) -> IResult<&[u8], Op> {
    map(
        tuple((
            one_of("Pg"),
            one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
        )),
        |c| match c {
            ('P', i @ 'A'..='Z') => Op::SetStatic(i as usize - 'A' as usize),
            ('P', i @ 'a'..='z') => Op::SetDynamic(i as usize - 'a' as usize),
            ('g', i @ 'A'..='Z') => Op::GetStatic(i as usize - 'A' as usize),
            ('g', i @ 'a'..='z') => Op::GetDynamic(i as usize - 'a' as usize),
            _ => unreachable!(),
        },
    )(input)
}

fn char_op(input: &[u8]) -> IResult<&[u8], Op> {
    map(delimited(tag("'"), anychar, tag("'")), Op::PushChar)(input)
}

fn int_op(input: &[u8]) -> IResult<&[u8], Op> {
    map(delimited(tag("{"), i32, tag("}")), Op::PushInt)(input)
}