//! 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 { 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 { 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}"), } }