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)
}