use crate::arena::DataValue;
use datavalue::OwnedDataValue;
use self_cell::self_cell;
#[derive(Debug, Clone)]
struct LitOwner {
value: OwnedDataValue,
children: Box<[PreLit]>,
}
#[derive(Debug)]
enum LitSpine<'a> {
None,
Array(Box<[DataValue<'a>]>),
Object(Box<[(&'a str, DataValue<'a>)]>),
}
self_cell!(
struct SpineCell {
owner: LitOwner,
#[covariant]
dependent: LitSpine,
}
impl {Debug}
);
self_cell!(
struct RootCell {
owner: SpineCell,
#[covariant]
dependent: DataValue,
}
impl {Debug}
);
pub(crate) struct PreLit(Box<PreLitInner>);
#[derive(Debug)]
enum PreLitInner {
Static(DataValue<'static>),
Cell(RootCell),
}
impl PreLit {
#[inline]
pub(crate) fn from_static(dv: DataValue<'static>) -> Self {
PreLit(Box::new(PreLitInner::Static(dv)))
}
pub(crate) fn composite(value: &OwnedDataValue) -> Option<Self> {
match value {
OwnedDataValue::String(s) if !s.is_empty() => {}
OwnedDataValue::Array(a) if !a.is_empty() => {}
OwnedDataValue::Object(o) if !o.is_empty() => {}
_ => return None,
}
Some(PreLit(Box::new(PreLitInner::Cell(build_cell(LitOwner {
value: value.clone(),
children: build_children(value),
})))))
}
#[inline]
pub(crate) fn as_ref<'s>(&'s self) -> &'s DataValue<'s> {
match &*self.0 {
PreLitInner::Static(dv) => dv,
PreLitInner::Cell(cell) => cell.borrow_dependent(),
}
}
}
impl Clone for PreLit {
fn clone(&self) -> Self {
match &*self.0 {
PreLitInner::Static(dv) => PreLit(Box::new(PreLitInner::Static(*dv))),
PreLitInner::Cell(cell) => PreLit(Box::new(PreLitInner::Cell(build_cell(
cell.borrow_owner().borrow_owner().clone(),
)))),
}
}
}
impl std::fmt::Debug for PreLit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("PreLit").field(self.as_ref()).finish()
}
}
fn build_cell(owner: LitOwner) -> RootCell {
let spine = SpineCell::new(owner, build_spine);
RootCell::new(spine, build_root)
}
fn build_children(value: &OwnedDataValue) -> Box<[PreLit]> {
fn is_child(v: &OwnedDataValue) -> bool {
match v {
OwnedDataValue::Array(a) => !a.is_empty(),
OwnedDataValue::Object(o) => !o.is_empty(),
_ => false,
}
}
let mut children = Vec::new();
match value {
OwnedDataValue::Array(items) => {
for it in items.iter().filter(|it| is_child(it)) {
children.extend(PreLit::composite(it));
}
}
OwnedDataValue::Object(pairs) => {
for (_, v) in pairs.iter().filter(|(_, v)| is_child(v)) {
children.extend(PreLit::composite(v));
}
}
_ => {}
}
children.into_boxed_slice()
}
fn build_spine(owner: &LitOwner) -> LitSpine<'_> {
let mut cursor = 0usize;
match &owner.value {
OwnedDataValue::Array(items) => LitSpine::Array(
items
.iter()
.map(|it| element_dv(it, &owner.children, &mut cursor))
.collect(),
),
OwnedDataValue::Object(pairs) => LitSpine::Object(
pairs
.iter()
.map(|(k, v)| (k.as_str(), element_dv(v, &owner.children, &mut cursor)))
.collect(),
),
_ => LitSpine::None,
}
}
fn element_dv<'a>(
v: &'a OwnedDataValue,
children: &'a [PreLit],
cursor: &mut usize,
) -> DataValue<'a> {
match v {
OwnedDataValue::Null => DataValue::Null,
OwnedDataValue::Bool(b) => DataValue::Bool(*b),
OwnedDataValue::Number(n) => DataValue::Number(*n),
OwnedDataValue::String(s) => DataValue::String(s.as_str()),
OwnedDataValue::Array(a) if a.is_empty() => DataValue::Array(&[]),
OwnedDataValue::Object(o) if o.is_empty() => DataValue::Object(&[]),
OwnedDataValue::Array(_) | OwnedDataValue::Object(_) => {
let child = children.get(*cursor);
*cursor += 1;
debug_assert!(child.is_some(), "spine walk out of sync with children");
child.map_or(DataValue::Null, |c| *c.as_ref())
}
#[cfg(feature = "datetime")]
OwnedDataValue::DateTime(d) => DataValue::DateTime(*d),
#[cfg(feature = "datetime")]
OwnedDataValue::Duration(d) => DataValue::Duration(*d),
}
}
fn build_root<'a>(sc: &'a SpineCell) -> DataValue<'a> {
match sc.borrow_dependent() {
LitSpine::Array(v) => DataValue::Array(v),
LitSpine::Object(v) => DataValue::Object(v),
LitSpine::None => match &sc.borrow_owner().value {
OwnedDataValue::String(s) => DataValue::String(s.as_str()),
_ => DataValue::Null,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
fn owned(json: &str) -> OwnedDataValue {
OwnedDataValue::from_json(json).unwrap()
}
#[test]
fn prelit_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<PreLit>();
}
#[test]
fn trivial_shapes_are_rejected() {
for json in ["null", "true", "42", "\"\"", "[]", "{}"] {
assert!(
PreLit::composite(&owned(json)).is_none(),
"expected no composite cell for {json}"
);
}
}
#[test]
fn flat_array_prebuilds_and_borrows() {
let value = owned(r#"[1, "two", 3.5, true, null, []]"#);
let lit = PreLit::composite(&value).unwrap();
let dv = lit.as_ref();
let items = match dv {
DataValue::Array(items) => *items,
other => panic!("expected array, got {other:?}"),
};
assert_eq!(items.len(), 6);
assert_eq!(items[0].as_i64(), Some(1));
assert_eq!(items[1].as_str(), Some("two"));
assert_eq!(items[2].as_f64(), Some(3.5));
assert!(matches!(items[4], DataValue::Null));
assert!(matches!(items[5], DataValue::Array(&[])));
}
#[test]
fn nested_composites_round_trip() {
let value = owned(r#"{"a": [[1, "x"], {"b": 2}], "c": "s"}"#);
let lit = PreLit::composite(&value).unwrap();
assert_eq!(lit.as_ref().to_owned(), value);
}
#[test]
fn clone_rebuilds_and_survives_the_original() {
let value = owned(r#"[["deep", ["deeper"]], {"k": "v"}]"#);
let lit = PreLit::composite(&value).unwrap();
let cloned = lit.clone();
drop(lit);
drop(value);
let moved = cloned; assert_eq!(
moved.as_ref().to_owned(),
owned(r#"[["deep", ["deeper"]], {"k": "v"}]"#)
);
}
#[test]
fn string_literal_prebuilds() {
let value = owned(r#""hello world""#);
let lit = PreLit::composite(&value).unwrap();
assert_eq!(lit.as_ref().as_str(), Some("hello world"));
}
}