use std::borrow::Cow;
use memchr::memchr2;
use crate::error::{Error, ErrorKind, Span};
use crate::value::{ObjectMap, Value};
use crate::whitespace::{inline_whitespace_ascii, is_inline_whitespace};
use super::classify::{fast_plain_decimal_i64, is_float_literal, lossy_scalar, try_parse_integer};
use super::insert::insert_value;
pub(crate) const MAX_INLINE_DEPTH: usize = 128;
pub(crate) fn parse_inline_object(
input: &str,
line_num: usize,
span: Span,
strict: bool,
bounds: InlineBounds<'_>,
) -> Result<Value, Error> {
let has_quotes = has_quote_bytes(input.as_bytes());
parse_inline_object_inner(input, line_num, span, 0, strict, bounds, has_quotes)
}
pub(crate) fn parse_inline_array(
input: &str,
line_num: usize,
span: Span,
strict: bool,
bounds: InlineBounds<'_>,
) -> Result<Value, Error> {
let has_quotes = has_quote_bytes(input.as_bytes());
parse_inline_array_inner(input, line_num, span, 0, strict, bounds, has_quotes)
}
fn parse_inline_object_inner(
input: &str,
line_num: usize,
span: Span,
depth: usize,
strict: bool,
bounds: InlineBounds<'_>,
has_quotes: bool,
) -> 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_matches(is_inline_whitespace).is_empty() {
return Ok(Value::Object(ObjectMap::default()));
}
let segments = split_top_level(
inner,
line_num,
span,
InlineBody::Object,
bounds,
has_quotes,
)?;
let mut map = ObjectMap::default();
let n = segments.len();
for (i, seg) in segments.into_iter().enumerate() {
let trimmed = seg.trim_matches(is_inline_whitespace);
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_inline(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_matches(is_inline_whitespace);
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_matches(is_inline_whitespace),
line_num,
span,
)?;
Value::String(processed.into_owned().into())
} else {
parse_inline_value(
value_body, line_num, span, depth, strict, bounds, has_quotes,
)?
};
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,
strict: bool,
bounds: InlineBounds<'_>,
has_quotes: bool,
) -> 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_matches(is_inline_whitespace).is_empty() {
return Ok(Value::Array(Vec::new()));
}
let segments = split_top_level(inner, line_num, span, InlineBody::Array, bounds, has_quotes)?;
let mut items: Vec<Value> = Vec::new();
let n = segments.len();
for (i, seg) in segments.into_iter().enumerate() {
let trimmed = seg.trim_matches(is_inline_whitespace);
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)",
));
}
let value =
parse_inline_value_raw(trimmed, line_num, span, depth, strict, bounds, has_quotes)?;
items.push(value);
}
Ok(Value::Array(items))
}
fn parse_inline_value(
body: &str,
line_num: usize,
span: Span,
depth: usize,
strict: bool,
bounds: InlineBounds<'_>,
has_quotes: bool,
) -> Result<Value, Error> {
let trimmed = body.trim_matches(is_inline_whitespace);
if trimmed.is_empty() {
return Ok(Value::String("".into()));
}
parse_inline_value_raw(trimmed, line_num, span, depth, strict, bounds, has_quotes)
}
fn parse_inline_value_raw(
trimmed: &str,
line_num: usize,
span: Span,
depth: usize,
strict: bool,
bounds: InlineBounds<'_>,
has_quotes: bool,
) -> Result<Value, Error> {
let first_byte = trimmed.as_bytes()[0];
if first_byte == b'{' {
let close_idx = match bounds.known_closer(trimmed) {
Some(idx) => Some(idx),
None => match find_matching_close(trimmed, b'{', b'}') {
Some(close) => Some(close),
None => match scan_inline_closer(trimmed, b'{', b'}', line_num, span) {
InlineCloserScan::Found(idx) => Some(idx),
InlineCloserScan::NotFound => None,
InlineCloserScan::BadEscape(err) => return Err(err),
},
},
};
match close_idx {
Some(idx) if idx == trimmed.len() - 1 => {
let inner = &trimmed[1..trimmed.len() - 1];
if inner.trim_matches(is_inline_whitespace).is_empty() {
return Ok(Value::Object(ObjectMap::default()));
}
return parse_inline_object_inner(
trimmed,
line_num,
span,
depth + 1,
strict,
bounds,
has_quotes,
);
}
Some(_) => return Err(malformed_closer_not_at_end(line_num, span)),
None => {
return Err(Error::Structured(ErrorKind::UnterminatedInlineCompound {
line: line_num as u32,
span,
}));
}
}
}
if first_byte == b'[' {
let close_idx = match bounds.known_closer(trimmed) {
Some(idx) => Some(idx),
None => match find_matching_close(trimmed, b'[', b']') {
Some(close) => Some(close),
None => match scan_inline_closer(trimmed, b'[', b']', line_num, span) {
InlineCloserScan::Found(idx) => Some(idx),
InlineCloserScan::NotFound => None,
InlineCloserScan::BadEscape(err) => return Err(err),
},
},
};
match close_idx {
Some(idx) if idx == trimmed.len() - 1 => {
let inner = &trimmed[1..trimmed.len() - 1];
if inner.trim_matches(is_inline_whitespace).is_empty() {
return Ok(Value::Array(Vec::new()));
}
return parse_inline_array_inner(
trimmed,
line_num,
span,
depth + 1,
strict,
bounds,
has_quotes,
);
}
Some(_) => return Err(malformed_closer_not_at_end(line_num, span)),
None => {
return Err(Error::Structured(ErrorKind::UnterminatedInlineCompound {
line: line_num as u32,
span,
}));
}
}
}
if trimmed == "()" || trimmed == "(())" {
return Ok(Value::String("".into()));
}
let processed = process_escapes(trimmed, line_num, span)?;
if matches!(&processed, Cow::Owned(_)) {
return Ok(Value::String(processed.into_owned().into()));
}
classify_inline_scalar(&processed, line_num, span, strict)
}
fn classify_inline_scalar(
body: &str,
line_num: usize,
span: Span,
strict: bool,
) -> 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);
if strict && canonical != body {
return Err(lossy_scalar(body, canonical, line_num, span));
}
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 strict {
let rendered = crate::render::canonical::canonical_float(canonical);
if rendered != body {
return Err(lossy_scalar(body, &rendered, line_num, span));
}
return Ok(Value::Float(canonical.into()));
}
if canonical == body {
return Ok(Value::Float(body.into()));
}
return Ok(Value::Float(canonical.into()));
}
}
Ok(Value::String(body.into()))
}
pub(crate) 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)
}
enum RecognisedEscape {
Simple(char),
Unicode(char),
SurrogatePair(char),
}
impl RecognisedEscape {
fn len(&self) -> usize {
match self {
RecognisedEscape::Simple(_) => 2,
RecognisedEscape::Unicode(_) => 6,
RecognisedEscape::SurrogatePair(_) => 12,
}
}
}
fn scan_escape(bytes: &[u8], i: usize) -> Result<RecognisedEscape, String> {
if i + 1 >= bytes.len() {
return Err("\\<end-of-line>".to_string());
}
let next = bytes[i + 1];
let simple = match next {
b'\\' => Some('\\'),
b',' => Some(','),
b'}' => Some('}'),
b']' => Some(']'),
b'{' => Some('{'),
b'[' => Some('['),
b'n' => Some('\n'),
b'r' => Some('\r'),
b'.' => Some('.'),
b':' => Some(':'),
b'"' => Some('"'),
b'\'' => Some('\''),
b'`' => Some('`'),
_ => None,
};
if let Some(ch) = simple {
return Ok(RecognisedEscape::Simple(ch));
}
if next == b'u' {
if i + 6 > bytes.len() || !bytes[i + 2..i + 6].iter().all(|b| b.is_ascii_hexdigit()) {
return Err(render_malformed_unicode_escape(bytes, i));
}
let hex = std::str::from_utf8(&bytes[i + 2..i + 6]).unwrap();
let value = u32::from_str_radix(hex, 16).expect("4 ASCII hex digits");
if (0xD800..=0xDBFF).contains(&value) {
if i + 12 <= bytes.len()
&& bytes[i + 6] == b'\\'
&& bytes[i + 7] == b'u'
&& bytes[i + 8..i + 12].iter().all(|b| b.is_ascii_hexdigit())
{
let low_hex = std::str::from_utf8(&bytes[i + 8..i + 12]).unwrap();
let low = u32::from_str_radix(low_hex, 16).expect("4 ASCII hex digits");
if (0xDC00..=0xDFFF).contains(&low) {
let combined = 0x10000 + (value - 0xD800) * 0x400 + (low - 0xDC00);
let ch = char::from_u32(combined).expect("valid surrogate pair");
return Ok(RecognisedEscape::SurrogatePair(ch));
}
}
return Err(render_malformed_unicode_escape(bytes, i));
}
if (0xDC00..=0xDFFF).contains(&value) {
return Err(render_malformed_unicode_escape(bytes, i));
}
let ch = char::from_u32(value).expect("BMP non-surrogate value");
return Ok(RecognisedEscape::Unicode(ch));
}
if next < 0x80 {
Err(format!("\\{}", next as char))
} else {
Err(format!("\\<0x{:02X}>", next))
}
}
pub(crate) fn process_escapes<'a>(
input: &'a str,
line_num: usize,
span: Span,
) -> Result<Cow<'a, str>, Error> {
if !input.as_bytes().contains(&b'\\') {
return Ok(Cow::Borrowed(input));
}
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'\\' {
let esc = match scan_escape(bytes, i) {
Err(sequence) => {
return Err(Error::Structured(ErrorKind::BadEscapeSequence {
line: line_num as u32,
span,
sequence,
}));
}
Ok(esc) => esc,
};
let len = esc.len();
match esc {
RecognisedEscape::Simple(ch)
| RecognisedEscape::Unicode(ch)
| RecognisedEscape::SurrogatePair(ch) => out.push(ch),
}
i += len;
} else {
let ch = input[i..].chars().next().unwrap();
out.push(ch);
i += ch.len_utf8();
}
}
Ok(Cow::Owned(out))
}
fn render_malformed_unicode_escape(bytes: &[u8], i: usize) -> String {
let end = usize::min(i + 6, bytes.len());
let mut seq = String::from("\\u");
for &b in &bytes[i + 2..end] {
if b < 0x80 {
seq.push(b as char);
} else {
seq.push_str(&format!("<0x{:02X}>", b));
}
}
seq
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ColonScan {
Found(usize),
Absent,
UnterminatedQuote,
}
fn is_quote_byte(b: u8) -> bool {
b == b'"' || b == b'\'' || b == b'`'
}
pub(crate) fn has_quote_bytes(bytes: &[u8]) -> bool {
#[cfg(test)]
ix_probe::record_quote_prescan(bytes.len());
bytes.contains(&b'"') || bytes.contains(&b'\'') || bytes.contains(&b'`')
}
fn skip_segment_ws(s: &str, mut i: usize) -> usize {
while let Some(len) = inline_whitespace_at(s, i) {
i += len;
}
i
}
fn inline_whitespace_at(s: &str, i: usize) -> Option<usize> {
let bytes = s.as_bytes();
let b = *bytes.get(i)?;
if inline_whitespace_ascii(b) {
return Some(1);
}
if b < 0x80 || !s.is_char_boundary(i) {
return None;
}
let ch = s[i..].chars().next()?;
if is_inline_whitespace(ch) {
Some(ch.len_utf8())
} else {
None
}
}
pub(crate) fn scan_unescaped_colon(s: &str) -> ColonScan {
let bytes = s.as_bytes();
let mut from = 0usize;
while let Some(rel) = find_unescaped_colon_fast(&s[from..]) {
let cand = from + rel;
match key_prefix_quote_state(s, from, cand) {
KeyPrefixQuote::Opaque => return ColonScan::Found(cand),
KeyPrefixQuote::OpenSegment { resume } => from = resume,
KeyPrefixQuote::Unterminated => return ColonScan::UnterminatedQuote,
}
}
if has_quote_bytes(bytes) {
return scan_unescaped_colon_slow(s);
}
ColonScan::Absent
}
enum KeyPrefixQuote {
Opaque,
OpenSegment { resume: usize },
Unterminated,
}
fn key_prefix_quote_state(s: &str, from: usize, cand: usize) -> KeyPrefixQuote {
let bytes = s.as_bytes();
if !has_quote_bytes(&bytes[from..cand]) {
return KeyPrefixQuote::Opaque;
}
let mut i = from;
let mut seg_start = from == 0;
while i < cand {
if seg_start {
i = skip_segment_ws(s, i);
if i < cand && is_quote_byte(bytes[i]) {
return match quoted_span_end(bytes, i) {
Some(end) if end > cand => KeyPrefixQuote::OpenSegment { resume: end + 1 },
Some(end) => {
i = end + 1;
seg_start = false;
continue;
}
None => KeyPrefixQuote::Unterminated,
};
}
seg_start = false;
}
match bytes[i] {
b'\\' => i += 2, b'.' => {
seg_start = true;
i += 1;
}
_ => i += 1,
}
}
KeyPrefixQuote::Opaque
}
fn scan_unescaped_colon_slow(s: &str) -> ColonScan {
let bytes = s.as_bytes();
let mut i = 0;
let mut seg_start = true; while i < bytes.len() {
if seg_start {
i = skip_segment_ws(s, i);
if i < bytes.len() && is_quote_byte(bytes[i]) {
return match quoted_span_end(bytes, i) {
Some(end) => {
i = end + 1;
seg_start = false;
continue;
}
None => ColonScan::UnterminatedQuote,
};
}
seg_start = false;
}
match bytes[i] {
b'\\' => i += 2, b'.' => {
seg_start = true;
i += 1;
}
b':' => return ColonScan::Found(i),
_ => i += 1,
}
}
ColonScan::Absent
}
fn find_unescaped_colon_fast(s: &str) -> Option<usize> {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
let rel = memchr2(b'\\', b':', &bytes[i..])?;
let abs = i + rel;
if bytes[abs] == b':' {
return Some(abs);
}
i = abs + 2;
}
None
}
pub(crate) fn find_unescaped_colon(s: &str) -> Option<usize> {
match scan_unescaped_colon(s) {
ColonScan::Found(p) => Some(p),
_ => None,
}
}
pub(crate) fn split_key_path(s: &str) -> Vec<&str> {
let bytes = s.as_bytes();
let mut out = Vec::new();
if !has_quote_bytes(bytes) {
let mut start = 0;
let mut i = 0;
while i < bytes.len() {
let rel = match memchr2(b'\\', b'.', &bytes[i..]) {
Some(p) => p,
None => break,
};
let abs = i + rel;
if bytes[abs] == b'.' {
out.push(&s[start..abs]);
start = abs + 1;
i = abs + 1;
} else {
if abs + 1 < bytes.len() {
i = abs + 2;
} else {
i = abs + 1;
}
}
}
out.push(&s[start..]);
return out;
}
let mut start = 0;
let mut i = 0;
let mut seg_start = true;
while i < bytes.len() {
if seg_start {
i = skip_segment_ws(s, i);
if i < bytes.len() && is_quote_byte(bytes[i]) {
match quoted_span_end(bytes, i) {
Some(end) => {
i = end + 1;
seg_start = false;
continue;
}
None => {
break;
}
}
}
seg_start = false;
}
match bytes[i] {
b'\\' => i += 2, b'.' => {
out.push(&s[start..i]);
start = i + 1;
i += 1;
seg_start = true;
}
_ => i += 1,
}
}
out.push(&s[start..]);
out
}
pub(crate) fn key_is_single_segment(s: &str) -> bool {
let bytes = s.as_bytes();
if !has_quote_bytes(bytes) {
let mut i = 0;
while i < bytes.len() {
let rel = match memchr2(b'\\', b'.', &bytes[i..]) {
Some(p) => p,
None => return true,
};
let abs = i + rel;
if bytes[abs] == b'.' {
return false;
}
i = abs + 2;
}
return true;
}
let mut i = 0;
let mut seg_start = true;
while i < bytes.len() {
if seg_start {
i = skip_segment_ws(s, i);
if i < bytes.len() && is_quote_byte(bytes[i]) {
match quoted_span_end(bytes, i) {
Some(end) => {
i = end + 1;
seg_start = false;
continue;
}
None => {
return true;
}
}
}
seg_start = false;
}
match bytes[i] {
b'\\' => i += 2, b'.' => return false,
_ => i += 1,
}
}
true
}
pub(crate) fn quoted_span_end(bytes: &[u8], open_idx: usize) -> Option<usize> {
let quote = bytes[open_idx];
let mut j = open_idx + 1;
while j < bytes.len() {
if bytes[j] == b'\\' {
j += 2;
continue;
}
if bytes[j] == quote {
return Some(j);
}
j += 1;
}
None
}
pub(crate) fn decode_key_segment<'a>(
input: &'a str,
line_num: usize,
span: Span,
) -> Result<Cow<'a, str>, Error> {
if !input.is_empty() {
let first = input.as_bytes()[0];
if first == b'"' || first == b'\'' || first == b'`' {
debug_assert!(input.len() >= 2 && input.as_bytes()[input.len() - 1] == first);
let interior = &input[1..input.len() - 1];
if !interior.as_bytes().contains(&b'\\') {
return Ok(Cow::Borrowed(interior));
}
return process_escapes(interior, line_num, span);
}
}
if !input.as_bytes().contains(&b'\\') {
return Ok(Cow::Borrowed(input));
}
process_escapes(input, line_num, span)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InlineBody {
Object,
Array,
}
#[derive(Clone, Copy)]
pub(crate) struct InlineBounds<'a> {
origin: usize, pairs: &'a [(usize, usize)],
}
impl<'a> InlineBounds<'a> {
pub(crate) fn for_input(input: &str) -> Self {
InlineBounds {
origin: input.as_ptr() as usize,
pairs: &[],
}
}
pub(crate) fn over<'b>(top: &str, pairs: &'b [(usize, usize)]) -> InlineBounds<'b> {
InlineBounds {
origin: top.as_ptr() as usize,
pairs,
}
}
pub(crate) fn known_closer(&self, s: &str) -> Option<usize> {
let off = s.as_ptr() as usize - self.origin;
#[cfg(test)]
if ix_probe::bypass_engaged() {
return None;
}
#[cfg(test)]
let idx = {
let mut steps = 0usize;
let idx = self.pairs.binary_search_by(|p| {
steps += 1;
p.0.cmp(&off)
});
ix_probe::record_kc_lookup(steps);
idx
};
#[cfg(not(test))]
let idx = self.pairs.binary_search_by_key(&off, |p| p.0);
let idx = idx.ok()?;
let rel = self.pairs[idx].1 - off;
let hit = (rel < s.len()).then_some(rel);
#[cfg(test)]
if hit.is_some() {
ix_probe::record_kc_hit();
}
hit
}
fn opener_close_at(&self, abs: usize) -> Option<usize> {
#[cfg(test)]
if ix_probe::bypass_engaged() {
return None;
}
#[cfg(test)]
let idx = {
let mut steps = 0usize;
let idx = self.pairs.binary_search_by(|p| {
steps += 1;
p.0.cmp(&abs)
});
ix_probe::record_oca_lookup(steps);
idx
};
#[cfg(not(test))]
let idx = self.pairs.binary_search_by_key(&abs, |p| p.0);
let idx = idx.ok()?;
#[cfg(test)]
ix_probe::record_oca_hit();
Some(self.pairs[idx].1)
}
}
#[cfg(test)]
pub(crate) mod ix_probe {
use std::cell::RefCell;
#[derive(Clone, Copy, Default)]
struct State {
bypass: bool,
kc_calls: u64,
kc_hits: u64,
kc_steps: u64,
kc_steps_max: u64,
oca_calls: u64,
oca_hits: u64,
oca_steps: u64,
oca_steps_max: u64,
sort_calls: u64,
sort_elems: u64,
sort_cmps: u64,
bodies: u64,
body_bytes: u64,
pairs_total: u64,
pairs_max: u64,
hq_calls: u64,
hq_bytes: u64,
hq_max: u64,
}
thread_local! {
static STATE: RefCell<State> = RefCell::new(State::default());
}
fn with_state<R>(f: impl FnOnce(&mut State) -> R) -> R {
STATE.with(|s| f(&mut s.borrow_mut()))
}
pub(crate) struct BypassGuard;
impl Drop for BypassGuard {
fn drop(&mut self) {
with_state(|s| s.bypass = false);
}
}
pub(crate) fn set_bypass(on: bool) -> BypassGuard {
with_state(|s| s.bypass = on);
BypassGuard
}
pub(crate) fn bypass_engaged() -> bool {
with_state(|s| s.bypass)
}
pub(crate) fn record_kc_lookup(steps: usize) {
with_state(|s| {
s.kc_calls += 1;
s.kc_steps += steps as u64;
s.kc_steps_max = s.kc_steps_max.max(steps as u64);
});
}
pub(crate) fn record_kc_hit() {
with_state(|s| s.kc_hits += 1);
}
pub(crate) fn record_oca_lookup(steps: usize) {
with_state(|s| {
s.oca_calls += 1;
s.oca_steps += steps as u64;
s.oca_steps_max = s.oca_steps_max.max(steps as u64);
});
}
pub(crate) fn record_oca_hit() {
with_state(|s| s.oca_hits += 1);
}
pub(crate) fn record_sort(elems: usize, cmps: usize) {
with_state(|s| {
s.sort_calls += 1;
s.sort_elems += elems as u64;
s.sort_cmps += cmps as u64;
});
}
pub(crate) fn record_body(input_len: usize, pairs: usize) {
with_state(|s| {
s.bodies += 1;
s.body_bytes += input_len as u64;
s.pairs_total += pairs as u64;
s.pairs_max = s.pairs_max.max(pairs as u64);
});
}
pub(crate) fn record_quote_prescan(len: usize) {
with_state(|s| {
s.hq_calls += 1;
s.hq_bytes += len as u64;
s.hq_max = s.hq_max.max(len as u64);
});
}
#[derive(Default, Clone, Copy)]
pub(crate) struct Snapshot {
pub kc_calls: u64,
pub kc_hits: u64,
pub kc_steps: u64,
pub kc_steps_max: u64,
pub oca_calls: u64,
pub oca_hits: u64,
pub oca_steps: u64,
pub oca_steps_max: u64,
pub sort_calls: u64,
pub sort_elems: u64,
pub sort_cmps: u64,
pub bodies: u64,
pub body_bytes: u64,
pub pairs_total: u64,
pub pairs_max: u64,
pub hq_calls: u64,
pub hq_bytes: u64,
pub hq_max: u64,
}
pub(crate) fn snapshot() -> Snapshot {
STATE.with(|s| {
let s = s.borrow();
Snapshot {
kc_calls: s.kc_calls,
kc_hits: s.kc_hits,
kc_steps: s.kc_steps,
kc_steps_max: s.kc_steps_max,
oca_calls: s.oca_calls,
oca_hits: s.oca_hits,
oca_steps: s.oca_steps,
oca_steps_max: s.oca_steps_max,
sort_calls: s.sort_calls,
sort_elems: s.sort_elems,
sort_cmps: s.sort_cmps,
bodies: s.bodies,
body_bytes: s.body_bytes,
pairs_total: s.pairs_total,
pairs_max: s.pairs_max,
hq_calls: s.hq_calls,
hq_bytes: s.hq_bytes,
hq_max: s.hq_max,
}
})
}
pub(crate) fn reset() {
with_state(|s| *s = State::default());
}
}
#[derive(Clone, Copy)]
struct ScopeFrame(u8);
impl ScopeFrame {
#[inline]
fn pack(kind: u8, saved_in_key: bool, saved_seg_start: bool, saved_raw: bool) -> Self {
ScopeFrame(
((kind == b'[') as u8)
| ((saved_in_key as u8) << 1)
| ((saved_seg_start as u8) << 2)
| ((saved_raw as u8) << 3),
)
}
#[inline]
fn kind(self) -> u8 {
if self.0 & 1 == 0 {
b'{'
} else {
b'['
}
}
#[inline]
fn saved_in_key(self) -> bool {
self.0 & 0b0010 != 0
}
#[inline]
fn saved_seg_start(self) -> bool {
self.0 & 0b0100 != 0
}
#[inline]
fn saved_raw(self) -> bool {
self.0 & 0b1000 != 0
}
}
enum ScanStop {
Closer { idx: usize, byte: u8 },
EofAfterWsSkip,
UnterminatedQuote,
BadEscape(Error),
Exhausted,
}
trait ScanCfg {
const TRACK_QUOTES: bool;
const USE_STACK: bool;
const OPENER_JUMP: bool;
const DECREMENT_ANY_CLOSER: bool;
const CLOSER_LITERAL: bool;
const VALIDATE_ESCAPES: bool;
const TRACK_RAW: bool;
const COMMA_SPLITS: bool;
const COMMA_CTX_SCOPE: bool;
const COMMA_CLEARS_RAW: bool;
const COLON_SETS_VS: bool;
const COLON_ELSE_CLEAR_VS: bool;
const RESTORE_IN_KEY_ON_MATCH: bool;
const RESTORE_SEG_ON_MATCH: bool;
const RESTORE_RAW_ON_MATCH: bool;
const MISMATCH_SEG_FALSE: bool;
const CLEAR_VS_ON_NESTED_CLOSE: bool;
const RECORD_BOUNDS: bool;
}
struct FindFast;
impl ScanCfg for FindFast {
const TRACK_QUOTES: bool = false;
const USE_STACK: bool = true;
const OPENER_JUMP: bool = false;
const DECREMENT_ANY_CLOSER: bool = true;
const CLOSER_LITERAL: bool = false;
const VALIDATE_ESCAPES: bool = false;
const TRACK_RAW: bool = true;
const COMMA_SPLITS: bool = false;
const COMMA_CTX_SCOPE: bool = true;
const COMMA_CLEARS_RAW: bool = true;
const COLON_SETS_VS: bool = true;
const COLON_ELSE_CLEAR_VS: bool = false;
const RESTORE_IN_KEY_ON_MATCH: bool = true;
const RESTORE_SEG_ON_MATCH: bool = false;
const RESTORE_RAW_ON_MATCH: bool = false;
const MISMATCH_SEG_FALSE: bool = false;
const CLEAR_VS_ON_NESTED_CLOSE: bool = true;
const RECORD_BOUNDS: bool = false;
}
struct FindQ;
impl ScanCfg for FindQ {
const TRACK_QUOTES: bool = true;
const USE_STACK: bool = true;
const OPENER_JUMP: bool = false;
const DECREMENT_ANY_CLOSER: bool = true;
const CLOSER_LITERAL: bool = false;
const VALIDATE_ESCAPES: bool = false;
const TRACK_RAW: bool = true;
const COMMA_SPLITS: bool = false;
const COMMA_CTX_SCOPE: bool = true;
const COMMA_CLEARS_RAW: bool = true;
const COLON_SETS_VS: bool = true;
const COLON_ELSE_CLEAR_VS: bool = false;
const RESTORE_IN_KEY_ON_MATCH: bool = true;
const RESTORE_SEG_ON_MATCH: bool = true;
const RESTORE_RAW_ON_MATCH: bool = true;
const MISMATCH_SEG_FALSE: bool = true;
const CLEAR_VS_ON_NESTED_CLOSE: bool = true;
const RECORD_BOUNDS: bool = false;
}
struct ScanFast;
impl ScanCfg for ScanFast {
const TRACK_QUOTES: bool = false;
const USE_STACK: bool = true;
const OPENER_JUMP: bool = false;
const DECREMENT_ANY_CLOSER: bool = true;
const CLOSER_LITERAL: bool = false;
const VALIDATE_ESCAPES: bool = true;
const TRACK_RAW: bool = true;
const COMMA_SPLITS: bool = false;
const COMMA_CTX_SCOPE: bool = true;
const COMMA_CLEARS_RAW: bool = true;
const COLON_SETS_VS: bool = true;
const COLON_ELSE_CLEAR_VS: bool = false;
const RESTORE_IN_KEY_ON_MATCH: bool = true;
const RESTORE_SEG_ON_MATCH: bool = false;
const RESTORE_RAW_ON_MATCH: bool = false;
const MISMATCH_SEG_FALSE: bool = false;
const CLEAR_VS_ON_NESTED_CLOSE: bool = true;
const RECORD_BOUNDS: bool = true;
}
struct ScanQ;
impl ScanCfg for ScanQ {
const TRACK_QUOTES: bool = true;
const USE_STACK: bool = true;
const OPENER_JUMP: bool = false;
const DECREMENT_ANY_CLOSER: bool = true;
const CLOSER_LITERAL: bool = false;
const VALIDATE_ESCAPES: bool = true;
const TRACK_RAW: bool = true;
const COMMA_SPLITS: bool = false;
const COMMA_CTX_SCOPE: bool = true;
const COMMA_CLEARS_RAW: bool = true;
const COLON_SETS_VS: bool = true;
const COLON_ELSE_CLEAR_VS: bool = false;
const RESTORE_IN_KEY_ON_MATCH: bool = true;
const RESTORE_SEG_ON_MATCH: bool = true;
const RESTORE_RAW_ON_MATCH: bool = true;
const MISMATCH_SEG_FALSE: bool = true;
const CLEAR_VS_ON_NESTED_CLOSE: bool = true;
const RECORD_BOUNDS: bool = true;
}
struct SplitFast;
impl ScanCfg for SplitFast {
const TRACK_QUOTES: bool = false;
const USE_STACK: bool = false;
const OPENER_JUMP: bool = true;
const DECREMENT_ANY_CLOSER: bool = false;
const CLOSER_LITERAL: bool = true;
const VALIDATE_ESCAPES: bool = false;
const TRACK_RAW: bool = false;
const COMMA_SPLITS: bool = true;
const COMMA_CTX_SCOPE: bool = false;
const COMMA_CLEARS_RAW: bool = false;
const COLON_SETS_VS: bool = true;
const COLON_ELSE_CLEAR_VS: bool = true;
const RESTORE_IN_KEY_ON_MATCH: bool = false;
const RESTORE_SEG_ON_MATCH: bool = false;
const RESTORE_RAW_ON_MATCH: bool = false;
const MISMATCH_SEG_FALSE: bool = false;
const CLEAR_VS_ON_NESTED_CLOSE: bool = false;
const RECORD_BOUNDS: bool = false;
}
struct SplitQ;
impl ScanCfg for SplitQ {
const TRACK_QUOTES: bool = true;
const USE_STACK: bool = false;
const OPENER_JUMP: bool = true;
const DECREMENT_ANY_CLOSER: bool = false;
const CLOSER_LITERAL: bool = true;
const VALIDATE_ESCAPES: bool = false;
const TRACK_RAW: bool = false;
const COMMA_SPLITS: bool = true;
const COMMA_CTX_SCOPE: bool = false;
const COMMA_CLEARS_RAW: bool = false;
const COLON_SETS_VS: bool = true;
const COLON_ELSE_CLEAR_VS: bool = true;
const RESTORE_IN_KEY_ON_MATCH: bool = false;
const RESTORE_SEG_ON_MATCH: bool = false;
const RESTORE_RAW_ON_MATCH: bool = false;
const MISMATCH_SEG_FALSE: bool = false;
const CLEAR_VS_ON_NESTED_CLOSE: bool = false;
const RECORD_BOUNDS: bool = false;
}
struct Scanner<'a, 'b, C: ScanCfg> {
input: &'a str,
bytes: &'a [u8],
open: u8,
close: u8,
body_object: bool,
line_num: usize,
span: Span,
i: usize,
depth: i32,
in_key: bool,
seg_start: bool,
value_start: bool,
raw: bool,
prev: u8,
stack: Vec<ScopeFrame>,
segments: Vec<&'a str>,
seg_at: usize,
input_off: usize, bounds: InlineBounds<'b>,
open_at: Vec<(usize, bool, i32)>, pairs_out: Vec<(usize, usize)>, _cfg: std::marker::PhantomData<C>,
}
impl<'a, 'b, C: ScanCfg> Scanner<'a, 'b, C> {
#[allow(clippy::too_many_arguments)]
fn new(
input: &'a str,
open: u8,
close: u8,
body_object: bool,
line_num: usize,
span: Span,
bounds: InlineBounds<'b>,
) -> Self {
Self {
input,
bytes: input.as_bytes(),
open,
close,
body_object,
line_num,
span,
i: 0,
depth: 0,
in_key: false,
seg_start: false,
value_start: false,
raw: false,
prev: open,
stack: Vec::new(),
segments: Vec::new(),
seg_at: 0,
input_off: input.as_ptr() as usize - bounds.origin,
bounds,
open_at: Vec::new(),
pairs_out: Vec::new(),
_cfg: std::marker::PhantomData,
}
}
fn run(&mut self) -> ScanStop {
let input = self.input;
let bytes = self.bytes;
let len = bytes.len();
let open = self.open;
let close = self.close;
let body_object = self.body_object;
let mut i = self.i;
let mut depth = self.depth;
let mut in_key = self.in_key;
let mut seg_start = self.seg_start;
let mut value_start = self.value_start;
let mut raw = self.raw;
let mut prev = self.prev;
let mut seg_at = self.seg_at;
let mut stack = std::mem::take(&mut self.stack);
let mut segments = std::mem::take(&mut self.segments);
let mut open_at = std::mem::take(&mut self.open_at);
let mut pairs_out = std::mem::take(&mut self.pairs_out);
let stop = loop {
if i >= len {
break ScanStop::Exhausted;
}
if C::TRACK_QUOTES && in_key && seg_start {
i = skip_segment_ws(input, i);
if i >= len {
break ScanStop::EofAfterWsSkip;
}
if is_quote_byte(bytes[i]) {
match quoted_span_end(bytes, i) {
Some(end) => {
if C::TRACK_RAW {
prev = bytes[end];
}
i = end + 1;
seg_start = false;
continue;
}
None => break ScanStop::UnterminatedQuote,
}
}
seg_start = false;
}
let b = bytes[i];
match b {
b'\\' => {
if C::VALIDATE_ESCAPES {
match scan_escape(bytes, i) {
Err(seq) => {
break ScanStop::BadEscape(Error::Structured(
ErrorKind::BadEscapeSequence {
line: self.line_num as u32,
span: self.span,
sequence: seq,
},
))
}
Ok(esc) => {
value_start = false;
if C::TRACK_RAW {
prev = b'\\';
}
i += esc.len();
continue;
}
}
}
value_start = false;
i += 2;
continue;
}
b':' => {
if in_key {
in_key = false;
if C::COLON_SETS_VS {
value_start = true;
}
} else if C::TRACK_RAW && prev == b':' && value_start {
raw = true;
} else if C::TRACK_RAW {
if value_start && bytes.get(i + 1) != Some(&b':') {
value_start = false;
}
} else if C::COLON_ELSE_CLEAR_VS {
value_start = false;
}
}
b',' => {
if C::COMMA_SPLITS {
segments.push(&input[seg_at..i]);
seg_at = i + 1;
}
let scope_object = if C::COMMA_CTX_SCOPE {
stack.last().map_or(body_object, |f| f.kind() == b'{')
} else {
body_object
};
in_key = scope_object;
if C::TRACK_QUOTES {
seg_start = scope_object;
}
if C::COMMA_CLEARS_RAW {
raw = false;
}
value_start = !scope_object;
}
b'.' if C::TRACK_QUOTES && in_key => {
seg_start = true;
}
b'{' | b'[' => {
let gated_open = value_start && !raw;
if !gated_open {
value_start = false;
} else if C::OPENER_JUMP {
let (o, c) = if b == b'{' {
(b'{', b'}')
} else {
(b'[', b']')
};
if let Some(end_abs) = self.bounds.opener_close_at(self.input_off + i) {
let end = end_abs - self.input_off; if end < len {
i = end + 1;
value_start = false;
if C::TRACK_QUOTES {
seg_start = false;
}
continue;
}
}
if let InlineCloserScan::Found(pos) =
scan_inline_closer(&input[i..], o, c, self.line_num, self.span)
{
i += pos + 1;
value_start = false;
if C::TRACK_QUOTES {
seg_start = false;
}
continue;
}
value_start = false;
i += 1;
continue;
} else {
if C::DECREMENT_ANY_CLOSER || b == open {
depth += 1;
}
if C::USE_STACK {
stack.push(ScopeFrame::pack(b, in_key, seg_start, raw));
if C::RECORD_BOUNDS {
open_at.push((i, true, depth));
}
}
value_start = b == b'[';
in_key = b == b'{';
if C::TRACK_QUOTES {
seg_start = b == b'{';
}
}
}
b'}' | b']' if !C::CLOSER_LITERAL => {
if !C::DECREMENT_ANY_CLOSER && b != close {
if C::TRACK_QUOTES {
let want = if b == b']' { b'[' } else { b'{' };
if stack.last().is_some_and(|f| f.kind() == want) {
let f = stack.pop().unwrap();
in_key = f.saved_in_key();
seg_start = if C::RESTORE_SEG_ON_MATCH {
f.saved_seg_start()
} else {
false
};
} else {
seg_start = false;
}
}
} else {
raw = false;
depth -= 1;
if depth == 0 {
break ScanStop::Closer { idx: i, byte: b };
}
let want = if b == b']' { b'[' } else { b'{' };
if C::RECORD_BOUNDS {
match open_at.last_mut() {
Some(top) if depth == top.2 - 1 => {
if !stack.last().is_some_and(|f| f.kind() == want) {
top.1 = false;
}
}
Some(top) if depth < top.2 - 1 => top.1 = false,
_ => {}
}
}
if stack.last().is_some_and(|f| f.kind() == want) {
let f = stack.pop().unwrap();
if C::RECORD_BOUNDS {
let (open, pure, entry) =
open_at.pop().expect("open_at parallels the scope stack");
if pure && depth == entry - 1 {
pairs_out.push((open, i));
}
}
if C::RESTORE_IN_KEY_ON_MATCH {
in_key = f.saved_in_key();
}
if C::TRACK_QUOTES {
seg_start = if C::RESTORE_SEG_ON_MATCH {
f.saved_seg_start()
} else {
false
};
if C::RESTORE_RAW_ON_MATCH {
raw = f.saved_raw();
}
}
} else if C::MISMATCH_SEG_FALSE && C::TRACK_QUOTES {
seg_start = false;
}
if C::CLEAR_VS_ON_NESTED_CLOSE {
value_start = false;
}
}
}
_ => {
if let Some(ws_len) = inline_whitespace_at(input, i) {
if C::TRACK_RAW {
prev = bytes[i + ws_len - 1];
}
i += ws_len;
continue;
}
value_start = false;
}
}
prev = b;
i += 1;
};
self.i = i;
self.depth = depth;
self.in_key = in_key;
self.seg_start = seg_start;
self.value_start = value_start;
self.raw = raw;
self.prev = prev;
self.seg_at = seg_at;
self.stack = stack;
self.segments = segments;
self.open_at = open_at;
self.pairs_out = pairs_out;
stop
}
}
pub(crate) fn split_top_level<'a>(
input: &'a str,
line_num: usize,
span: Span,
body: InlineBody,
bounds: InlineBounds<'_>,
has_quotes: bool,
) -> Result<Vec<&'a str>, Error> {
if body == InlineBody::Array || !has_quotes {
return Ok(split_top_level_fast(input, line_num, span, body, bounds));
}
let object = body == InlineBody::Object;
let (open, close) = if object { (b'{', b'}') } else { (b'[', b']') };
let mut sc: Scanner<'_, '_, SplitQ> =
Scanner::new(input, open, close, object, line_num, span, bounds);
sc.in_key = object;
sc.seg_start = object;
sc.value_start = !object;
match sc.run() {
ScanStop::UnterminatedQuote => {
Err(Error::Structured(ErrorKind::UnterminatedInlineCompound {
line: line_num as u32,
span,
}))
}
ScanStop::EofAfterWsSkip => {
if input[sc.seg_at..]
.trim_matches(is_inline_whitespace)
.is_empty()
{
Ok(sc.segments)
} else {
sc.segments.push(&input[sc.seg_at..]);
Ok(sc.segments)
}
}
ScanStop::Exhausted => {
sc.segments.push(&input[sc.seg_at..]);
Ok(sc.segments)
}
ScanStop::Closer { .. } | ScanStop::BadEscape(_) => {
unreachable!("split scanner cannot stop on a closer or bad escape")
}
}
}
fn split_top_level_fast<'a>(
input: &'a str,
line_num: usize,
span: Span,
body: InlineBody,
bounds: InlineBounds<'_>,
) -> Vec<&'a str> {
let object = body == InlineBody::Object;
let (open, close) = if object { (b'{', b'}') } else { (b'[', b']') };
let mut sc: Scanner<'_, '_, SplitFast> =
Scanner::new(input, open, close, object, line_num, span, bounds);
sc.in_key = object;
sc.seg_start = object;
sc.value_start = !object;
match sc.run() {
ScanStop::Exhausted => {
sc.segments.push(&input[sc.seg_at..]);
sc.segments
}
ScanStop::Closer { .. } => {
unreachable!("quote-free split scanner cannot stop on a closer")
}
ScanStop::BadEscape(_) => {
unreachable!("quote-free split scanner never validates escapes")
}
ScanStop::UnterminatedQuote | ScanStop::EofAfterWsSkip => {
unreachable!("quote-free split scanner tracks no quoted key segments")
}
}
}
pub(crate) 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;
}
if !has_quote_bytes(bytes) {
run_find::<FindFast>(input, open, close, open == b'{')
} else {
run_find::<FindQ>(input, open, close, open == b'{')
}
}
fn run_find<C: ScanCfg>(input: &str, open: u8, close: u8, object: bool) -> Option<usize> {
let mut sc: Scanner<'_, '_, C> = Scanner::new(
input,
open,
close,
object,
0,
Span::EMPTY,
InlineBounds::for_input(input),
);
sc.i = 1;
sc.depth = 1;
sc.in_key = object;
sc.seg_start = object;
sc.value_start = !object;
match sc.run() {
ScanStop::Closer { idx, byte } => (byte == close).then_some(idx),
ScanStop::EofAfterWsSkip
| ScanStop::UnterminatedQuote
| ScanStop::BadEscape(_)
| ScanStop::Exhausted => None,
}
}
pub(crate) fn find_unescaped_colon_inline(s: &str) -> Option<usize> {
find_unescaped_colon(s)
}
fn malformed(line_num: usize, span: Span, detail: &str) -> Error {
Error::Structured(ErrorKind::MalformedInlineCompound {
line: line_num as u32,
span,
detail: detail.to_string(),
})
}
pub(crate) fn malformed_closer_not_at_end(line_num: usize, span: Span) -> Error {
malformed(
line_num,
span,
"matching closer is not the last byte of the body; non-whitespace content follows the closed inline compound",
)
}
pub(crate) enum InlineCloserScan {
Found(usize),
NotFound,
BadEscape(Error),
}
pub(crate) fn scan_inline_closer(
input: &str,
open: u8,
close: u8,
line_num: usize,
span: Span,
) -> InlineCloserScan {
let bytes = input.as_bytes();
let object = open == b'{';
if bytes.is_empty() || bytes[0] != open {
return InlineCloserScan::NotFound;
}
if !has_quote_bytes(bytes) {
run_scan::<ScanFast>(input, open, close, object, line_num, span, &mut Vec::new())
} else {
run_scan::<ScanQ>(input, open, close, object, line_num, span, &mut Vec::new())
}
}
pub(crate) fn scan_inline_closer_with_bounds(
input: &str,
open: u8,
close: u8,
line_num: usize,
span: Span,
bounds_out: &mut Vec<(usize, usize)>,
) -> InlineCloserScan {
let bytes = input.as_bytes();
let object = open == b'{';
if bytes.is_empty() || bytes[0] != open {
return InlineCloserScan::NotFound;
}
if !has_quote_bytes(bytes) {
run_scan::<ScanFast>(input, open, close, object, line_num, span, bounds_out)
} else {
run_scan::<ScanQ>(input, open, close, object, line_num, span, bounds_out)
}
}
fn run_scan<C: ScanCfg>(
input: &str,
open: u8,
close: u8,
object: bool,
line_num: usize,
span: Span,
bounds_out: &mut Vec<(usize, usize)>,
) -> InlineCloserScan {
let mut sc: Scanner<'_, '_, C> = Scanner::new(
input,
open,
close,
object,
line_num,
span,
InlineBounds::for_input(input),
);
sc.i = 1;
sc.depth = 1;
sc.in_key = object;
sc.seg_start = object;
sc.value_start = !object;
sc.pairs_out = std::mem::take(bounds_out);
let stop = sc.run();
let mut pairs = std::mem::take(&mut sc.pairs_out);
match stop {
ScanStop::Closer { idx, byte } => {
if byte == close {
#[cfg(test)]
{
let mut cmps = 0usize;
pairs.sort_unstable_by(|a, b| {
cmps += 1;
a.0.cmp(&b.0)
});
ix_probe::record_sort(pairs.len(), cmps);
ix_probe::record_body(input.len(), pairs.len());
}
#[cfg(not(test))]
pairs.sort_unstable_by_key(|p| p.0);
*bounds_out = pairs;
InlineCloserScan::Found(idx)
} else {
InlineCloserScan::NotFound
}
}
ScanStop::EofAfterWsSkip | ScanStop::UnterminatedQuote | ScanStop::Exhausted => {
InlineCloserScan::NotFound
}
ScanStop::BadEscape(e) => InlineCloserScan::BadEscape(e),
}
}