use crate::expr::arena::with_arena;
use crate::expr::path;
use crate::expr::rules::{self, Shape, Step};
use crate::expr::{Path, Seg};
use crate::state::splice;
use crate::state::{Lookup, RunContext};
use bumpalo::Bump;
use datavalue::{DataValue, OwnedDataValue};
#[derive(Clone, Debug, Default)]
pub(crate) struct Overlay(Vec<(Path, Write)>);
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Write {
Set(OwnedDataValue),
Blocked(OwnedDataValue),
Removed,
}
impl Overlay {
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub(crate) fn set(&mut self, base: DataValue<'_>, path: &Path, value: OwnedDataValue) {
let lands = self.lands(base, path.segs());
self.push(path, value, lands);
}
pub(crate) fn push(&mut self, path: &Path, value: OwnedDataValue, lands: bool) {
let write = if lands {
Write::Set(value)
} else {
Write::Blocked(value)
};
self.0.push((path.clone(), write));
}
pub(crate) fn remove(&mut self, path: &Path) {
self.0.push((path.clone(), Write::Removed));
}
pub(crate) fn lookup(&self, path: &Path) -> Lookup<'_> {
let p = path.segs();
for (at, write) in self.0.iter().rev() {
let e = at.segs();
let set = match write {
Write::Set(value) => Some(value),
Write::Removed => None,
Write::Blocked(_) => continue,
};
match relation(e, p) {
Rel::Apart => {}
Rel::Siblings { at, e: x, p: y } if !moves(e.len(), set.is_some(), at, x, y) => {}
Rel::Above => {
return match set {
Some(value) => Lookup::Owned(path::get(value, &p[e.len()..])),
None if may_shift(e) => Lookup::Dirty,
None => Lookup::Owned(None),
};
}
Rel::Below | Rel::Siblings { .. } | Rel::Unsure => return Lookup::Dirty,
}
}
Lookup::Clean
}
pub(crate) fn splice_onto<'a>(&'a self, root: DataValue<'a>, arena: &'a Bump) -> DataValue<'a> {
self.0.iter().fold(root, |root, (path, write)| {
let spliced = match write {
Write::Set(value) => splice::set(arena, root, path.segs(), value.view_in(arena)),
Write::Removed => splice::remove(arena, root, path.segs()),
Write::Blocked(_) => return root,
};
debug_assert!(
spliced.is_some(),
"a write lands on the root it was made on"
);
spliced.unwrap_or(root)
})
}
pub(crate) fn into_writes(self) -> Vec<(Path, Write)> {
self.0
}
pub(crate) fn apply(self, rc: &mut RunContext<'_, '_>) {
for (path, write) in self.0 {
match write {
Write::Set(value) | Write::Blocked(value) => {
rc.set_segs(path.segs(), value, None);
}
Write::Removed => {
rc.remove_segs(path.segs());
}
}
}
}
fn lands(&self, base: DataValue<'_>, segs: &[Seg]) -> bool {
if self.is_empty() {
return splice::resolve(base, segs).is_some();
}
let mut holder = Shape::Object;
for i in 1..segs.len() {
let last = i + 1 == segs.len();
let child = if last {
None
} else {
match self.held_at(base, &segs[..=i]) {
Some(held) => held,
None => {
return with_arena(|arena| {
splice::resolve(self.splice_onto(base, arena), segs).is_some()
});
}
}
};
match rules::step(holder, &segs[i], child, last) {
Step::Block => return false,
Step::Create => return true,
Step::Into => holder = child.unwrap_or(Shape::Null),
}
}
true
}
fn held_at(&self, base: DataValue<'_>, q: &[Seg]) -> Option<Option<Shape>> {
let mut created = None;
for (at, write) in self.0.iter().rev() {
let e = at.segs();
let set = match write {
Write::Set(value) => Some(value),
Write::Removed => None,
Write::Blocked(_) => continue,
};
match relation(e, q) {
Rel::Apart => {}
Rel::Siblings { at, e: x, p: y } if !moves(e.len(), set.is_some(), at, x, y) => {}
Rel::Below => {
if set.is_some() {
created = Some(&e[q.len()]);
}
}
Rel::Above => {
let held = match set {
Some(value) => path::get(value, &q[e.len()..]).map(Shape::of_owned),
None if may_shift(e) => return None,
None => None,
};
return Some(or_created(held, created));
}
Rel::Siblings { .. } | Rel::Unsure => return None,
}
}
Some(or_created(
splice::get(base, q).map(|v| Shape::of_view(&v)),
created,
))
}
}
fn or_created(held: Option<Shape>, below: Option<&Seg>) -> Option<Shape> {
match (held, below) {
(None | Some(Shape::Null), Some(next)) => Some(rules::container_for(next)),
(held, _) => held,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Rel {
Above,
Below,
Apart,
Siblings { at: usize, e: usize, p: usize },
Unsure,
}
fn relation(e: &[Seg], p: &[Seg]) -> Rel {
for (i, (a, b)) in e.iter().zip(p).enumerate() {
let one_key = a.hash == b.hash && a.key == b.key;
if one_key && a.index == b.index {
continue;
}
return match (a.index, b.index) {
(Some(x), Some(y)) if x != y => Rel::Siblings { at: i, e: x, p: y },
(Some(_), Some(_)) => Rel::Unsure,
_ if one_key => Rel::Unsure,
_ => Rel::Apart,
};
}
if e.len() <= p.len() {
Rel::Above
} else {
Rel::Below
}
}
fn moves(e_len: usize, set: bool, at: usize, x: usize, y: usize) -> bool {
if set { x > y } else { e_len == at + 1 && x < y }
}
fn may_shift(e: &[Seg]) -> bool {
e.last().is_some_and(|seg| seg.index.is_some())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::tests::random::{self, Rng};
use crate::state::{RunContext, Tuning};
fn step(rng: &mut Rng, overlay: &mut Overlay, base: DataValue<'_>, model: &mut OwnedDataValue) {
let Ok(path) = random::rooted_path(rng, model).parse::<Path>() else {
return;
};
if rng.chance(1, 3) {
if path.is_root() || path.get(model).is_none() {
return;
}
path::remove(model, path.segs());
overlay.remove(&path);
} else {
let value = random::value(rng);
if path.check_set(&value).is_err() {
return;
}
path.assign(model, value.clone());
overlay.set(base, &path, value);
}
}
#[test]
fn the_overlay_is_its_writes_applied_in_order() {
let mut rng = Rng::new(0x5eed_0f0e_71a7);
for case in 0..1500 {
let owned = random::rooted_tree(&mut rng, 3);
let arena = Bump::new();
let base = owned.view_in(&arena);
let mut overlay = Overlay::default();
let mut model = owned.clone();
for at in 0..1 + rng.below(6) {
step(&mut rng, &mut overlay, base, &mut model);
let here = Bump::new();
let root = overlay.splice_onto(base, &here);
let want = random::exact(&model);
assert_eq!(
random::exact(&root.to_owned()),
want,
"case {case} step {at}: the spliced root"
);
for _ in 0..4 {
let Ok(read) = random::rooted_path(&mut rng, &model).parse::<Path>() else {
continue;
};
let got = match overlay.lookup(&read) {
Lookup::Clean => read.get(&owned).cloned(),
Lookup::Owned(v) => v.cloned(),
Lookup::Dirty => splice::get(root, read.segs()).map(|v| v.to_owned()),
};
assert_eq!(
got.as_ref().map(random::exact),
read.get(&model).map(random::exact),
"case {case} step {at}: a read of {read}"
);
}
}
let mut slot = owned.clone();
let mut rc = RunContext::new(&mut slot, Tuning::default());
if rng.chance(1, 2) {
rc.read(|_, _| ());
}
overlay.apply(&mut rc);
rc.finish();
assert_eq!(
random::exact(&slot),
random::exact(&model),
"case {case}: applied"
);
}
}
fn draw(rng: &mut Rng, own: &OwnedDataValue) -> Option<(Path, Option<OwnedDataValue>)> {
let path = random::rooted_path(rng, own).parse::<Path>().ok()?;
if rng.chance(1, 3) {
(!path.is_root() && path.get(own).is_some()).then_some((path, None))
} else {
let value = random::value(rng);
path.check_set(&value)
.is_ok()
.then_some((path, Some(value)))
}
}
fn write(context: &mut OwnedDataValue, path: &Path, value: Option<&OwnedDataValue>) {
match value {
Some(value) => {
path.assign(context, value.clone());
}
None => {
path::remove(context, path.segs());
}
}
}
#[test]
fn overlays_planned_over_one_base_compose_in_order() {
let mut rng = Rng::new(0xc0_4405_e0f1);
for case in 0..1500 {
let owned = random::rooted_tree(&mut rng, 3);
let arena = Bump::new();
let base = owned.view_in(&arena);
let mut sequential = owned.clone();
let mut overlays = Vec::new();
for _ in 0..2 + rng.below(2) {
let mut overlay = Overlay::default();
let mut own = owned.clone();
for _ in 0..1 + rng.below(5) {
let Some((path, value)) = draw(&mut rng, &own) else {
continue;
};
write(&mut own, &path, value.as_ref());
write(&mut sequential, &path, value.as_ref());
match value {
Some(value) => overlay.set(base, &path, value),
None => overlay.remove(&path),
}
}
overlays.push(overlay);
}
let mut slot = owned.clone();
let mut rc = RunContext::new(&mut slot, Tuning::default());
if rng.chance(1, 2) {
rc.read(|_, _| ());
}
for overlay in overlays {
overlay.apply(&mut rc);
}
rc.finish();
let (got, want) = (random::exact(&slot), random::exact(&sequential));
assert!(got == want, "case {case}: {}", random::diff(&got, &want));
}
}
fn parse(dotted: &str) -> Path {
dotted.parse().unwrap()
}
fn dv(v: serde_json::Value) -> OwnedDataValue {
OwnedDataValue::from(&v)
}
fn spliced(overlay: &Overlay, base: DataValue<'_>) -> String {
let arena = Bump::new();
random::exact(&overlay.splice_onto(base, &arena).to_owned())
}
#[test]
fn a_key_removed_and_written_again_moves_to_its_objects_end() {
let owned = dv(serde_json::json!({"data": {"a": 1, "b": 2, "c": 3}}));
let arena = Bump::new();
let base = owned.view_in(&arena);
let mut overlay = Overlay::default();
overlay.remove(&parse("data.a"));
overlay.set(base, &parse("data.a"), dv(serde_json::json!(4)));
assert_eq!(spliced(&overlay, base), r#"{"data":{"b":2,"c":3,"a":4}}"#);
}
#[test]
fn a_new_key_removed_and_written_again_follows_a_later_new_key() {
let owned = dv(serde_json::json!({"data": {"a": 1}}));
let arena = Bump::new();
let base = owned.view_in(&arena);
let mut overlay = Overlay::default();
overlay.set(base, &parse("data.n"), dv(serde_json::json!(1)));
overlay.set(base, &parse("data.m"), dv(serde_json::json!(2)));
overlay.remove(&parse("data.n"));
overlay.set(base, &parse("data.n"), dv(serde_json::json!(3)));
assert_eq!(spliced(&overlay, base), r#"{"data":{"a":1,"m":2,"n":3}}"#);
}
#[test]
fn padding_and_a_removed_element_are_read_as_written() {
let owned = dv(serde_json::json!({"data": {"list": [1, 2]}}));
let arena = Bump::new();
let base = owned.view_in(&arena);
let mut overlay = Overlay::default();
overlay.set(base, &parse("data.list.4"), dv(serde_json::json!(5)));
assert_eq!(overlay.lookup(&parse("data.list.3")), Lookup::Dirty);
assert_eq!(
spliced(&overlay, base),
r#"{"data":{"list":[1,2,null,null,5]}}"#
);
overlay.remove(&parse("data.list.0"));
assert_eq!(
spliced(&overlay, base),
r#"{"data":{"list":[2,null,null,5]}}"#
);
}
}