use std::cell::RefCell;
use std::collections::HashMap;
use bumpalo::Bump;
use hermes_atom_table::{AtomBytes, AtomTable};
use super::{JSONHiddenClass, JSONValue};
pub type Prop<'a> = (&'a JSONValue<'a>, &'a JSONValue<'a>);
pub struct JSONFactory<'a> {
arena: &'a Bump,
atoms: &'a AtomTable,
strings: RefCell<HashMap<AtomBytes, &'a JSONValue<'a>>>,
numbers: RefCell<HashMap<u64, &'a JSONValue<'a>>>,
classes: RefCell<HashMap<Box<[AtomBytes]>, &'a JSONHiddenClass<'a>>>,
null_v: &'a JSONValue<'a>,
true_v: &'a JSONValue<'a>,
false_v: &'a JSONValue<'a>,
}
impl<'a> JSONFactory<'a> {
pub fn new(arena: &'a Bump, atoms: &'a AtomTable) -> JSONFactory<'a> {
JSONFactory {
arena,
atoms,
strings: RefCell::new(HashMap::new()),
numbers: RefCell::new(HashMap::new()),
classes: RefCell::new(HashMap::new()),
null_v: arena.alloc(JSONValue::Null),
true_v: arena.alloc(JSONValue::Boolean(true)),
false_v: arena.alloc(JSONValue::Boolean(false)),
}
}
pub fn arena(&self) -> &'a Bump {
self.arena
}
pub fn atoms(&self) -> &'a AtomTable {
self.atoms
}
pub fn get_null(&self) -> &'a JSONValue<'a> {
self.null_v
}
pub fn get_boolean(&self, v: bool) -> &'a JSONValue<'a> {
if v {
self.true_v
} else {
self.false_v
}
}
pub fn get_string(&self, lit: AtomBytes) -> &'a JSONValue<'a> {
if let Some(found) = self.strings.borrow().get(&lit) {
return found;
}
let node: &'a JSONValue<'a> = self.arena.alloc(JSONValue::String(lit));
self.strings.borrow_mut().insert(lit, node);
node
}
pub fn get_string_str(&self, s: &str) -> &'a JSONValue<'a> {
self.get_string(self.atoms.atom_bytes(s))
}
pub fn get_number(&self, value: f64) -> &'a JSONValue<'a> {
let bits = value.to_bits();
if let Some(found) = self.numbers.borrow().get(&bits) {
return found;
}
let node: &'a JSONValue<'a> = self.arena.alloc(JSONValue::Number(value));
self.numbers.borrow_mut().insert(bits, node);
node
}
fn key_bytes(&self, key: &'a JSONValue<'a>) -> AtomBytes {
key.as_string().expect("object key must be a JSON string")
}
pub fn sort_props(&self, props: &mut [Prop<'a>]) -> Option<AtomBytes> {
props.sort_by(|a, b| {
self.atoms
.bytes(self.key_bytes(a.0))
.cmp(self.atoms.bytes(self.key_bytes(b.0)))
});
let mut last: Option<AtomBytes> = None;
for p in props.iter() {
let kb = self.key_bytes(p.0);
if last == Some(kb) {
return Some(kb);
}
last = Some(kb);
}
None
}
pub fn get_hidden_class(&self, keys: &[AtomBytes]) -> &'a JSONHiddenClass<'a> {
if let Some(found) = self.classes.borrow().get(keys) {
return found;
}
let arena_keys: &'a [AtomBytes] = self.arena.alloc_slice_copy(keys);
let cls: &'a JSONHiddenClass<'a> =
self.arena.alloc(JSONHiddenClass { keys: arena_keys });
self.classes.borrow_mut().insert(keys.into(), cls);
cls
}
pub fn new_object(&self, props: &mut [Prop<'a>]) -> Option<&'a JSONValue<'a>> {
if self.sort_props(props).is_some() {
return None;
}
let keys: Vec<AtomBytes> = props.iter().map(|p| self.key_bytes(p.0)).collect();
let cls = self.get_hidden_class(&keys);
let values: Vec<&'a JSONValue<'a>> = props.iter().map(|p| p.1).collect();
let values: &'a [&'a JSONValue<'a>] = self.arena.alloc_slice_copy(&values);
Some(self.arena.alloc(JSONValue::Object(cls, values)))
}
pub fn new_object_sorted(&self, props: &[Prop<'a>]) -> Option<&'a JSONValue<'a>> {
let keys: Vec<AtomBytes> = props.iter().map(|p| self.key_bytes(p.0)).collect();
let cls = self.get_hidden_class(&keys);
let values: Vec<&'a JSONValue<'a>> = props.iter().map(|p| p.1).collect();
let values: &'a [&'a JSONValue<'a>] = self.arena.alloc_slice_copy(&values);
Some(self.arena.alloc(JSONValue::Object(cls, values)))
}
pub fn new_array(&self, values: &[&'a JSONValue<'a>]) -> &'a JSONValue<'a> {
let values: &'a [&'a JSONValue<'a>] = self.arena.alloc_slice_copy(values);
self.arena.alloc(JSONValue::Array(values))
}
}
#[cfg(test)]
mod factory_tests {
use super::super::*;
use bumpalo::Bump;
use hermes_atom_table::AtomTable;
#[test]
fn objects_arrays_and_hidden_class_sharing() {
use super::super::JSONFactory;
let arena = Bump::new();
let atoms = AtomTable::new();
let f = JSONFactory::new(&arena, &atoms);
fn mk<'b>(f: &'b JSONFactory<'b>, k1: f64, k2: f64) -> &'b super::super::JSONValue<'b> {
let p1 = (f.get_string_str("key1"), f.get_number(k1));
let p2 = (f.get_string_str("key2"), f.get_number(k2));
f.new_object(&mut [p2, p1]).unwrap() }
let o1 = mk(&f, 1.0, 2.0);
let o3 = mk(&f, 20.0, 10.0);
let v1 = o1.as_object().unwrap();
assert_eq!(v1.size(), 2);
assert!(std::ptr::eq(
v1.get_hidden_class(),
o3.as_object().unwrap().get_hidden_class()
));
assert_eq!(v1.count("key1", &atoms), 1);
assert_eq!(v1.count("zzz", &atoms), 0);
assert_eq!(v1.get("key1", &atoms).and_then(|v| v.as_number()), Some(1.0));
let dup = (f.get_string_str("k"), f.get_number(1.0));
assert!(f.new_object(&mut [dup, dup]).is_none());
let a = f.new_array(&[f.get_number(5.0), f.get_null()]);
let av = a.as_array().unwrap();
assert_eq!(av.len(), 2);
assert_eq!(av.at(0).as_number(), Some(5.0));
}
#[test]
fn object_positional_accessors_and_iter_sorted_order() {
use super::super::JSONFactory;
let arena = Bump::new();
let atoms = AtomTable::new();
let f = JSONFactory::new(&arena, &atoms);
let obj = f
.new_object(&mut [
(f.get_string_str("b"), f.get_number(2.0)),
(f.get_string_str("a"), f.get_number(1.0)),
(f.get_string_str("c"), f.get_number(3.0)),
])
.unwrap();
let o = obj.as_object().unwrap();
assert_eq!(o.size(), 3);
assert_eq!(atoms.bytes(o.key_at(0)), b"a");
assert_eq!(o.value_at(0).as_number(), Some(1.0));
assert_eq!(atoms.bytes(o.key_at(2)), b"c");
assert_eq!(o.value_at(2).as_number(), Some(3.0));
let collected: Vec<(Vec<u8>, f64)> = o
.iter()
.map(|(k, v)| (atoms.bytes(k).to_vec(), v.as_number().unwrap()))
.collect();
assert_eq!(
collected,
vec![(b"a".to_vec(), 1.0), (b"b".to_vec(), 2.0), (b"c".to_vec(), 3.0)]
);
}
#[test]
fn uniquing_and_singletons() {
let arena = Bump::new();
let atoms = AtomTable::new();
let f = JSONFactory::new(&arena, &atoms);
let a = f.get_string_str("key2");
let b = f.get_string_str("key2");
assert!(std::ptr::eq(a, b));
assert_eq!(a.as_string().map(|h| atoms.bytes(h).to_vec()), Some(b"key2".to_vec()));
assert!(std::ptr::eq(f.get_number(1.0), f.get_number(1.0)));
assert!(!std::ptr::eq(f.get_number(0.0), f.get_number(-0.0)));
assert!(std::ptr::eq(f.get_null(), f.get_null()));
assert!(std::ptr::eq(f.get_boolean(true), f.get_boolean(true)));
assert!(!std::ptr::eq(f.get_boolean(true), f.get_boolean(false)));
}
}