hara-native 0.1.13

HAL-free native host runtime and package launcher for Hara
Documentation
#[cfg(all(target_arch = "wasm32", not(feature = "raw-wasm")))]
fn js_error_string(error: JsValue) -> String {
    if let Some(message) = error.as_string() {
        return message;
    }
    js_sys::Reflect::get(&error, &JsValue::from_str("message"))
        .ok()
        .and_then(|message| message.as_string())
        .unwrap_or_else(|| format!("{error:?}"))
}

#[cfg(all(target_arch = "wasm32", not(feature = "raw-wasm")))]
fn host_key_to_string(key: &core::Value) -> String {
    match key {
        core::Value::String(text) => text.clone(),
        core::Value::Keyword(keyword) => keyword.as_str().to_owned(),
        core::Value::Symbol(symbol) => symbol.as_str().to_owned(),
        other => other.display(),
    }
}

#[cfg(all(target_arch = "wasm32", not(feature = "raw-wasm")))]
fn host_seq_to_js<'a>(values: impl Iterator<Item = &'a core::Value>) -> Result<JsValue, String> {
    let array = js_sys::Array::new();
    for value in values {
        array.push(&value_to_js(value)?);
    }
    Ok(array.into())
}

#[cfg(all(target_arch = "wasm32", not(feature = "raw-wasm")))]
fn value_to_js(value: &core::Value) -> Result<JsValue, String> {
    match value {
        core::Value::Nil => Ok(JsValue::NULL),
        core::Value::Bool(flag) => Ok(JsValue::from_bool(*flag)),
        core::Value::Number(number)
            if (*number as i128).abs() <= js_sys::Number::MAX_SAFE_INTEGER as i128 =>
        {
            Ok(JsValue::from_f64(*number as f64))
        }
        core::Value::Number(number) => Ok(js_sys::BigInt::from(*number).into()),
        core::Value::BigInteger(number) => js_sys::BigInt::new(&JsValue::from_str(&number.to_string()))
            .map(Into::into)
            .map_err(|error| format!("std.native.Host/call integer-invalid: {}", js_error_string(error.into()))),
        core::Value::Float(number) => crate::numeric::finite_float(*number)
            .map(|number| JsValue::from_f64(number)),
        core::Value::String(text) => Ok(JsValue::from_str(text)),
        core::Value::Keyword(keyword) => Ok(JsValue::from_str(keyword.as_str())),
        core::Value::Symbol(symbol) => Ok(JsValue::from_str(symbol.as_str())),
        core::Value::Bytes(bytes) => Ok(js_sys::Uint8Array::from(&bytes[..]).into()),
        core::Value::Vector(values) => host_seq_to_js(values.iter()),
        core::Value::List(values) => host_seq_to_js(values.iter()),
        core::Value::Set(values) => host_seq_to_js(values.iter()),
        core::Value::OrderedSet(values) => host_seq_to_js(values.iter()),
        core::Value::Map(values) => {
            let object = js_sys::Object::new();
            for (key, value) in values.iter() {
                js_sys::Reflect::set(
                    &object,
                    &JsValue::from_str(&host_key_to_string(key)),
                    &value_to_js(value)?,
                )
                .map_err(js_error_string)?;
            }
            Ok(object.into())
        }
        core::Value::OrderedMap(values) => {
            let object = js_sys::Object::new();
            for entry in values.iter() {
                js_sys::Reflect::set(
                    &object,
                    &JsValue::from_str(&host_key_to_string(&entry.0)),
                    &value_to_js(&entry.1)?,
                )
                .map_err(js_error_string)?;
            }
            Ok(object.into())
        }
        other => Err(format!(
            "std.native.Host/call type-not-transportable: {}",
            other.display()
        )),
    }
}

