boa_runtime 0.21.1

Example runtime for the Boa JavaScript engine.
Documentation
//! All methods for deserializing a [`JsValueStore`] into a [`JsValue`].
use crate::store::{JsValueStore, StringStore, ValueStoreInner, unsupported_type};
use boa_engine::builtins::array_buffer::{AlignedVec, SharedArrayBuffer};
use boa_engine::builtins::typed_array::TypedArrayKind;
use boa_engine::object::builtins::{
    JsArray, JsArrayBuffer, JsDataView, JsDate, JsMap, JsRegExp, JsSet, JsSharedArrayBuffer,
    js_typed_array_from_kind,
};
use boa_engine::{Context, JsBigInt, JsObject, JsResult, JsString, JsValue, js_error};
use std::collections::HashMap;

#[derive(Default)]
pub(super) struct ReverseSeenMap(HashMap<usize, JsObject>);

impl ReverseSeenMap {
    fn get(&self, object: &JsValueStore) -> Option<JsObject> {
        let addr = std::ptr::from_ref(object.0.as_ref()).addr();
        self.0.get(&addr).cloned()
    }

    fn insert(&mut self, original: &JsValueStore, object: JsObject) {
        let addr = std::ptr::from_ref(original.0.as_ref()).addr();
        self.0.insert(addr, object);
    }
}

fn try_fields_into_js_object(
    store: &JsValueStore,
    fields: &Vec<(StringStore, JsValueStore)>,
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    let dolly = JsObject::with_object_proto(context.intrinsics());
    seen.insert(store, dolly.clone());

    for (k, v) in fields {
        let k = k.to_js_string();
        let value = try_value_into_js(v, seen, context)?;
        dolly.set(k, value, true, context)?;
    }
    Ok(JsValue::from(dolly))
}

fn try_items_into_js_array(
    store: &JsValueStore,
    items: &[Option<JsValueStore>],
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    let dolly = JsArray::new(context);
    seen.insert(store, dolly.clone().into());

    for (k, v) in items
        .iter()
        .enumerate()
        .filter_map(|(k, v)| v.as_ref().map(|v| (k, v)))
    {
        let value = try_value_into_js(v, seen, context)?;
        dolly.set(k, value, true, context)?;
    }
    Ok(JsValue::from(dolly))
}

fn try_into_js_array_buffer(
    store: &JsValueStore,
    data: &[u8],
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    let buffer = JsArrayBuffer::from_byte_block(AlignedVec::from_slice(0, data), context)?;
    let obj = JsObject::from(buffer);
    seen.insert(store, obj.clone());
    Ok(JsValue::from(obj))
}

fn try_into_js_shared_array_buffer(
    store: &JsValueStore,
    inner: &SharedArrayBuffer,
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsValue {
    let buffer = JsSharedArrayBuffer::from_buffer(inner.clone(), context);
    let obj = JsObject::from(buffer);
    seen.insert(store, obj.clone());
    JsValue::from(obj)
}

fn try_into_js_typed_array(
    store: &JsValueStore,
    kind: TypedArrayKind,
    buffer: &JsValueStore,
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    let buffer = try_value_into_js(buffer, seen, context)?;
    let Some(buffer) = buffer.as_object() else {
        return Err(unsupported_type());
    };
    let buffer = JsArrayBuffer::from_object(buffer)?;
    let array = js_typed_array_from_kind(kind, buffer, context)?;
    if let Some(o) = array.as_object() {
        seen.insert(store, o);
    }
    Ok(array)
}

fn try_into_js_map(
    store: &JsValueStore,
    key_values: &[(JsValueStore, JsValueStore)],
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    let map = JsMap::new(context);
    seen.insert(store, map.clone().into());
    for (k, v) in key_values {
        let k = try_value_into_js(k, seen, context)?;
        let v = try_value_into_js(v, seen, context)?;
        map.set(k, v, context)?;
    }

    Ok(JsValue::from(map))
}

fn try_into_js_set(
    store: &JsValueStore,
    values: &[JsValueStore],
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    let set = JsSet::new(context);
    seen.insert(store, set.clone().into());
    for v in values {
        let v = try_value_into_js(v, seen, context)?;
        set.add(v, context)?;
    }

    Ok(JsValue::from(set))
}

fn try_into_js_date(
    store: &JsValueStore,
    ms_since_epoch: f64,
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    let date = JsDate::new(context);
    date.set_time(ms_since_epoch, context)?;
    seen.insert(store, date.clone().into());

    Ok(JsValue::from(date))
}

fn try_into_regexp(
    store: &JsValueStore,
    source: &str,
    flags: &str,
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    let re = JsRegExp::new(JsString::from(source), JsString::from(flags), context)?;
    seen.insert(store, re.clone().into());
    Ok(JsValue::from(re))
}

fn try_into_data_view(
    store: &JsValueStore,
    buffer: &JsValueStore,
    byte_length: u64,
    byte_offset: u64,
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    let buffer = try_value_into_js(buffer, seen, context)?;
    let data_view = JsDataView::from_js_array_buffer(
        JsArrayBuffer::from_object(buffer.as_object().ok_or_else(unsupported_type)?)?,
        Some(byte_offset),
        Some(byte_length),
        context,
    )?;

    seen.insert(store, data_view.clone().into());
    Ok(JsValue::from(data_view))
}

pub(super) fn try_value_into_js(
    store: &JsValueStore,
    seen: &mut ReverseSeenMap,
    context: &mut Context,
) -> JsResult<JsValue> {
    if let Some(v) = seen.get(store) {
        return Ok(JsValue::from(v));
    }

    // Match the value
    match &*store.0 {
        ValueStoreInner::Empty => {
            unreachable!("ValueStoreInner::Empty should not exist after storage.");
        }
        ValueStoreInner::Null => Ok(JsValue::null()),
        ValueStoreInner::Undefined => Ok(JsValue::undefined()),
        ValueStoreInner::Boolean(b) => Ok(JsValue::from(*b)),
        ValueStoreInner::Float(f) => Ok(JsValue::from(*f)),
        ValueStoreInner::String(s) => Ok(JsValue::from(s.to_js_string())),
        ValueStoreInner::BigInt(b) => Ok(JsValue::from(JsBigInt::new(b.clone()))),
        ValueStoreInner::Object(fields) => try_fields_into_js_object(store, fields, seen, context),
        ValueStoreInner::Map(key_values) => try_into_js_map(store, key_values, seen, context),
        ValueStoreInner::Set(values) => try_into_js_set(store, values, seen, context),
        ValueStoreInner::Array(items) => try_items_into_js_array(store, items, seen, context),
        ValueStoreInner::Date(msec) => try_into_js_date(store, *msec, seen, context),
        ValueStoreInner::Error { .. } => Err(js_error!("Not yet implemented.")),
        ValueStoreInner::RegExp { source, flags } => {
            try_into_regexp(store, source, flags, seen, context)
        }
        ValueStoreInner::ArrayBuffer(data) => try_into_js_array_buffer(store, data, seen, context),
        ValueStoreInner::SharedArrayBuffer(inner) => {
            Ok(try_into_js_shared_array_buffer(store, inner, seen, context))
        }
        ValueStoreInner::DataView {
            buffer,
            byte_length,
            byte_offset,
        } => try_into_data_view(store, buffer, *byte_length, *byte_offset, seen, context),
        ValueStoreInner::TypedArray { kind, buffer } => {
            try_into_js_typed_array(store, *kind, buffer, seen, context)
        }
    }
}