use crate::error::{Error, ErrorKind, Span};
use crate::value::{ObjectMap, Value};
use super::classify::{fast_plain_decimal_i64, is_float_literal, try_parse_integer};
use super::insert::insert_value;
const MAX_INLINE_DEPTH: usize = 128;
pub(crate) fn parse_inline_object(
input: &str,
line_num: usize,
span: Span,
) -> Result<Value, Error> {
parse_inline_object_inner(input, line_num, span, 0)
}
pub(crate) fn parse_inline_array(input: &str, line_num: usize, span: Span) -> Result<Value, Error> {
parse_inline_array_inner(input, line_num, span, 0)
}
fn parse_inline_object_inner(
input: &str,
line_num: usize,
span: Span,
depth: usize,
) -> Result<Value, Error> {
if depth >= MAX_INLINE_DEPTH {
return Err(malformed(
line_num,
span,
"nesting depth exceeds limit (128)",
));
}
debug_assert!(input.starts_with('{') && input.ends_with('}'));
let inner = &input[1..input.len() - 1];
if inner.trim().is_empty() {
return Ok(Value::Object(ObjectMap::default()));
}
let segments = split_top_level(inner, line_num, span)?;
let mut map = ObjectMap::default();
let n = segments.len();
for (i, seg) in segments.into_iter().enumerate() {
let trimmed = seg.trim();
if trimmed.is_empty() {
if i == n - 1 {
break;
}
return Err(malformed(
line_num,
span,
"empty pair segment (leading comma, double comma, or missing pair)",
));
}
let colon_pos = find_unescaped_colon(trimmed);
let colon_pos = match colon_pos {
Some(p) => p,
None => {
return Err(malformed(
line_num,
span,
&format!("inline object pair missing ':' separator in '{}'", trimmed),
));
}
};
let raw_key = &trimmed[..colon_pos];
let after_colon = &trimmed[colon_pos + 1..];
let (is_raw, value_body) = if let Some(stripped) = after_colon.strip_prefix(':') {
(true, stripped)
} else {
(false, after_colon)
};
let key = raw_key.trim();
if key.is_empty() {
return Err(Error::Structured(ErrorKind::EmptyKey {
line: line_num as u32,
span,
}));
}
let value = if is_raw {
let processed = process_escapes(value_body.trim(), line_num, span)?;
Value::String(processed.into())
} else {
parse_inline_value(value_body, line_num, span, depth)?
};
insert_value(&mut map, key, value, line_num, span)?;
}
Ok(Value::Object(map))
}
fn parse_inline_array_inner(
input: &str,
line_num: usize,
span: Span,
depth: usize,
) -> Result<Value, Error> {
if depth >= MAX_INLINE_DEPTH {
return Err(malformed(
line_num,
span,
"nesting depth exceeds limit (128)",
));
}
debug_assert!(input.starts_with('[') && input.ends_with(']'));
let inner = &input[1..input.len() - 1];
if inner.trim().is_empty() {
return Ok(Value::Array(Vec::new()));
}
let segments = split_top_level(inner, line_num, span)?;
let mut items: Vec<Value> = Vec::new();
let n = segments.len();
for (i, seg) in segments.into_iter().enumerate() {
let trimmed = seg.trim();
if trimmed.is_empty() {
if i == n - 1 {
break;
}
return Err(malformed(
line_num,
span,
"empty inline-array item (leading comma, double comma, or empty position)",
));
}
if let Some(rest) = trimmed.strip_prefix("::") {
let processed = process_escapes(rest.trim(), line_num, span)?;
items.push(Value::String(processed.into()));
continue;
}
let value = parse_inline_value_raw(trimmed, line_num, span, depth)?;
items.push(value);
}
Ok(Value::Array(items))
}
fn parse_inline_value(
body: &str,
line_num: usize,
span: Span,
depth: usize,
) -> Result<Value, Error> {
let trimmed = body.trim();
if trimmed.is_empty() {
return Ok(Value::String("".into()));
}
parse_inline_value_raw(trimmed, line_num, span, depth)
}
fn parse_inline_value_raw(
trimmed: &str,
line_num: usize,
span: Span,
depth: usize,
) -> Result<Value, Error> {
let first_byte = trimmed.as_bytes()[0];
if first_byte == b'{' {
if let Some(close) = find_matching_close(trimmed, b'{', b'}') {
if close == trimmed.len() - 1 {
let inner = &trimmed[1..trimmed.len() - 1];
if inner.trim().is_empty() {
return Ok(Value::Object(ObjectMap::default()));
}
return parse_inline_object_inner(trimmed, line_num, span, depth + 1);
}
}
return Err(Error::Structured(ErrorKind::UnterminatedInlineCompound {
line: line_num as u32,
span,
}));
}
if first_byte == b'[' {
if let Some(close) = find_matching_close(trimmed, b'[', b']') {
if close == trimmed.len() - 1 {
let inner = &trimmed[1..trimmed.len() - 1];
if inner.trim().is_empty() {
return Ok(Value::Array(Vec::new()));
}
return parse_inline_array_inner(trimmed, line_num, span, depth + 1);
}
}
return Err(Error::Structured(ErrorKind::UnterminatedInlineCompound {
line: line_num as u32,
span,
}));
}
let processed = process_escapes(trimmed, line_num, span)?;
let body = processed.trim();
classify_inline_scalar(body, line_num, span)
}
fn classify_inline_scalar(body: &str, _line_num: usize, _span: Span) -> Result<Value, Error> {
if body.is_empty() {
return Ok(Value::String("".into()));
}
match body {
"null" => return Ok(Value::Null),
"true" => return Ok(Value::Bool(true)),
"false" => return Ok(Value::Bool(false)),
_ => {}
}
if let Some(_val) = fast_plain_decimal_i64(body) {
return Ok(Value::Integer(body.into()));
}
if let Some(val) = try_parse_integer(body) {
let mut buf = itoa::Buffer::new();
let canonical = buf.format(val);
return Ok(Value::Integer(canonical.into()));
}
if is_float_literal(body) {
if let Some(val) = parse_float_value(body) {
let mut buf = ryu::Buffer::new();
let canonical = buf.format(val);
if canonical == body {
return Ok(Value::Float(body.into()));
}
return Ok(Value::Float(canonical.into()));
}
}
Ok(Value::String(body.into()))
}
fn parse_float_value(s: &str) -> Option<f64> {
if !s.as_bytes().contains(&b'_') {
let val: f64 = s.parse().ok()?;
if val.is_nan() || val.is_infinite() {
return None;
}
return Some(val);
}
let cleaned: String = s.chars().filter(|&c| c != '_').collect();
let val: f64 = cleaned.parse().ok()?;
if val.is_nan() || val.is_infinite() {
return None;
}
Some(val)
}
pub(crate) fn process_escapes(input: &str, line_num: usize, span: Span) -> Result<String, Error> {
if !input.as_bytes().contains(&b'\\') {
return Ok(input.to_string());
}
let bytes = input.as_bytes();
let mut out = String::with_capacity(input.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'\\' {
if i + 1 >= bytes.len() {
return Err(Error::Structured(ErrorKind::BadEscapeSequence {
line: line_num as u32,
span,
sequence: "\\<end-of-line>".to_string(),
}));
}
let next = bytes[i + 1];
match next {
b'\\' => out.push('\\'),
b',' => out.push(','),
b'}' => out.push('}'),
b']' => out.push(']'),
b'{' => out.push('{'),
b'[' => out.push('['),
b'n' => out.push('\n'),
b'r' => out.push('\r'),
_ => {
let seq = if next < 0x80 {
format!("\\{}", next as char)
} else {
format!("\\<0x{:02X}>", next)
};
return Err(Error::Structured(ErrorKind::BadEscapeSequence {
line: line_num as u32,
span,
sequence: seq,
}));
}
}
i += 2;
} else {
let ch = input[i..].chars().next().unwrap();
out.push(ch);
i += ch.len_utf8();
}
}
Ok(out)
}
fn split_top_level<'a>(
input: &'a str,
_line_num: usize,
_span: Span,
) -> Result<Vec<&'a str>, Error> {
let bytes = input.as_bytes();
let mut segments: Vec<&'a str> = Vec::new();
let mut start = 0;
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'\\' => {
i += 2;
continue;
}
b'{' | b'[' => {
let open = bytes[i];
let close = if open == b'{' { b'}' } else { b']' };
if let Some(close_pos) = find_matching_close(&input[i..], open, close) {
i += close_pos + 1;
continue;
}
}
b',' => {
segments.push(&input[start..i]);
start = i + 1;
i += 1;
continue;
}
_ => {}
}
i += 1;
}
segments.push(&input[start..]);
Ok(segments)
}
fn find_matching_close(input: &str, open: u8, close: u8) -> Option<usize> {
let bytes = input.as_bytes();
if bytes.is_empty() || bytes[0] != open {
return None;
}
let mut depth: i32 = 0;
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'\\' => {
i += 2; continue;
}
b if b == open => {
depth += 1;
}
b if b == close => {
depth -= 1;
if depth == 0 {
return Some(i);
}
}
_ => {}
}
i += 1;
}
None
}
fn find_unescaped_colon(s: &str) -> Option<usize> {
let bytes = s.as_bytes();
let mut depth: i32 = 0;
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'\\' => {
i += 2;
continue;
}
b'{' | b'[' => depth += 1,
b'}' | b']' => depth -= 1,
b':' if depth == 0 => return Some(i),
_ => {}
}
i += 1;
}
None
}
fn malformed(line_num: usize, span: Span, detail: &str) -> Error {
Error::Structured(ErrorKind::MalformedInlineCompound {
line: line_num as u32,
span,
detail: detail.to_string(),
})
}