#[cfg(all(target_arch = "wasm32", not(feature = "raw-wasm")))]
fn js_to_value(value: &JsValue) -> Result<core::Value, String> {
    use crate::lang::data::{OrderedMap as POrderedMap, Vector as PVector};
    use wasm_bindgen::{closure::Closure, JsCast};

    if value.is_null() || value.is_undefined() {
        return Ok(core::Value::Nil);
    }
    if let Some(flag) = value.as_bool() {
        return Ok(core::Value::Bool(flag));
    }
    if value.is_bigint() {
        let integer: js_sys::BigInt = value.clone().unchecked_into();
        if let Ok(value) = i64::try_from(integer.clone()) {
            return Ok(core::Value::Number(value));
        }
        let text = integer
            .to_string(10)
            .map_err(|error| format!("std.native.Host/call bigint is invalid: {error:?}"))?
            .as_string()
            .ok_or("std.native.Host/call bigint has no decimal representation")?;
        let value = num_bigint::BigInt::parse_bytes(text.as_bytes(), 10)
            .ok_or("std.native.Host/call bigint is invalid")?;
        return Ok(crate::numeric::compact_integer(value));
    }
    if let Some(number) = value.as_f64() {
        if number.fract() == 0.0
            && number >= js_sys::Number::MIN_SAFE_INTEGER
            && number <= js_sys::Number::MAX_SAFE_INTEGER
        {
            return Ok(core::Value::Number(number as i64));
        }
        return crate::numeric::finite_float(number).map(core::Value::Float);
    }
    if let Some(text) = value.as_string() {
        return Ok(core::Value::String(text));
    }
    if value.is_instance_of::<js_sys::Uint8Array>() {
        return Ok(core::Value::Bytes(js_sys::Uint8Array::new(value).to_vec()));
    }
    if value.is_instance_of::<js_sys::Promise>() {
        let source: js_sys::Promise = value.clone().unchecked_into();
        let pending = core::Promise::new();
        let fulfilled = pending.clone();
        let rejected = pending.clone();
        let on_fulfilled = Closure::once(move |value: JsValue| {
            match js_to_value(&value) {
                Ok(value) => fulfilled.resolve(value),
                Err(error) => fulfilled.reject(format!("host/result-invalid: {error}")),
            };
        });
        let on_rejected = Closure::once(move |error: JsValue| {
            rejected.reject(format!("host/rejected: {}", js_error_string(error)));
        });
        let _ = source.then2(&on_fulfilled, &on_rejected);
        on_fulfilled.forget();
        on_rejected.forget();
        return Ok(core::Value::Promise(pending));
    }
    if js_sys::Array::is_array(value) {
        let array = js_sys::Array::from(value);
        let mut items = Vec::with_capacity(array.length() as usize);
        for index in 0..array.length() {
            items.push(js_to_value(&array.get(index))?);
        }
        return Ok(core::Value::Vector(PVector::from_iter(items)));
    }
    if value.is_object() {
        let entries = js_sys::Object::entries(value.unchecked_ref::<js_sys::Object>());
        let mut items = Vec::with_capacity(entries.length() as usize);
        for index in 0..entries.length() {
            let entry = js_sys::Array::from(&entries.get(index));
            let key = entry.get(0).as_string().unwrap_or_default();
            let item = js_to_value(&entry.get(1))?;
            items.push((core::Value::String(key), item));
        }
        return Ok(core::Value::OrderedMap(Box::new(POrderedMap::from_iter(
            items,
        ))));
    }
    Err("std.native.Host/call type-not-transportable: unsupported JS result".into())
}

#[cfg(all(target_arch = "wasm32", not(feature = "raw-wasm")))]
fn host_call_bridge(
    handler: js_sys::Function,
) -> Rc<dyn Fn(String, String, Vec<core::Value>) -> Result<core::Value, String>> {
    Rc::new(move |service, method, args| {
        let js_args = js_sys::Array::new();
        for value in &args {
            js_args.push(&value_to_js(value)?);
        }
        let result = handler
            .call3(
                &JsValue::NULL,
                &JsValue::from(service),
                &JsValue::from(method),
                js_args.as_ref(),
            )
            .map_err(js_error_string)?;
        js_to_value(&result)
    })
}

#[cfg(all(target_arch = "wasm32", not(feature = "raw-wasm")))]
#[wasm_bindgen]
pub fn target_profile() -> String {
    if cfg!(target_os = "wasi") {
        "wasi".into()
    } else if cfg!(target_arch = "wasm32") {
        "wasm".into()
    } else {
        "native".into()
    }
}

#[cfg(all(target_arch = "wasm32", not(feature = "raw-wasm")))]
#[wasm_bindgen]
pub fn version() -> String {
    "hara-wasm/0.1 core-language slice".to_string()
}