// Values: the types that every other part uses, the allocator, errors, and // JS's operators on every kind of value. // An object is a struct of its class, which the emitted C defines, and every // pointer to one is a real object: undefined and null are values of their own #pragma once #include #include #include typedef struct { char _; } undefined_t; typedef struct { char _; } null_t; // A native fn as a value: its index in the compiler's list (see native.rs) typedef struct { int32_t idx; } native_t; // A string: UTF-16 code units, as JS counts them typedef struct jsrt_string *string_t; // A new heap cell of `size` bytes, all zero void *__rt_alloc(size_t size); // `items`, an array's elements of `size` bytes each, with room for at least // `need` of them. `*cap` is the room that it has, and is set to the new room. // The new room is all zero void *__rt_grow(void *items, int32_t *cap, int32_t need, size_t size); // Throws a TypeError, which says `what`. Exceptions are not supported yet, so // it stops the program _Noreturn void __rt_type_error(const char *what); #ifdef JSRT_IMPLEMENTATION #include #include #include void *__rt_alloc(size_t size) { void *p = calloc(1, size); if (!p) { fputs("jsrt: out of memory\n", stderr); abort(); } return p; } void *__rt_grow(void *items, int32_t *cap, int32_t need, size_t size) { if (need <= *cap) { return items; } int32_t room = *cap * 2 > need ? *cap * 2 : need < 4 ? 4 : need; items = realloc(items, (size_t)room * size); if (!items) { fputs("jsrt: out of memory\n", stderr); abort(); } memset((char *)items + (size_t)*cap * size, 0, (size_t)(room - *cap) * size); *cap = room; return items; } _Noreturn void __rt_type_error(const char *what) { fprintf(stderr, "TypeError: %s\n", what); exit(1); } #endif // JSRT_IMPLEMENTATION // ─────────────────────────────────── Values and operators ──────────────────────────────── // // Infer gives each value a type. A value whose type has one member is a plain // C value of that member's C type: undefined_t, null_t, bool, int32_t, double, // string_t, native_t, or a pointer to an object's struct. A value whose type // has several members is a jsrt_value: a tag, which says which kind it is, and // the value. A number in a jsrt_value is always a double // // Each macro gives a result of one exact C type, because SET picks the // conversion by it: a comparison is cast to bool, and a bitwise result to int32_t // // The macros here take a value of any of these C types. VALUE(x) makes a // jsrt_value of any value, and each operator is one fn on jsrt_values. With a // plain C value, the tag is a constant, so the C compiler removes the tag and // the switch, and only the code for that kind is left // // The emitted C defines two things that the macros use: // JSRT_CLASS(x) the class of `x`, a pointer to an object // jsrt_class_is_function[k] whether the objects of class `k` are closures // // The array macros use their args more than once, so an arg must be a name // // The operators call str.h's and number.h's fns, so this part comes after them #include #include #include "str.h" #include "number.h" enum { JSRT_UNDEFINED, JSRT_NULL, JSRT_BOOL, JSRT_NUMBER, JSRT_STRING, JSRT_NATIVE, JSRT_OBJECT, // an object of class k has the tag JSRT_OBJECT + k }; typedef struct { uint32_t tag; union { bool b; double n; string_t s; int32_t native; void *o; } as; } jsrt_value; extern const bool jsrt_class_is_function[]; #define JSRT_INLINE static inline __attribute__((always_inline)) #define JSRT_NO_CONVERSION "converting an object to a string or a number is not supported" // ─────────────────────────────── Making and reading values ─────────────────────────────── // Any value, as a jsrt_value #define VALUE(x) \ _Generic((x), \ undefined_t: jsrt_of_undefined, \ null_t: jsrt_of_null, \ bool: jsrt_of_bool, \ int32_t: jsrt_of_int, \ double: jsrt_of_number, \ string_t: jsrt_of_string, \ native_t: jsrt_of_native, \ jsrt_value: jsrt_of_value, \ default: jsrt_of_object)((x), JSRT_CLASS(x)) // A jsrt_value, as a value of the C type of `like`. Its kind must be one that // the C type holds: infer makes sure of it #define FROM_VALUE(like, v) \ _Generic((like), \ undefined_t: jsrt_to_undefined, \ null_t: jsrt_to_null, \ bool: jsrt_to_bool, \ int32_t: jsrt_to_int, \ double: jsrt_to_number, \ string_t: jsrt_to_string, \ native_t: jsrt_to_native, \ jsrt_value: jsrt_to_value, \ default: jsrt_to_object)(v) // `dst = x`, with `x` converted to the C type of `dst` #define SET(dst, x) ((dst) = FROM_VALUE(dst, VALUE(x))) // `x`, converted to the C type `T` #define CAST(T, x) FROM_VALUE((T){0}, VALUE(x)) JSRT_INLINE jsrt_value jsrt_of_undefined(undefined_t x, uint32_t class) { (void)x, (void)class; return (jsrt_value){.tag = JSRT_UNDEFINED}; } JSRT_INLINE jsrt_value jsrt_of_null(null_t x, uint32_t class) { (void)x, (void)class; return (jsrt_value){.tag = JSRT_NULL}; } JSRT_INLINE jsrt_value jsrt_of_bool(bool x, uint32_t class) { (void)class; return (jsrt_value){.tag = JSRT_BOOL, .as.b = x}; } JSRT_INLINE jsrt_value jsrt_of_int(int32_t x, uint32_t class) { (void)class; return (jsrt_value){.tag = JSRT_NUMBER, .as.n = x}; } JSRT_INLINE jsrt_value jsrt_of_number(double x, uint32_t class) { (void)class; return (jsrt_value){.tag = JSRT_NUMBER, .as.n = x}; } JSRT_INLINE jsrt_value jsrt_of_string(string_t x, uint32_t class) { (void)class; return (jsrt_value){.tag = JSRT_STRING, .as.s = x}; } JSRT_INLINE jsrt_value jsrt_of_native(native_t x, uint32_t class) { (void)class; return (jsrt_value){.tag = JSRT_NATIVE, .as.native = x.idx}; } JSRT_INLINE jsrt_value jsrt_of_value(jsrt_value x, uint32_t class) { (void)class; return x; } JSRT_INLINE jsrt_value jsrt_of_object(const void *x, uint32_t class) { return (jsrt_value){.tag = JSRT_OBJECT + class, .as.o = (void *)x}; } // A read of a value as a kind that it is not is a bug in the compiler. The tag // is a constant wherever the kind is known, so the check costs nothing there JSRT_INLINE void jsrt_check(bool ok) { if (!ok) __rt_type_error("jsrt: a value was read as a kind that it is not"); } JSRT_INLINE undefined_t jsrt_to_undefined(jsrt_value v) { jsrt_check(v.tag == JSRT_UNDEFINED); return (undefined_t){0}; } JSRT_INLINE null_t jsrt_to_null(jsrt_value v) { jsrt_check(v.tag == JSRT_NULL); return (null_t){0}; } JSRT_INLINE bool jsrt_to_bool(jsrt_value v) { jsrt_check(v.tag == JSRT_BOOL); return v.as.b; } JSRT_INLINE int32_t jsrt_to_int(jsrt_value v) { jsrt_check(v.tag == JSRT_NUMBER); return (int32_t)v.as.n; } JSRT_INLINE double jsrt_to_number(jsrt_value v) { jsrt_check(v.tag == JSRT_NUMBER); return v.as.n; } JSRT_INLINE string_t jsrt_to_string(jsrt_value v) { jsrt_check(v.tag == JSRT_STRING); return v.as.s; } JSRT_INLINE native_t jsrt_to_native(jsrt_value v) { jsrt_check(v.tag == JSRT_NATIVE); return (native_t){v.as.native}; } JSRT_INLINE jsrt_value jsrt_to_value(jsrt_value v) { return v; } JSRT_INLINE void *jsrt_to_object(jsrt_value v) { jsrt_check(v.tag >= JSRT_OBJECT); return v.as.o; } // ────────────────────────────────────── Conversions ────────────────────────────────────── // The strings that conversions and `typeof` give, made once enum { JSRT_S_UNDEFINED, JSRT_S_NULL, JSRT_S_TRUE, JSRT_S_FALSE, JSRT_S_BOOLEAN, JSRT_S_NUMBER, JSRT_S_STRING, JSRT_S_FUNCTION, JSRT_S_OBJECT }; static inline string_t jsrt_name(int name) { static const char *const texts[] = {"undefined", "null", "true", "false", "boolean", "number", "string", "function", "object"}; static string_t made[sizeof texts / sizeof *texts]; return made[name] ? made[name] : (made[name] = __rt_string_lit(texts[name])); } // JS's ToNumber. An object's conversion would call its methods, which is not supported JSRT_INLINE double jsrt_number(jsrt_value v) { switch (v.tag) { case JSRT_UNDEFINED: return NAN; case JSRT_NULL: return 0; case JSRT_BOOL: return v.as.b; case JSRT_NUMBER: return v.as.n; case JSRT_STRING: return __rt_string_to_number(v.as.s); default: __rt_type_error(JSRT_NO_CONVERSION); } } // JS's ToString JSRT_INLINE string_t jsrt_string(jsrt_value v) { switch (v.tag) { case JSRT_UNDEFINED: return jsrt_name(JSRT_S_UNDEFINED); case JSRT_NULL: return jsrt_name(JSRT_S_NULL); case JSRT_BOOL: return jsrt_name(v.as.b ? JSRT_S_TRUE : JSRT_S_FALSE); case JSRT_NUMBER: return __rt_number_to_string(v.as.n); case JSRT_STRING: return v.as.s; default: __rt_type_error(JSRT_NO_CONVERSION); } } // JS's ToBoolean JSRT_INLINE bool jsrt_truthy(jsrt_value v) { switch (v.tag) { case JSRT_UNDEFINED: case JSRT_NULL: return false; case JSRT_BOOL: return v.as.b; case JSRT_NUMBER: return v.as.n == v.as.n && v.as.n != 0; case JSRT_STRING: return __rt_string_length(v.as.s) != 0; default: return true; } } JSRT_INLINE bool jsrt_is_function(jsrt_value v) { if (v.tag == JSRT_NATIVE) return true; return v.tag >= JSRT_OBJECT && jsrt_class_is_function[v.tag - JSRT_OBJECT]; } JSRT_INLINE string_t jsrt_typeof(jsrt_value v) { switch (v.tag) { case JSRT_UNDEFINED: return jsrt_name(JSRT_S_UNDEFINED); case JSRT_BOOL: return jsrt_name(JSRT_S_BOOLEAN); case JSRT_NUMBER: return jsrt_name(JSRT_S_NUMBER); case JSRT_STRING: return jsrt_name(JSRT_S_STRING); default: return jsrt_name(jsrt_is_function(v) ? JSRT_S_FUNCTION : JSRT_S_OBJECT); } } // ─────────────────────────────────────── Operators ─────────────────────────────────────── // `a + b`: a concatenation if either side is a string or an object, else a sum JSRT_INLINE jsrt_value jsrt_add(jsrt_value a, jsrt_value b) { if (a.tag >= JSRT_STRING || b.tag >= JSRT_STRING) { string_t s = __rt_string_concat(jsrt_string(a), jsrt_string(b)); return (jsrt_value){.tag = JSRT_STRING, .as.s = s}; } return (jsrt_value){.tag = JSRT_NUMBER, .as.n = jsrt_number(a) + jsrt_number(b)}; } // `a < b`, or `a <= b` if `or_equal`. Two strings compare as strings, anything else // as numbers JSRT_INLINE bool jsrt_less(jsrt_value a, jsrt_value b, bool or_equal) { if (a.tag == JSRT_STRING && b.tag == JSRT_STRING) { int32_t cmp = __rt_string_cmp(a.as.s, b.as.s); return or_equal ? cmp <= 0 : cmp < 0; } double x = jsrt_number(a), y = jsrt_number(b); return or_equal ? x <= y : x < y; } // `a === b`. Values of different kinds are never equal, and objects of // different classes are different objects JSRT_INLINE bool jsrt_strict_eq(jsrt_value a, jsrt_value b) { if (a.tag != b.tag) return false; switch (a.tag) { case JSRT_UNDEFINED: case JSRT_NULL: return true; case JSRT_BOOL: return a.as.b == b.as.b; case JSRT_NUMBER: return a.as.n == b.as.n; case JSRT_STRING: return __rt_string_eq(a.as.s, b.as.s); case JSRT_NATIVE: return a.as.native == b.as.native; default: return a.as.o == b.as.o; } } // `a == b`. Undefined and null are equal to each other and to nothing else. Two // objects compare as `===` does, and so do two values of one kind. Anything // else compares as numbers JSRT_INLINE bool jsrt_loose_eq(jsrt_value a, jsrt_value b) { bool a_nullish = a.tag <= JSRT_NULL, b_nullish = b.tag <= JSRT_NULL; bool objects = a.tag >= JSRT_NATIVE && b.tag >= JSRT_NATIVE; if (a_nullish || b_nullish) return a_nullish && b_nullish; if (objects || a.tag == b.tag) return jsrt_strict_eq(a, b); return jsrt_number(a) == jsrt_number(b); } #define NUMBER(x) _Generic((x), double: (x), default: jsrt_number(VALUE(x))) #define INT32(x) _Generic((x), int32_t: (x), default: __rt_to_int32(NUMBER(x))) #define STRING(x) jsrt_string(VALUE(x)) #define TRUTHY(x) jsrt_truthy(VALUE(x)) // Arithmetic. Every number is a double, so every result is a double, except // `+x` of an int #define ADD(a, b) jsrt_add(VALUE(a), VALUE(b)) #define SUB(a, b) (NUMBER(a) - NUMBER(b)) #define MUL(a, b) (NUMBER(a) * NUMBER(b)) #define DIV(a, b) (NUMBER(a) / NUMBER(b)) #define MOD(a, b) fmod(NUMBER(a), NUMBER(b)) #define POW(a, b) __rt_pow(NUMBER(a), NUMBER(b)) #define NEG(x) (-NUMBER(x)) #define PLUS(x) _Generic((x), int32_t: (x), default: NUMBER(x)) #define INC(x) (NUMBER(x) + 1) #define DEC(x) (NUMBER(x) - 1) // `x++` and `x--`: `old` gets x as a number, and `new` gets the next number #define POST_INC(old, new, x) \ do { \ double n = NUMBER(x); \ SET(old, n); \ SET(new, n + 1); \ } while (0) #define POST_DEC(old, new, x) \ do { \ double n = NUMBER(x); \ SET(old, n); \ SET(new, n - 1); \ } while (0) // Bitwise operators: on int32s, and a shift counts the low 5 bits only. `>>>` // gives a uint32, which is a double #define NOT(x) ((int32_t)~INT32(x)) #define AND(a, b) ((int32_t)(INT32(a) & INT32(b))) #define OR(a, b) ((int32_t)(INT32(a) | INT32(b))) #define XOR(a, b) ((int32_t)(INT32(a) ^ INT32(b))) #define SHL(a, b) ((int32_t)((uint32_t)INT32(a) << (INT32(b) & 31))) #define SAR(a, b) ((int32_t)(INT32(a) >> (INT32(b) & 31))) #define SHR(a, b) ((double)((uint32_t)INT32(a) >> (INT32(b) & 31))) // Comparisons. `a > b` is `b < a`, and `a >= b` is `b <= a` #define LT(a, b) jsrt_less(VALUE(a), VALUE(b), false) #define LTE(a, b) jsrt_less(VALUE(a), VALUE(b), true) #define GT(a, b) jsrt_less(VALUE(b), VALUE(a), false) #define GTE(a, b) jsrt_less(VALUE(b), VALUE(a), true) #define STRICT_EQ(a, b) jsrt_strict_eq(VALUE(a), VALUE(b)) #define STRICT_NEQ(a, b) ((bool)!STRICT_EQ(a, b)) #define EQ(a, b) jsrt_loose_eq(VALUE(a), VALUE(b)) #define NEQ(a, b) ((bool)!EQ(a, b)) // Tests of one value #define LNOT(x) ((bool)!TRUTHY(x)) #define TYPEOF(x) jsrt_typeof(VALUE(x)) #define IS_UNDEFINED(x) ((bool)(VALUE(x).tag == JSRT_UNDEFINED)) #define IS_NULL(x) ((bool)(VALUE(x).tag == JSRT_NULL)) #define IS_NULLISH(x) ((bool)(VALUE(x).tag <= JSRT_NULL)) #define IS_FUNCTION(x) jsrt_is_function(VALUE(x)) // ──────────────────────────────────── Natives' args ────────────────────────────────────── // An arg of a native, as its param's C type. An int and a float both pass for a // number, and an arg of any other wrong kind throws #define ARG_INT(x) \ _Generic((x), int32_t: (x), default: __rt_to_int32(jsrt_arg_number(VALUE(x)))) #define ARG_NUMBER(x) jsrt_arg_number(VALUE(x)) #define ARG_STRING(x) jsrt_arg_string(VALUE(x)) #define ARG_UNDEFINED(x) jsrt_arg_undefined(VALUE(x)) #define JSRT_WRONG_ARG "a native got an arg of the wrong kind" JSRT_INLINE double jsrt_arg_number(jsrt_value v) { if (v.tag != JSRT_NUMBER) __rt_type_error(JSRT_WRONG_ARG); return v.as.n; } JSRT_INLINE string_t jsrt_arg_string(jsrt_value v) { if (v.tag != JSRT_STRING) __rt_type_error(JSRT_WRONG_ARG); return v.as.s; } JSRT_INLINE undefined_t jsrt_arg_undefined(jsrt_value v) { if (v.tag != JSRT_UNDEFINED) __rt_type_error(JSRT_WRONG_ARG); return (undefined_t){0}; } // ──────────────────────────────────── Arrays and cells ─────────────────────────────────── // // Every array's struct has `length`, `cap` and `items`, so these work on any of them // Whether `i`, a number, is an index below `length` #define JSRT_IN_RANGE(i, length) ((i) >= 0 && (i) < (length) && (i) == (double)(int32_t)(i)) #define JSRT_BAD_INDEX "an array index must be a whole number from 0" #define JSRT_BAD_LENGTH "an array's length must be a whole number from 0" // `a = [...]` with room for `n` elements, which the caller sets #define ARRAY_NEW(a, n) \ do { \ (a) = __rt_alloc(sizeof *(a)); \ (a)->items = __rt_grow(NULL, &(a)->cap, (n), sizeof *(a)->items); \ (a)->length = (n); \ } while (0) // `dst = a[i]`: undefined if `i` is not an index in the array #define ARRAY_GET(dst, a, i) \ do { \ double index = NUMBER(i); \ if (JSRT_IN_RANGE(index, (a)->length)) SET(dst, (a)->items[(int32_t)index]); \ else SET(dst, (undefined_t){0}); \ } while (0) // `dst = s[i]`: undefined if `i` is not an index in the string #define STRING_GET(dst, s, i) \ do { \ double index = NUMBER(i); \ if (!JSRT_IN_RANGE(index, __rt_string_length(s))) SET(dst, (undefined_t){0}); \ else SET(dst, __rt_string_at((s), (int32_t)index)); \ } while (0) // `a[i] = v`. The array grows to hold `i`, and the elements between are holes. // An `i` that is not an index would be a named field, which is not supported #define ARRAY_PUT(a, i, v) \ do { \ double index = NUMBER(i); \ if (!JSRT_IN_RANGE(index, INT32_MAX)) __rt_type_error(JSRT_BAD_INDEX); \ int32_t at = (int32_t)index; \ if (at >= (a)->length) { \ (a)->items = __rt_grow((a)->items, &(a)->cap, at + 1, sizeof *(a)->items); \ (a)->length = at + 1; \ } \ SET((a)->items[at], v); \ } while (0) // `a.length = n`: the array gets `n` elements, and the new ones are holes. `n` // must be a whole number from 0, as JS's RangeError says #define ARRAY_SET_LENGTH(a, n) \ do { \ double length = NUMBER(n); \ if (!JSRT_IN_RANGE(length, INT32_MAX)) __rt_type_error(JSRT_BAD_LENGTH); \ (a)->items = __rt_grow((a)->items, &(a)->cap, (int32_t)length, sizeof *(a)->items); \ (a)->length = (int32_t)length; \ } while (0) // A new cell for `p`, a pointer to a cell, with the same value. Closures that // point to the old cell keep it (see transform/closure.rs) #define NEW_CELL(p) \ do { \ void *cell = __rt_alloc(sizeof *(p)); \ memcpy(cell, (p), sizeof *(p)); \ (p) = cell; \ } while (0)