use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use memchr::{memchr, memchr2};
use rustc_hash::FxHashMap;
use crate::error::{CompoundKind, ConflictKind, Error, ErrorKind, Result, Span};
use crate::parser::classify::{is_float_literal, is_pair_shape, try_parse_integer};
use crate::parser::inline::{
decode_key_segment, key_is_single_segment, scan_unescaped_colon, split_key_path, ColonScan,
InlineBody,
};
use crate::parser::leading_bom_len;
use crate::parser::validate::{check_key, KeyValidity};
use crate::whitespace::{
common_leading_whitespace_prefix_len, is_inline_whitespace, is_ktav_whitespace,
};
use super::event::{Event, EventSink, EventStream};
use super::inline_emit::{fast_plain_decimal_i64, scan_inline_events};
pub(crate) fn parse_events<'a>(text: &'a str, bump: &'a Bump) -> Result<(EventStream<'a>, usize)> {
let mut events: EventStream<'a> = BumpVec::with_capacity_in(text.len() / 4 + 64, bump);
let bytes = text.as_bytes();
let start = leading_bom_len(text);
let mut p = EventParser::new(bump);
if memchr(b'\r', bytes).is_none() {
let mut line_num: usize = 0;
let mut line_start: usize = start;
while line_start <= bytes.len() {
let end = memchr(b'\n', &bytes[line_start..])
.map(|p| line_start + p)
.unwrap_or(bytes.len());
let line: &'a str = &text[line_start..end];
line_num += 1;
p.handle_line(line, line_num, line_start as u32, &mut events)?;
if end == bytes.len() {
break;
}
line_start = end + 1;
}
} else {
let mut line_start: usize = start;
let mut line_num: usize = 0;
while line_start < bytes.len() {
let end = memchr2(b'\n', b'\r', &bytes[line_start..])
.map(|p| line_start + p)
.unwrap_or(bytes.len());
let content_end = end;
let next_start = if end < bytes.len() {
if bytes[end] == b'\r' && end + 1 < bytes.len() && bytes[end + 1] == b'\n' {
end + 2 } else {
end + 1 }
} else {
end };
let line: &'a str = &text[line_start..content_end];
line_num += 1;
p.handle_line(line, line_num, line_start as u32, &mut events)?;
line_start = next_start;
}
}
p.finish(bytes.len() as u32, &mut events)?;
Ok((events, p.reopens))
}
const LINEAR_INDEX_THRESHOLD: usize = 8;
struct LinearEntry<'a> {
parent: NodeId,
segment: &'a str,
id: NodeId,
}
pub(crate) struct EventParser<'a> {
pub(crate) bump: &'a Bump,
pub(crate) stack: Vec<Frame<'a>>,
pub(crate) collecting: Option<Collecting<'a>>,
pub(crate) opener_offsets: Vec<u32>,
pub(crate) multiline_opener: Option<u32>,
pub(crate) root_initialized: bool,
pub(crate) root_consumed: bool,
pub(crate) root_is_explicit_compound: bool,
pub(crate) reopens: usize,
staging: Vec<Event<'a>>,
path_node_stack: Vec<NodeId>,
pub(crate) nodes: BumpVec<'a, PathShape>,
linear: BumpVec<'a, LinearEntry<'a>>,
index: Option<FxHashMap<(NodeId, &'a str), NodeId>>,
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) dbg_node_allocs: usize,
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) dbg_index_probes: usize,
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) dbg_entry_compares: usize,
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) dbg_reg_scans: usize,
}
impl<'a> EventParser<'a> {
pub(crate) fn new(bump: &'a Bump) -> Self {
let mut p = EventParser {
bump,
stack: Vec::with_capacity(8),
collecting: None,
opener_offsets: Vec::with_capacity(8),
multiline_opener: None,
root_initialized: false,
root_consumed: false,
root_is_explicit_compound: false,
reopens: 0,
staging: Vec::new(),
path_node_stack: Vec::new(),
nodes: {
let mut nodes = BumpVec::with_capacity_in(16, bump);
nodes.push(PathShape::Object);
nodes
},
linear: BumpVec::with_capacity_in(LINEAR_INDEX_THRESHOLD, bump),
index: None,
dbg_node_allocs: 0,
dbg_index_probes: 0,
dbg_entry_compares: 0,
dbg_reg_scans: 0,
};
p.dbg_node_allocs += 1;
p
}
fn node_shape(&self, id: NodeId) -> PathShape {
self.nodes[id as usize]
}
fn probe(&mut self, parent: NodeId, segment: &str) -> Option<NodeId> {
self.dbg_index_probes += 1;
if let Some(index) = &self.index {
self.dbg_entry_compares += 1;
return index.get(&(parent, segment)).copied();
}
for i in 0..self.linear.len() {
let e = &self.linear[i];
self.dbg_entry_compares += 1;
if e.parent == parent && e.segment == segment {
return Some(e.id);
}
}
None
}
fn add_child(&mut self, parent: NodeId, segment: &'a str, shape: PathShape) -> NodeId {
let id = u32::try_from(self.nodes.len()).expect("path node count overflow");
self.nodes.push(shape);
self.dbg_node_allocs += 1;
if let Some(index) = &mut self.index {
index.insert((parent, segment), id);
} else if self.linear.len() >= LINEAR_INDEX_THRESHOLD {
let mut map = FxHashMap::default();
for e in &self.linear {
map.insert((e.parent, e.segment), e.id);
}
map.insert((parent, segment), id);
self.index = Some(map);
} else {
self.linear.push(LinearEntry {
parent,
segment,
id,
});
}
id
}
fn add_detached_root(&mut self) -> NodeId {
let id = u32::try_from(self.nodes.len()).expect("path node count overflow");
self.nodes.push(PathShape::Object);
self.dbg_node_allocs += 1;
id
}
}
pub(crate) type NodeId = u32;
pub(crate) enum Frame<'a> {
Object {
levels: BumpVec<'a, ObjectLevel<'a>>,
frame_root: NodeId,
},
Array,
}
#[derive(Clone, Copy)]
pub(crate) enum PathShape {
Object,
Leaf(&'static str),
}
pub(crate) struct ObjectLevel<'a> {
prefix: Option<&'a str>,
}
impl<'a> Frame<'a> {
pub(crate) fn new_object(bump: &'a Bump, frame_root: NodeId) -> Self {
let mut levels = BumpVec::with_capacity_in(2, bump);
levels.push(ObjectLevel { prefix: None });
Frame::Object { levels, frame_root }
}
pub(crate) fn new_array() -> Self {
Frame::Array
}
}
#[derive(Copy, Clone)]
pub(crate) enum MultilineMode {
Stripped,
Verbatim,
}
pub(crate) struct Collecting<'a> {
pub(crate) mode: MultilineMode,
pub(crate) lines: BumpVec<'a, &'a str>,
}
impl<'a> EventParser<'a> {
pub(crate) fn finish<S: EventSink<'a>>(
&mut self,
eof_offset: u32,
events: &mut S,
) -> Result<()> {
if let Some(c) = &self.collecting {
let kind = match c.mode {
MultilineMode::Stripped => CompoundKind::MultilineStripped,
MultilineMode::Verbatim => CompoundKind::MultilineVerbatim,
};
let start = self.multiline_opener.unwrap_or(eof_offset);
return Err(Error::Structured(ErrorKind::UnclosedCompound {
kind,
span: Span::new(start, eof_offset),
}));
}
if self.stack.len() > 1 {
let kind = match self.stack.last().unwrap() {
Frame::Object { .. } => CompoundKind::Object,
Frame::Array => CompoundKind::Array,
};
let start = *self.opener_offsets.last().unwrap();
return Err(Error::Structured(ErrorKind::UnclosedCompound {
kind,
span: Span::new(start, eof_offset),
}));
}
if self.root_consumed {
debug_assert!(
self.stack.is_empty(),
"consumed root must have no open frames"
);
return Ok(());
}
match self.stack.last() {
Some(Frame::Object { .. }) => {
self.close_synthetics_until(0, events);
events.push(Event::EndObject);
}
Some(Frame::Array) => {
events.push(Event::EndArray);
}
None => {
events.push(Event::BeginObject);
events.push(Event::EndObject);
}
}
Ok(())
}
pub(crate) fn handle_line<S: EventSink<'a>>(
&mut self,
raw: &'a str,
line_num: usize,
line_start: u32,
events: &mut S,
) -> Result<()> {
if let Some(ref mut c) = self.collecting {
let trimmed = raw.trim_matches(is_ktav_whitespace);
let term = match c.mode {
MultilineMode::Stripped => ")",
MultilineMode::Verbatim => "))",
};
if trimmed.len() <= 2 && trimmed == term {
let collecting = self.collecting.take().unwrap();
let s = finalize_multiline(collecting, self.bump);
self.multiline_opener = None;
return self.attach_scalar(Event::Str(s), line_num, events);
}
c.lines.push(raw);
return Ok(());
}
let trimmed = raw.trim_matches(is_ktav_whitespace);
if trimmed.is_empty() || trimmed.starts_with("##") {
return Ok(());
}
let trimmed_span = trimmed_span_in(raw, trimmed, line_start);
if self.root_consumed {
return Err(Error::Structured(
ErrorKind::OrphanLineAfterTopLevelInline {
line: line_num as u32,
span: trimmed_span,
},
));
}
if !self.root_initialized {
self.root_initialized = true;
if trimmed != "}"
&& trimmed != "]"
&& self.classify_root(trimmed, line_num, trimmed_span, events)?
{
return Ok(());
}
}
if trimmed == "}" {
return self.close_frame(BracketKind::Object, line_num, trimmed_span, events);
}
if trimmed == "]" {
return self.close_frame(BracketKind::Array, line_num, trimmed_span, events);
}
if matches!(self.stack.last(), Some(Frame::Array)) {
self.handle_array_item(trimmed, line_num, trimmed_span, events)
} else {
self.handle_object_pair(trimmed, line_num, trimmed_span, events)
}
}
fn classify_root<S: EventSink<'a>>(
&mut self,
trimmed: &'a str,
line_num: usize,
trimmed_span: Span,
events: &mut S,
) -> Result<bool> {
if trimmed == "{" {
self.root_is_explicit_compound = true;
self.stack.push(Frame::new_object(self.bump, 0));
self.opener_offsets.push(trimmed_span.start);
EventSink::push(events, Event::BeginObject);
return Ok(true);
}
if trimmed == "[" {
self.root_is_explicit_compound = true;
self.stack.push(Frame::new_array());
self.opener_offsets.push(trimmed_span.start);
EventSink::push(events, Event::BeginArray);
return Ok(true);
}
if trimmed.starts_with('{') || trimmed.starts_with('[') {
let kind = if trimmed.starts_with('{') {
InlineBody::Object
} else {
InlineBody::Array
};
scan_inline_events(trimmed, kind, line_num, trimmed_span, self.bump, events)?;
self.root_consumed = true;
return Ok(true);
}
if is_pair_shape(trimmed) {
self.stack.push(Frame::new_object(self.bump, 0));
self.opener_offsets.push(0);
EventSink::push(events, Event::BeginObject);
} else {
self.stack.push(Frame::new_array());
self.opener_offsets.push(0);
EventSink::push(events, Event::BeginArray);
}
Ok(false)
}
fn handle_object_pair<S: EventSink<'a>>(
&mut self,
trimmed: &'a str,
line_num: usize,
trimmed_span: Span,
events: &mut S,
) -> Result<()> {
let colon = match scan_unescaped_colon(trimmed) {
ColonScan::Found(c) => c,
ColonScan::UnterminatedQuote => {
return Err(Error::Structured(ErrorKind::UnterminatedQuotedKey {
line: line_num as u32,
span: trimmed_span,
}));
}
ColonScan::Absent => {
return Err(Error::Structured(ErrorKind::MissingSeparator {
line: line_num as u32,
span: trimmed_span,
}));
}
};
let key = trimmed[..colon].trim_end_matches(is_ktav_whitespace);
let key_start = trimmed_span.start;
let key_end = key_start + key.len() as u32;
if key.is_empty() {
return Err(Error::Structured(ErrorKind::EmptyKey {
line: line_num as u32,
span: Span::new(key_start, key_start + 1),
}));
}
let after_colon = &trimmed[colon + 1..];
let after_colon_off = key_start + (colon as u32) + 1;
let key_span = Span::new(key_start, key_end);
match classify_separator(after_colon) {
Separator::Raw(rest) => {
require_sep_end(rest, line_num, after_colon_off + 1, trimmed_span)?;
self.emit_keyed_scalar(
key,
Event::Str(rest.trim_matches(is_ktav_whitespace)),
line_num,
key_span,
events,
)
}
Separator::Plain => {
require_sep_end(after_colon, line_num, after_colon_off, trimmed_span)?;
let body = after_colon.trim_start_matches(is_ktav_whitespace);
match classify(body, self.bump)? {
ValueStart::Scalar(s) => {
self.emit_keyed_scalar(key, Event::Str(s), line_num, key_span, events)
}
ValueStart::Integer(s) => {
self.emit_keyed_scalar(key, Event::Integer(s), line_num, key_span, events)
}
ValueStart::Float(s) => {
self.emit_keyed_scalar(key, Event::Float(s), line_num, key_span, events)
}
ValueStart::Null => {
self.emit_keyed_scalar(key, Event::Null, line_num, key_span, events)
}
ValueStart::Bool(b) => {
self.emit_keyed_scalar(key, Event::Bool(b), line_num, key_span, events)
}
ValueStart::EmptyObject => self.emit_keyed_compound(
key,
Event::BeginObject,
Event::EndObject,
line_num,
key_span,
events,
),
ValueStart::EmptyArray => self.emit_keyed_compound(
key,
Event::BeginArray,
Event::EndArray,
line_num,
key_span,
events,
),
ValueStart::OpenObject => {
let node = self.emit_keyed_open(
key,
Event::BeginObject,
line_num,
key_span,
events,
)?;
self.stack.push(Frame::new_object(self.bump, node));
self.opener_offsets.push(trimmed_span.end - 1);
Ok(())
}
ValueStart::OpenArray => {
self.emit_keyed_open(key, Event::BeginArray, line_num, key_span, events)?;
self.stack.push(Frame::new_array());
self.opener_offsets.push(trimmed_span.end - 1);
Ok(())
}
ValueStart::OpenMultilineStripped => {
let r = self.emit_keyed_open_multiline(
key,
MultilineMode::Stripped,
line_num,
key_span,
events,
);
self.multiline_opener = Some(trimmed_span.end - 1);
r
}
ValueStart::OpenMultilineVerbatim => {
let r = self.emit_keyed_open_multiline(
key,
MultilineMode::Verbatim,
line_num,
key_span,
events,
);
self.multiline_opener = Some(trimmed_span.end - 2);
r
}
ValueStart::InlineCompound(kind) => {
let mut staging = std::mem::take(&mut self.staging);
staging.clear();
scan_inline_events(
body,
kind,
line_num,
trimmed_span,
self.bump,
&mut staging,
)?;
let (leaf, parent_node) =
self.reconcile_dotted_key(key, line_num, key_span, events)?;
let shape = match staging.first() {
Some(ev) => path_shape_of(ev),
None => unreachable!("inline compound always emits events"),
};
let node = self.register_value_path(
parent_node,
leaf,
shape,
key,
line_num,
key_span,
)?;
if matches!(shape, PathShape::Object) {
self.register_inline_child_paths(node, &staging, line_num, key_span)?;
}
events.push(Event::Key(leaf));
for ev in &staging {
events.push(*ev);
}
self.staging = staging;
Ok(())
}
}
}
}
}
fn emit_keyed_scalar<S: EventSink<'a>>(
&mut self,
key: &'a str,
value: Event<'a>,
line_num: usize,
key_span: Span,
events: &mut S,
) -> Result<()> {
let (leaf, parent_node) = self.reconcile_dotted_key(key, line_num, key_span, events)?;
let label = event_label(&value);
self.register_value_path(
parent_node,
leaf,
PathShape::Leaf(label),
key,
line_num,
key_span,
)?;
events.push(Event::Key(leaf));
events.push(value);
Ok(())
}
fn emit_keyed_compound<S: EventSink<'a>>(
&mut self,
key: &'a str,
open: Event<'a>,
close: Event<'a>,
line_num: usize,
key_span: Span,
events: &mut S,
) -> Result<()> {
let (leaf, parent_node) = self.reconcile_dotted_key(key, line_num, key_span, events)?;
let shape = path_shape_of(&open);
self.register_value_path(parent_node, leaf, shape, key, line_num, key_span)?;
events.push(Event::Key(leaf));
events.push(open);
events.push(close);
Ok(())
}
fn emit_keyed_open<S: EventSink<'a>>(
&mut self,
key: &'a str,
open: Event<'a>,
line_num: usize,
key_span: Span,
events: &mut S,
) -> Result<NodeId> {
let (leaf, parent_node) = self.reconcile_dotted_key(key, line_num, key_span, events)?;
let shape = path_shape_of(&open);
let node = self.register_value_path(parent_node, leaf, shape, key, line_num, key_span)?;
events.push(Event::Key(leaf));
events.push(open);
Ok(node)
}
fn emit_keyed_open_multiline<S: EventSink<'a>>(
&mut self,
key: &'a str,
mode: MultilineMode,
line_num: usize,
key_span: Span,
events: &mut S,
) -> Result<()> {
let (leaf, parent_node) = self.reconcile_dotted_key(key, line_num, key_span, events)?;
self.register_value_path(
parent_node,
leaf,
PathShape::Leaf("string"),
key,
line_num,
key_span,
)?;
events.push(Event::Key(leaf));
self.collecting = Some(Collecting {
mode,
lines: BumpVec::with_capacity_in(8, self.bump),
});
Ok(())
}
fn handle_array_item<S: EventSink<'a>>(
&mut self,
trimmed: &'a str,
line_num: usize,
trimmed_span: Span,
events: &mut S,
) -> Result<()> {
let line_start = trimmed_span.start;
if let Some(rest) = trimmed.strip_prefix("::") {
require_sep_end(rest, line_num, line_start + 2, trimmed_span)?;
events.push(Event::Str(rest.trim_start_matches(is_ktav_whitespace)));
return Ok(());
}
match classify(trimmed, self.bump)? {
ValueStart::Scalar(s) => events.push(Event::Str(s)),
ValueStart::Integer(s) => events.push(Event::Integer(s)),
ValueStart::Float(s) => events.push(Event::Float(s)),
ValueStart::Null => events.push(Event::Null),
ValueStart::Bool(b) => events.push(Event::Bool(b)),
ValueStart::EmptyObject => {
events.push(Event::BeginObject);
events.push(Event::EndObject);
}
ValueStart::EmptyArray => {
events.push(Event::BeginArray);
events.push(Event::EndArray);
}
ValueStart::OpenObject => {
events.push(Event::BeginObject);
let fr = self.add_detached_root();
self.stack.push(Frame::new_object(self.bump, fr));
self.opener_offsets.push(trimmed_span.end - 1);
}
ValueStart::OpenArray => {
events.push(Event::BeginArray);
self.stack.push(Frame::new_array());
self.opener_offsets.push(trimmed_span.end - 1);
}
ValueStart::OpenMultilineStripped => {
self.collecting = Some(Collecting {
mode: MultilineMode::Stripped,
lines: BumpVec::with_capacity_in(8, self.bump),
});
self.multiline_opener = Some(trimmed_span.end - 1);
}
ValueStart::OpenMultilineVerbatim => {
self.collecting = Some(Collecting {
mode: MultilineMode::Verbatim,
lines: BumpVec::with_capacity_in(8, self.bump),
});
self.multiline_opener = Some(trimmed_span.end - 2);
}
ValueStart::InlineCompound(kind) => {
scan_inline_events(trimmed, kind, line_num, trimmed_span, self.bump, events)?
}
}
Ok(())
}
fn attach_scalar<S: EventSink<'a>>(
&mut self,
value: Event<'a>,
_line_num: usize,
events: &mut S,
) -> Result<()> {
events.push(value);
Ok(())
}
fn reconcile_dotted_key<S: EventSink<'a>>(
&mut self,
key: &'a str,
line_num: usize,
key_span: Span,
events: &mut S,
) -> Result<(&'a str, NodeId)> {
if key_is_single_segment(key) {
self.close_synthetics_to_real(events);
match check_key(key) {
KeyValidity::Valid => {}
KeyValidity::Empty => {
return Err(Error::Structured(ErrorKind::EmptyKey {
line: line_num as u32,
span: key_span,
}));
}
KeyValidity::Invalid => {
return Err(Error::Structured(ErrorKind::InvalidKey {
line: line_num as u32,
key: key.to_string(),
span: key_span,
}));
}
}
let leaf = self.decode_key_in_arena(key, line_num, key_span)?;
let frame_root = match self.stack.last() {
Some(Frame::Object { frame_root, .. }) => *frame_root,
_ => unreachable!("dispatched as object"),
};
return Ok((leaf, frame_root));
}
let raw_segments = split_key_path(key);
debug_assert!(raw_segments.len() >= 2);
let mut decoded_segments: Vec<&'a str> = Vec::with_capacity(raw_segments.len());
for seg in &raw_segments {
let trimmed = seg.trim_matches(is_inline_whitespace);
match check_key(trimmed) {
KeyValidity::Valid => {}
KeyValidity::Empty => {
return Err(Error::Structured(ErrorKind::EmptyKey {
line: line_num as u32,
span: key_span,
}));
}
KeyValidity::Invalid => {
return Err(Error::Structured(ErrorKind::InvalidKey {
line: line_num as u32,
key: key.to_string(),
span: key_span,
}));
}
}
let decoded = self.decode_key_in_arena(trimmed, line_num, key_span)?;
decoded_segments.push(decoded);
}
let leaf = *decoded_segments.last().unwrap();
let frame_root = match self.stack.last() {
Some(Frame::Object { frame_root, .. }) => *frame_root,
_ => unreachable!("dispatched as object"),
};
let mut cur = frame_root;
let mut prefix_existed = vec![false; decoded_segments.len()];
for k in 0..decoded_segments.len() - 1 {
let seg = decoded_segments[k];
match self.probe(cur, seg) {
Some(id) if matches!(self.node_shape(id), PathShape::Leaf(_)) => {
return Err(Error::Structured(ErrorKind::KeyPathConflict {
line: line_num as u32,
path: key.to_string(),
kind: ConflictKind::BlockedByValue,
span: key_span,
}));
}
Some(id) => {
prefix_existed[k + 1] = true;
cur = id;
}
None => {
cur = self.add_child(cur, seg, PathShape::Object);
}
}
}
let prefix_segments = &decoded_segments[..decoded_segments.len() - 1];
let cur_levels_len = match self.stack.last().unwrap() {
Frame::Object { levels, .. } => levels.len(),
_ => unreachable!("dispatched as object"),
};
let mut lcp_count: usize = 0;
let mut pending_seg_idx: Option<usize> = None;
for (i, seg) in prefix_segments.iter().enumerate() {
if lcp_count + 1 >= cur_levels_len {
pending_seg_idx = Some(i);
break;
}
let cur_prefix = match self.stack.last().unwrap() {
Frame::Object { levels, .. } => levels[1 + lcp_count].prefix.unwrap(),
_ => unreachable!(),
};
if *seg != cur_prefix {
pending_seg_idx = Some(i);
break;
}
lcp_count += 1;
}
let pops = cur_levels_len - 1 - lcp_count;
for _ in 0..pops {
self.pop_synthetic_level(events);
}
let push_start = pending_seg_idx.unwrap_or(prefix_segments.len());
for (i, seg) in prefix_segments.iter().enumerate().skip(push_start) {
if prefix_existed[i + 1] {
self.reopens += 1;
}
self.push_synthetic(seg, events);
}
Ok((leaf, cur))
}
fn decode_key_in_arena(
&self,
seg: &'a str,
line_num: usize,
key_span: Span,
) -> Result<&'a str> {
let is_quoted = seg
.as_bytes()
.first()
.is_some_and(|&b| b == b'"' || b == b'\'' || b == b'`');
if !is_quoted && !seg.as_bytes().contains(&b'\\') {
return Ok(seg);
}
if is_quoted {
debug_assert!(seg.len() >= 2 && seg.as_bytes()[seg.len() - 1] == seg.as_bytes()[0]);
let interior = &seg[1..seg.len() - 1];
if !interior.as_bytes().contains(&b'\\') {
return Ok(interior);
}
}
let decoded = decode_key_segment(seg, line_num, key_span)?;
Ok(self.bump.alloc_str(&decoded))
}
#[inline]
fn push_synthetic<S: EventSink<'a>>(&mut self, seg: &'a str, events: &mut S) {
events.push(Event::Key(seg));
events.push(Event::BeginObject);
match self.stack.last_mut().unwrap() {
Frame::Object { levels, .. } => levels.push(ObjectLevel { prefix: Some(seg) }),
_ => unreachable!(),
}
}
fn close_synthetics_to_real<S: EventSink<'a>>(&mut self, events: &mut S) {
let cur_levels_len = match self.stack.last().unwrap() {
Frame::Object { levels, .. } => levels.len(),
_ => return,
};
let pops = cur_levels_len - 1;
for _ in 0..pops {
self.pop_synthetic_level(events);
}
}
pub(crate) fn close_synthetics_until<S: EventSink<'a>>(
&mut self,
target_synthetic_count: usize,
events: &mut S,
) {
loop {
let cur = match self.stack.last() {
Some(Frame::Object { levels, .. }) => levels.len() - 1,
_ => return,
};
if cur <= target_synthetic_count {
return;
}
self.pop_synthetic_level(events);
}
}
fn pop_synthetic_level<S: EventSink<'a>>(&mut self, events: &mut S) {
match self.stack.last_mut().unwrap() {
Frame::Object { levels, .. } => {
levels.pop();
events.push(Event::EndObject);
}
_ => unreachable!(),
}
}
fn register_value_path(
&mut self,
parent_node: NodeId,
leaf: &'a str,
shape: PathShape,
raw_key: &str,
line_num: usize,
key_span: Span,
) -> Result<NodeId> {
let frame_root = match self.stack.last() {
Some(Frame::Object { frame_root, .. }) => *frame_root,
_ => unreachable!("only objects have keys"),
};
let dotted = parent_node != frame_root;
match self.probe(parent_node, leaf) {
Some(_) if dotted => Err(Error::Structured(ErrorKind::DuplicateKey {
line: line_num as u32,
key: raw_key.to_string(),
span: key_span,
})),
Some(id) => {
let existing = self.node_shape(id);
match existing {
PathShape::Object => match shape {
PathShape::Leaf(label) => {
Err(Error::Structured(ErrorKind::KeyPathConflict {
line: line_num as u32,
path: raw_key.to_string(),
kind: ConflictKind::Overwrite {
existing: "object",
new_kind: label,
},
span: key_span,
}))
}
PathShape::Object => Err(Error::Structured(ErrorKind::DuplicateKey {
line: line_num as u32,
key: raw_key.to_string(),
span: key_span,
})),
},
PathShape::Leaf(existing) => match shape {
PathShape::Leaf(_) => Err(Error::Structured(ErrorKind::DuplicateKey {
line: line_num as u32,
key: raw_key.to_string(),
span: key_span,
})),
PathShape::Object => Err(Error::Structured(ErrorKind::KeyPathConflict {
line: line_num as u32,
path: raw_key.to_string(),
kind: ConflictKind::Overwrite {
existing,
new_kind: "object",
},
span: key_span,
})),
},
}
}
None => Ok(self.add_child(parent_node, leaf, shape)),
}
}
fn register_inline_child_paths(
&mut self,
base_node: NodeId,
events: &[Event<'a>],
line_num: usize,
key_span: Span,
) -> Result<()> {
debug_assert!(matches!(events.first(), Some(Event::BeginObject)));
debug_assert!(matches!(events.last(), Some(Event::EndObject)));
let inner = &events[1..events.len() - 1];
let mut node_stack = std::mem::take(&mut self.path_node_stack);
node_stack.clear();
node_stack.push(base_node);
let result = self.register_inline_child_walk(inner, &mut node_stack, line_num, key_span);
self.path_node_stack = node_stack;
result
}
fn register_inline_child_walk(
&mut self,
inner: &[Event<'a>],
node_stack: &mut Vec<NodeId>,
line_num: usize,
key_span: Span,
) -> Result<()> {
let mut array_depth: usize = 0;
let mut i = 0;
while i < inner.len() {
self.dbg_reg_scans += 1;
if array_depth > 0 {
match inner[i] {
Event::BeginArray => array_depth += 1,
Event::EndArray => array_depth -= 1,
_ => {}
}
i += 1;
continue;
}
match inner[i] {
Event::Key(k) => {
self.dbg_reg_scans += 1;
let value_ev = &inner[i + 1];
let shape = path_shape_of(value_ev);
let parent = match node_stack.last() {
Some(&p) => p,
None => unreachable!("object node stack underflow"),
};
let child_node =
self.register_value_path(parent, k, shape, k, line_num, key_span)?;
if matches!(value_ev, Event::BeginObject) {
node_stack.push(child_node);
} else if matches!(value_ev, Event::BeginArray) {
array_depth = 1;
}
i += 2;
}
Event::EndObject => {
debug_assert!(node_stack.len() > 1, "unbalanced object node stack");
node_stack.pop();
i += 1;
}
other => unreachable!("pair position must be Key, got {other:?}"),
}
}
debug_assert_eq!(node_stack.len(), 1, "unbalanced object node stack");
debug_assert_eq!(array_depth, 0, "unterminated inline array");
Ok(())
}
fn close_frame<S: EventSink<'a>>(
&mut self,
expected: BracketKind,
line_num: usize,
trimmed_span: Span,
events: &mut S,
) -> Result<()> {
if self.stack.len() == 1 && self.root_is_explicit_compound {
let frame_kind = match self.stack.last() {
Some(Frame::Object { .. }) => BracketKind::Object,
_ => BracketKind::Array,
};
if frame_kind as u8 != expected as u8 {
return Err(Error::Structured(ErrorKind::UnbalancedBracket {
line: line_num as u32,
span: trimmed_span,
expected: frame_kind.to_compound(),
found: expected.close(),
}));
}
if matches!(self.stack.last(), Some(Frame::Object { .. })) {
self.close_synthetics_to_real(events);
}
let got = match self.stack.pop().unwrap() {
Frame::Object { .. } => BracketKind::Object,
Frame::Array => BracketKind::Array,
};
let _ = self.opener_offsets.pop();
self.root_consumed = true;
let close_event = match got {
BracketKind::Object => Event::EndObject,
BracketKind::Array => Event::EndArray,
};
events.push(close_event);
return Ok(());
}
if self.stack.len() <= 1 {
return Err(Error::Structured(ErrorKind::UnbalancedBracket {
line: line_num as u32,
span: trimmed_span,
expected: expected.to_compound(),
found: expected.close(),
}));
}
if matches!(self.stack.last(), Some(Frame::Object { .. })) {
self.close_synthetics_to_real(events);
}
let popped = self.stack.pop().unwrap();
let got = match &popped {
Frame::Object { .. } => BracketKind::Object,
Frame::Array => BracketKind::Array,
};
let _ = self.opener_offsets.pop();
if got as u8 != expected as u8 {
return Err(Error::Structured(ErrorKind::UnbalancedBracket {
line: line_num as u32,
span: trimmed_span,
expected: got.to_compound(),
found: expected.close(),
}));
}
let close_event = match got {
BracketKind::Object => Event::EndObject,
BracketKind::Array => Event::EndArray,
};
events.push(close_event);
Ok(())
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
enum BracketKind {
Object = 0,
Array = 1,
}
impl BracketKind {
fn close(self) -> char {
match self {
BracketKind::Object => '}',
BracketKind::Array => ']',
}
}
fn to_compound(self) -> CompoundKind {
match self {
BracketKind::Object => CompoundKind::Object,
BracketKind::Array => CompoundKind::Array,
}
}
}
enum ValueStart<'a> {
Scalar(&'a str),
Integer(&'a str),
Float(&'a str),
Null,
Bool(bool),
EmptyObject,
EmptyArray,
OpenObject,
OpenArray,
OpenMultilineStripped,
OpenMultilineVerbatim,
InlineCompound(InlineBody),
}
enum Separator<'a> {
Raw(&'a str),
Plain,
}
#[inline]
fn require_sep_end(rest: &str, line_num: usize, body_off: u32, trimmed_span: Span) -> Result<()> {
if rest.is_empty() || rest.starts_with(is_ktav_whitespace) {
Ok(())
} else {
Err(Error::Structured(ErrorKind::MissingSeparatorSpace {
line: line_num as u32,
column: 0,
marker: ':',
span: Span::new(body_off, trimmed_span.end),
}))
}
}
fn trimmed_span_in(raw: &str, trimmed: &str, line_start: u32) -> Span {
if trimmed.is_empty() {
return Span::new(line_start, line_start);
}
let raw_ptr = raw.as_ptr() as usize;
let trim_ptr = trimmed.as_ptr() as usize;
debug_assert!(trim_ptr >= raw_ptr && trim_ptr - raw_ptr <= raw.len());
let off = (trim_ptr - raw_ptr) as u32;
let start = line_start + off;
Span::new(start, start + trimmed.len() as u32)
}
#[inline]
fn classify_separator<'a>(after_colon: &'a str) -> Separator<'a> {
if let Some(rest) = after_colon.strip_prefix(':') {
return Separator::Raw(rest);
}
Separator::Plain
}
#[inline]
fn event_label(ev: &Event<'_>) -> &'static str {
match ev {
Event::Null => "null",
Event::Bool(_) => "bool",
Event::Integer(_) => "integer",
Event::Float(_) => "float",
Event::Str(_) => "string",
Event::BeginArray => "array",
Event::BeginObject => "object",
_ => unreachable!("not a value-start event"),
}
}
#[inline]
fn path_shape_of(ev: &Event<'_>) -> PathShape {
match ev {
Event::BeginObject => PathShape::Object,
other => PathShape::Leaf(event_label(other)),
}
}
#[inline]
fn classify<'a>(trimmed: &'a str, bump: &'a Bump) -> Result<ValueStart<'a>> {
if trimmed == "{" {
return Ok(ValueStart::OpenObject);
}
if trimmed == "[" {
return Ok(ValueStart::OpenArray);
}
if trimmed.starts_with('{') {
if trimmed.ends_with('}')
&& trimmed[1..trimmed.len() - 1]
.trim_matches(is_ktav_whitespace)
.is_empty()
{
return Ok(ValueStart::EmptyObject);
}
return Ok(ValueStart::InlineCompound(InlineBody::Object));
}
if trimmed.starts_with('[') {
if trimmed.ends_with(']')
&& trimmed[1..trimmed.len() - 1]
.trim_matches(is_ktav_whitespace)
.is_empty()
{
return Ok(ValueStart::EmptyArray);
}
return Ok(ValueStart::InlineCompound(InlineBody::Array));
}
match trimmed {
"(" => return Ok(ValueStart::OpenMultilineStripped),
"((" => return Ok(ValueStart::OpenMultilineVerbatim),
"()" | "(())" => return Ok(ValueStart::Scalar("")),
_ => {}
}
match trimmed {
"null" => return Ok(ValueStart::Null),
"true" => return Ok(ValueStart::Bool(true)),
"false" => return Ok(ValueStart::Bool(false)),
_ => {}
}
if let Some(_val) = fast_plain_decimal_i64(trimmed) {
return Ok(ValueStart::Integer(trimmed));
}
if let Some(val) = try_parse_integer(trimmed) {
let mut buf = itoa::Buffer::new();
let canonical = buf.format(val);
let s = bump.alloc_str(canonical);
return Ok(ValueStart::Integer(s));
}
if is_float_literal(trimmed) {
let has_underscore = trimmed.as_bytes().contains(&b'_');
if has_underscore {
let cleaned: String = trimmed.chars().filter(|&c| c != '_').collect();
if let Ok(val) = cleaned.parse::<f64>() {
if !val.is_nan() && !val.is_infinite() {
let mut buf = ryu::Buffer::new();
let canonical = buf.format(val);
let s = bump.alloc_str(canonical);
return Ok(ValueStart::Float(s));
}
}
} else if let Ok(val) = trimmed.parse::<f64>() {
if !val.is_nan() && !val.is_infinite() {
let mut buf = ryu::Buffer::new();
let canonical = buf.format(val);
if canonical == trimmed {
return Ok(ValueStart::Float(trimmed));
}
let s = bump.alloc_str(canonical);
return Ok(ValueStart::Float(s));
}
}
}
Ok(ValueStart::Scalar(trimmed))
}
fn finalize_multiline<'a>(c: Collecting<'a>, bump: &'a Bump) -> &'a str {
match c.mode {
MultilineMode::Verbatim if c.lines.len() == 1 => c.lines[0],
MultilineMode::Verbatim => {
let joined = c.lines.join("\n");
bump.alloc_str(&joined)
}
MultilineMode::Stripped if c.lines.len() == 1 => {
let only = c.lines[0];
if only.trim_matches(is_ktav_whitespace).is_empty() {
""
} else {
only.trim_start_matches(is_ktav_whitespace)
.trim_end_matches(is_ktav_whitespace)
}
}
MultilineMode::Stripped => {
let dedented = dedent(&c.lines);
bump.alloc_str(&dedented)
}
}
}
fn dedent(lines: &[&str]) -> String {
let common_len = common_leading_whitespace_prefix_len(lines.iter().copied());
let mut cap: usize = lines
.iter()
.filter(|l| !l.trim_matches(is_ktav_whitespace).is_empty())
.map(|l| l.len() - common_len)
.sum();
cap = cap.saturating_add(lines.len());
let mut out = String::with_capacity(cap);
for (i, l) in lines.iter().enumerate() {
if i > 0 {
out.push('\n');
}
if l.trim_matches(is_ktav_whitespace).is_empty() {
} else if common_len > 0 && l.len() >= common_len {
out.push_str(l[common_len..].trim_end_matches(is_ktav_whitespace));
} else {
out.push_str(l.trim_end_matches(is_ktav_whitespace));
}
}
out
}
#[cfg(test)]
mod counter_tests {
use super::*;
fn parse_counters(doc: &str) -> (usize, usize, usize) {
let bump = Bump::new();
let mut p = EventParser::new(&bump);
let mut events: EventStream<'_> = BumpVec::with_capacity_in(64, &bump);
let bytes = doc.as_bytes();
let mut line_num: usize = 0;
let mut line_start: usize = 0;
while line_start <= bytes.len() {
let end = memchr(b'\n', &bytes[line_start..])
.map(|p| line_start + p)
.unwrap_or(bytes.len());
let line: &str = &doc[line_start..end];
line_num += 1;
p.handle_line(line, line_num, line_start as u32, &mut events)
.unwrap();
if end == bytes.len() {
break;
}
line_start = end + 1;
}
p.finish(bytes.len() as u32, &mut events).unwrap();
(p.dbg_node_allocs, p.dbg_index_probes, p.dbg_entry_compares)
}
fn registration_scans(doc: &str) -> usize {
let bump = Bump::new();
let mut p = EventParser::new(&bump);
let mut events: EventStream<'_> = BumpVec::with_capacity_in(64, &bump);
let bytes = doc.as_bytes();
let mut line_num: usize = 0;
let mut line_start: usize = 0;
while line_start <= bytes.len() {
let end = memchr(b'\n', &bytes[line_start..])
.map(|p| line_start + p)
.unwrap_or(bytes.len());
let line: &str = &doc[line_start..end];
line_num += 1;
p.handle_line(line, line_num, line_start as u32, &mut events)
.unwrap();
if end == bytes.len() {
break;
}
line_start = end + 1;
}
p.finish(bytes.len() as u32, &mut events).unwrap();
p.dbg_reg_scans
}
#[test]
fn inline_child_registration_scans_are_linear() {
for d in [4usize, 8, 16, 32] {
let key = format!("{}x", "a.".repeat(d));
let doc = format!("root: {{{key}: 1}}");
assert_eq!(
registration_scans(&doc),
3 * d + 2,
"registration event views at D={d}"
);
}
}
#[test]
fn dotted_inline_compound_expansion_is_unchanged() {
let doc = "root: {a.a.a.a.x: 1}";
let v: serde_json::Value = crate::from_str(doc).unwrap();
assert_eq!(
serde_json::to_string(&v).unwrap(),
r#"{"root":{"a":{"a":{"a":{"a":{"x":1}}}}}}"#
);
let v: serde_json::Value = crate::from_str("a: {x: 1}\na.y: 2").unwrap();
assert_eq!(serde_json::to_string(&v).unwrap(), r#"{"a":{"x":1,"y":2}}"#);
let err = crate::from_str::<serde_json::Value>("a: {x: 1}\na.x: 2").unwrap_err();
assert!(err.to_string().contains("duplicate key"), "{err}");
}
#[test]
fn deep_chain_path_metadata_is_linear() {
for d in [4usize, 8, 16, 32] {
let mut doc = String::new();
for _ in 0..d {
doc.push_str("a: {\n");
}
doc.push_str("x: 1\n");
for _ in 0..d {
doc.push_str("}\n");
}
let (allocs, probes, _) = parse_counters(&doc);
assert_eq!(allocs, d + 2, "node allocs at D={d}");
assert_eq!(probes, d + 1, "index probes at D={d}");
}
let allocs_of = |d: usize| {
let mut doc = String::new();
for _ in 0..d {
doc.push_str("a: {\n");
}
doc.push_str("x: 1\n");
for _ in 0..d {
doc.push_str("}\n");
}
parse_counters(&doc).0
};
let a4 = allocs_of(4);
let a8 = allocs_of(8);
let a16 = allocs_of(16);
let a32 = allocs_of(32);
assert!(a8 <= 3 * a4);
assert!(a16 <= 3 * a8);
assert!(a32 <= 3 * a16);
}
#[test]
fn flat_object_index_probes_are_linear() {
let build = |k: usize| {
let mut doc = String::new();
for i in 0..k {
doc.push_str(&format!("k{i}: {i}\n"));
}
doc
};
for k in [8usize, 16, 32, 64] {
let (allocs, probes, compares) = parse_counters(&build(k));
assert_eq!(allocs, k + 1, "node allocs at K={k}");
assert_eq!(probes, k, "index probes at K={k}");
let expected_compares = if k <= 8 {
k * (k - 1) / 2
} else {
28 + 8 + (k - 9)
};
assert_eq!(compares, expected_compares, "entry compares at K={k}");
}
let counters_of = |k: usize| parse_counters(&build(k));
let probes_of = |k: usize| parse_counters(&build(k)).1;
let p8 = probes_of(8);
let p16 = counters_of(16).1;
let p32 = counters_of(32).1;
let p64 = counters_of(64).1;
assert!(p16 <= 3 * p8);
assert!(p32 <= 3 * p16);
assert!(p64 <= 3 * p32);
assert!(counters_of(32).2 <= 3 * counters_of(16).2);
assert!(counters_of(64).2 <= 3 * counters_of(32).2);
}
}