use crate::{RsonValue, RsonError, RsonResult};
use indexmap::IndexMap;
use nom::{
branch::alt,
bytes::complete::{tag, take_while, take_while1},
character::complete::{
char, multispace1, none_of,
alpha1, digit1
},
combinator::{map, map_res, opt, recognize, value},
multi::separated_list0,
number::complete::recognize_float,
sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
IResult,
};
#[cfg(not(feature = "std"))]
use alloc::{
string::{String, ToString},
vec::Vec,
};
pub fn parse_rson(input: &str) -> RsonResult<RsonValue> {
let result = terminated(
preceded(ws_and_comments, parse_value),
preceded(ws_and_comments, nom::combinator::eof)
)(input);
match result {
Ok((_, value)) => Ok(value),
Err(e) => Err(RsonError::from(e)),
}
}
pub fn parse_rson_value(input: &str) -> RsonResult<RsonValue> {
let result = preceded(ws_and_comments, parse_value)(input);
match result {
Ok((_, value)) => Ok(value),
Err(e) => Err(RsonError::from(e)),
}
}
fn parse_value(input: &str) -> IResult<&str, RsonValue> {
alt((
parse_null,
parse_bool,
parse_option,
parse_enum,
parse_struct,
parse_tuple,
parse_number,
parse_char,
parse_string,
parse_array,
parse_map,
))(input)
}
fn ws_and_comments(input: &str) -> IResult<&str, ()> {
value(
(),
nom::multi::many0(alt((
value((), multispace1),
parse_line_comment,
parse_block_comment,
)))
)(input)
}
fn parse_line_comment(input: &str) -> IResult<&str, ()> {
value(
(),
tuple((
tag("//"),
take_while(|c| c != '\n'),
opt(char('\n')),
))
)(input)
}
fn parse_block_comment(input: &str) -> IResult<&str, ()> {
value(
(),
tuple((
tag("/*"),
take_while(|c| c != '*'),
tag("*/"),
))
)(input)
}
fn parse_null(input: &str) -> IResult<&str, RsonValue> {
value(RsonValue::Null, tag("null"))(input)
}
fn parse_bool(input: &str) -> IResult<&str, RsonValue> {
alt((
value(RsonValue::Bool(true), tag("true")),
value(RsonValue::Bool(false), tag("false")),
))(input)
}
fn parse_option(input: &str) -> IResult<&str, RsonValue> {
alt((
map(
preceded(
tag("Some"),
delimited(
preceded(ws_and_comments, char('(')),
preceded(ws_and_comments, parse_value),
preceded(ws_and_comments, char(')')),
)
),
|v| RsonValue::Option(Some(Box::new(v)))
),
value(RsonValue::Option(None), tag("None")),
))(input)
}
fn parse_number(input: &str) -> IResult<&str, RsonValue> {
let float_result: IResult<&str, &str> = recognize_float(input);
if let Ok((remaining, number_str)) = float_result {
if number_str.contains('.') || number_str.contains('e') || number_str.contains('E') {
if let Ok(f) = number_str.parse::<f64>() {
return Ok((remaining, RsonValue::Float(f)));
}
}
}
map_res(
recognize(pair(opt(char('-')), digit1)),
|s: &str| s.parse::<i64>().map(RsonValue::Int)
)(input)
}
fn parse_char(input: &str) -> IResult<&str, RsonValue> {
map(
delimited(char('\''), parse_char_content, char('\'')),
RsonValue::Char
)(input)
}
fn parse_char_content(input: &str) -> IResult<&str, char> {
alt((
none_of("'\\"),
preceded(char('\\'), parse_escape_char),
))(input)
}
fn parse_escape_char(input: &str) -> IResult<&str, char> {
alt((
value('\n', char('n')),
value('\r', char('r')),
value('\t', char('t')),
value('\\', char('\\')),
value('\'', char('\'')),
value('\"', char('\"')),
value('\0', char('0')),
parse_unicode_escape,
))(input)
}
fn parse_unicode_escape(input: &str) -> IResult<&str, char> {
map_res(
preceded(char('u'), take_while1(|c: char| c.is_ascii_hexdigit())),
|hex_str: &str| {
u32::from_str_radix(hex_str, 16)
.ok()
.and_then(char::from_u32)
.ok_or("Invalid Unicode codepoint")
}
)(input)
}
fn parse_string(input: &str) -> IResult<&str, RsonValue> {
map(
delimited(char('"'), parse_string_content, char('"')),
|s| RsonValue::String(s.to_string())
)(input)
}
fn parse_string_content(input: &str) -> IResult<&str, String> {
let mut result = String::new();
let mut remaining = input;
while !remaining.is_empty() {
if remaining.starts_with('"') {
break;
} else if remaining.starts_with('\\') {
let (rest, escaped_char) = preceded(char('\\'), parse_escape_char)(remaining)?;
result.push(escaped_char);
remaining = rest;
} else {
let (rest, ch) = nom::character::complete::anychar(remaining)?;
result.push(ch);
remaining = rest;
}
}
Ok((remaining, result))
}
fn parse_identifier(input: &str) -> IResult<&str, String> {
map(
recognize(pair(
alt((alpha1, tag("_"))),
take_while(|c: char| c.is_alphanumeric() || c == '_')
)),
|s: &str| s.to_string()
)(input)
}
fn parse_array(input: &str) -> IResult<&str, RsonValue> {
map(
delimited(
char('['),
terminated(
separated_list0(
preceded(ws_and_comments, char(',')),
preceded(ws_and_comments, parse_value)
),
opt(preceded(ws_and_comments, char(',')))
),
preceded(ws_and_comments, char(']'))
),
RsonValue::Array
)(input)
}
fn parse_map(input: &str) -> IResult<&str, RsonValue> {
map(
delimited(
char('{'),
terminated(
separated_list0(
preceded(ws_and_comments, char(',')),
preceded(
ws_and_comments,
separated_pair(
parse_map_key,
preceded(ws_and_comments, char(':')),
preceded(ws_and_comments, parse_value)
)
)
),
opt(preceded(ws_and_comments, char(',')))
),
preceded(ws_and_comments, char('}'))
),
|pairs| {
let mut map = IndexMap::new();
for (key, value) in pairs {
map.insert(key, value);
}
RsonValue::Map(map)
}
)(input)
}
fn parse_map_key(input: &str) -> IResult<&str, String> {
alt((
parse_identifier,
map(
delimited(char('"'), parse_string_content, char('"')),
|s| s
),
))(input)
}
fn parse_struct(input: &str) -> IResult<&str, RsonValue> {
map(
pair(
parse_identifier,
delimited(
char('('),
terminated(
separated_list0(
preceded(ws_and_comments, char(',')),
preceded(
ws_and_comments,
separated_pair(
parse_identifier,
preceded(ws_and_comments, char(':')),
preceded(ws_and_comments, parse_value)
)
)
),
opt(preceded(ws_and_comments, char(',')))
),
preceded(ws_and_comments, char(')'))
)
),
|(name, fields)| {
let mut field_map = IndexMap::new();
for (field_name, value) in fields {
field_map.insert(field_name, value);
}
RsonValue::Struct {
name,
fields: field_map,
}
}
)(input)
}
fn parse_tuple(input: &str) -> IResult<&str, RsonValue> {
let lookahead = preceded(
ws_and_comments,
alt((
value(false, tuple((parse_identifier, preceded(ws_and_comments, char(':'))))),
value(true, take_while(|_| true))
))
);
if let Ok((_, false)) = preceded(char('('), lookahead)(input) {
return Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Alt)));
}
map(
delimited(
char('('),
terminated(
separated_list0(
preceded(ws_and_comments, char(',')),
preceded(ws_and_comments, parse_value)
),
opt(preceded(ws_and_comments, char(',')))
),
preceded(ws_and_comments, char(')'))
),
RsonValue::Tuple
)(input)
}
fn parse_enum(input: &str) -> IResult<&str, RsonValue> {
map(
tuple((
parse_identifier,
preceded(tag("::"), parse_identifier),
opt(delimited(
char('('),
preceded(ws_and_comments, parse_value),
preceded(ws_and_comments, char(')'))
))
)),
|(enum_name, variant, value)| RsonValue::Enum {
name: enum_name,
variant,
value: value.map(Box::new),
}
)(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_null() {
assert_eq!(parse_rson("null").unwrap(), RsonValue::Null);
}
#[test]
fn test_parse_bool() {
assert_eq!(parse_rson("true").unwrap(), RsonValue::Bool(true));
assert_eq!(parse_rson("false").unwrap(), RsonValue::Bool(false));
}
#[test]
fn test_parse_numbers() {
assert_eq!(parse_rson("42").unwrap(), RsonValue::Int(42));
assert_eq!(parse_rson("-17").unwrap(), RsonValue::Int(-17));
assert_eq!(parse_rson("3.14").unwrap(), RsonValue::Float(3.14));
assert_eq!(parse_rson("-2.5").unwrap(), RsonValue::Float(-2.5));
}
#[test]
fn test_parse_string() {
assert_eq!(parse_rson(r#""hello""#).unwrap(), RsonValue::String("hello".to_string()));
assert_eq!(parse_rson(r#""hello\nworld""#).unwrap(), RsonValue::String("hello\nworld".to_string()));
}
#[test]
fn test_parse_char() {
assert_eq!(parse_rson("'a'").unwrap(), RsonValue::Char('a'));
assert_eq!(parse_rson(r"'\n'").unwrap(), RsonValue::Char('\n'));
}
#[test]
fn test_parse_array() {
assert_eq!(
parse_rson("[1, 2, 3]").unwrap(),
RsonValue::Array(vec![
RsonValue::Int(1),
RsonValue::Int(2),
RsonValue::Int(3),
])
);
assert_eq!(
parse_rson("[1, 2, 3,]").unwrap(),
RsonValue::Array(vec![
RsonValue::Int(1),
RsonValue::Int(2),
RsonValue::Int(3),
])
);
}
#[test]
fn test_parse_map() {
let mut expected = IndexMap::new();
expected.insert("name".to_string(), RsonValue::String("Alice".to_string()));
expected.insert("age".to_string(), RsonValue::Int(30));
assert_eq!(
parse_rson(r#"{ name: "Alice", age: 30 }"#).unwrap(),
RsonValue::Map(expected)
);
}
#[test]
fn test_parse_struct() {
let mut expected = IndexMap::new();
expected.insert("x".to_string(), RsonValue::Int(10));
expected.insert("y".to_string(), RsonValue::Int(20));
assert_eq!(
parse_rson("Point(x: 10, y: 20)").unwrap(),
RsonValue::Struct {
name: "Point".to_string(),
fields: expected,
}
);
}
#[test]
fn test_parse_tuple() {
assert_eq!(
parse_rson("(1, 2, 3)").unwrap(),
RsonValue::Tuple(vec![
RsonValue::Int(1),
RsonValue::Int(2),
RsonValue::Int(3),
])
);
}
#[test]
fn test_parse_enum() {
assert_eq!(
parse_rson("Color::Red").unwrap(),
RsonValue::Enum {
name: "Color".to_string(),
variant: "Red".to_string(),
value: None,
}
);
assert_eq!(
parse_rson(r#"Result::Ok("success")"#).unwrap(),
RsonValue::Enum {
name: "Result".to_string(),
variant: "Ok".to_string(),
value: Some(Box::new(RsonValue::String("success".to_string()))),
}
);
}
#[test]
fn test_parse_option() {
assert_eq!(
parse_rson("None").unwrap(),
RsonValue::Option(None)
);
assert_eq!(
parse_rson("Some(42)").unwrap(),
RsonValue::Option(Some(Box::new(RsonValue::Int(42))))
);
}
#[test]
fn test_parse_with_comments() {
let input = r#"
// This is a user struct
User(
id: 1, // user ID
name: "Alice", /* user name */
active: true,
)
"#;
let mut expected = IndexMap::new();
expected.insert("id".to_string(), RsonValue::Int(1));
expected.insert("name".to_string(), RsonValue::String("Alice".to_string()));
expected.insert("active".to_string(), RsonValue::Bool(true));
assert_eq!(
parse_rson(input).unwrap(),
RsonValue::Struct {
name: "User".to_string(),
fields: expected,
}
);
}
#[test]
fn test_json_compatibility() {
let json_input = r#"{
"name": "Alice",
"age": 30,
"active": true,
"score": 95.5,
"tags": ["admin", "user"],
"metadata": null
}"#;
let result = parse_rson(json_input);
assert!(result.is_ok());
let value = result.unwrap();
match value {
RsonValue::Map(_) => {}
_ => panic!("Expected map"),
}
}
}