use std::borrow::Cow;
use std::collections::HashMap;
use oneyaml_core::{Diagnostic, DuplicateKeys, Settings, Span};
use oneyaml_syntax::tree::SyntaxTree;
use super::Src;
use crate::construct::build::{Cx, build_from};
use crate::construct::expand::{recursive_alias, undefined_alias};
use crate::construct::keys::{KeyMap, KeyRepr, Merge, duplicate_at, invalid_merge, key_text};
use crate::construct::merge::MergeAcc;
use crate::construct::scalar::{is_merge_key, resolve, resolve_collection, scalar_value};
use crate::construct::tree::{Replay, TreeSource};
use crate::construct::{Ev, Node, Pull, Source, Tag as EvTag};
use crate::error::Error;
use crate::resolve::{CollectionKind, Resolved, ResolvedCollection, Resolver};
use crate::value::walk::{OPAQUE_ALIAS, OPAQUE_MERGED};
use crate::value::{Mapping, Sequence, Tag, TaggedValue, Value};
#[cfg(test)]
thread_local! {
pub(crate) static PLANNED_EVENTS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
pub(crate) static PLANNED_MERGE_VALUES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
pub(crate) static COUNT_CAP: std::cell::Cell<usize> = const { std::cell::Cell::new(usize::MAX) };
}
fn count_add(a: usize, b: usize) -> usize {
let sum = a.saturating_add(b);
#[cfg(test)]
let sum = sum.min(COUNT_CAP.with(std::cell::Cell::get));
sum
}
struct Ent {
k: usize,
v: usize,
val: Option<Value>,
}
type Acc<'de> = MergeAcc<KeyRepr<'de>, Ent>;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Shape {
Scalar,
Map,
MapSeq,
Other,
}
enum Item {
Leaf(usize),
Coll {
start: Option<usize>,
end: Option<usize>,
kids: Vec<usize>,
},
}
struct Done<'de> {
item: usize,
span: Span,
replayed: bool,
shape: Shape,
key: Option<KeyRepr<'de>>,
value: Option<Value>,
source: Option<Box<Acc<'de>>>,
unknown: bool,
}
enum Pending<'de> {
Key {
key: KeyRepr<'de>,
span: Span,
replayed: bool,
item: usize,
},
Merge(Span, bool),
}
#[derive(Default)]
struct MapState<'de> {
seen: KeyMap<KeyRepr<'de>, usize>,
first: Vec<Span>,
picks: Vec<(usize, usize, Option<Value>)>,
keep: bool,
pending: Option<Pending<'de>>,
merge: Merge<KeyRepr<'de>, Ent>,
unknown: bool,
}
enum Body<'de> {
Seq {
kids: Vec<usize>,
all_maps: bool,
source: Option<Box<Acc<'de>>>,
vals: Vec<Value>,
unknown: bool,
len: usize,
},
Map(Box<MapState<'de>>),
}
struct Frame<'de> {
start: Option<usize>,
span: Span,
replayed: bool,
custom: bool,
merge_part: bool,
in_key: bool,
count: (usize, usize),
extra: Option<Box<Extra<'de>>>,
body: Body<'de>,
}
struct Extra<'de> {
tag: Option<Tag>,
anchor: Option<AnchorAt<'de>>,
}
struct AnchorAt<'de> {
name: &'de str,
serial: usize,
slot: Option<u32>,
}
const NO_SLOT: u32 = u32::MAX;
struct Anchored<'de> {
shape: Shape,
key: Option<KeyRepr<'de>>,
merge: Option<Box<RawScalar<'de>>>,
def: Def,
prev: u32,
}
enum Def {
Open(usize),
Node { slot: u32, size: u32, bytes: u32 },
Built(Box<Built>),
Opaque { slot: u32, size: u32, bytes: u32 },
}
impl Def {
fn slot(&self) -> Option<u32> {
match self {
Self::Open(_) => None,
Self::Node { slot, .. } | Self::Opaque { slot, .. } => {
(*slot != NO_SLOT).then_some(*slot)
}
Self::Built(b) => b.slot,
}
}
fn size(&self) -> usize {
match self {
Self::Open(_) => 0,
Self::Node { size, .. } | Self::Opaque { size, .. } => *size as usize,
Self::Built(b) => b.nodes,
}
}
fn bytes(&self) -> usize {
match self {
Self::Open(_) => 0,
Self::Node { bytes, .. } | Self::Opaque { bytes, .. } => *bytes as usize,
Self::Built(b) => b.bytes,
}
}
}
fn saturate(n: usize) -> u32 {
u32::try_from(n).unwrap_or(u32::MAX)
}
fn broken(what: &'static str) -> Error {
debug_assert!(false, "{what}");
Error::internal(what, None)
}
fn replay_counted(counted: (u32, u32), produced: (usize, usize)) -> Result<(), Error> {
replay_counts(counted, produced)
.inspect_err(|_| debug_assert!(false, "replay count drifted from the walk's"))
}
fn replay_counts(counted: (u32, u32), produced: (usize, usize)) -> Result<(), Error> {
let agrees = |c: u32, n: usize| c == u32::MAX || n == c as usize;
if agrees(counted.0, produced.0) && agrees(counted.1, produced.1) {
Ok(())
} else {
Err(Error::internal(
"replay count drifted from the walk's",
None,
))
}
}
struct Built {
value: Value,
nodes: usize,
bytes: usize,
slot: Option<u32>,
}
struct Defs<'h, 'de> {
tree: &'de SyntaxTree,
anchors: Option<&'h HashMap<&'de str, Anchored<'de>>>,
history: &'h HashMap<&'de str, Vec<u32>>,
}
impl<'de> Defs<'_, 'de> {
fn replay(&self, slot: u32) -> Result<Replay<'de>, Error> {
Replay::at(self.tree, slot).ok_or_else(|| broken("anchored node gone"))
}
fn before(&self, name: &str, at: usize) -> Result<Option<Replay<'de>>, Error> {
let Some(a) = self.anchors.and_then(|a| a.get(name)) else {
return Ok(None);
};
if let Some(slot) = a.def.slot() {
let r = self.replay(slot)?;
if r.start() < at {
return Ok(Some(r));
}
}
if a.prev != NO_SLOT {
let r = self.replay(a.prev)?;
if r.start() < at {
return Ok(Some(r));
}
}
let Some(defs) = self.history.get(name) else {
return Ok(None);
};
let mut dead = false;
let i = defs.partition_point(|s| match Replay::at(self.tree, *s) {
Some(r) => r.start() < at,
None => {
dead = true;
false
}
});
if dead {
return Err(broken("anchored node gone"));
}
match i.checked_sub(1).and_then(|i| defs.get(i)) {
Some(s) => self.replay(*s).map(Some),
None => Ok(None),
}
}
}
struct AliasMeter {
limits: oneyaml_core::Limits,
own: usize,
input: usize,
start: usize,
spent: usize,
bytes: usize,
}
impl AliasMeter {
fn new(limits: &oneyaml_core::Limits) -> Self {
Self {
limits: limits.clone(),
own: 0,
input: 0,
start: 0,
spent: 0,
bytes: 0,
}
}
fn reset(&mut self, start: usize) {
self.own = 0;
self.input = 0;
self.start = start;
self.spent = 0;
self.bytes = 0;
}
fn read_to(&mut self, end: usize) {
self.input = self.input.max(end.saturating_sub(self.start));
}
fn left(&self) -> (usize, usize) {
let nodes = self
.limits
.get_alias_budget(self.own)
.map_or(usize::MAX, |b| b.saturating_sub(self.spent));
let bytes = self
.limits
.get_alias_byte_budget(self.input)
.map_or(usize::MAX, |b| b.saturating_sub(self.bytes));
(nodes, bytes)
}
fn take(&mut self, nodes: usize, bytes: usize) -> bool {
let (n, b) = self.left();
if nodes > n || bytes > b {
return false;
}
self.charge(nodes, bytes);
true
}
fn charge(&mut self, nodes: usize, bytes: usize) {
self.spent = self.spent.saturating_add(nodes);
self.bytes = self.bytes.saturating_add(bytes);
}
}
struct RawScalar<'de> {
tag: EvTag<'de>,
plain: bool,
value: Cow<'de, str>,
}
fn scalar_key<'de>(r: Resolved<'_>, value: &Cow<'de, str>, tag: &EvTag<'_>) -> KeyRepr<'de> {
match r {
Resolved::Str => KeyRepr::Str(value.clone()),
other => KeyRepr::of_value(scalar_value(other, value.clone(), tag)),
}
}
struct Walker<'de, 'w> {
record: bool,
inline: bool,
anchors: Option<HashMap<&'de str, Anchored<'de>>>,
serial: usize,
copies: AliasMeter,
replay: Option<Replay<'de>>,
tree: Option<&'de SyntaxTree>,
history: HashMap<&'de str, Vec<u32>>,
resolver: Resolver,
policy: DuplicateKeys,
warnings: &'w mut crate::diag_cap::Capped,
stack: Vec<Frame<'de>>,
items: Vec<Item>,
#[allow(clippy::vec_box)]
pool: Vec<Box<MapState<'de>>>,
}
#[derive(Default)]
pub(crate) struct Scratch<'de> {
stack: Vec<Frame<'de>>,
#[allow(clippy::vec_box)]
pool: Vec<Box<MapState<'de>>>,
}
fn wrap(tag: Option<Tag>, value: Value) -> Value {
match tag {
None => value,
Some(tag) => Value::Tagged(Box::new(TaggedValue { tag, value })),
}
}
impl<'de> Walker<'de, '_> {
fn key_position(&self) -> bool {
matches!(self.stack.last(), Some(Frame { body: Body::Map(m), .. }) if m.pending.is_none())
}
fn in_key(&self) -> bool {
self.stack.last().is_some_and(|f| f.in_key)
}
fn count(&mut self, nodes: usize, bytes: usize) {
if let Some(f) = self.stack.last_mut() {
f.count = (count_add(f.count.0, nodes), count_add(f.count.1, bytes));
}
}
fn push_item(&mut self, item: Item) -> usize {
if !self.record {
return 0;
}
self.items.push(item);
self.items.len().saturating_sub(1)
}
fn scalar(&mut self, idx: usize, ev: &Ev<'de>) -> Result<Option<Done<'de>>, Error> {
let Node::Scalar { value, plain, tag } = &ev.node else {
return Err(Error::internal("scalar expected", Some(ev.span)));
};
self.count(1, value.len());
let key_pos = self.key_position();
let merge = is_merge_key(&self.resolver, tag, *plain, value);
let anchor = ev.anchor.filter(|_| self.inline);
let raw = || {
Box::new(RawScalar {
tag: tag.clone(),
plain: *plain,
value: value.clone(),
})
};
if key_pos && merge {
if let Some(name) = anchor {
self.anchor(name, Shape::Scalar, None, Some(raw()), None, value.len());
}
self.pending_merge(ev.span, ev.replayed);
return Ok(None);
}
let r = resolve(&self.resolver, tag, *plain, value, ev.span)?;
if let Some(name) = anchor {
let key = scalar_key(r.clone(), value, tag);
self.anchor(
name,
Shape::Scalar,
Some(key),
merge.then(raw),
None,
value.len(),
);
}
let (key, val) = match (key_pos, r) {
(true, Resolved::Str) => (Some(KeyRepr::Str(value.clone())), None),
(true, other) => (
Some(KeyRepr::of_value(scalar_value(other, value.clone(), tag))),
None,
),
(false, other) if self.in_key() => {
(None, Some(scalar_value(other, value.clone(), tag)))
}
(false, _) => (None, None),
};
Ok(Some(Done {
item: self.push_item(Item::Leaf(idx)),
span: ev.span,
replayed: ev.replayed,
shape: Shape::Scalar,
key,
value: val,
source: None,
unknown: false,
}))
}
fn alias_value(&mut self, name: &str) -> Result<Option<Value>, Error> {
let left = self.copies.left();
let Some(a) = self.anchors.as_ref().and_then(|a| a.get(name)) else {
return Ok(None);
};
if let Def::Node { slot, size, bytes } = a.def {
let opaque = Def::Opaque { slot, size, bytes };
let new = match ((slot != NO_SLOT).then_some(slot), self.tree) {
_ if size as usize > left.0 => opaque,
_ if bytes as usize > left.1 => {
self.copies.charge(0, bytes as usize);
opaque
}
(Some(s), Some(tree)) => {
let defs = Defs {
tree,
anchors: self.anchors.as_ref(),
history: &self.history,
};
let replay = defs.replay(s)?;
match build_replayed(replay, &defs, self.resolver, self.policy, left)? {
Replayed::Built {
value,
nodes,
bytes: built_bytes,
} => {
replay_counted((size, bytes), (nodes, built_bytes))?;
Def::Built(Box::new(Built {
value,
nodes,
bytes: built_bytes,
slot: Some(s),
}))
}
Replayed::Refused { nodes, bytes } => {
self.copies.charge(nodes, bytes);
opaque
}
}
}
(Some(_), None) => return Err(broken("anchor slot without a tree")),
(None, _) => opaque,
};
if let Some(a) = self.anchors.as_mut().and_then(|a| a.get_mut(name)) {
a.def = new;
}
}
let Some(Def::Built(built)) = self
.anchors
.as_ref()
.and_then(|a| a.get(name))
.map(|a| &a.def)
else {
return Ok(None);
};
let (value, nodes, bytes) = (&built.value, built.nodes, built.bytes);
if !self.copies.take(nodes, bytes) {
return Ok(None);
}
Ok(Some(value.clone()))
}
fn pending_merge(&mut self, span: Span, replayed: bool) {
if let Some(Frame {
body: Body::Map(m), ..
}) = self.stack.last_mut()
{
m.pending = Some(Pending::Merge(span, replayed));
}
}
fn anchor(
&mut self,
name: &'de str,
shape: Shape,
key: Option<KeyRepr<'de>>,
merge: Option<Box<RawScalar<'de>>>,
open: Option<usize>,
bytes: usize,
) -> Option<u32> {
use std::collections::hash_map::Entry;
let slot = self.replay.take().map(|r| {
self.tree.get_or_insert(r.tree());
r.slot()
});
let def = match open {
Some(serial) => Def::Open(serial),
None => Def::Node {
slot: slot.unwrap_or(NO_SLOT),
size: 1,
bytes: saturate(bytes),
},
};
let new = Anchored {
shape,
key,
merge,
def,
prev: NO_SLOT,
};
match self.anchors.get_or_insert_with(HashMap::new).entry(name) {
Entry::Vacant(v) => {
v.insert(new);
}
Entry::Occupied(mut o) => {
let old = o.insert(new);
let prev = match old.def.slot() {
Some(s) => {
if old.prev != NO_SLOT {
self.history.entry(name).or_default().push(old.prev);
}
s
}
None => old.prev,
};
o.get_mut().prev = prev;
}
}
slot
}
fn opaque(serial: usize, name: &str) -> KeyRepr<'de> {
KeyRepr::Other(Value::Tagged(Box::new(TaggedValue {
tag: Tag::new(format!("{OPAQUE_ALIAS}{serial}/{name}")),
value: Value::Null,
})))
}
fn alias(&mut self, name: &str, span: Span) -> Result<Option<Done<'de>>, Error> {
let a = match self.anchors.as_ref().and_then(|a| a.get(name)) {
Some(a) if !matches!(a.def, Def::Open(_)) => a,
Some(_) => return Err(recursive_alias(name, span)),
None => return Err(undefined_alias(name, span)),
};
let shape = a.shape;
if let Some(f) = self.stack.last_mut() {
f.count = (
count_add(f.count.0, a.def.size()),
count_add(f.count.1, a.def.bytes()),
);
}
if let (true, Some(_)) = (self.key_position(), a.merge.as_ref()) {
self.pending_merge(span, false);
return Ok(None);
}
let key = match (&a.key, &a.merge) {
(Some(k), _) => k.clone(),
(None, Some(raw)) => {
let r = resolve(&self.resolver, &raw.tag, raw.plain, &raw.value, span)?;
scalar_key(r, &raw.value, &raw.tag)
}
(None, None) => return Err(Error::internal("anchored scalar", Some(span))),
};
let wanted = self.key_position() || self.in_key();
let full = if wanted && shape != Shape::Scalar {
self.alias_value(name)?
} else {
None
};
let mergeable = match self.merging_in_key() {
Some(true) => matches!(shape, Shape::Map | Shape::MapSeq),
Some(false) => shape == Shape::Map,
None => false,
};
let source = match full {
Some(v) if mergeable => Some(merge_source(v)?),
Some(v) => {
return Ok(Some(self.alias_done(span, shape, KeyRepr::of_value(v))));
}
None => None,
};
if source.is_some() {
return Ok(Some(Done {
item: 0,
span,
replayed: false,
shape,
key: None,
value: None,
source,
unknown: false,
}));
}
Ok(Some(self.alias_done(span, shape, key)))
}
fn merging_in_key(&self) -> Option<bool> {
let f = self.stack.last().filter(|f| self.inline && f.in_key)?;
match &f.body {
Body::Map(m) => matches!(m.pending, Some(Pending::Merge(..))).then_some(true),
Body::Seq { .. } => f.merge_part.then_some(false),
}
}
fn alias_done(&self, span: Span, shape: Shape, key: KeyRepr<'de>) -> Done<'de> {
let (key, value) = if self.key_position() {
(Some(key), None)
} else if self.in_key() {
(None, Some(key.into_value()))
} else {
(None, None)
};
Done {
item: 0,
span,
replayed: false,
shape,
key,
value,
source: None,
unknown: shape != Shape::Scalar,
}
}
fn step(&mut self, ev: &Ev<'de>) -> Result<(), Error> {
let done = match ev.node {
Node::Scalar { .. } => self.scalar(0, ev)?,
Node::SeqStart { .. } | Node::MapStart { .. } => {
self.open(None, ev)?;
None
}
Node::SeqEnd | Node::MapEnd => Some(self.close(None)?),
Node::Alias { name } => self.alias(name, ev.span)?,
_ => return Err(Error::internal("node expected", Some(ev.span))),
};
if let Some(d) = done {
self.deliver(d)?;
}
Ok(())
}
fn open(&mut self, idx: Option<usize>, ev: &Ev<'de>) -> Result<(), Error> {
let (kind, tag) = match &ev.node {
Node::SeqStart { tag } => (CollectionKind::Sequence, tag),
Node::MapStart { tag } => (CollectionKind::Mapping, tag),
_ => return Err(Error::internal("collection expected", Some(ev.span))),
};
let in_key = (self.record || self.inline) && (self.key_position() || self.in_key());
let (custom, tag) = match resolve_collection(&self.resolver, tag, kind, ev.span)? {
ResolvedCollection::Custom(t) => (true, in_key.then(|| tag.value_tag(t))),
ResolvedCollection::Plain => (false, None),
};
let merge_part = match self.stack.last() {
Some(Frame {
body: Body::Map(m), ..
}) => matches!(m.pending, Some(Pending::Merge(..))),
Some(f) => f.merge_part && kind == CollectionKind::Mapping,
None => false,
};
let body = match kind {
CollectionKind::Sequence => Body::Seq {
kids: Vec::new(),
all_maps: true,
source: None,
vals: Vec::new(),
unknown: false,
len: 0,
},
CollectionKind::Mapping => {
let mut m = self.pool.pop().unwrap_or_default();
m.keep = merge_part && (self.record || in_key);
Body::Map(m)
}
};
let anchor = ev.anchor.filter(|_| self.inline).map(|name| {
let serial = self.serial;
self.serial = serial.saturating_add(1);
let key = Some(Self::opaque(serial, name));
let slot = self.anchor(name, Shape::Other, key, None, Some(serial), 0);
AnchorAt { name, serial, slot }
});
self.stack.push(Frame {
start: idx,
span: ev.span,
replayed: ev.replayed,
custom,
merge_part,
in_key,
count: (1, 0),
extra: (tag.is_some() || anchor.is_some()).then(|| Box::new(Extra { tag, anchor })),
body,
});
Ok(())
}
fn close(&mut self, end: Option<usize>) -> Result<Done<'de>, Error> {
let frame = self
.stack
.pop()
.ok_or_else(|| Error::internal("unbalanced collection end", None))?;
let (tag, anchor) = match frame.extra {
Some(x) => (x.tag, x.anchor),
None => (None, None),
};
let (kids, shape, source, value, unknown) = match frame.body {
Body::Seq {
kids,
all_maps,
source,
vals,
unknown,
..
} => {
let shape = if frame.custom || !all_maps {
Shape::Other
} else {
Shape::MapSeq
};
let value = frame
.in_key
.then(|| wrap(tag, Value::Sequence(Sequence::from(vals))));
(kids, shape, source, value, unknown)
}
Body::Map(mut m) => {
let shape = if frame.custom {
Shape::Other
} else {
Shape::Map
};
let fin = finish_map(&mut m, frame.in_key).map_err(|e| e.at(frame.span))?;
self.pool.push(m);
let value = if fin.unknown && fin.value.is_some() {
let serial = self.serial;
self.serial = serial.saturating_add(1);
Some(Self::opaque_merged(serial))
} else {
fin.value.map(|v| wrap(tag, Value::Mapping(v)))
};
(fin.kids, shape, fin.source, value, fin.unknown)
}
};
let (nodes, bytes) = frame.count;
self.count(nodes, bytes);
let (size, bytes) = (saturate(nodes), saturate(bytes));
let anchors = self.anchors.as_mut();
let found = anchor.and_then(|at| {
anchors
.and_then(|a| a.get_mut(at.name))
.filter(|a| matches!(a.def, Def::Open(s) if s == at.serial))
.map(|a| (a, at.slot))
});
if let Some((a, slot)) = found {
a.shape = shape;
a.def = Def::Node {
slot: slot.unwrap_or(NO_SLOT),
size,
bytes,
};
}
let (key, value) = if self.key_position() {
(value.map(KeyRepr::of_value), None)
} else {
(None, value)
};
let item = self.push_item(Item::Coll {
start: frame.start,
end,
kids,
});
Ok(Done {
item,
span: frame.span,
replayed: frame.replayed,
shape,
key,
value,
source,
unknown,
})
}
fn with_path(&self, mut e: Error) -> Error {
for f in self.stack.iter().rev() {
e = match &f.body {
Body::Seq { len, .. } => e.in_index(*len),
Body::Map(m) => match &m.pending {
Some(Pending::Key { key, .. }) => match key {
KeyRepr::Str(s) => e.in_key(s),
KeyRepr::Other(v) => e.in_key(&key_text(v)),
},
Some(Pending::Merge(..)) => e.in_key("<<"),
None => e,
},
};
}
e
}
fn opaque_merged(serial: usize) -> Value {
Value::Tagged(Box::new(TaggedValue {
tag: Tag::new(format!("{OPAQUE_MERGED}{serial}")),
value: Value::Null,
}))
}
fn deliver(&mut self, done: Done<'de>) -> Result<Option<Done<'de>>, Error> {
let record = self.record;
let policy = self.policy;
let Some(top) = self.stack.last_mut() else {
return Ok(Some(done));
};
let merge_part = top.merge_part;
let in_key = top.in_key;
match &mut top.body {
Body::Seq {
kids,
all_maps,
source,
vals,
unknown,
len,
} => {
*len = len.saturating_add(1);
if record {
kids.push(done.item);
}
if done.shape != Shape::Map {
*all_maps = false;
}
if merge_part {
*unknown |= done.unknown;
if let Some(src) = done.source {
match source.as_mut() {
None => *source = Some(src),
Some(acc) => acc.absorb_later(*src),
}
}
} else if in_key {
let v = done
.value
.ok_or_else(|| Error::internal("key item value", Some(done.span)))?;
vals.push(v);
}
}
Body::Map(m) => match m.pending.take() {
None => {
let key = done
.key
.ok_or_else(|| Error::internal("key without value", Some(done.span)))?;
m.pending = Some(Pending::Key {
key,
span: done.span,
replayed: done.replayed,
item: done.item,
});
}
Some(Pending::Key {
key,
span,
replayed,
item,
}) => {
if let Some(&i) = m.seen.get(&key) {
let first = m.first.get(i).copied().unwrap_or(span);
let text = || match &key {
KeyRepr::Str(s) => crate::error::segment(s),
KeyRepr::Other(v) => key_text(v),
};
duplicate_at(self.warnings, policy, replayed, text, first, span)?;
if let Some(p) = m.picks.get_mut(i) {
p.1 = done.item;
p.2 = done.value;
}
} else {
let i = m.first.len();
m.first.push(span);
if record || in_key {
m.picks.push((item, done.item, done.value));
}
m.seen.insert_new(key, i);
}
}
Some(Pending::Merge(span, replayed)) => {
m.merge.key(span, replayed, policy, self.warnings)?;
if !matches!(done.shape, Shape::Map | Shape::MapSeq) {
return Err(invalid_merge(span));
}
m.unknown = done.unknown;
if let Some(src) = done.source {
m.merge.absorb(src);
}
}
},
}
Ok(None)
}
}
fn merge_source<'de>(value: Value) -> Result<Box<Acc<'de>>, Error> {
let entries = |m: Mapping| {
MergeAcc::from_entries(m.into_iter().map(|(k, v)| {
let ent = Ent {
k: 0,
v: 0,
val: Some(v),
};
(KeyRepr::of_value(k), ent)
}))
};
match value {
Value::Mapping(m) => Ok(Box::new(entries(m))),
Value::Sequence(items) => {
let mut acc: Acc<'de> = MergeAcc::default();
for item in items {
match item {
Value::Mapping(m) => acc.absorb_later(entries(m)),
_ => return Err(broken("merged alias item")),
}
}
Ok(Box::new(acc))
}
_ => Err(broken("merged alias value")),
}
}
struct Finished<'de> {
kids: Vec<usize>,
source: Option<Box<Acc<'de>>>,
value: Option<Mapping>,
unknown: bool,
}
fn finish_map<'de>(m: &mut MapState<'de>, in_key: bool) -> Result<Finished<'de>, Error> {
if m.pending.is_some() {
return Err(Error::internal("mapping ended after a key", None));
}
let merge = std::mem::take(&mut m.merge);
let keep = std::mem::take(&mut m.keep);
m.first.clear();
let mut out = Finished {
kids: Vec::new(),
source: None,
value: None,
unknown: std::mem::take(&mut m.unknown),
};
if !keep && !in_key {
for (k, v, _) in m.picks.drain(..) {
out.kids.push(k);
out.kids.push(v);
}
if let Some(merged) = merge.into_merged() {
for (key, ent) in merged.into_entries() {
if !m.seen.contains_key(&key) {
out.kids.push(ent.k);
out.kids.push(ent.v);
}
}
}
m.seen.clear();
return Ok(out);
}
let mut keys: Vec<Option<KeyRepr<'de>>> = Vec::new();
keys.resize_with(m.picks.len(), || None);
for (k, i) in m.seen.drain() {
if let Some(slot) = keys.get_mut(i) {
*slot = Some(k);
}
}
let mut explicit = Vec::with_capacity(m.picks.len());
for (key, (k, v, val)) in keys.into_iter().zip(m.picks.drain(..)) {
let key = key.ok_or_else(|| Error::internal("planned key", None))?;
explicit.push((key, Ent { k, v, val }));
}
let all: Box<dyn Iterator<Item = (KeyRepr<'de>, Ent)>> = if !keep && merge.is_empty() {
Box::new(explicit.into_iter())
} else {
let all = merge.finish(MergeAcc::from_entries(explicit));
if keep {
out.source = Some(Box::new(all));
return Ok(out);
}
Box::new(all.into_entries())
};
let mut value = in_key.then(Mapping::new);
for (key, ent) in all {
out.kids.push(ent.k);
out.kids.push(ent.v);
if let Some(map) = value.as_mut() {
let v = ent
.val
.ok_or_else(|| Error::internal("key entry value", None))?;
map.insert(key.into_value(), v);
}
}
out.value = value;
Ok(out)
}
pub(crate) fn plan<'de>(
p: &mut impl Pull<'de>,
resolver: Resolver,
policy: DuplicateKeys,
warnings: &mut crate::diag_cap::Capped,
) -> Result<Vec<Ev<'de>>, Error> {
let mut entries: Vec<Ev<'de>> = Vec::new();
let root = {
let mut w = Walker {
record: true,
inline: false,
anchors: None,
serial: 0,
copies: AliasMeter::new(&oneyaml_core::Limits::default()),
replay: None,
tree: None,
history: HashMap::new(),
resolver,
policy,
warnings,
stack: vec![Frame {
start: None,
span: p.peek()?.span,
replayed: false,
custom: false,
merge_part: false,
in_key: false,
count: (0, 0),
extra: None,
body: Body::Map(Box::default()),
}],
items: Vec::new(),
pool: Vec::new(),
};
loop {
match plan_step(&mut w, p, &mut entries) {
Ok(true) => break,
Ok(false) => {}
Err(e) => return Err(w.with_path(e)),
}
}
let done = w.close(None)?;
if !w.stack.is_empty() {
return Err(Error::internal("unbalanced buffered mapping", None));
}
(done.item, w.items)
};
#[cfg(test)]
PLANNED_EVENTS.with(|c| c.set(c.get().saturating_add(entries.len())));
flatten(root.1, root.0, entries)
}
fn plan_step<'de>(
w: &mut Walker<'de, '_>,
p: &mut impl Pull<'de>,
entries: &mut Vec<Ev<'de>>,
) -> Result<bool, Error> {
if w.stack.len() == 1 && matches!(p.peek()?.node, Node::MapEnd) {
return Ok(true);
}
let ev = p.next()?;
let i = entries.len();
let done = match ev.node {
Node::Scalar { .. } => w.scalar(i, &ev)?,
Node::SeqStart { .. } | Node::MapStart { .. } => {
w.open(Some(i), &ev)?;
None
}
Node::SeqEnd | Node::MapEnd => Some(w.close(Some(i))?),
_ => return Err(Error::internal("node expected", Some(ev.span))),
};
entries.push(ev);
if let Some(d) = done {
if w.deliver(d)?.is_some() {
return Err(Error::internal("unbalanced buffered mapping", None));
}
}
Ok(false)
}
pub(crate) struct MergeSources<'de> {
root: Box<MapState<'de>>,
items: Vec<Item>,
events: Vec<Ev<'de>>,
#[allow(clippy::vec_box)]
pool: Vec<Box<MapState<'de>>>,
replayed: Vec<(KeyRepr<'de>, usize, usize)>,
}
impl<'de> MergeSources<'de> {
pub(crate) fn new() -> Box<Self> {
Box::new(Self {
root: Box::default(),
items: Vec::new(),
events: Vec::new(),
pool: Vec::new(),
replayed: Vec::new(),
})
}
pub(crate) fn read_value<P: Pull<'de>>(
&mut self,
p: &mut Src<'de, P>,
span: Span,
replayed: bool,
resolver: Resolver,
warnings: &mut crate::diag_cap::Capped,
) -> Result<(), Error> {
#[cfg(test)]
let before = self.events.len();
let result = match self.read_replayed(p, span, replayed, resolver, warnings) {
Ok(true) => Ok(()),
Ok(false) => self.plan_value(p, span, replayed, resolver, warnings),
Err(e) => Err(e),
};
#[cfg(test)]
PLANNED_EVENTS.with(|c| {
c.set(
c.get()
.saturating_add(self.events.len().saturating_sub(before)),
);
});
result
}
fn read_replayed<P: Pull<'de>>(
&mut self,
p: &mut Src<'de, P>,
span: Span,
replayed: bool,
resolver: Resolver,
warnings: &mut crate::diag_cap::Capped,
) -> Result<bool, Error> {
let first_ev = p.peek().map_err(|e| e.in_key("<<"))?;
if !matches!(first_ev.node, Node::MapStart { tag: EvTag::Absent })
|| !p.peeked_delivered_replay()
{
return Ok(false);
}
let start = self.events.len();
let entries = self.replayed.len();
let mut depth = 0usize;
let mut key: Option<(KeyRepr<'de>, usize)> = None;
let mut first = true;
loop {
let ev = match p.next() {
Ok(ev) => ev,
Err(e) => {
return Err(self.path_at(start, span, replayed, resolver, e));
}
};
let at = self.events.len();
let step = match &ev.node {
Node::MapStart { .. } if first => Scan::Go,
Node::MapEnd if depth == 0 && key.is_none() => {
self.events.push(ev);
break;
}
_ if !ev.replayed => Scan::Plan,
Node::Scalar { value, plain, tag } if depth == 0 && key.is_none() => {
if is_merge_key(&resolver, tag, *plain, value) {
Scan::Plan
} else {
let r = resolve(&resolver, tag, *plain, value, ev.span)
.map_err(|e| e.in_key("<<"))?;
key = Some((scalar_key(r, value, tag), at));
Scan::Go
}
}
Node::SeqStart { .. } | Node::MapStart { .. } if depth == 0 && key.is_none() => {
Scan::Plan
}
Node::SeqStart { .. } | Node::MapStart { .. } => {
depth = depth.saturating_add(1);
Scan::Go
}
Node::SeqEnd | Node::MapEnd => {
depth = depth.saturating_sub(1);
if depth == 0 {
Scan::EntryEnds
} else {
Scan::Go
}
}
Node::Scalar { .. } if depth == 0 => Scan::EntryEnds,
Node::Scalar { .. } => Scan::Go,
_ => Scan::Plan,
};
first = false;
self.events.push(ev);
match step {
Scan::Go => {}
Scan::EntryEnds => {
if let Some((k, from)) = key.take() {
self.replayed.push((k, from, at.saturating_add(1)));
}
}
Scan::Plan => {
let read: Vec<Ev<'de>> = self.events.drain(start..).collect();
self.replayed.truncate(entries);
p.push_front_all(read, false);
return Ok(false);
}
}
}
self.root
.merge
.key(span, replayed, DuplicateKeys::Error, warnings)?;
Ok(true)
}
fn path_at(
&self,
start: usize,
span: Span,
replayed: bool,
resolver: Resolver,
e: Error,
) -> Error {
let mut root: Box<MapState<'de>> = Box::default();
root.pending = Some(Pending::Merge(span, replayed));
let mut quiet = crate::diag_cap::Capped::with_keep(0);
let mut w = Walker {
record: true,
inline: false,
anchors: None,
serial: 0,
copies: AliasMeter::new(&oneyaml_core::Limits::default()),
replay: None,
tree: None,
history: HashMap::new(),
resolver,
policy: DuplicateKeys::Error,
warnings: &mut quiet,
stack: vec![Frame {
start: None,
span,
replayed,
custom: false,
merge_part: false,
in_key: false,
count: (0, 0),
extra: None,
body: Body::Map(root),
}],
items: Vec::new(),
pool: Vec::new(),
};
for (i, ev) in self.events.iter().enumerate().skip(start) {
let done = match ev.node {
Node::Scalar { .. } => w.scalar(i, ev),
Node::SeqStart { .. } | Node::MapStart { .. } => w.open(Some(i), ev).map(|()| None),
Node::SeqEnd | Node::MapEnd => w.close(Some(i)).map(Some),
_ => Ok(None),
};
let delivered = match done {
Ok(Some(d)) => w.deliver(d).map(|_| ()),
Ok(None) => Ok(()),
Err(x) => Err(x),
};
if delivered.is_err() {
return e.in_key("<<");
}
}
w.with_path(e)
}
fn plan_value(
&mut self,
p: &mut impl Pull<'de>,
span: Span,
replayed: bool,
resolver: Resolver,
warnings: &mut crate::diag_cap::Capped,
) -> Result<(), Error> {
#[cfg(test)]
PLANNED_MERGE_VALUES.with(|c| c.set(c.get().saturating_add(1)));
let mut root = std::mem::take(&mut self.root);
root.pending = Some(Pending::Merge(span, replayed));
let mut w = Walker {
record: true,
inline: false,
anchors: None,
serial: 0,
copies: AliasMeter::new(&oneyaml_core::Limits::default()),
replay: None,
tree: None,
history: HashMap::new(),
resolver,
policy: DuplicateKeys::Error,
warnings,
stack: vec![Frame {
start: None,
span,
replayed,
custom: false,
merge_part: false,
in_key: false,
count: (0, 0),
extra: None,
body: Body::Map(root),
}],
items: std::mem::take(&mut self.items),
pool: std::mem::take(&mut self.pool),
};
let result = loop {
match merge_step(&mut w, p, &mut self.events) {
Ok(true) => break Ok(()),
Ok(false) => {}
Err(e) => break Err(w.with_path(e)),
}
};
let root = match w.stack.drain(..).next() {
Some(Frame {
body: Body::Map(m), ..
}) => m,
_ => return Err(Error::internal("merge root", Some(span))),
};
self.root = root;
self.items = std::mem::take(&mut w.items);
self.pool = std::mem::take(&mut w.pool);
result
}
pub(crate) fn merged(
self,
explicit: impl Fn(&KeyRepr<'de>) -> bool,
) -> Result<(Vec<Ev<'de>>, bool), Error> {
let Self {
mut root,
mut items,
events,
replayed,
..
} = self;
if !replayed.is_empty() {
let mut kept = replayed
.into_iter()
.filter(|(k, _, _)| !explicit(k))
.map(|(_, from, to)| (from, to))
.peekable();
let mut out = Vec::new();
for (i, ev) in events.into_iter().enumerate() {
while kept.peek().is_some_and(|&(_, to)| i >= to) {
kept.next();
}
match kept.peek() {
Some(&(from, _)) if from <= i => out.push(ev),
Some(_) => {}
None => break,
}
}
return Ok((out, false));
}
let mut kids = Vec::new();
if let Some(merged) = std::mem::take(&mut root.merge).into_merged() {
for (key, ent) in merged.into_entries() {
if !explicit(&key) {
kids.push(ent.k);
kids.push(ent.v);
}
}
}
if kids.is_empty() {
return Ok((Vec::new(), true));
}
items.push(Item::Coll {
start: None,
end: None,
kids,
});
let at = items.len().saturating_sub(1);
flatten(items, at, events).map(|evs| (evs, true))
}
}
enum Scan {
Go,
EntryEnds,
Plan,
}
fn merge_step<'de>(
w: &mut Walker<'de, '_>,
p: &mut impl Pull<'de>,
entries: &mut Vec<Ev<'de>>,
) -> Result<bool, Error> {
let ev = p.next()?;
let i = entries.len();
let done = match ev.node {
Node::Scalar { .. } => w.scalar(i, &ev)?,
Node::SeqStart { .. } | Node::MapStart { .. } => {
w.open(Some(i), &ev)?;
None
}
Node::SeqEnd | Node::MapEnd => Some(w.close(Some(i))?),
_ => return Err(Error::internal("node expected", Some(ev.span))),
};
entries.push(ev);
if let Some(d) = done {
if w.deliver(d)?.is_some() {
return Err(Error::internal("unbalanced merge value", None));
}
}
Ok(w.stack.len() == 1)
}
pub(crate) struct Unexpanded {
pub(crate) warnings: Vec<Diagnostic>,
pub(crate) errors: Vec<Error>,
pub(crate) suppressed: crate::diag_cap::Dropped,
pub(crate) dropped_errors: Vec<(oneyaml_core::Code, oneyaml_core::Span)>,
}
pub(crate) fn check_unexpanded<'de>(
src: &mut impl Source<'de>,
settings: &Settings,
single: bool,
recover: bool,
) -> Unexpanded {
let mut warnings = crate::diag_cap::Capped::new(settings.get_limits());
let mut errors = Vec::new();
let keep_errors = settings
.get_limits()
.get_max_diagnostics()
.saturating_add(1);
let mut dropped_errors = Vec::new();
{
let mut w = Walker {
record: false,
inline: true,
anchors: None,
serial: 0,
copies: AliasMeter::new(settings.get_limits()),
replay: None,
tree: None,
history: HashMap::new(),
resolver: Resolver::new(settings),
policy: settings.get_duplicate_keys(),
warnings: &mut warnings,
stack: Vec::new(),
items: Vec::new(),
pool: Vec::new(),
};
let mut docs = 0usize;
let mut skip = false;
loop {
let ev = match src.next_event() {
Ok(ev) => {
if ev.anchor.is_some() {
w.replay = src.replay_last();
}
if ev.starts_node() {
w.copies.own = w.copies.own.saturating_add(1);
}
w.copies.read_to(ev.span.end);
ev
}
Err(e) => {
if !recover && !skip {
errors.push(w.with_path(e));
} else if !recover {
errors.push(e);
}
break;
}
};
let step = match ev.node {
Node::StreamEnd => break,
Node::DocStart { .. } if single && docs == 1 => {
errors.push(
Error::new(
oneyaml_core::code::MULTIPLE_DOCUMENTS,
"the input has more than one document",
Some(ev.span),
)
.with_help("use oneyaml::Stream to read each document"),
);
break;
}
Node::DocStart { yaml11 } => {
docs = docs.saturating_add(1);
w.resolver = Resolver::for_document(settings, yaml11);
w.anchors = None;
w.history.clear();
w.copies.reset(ev.span.start);
w.stack.clear();
skip = false;
Ok(())
}
Node::DocEnd => Ok(()),
_ if skip => Ok(()),
_ => w.step(&ev).map_err(|e| w.with_path(e)),
};
if let Err(e) = step {
if errors.len() < keep_errors {
errors.push(e);
} else {
let d = e.diagnostic();
dropped_errors.push((d.code, d.primary.span));
}
if !recover {
break;
}
skip = true;
w.stack.clear();
}
}
}
let (warnings, mut suppressed) = warnings.into_parts();
for (_, span) in &dropped_errors {
suppressed = suppressed.and(crate::diag_cap::Dropped::one(span.start));
}
Unexpanded {
warnings,
errors,
suppressed,
dropped_errors,
}
}
fn flatten<'de>(items: Vec<Item>, root: usize, evs: Vec<Ev<'de>>) -> Result<Vec<Ev<'de>>, Error> {
enum Step {
Node(usize),
End(usize),
}
let mut evs: Vec<Option<Ev<'de>>> = evs.into_iter().map(Some).collect();
let mut take = |i: usize| {
evs.get_mut(i)
.and_then(Option::take)
.ok_or_else(|| Error::internal("planned event reused", None))
};
let mut out = Vec::new();
let mut todo: Vec<Step> = match items.get(root) {
Some(Item::Coll { kids, .. }) => kids.iter().rev().map(|k| Step::Node(*k)).collect(),
_ => return Err(Error::internal("planned root", None)),
};
while let Some(step) = todo.pop() {
match step {
Step::End(i) => out.push(take(i)?),
Step::Node(n) => match items.get(n) {
Some(Item::Leaf(i)) => out.push(take(*i)?),
Some(Item::Coll { start, end, kids }) => {
if let Some(s) = start {
out.push(take(*s)?);
}
if let Some(e) = end {
todo.push(Step::End(*e));
}
todo.extend(kids.iter().rev().map(|k| Step::Node(*k)));
}
None => return Err(Error::internal("planned item", None)),
},
}
}
Ok(out)
}
pub(crate) fn check_node<'de>(
p: &mut impl Pull<'de>,
first: Ev<'de>,
resolver: Resolver,
policy: DuplicateKeys,
warnings: &mut crate::diag_cap::Capped,
scratch: &mut Scratch<'de>,
) -> Result<(), Error> {
if let Node::Scalar { value, plain, tag } = &first.node {
resolve(&resolver, tag, *plain, value, first.span)?;
return Ok(());
}
let mut w = Walker {
record: false,
inline: false,
anchors: None,
serial: 0,
copies: AliasMeter::new(&oneyaml_core::Limits::default()),
replay: None,
tree: None,
history: HashMap::new(),
resolver,
policy,
warnings,
stack: std::mem::take(&mut scratch.stack),
items: Vec::new(),
pool: std::mem::take(&mut scratch.pool),
};
let result = check_with(&mut w, p, first);
w.stack.clear();
scratch.stack = w.stack;
scratch.pool = w.pool;
result
}
fn check_with<'de>(
w: &mut Walker<'de, '_>,
p: &mut impl Pull<'de>,
first: Ev<'de>,
) -> Result<(), Error> {
let (resolver, policy) = (w.resolver, w.policy);
let mut ev = first;
loop {
let done = match ev.node {
Node::Scalar { .. } => w.scalar(0, &ev)?,
Node::SeqStart { .. } | Node::MapStart { .. } if w.key_position() => {
let (span, replayed) = (ev.span, ev.replayed);
let mut cx = Cx {
resolver,
policy,
warnings: &mut *w.warnings,
};
let key = build_from(p, &mut cx, ev)?;
Some(Done {
item: 0,
span,
replayed,
shape: Shape::Other,
key: Some(KeyRepr::of_value(key)),
value: None,
source: None,
unknown: false,
})
}
Node::SeqStart { .. } | Node::MapStart { .. } => {
w.open(None, &ev)?;
None
}
Node::SeqEnd | Node::MapEnd => Some(w.close(None)?),
_ => return Err(Error::internal("node expected", Some(ev.span))),
};
if let Some(d) = done {
if w.deliver(d)?.is_some() {
return Ok(());
}
}
ev = p.next()?;
}
}
enum Replayed {
Built {
value: Value,
nodes: usize,
bytes: usize,
},
Refused { nodes: usize, bytes: usize },
}
fn build_replayed<'de>(
replay: Replay<'de>,
defs: &Defs<'_, 'de>,
resolver: Resolver,
policy: DuplicateKeys,
(cap, byte_cap): (usize, usize),
) -> Result<Replayed, Error> {
let mut pull = Replayer {
stack: vec![replay.source()],
defs,
peeked: None,
produced: 0,
bytes: 0,
cap,
byte_cap,
over: false,
last_end: 0,
};
let mut warnings = crate::diag_cap::Capped::with_keep(0);
let mut cx = Cx {
resolver,
policy,
warnings: &mut warnings,
};
let built = pull
.next()
.and_then(|first| build_from(&mut pull, &mut cx, first));
let (nodes, bytes) = (pull.produced, pull.bytes);
match built {
Ok(value) => Ok(Replayed::Built {
value,
nodes,
bytes,
}),
Err(_) if pull.over => Ok(Replayed::Refused { nodes, bytes }),
Err(e) if e.code() == oneyaml_core::code::INTERNAL_ERROR => Err(e),
Err(_) => Ok(Replayed::Refused { nodes, bytes }),
}
}
struct Replayer<'h, 'de> {
stack: Vec<TreeSource<'de>>,
defs: &'h Defs<'h, 'de>,
peeked: Option<Ev<'de>>,
produced: usize,
bytes: usize,
cap: usize,
byte_cap: usize,
over: bool,
last_end: usize,
}
impl<'de> Replayer<'_, 'de> {
fn pull(&mut self) -> Result<Ev<'de>, Error> {
loop {
let Some(top) = self.stack.last_mut() else {
return Ok(Ev::new(Node::StreamEnd, Span::empty(self.last_end)));
};
let ev = top.next_event()?;
match ev.node {
Node::StreamEnd => {
self.stack.pop();
continue;
}
Node::Alias { name } => {
let target = self
.defs
.before(name, ev.span.start)?
.ok_or_else(|| broken("replayed alias undefined"))?;
self.stack.push(target.source());
continue;
}
_ => {}
}
if let Node::Scalar { value, .. } = &ev.node {
self.bytes = self.bytes.saturating_add(value.len());
}
if ev.starts_node() {
self.produced = self.produced.saturating_add(1);
}
if self.produced > self.cap || self.bytes > self.byte_cap {
self.over = true;
return Err(Error::internal("past the copy budget", Some(ev.span)));
}
self.last_end = ev.span.end;
return Ok(ev);
}
}
}
impl<'de> Pull<'de> for Replayer<'_, 'de> {
fn next(&mut self) -> Result<Ev<'de>, Error> {
match self.peeked.take() {
Some(ev) => Ok(ev),
None => self.pull(),
}
}
fn peek(&mut self) -> Result<&Ev<'de>, Error> {
if self.peeked.is_none() {
self.peeked = Some(self.pull()?);
}
self.peeked
.as_ref()
.ok_or_else(|| Error::internal("peeked event", None))
}
fn last_end(&self) -> usize {
self.last_end
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::PLANNED_EVENTS;
use oneyaml_core::{Compat, Settings};
fn nested(width: usize, depth: usize) -> String {
let entries: Vec<String> = (0..width.saturating_sub(1))
.map(|i| format!("k{i}: {i}"))
.collect();
let open = format!("{{{}, n: ", entries.join(", "));
format!("{}{{}}{}", open.repeat(depth), "}".repeat(depth))
}
fn planned(src: &str, settings: &Settings) -> usize {
PLANNED_EVENTS.with(|c| c.set(0));
let v: serde_json::Value = crate::from_str_with(src, settings).unwrap();
assert!(v.is_object());
PLANNED_EVENTS.with(std::cell::Cell::get)
}
fn big_stack(f: impl FnOnce() + Send + 'static) {
std::thread::Builder::new()
.stack_size(64 * 1024 * 1024)
.spawn(f)
.unwrap()
.join()
.unwrap();
}
fn nested_keys(width: usize, depth: usize) -> String {
let rest: Vec<String> = (0..width.saturating_sub(1))
.map(|i| format!("k{i}: {i}"))
.collect();
let rest = rest.join(", ");
let mut s = "{a: 1}".to_owned();
for _ in 0..depth {
s = format!("{{{s}: 1, {rest}}}");
}
s
}
#[test]
fn nested_complex_keys_are_built_once() {
use crate::construct::build::BUILT_EVENTS;
use std::collections::HashMap;
big_stack(|| {
for settings in [Compat::serde_yaml(), Settings::new()] {
for depth in [1usize, 10, 100, 400] {
let src = nested_keys(20, depth);
BUILT_EVENTS.with(|c| c.set(0));
let m: HashMap<crate::Value, crate::Value> =
crate::from_str_with(&src, &settings).unwrap();
assert_eq!(m.len(), 20);
let built = BUILT_EVENTS.with(std::cell::Cell::get);
let events = depth.saturating_mul(41).saturating_add(4);
assert!(built <= 3 * events, "depth {depth}: {built} > 3 × {events}");
let v: crate::Value = crate::from_str_with(&src, &settings).unwrap();
let key = v.as_mapping().unwrap().get_index(0).unwrap().0;
assert!(m.contains_key(key));
}
}
});
}
fn merge_chain(keys: usize, depth: usize, side: bool) -> String {
let inner: Vec<String> = (0..keys).map(|i| format!("k{i}: {i}")).collect();
let mut s = format!("{{{}}}", inner.join(", "));
for d in 0..depth {
s = if side {
format!("{{x{d}: {d}, <<: [{{k{d}: -1, y{d}: {d}}}, {s}]}}")
} else {
format!("{{<<: {s}}}")
};
}
s
}
#[test]
fn merge_chains_are_linear_on_both_paths() {
use crate::construct::merge::MOVES;
use std::collections::BTreeMap;
big_stack(|| {
let s = Settings::new().merge_keys(true);
for side in [false, true] {
let top = if side { 400usize } else { 1000 };
for depth in [1usize, 10, 100, top] {
let keys = 2000usize;
let src = merge_chain(keys, depth, side);
MOVES.with(|c| c.set(0));
let v: crate::Value = crate::from_str_with(&src, &s).unwrap();
let value_moves = MOVES.with(std::cell::Cell::get);
MOVES.with(|c| c.set(0));
let t: BTreeMap<String, i64> = crate::from_str_with(&src, &s).unwrap();
let typed_moves = MOVES.with(std::cell::Cell::get);
let n = if side { keys + 2 * depth } else { keys };
assert_eq!(v.as_mapping().unwrap().len(), n);
assert_eq!(t.len(), n);
if side && depth > 0 {
assert_eq!(t["k0"], -1);
assert_eq!(v["x0"], crate::Value::from(0));
}
let bound = if side { 4 * (keys + 4 * depth) } else { keys };
assert!(value_moves <= bound, "value {side} {depth}: {value_moves}");
assert!(typed_moves <= bound, "typed {side} {depth}: {typed_moves}");
}
}
});
}
#[test]
#[cfg(target_pointer_width = "64")]
fn anchors_cost_what_they_did() {
assert_eq!(std::mem::size_of::<super::Def>(), 16);
assert_eq!(std::mem::size_of::<super::Anchored<'static>>(), 112);
}
#[test]
fn replay_counts_that_agree_pass() {
assert!(super::replay_counted((3, 10), (3, 10)).is_ok());
let big = usize::MAX;
assert!(super::replay_counted((u32::MAX, u32::MAX), (big, big)).is_ok());
assert!(super::replay_counted((u32::MAX, 7), (big, 7)).is_ok());
}
#[test]
fn replay_count_drift_is_an_internal_error() {
for (counted, produced) in [
((3, 10), (4, 10)),
((3, 10), (3, 9)),
((u32::MAX, 1), (9, 2)),
((0, 0), (2, 1)),
] {
let e = super::replay_counts(counted, produced).unwrap_err();
assert_eq!(e.code(), oneyaml_core::code::INTERNAL_ERROR, "{counted:?}");
}
}
#[test]
#[cfg_attr(debug_assertions, should_panic(expected = "replay count drifted"))]
fn replay_count_drift_asserts_in_debug() {
let e = super::replay_counted((3, 10), (4, 10)).unwrap_err();
assert_eq!(e.code(), oneyaml_core::code::INTERNAL_ERROR);
}
fn laughs(levels: usize, l0: &str, tail: &str) -> String {
let mut s = format!("l0: &l0 [{l0}]\n");
for i in 1..=levels {
let refs = vec![format!("*l{}", i.saturating_sub(1)); 10].join(", ");
s.push_str(&format!("l{i}: &l{i} [{refs}]\n"));
}
s.push_str(tail);
s
}
#[test]
fn saturated_counts_never_undercount() {
let x = "x".repeat(1000);
let empties = ["''"; 10].join(", ");
let dup = Some(oneyaml_core::code::DUPLICATE_KEY);
let cases = [
(laughs(7, &x, "k: &k [y]\nm: {? *k : 1}\n"), None),
(laughs(6, &x, "k: &k [y]\nm: {? *k : 1}\n"), None),
(laughs(7, &x, "k: &k [y]\nm: {[y]: 1}\n"), None),
(laughs(7, &x, "k: &k [y]\nm: {? *k : 1, [y]: 2}\n"), dup),
(
laughs(9, &empties, "k: &k ['']\nm: {? *k : 1, ['']: 2}\n"),
dup,
),
(laughs(7, &x, "m: {? *l7 : 1, ? *l6 : 2}\n"), None),
(laughs(7, &x, "m: {? *l7 : 1, ? *l7 : 2}\n"), dup),
];
for cap in [u32::MAX as usize, usize::MAX] {
super::COUNT_CAP.with(|c| c.set(cap));
for (src, want) in &cases {
let got = crate::Document::parse_with(src.as_str(), &Settings::new())
.err()
.map(|e| e.code());
assert_eq!(got, *want, "cap {cap}: {src:.80}");
}
}
super::COUNT_CAP.with(|c| c.set(usize::MAX));
}
fn nested_merging(width: usize, depth: usize) -> String {
let mut s = "{}".to_owned();
for d in 0..depth {
let entries: Vec<String> = (0..width.saturating_sub(1))
.map(|i| format!("k{i}: {i}"))
.collect();
s = format!("{{{}, <<: {{m: {d}}}, n: {s}}}", entries.join(", "));
}
s
}
#[test]
fn replayed_source_mappings_are_buffered_not_planned() {
use super::PLANNED_MERGE_VALUES;
use std::collections::{BTreeMap, HashMap};
let s = Settings::new().merge_keys(true);
for (src, walks) in [
("b: &b {x: 1, y: {z: 2}}\nm: {y: 0, <<: *b}\n", 0),
("b: &b {}\nm: {<<: *b}\n", 0),
("b: &b {x: 1}\nc: &c {k: {<<: *b}}\nm: {<<: *c}\n", 0),
("b: {}\nm: {<<: {x: 1}}\n", 1),
("b: {}\nm: {<<: {}}\n", 1),
("b: &b {x: 1}\nm: {<<: [*b]}\n", 1),
("b: &b !!map {x: 1}\nm: {<<: *b}\n", 1),
("c: &c {w: 3}\nb: &b {x: 1, <<: *c}\nm: {<<: *b}\n", 1),
("b: &b {[1]: 1}\nm: {<<: *b}\n", 1),
] {
PLANNED_MERGE_VALUES.with(|c| c.set(0));
type M = HashMap<crate::Value, crate::Value>;
let t: BTreeMap<String, M> = crate::from_str_with(src, &s).unwrap();
assert_eq!(
PLANNED_MERGE_VALUES.with(std::cell::Cell::get),
walks,
"{src:?}"
);
let v: crate::Value = crate::from_str_with(src, &s).unwrap();
let want: M = v["m"].as_mapping().unwrap().clone().into_iter().collect();
assert_eq!(t["m"], want, "{src:?}");
}
let doc = crate::Document::parse_with("b: &b {x: 1}\nm: {y: 2, <<: *b}\n", &s).unwrap();
PLANNED_MERGE_VALUES.with(|c| c.set(0));
let t: HashMap<crate::Value, crate::Value> = doc.get("m").unwrap().deserialize().unwrap();
assert_eq!(PLANNED_MERGE_VALUES.with(std::cell::Cell::get), 1);
assert_eq!(t.len(), 2);
}
#[test]
fn planning_is_linear_in_nesting() {
std::thread::Builder::new()
.stack_size(64 * 1024 * 1024)
.spawn(|| {
let width = 20usize;
let lastwins = Compat::serde_yaml();
let merge = Settings::new().merge_keys(true);
for depth in [1usize, 10, 60, 120] {
let events = depth.saturating_mul(width.saturating_mul(2).saturating_add(1));
assert_eq!(planned(&nested(width, depth), &lastwins), events);
assert_eq!(planned(&nested(width, depth), &merge), 0);
let src = nested_merging(width, depth);
assert_eq!(planned(&src, &merge), 4 * depth, "depth {depth}");
let keys = 50usize;
let chain = merge_chain(keys, depth, false);
let events = 2 + 2 * keys + 3 * (depth - 1);
assert_eq!(planned(&chain, &merge), events, "chain {depth}");
}
assert_eq!(planned(&nested(width, 60), &Settings::new()), 0);
})
.unwrap()
.join()
.unwrap();
}
}