use nom::{
branch::alt,
bytes::complete::{is_not, tag, take_while_m_n},
character::complete::{alpha1, alphanumeric1, char, multispace1, one_of, space0, space1},
combinator::{map, map_opt, map_res, opt, recognize, value, verify},
multi::{fold_many0, many0, many1, separated_list0},
sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
IResult,
};
use crate::ValueRaw;
fn parse_unicode(input: &str) -> IResult<&str, char> {
let parse_hex = take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit());
let parse_delimited_hex = preceded(
char('u'),
delimited(char('{'), parse_hex, char('}')),
);
let parse_u32 = map_res(parse_delimited_hex, move |hex| u32::from_str_radix(hex, 16));
map_opt(parse_u32, std::char::from_u32)(input)
}
fn parse_escaped_char(input: &str) -> IResult<&str, char> {
preceded(
char('\\'),
alt((
parse_unicode,
value('\n', char('n')),
value('\r', char('r')),
value('\t', char('t')),
value('\u{08}', char('b')),
value('\u{0C}', char('f')),
value('\\', char('\\')),
value('/', char('/')),
value('"', char('"')),
value('\'', char('\'')),
)),
)(input)
}
fn parse_escaped_whitespace(input: &str) -> IResult<&str, &str> {
preceded(char('\\'), multispace1)(input)
}
fn parse_literal(input: &str) -> IResult<&str, &str> {
let not_quote_slash = is_not("\"\\\'");
verify(not_quote_slash, |s: &str| !s.is_empty())(input)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StringFragment<'a> {
Literal(&'a str),
EscapedChar(char),
EscapedWS,
}
fn parse_fragment(input: &str) -> IResult<&str, StringFragment> {
alt((
map(parse_literal, StringFragment::Literal),
map(parse_escaped_char, StringFragment::EscapedChar),
value(StringFragment::EscapedWS, parse_escaped_whitespace),
))(input)
}
fn parse_string(input: &str) -> IResult<&str, String> {
let build_string = || {
fold_many0(
parse_fragment,
String::new,
|mut string, fragment| {
match fragment {
StringFragment::Literal(s) => string.push_str(s),
StringFragment::EscapedChar(c) => string.push(c),
StringFragment::EscapedWS => {}
}
string
},
)
};
alt((
delimited(char('"'), build_string(), char('"')),
delimited(char('\''), build_string(), char('\'')),
))(input)
}
fn parse_int(input: &str) -> IResult<&str, (i64, &str)> {
map_res(
recognize(many1(terminated(one_of("0123456789"), many0(char('_'))))),
|s: &str| s.replace('_', "").parse::<i64>().map(|res| (res, s)),
)(input)
}
fn parse_float(input: &str) -> IResult<&str, (f64, &str)> {
map_res(
alt((
recognize(tuple((
char('.'),
parse_int,
opt(tuple((one_of("eE"), opt(one_of("+-")), parse_int))),
))),
recognize(tuple((
parse_int,
opt(preceded(char('.'), parse_int)),
one_of("eE"),
opt(one_of("+-")),
parse_int,
))),
recognize(tuple((parse_int, char('.'), opt(parse_int)))),
)),
|s: &str| s.replace('_', "").parse::<f64>().map(|res| (res, s)),
)(input)
}
fn parse_bool(input: &str) -> IResult<&str, (bool, &str)> {
alt((
map(alt((tag("true"), tag("TRUE"), tag("1"))), |s| (true, s)),
map(alt((tag("false"), tag("FALSE"), tag("0"))), |s| (false, s)),
))(input)
}
fn parse_value(input: &str) -> IResult<&str, ValueRaw> {
alt((
map(parse_string, ValueRaw::String),
map(parse_float, |(num, raw)| ValueRaw::Float(num, raw)),
map(parse_int, |(num, raw)| ValueRaw::Int(num, raw)),
map(parse_bool, |(b, raw)| ValueRaw::Bool(b, raw)),
map(
recognize(many1(one_of(
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP_-",
))),
|s: &str| ValueRaw::String(s.to_string()),
),
))(input)
}
pub fn parse_value_list(input: &str) -> IResult<&str, Vec<ValueRaw>> {
delimited(space0, separated_list0(space1, parse_value), space0)(input)
}
pub fn parse_command_name(input: &str) -> IResult<&str, &str> {
recognize(pair(
alt((alpha1, tag("_"))),
many0(alt((alphanumeric1, tag("_")))),
))(input)
}
pub fn parse_full_command(input: &str) -> IResult<&str, (&str, Vec<ValueRaw>)> {
delimited(
space0,
alt((
separated_pair(parse_command_name, space1, parse_value_list),
map(parse_command_name, |command| (command, Vec::new())),
)),
space0,
)(input)
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use crate::ValueRaw;
use super::{parse_bool, parse_float, parse_int, parse_string, parse_value, parse_value_list};
#[test]
fn it_parses_strings() {
assert_eq!(
parse_string(r#""hello world""#),
Ok(("", "hello world".to_string()))
);
assert_eq!(
parse_string(r#""hello \"world""#),
Ok(("", "hello \"world".to_string()))
);
assert_eq!(
parse_string("'hello world'"),
Ok(("", "hello world".to_string()))
);
assert!(parse_string(r#""hello world"#).is_err());
assert!(parse_string("'hello world").is_err());
assert!(parse_string(r#""hello world'"#).is_err());
assert!(parse_string(r#"'hello world""#).is_err());
}
#[test]
fn it_parses_ints() {
assert_eq!(parse_int("124"), Ok(("", (124, "124"))));
assert_eq!(parse_int("124hello"), Ok(("hello", (124, "124"))));
assert_eq!(parse_int("123_456"), Ok(("", (123456, "123_456"))));
}
#[test]
fn it_parses_floats() {
assert_eq!(parse_float("124."), Ok(("", (124.0, "124."))));
assert_eq!(parse_float("124.hello"), Ok(("hello", (124.0, "124."))));
assert_eq!(parse_float("123_456."), Ok(("", (123456.0, "123_456."))));
assert_eq!(
parse_float("123_456.789_123"),
Ok(("", (123456.789123, "123_456.789_123")))
);
}
#[test]
fn it_parses_bools() {
assert_eq!(parse_bool("true"), Ok(("", (true, "true"))));
assert_eq!(parse_bool("TRUE"), Ok(("", (true, "TRUE"))));
assert_eq!(parse_bool("1"), Ok(("", (true, "1"))));
assert_eq!(parse_bool("false"), Ok(("", (false, "false"))));
assert_eq!(parse_bool("FALSE"), Ok(("", (false, "FALSE"))));
assert_eq!(parse_bool("0"), Ok(("", (false, "0"))));
assert_eq!(parse_bool("true_"), Ok(("_", (true, "true"))));
assert_eq!(parse_bool("false_"), Ok(("_", (false, "false"))));
assert_eq!(parse_bool("1_"), Ok(("_", (true, "1"))));
assert_eq!(parse_bool("0_"), Ok(("_", (false, "0"))));
}
#[test]
fn it_parses_values() {
assert_eq!(
parse_value(r#""hello world""#),
Ok(("", ValueRaw::String("hello world".to_string())))
);
assert_eq!(
parse_value(r#""hello world""#),
Ok(("", ValueRaw::String("hello world".to_string())))
);
}
#[test]
fn it_parses_value_list() {
assert_eq!(
parse_value_list(r#""hello world" 10 true"#),
Ok((
"",
vec![
ValueRaw::String("hello world".to_string()),
ValueRaw::Int(10, "10"),
ValueRaw::Bool(true, "true"),
]
))
);
assert_eq!(
parse_value_list(r#""hello world" 10. true"#),
Ok((
"",
vec![
ValueRaw::String("hello world".to_string()),
ValueRaw::Float(10.0, "10."),
ValueRaw::Bool(true, "true"),
]
))
);
}
}