pub mod factory;
pub mod parser;
pub use factory::{JSONFactory, Prop};
pub use parser::JSONParser;
use hermes_atom_table::AtomBytes;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum JSONKind {
Object,
Array,
String,
Number,
Boolean,
Null,
}
pub fn kind_to_string(kind: JSONKind) -> &'static str {
match kind {
JSONKind::Object => "Object",
JSONKind::Array => "Array",
JSONKind::String => "String",
JSONKind::Number => "Number",
JSONKind::Boolean => "Boolean",
JSONKind::Null => "Null",
}
}
pub struct JSONHiddenClass<'a> {
pub(crate) keys: &'a [AtomBytes],
}
impl<'a> JSONHiddenClass<'a> {
pub fn size(&self) -> usize {
self.keys.len()
}
pub fn keys(&self) -> &'a [AtomBytes] {
self.keys
}
pub fn find(&self, name: &[u8], atoms: &hermes_atom_table::AtomTable) -> Option<usize> {
self.keys
.binary_search_by(|k| atoms.bytes(*k).cmp(name))
.ok()
}
}
pub enum JSONValue<'a> {
Null,
Boolean(bool),
Number(f64),
String(AtomBytes),
Array(&'a [&'a JSONValue<'a>]),
Object(&'a JSONHiddenClass<'a>, &'a [&'a JSONValue<'a>]),
}
impl<'a> JSONValue<'a> {
pub fn kind(&self) -> JSONKind {
match self {
JSONValue::Null => JSONKind::Null,
JSONValue::Boolean(_) => JSONKind::Boolean,
JSONValue::Number(_) => JSONKind::Number,
JSONValue::String(_) => JSONKind::String,
JSONValue::Array(_) => JSONKind::Array,
JSONValue::Object(..) => JSONKind::Object,
}
}
pub fn as_number(&self) -> Option<f64> {
match self {
JSONValue::Number(n) => Some(*n),
_ => None,
}
}
pub fn as_boolean(&self) -> Option<bool> {
match self {
JSONValue::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn as_string(&self) -> Option<AtomBytes> {
match self {
JSONValue::String(a) => Some(*a),
_ => None,
}
}
pub fn as_array(&self) -> Option<ArrayView<'a>> {
match self {
JSONValue::Array(v) => Some(ArrayView { values: v }),
_ => None,
}
}
pub fn as_object(&self) -> Option<ObjectView<'a>> {
match self {
JSONValue::Object(c, v) => Some(ObjectView { class: c, values: v }),
_ => None,
}
}
pub fn emit_into(
&self,
emitter: &mut hermes_support::json_emitter::JSONEmitter,
atoms: &hermes_atom_table::AtomTable,
) {
match self {
JSONValue::Object(class, values) => {
emitter.open_dict();
for (k, v) in class.keys.iter().copied().zip(values.iter().copied()) {
let ku = crate::utf8::convert_utf8_with_surrogates_to_utf16(atoms.bytes(k));
emitter.emit_key_u16(&ku);
v.emit_into(emitter, atoms);
}
emitter.close_dict();
}
JSONValue::Array(values) => {
emitter.open_array();
for &v in values.iter() {
v.emit_into(emitter, atoms);
}
emitter.close_array();
}
JSONValue::String(a) => {
let vu = crate::utf8::convert_utf8_with_surrogates_to_utf16(atoms.bytes(*a));
emitter.emit_u16(&vu);
}
JSONValue::Number(n) => emitter.emit_f64(*n),
JSONValue::Boolean(b) => emitter.emit_bool(*b),
JSONValue::Null => emitter.emit_null_value(),
}
}
}
pub struct ArrayView<'a> {
values: &'a [&'a JSONValue<'a>],
}
impl<'a> ArrayView<'a> {
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn at(&self, pos: usize) -> &'a JSONValue<'a> {
self.values[pos]
}
pub fn iter(&self) -> impl Iterator<Item = &'a JSONValue<'a>> + '_ {
self.values.iter().copied()
}
}
pub struct ObjectView<'a> {
pub(crate) class: &'a JSONHiddenClass<'a>,
pub(crate) values: &'a [&'a JSONValue<'a>],
}
impl<'a> ObjectView<'a> {
pub fn size(&self) -> usize {
self.values.len()
}
pub fn get_hidden_class(&self) -> &'a JSONHiddenClass<'a> {
self.class
}
pub fn get(
&self,
name: &str,
atoms: &hermes_atom_table::AtomTable,
) -> Option<&'a JSONValue<'a>> {
self.class.find(name.as_bytes(), atoms).map(|i| self.values[i])
}
pub fn at(&self, name: &str, atoms: &hermes_atom_table::AtomTable) -> &'a JSONValue<'a> {
self.get(name, atoms).expect("name not found")
}
pub fn count(&self, name: &str, atoms: &hermes_atom_table::AtomTable) -> usize {
if self.class.find(name.as_bytes(), atoms).is_some() {
1
} else {
0
}
}
pub fn value_at(&self, index: usize) -> &'a JSONValue<'a> {
self.values[index]
}
pub fn key_at(&self, index: usize) -> hermes_atom_table::AtomBytes {
self.class.keys[index]
}
pub fn find(&self, name: &str, atoms: &hermes_atom_table::AtomTable) -> Option<usize> {
self.class.find(name.as_bytes(), atoms)
}
pub fn iter(
&self,
) -> impl Iterator<Item = (hermes_atom_table::AtomBytes, &'a JSONValue<'a>)> + '_ {
self.class.keys.iter().copied().zip(self.values.iter().copied())
}
}
pub struct JSONSharedValue {
value: *const JSONValue<'static>,
#[allow(dead_code)] allocator: std::rc::Rc<bumpalo::Bump>,
}
impl JSONSharedValue {
pub fn new(value: &JSONValue<'_>, allocator: std::rc::Rc<bumpalo::Bump>) -> JSONSharedValue {
let value: *const JSONValue<'static> =
(value as *const JSONValue<'_>).cast::<JSONValue<'static>>();
JSONSharedValue { value, allocator }
}
pub fn get(&self) -> &JSONValue<'_> {
#[allow(unsafe_code)] unsafe { &*self.value }
}
}
#[cfg(test)]
mod model_tests {
use super::*;
use bumpalo::Bump;
#[test]
fn kinds_and_scalar_accessors() {
let arena = Bump::new();
let n: &JSONValue = arena.alloc(JSONValue::Number(1.5));
let b: &JSONValue = arena.alloc(JSONValue::Boolean(true));
assert_eq!(n.kind(), JSONKind::Number);
assert_eq!(b.kind(), JSONKind::Boolean);
assert_eq!(n.as_number(), Some(1.5));
assert_eq!(b.as_boolean(), Some(true));
assert_eq!(n.as_boolean(), None);
assert_eq!(JSONValue::Null.kind(), JSONKind::Null);
assert_eq!(kind_to_string(JSONKind::Array), "Array");
}
#[test]
fn array_accessors() {
let arena = Bump::new();
let a = arena.alloc(JSONValue::Number(10.0));
let b = arena.alloc(JSONValue::Number(20.0));
let elems: &[&JSONValue] = arena.alloc_slice_copy(&[&*a, &*b]);
let arr = arena.alloc(JSONValue::Array(elems));
let view = arr.as_array().unwrap();
assert_eq!(view.len(), 2);
assert_eq!(view.at(0).as_number(), Some(10.0));
assert_eq!(view.iter().count(), 2);
}
#[test]
fn kind_to_string_all_variants() {
use JSONKind::*;
let pairs = [
(Object, "Object"),
(Array, "Array"),
(String, "String"),
(Number, "Number"),
(Boolean, "Boolean"),
(Null, "Null"),
];
for (k, s) in pairs {
assert_eq!(kind_to_string(k), s);
}
}
#[test]
fn emit_into_round_trip() {
use super::JSONFactory;
use bumpalo::Bump;
use hermes_atom_table::AtomTable;
use hermes_support::json_emitter::JSONEmitter;
let arena = Bump::new();
let atoms = AtomTable::new();
let f = JSONFactory::new(&arena, &atoms);
let nested = {
let p = (f.get_string_str("nested1"), f.get_boolean(true));
f.new_object(&mut [p]).unwrap()
};
let arr = f.new_array(&[f.get_boolean(false), f.get_null(), f.get_string_str("value2")]);
let obj = f.new_object(&mut [
(f.get_string_str("key1"), f.get_number(1.0)),
(f.get_string_str("key2"), f.get_string_str("value2")),
(f.get_string_str("key3"), nested),
(f.get_string_str("key4"), arr),
]).unwrap();
let mut s = String::new();
{
let mut e = JSONEmitter::new(&mut s, false);
obj.emit_into(&mut e, &atoms);
}
assert_eq!(s, r#"{"key1":1,"key2":"value2","key3":{"nested1":true},"key4":[false,null,"value2"]}"#);
}
#[test]
fn emit_into_astral_string() {
use super::JSONFactory;
use bumpalo::Bump;
use hermes_atom_table::AtomTable;
use hermes_support::json_emitter::JSONEmitter;
let arena = Bump::new();
let atoms = AtomTable::new();
let f = JSONFactory::new(&arena, &atoms);
let s = f.get_string_str("\u{10000}");
let mut out = String::new();
{ let mut e = JSONEmitter::new(&mut out, false); s.emit_into(&mut e, &atoms); }
assert_eq!(out, "\"\\ud800\\udc00\"");
}
#[test]
fn shared_value_outlives_parse() {
use std::rc::Rc;
use bumpalo::Bump;
let shared: JSONSharedValue = {
let arena = Rc::new(Bump::new());
let v: &JSONValue = arena.alloc(JSONValue::Number(3.5));
JSONSharedValue::new(v, arena.clone())
};
assert_eq!(shared.get().as_number(), Some(3.5));
}
#[test]
fn string_accessor_and_hidden_class_find() {
use hermes_atom_table::AtomTable;
let arena = Bump::new();
let atoms = AtomTable::new();
let a = atoms.atom_bytes("foo");
let s = arena.alloc(JSONValue::String(a));
assert_eq!(s.as_string(), Some(a));
assert_eq!(s.as_number(), None);
let ka = atoms.atom_bytes("a");
let kb = atoms.atom_bytes("b");
let kc = atoms.atom_bytes("c");
let keys: &[hermes_atom_table::AtomBytes] = arena.alloc_slice_copy(&[ka, kb, kc]);
let hc = JSONHiddenClass { keys };
assert_eq!(hc.find(b"a", &atoms), Some(0));
assert_eq!(hc.find(b"b", &atoms), Some(1));
assert_eq!(hc.find(b"c", &atoms), Some(2));
assert_eq!(hc.find(b"z", &atoms), None);
}
#[test]
fn object_find_index() {
use super::JSONFactory;
use bumpalo::Bump;
use hermes_atom_table::AtomTable;
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)),
])
.unwrap();
let o = obj.as_object().unwrap();
assert_eq!(o.find("a", &atoms), Some(0));
assert_eq!(o.find("b", &atoms), Some(1));
assert_eq!(o.find("zzz", &atoms), None);
assert_eq!(o.value_at(o.find("a", &atoms).unwrap()).as_number(), Some(1.0));
}
}