#![allow(missing_docs)]
use std::{collections::BTreeMap, result::Result as StdResult};
use pest::{Parser, iterators::Pair};
use pest_derive::Parser;
use super::{Error, Result};
use crate::{Binary, Timestamp, Value};
pub(super) type PestError = pest::error::Error<Rule>;
#[derive(Parser)]
#[grammar = "parser/grammar.pest"]
pub(super) struct JasnParser;
pub(super) fn parse_impl(input: &str) -> Result<Value> {
let mut pairs = JasnParser::parse(Rule::jasn, input)?;
let pair = pairs.next().unwrap(); let inner = pair.into_inner().next().unwrap(); parse_value(inner)
}
fn parse_value(pair: Pair<Rule>) -> Result<Value> {
let rule = if pair.as_rule() == Rule::value {
pair.into_inner().next().unwrap()
} else {
pair
};
match rule.as_rule() {
Rule::null => Ok(Value::Null),
Rule::boolean => Ok(Value::Bool(rule.as_str() == "true")),
Rule::integer => parse_int(rule),
Rule::float => parse_float(rule),
Rule::string => parse_string(rule),
Rule::binary => parse_binary(rule),
Rule::timestamp => parse_timestamp(rule),
Rule::list => parse_list(rule),
Rule::map => parse_map(rule),
_ => unreachable!("Unexpected rule: {:?}", rule.as_rule()),
}
}
fn parse_int(pair: Pair<Rule>) -> Result<Value> {
let s = pair.as_str();
let normalized = s.replace('_', "");
let normalized = normalized.strip_prefix('+').unwrap_or(&normalized);
let (is_negative, unsigned_str) = match normalized.strip_prefix('-') {
Some(rest) => (true, rest),
None => (false, normalized),
};
let uint = match unsigned_str {
s if s.starts_with("0x") || s.starts_with("0X") => parse_int_radix(&s[2..], 16)?,
s if s.starts_with("0b") || s.starts_with("0B") => parse_int_radix(&s[2..], 2)?,
s if s.starts_with("0o") || s.starts_with("0O") => parse_int_radix(&s[2..], 8)?,
_ => return Ok(Value::Int(normalized.parse::<i64>()?)),
};
let int = if is_negative { -uint } else { uint };
Ok(Value::Int(int))
}
fn parse_int_radix(s: &str, radix: u32) -> Result<i64> {
i64::from_str_radix(s, radix).map_err(Into::into)
}
fn parse_float(pair: Pair<Rule>) -> Result<Value> {
let s = pair.as_str();
let value = match s {
"inf" | "+inf" => f64::INFINITY,
"-inf" => f64::NEG_INFINITY,
"nan" | "+nan" | "-nan" => f64::NAN,
_ => s.parse::<f64>()?,
};
Ok(Value::Float(value))
}
fn parse_string(pair: Pair<Rule>) -> Result<Value> {
let mut inner = pair.into_inner();
let quoted = inner.next().unwrap(); let content_pair = quoted.into_inner().next().unwrap(); let content = content_pair.as_str();
let mut result = String::with_capacity(content.len());
let mut chars = content.chars();
while let Some(ch) = chars.next() {
if ch == '\\' {
match chars.next() {
Some('"') => result.push('"'),
Some('\'') => result.push('\''),
Some('\\') => result.push('\\'),
Some('/') => result.push('/'),
Some('b') => result.push('\u{0008}'),
Some('f') => result.push('\u{000C}'),
Some('n') => result.push('\n'),
Some('r') => result.push('\r'),
Some('t') => result.push('\t'),
Some('u') => result.push(parse_unicode_escape(&mut chars)?),
Some(c) => return Err(Error::InvalidEscapeChar(c)),
None => return Err(Error::InvalidEscapeChar('\\')),
}
} else {
result.push(ch);
}
}
Ok(Value::String(result))
}
fn parse_unicode_escape(chars: &mut std::str::Chars) -> Result<char> {
let hex: String = chars.take(4).collect();
if hex.len() < 4 {
return Err(Error::InvalidUnicodeEscape(hex));
}
let code =
u32::from_str_radix(&hex, 16).map_err(|_| Error::InvalidUnicodeEscape(hex.clone()))?;
if (0xD800..=0xDBFF).contains(&code) {
let saved_chars = chars.clone();
if chars.next() == Some('\\') && chars.next() == Some('u') {
let low_hex: String = chars.take(4).collect();
if low_hex.len() == 4
&& let Ok(low_code) = u32::from_str_radix(&low_hex, 16)
{
if (0xDC00..=0xDFFF).contains(&low_code) {
let high = code - 0xD800;
let low = low_code - 0xDC00;
let codepoint = 0x10000 + (high << 10) + low;
return char::from_u32(codepoint)
.ok_or(Error::InvalidUnicodeCodepoint(codepoint));
}
}
}
*chars = saved_chars;
}
char::from_u32(code).ok_or(Error::InvalidUnicodeCodepoint(code))
}
fn parse_binary(pair: Pair<Rule>) -> Result<Value> {
let s = pair.as_str();
let bytes = match s {
s if s.starts_with("b64\"") => {
let content = &s[4..s.len() - 1]; parse_binary_b64(content)?
}
s if s.starts_with("hex\"") => {
let content = &s[4..s.len() - 1]; parse_binary_hex(content)?
}
_ => {
let encoding = s.split('"').next().unwrap_or(s);
return Err(Error::UnknownBinaryEncoding(encoding.to_string()));
}
};
Ok(Value::Binary(Binary(bytes)))
}
fn parse_binary_b64(content: &str) -> Result<Vec<u8>> {
Ok(base64::Engine::decode(
&base64::engine::general_purpose::STANDARD,
content,
)?)
}
fn parse_binary_hex(content: &str) -> Result<Vec<u8>> {
if !content.len().is_multiple_of(2) {
return Err(Error::OddHexDigits);
}
(0..content.len())
.step_by(2)
.map(|i| u8::from_str_radix(&content[i..i + 2], 16))
.collect::<StdResult<Vec<u8>, _>>()
.map_err(Into::into)
}
fn parse_timestamp(pair: Pair<Rule>) -> Result<Value> {
let s = pair.as_str();
let content = &s[3..s.len() - 1];
let dt = Timestamp::parse(content, &time::format_description::well_known::Rfc3339)
.map_err(|e| Error::InvalidTimestamp(content.to_string(), e.to_string()))?;
Ok(Value::Timestamp(dt))
}
fn parse_list(pair: Pair<Rule>) -> Result<Value> {
let values = pair
.into_inner()
.map(parse_value)
.collect::<StdResult<Vec<_>, _>>()?;
Ok(Value::List(values))
}
fn parse_map(pair: Pair<Rule>) -> Result<Value> {
let mut map = BTreeMap::new();
for member in pair.into_inner() {
let mut inner = member.into_inner();
let key_pair = inner.next().unwrap();
let value_pair = inner.next().unwrap();
let key = parse_map_key(key_pair)?;
let value = parse_value(value_pair)?;
if map.contains_key(&key) {
return Err(Error::DuplicateKey(key));
}
map.insert(key, value);
}
Ok(Value::Map(map))
}
fn parse_map_key(pair: Pair<Rule>) -> Result<String> {
match pair.as_rule() {
Rule::key => {
let actual_key = pair.into_inner().next().unwrap();
parse_map_key(actual_key)
}
Rule::string => {
if let Value::String(s) = parse_string(pair)? {
Ok(s)
} else {
unreachable!("parse_string should always return Value::String")
}
}
Rule::identifier => Ok(pair.as_str().to_string()),
_ => unreachable!("Unexpected rule for map key: {:?}", pair.as_rule()),
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[test]
fn test_parse_null() {
assert_eq!(parse_impl("null").unwrap(), Value::Null);
}
#[rstest]
#[case("true", true)]
#[case("false", false)]
fn test_parse_bool(#[case] input: &str, #[case] expected: bool) {
assert_eq!(parse_impl(input).unwrap(), Value::Bool(expected));
}
#[rstest]
#[case("42", 42)]
#[case("-123", -123)]
#[case("0xFF", 255)]
#[case("0b1010", 10)]
#[case("0o755", 493)]
fn test_parse_integer(#[case] input: &str, #[case] expected: i64) {
assert_eq!(parse_impl(input).unwrap(), Value::Int(expected));
}
#[rstest]
#[case("1__000", 1000)]
#[case("1___000", 1000)]
#[case("1_000_000", 1000000)]
#[case("0xFF__FF", 0xFFFF)]
#[case("0b1111__0000", 0b11110000)]
#[case("0o777__000", 0o777000)]
fn test_parse_integer_multiple_underscores(#[case] input: &str, #[case] expected: i64) {
assert_eq!(parse_impl(input).unwrap(), Value::Int(expected));
}
#[rstest]
#[case("2.5", 2.5)]
#[case("1e10", 1e10)]
fn test_parse_float_numbers(#[case] input: &str, #[case] expected: f64) {
assert_eq!(parse_impl(input).unwrap(), Value::Float(expected));
}
#[rstest]
#[case("inf", true, true)] #[case("-inf", true, false)] fn test_parse_float_infinity(#[case] input: &str, #[case] is_inf: bool, #[case] is_pos: bool) {
match parse_impl(input).unwrap() {
Value::Float(f) => {
assert_eq!(f.is_infinite(), is_inf);
assert_eq!(f.is_sign_positive(), is_pos);
}
_ => panic!("Expected Float value"),
}
}
#[test]
fn test_parse_float_nan() {
assert!(matches!(parse_impl("nan").unwrap(), Value::Float(f) if f.is_nan()));
}
#[rstest]
#[case("\"hello\"", "hello")]
#[case("'world'", "world")]
fn test_parse_string(#[case] input: &str, #[case] expected: &str) {
assert_eq!(
parse_impl(input).unwrap(),
Value::String(expected.to_string())
);
}
#[rstest]
#[case(r#""a\nb""#, "a\nb")]
#[case(r#""a\tb""#, "a\tb")]
#[case(r#""a\rb""#, "a\rb")]
#[case(r#""a\\b""#, "a\\b")]
#[case(r#""a\/b""#, "a/b")]
#[case(r#""a\"b""#, "a\"b")]
#[case(r#"'a\'b'"#, "a'b")]
#[case(r#""a\bb""#, "a\u{0008}b")]
#[case(r#""a\fb""#, "a\u{000C}b")]
#[case(r#""\u0041""#, "A")]
#[case(r#""\u03B1""#, "α")]
#[case(r#""\u4E2D\u6587""#, "中文")]
#[case(r#""Hello\u0020World""#, "Hello World")]
fn test_parse_string_escapes(#[case] input: &str, #[case] expected: &str) {
assert_eq!(
parse_impl(input).unwrap(),
Value::String(expected.to_string())
);
}
#[rstest]
#[case(r#""\ud83d\ude00""#, "😀")] #[case(r#""\ud83c\udf0d""#, "🌍")] #[case(r#""\ud83d\udc4d""#, "👍")] #[case(r#""\ud834\udd1e""#, "𝄞")]
#[case(r#""Hello \ud83d\ude00 World""#, "Hello 😀 World")]
#[case(r#""\ud83d\ude00\ud83d\ude01\ud83d\ude02""#, "😀😁😂")]
fn test_parse_surrogate_pairs(#[case] input: &str, #[case] expected: &str) {
assert_eq!(
parse_impl(input).unwrap(),
Value::String(expected.to_string())
);
}
#[test]
fn test_parse_invalid_surrogate_pairs() {
let result = parse_impl(r#""\ud83d""#);
assert!(result.is_err());
let result = parse_impl(r#""\ud83dA""#);
assert!(result.is_err());
let result = parse_impl(r#""\ud83d\ud83d""#);
assert!(result.is_err());
let result = parse_impl(r#""\ude00""#);
assert!(result.is_err());
}
#[rstest]
#[case("hex\"48656c6c6f\"", b"Hello")]
#[case("b64\"SGVsbG8=\"", b"Hello")]
#[case("hex\"\"", b"")]
#[case("b64\"\"", b"")]
fn test_parse_binary(#[case] input: &str, #[case] expected: &[u8]) {
let result = parse_impl(input).unwrap();
assert!(matches!(result, Value::Binary(ref b) if b.0 == expected));
}
#[rstest]
#[case("ts\"2024-01-15T12:30:45Z\"")]
#[case("ts\"2024-01-15T12:30:45.123Z\"")]
#[case("ts\"2024-01-15T12:30:45.123456789Z\"")]
#[case("ts\"2024-01-15T12:30:45+00:00\"")]
#[case("ts\"2024-01-15T12:30:45-05:00\"")]
#[case("ts\"2024-01-15T12:30:45.5Z\"")]
#[case("ts\"2024-01-15T12:30:45.1234567Z\"")]
#[case("ts\"2009-02-13T23:31:30+00:00\"")]
fn test_parse_timestamp(#[case] input: &str) {
let result = parse_impl(input).unwrap();
assert!(matches!(result, Value::Timestamp(_)));
}
#[test]
fn test_parse_timestamp_values() {
let result = parse_impl("ts\"2009-02-13T23:31:30Z\"").unwrap();
if let Value::Timestamp(dt) = result {
assert_eq!(dt.unix_timestamp(), 1234567890);
} else {
panic!("Expected timestamp value");
}
let result = parse_impl("ts\"2009-02-13T23:31:30.5Z\"").unwrap();
assert!(matches!(result, Value::Timestamp(_)));
let result = parse_impl("ts\"2024-01-15T12:30:45-05:00\"").unwrap();
assert!(matches!(result, Value::Timestamp(_)));
}
#[test]
fn test_parse_list() {
let result = parse_impl("[1, 2, 3]").unwrap();
assert!(matches!(result, Value::List(ref v) if v.len() == 3));
}
#[test]
fn test_parse_map() {
let result = parse_impl("{\"key\": \"value\"}").unwrap();
assert!(matches!(result, Value::Map(_)));
}
#[rstest]
#[case("{null: 1}", "null")]
#[case("{true: 1}", "true")]
#[case("{false: 1}", "false")]
#[case("{inf: 1}", "inf")]
#[case("{nan: 1}", "nan")]
fn test_parse_keywords_as_map_keys(#[case] input: &str, #[case] expected_key: &str) {
let result = parse_impl(input).unwrap();
match result {
Value::Map(map) => {
assert!(
map.contains_key(expected_key),
"Map should contain key '{}'",
expected_key
);
assert_eq!(map.len(), 1);
}
_ => panic!("Expected Map value"),
}
}
#[rstest]
#[case(r#"{a: 1, a: 2}"#, "a")]
#[case(r#"{"key": 1, "key": 2}"#, "key")]
#[case(r#"{a: 1, "a": 2}"#, "a")]
#[case(r#"{null: 1, null: 2}"#, "null")]
fn test_parse_duplicate_keys_rejected(#[case] input: &str, #[case] duplicate_key: &str) {
let result = parse_impl(input);
assert!(
result.is_err(),
"Expected error for duplicate key '{}'",
duplicate_key
);
match result {
Err(Error::DuplicateKey(key)) => {
assert_eq!(key, duplicate_key, "Error should mention the duplicate key");
}
_ => panic!("Expected DuplicateKey error, got: {:?}", result),
}
}
#[test]
fn test_parse_map_allows_different_keys() {
let result = parse_impl(r#"{a: 1, b: 2, c: 3}"#).unwrap();
match result {
Value::Map(map) => {
assert_eq!(map.len(), 3);
assert!(map.contains_key("a"));
assert!(map.contains_key("b"));
assert!(map.contains_key("c"));
}
_ => panic!("Expected Map value"),
}
}
}