// any_t: a value of any kind, and JS's operators on it #pragma once #include "number.h" // A value of any kind. An object in it is the pointer to its cell, so it keeps // its identity. A field can also hold absent typedef enum { JSRT_KIND_ABSENT, JSRT_KIND_UNDEFINED, JSRT_KIND_NULL, JSRT_KIND_BOOL, JSRT_KIND_INT, JSRT_KIND_FLOAT, JSRT_KIND_STRING, JSRT_KIND_OBJECT, } jsrt_kind; typedef struct { jsrt_kind kind; union { bool b; int32_t i; double f; string_t s; void *o; } as; } any_t; // Box a value. A pointer that is a sentinel (NULL, JSRT_UNDEFINED, // JSRT_ABSENT) boxes as null, undefined or absent any_t __any_undefined(void); any_t __any_null(void); any_t __any_absent(void); any_t __any_from_bool(bool b); any_t __any_from_int(int32_t n); any_t __any_from_float(double x); any_t __any_from_string(string_t s); any_t __any_from_object(void *o); // Unbox a value. A value of another kind throws. A string or an object unboxes // null, undefined and absent to their sentinels bool __any_as_bool(any_t a); int32_t __any_as_int(any_t a); double __any_as_float(any_t a); // an int too string_t __any_as_string(any_t a); void *__any_as_object(any_t a); // The kind of a value bool __any_is_absent(any_t a); bool __any_is_undefined(any_t a); bool __any_is_null(any_t a); bool __any_is_nullish(any_t a); // undefined or null bool __any_is_boolean(any_t a); bool __any_is_number(any_t a); bool __any_is_string(any_t a); bool __any_is_object(any_t a); // a fn too bool __any_is_function(any_t a); // JS's operators, with JS's conversions any_t __any_add(any_t a, any_t b); any_t __any_sub(any_t a, any_t b); any_t __any_mul(any_t a, any_t b); any_t __any_div(any_t a, any_t b); any_t __any_mod(any_t a, any_t b); any_t __any_pow(any_t a, any_t b); any_t __any_neg(any_t a); any_t __any_plus(any_t a); // ToNumber any_t __any_inc(any_t a); any_t __any_dec(any_t a); bool __any_lt(any_t a, any_t b); bool __any_lte(any_t a, any_t b); bool __any_gt(any_t a, any_t b); bool __any_gte(any_t a, any_t b); bool __any_eq(any_t a, any_t b); bool __any_neq(any_t a, any_t b); bool __any_strict_eq(any_t a, any_t b); bool __any_strict_neq(any_t a, any_t b); // JS's abstract operations bool __any_truthy(any_t a); // ToBoolean int32_t __any_to_int32(any_t a); // ToInt32 string_t __any_to_string(any_t a); // ToString string_t __any_typeof(any_t a); #ifdef JSRT_IMPLEMENTATION #include // ── any: boxing ── any_t __any_undefined(void) { return (any_t){ .kind = JSRT_KIND_UNDEFINED }; } any_t __any_null(void) { return (any_t){ .kind = JSRT_KIND_NULL }; } any_t __any_absent(void) { return (any_t){ .kind = JSRT_KIND_ABSENT }; } any_t __any_from_bool(bool b) { return (any_t){ .kind = JSRT_KIND_BOOL, .as.b = b }; } any_t __any_from_int(int32_t n) { return (any_t){ .kind = JSRT_KIND_INT, .as.i = n }; } any_t __any_from_float(double x) { return (any_t){ .kind = JSRT_KIND_FLOAT, .as.f = x }; } // A pointer that can be a sentinel, as the kind it stands for, else as `kind` static any_t jsrt_from_ptr(void *p, jsrt_kind kind) { if (p == NULL) return __any_null(); if (p == JSRT_UNDEFINED) return __any_undefined(); if (p == JSRT_ABSENT) return __any_absent(); return (any_t){ .kind = kind, .as.o = p }; } any_t __any_from_string(string_t s) { return jsrt_from_ptr(s, JSRT_KIND_STRING); } any_t __any_from_object(void *o) { return jsrt_from_ptr(o, JSRT_KIND_OBJECT); } bool __any_as_bool(any_t a) { if (a.kind != JSRT_KIND_BOOL) jsrt_type_error(); return a.as.b; } double __any_as_float(any_t a) { if (a.kind == JSRT_KIND_INT) return a.as.i; if (a.kind != JSRT_KIND_FLOAT) jsrt_type_error(); return a.as.f; } int32_t __any_as_int(any_t a) { return a.kind == JSRT_KIND_INT ? a.as.i : (int32_t)__any_as_float(a); } // A pointer of kind `kind`, or the sentinel of a nullish or absent value static void *jsrt_as_ptr(any_t a, jsrt_kind kind) { switch (a.kind) { case JSRT_KIND_NULL: return NULL; case JSRT_KIND_UNDEFINED: return JSRT_UNDEFINED; case JSRT_KIND_ABSENT: return JSRT_ABSENT; default: if (a.kind != kind) jsrt_type_error(); return a.as.o; } } string_t __any_as_string(any_t a) { return jsrt_as_ptr(a, JSRT_KIND_STRING); } void *__any_as_object(any_t a) { return jsrt_as_ptr(a, JSRT_KIND_OBJECT); } // ── any: kinds ── bool __any_is_absent(any_t a) { return a.kind == JSRT_KIND_ABSENT; } bool __any_is_undefined(any_t a) { return a.kind == JSRT_KIND_UNDEFINED || a.kind == JSRT_KIND_ABSENT; } bool __any_is_null(any_t a) { return a.kind == JSRT_KIND_NULL; } bool __any_is_nullish(any_t a) { return __any_is_undefined(a) || __any_is_null(a); } bool __any_is_boolean(any_t a) { return a.kind == JSRT_KIND_BOOL; } bool __any_is_number(any_t a) { return a.kind == JSRT_KIND_INT || a.kind == JSRT_KIND_FLOAT; } bool __any_is_string(any_t a) { return a.kind == JSRT_KIND_STRING; } bool __any_is_object(any_t a) { return a.kind == JSRT_KIND_OBJECT; } bool __any_is_function(any_t a) { return a.kind == JSRT_KIND_OBJECT && jsrt_fn_sites[((jsrt_header *)a.as.o)->site]; } // ── any: abstract operations ── // ToNumber. An object would call its `valueOf` or `toString` first, which the // runtime cannot do, so it gives NaN static double jsrt_to_number(any_t a) { switch (a.kind) { case JSRT_KIND_NULL: return 0; case JSRT_KIND_BOOL: return a.as.b; case JSRT_KIND_INT: return a.as.i; case JSRT_KIND_FLOAT: return a.as.f; case JSRT_KIND_STRING: return __native_string_to_number(a.as.s); default: return NAN; } } bool __any_truthy(any_t a) { switch (a.kind) { case JSRT_KIND_BOOL: return a.as.b; case JSRT_KIND_INT: return a.as.i != 0; case JSRT_KIND_FLOAT: return a.as.f != 0 && !isnan(a.as.f); case JSRT_KIND_STRING: return a.as.s->length != 0; case JSRT_KIND_OBJECT: return true; default: return false; } } int32_t __any_to_int32(any_t a) { double x = jsrt_to_number(a); if (isnan(x) || isinf(x)) { return 0; } x = fmod(trunc(x), 4294967296.0); return (int32_t)(uint32_t)(int64_t)(x < 0 ? x + 4294967296.0 : x); } // ToString. An object would call its `toString`, which the runtime cannot do string_t __any_to_string(any_t a) { switch (a.kind) { case JSRT_KIND_UNDEFINED: case JSRT_KIND_ABSENT: return jsrt_string_lit("undefined"); case JSRT_KIND_NULL: return jsrt_string_lit("null"); case JSRT_KIND_BOOL: return jsrt_string_lit(a.as.b ? "true" : "false"); case JSRT_KIND_INT: return __native_number_to_string(a.as.i); case JSRT_KIND_FLOAT: return __native_number_to_string(a.as.f); case JSRT_KIND_STRING: return a.as.s; default: return jsrt_string_lit(__any_is_function(a) ? "function" : "[object Object]"); } } string_t __any_typeof(any_t a) { switch (a.kind) { case JSRT_KIND_UNDEFINED: case JSRT_KIND_ABSENT: return jsrt_string_lit("undefined"); case JSRT_KIND_BOOL: return jsrt_string_lit("boolean"); case JSRT_KIND_INT: case JSRT_KIND_FLOAT: return jsrt_string_lit("number"); case JSRT_KIND_STRING: return jsrt_string_lit("string"); case JSRT_KIND_OBJECT: return jsrt_string_lit(__any_is_function(a) ? "function" : "object"); default: return jsrt_string_lit("object"); } } // ── any: operators ── any_t __any_add(any_t a, any_t b) { // An object becomes a string (see `__any_to_string`), so it concatenates bool strings = a.kind == JSRT_KIND_STRING || b.kind == JSRT_KIND_STRING || a.kind == JSRT_KIND_OBJECT || b.kind == JSRT_KIND_OBJECT; if (strings) { return __any_from_string(__native_string_concat(__any_to_string(a), __any_to_string(b))); } return __any_from_float(jsrt_to_number(a) + jsrt_to_number(b)); } any_t __any_sub(any_t a, any_t b) { return __any_from_float(jsrt_to_number(a) - jsrt_to_number(b)); } any_t __any_mul(any_t a, any_t b) { return __any_from_float(jsrt_to_number(a) * jsrt_to_number(b)); } any_t __any_div(any_t a, any_t b) { return __any_from_float(jsrt_to_number(a) / jsrt_to_number(b)); } any_t __any_mod(any_t a, any_t b) { return __any_from_float(fmod(jsrt_to_number(a), jsrt_to_number(b))); } any_t __any_pow(any_t a, any_t b) { return __any_from_float(pow(jsrt_to_number(a), jsrt_to_number(b))); } any_t __any_neg(any_t a) { return __any_from_float(-jsrt_to_number(a)); } any_t __any_plus(any_t a) { return __any_from_float(jsrt_to_number(a)); } any_t __any_inc(any_t a) { return __any_from_float(jsrt_to_number(a) + 1); } any_t __any_dec(any_t a) { return __any_from_float(jsrt_to_number(a) - 1); } // ── any: comparisons ── // Two strings compare by code units. Anything else compares as numbers, where // NaN makes every comparison false bool __any_lt(any_t a, any_t b) { bool strings = a.kind == JSRT_KIND_STRING && b.kind == JSRT_KIND_STRING; return strings ? jsrt_string_cmp(a.as.s, b.as.s) < 0 : jsrt_to_number(a) < jsrt_to_number(b); } bool __any_lte(any_t a, any_t b) { bool strings = a.kind == JSRT_KIND_STRING && b.kind == JSRT_KIND_STRING; return strings ? jsrt_string_cmp(a.as.s, b.as.s) <= 0 : jsrt_to_number(a) <= jsrt_to_number(b); } bool __any_gt(any_t a, any_t b) { return __any_lt(b, a); } bool __any_gte(any_t a, any_t b) { return __any_lte(b, a); } bool __any_strict_eq(any_t a, any_t b) { if (__any_is_number(a) && __any_is_number(b)) { return jsrt_to_number(a) == jsrt_to_number(b); } if (__any_is_undefined(a) || __any_is_undefined(b)) { return __any_is_undefined(a) && __any_is_undefined(b); } if (a.kind != b.kind) { return false; } switch (a.kind) { case JSRT_KIND_NULL: return true; case JSRT_KIND_BOOL: return a.as.b == b.as.b; case JSRT_KIND_STRING: return jsrt_string_eq(a.as.s, b.as.s); default: return a.as.o == b.as.o; } } bool __any_strict_neq(any_t a, any_t b) { return !__any_strict_eq(a, b); } // Undefined and null equal each other and nothing else. Values of one kind, // and objects, compare as `===` does. Other primitives compare as numbers bool __any_eq(any_t a, any_t b) { if (__any_is_nullish(a) || __any_is_nullish(b)) { return __any_is_nullish(a) && __any_is_nullish(b); } bool same = a.kind == b.kind || (__any_is_number(a) && __any_is_number(b)); if (same || a.kind == JSRT_KIND_OBJECT || b.kind == JSRT_KIND_OBJECT) { return __any_strict_eq(a, b); } return jsrt_to_number(a) == jsrt_to_number(b); } bool __any_neq(any_t a, any_t b) { return !__any_eq(a, b); } #endif // JSRT_IMPLEMENTATION