use nom::{
bytes::complete::{tag, take, take_till, take_while},
character::complete::{anychar, digit1, one_of},
combinator::{opt, peek},
Err, IResult, Needed,
};
use std::num::NonZeroUsize;
use unicode_xid::UnicodeXID;
fn parse_count(input: &str) -> IResult<&str, Count> {
let error = Err(Err::Error(nom::error::Error {
input: "",
code: nom::error::ErrorKind::Fail,
}));
if input.is_empty() {
return error;
}
if let (new_input, Some(arg)) = opt(parse_identifier)(input)? {
if let (new_new_input, Some(_)) = opt(tag("$"))(new_input)? {
Ok((new_new_input, Count::Parameter(Argument::Identifier(arg))))
} else {
Err(Err::Error(nom::error::Error {
input: new_input,
code: nom::error::ErrorKind::Fail,
}))
}
} else if let (new_input, Some(count)) = opt(digit1)(input)? {
Ok((new_input, Count::Integer(count.parse::<usize>().unwrap())))
} else {
error
}
}
fn parse_identifier(input: &str) -> IResult<&str, String> {
let (car, cdr) = (
input
.chars()
.nth(0)
.ok_or(Err::Incomplete(Needed::Size(unsafe {
NonZeroUsize::new_unchecked(1)
})))?,
&input[1..],
);
if !UnicodeXID::is_xid_start(car) {
return Err(Err::Error(nom::error::Error {
input: "",
code: nom::error::ErrorKind::Fail,
}));
}
let out: IResult<&str, &str> = take_while(UnicodeXID::is_xid_continue)(cdr);
let (input, cdr) = out?;
let out = format!("{}{}", car, cdr);
return Ok((input, out));
}
fn parse_argument(input: &str) -> IResult<&str, Argument> {
if let (new_input, Some(arg)) = opt(parse_identifier)(input)? {
Ok((new_input, Argument::Identifier(arg)))
} else if let (new_input, Some(arg)) = opt(digit1)(input)? {
Ok((new_input, Argument::Integer(arg.parse::<usize>().unwrap())))
} else {
Err(Err::Error(nom::error::Error {
input: "",
code: nom::error::ErrorKind::Fail,
}))
}
}
pub fn parse_fmt_spec(input: &str) -> IResult<&str, FormatSlot> {
let (input, arg) = if let (input, Some(ident)) = opt(parse_argument)(input)? {
(input, Some(ident))
} else {
(input, None)
};
let (input, fmt_spec) = if let (input, Some(_)) = opt(tag(":"))(input)? {
let (input, fill) = if let (input, Some(fill)) = opt(anychar)(input)? {
(input, Some(fill))
} else {
(input, None)
};
let (input, align) =
if let (input, Some(align)) = opt(one_of("<^>"))(input)? {
(
input,
Some(match align {
'<' => Align::Left,
'^' => Align::Center,
'>' => Align::Right,
_ => {
unreachable!()
}
}),
)
} else {
(input, None)
};
let (input, sign) = if let (input, Some(sign)) = opt(one_of("+-"))(input)? {
(
input,
Some(match sign {
'+' => Sign::Positive,
'-' => Sign::Negative,
_ => {
unreachable!()
}
}),
)
} else {
(input, None)
};
let (input, alternate) = if let (input, Some(_)) = opt(tag("#"))(input)? {
(input, true)
} else {
(input, false)
};
let (input, pad_with_zeros) =
if let (input, Some(_)) = opt(tag("0"))(input)? {
(input, true)
} else {
(input, false)
};
let (input, width) = if let (input, Some(width)) = opt(parse_count)(input)?
{
(input, Some(width))
} else {
(input, None)
};
let (input, percision) = if let (input, Some(_)) = opt(tag("."))(input)? {
let input = input;
if let (input, Some(percision)) = opt(parse_count)(input)? {
(input, Some(Precision::Count(percision)))
} else if let (input, Some(_)) = opt(tag("*"))(input)? {
(input, Some(Precision::SpecifiedPrecision))
} else {
(input, None)
}
} else {
(input, None)
};
let (input, kind) =
if let (input, Some(kind)) = opt(one_of("?oxXpbeE"))(input)? {
match kind {
'x' => {
if let (input, Some(_)) = opt(tag("?"))(input)? {
(input, Type::DebugLowerHex)
} else {
(input, Type::LowerHex)
}
}
'X' => {
if let (input, Some(_)) = opt(tag("?"))(input)? {
(input, Type::DebugUpperHex)
} else {
(input, Type::UpperHex)
}
}
_ => (
input,
match kind {
'?' => Type::Debug,
'o' => Type::Octal,
'p' => Type::Pointer,
'b' => Type::Binary,
'e' => Type::LowerExp,
'E' => Type::UpperExp,
_ => {
unreachable!()
}
},
),
}
} else {
(input, Type::None)
};
(
input,
Some(FormatSpec {
fill,
align,
sign,
alternate,
pad_with_zeros,
width,
percision,
kind,
}),
)
} else {
(input, None)
};
Ok((input, FormatSlot { arg, fmt_spec }))
}
#[derive(Debug, PartialEq, Eq)]
enum State {
Text,
MaybeFormat,
}
pub fn parse_fmt_str(input: &str) -> Result<FormatString, &str> {
let mut strings: Vec<String> = vec![];
let mut slots: Vec<PossibleFormatSlot> = vec![];
let mut input: String = input.to_string();
let mut state: State = State::MaybeFormat;
loop {
println!(
"INPUT: {:?}, strings: {:?}, state: {:?}",
input, strings, state
);
if input.is_empty() {
break;
}
let part1: IResult<&str, &str> = peek(tag("{{"))(&input);
let part2: IResult<&str, &str> = peek(tag("}}"))(&input);
if !part1.is_err() || !part2.is_err() {
if state == State::MaybeFormat {
strings.push("".to_string());
}
let out: IResult<&str, &str> = take(1usize)(&input);
let (input_str, push2str) = out.unwrap();
let mut input_str = input_str.to_string();
slots.push(match push2str {
"{" => PossibleFormatSlot::LeftBrace,
"}" => PossibleFormatSlot::RightBrace,
_ => {
unreachable!()
}
});
input_str.remove(0);
input = input_str.to_string();
state = State::MaybeFormat;
continue;
}
if input.starts_with("{") {
if state == State::MaybeFormat {
strings.push("".to_string());
state = State::Text;
}
let out: IResult<&str, &str> = take_till(|chr| chr == '}')(&input);
let (input_str, format) = out.unwrap();
let mut input_str = input_str.to_string();
let mut format = format.to_string();
if format.is_empty() {
strings.push(input_str);
break;
}
if !input_str.is_empty() {
input_str.remove(0);
}
format.remove(0);
if format.is_empty() {
strings.push(input_str);
break;
}
input = input_str.to_string();
let next = parse_fmt_spec(&format);
if next.is_err() {
return Err("Invalid format string. (slot didn't parse)");
}
let (left, slot) = next.unwrap();
if !left.is_empty() {
return Err("Invalid format string. (slot had additional data)");
}
slots.push(PossibleFormatSlot::FormatSlot(slot));
state = State::MaybeFormat;
} else {
assert_eq!(state, State::MaybeFormat);
let cloned_input = input.clone();
let out: IResult<&str, &str> = take_till(|chr| chr == '{' || chr == '}')(&cloned_input);
let (input_str, push2str) = out.unwrap();
input = input_str.to_string();
strings.push(push2str.to_string());
state = State::Text;
}
}
if state == State::MaybeFormat {
strings.push("".to_string());
}
Ok(FormatString {
text: strings,
maybe_fmt: slots,
})
}
pub type Fill = char;
#[derive(Debug, PartialEq, Eq)]
pub enum Align {
Left,
Center,
Right,
}
#[derive(Debug, PartialEq, Eq)]
pub enum Sign {
Positive,
Negative,
}
#[derive(Debug, PartialEq, Eq)]
pub enum Count {
Parameter(Argument),
Integer(usize),
}
#[derive(Debug, PartialEq, Eq)]
pub enum Precision {
Count(Count),
SpecifiedPrecision,
}
#[derive(Debug, PartialEq, Eq)]
pub enum Type {
Debug,
DebugLowerHex,
DebugUpperHex,
Octal,
LowerHex,
UpperHex,
Pointer,
Binary,
LowerExp,
UpperExp,
None,
}
#[derive(Debug, PartialEq, Eq)]
pub struct FormatSpec {
pub fill: Option<Fill>,
pub align: Option<Align>,
pub sign: Option<Sign>,
pub alternate: bool,
pub pad_with_zeros: bool,
pub width: Option<Count>,
pub percision: Option<Precision>,
pub kind: Type,
}
#[derive(Debug, PartialEq, Eq)]
pub enum Argument {
Identifier(String),
Integer(usize),
}
#[derive(Debug, PartialEq, Eq)]
pub struct FormatSlot {
pub arg: Option<Argument>,
pub fmt_spec: Option<FormatSpec>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum PossibleFormatSlot {
FormatSlot(FormatSlot),
LeftBrace,
RightBrace,
}
#[derive(Debug, PartialEq, Eq)]
pub struct FormatString {
pub text: Vec<String>,
pub maybe_fmt: Vec<PossibleFormatSlot>,
}