Files
jsrt/compiler/src/emit/values.rs
T
2026-10-22 13:05:04 +00:00

105 lines
3.7 KiB
Rust

//! JS's rules on values, as C: the conversions and tests that depend only on a value's member
use super::types::{CType, Member};
/// The literal of a member that has one value
pub fn unit_literal(member: Member) -> &'static str {
match member {
Member::Undefined | Member::Native(_) => "(undefined_t){0}",
Member::Null => "(null_t){0}",
_ => unreachable!(),
}
}
/// The undefined value, and its C type
pub fn undefined() -> (String, CType) {
(
unit_literal(Member::Undefined).into(),
CType::One(Member::Undefined),
)
}
/// The C literal of an int
pub fn int(n: i64) -> String {
match n == i32::MIN as i64 {
true => "INT32_MIN".into(),
false => n.to_string(),
}
}
/// `expr`, a value of `member`, as a number (a `double`), as JS's ToNumber does. `None` for an
/// object: its conversion calls its methods, which is not supported
pub fn to_number(expr: &str, member: Member) -> Option<String> {
Some(match member {
Member::Undefined => "NAN".into(),
Member::Null => "0.0".into(),
Member::Bool | Member::Int => format!("(double){expr}"),
Member::Float => expr.into(),
Member::Str => format!("__rt_string_to_number({expr})"),
Member::Obj(_) | Member::Native(_) => return None,
})
}
/// `expr`, a value of `member`, as an int32, as JS's ToInt32 does. `None` for an object
pub fn to_int32(expr: &str, member: Member) -> Option<String> {
match member {
Member::Int => Some(expr.into()),
_ => Some(format!("__rt_to_int32({})", to_number(expr, member)?)),
}
}
/// Whether `expr`, a value of `member`, is truthy
pub fn truthy(expr: &str, member: Member) -> String {
match member {
Member::Undefined | Member::Null => "false".into(),
Member::Bool => expr.into(),
Member::Int => format!("({expr} != 0)"),
Member::Float => format!("({expr} == {expr} && {expr} != 0)"),
Member::Str => format!("(__rt_string_length({expr}) != 0)"),
Member::Obj(_) | Member::Native(_) => "true".into(),
}
}
/// A JS base type: the kinds of value that `===` tells apart
pub fn base(member: Member) -> u8 {
match member {
Member::Undefined => 0,
Member::Null => 1,
Member::Bool => 2,
Member::Int | Member::Float => 3,
Member::Str => 4,
Member::Obj(_) | Member::Native(_) => 5,
}
}
/// `a === b`, for one member of each. Different kinds are never equal. Numbers compare as
/// numbers, strings by their text, and objects by identity
pub fn strict_eq(l: &str, lm: Member, r: &str, rm: Member) -> String {
match (lm, rm) {
_ if base(lm) != base(rm) => "false".into(),
(Member::Undefined | Member::Null, _) => "true".into(),
(Member::Str, _) => format!("__rt_string_eq({l}, {r})"),
(Member::Obj(_), Member::Obj(_)) => format!("((void *){l} == (void *){r})"),
(Member::Native(a), Member::Native(b)) => (a == b).to_string(),
(Member::Obj(_) | Member::Native(_), _) => "false".into(),
_ => format!("({l} == {r})"),
}
}
/// An index of member `member` as an `int32_t`
pub fn as_index(index: &str, member: Member) -> String {
match member {
Member::Int => index.into(),
Member::Float => format!("(int32_t){index}"),
_ => unreachable!("an index is a number (see infer)"),
}
}
/// Whether an index is an array index below `length`. A float must be a whole number
pub fn in_range(index: &str, member: Member, length: &str) -> String {
match member {
Member::Int => format!("{index} >= 0 && {index} < {length}"),
_ => format!("{index} >= 0 && {index} < {length} && {index} == (double)(int32_t){index}"),
}
}