From a04bebd0d7b926ae3ece61d27d2011b24bc90f47 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 2 Feb 2026 10:38:48 -0600 Subject: [PATCH] fix str --- meson.build | 1 + source/cell.c | 36 ++- source/quickjs.c | 98 ++++---- source/suite.c | 578 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 664 insertions(+), 49 deletions(-) create mode 100644 source/suite.c diff --git a/meson.build b/meson.build index 31ff2435..ceab2c0b 100644 --- a/meson.build +++ b/meson.build @@ -40,6 +40,7 @@ sources = [] src += [ # core 'monocypher.c', 'cell.c', + 'suite.c', 'wildmatch.c', 'qjs_actor.c', 'qjs_wota.c', diff --git a/source/cell.c b/source/cell.c index b591ef08..fe69f642 100644 --- a/source/cell.c +++ b/source/cell.c @@ -19,6 +19,9 @@ #include #include +/* Test suite declaration */ +int run_c_test_suite(JSContext *ctx); + cell_rt *root_cell = NULL; static char *core_path = NULL; @@ -196,12 +199,43 @@ static void signal_handler(int sig) } #endif if (!str) return; - + exit_handler(); } +/* Run the C test suite with minimal runtime setup */ +static int run_test_suite(void) +{ + JSRuntime *rt = JS_NewRuntime(); + if (!rt) { + printf("Failed to create JS runtime\n"); + return 1; + } + + JSContext *ctx = JS_NewContextRaw(rt); + if (!ctx) { + printf("Failed to create JS context\n"); + JS_FreeRuntime(rt); + return 1; + } + + JS_AddIntrinsicBaseObjects(ctx); + + int result = run_c_test_suite(ctx); + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return result; +} + int cell_init(int argc, char **argv) { + /* Check for --test flag to run C test suite */ + if (argc >= 2 && strcmp(argv[1], "--test") == 0) { + return run_test_suite(); + } + int script_start = 1; /* Find the cell shop at ~/.cell */ diff --git a/source/quickjs.c b/source/quickjs.c index b68cbedb..e05136a4 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -2791,18 +2791,6 @@ static JSText *pretext_concat_value (JSContext *ctx, JSText *s, JSValue v) { return s; } -static JSText *pretext_concat_value_free (JSContext *ctx, JSText *s, JSValue v) { - JSText *p; - - if (unlikely (JS_VALUE_GET_TAG (v) != JS_TAG_STRING)) { - v = JS_ToString (ctx, v); - if (JS_IsException (v)) return NULL; - } - p = JS_VALUE_GET_STRING (v); - s = pretext_concat (ctx, s, p, 0, (uint32_t)JSText_len (p)); - return s; -} - /* Finalize a pretext into an immutable JSValue string */ static JSValue pretext_end (JSContext *ctx, JSText *s) { if (!s) return JS_EXCEPTION; @@ -2889,23 +2877,23 @@ JSValue JS_NewStringLen (JSContext *ctx, const char *buf, size_t buf_len) { static JSValue JS_ConcatString3 (JSContext *ctx, const char *str1, JSValue str2, const char *str3) { JSText *b; - int len1, len3; - JSText *p; + int len1, len3, str2_len; - if (unlikely (JS_VALUE_GET_TAG (str2) != JS_TAG_STRING)) { + if (!JS_IsText (str2)) { str2 = JS_ToString (ctx, str2); if (JS_IsException (str2)) goto fail; } - p = JS_VALUE_GET_STRING (str2); + + str2_len = js_string_value_len (str2); len1 = strlen (str1); len3 = strlen (str3); - b = pretext_init (ctx, len1 + (int)JSText_len (p) + len3); + b = pretext_init (ctx, len1 + str2_len + len3); if (!b) goto fail; b = pretext_write8 (ctx, b, (const uint8_t *)str1, len1); if (!b) goto fail; - b = pretext_concat (ctx, b, p, 0, (uint32_t)JSText_len (p)); + b = pretext_concat_value (ctx, b, str2); if (!b) goto fail; b = pretext_write8 (ctx, b, (const uint8_t *)str3, len3); if (!b) goto fail; @@ -2922,19 +2910,32 @@ fail: * sequences */ const char *JS_ToCStringLen2 (JSContext *ctx, size_t *plen, JSValue val1, BOOL cesu8) { JSValue val; - JSText *str; char *q, *ret; size_t size; int i, len; - if (JS_VALUE_GET_TAG (val1) != JS_TAG_STRING) { + if (!JS_IsText (val1)) { val = JS_ToString (ctx, val1); if (JS_IsException (val)) goto fail; } else { val = val1; } - str = JS_VALUE_GET_STRING (val); + /* Handle immediate ASCII strings */ + if (MIST_IsImmediateASCII (val)) { + len = MIST_GetImmediateASCIILen (val); + ret = js_malloc (ctx, len + 1); + if (!ret) goto fail; + for (i = 0; i < len; i++) { + ret[i] = MIST_GetImmediateASCIIChar (val, i); + } + ret[len] = '\0'; + if (plen) *plen = len; + return ret; + } + + /* Handle heap strings (JSText) */ + JSText *str = JS_VALUE_GET_STRING (val); len = (int)JSText_len (str); /* Calculate UTF-8 size */ @@ -4350,9 +4351,8 @@ static BOOL js_object_has_name (JSContext *ctx, JSValue obj) { int slot = rec_find_slot (rec, name_key); if (slot <= 0) return FALSE; JSValue val = rec->slots[slot].val; - if (JS_VALUE_GET_TAG (val) != JS_TAG_STRING) return TRUE; - JSText *p = JS_VALUE_GET_STRING (val); - return (JSText_len (p) != 0); + if (!JS_IsText (val)) return TRUE; /* has name but it's not a string = truthy */ + return (js_string_value_len (val) != 0); } static int JS_DefineObjectName (JSContext *ctx, JSValue obj, JSValue name) { @@ -4942,7 +4942,7 @@ int JS_ToFloat64 (JSContext *ctx, double *pres, JSValue val) { uint32_t tag; tag = JS_VALUE_GET_TAG (val); - if (tag <= JS_TAG_NULL) { + if (tag == JS_TAG_INT) { *pres = JS_VALUE_GET_INT (val); return 0; } else if (JS_TAG_IS_FLOAT64 (tag)) { @@ -5344,16 +5344,8 @@ static void js_dump_char (JSPrintValueState *s, int c, int sep) { } static void js_print_string_rec (JSPrintValueState *s, JSValue val, int sep, uint32_t pos) { - if (JS_VALUE_GET_TAG (val) == JS_TAG_STRING) { - JSText *p = JS_VALUE_GET_STRING (val); - uint32_t i, len; - if (pos < s->options.max_string_length) { - len = min_uint32 ((uint32_t)JSText_len (p), s->options.max_string_length - pos); - for (i = 0; i < len; i++) { - js_dump_char (s, string_get (p, i), sep); - } - } - } else if (MIST_IsImmediateASCII (val)) { + if (MIST_IsImmediateASCII (val)) { + /* Immediate ASCII string */ int len = MIST_GetImmediateASCIILen (val); if (pos < s->options.max_string_length) { uint32_t i, l; @@ -5362,6 +5354,16 @@ static void js_print_string_rec (JSPrintValueState *s, JSValue val, int sep, uin js_dump_char (s, MIST_GetImmediateASCIIChar (val, i), sep); } } + } else if (JS_IsPtr (val) && objhdr_type (*(objhdr_t *)JS_VALUE_GET_PTR (val)) == OBJ_TEXT) { + /* Heap text (JSText) */ + JSText *p = (JSText *)JS_VALUE_GET_PTR (val); + uint32_t i, len; + if (pos < s->options.max_string_length) { + len = min_uint32 ((uint32_t)JSText_len (p), s->options.max_string_length - pos); + for (i = 0; i < len; i++) { + js_dump_char (s, string_get (p, i), sep); + } + } } else { js_printf (s, "", (int)JS_VALUE_GET_TAG (val)); } @@ -18944,9 +18946,8 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSRecord *p; JSRegExp *re; - /* sanity check */ - if (JS_VALUE_GET_TAG (bc) != JS_TAG_STRING - || JS_VALUE_GET_TAG (pattern) != JS_TAG_STRING) { + /* sanity check - need heap strings for pattern and bytecode */ + if (!JS_IsText (bc) || !JS_IsText (pattern)) { JS_ThrowTypeError (ctx, "string expected"); fail: return JS_EXCEPTION; @@ -18959,8 +18960,9 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, re = js_malloc (ctx, sizeof(JSRegExp)); if (!re) goto fail; REC_SET_OPAQUE(p, re); - re->pattern = JS_VALUE_GET_STRING (pattern); - re->bytecode = JS_VALUE_GET_STRING (bc); + /* Store pattern and bytecode - need to handle both immediate and heap strings */ + re->pattern = MIST_IsImmediateASCII (pattern) ? NULL : (JSText *)JS_VALUE_GET_PTR (pattern); + re->bytecode = MIST_IsImmediateASCII (bc) ? NULL : (JSText *)JS_VALUE_GET_PTR (bc); { JSValue key = JS_KEY_STR (ctx, "lastIndex"); JS_SetPropertyInternal (ctx, obj, key, JS_NewInt32 (ctx, 0)); @@ -19873,7 +19875,7 @@ concat_primitive: case JS_TAG_BOOL: case JS_TAG_NULL: concat_value: - jsc->b = pretext_concat_value_free (ctx, jsc->b, val); + jsc->b = pretext_concat_value (ctx, jsc->b, val); return jsc->b ? 0 : -1; default: return 0; @@ -20928,7 +20930,7 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue JSValue item_str = JS_ToString (ctx, item); if (JS_IsException (item_str)) goto array_fail; - b = pretext_concat_value_free (ctx, b, item_str); + b = pretext_concat_value (ctx, b, item_str); if (!b) goto array_fail; } @@ -21120,7 +21122,7 @@ static JSValue js_cell_text_codepoint (JSContext *ctx, JSValue this_val, int arg static JSText *pt_concat_value_to_string_free (JSContext *ctx, JSText *b, JSValue v) { JSValue s = JS_ToString (ctx, v); if (JS_IsException (s)) return NULL; - b = pretext_concat_value_free (ctx, b, s); + b = pretext_concat_value (ctx, b, s); return b; } @@ -21239,7 +21241,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (boundary < len) { JSValue ch = js_sub_string (ctx, sp, boundary, boundary + 1); if (JS_IsException (ch)) goto fail_str_target; - b = pretext_concat_value_free (ctx, b, ch); + b = pretext_concat_value (ctx, b, ch); if (!b) goto fail_str_target; } } @@ -21264,7 +21266,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (found > pos) { JSValue sub = js_sub_string (ctx, sp, pos, found); if (JS_IsException (sub)) goto fail_str_target; - b = pretext_concat_value_free (ctx, b, sub); + b = pretext_concat_value (ctx, b, sub); if (!b) goto fail_str_target; } @@ -21288,7 +21290,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (pos < len) { JSValue sub = js_sub_string (ctx, sp, pos, len); if (JS_IsException (sub)) goto fail_str_target; - b = pretext_concat_value_free (ctx, b, sub); + b = pretext_concat_value (ctx, b, sub); if (!b) goto fail_str_target; } @@ -21360,7 +21362,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (found > pos) { JSValue prefix = js_sub_string (ctx, sp, pos, found); if (JS_IsException (prefix)) goto fail_rx; - b = pretext_concat_value_free (ctx, b, prefix); + b = pretext_concat_value (ctx, b, prefix); if (!b) goto fail_rx; } @@ -21387,7 +21389,7 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (pos < len) { JSValue tail = js_sub_string (ctx, sp, pos, len); if (JS_IsException (tail)) goto fail_rx; - b = pretext_concat_value_free (ctx, b, tail); + b = pretext_concat_value (ctx, b, tail); if (!b) goto fail_rx; } diff --git a/source/suite.c b/source/suite.c new file mode 100644 index 00000000..d07dccbe --- /dev/null +++ b/source/suite.c @@ -0,0 +1,578 @@ +/* + * C-level test suite for cell runtime + * Tests core object representation using C API exclusively + * Bypasses parser/bytecode to verify low-level operations + */ + +#include "quickjs.h" +#include +#include +#include + +static int tests_passed = 0; +static int tests_failed = 0; + +#define TEST(name) static int test_##name(JSContext *ctx) +#define RUN_TEST(name) do { \ + printf(" %-50s ", #name); \ + if (test_##name(ctx)) { \ + printf("PASS\n"); \ + tests_passed++; \ + } else { \ + printf("FAIL\n"); \ + tests_failed++; \ + } \ +} while(0) + +#define ASSERT(cond) do { if (!(cond)) { printf("[line %d] ", __LINE__); return 0; } } while(0) +#define ASSERT_INT(v, expected) do { \ + if (!JS_IsInt(v)) { printf("[line %d: not int] ", __LINE__); return 0; } \ + if (JS_VALUE_GET_INT(v) != (expected)) { printf("[line %d: %d != %d] ", __LINE__, JS_VALUE_GET_INT(v), expected); return 0; } \ +} while(0) + +/* ============================================================================ + NUMBER TESTS + ============================================================================ */ + +TEST(int_creation) { + JSValue v = JS_NewInt32(ctx, 42); + ASSERT(JS_IsInt(v)); + ASSERT(JS_VALUE_GET_INT(v) == 42); + return 1; +} + +TEST(int_zero) { + JSValue v = JS_NewInt32(ctx, 0); + ASSERT(JS_IsInt(v)); + ASSERT(JS_VALUE_GET_INT(v) == 0); + return 1; +} + +TEST(int_negative) { + JSValue v = JS_NewInt32(ctx, -100); + ASSERT(JS_IsInt(v)); + ASSERT(JS_VALUE_GET_INT(v) == -100); + return 1; +} + +TEST(int_max) { + JSValue v = JS_NewInt32(ctx, INT32_MAX); + ASSERT(JS_IsInt(v)); + ASSERT(JS_VALUE_GET_INT(v) == INT32_MAX); + return 1; +} + +TEST(int_min) { + JSValue v = JS_NewInt32(ctx, INT32_MIN); + ASSERT(JS_IsInt(v)); + ASSERT(JS_VALUE_GET_INT(v) == INT32_MIN); + return 1; +} + +TEST(float_creation) { + JSValue v = JS_NewFloat64(ctx, 3.14159); + ASSERT(JS_IsNumber(v)); + double d; + JS_ToFloat64(ctx, &d, v); + ASSERT(fabs(d - 3.14159) < 0.00001); + return 1; +} + +TEST(float_zero_becomes_int) { + JSValue v = JS_NewFloat64(ctx, 0.0); + ASSERT(JS_IsInt(v)); + ASSERT(JS_VALUE_GET_INT(v) == 0); + return 1; +} + +TEST(float_whole_becomes_int) { + JSValue v = JS_NewFloat64(ctx, 42.0); + ASSERT(JS_IsInt(v)); + ASSERT(JS_VALUE_GET_INT(v) == 42); + return 1; +} + +TEST(float_negative) { + JSValue v = JS_NewFloat64(ctx, -2.5); + ASSERT(JS_IsNumber(v)); + double d; + JS_ToFloat64(ctx, &d, v); + ASSERT(fabs(d - (-2.5)) < 0.00001); + return 1; +} + +/* ============================================================================ + BOOLEAN TESTS + ============================================================================ */ + +TEST(bool_true) { + JSValue v = JS_TRUE; + ASSERT(JS_IsBool(v)); + ASSERT(JS_VALUE_GET_BOOL(v) == 1); + return 1; +} + +TEST(bool_false) { + JSValue v = JS_FALSE; + ASSERT(JS_IsBool(v)); + ASSERT(JS_VALUE_GET_BOOL(v) == 0); + return 1; +} + +TEST(bool_new_true) { + JSValue v = JS_NewBool(ctx, 1); + ASSERT(JS_IsBool(v)); + ASSERT(JS_VALUE_GET_BOOL(v) == 1); + return 1; +} + +TEST(bool_new_false) { + JSValue v = JS_NewBool(ctx, 0); + ASSERT(JS_IsBool(v)); + ASSERT(JS_VALUE_GET_BOOL(v) == 0); + return 1; +} + +/* ============================================================================ + NULL TESTS + ============================================================================ */ + +TEST(null_value) { + JSValue v = JS_NULL; + ASSERT(JS_IsNull(v)); + ASSERT(!JS_IsBool(v)); + ASSERT(!JS_IsNumber(v)); + return 1; +} + +/* ============================================================================ + STRING TESTS - IMMEDIATE ASCII + ============================================================================ */ + +TEST(string_immediate_short) { + JSValue v = JS_NewString(ctx, "hello"); + ASSERT(JS_IsText(v)); + ASSERT(MIST_IsImmediateASCII(v)); + ASSERT(MIST_GetImmediateASCIILen(v) == 5); + ASSERT(MIST_GetImmediateASCIIChar(v, 0) == 'h'); + ASSERT(MIST_GetImmediateASCIIChar(v, 4) == 'o'); + return 1; +} + +TEST(string_immediate_empty) { + JSValue v = JS_NewString(ctx, ""); + ASSERT(JS_IsText(v)); + ASSERT(MIST_IsImmediateASCII(v)); + ASSERT(MIST_GetImmediateASCIILen(v) == 0); + return 1; +} + +TEST(string_immediate_max) { + JSValue v = JS_NewString(ctx, "1234567"); /* 7 chars = max immediate */ + ASSERT(JS_IsText(v)); + ASSERT(MIST_IsImmediateASCII(v)); + ASSERT(MIST_GetImmediateASCIILen(v) == 7); + return 1; +} + +TEST(string_heap_long) { + JSValue v = JS_NewString(ctx, "12345678"); /* 8 chars = heap allocated */ + ASSERT(JS_IsText(v)); + ASSERT(!MIST_IsImmediateASCII(v)); + return 1; +} + +TEST(string_to_cstring) { + JSValue v = JS_NewString(ctx, "test"); + const char *str = JS_ToCString(ctx, v); + ASSERT(str != NULL); + ASSERT(strcmp(str, "test") == 0); + JS_FreeCString(ctx, str); + return 1; +} + +TEST(string_heap_to_cstring) { + JSValue v = JS_NewString(ctx, "this is a longer string"); + const char *str = JS_ToCString(ctx, v); + ASSERT(str != NULL); + ASSERT(strcmp(str, "this is a longer string") == 0); + JS_FreeCString(ctx, str); + return 1; +} + +/* ============================================================================ + OBJECT/RECORD TESTS + ============================================================================ */ + +TEST(object_create) { + JSValue obj = JS_NewObject(ctx); + ASSERT(JS_IsObject(obj)); + ASSERT(JS_IsRecord(obj)); + return 1; +} + +TEST(object_set_get_property) { + JSValue obj = JS_NewObject(ctx); + int r = JS_SetPropertyStr(ctx, obj, "foo", JS_NewInt32(ctx, 42)); + ASSERT(r >= 0); + JSValue val = JS_GetPropertyStr(ctx, obj, "foo"); + ASSERT_INT(val, 42); + return 1; +} + +TEST(object_multiple_properties) { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, obj, "a", JS_NewInt32(ctx, 1)); + JS_SetPropertyStr(ctx, obj, "b", JS_NewInt32(ctx, 2)); + JS_SetPropertyStr(ctx, obj, "c", JS_NewInt32(ctx, 3)); + + JSValue a = JS_GetPropertyStr(ctx, obj, "a"); + JSValue b = JS_GetPropertyStr(ctx, obj, "b"); + JSValue c = JS_GetPropertyStr(ctx, obj, "c"); + + ASSERT_INT(a, 1); + ASSERT_INT(b, 2); + ASSERT_INT(c, 3); + return 1; +} + +TEST(object_overwrite_property) { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, 10)); + JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, 20)); + + JSValue x = JS_GetPropertyStr(ctx, obj, "x"); + ASSERT_INT(x, 20); + return 1; +} + +TEST(object_missing_property_is_null) { + JSValue obj = JS_NewObject(ctx); + JSValue val = JS_GetPropertyStr(ctx, obj, "nonexistent"); + ASSERT(JS_IsNull(val)); + return 1; +} + +TEST(object_string_property) { + JSValue obj = JS_NewObject(ctx); + JSValue str = JS_NewString(ctx, "hello"); + JS_SetPropertyStr(ctx, obj, "msg", str); + + JSValue val = JS_GetPropertyStr(ctx, obj, "msg"); + ASSERT(JS_IsText(val)); + const char *s = JS_ToCString(ctx, val); + ASSERT(strcmp(s, "hello") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(object_nested) { + JSValue outer = JS_NewObject(ctx); + JSValue inner = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, inner, "value", JS_NewInt32(ctx, 99)); + JS_SetPropertyStr(ctx, outer, "inner", inner); + + JSValue gotInner = JS_GetPropertyStr(ctx, outer, "inner"); + ASSERT(JS_IsRecord(gotInner)); + JSValue val = JS_GetPropertyStr(ctx, gotInner, "value"); + ASSERT_INT(val, 99); + return 1; +} + +TEST(object_many_properties_resize) { + JSValue obj = JS_NewObject(ctx); + /* Add many properties to trigger resize */ + for (int i = 0; i < 100; i++) { + char key[16]; + snprintf(key, sizeof(key), "prop%d", i); + JS_SetPropertyStr(ctx, obj, key, JS_NewInt32(ctx, i * 10)); + } + + /* Verify all properties */ + for (int i = 0; i < 100; i++) { + char key[16]; + snprintf(key, sizeof(key), "prop%d", i); + JSValue val = JS_GetPropertyStr(ctx, obj, key); + ASSERT_INT(val, i * 10); + } + return 1; +} + +/* ============================================================================ + ARRAY TESTS + ============================================================================ */ + +TEST(array_create) { + JSValue arr = JS_NewArray(ctx); + ASSERT(JS_IsObject(arr)); + ASSERT(JS_IsArray(arr)); + return 1; +} + +TEST(array_push_and_length) { + JSValue arr = JS_NewArray(ctx); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 10)); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 20)); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 30)); + + int64_t len; + JS_GetLength(ctx, arr, &len); + ASSERT(len == 3); + return 1; +} + +TEST(array_get_by_index) { + JSValue arr = JS_NewArray(ctx); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 100)); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 200)); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 300)); + + JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0); + JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1); + JSValue v2 = JS_GetPropertyUint32(ctx, arr, 2); + + ASSERT_INT(v0, 100); + ASSERT_INT(v1, 200); + ASSERT_INT(v2, 300); + return 1; +} + +TEST(array_set_by_index) { + JSValue arr = JS_NewArray(ctx); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 0)); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 0)); + + JS_SetPropertyUint32(ctx, arr, 0, JS_NewInt32(ctx, 55)); + JS_SetPropertyUint32(ctx, arr, 1, JS_NewInt32(ctx, 66)); + + JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0); + JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1); + + ASSERT_INT(v0, 55); + ASSERT_INT(v1, 66); + return 1; +} + +TEST(array_pop) { + JSValue arr = JS_NewArray(ctx); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 1)); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 2)); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 3)); + + JSValue popped = JS_ArrayPop(ctx, arr); + ASSERT_INT(popped, 3); + + int64_t len; + JS_GetLength(ctx, arr, &len); + ASSERT(len == 2); + return 1; +} + +TEST(array_out_of_bounds_is_null) { + JSValue arr = JS_NewArray(ctx); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 1)); + + JSValue val = JS_GetPropertyUint32(ctx, arr, 999); + ASSERT(JS_IsNull(val)); + return 1; +} + +TEST(array_mixed_types) { + JSValue arr = JS_NewArray(ctx); + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, 42)); + JS_ArrayPush(ctx, arr, JS_NewString(ctx, "hello")); + JS_ArrayPush(ctx, arr, JS_TRUE); + JS_ArrayPush(ctx, arr, JS_NULL); + + JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0); + JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1); + JSValue v2 = JS_GetPropertyUint32(ctx, arr, 2); + JSValue v3 = JS_GetPropertyUint32(ctx, arr, 3); + + ASSERT(JS_IsInt(v0)); + ASSERT(JS_IsText(v1)); + ASSERT(JS_IsBool(v2)); + ASSERT(JS_IsNull(v3)); + return 1; +} + +TEST(array_many_elements_resize) { + JSValue arr = JS_NewArray(ctx); + for (int i = 0; i < 1000; i++) { + JS_ArrayPush(ctx, arr, JS_NewInt32(ctx, i)); + } + + int64_t len; + JS_GetLength(ctx, arr, &len); + ASSERT(len == 1000); + + /* Verify some values */ + JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0); + JSValue v500 = JS_GetPropertyUint32(ctx, arr, 500); + JSValue v999 = JS_GetPropertyUint32(ctx, arr, 999); + + ASSERT_INT(v0, 0); + ASSERT_INT(v500, 500); + ASSERT_INT(v999, 999); + return 1; +} + +/* ============================================================================ + TYPE CHECK TESTS + ============================================================================ */ + +TEST(type_checks) { + JSValue num = JS_NewInt32(ctx, 42); + JSValue flt = JS_NewFloat64(ctx, 3.14); + JSValue str = JS_NewString(ctx, "test"); + JSValue obj = JS_NewObject(ctx); + JSValue arr = JS_NewArray(ctx); + JSValue boo = JS_TRUE; + JSValue nul = JS_NULL; + + ASSERT(JS_IsNumber(num)); + ASSERT(JS_IsNumber(flt)); + ASSERT(JS_IsText(str)); + ASSERT(JS_IsRecord(obj)); + ASSERT(JS_IsArray(arr)); + ASSERT(JS_IsBool(boo)); + ASSERT(JS_IsNull(nul)); + + ASSERT(!JS_IsText(num)); + ASSERT(!JS_IsNumber(str)); + ASSERT(!JS_IsArray(obj)); + ASSERT(!JS_IsRecord(arr)); + + return 1; +} + +/* ============================================================================ + EQUALITY TESTS + ============================================================================ */ + +TEST(strict_eq_ints) { + JSValue a = JS_NewInt32(ctx, 42); + JSValue b = JS_NewInt32(ctx, 42); + JSValue c = JS_NewInt32(ctx, 43); + + ASSERT(JS_StrictEq(ctx, a, b)); + ASSERT(!JS_StrictEq(ctx, a, c)); + return 1; +} + +TEST(strict_eq_strings) { + JSValue a = JS_NewString(ctx, "hello"); + JSValue b = JS_NewString(ctx, "hello"); + JSValue c = JS_NewString(ctx, "world"); + + ASSERT(JS_StrictEq(ctx, a, b)); + ASSERT(!JS_StrictEq(ctx, a, c)); + return 1; +} + +TEST(strict_eq_null) { + JSValue a = JS_NULL; + JSValue b = JS_NULL; + JSValue c = JS_NewInt32(ctx, 0); + + ASSERT(JS_StrictEq(ctx, a, b)); + ASSERT(!JS_StrictEq(ctx, a, c)); + return 1; +} + +TEST(strict_eq_bool) { + ASSERT(JS_StrictEq(ctx, JS_TRUE, JS_TRUE)); + ASSERT(JS_StrictEq(ctx, JS_FALSE, JS_FALSE)); + ASSERT(!JS_StrictEq(ctx, JS_TRUE, JS_FALSE)); + return 1; +} + +/* ============================================================================ + GLOBAL OBJECT TEST + ============================================================================ */ + +TEST(global_object) { + JSValue global = JS_GetGlobalObject(ctx); + ASSERT(JS_IsRecord(global)); + + /* Set something on global */ + JS_SetPropertyStr(ctx, global, "testGlobal", JS_NewInt32(ctx, 777)); + JSValue val = JS_GetPropertyStr(ctx, global, "testGlobal"); + ASSERT_INT(val, 777); + return 1; +} + +/* ============================================================================ + MAIN TEST RUNNER + ============================================================================ */ + +int run_c_test_suite(JSContext *ctx) +{ + printf("\n=== Cell Runtime C Test Suite ===\n\n"); + + printf("Numbers:\n"); + RUN_TEST(int_creation); + RUN_TEST(int_zero); + RUN_TEST(int_negative); + RUN_TEST(int_max); + RUN_TEST(int_min); + RUN_TEST(float_creation); + RUN_TEST(float_zero_becomes_int); + RUN_TEST(float_whole_becomes_int); + RUN_TEST(float_negative); + + printf("\nBooleans:\n"); + RUN_TEST(bool_true); + RUN_TEST(bool_false); + RUN_TEST(bool_new_true); + RUN_TEST(bool_new_false); + + printf("\nNull:\n"); + RUN_TEST(null_value); + + printf("\nStrings:\n"); + RUN_TEST(string_immediate_short); + RUN_TEST(string_immediate_empty); + RUN_TEST(string_immediate_max); + RUN_TEST(string_heap_long); + RUN_TEST(string_to_cstring); + RUN_TEST(string_heap_to_cstring); + + printf("\nObjects/Records:\n"); + RUN_TEST(object_create); + RUN_TEST(object_set_get_property); + RUN_TEST(object_multiple_properties); + RUN_TEST(object_overwrite_property); + RUN_TEST(object_missing_property_is_null); + RUN_TEST(object_string_property); + RUN_TEST(object_nested); + RUN_TEST(object_many_properties_resize); + + printf("\nArrays:\n"); + RUN_TEST(array_create); + RUN_TEST(array_push_and_length); + RUN_TEST(array_get_by_index); + RUN_TEST(array_set_by_index); + RUN_TEST(array_pop); + RUN_TEST(array_out_of_bounds_is_null); + RUN_TEST(array_mixed_types); + RUN_TEST(array_many_elements_resize); + + printf("\nType Checks:\n"); + RUN_TEST(type_checks); + + printf("\nEquality:\n"); + RUN_TEST(strict_eq_ints); + RUN_TEST(strict_eq_strings); + RUN_TEST(strict_eq_null); + RUN_TEST(strict_eq_bool); + + printf("\nGlobal Object:\n"); + RUN_TEST(global_object); + + printf("\n=================================\n"); + printf("Results: %d passed, %d failed\n", tests_passed, tests_failed); + printf("=================================\n\n"); + + return tests_failed == 0 ? 0 : 1; +}