use kdl::KdlValue;
use nom::branch::alt;
use nom::bytes::complete::{tag, take_until, take_until1, take_while_m_n};
use nom::character::complete::{anychar, char, none_of, one_of};
use nom::combinator::{eof, map, map_opt, map_res, not, opt, recognize, value};
use nom::multi::{fold_many0, many0, many1, many_till};
use nom::sequence::{delimited, preceded, terminated, tuple};
use nom::IResult;
pub(crate) fn identifier(input: &str) -> IResult<&str, String> {
alt((string, (map(bare_identifier, String::from))))(input)
}
fn bare_identifier(input: &str) -> IResult<&str, &str> {
fn left(input: &str) -> IResult<&str, ()> {
not(keyword)(input)?;
not(one_of("0123456789"))(input)?;
not(one_of("+-"))(input)?;
let (input, _) = identifier_char(input)?;
let (input, _) = many0(identifier_char)(input)?;
Ok((input, ()))
}
fn right(input: &str) -> IResult<&str, ()> {
let (input, _) = one_of("+-")(input)?;
not(keyword)(input)?;
not(one_of("0123456789"))(input)?;
let (input, _) = opt(many1(identifier_char))(input)?;
Ok((input, ()))
}
recognize(alt((left, right)))(input)
}
fn keyword(input: &str) -> IResult<&str, String> {
map(alt((tag("true"), tag("false"), tag("null"))), String::from)(input)
}
fn identifier_char(input: &str) -> IResult<&str, &str> {
not(linespace)(input)?;
recognize(none_of(r#"\/(){}<>;[]=,""#))(input)
}
fn linespace(input: &str) -> IResult<&str, ()> {
value((), alt((newline, whitespace, single_line_comment)))(input)
}
fn newline(input: &str) -> IResult<&str, ()> {
value(
(),
alt((
tag("\r\n"),
tag("\r"),
tag("\n"),
tag("\u{0085}"),
tag("\u{000C}"),
tag("\u{2028}"),
tag("\u{2029}"),
)),
)(input)
}
pub(crate) fn whitespace(input: &str) -> IResult<&str, ()> {
value(
(),
alt((
tag("\u{FEFF}"),
unicode_space,
recognize(multi_line_comment),
)),
)(input)
}
fn single_line_comment(input: &str) -> IResult<&str, ()> {
let (input, _) = tag("//")(input)?;
let (input, _) = many_till(value((), anychar), alt((newline, value((), eof))))(input)?;
Ok((input, ()))
}
fn multi_line_comment(input: &str) -> IResult<&str, &str> {
let (input, _) = tag("/*")(input)?;
commented_block(input)
}
fn commented_block(input: &str) -> IResult<&str, &str> {
alt((
tag("*/"),
terminated(
alt((multi_line_comment, take_until1("*/"), tag("*"), tag("/"))),
commented_block,
),
))(input)
}
fn unicode_space(input: &str) -> IResult<&str, &str> {
alt((
tag(" "),
tag("\t"),
tag("\u{00A0}"),
tag("\u{1680}"),
tag("\u{2000}"),
tag("\u{2001}"),
tag("\u{2002}"),
tag("\u{2003}"),
tag("\u{2004}"),
tag("\u{2005}"),
tag("\u{2006}"),
tag("\u{2007}"),
tag("\u{2008}"),
tag("\u{2009}"),
tag("\u{200A}"),
tag("\u{202F}"),
tag("\u{205F}"),
tag("\u{3000}"),
))(input)
}
fn string(input: &str) -> IResult<&str, String> {
delimited(
char('"'),
fold_many0(character, String::new, |mut acc, ch| {
acc.push(ch);
acc
}),
char('"'),
)(input)
}
fn character(input: &str) -> IResult<&str, char> {
alt((preceded(char('\\'), escape), none_of("\\\"")))(input)
}
fn escape_chars(input: char) -> Option<char> {
match input {
'"' => Some('"'),
'\\' => Some('\\'),
'/' => Some('/'),
'b' => Some('\u{08}'),
'f' => Some('\u{0C}'),
'n' => Some('\n'),
'r' => Some('\r'),
't' => Some('\t'),
_ => None,
}
}
fn escape(input: &str) -> IResult<&str, char> {
alt((
delimited(tag("u{"), unicode, char('}')),
map_opt(anychar, escape_chars),
))(input)
}
fn unicode(input: &str) -> IResult<&str, char> {
map_opt(
map_res(
take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()),
|hex| u32::from_str_radix(hex, 16),
),
std::char::from_u32,
)(input)
}
pub(crate) fn node_value(input: &str) -> IResult<&str, KdlValue> {
alt((
map(string, KdlValue::String),
map(raw_string, |s| KdlValue::String(s.into())),
number,
boolean,
value(KdlValue::Null, tag("null")),
))(input)
}
fn raw_string(input: &str) -> IResult<&str, &str> {
let (input, _) = char('r')(input)?;
let (input, hashes) = recognize(many0(char('#')))(input)?;
let (input, _) = char('"')(input)?;
let close = format!("\"{}", hashes);
let (input, string) = take_until(&close[..])(input)?;
let (input, _) = tag(&close[..])(input)?;
Ok((input, string))
}
fn number(input: &str) -> IResult<&str, KdlValue> {
alt((
map(hexadecimal, KdlValue::Int),
map(octal, KdlValue::Int),
map(binary, KdlValue::Int),
map(float, KdlValue::Float),
map(integer, KdlValue::Int),
))(input)
}
fn sign(input: &str) -> IResult<&str, i64> {
let (input, sign) = opt(alt((char('+'), char('-'))))(input)?;
let mult = if let Some(sign) = sign {
if sign == '+' {
1
} else {
-1
}
} else {
1
};
Ok((input, mult))
}
fn hexadecimal(input: &str) -> IResult<&str, i64> {
let (input, sign) = sign(input)?;
map_res(
preceded(
alt((tag("0x"), tag("0X"))),
recognize(many1(terminated(
one_of("0123456789abcdefABCDEF"),
many0(char('_')),
))),
),
move |out: &str| i64::from_str_radix(&str::replace(out, "_", ""), 16).map(|x| x * sign),
)(input)
}
fn octal(input: &str) -> IResult<&str, i64> {
let (input, sign) = sign(input)?;
map_res(
preceded(
alt((tag("0o"), tag("0O"))),
recognize(many1(terminated(one_of("01234567"), many0(char('_'))))),
),
move |out: &str| i64::from_str_radix(&str::replace(out, "_", ""), 8).map(|x| x * sign),
)(input)
}
fn binary(input: &str) -> IResult<&str, i64> {
let (input, sign) = sign(input)?;
map_res(
preceded(
alt((tag("0b"), tag("0B"))),
recognize(many1(terminated(one_of("01"), many0(char('_'))))),
),
move |out: &str| i64::from_str_radix(&str::replace(out, "_", ""), 2).map(|x| x * sign),
)(input)
}
fn boolean(input: &str) -> IResult<&str, KdlValue> {
alt((
value(KdlValue::Boolean(true), tag("true")),
value(KdlValue::Boolean(false), tag("false")),
))(input)
}
fn float(input: &str) -> IResult<&str, f64> {
map_res(
alt((
recognize(tuple((
integer,
opt(preceded(char('.'), integer)),
one_of("eE"),
opt(one_of("+-")),
integer,
))),
recognize(tuple((integer, char('.'), integer))),
)),
|x| str::replace(x, "_", "").parse::<f64>(),
)(input)
}
fn integer(input: &str) -> IResult<&str, i64> {
let (input, sign) = sign(input)?;
map_res(
recognize(many1(terminated(one_of("0123456789"), many0(char('_'))))),
move |out: &str| {
str::replace(out, "_", "")
.parse::<i64>()
.map(move |x| x * sign)
},
)(input)
}