bamts-runtime 0.1.2

Execution runtime and built-in intrinsics for BamTS
Documentation
use std::collections::BTreeMap;

use bamts_bytecode::EcmaString;
use bamts_native::{Decoded, Value};

use super::{
    allocate_string, define_data, install_function, range_error, type_error, value_number,
};
use crate::intrinsics::{BuiltinHandler, BuiltinOutcome, BuiltinTable};
use crate::{EvalFailure, HeapEntry, Host, Machine, PropertyKey};

pub(super) fn install<H: Host>(
    heap: &mut Vec<HeapEntry>,
    globals: &mut BTreeMap<EcmaString, Value>,
    builtins: &mut BuiltinTable<H>,
) {
    let prototype = builtins.number_prototype();
    let constructor = install_function(heap, builtins, "Number", 1, constructor::<H>);
    builtins.set_constructor_prototype(heap, constructor, prototype);
    globals.insert(EcmaString::from_utf8("Number"), constructor);
    for (name, length, handler) in [
        ("isInteger", 1, is_integer::<H> as BuiltinHandler<H>),
        ("isSafeInteger", 1, is_safe_integer::<H>),
        ("isFinite", 1, is_finite::<H>),
        ("isNaN", 1, is_nan::<H>),
        ("parseFloat", 1, parse_float::<H>),
        ("parseInt", 2, parse_int::<H>),
    ] {
        let f = install_function(heap, builtins, name, length, handler);
        define_static(heap, constructor, name, f)
    }
    for (name, value) in [
        ("MAX_SAFE_INTEGER", 9007199254740991.0),
        ("MIN_SAFE_INTEGER", -9007199254740991.0),
        ("EPSILON", f64::EPSILON),
        ("MAX_VALUE", f64::MAX),
        ("MIN_VALUE", f64::from_bits(1)),
        ("POSITIVE_INFINITY", f64::INFINITY),
        ("NEGATIVE_INFINITY", f64::NEG_INFINITY),
        ("NaN", f64::NAN),
    ] {
        define_static(heap, constructor, name, crate::number_value(value))
    }
    for (name, length, handler) in [
        ("toString", 1, to_string::<H> as BuiltinHandler<H>),
        ("toFixed", 1, to_fixed::<H>),
        ("valueOf", 0, value_of::<H>),
    ] {
        let f = install_function(heap, builtins, name, length, handler);
        define_data(heap, prototype, name, f)
    }
}
fn define_static(heap: &mut [HeapEntry], constructor: Value, name: &str, value: Value) {
    let HeapEntry::NativeFunction { properties, .. } = &mut heap[super::heap_index(constructor)]
    else {
        panic!("Number constructor must be native")
    };
    properties.insert(
        PropertyKey::Named(EcmaString::from_utf8(name)),
        super::builtin_property(value),
    );
}
fn constructor<H: Host>(
    machine: &mut Machine<'_, H>,
    _: Value,
    args: &[Value],
    constructing: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let value = if args.is_empty() {
        Value::int32(0)
    } else {
        machine.to_number(args[0])?
    };
    if constructing {
        Ok(BuiltinOutcome::Value(machine.box_primitive(value)?))
    } else {
        Ok(BuiltinOutcome::Value(value))
    }
}
fn primitive_number<H: Host>(
    machine: &Machine<'_, H>,
    this: Value,
    op: &'static str,
) -> Result<f64, EvalFailure> {
    let value = machine.unbox_primitive_or_self(this)?;
    match value.decode() {
        Some(Decoded::Number(n)) => Ok(n),
        Some(Decoded::Int32(n)) => Ok(f64::from(n as i32)),
        _ => Err(type_error(op)),
    }
}
fn numeric(value: Value) -> Option<f64> {
    match value.decode() {
        Some(Decoded::Number(n)) => Some(n),
        Some(Decoded::Int32(n)) => Some(f64::from(n as i32)),
        _ => None,
    }
}
fn is_integer<H: Host>(
    _: &mut Machine<'_, H>,
    _: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let result = args
        .first()
        .and_then(|v| numeric(*v))
        .is_some_and(|n| n.is_finite() && n.fract() == 0.0);
    Ok(BuiltinOutcome::Value(Value::boolean(result)))
}
fn is_safe_integer<H: Host>(
    _: &mut Machine<'_, H>,
    _: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let result = args
        .first()
        .and_then(|v| numeric(*v))
        .is_some_and(|n| n.is_finite() && n.fract() == 0.0 && n.abs() <= 9007199254740991.0);
    Ok(BuiltinOutcome::Value(Value::boolean(result)))
}
fn is_finite<H: Host>(
    _: &mut Machine<'_, H>,
    _: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    Ok(BuiltinOutcome::Value(Value::boolean(
        args.first()
            .and_then(|v| numeric(*v))
            .is_some_and(f64::is_finite),
    )))
}
fn is_nan<H: Host>(
    _: &mut Machine<'_, H>,
    _: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    Ok(BuiltinOutcome::Value(Value::boolean(
        args.first()
            .and_then(|v| numeric(*v))
            .is_some_and(f64::is_nan),
    )))
}
fn trim_js(s: &str) -> &str {
    s.trim_matches(char::is_whitespace)
}
pub(super) fn parse_float<H: Host>(
    machine: &mut Machine<'_, H>,
    _: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let Ok(s) = machine
        .to_string(args.first().copied().unwrap_or(Value::UNDEFINED))?
        .to_utf8_strict()
    else {
        return Ok(BuiltinOutcome::Value(crate::number_value(f64::NAN)));
    };
    let s = trim_js(&s);
    let value = if s.starts_with("Infinity") {
        f64::INFINITY
    } else if s.starts_with("-Infinity") {
        f64::NEG_INFINITY
    } else {
        let mut end = 0;
        for i in 1..=s.len() {
            if s[..i].parse::<f64>().is_ok() {
                end = i
            }
        }
        if end == 0 {
            f64::NAN
        } else {
            s[..end].parse().unwrap_or(f64::NAN)
        }
    };
    Ok(BuiltinOutcome::Value(crate::number_value(value)))
}
pub(super) fn parse_int<H: Host>(
    machine: &mut Machine<'_, H>,
    _: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let Ok(text) = machine
        .to_string(args.first().copied().unwrap_or(Value::UNDEFINED))?
        .to_utf8_strict()
    else {
        return Ok(BuiltinOutcome::Value(crate::number_value(f64::NAN)));
    };
    let mut s = trim_js(&text);
    let mut sign = 1.0;
    if let Some(rest) = s.strip_prefix('-') {
        sign = -1.0;
        s = rest
    } else if let Some(rest) = s.strip_prefix('+') {
        s = rest
    }
    let requested =
        value_number(machine.to_number(args.get(1).copied().unwrap_or(Value::int32(0)))?) as i32;
    let mut radix = if requested == 0 { 10 } else { requested };
    if !(2..=36).contains(&radix) {
        return Ok(BuiltinOutcome::Value(crate::number_value(f64::NAN)));
    }
    if (requested == 0 || radix == 16) && s.get(..2).is_some_and(|x| x.eq_ignore_ascii_case("0x")) {
        radix = 16;
        s = &s[2..]
    }
    let mut value = 0.0;
    let mut found = false;
    for byte in s.bytes() {
        let digit = match byte {
            b'0'..=b'9' => u32::from(byte - b'0'),
            b'a'..=b'z' => u32::from(byte - b'a') + 10,
            b'A'..=b'Z' => u32::from(byte - b'A') + 10,
            _ => break,
        };
        if digit >= radix as u32 {
            break;
        }
        found = true;
        value = value * radix as f64 + f64::from(digit);
    }
    Ok(BuiltinOutcome::Value(crate::number_value(if found {
        sign * value
    } else {
        f64::NAN
    })))
}
pub(super) fn global_is_nan<H: Host>(
    machine: &mut Machine<'_, H>,
    _: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let n = value_number(machine.to_number(args.first().copied().unwrap_or(Value::UNDEFINED))?);
    Ok(BuiltinOutcome::Value(Value::boolean(n.is_nan())))
}
pub(super) fn global_is_finite<H: Host>(
    machine: &mut Machine<'_, H>,
    _: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let n = value_number(machine.to_number(args.first().copied().unwrap_or(Value::UNDEFINED))?);
    Ok(BuiltinOutcome::Value(Value::boolean(n.is_finite())))
}
fn to_string<H: Host>(
    machine: &mut Machine<'_, H>,
    this: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let n = primitive_number(
        machine,
        this,
        "Number.prototype.toString requires that 'this' be a Number",
    )?;
    let radix =
        value_number(machine.to_number(args.first().copied().unwrap_or(Value::int32(10)))?) as u32;
    if !(2..=36).contains(&radix) {
        return Err(range_error(
            "toString() radix argument must be between 2 and 36",
        ));
    }
    let text = if radix == 10 || !n.is_finite() {
        crate::format_number(n)
    } else {
        radix_string(n, radix)
    };
    Ok(BuiltinOutcome::Value(allocate_string(
        machine,
        EcmaString::from_utf8(&text),
    )?))
}
fn radix_string(n: f64, radix: u32) -> String {
    const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
    let sign = if n.is_sign_negative() { "-" } else { "" };
    let mut integer = n.abs().trunc() as u128;
    if integer == 0 {
        return format!("{sign}0");
    }
    let mut out = Vec::new();
    while integer > 0 {
        out.push(DIGITS[(integer % u128::from(radix)) as usize] as char);
        integer /= u128::from(radix)
    }
    out.reverse();
    format!("{sign}{}", out.into_iter().collect::<String>())
}
fn to_fixed<H: Host>(
    machine: &mut Machine<'_, H>,
    this: Value,
    args: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let n = primitive_number(
        machine,
        this,
        "Number.prototype.toFixed requires that 'this' be a Number",
    )?;
    let digits =
        value_number(machine.to_number(args.first().copied().unwrap_or(Value::int32(0)))?) as i32;
    if !(0..=100).contains(&digits) {
        return Err(range_error(
            "toFixed() digits argument must be between 0 and 100",
        ));
    }
    let text = if !n.is_finite() || n.abs() >= 1e21 {
        crate::format_number(n)
    } else {
        format!("{:.*}", digits as usize, n)
    };
    Ok(BuiltinOutcome::Value(allocate_string(
        machine,
        EcmaString::from_utf8(&text),
    )?))
}
fn value_of<H: Host>(
    machine: &mut Machine<'_, H>,
    this: Value,
    _: &[Value],
    _: bool,
) -> Result<BuiltinOutcome, EvalFailure> {
    let n = primitive_number(
        machine,
        this,
        "Number.prototype.valueOf requires that 'this' be a Number",
    )?;
    Ok(BuiltinOutcome::Value(crate::number_value(n)))
}