From bdf0461e1fafc44fde01387abe42ff3c1e9cceb5 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 2 Feb 2026 21:26:46 -0600 Subject: [PATCH] extensive C testing --- source/quickjs.c | 471 ++++++++++++++++++++- source/quickjs.h | 53 +++ source/suite.c | 1034 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 1546 insertions(+), 12 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index 429ce084..e8bde13e 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -1709,6 +1709,39 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_splat (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_modulo (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_neg (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_not (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_trim (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_codepoint (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_abs (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_sign (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_floor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_ceiling (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_round (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_trunc (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_whole (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_fraction (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_min (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_max (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); JSValue JS_ThrowOutOfMemory (JSContext *ctx); static JSVarRef *get_var_ref (JSContext *ctx, JSStackFrame *sf, int var_idx, BOOL is_arg); static JSValue JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx); @@ -3255,7 +3288,7 @@ JSValue JS_NewObject (JSContext *ctx) { /* Helper to check if a value is a bytecode function */ static BOOL js_is_bytecode_function (JSValue val) { - if (JS_VALUE_GET_TAG (val) != JS_TAG_FUNCTION) return FALSE; + if (!JS_IsFunction (val)) return FALSE; JSFunction *f = JS_VALUE_GET_FUNCTION (val); return f->kind == JS_FUNC_KIND_BYTECODE; } @@ -3263,7 +3296,7 @@ static BOOL js_is_bytecode_function (JSValue val) { /* return NULL without exception if not a function or no bytecode */ static JSFunctionBytecode *JS_GetFunctionBytecode (JSValue val) { JSFunction *f; - if (JS_VALUE_GET_TAG (val) != JS_TAG_FUNCTION) return NULL; + if (!JS_IsFunction (val)) return NULL; f = JS_VALUE_GET_FUNCTION (val); if (f->kind != JS_FUNC_KIND_BYTECODE) return NULL; return f->u.func.function_bytecode; @@ -3280,7 +3313,7 @@ static int js_method_set_properties (JSContext *ctx, JSValue func_obj, JSValue n (void)ctx; (void)flags; (void)home_obj; - if (JS_VALUE_GET_TAG (func_obj) != JS_TAG_FUNCTION) return -1; + if (!JS_IsFunction (func_obj)) return -1; JSFunction *f = JS_VALUE_GET_FUNCTION (func_obj); /* name is now JSValue text */ if (JS_IsText (name)) { f->name = name; } @@ -3735,7 +3768,7 @@ static void build_backtrace (JSContext *ctx, JSValue error_obj, const char *file dbuf_printf (&dbuf, " at %s", str1); JS_FreeCString (ctx, func_name_str); - if (JS_VALUE_GET_TAG (sf->cur_func) == JS_TAG_FUNCTION) { + if (JS_IsFunction (sf->cur_func)) { JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func); if (fn->kind == JS_FUNC_KIND_BYTECODE) { JSFunctionBytecode *b; @@ -4622,7 +4655,7 @@ int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop) { BOOL JS_IsCFunction (JSValue val, JSCFunction *func, int magic) { JSFunction *f; - if (JS_VALUE_GET_TAG (val) != JS_TAG_FUNCTION) return FALSE; + if (!JS_IsFunction (val)) return FALSE; f = JS_VALUE_GET_FUNCTION (val); if (f->kind == JS_FUNC_KIND_C) return (f->u.cfunc.c_function.generic == func @@ -6396,7 +6429,7 @@ static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue #endif if (js_poll_interrupts (caller_ctx)) return JS_EXCEPTION; - if (unlikely (JS_VALUE_GET_TAG (func_obj) != JS_TAG_FUNCTION)) { + if (unlikely (!JS_IsFunction (func_obj))) { not_a_function: return JS_ThrowTypeError (caller_ctx, "not a function"); } @@ -21722,6 +21755,178 @@ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, return arr; } +/* format(text, collection, transformer) - string interpolation + * Finds {name} or {name:format} patterns and substitutes from collection. + * Collection can be array (index by number) or record (index by key). + * Transformer can be function(value, format) or record of functions. + */ +static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + if (!JS_IsText (argv[0])) return JS_NULL; + + JSValue text_val = argv[0]; + JSValue collection = argv[1]; + JSValue transformer = argc > 2 ? argv[2] : JS_NULL; + + int is_array = JS_IsArray (collection); + int is_record = JS_IsRecord (collection); + if (!is_array && !is_record) return JS_NULL; + + JSText *sp = JS_VALUE_GET_STRING (text_val); + int len = (int)JSText_len (sp); + + JSText *result = pretext_init (ctx, len); + if (!result) return JS_EXCEPTION; + + int pos = 0; + while (pos < len) { + /* Find next '{' */ + int brace_start = -1; + for (int i = pos; i < len; i++) { + if (string_get (sp, i) == '{') { + brace_start = i; + break; + } + } + + if (brace_start < 0) { + /* No more braces, copy rest of string */ + JSValue tail = js_sub_string (ctx, sp, pos, len); + if (JS_IsException (tail)) return JS_EXCEPTION; + result = pretext_concat_value (ctx, result, tail); + break; + } + + /* Copy text before brace */ + if (brace_start > pos) { + JSValue prefix = js_sub_string (ctx, sp, pos, brace_start); + if (JS_IsException (prefix)) return JS_EXCEPTION; + result = pretext_concat_value (ctx, result, prefix); + if (!result) return JS_EXCEPTION; + } + + /* Find closing '}' */ + int brace_end = -1; + for (int i = brace_start + 1; i < len; i++) { + if (string_get (sp, i) == '}') { + brace_end = i; + break; + } + } + + if (brace_end < 0) { + /* No closing brace, copy '{' and continue */ + JSValue ch = js_sub_string (ctx, sp, brace_start, brace_start + 1); + if (JS_IsException (ch)) return JS_EXCEPTION; + result = pretext_concat_value (ctx, result, ch); + if (!result) return JS_EXCEPTION; + pos = brace_start + 1; + continue; + } + + /* Extract content between braces */ + JSValue middle = js_sub_string (ctx, sp, brace_start + 1, brace_end); + if (JS_IsException (middle)) return JS_EXCEPTION; + + /* Split on ':' to get name and format_spec */ + JSText *middle_str = JS_VALUE_GET_STRING (middle); + int middle_len = (int)JSText_len (middle_str); + + int colon_pos = -1; + for (int i = 0; i < middle_len; i++) { + if (string_get (middle_str, i) == ':') { + colon_pos = i; + break; + } + } + + JSValue name_val, format_spec; + if (colon_pos >= 0) { + name_val = js_sub_string (ctx, middle_str, 0, colon_pos); + format_spec = js_sub_string (ctx, middle_str, colon_pos + 1, middle_len); + } else { + name_val = middle; + format_spec = JS_KEY_empty; + } + + /* Get value from collection */ + JSValue coll_value = JS_NULL; + if (is_array) { + /* Parse name as integer index */ + int32_t idx = 0; + if (JS_ToInt32 (ctx, &idx, name_val) == 0 && idx >= 0) { + coll_value = JS_GetPropertyUint32 (ctx, collection, (uint32_t)idx); + } + } else { + /* Use name as key */ + coll_value = JS_GetProperty (ctx, collection, name_val); + } + + /* Try to get substitution */ + JSValue substitution = JS_NULL; + int made_substitution = 0; + + /* Try transformer first */ + if (!JS_IsNull (transformer)) { + if (JS_IsFunction (transformer)) { + /* transformer(value, format_spec) */ + JSValue args[2] = { coll_value, format_spec }; + JSValue result_val = JS_Call (ctx, transformer, JS_NULL, 2, args); + if (JS_IsText (result_val)) { + substitution = result_val; + made_substitution = 1; + } + } else if (JS_IsRecord (transformer)) { + /* transformer[format_spec](value) */ + JSValue func = JS_GetProperty (ctx, transformer, format_spec); + if (JS_IsFunction (func)) { + JSValue result_val = JS_Call (ctx, func, JS_NULL, 1, &coll_value); + if (JS_IsText (result_val)) { + substitution = result_val; + made_substitution = 1; + } + } + } + } + + /* If no transformer match and value is number, try number.text(format) */ + if (!made_substitution && JS_IsNumber (coll_value) && !JS_IsNull (format_spec)) { + JSValue text_method = JS_GetPropertyStr (ctx, coll_value, "text"); + if (JS_IsFunction (text_method)) { + JSValue result_val = JS_Call (ctx, text_method, coll_value, 1, &format_spec); + if (JS_IsText (result_val)) { + substitution = result_val; + made_substitution = 1; + } + } + } + + /* If still no substitution but we have a value, convert to text */ + if (!made_substitution && !JS_IsNull (coll_value)) { + JSValue text_val = JS_ToString (ctx, coll_value); + if (JS_IsText (text_val)) { + substitution = text_val; + made_substitution = 1; + } + } + + if (made_substitution) { + result = pretext_concat_value (ctx, result, substitution); + if (!result) return JS_EXCEPTION; + } else { + /* No substitution, keep original {name} or {name:format} */ + JSValue orig = js_sub_string (ctx, sp, brace_start, brace_end + 1); + if (JS_IsException (orig)) return JS_EXCEPTION; + result = pretext_concat_value (ctx, result, orig); + if (!result) return JS_EXCEPTION; + } + + pos = brace_end + 1; + } + + return pretext_end (ctx, result); +} + /* ---------------------------------------------------------------------------- * array function and sub-functions * ---------------------------------------------------------------------------- @@ -23542,6 +23747,258 @@ JSValue JS_ArrayReduce (JSContext *ctx, JSValue arr, JSValue fn, JSValue initial return js_cell_array_reduce (ctx, JS_NULL, argc, argv); } +/* ============================================================ + C API Wrappers for Cell Intrinsic Functions + ============================================================ */ + +/* C API: stone(val) - make value immutable */ +JSValue JS_CellStone (JSContext *ctx, JSValue val) { + return js_cell_stone (ctx, JS_NULL, 1, &val); +} + +/* C API: length(val) - get length of array/text/blob */ +JSValue JS_CellLength (JSContext *ctx, JSValue val) { + return js_cell_length (ctx, JS_NULL, 1, &val); +} + +/* C API: reverse(val) - reverse array or text */ +JSValue JS_CellReverse (JSContext *ctx, JSValue val) { + return js_cell_reverse (ctx, JS_NULL, 1, &val); +} + +/* C API: proto(obj) - get prototype */ +JSValue JS_CellProto (JSContext *ctx, JSValue obj) { + return js_cell_proto (ctx, JS_NULL, 1, &obj); +} + +/* C API: splat(val) - convert to array */ +JSValue JS_CellSplat (JSContext *ctx, JSValue val) { + return js_cell_splat (ctx, JS_NULL, 1, &val); +} + +/* C API: meme(obj, deep) - clone object */ +JSValue JS_CellMeme (JSContext *ctx, JSValue obj, JSValue deep) { + JSValue argv[2] = { obj, deep }; + int argc = JS_IsNull (deep) ? 1 : 2; + return js_cell_meme (ctx, JS_NULL, argc, argv); +} + +/* C API: apply(fn, args) - apply function to array of args */ +JSValue JS_CellApply (JSContext *ctx, JSValue fn, JSValue args) { + JSValue argv[2] = { fn, args }; + return js_cell_fn_apply (ctx, JS_NULL, 2, argv); +} + +/* C API: call(fn, this, args...) - call function */ +JSValue JS_CellCall (JSContext *ctx, JSValue fn, JSValue this_val, JSValue args) { + JSValue argv[3] = { fn, this_val, args }; + int argc = JS_IsNull (args) ? 2 : 3; + return js_cell_call (ctx, JS_NULL, argc, argv); +} + +/* C API: modulo(a, b) - modulo operation */ +JSValue JS_CellModulo (JSContext *ctx, JSValue a, JSValue b) { + JSValue argv[2] = { a, b }; + return js_cell_modulo (ctx, JS_NULL, 2, argv); +} + +/* C API: neg(val) - negate number */ +JSValue JS_CellNeg (JSContext *ctx, JSValue val) { + return js_cell_neg (ctx, JS_NULL, 1, &val); +} + +/* C API: not(val) - logical not */ +JSValue JS_CellNot (JSContext *ctx, JSValue val) { + return js_cell_not (ctx, JS_NULL, 1, &val); +} + +/* Text functions */ + +/* C API: text(val) - convert to text */ +JSValue JS_CellText (JSContext *ctx, JSValue val) { + return js_cell_text (ctx, JS_NULL, 1, &val); +} + +/* C API: lower(text) - convert to lowercase */ +JSValue JS_CellLower (JSContext *ctx, JSValue text) { + return js_cell_text_lower (ctx, JS_NULL, 1, &text); +} + +/* C API: upper(text) - convert to uppercase */ +JSValue JS_CellUpper (JSContext *ctx, JSValue text) { + return js_cell_text_upper (ctx, JS_NULL, 1, &text); +} + +/* C API: trim(text, chars) - trim whitespace or specified chars */ +JSValue JS_CellTrim (JSContext *ctx, JSValue text, JSValue chars) { + JSValue argv[2] = { text, chars }; + int argc = JS_IsNull (chars) ? 1 : 2; + return js_cell_text_trim (ctx, JS_NULL, argc, argv); +} + +/* C API: codepoint(text, idx) - get codepoint at index */ +JSValue JS_CellCodepoint (JSContext *ctx, JSValue text, JSValue idx) { + JSValue argv[2] = { text, idx }; + int argc = JS_IsNull (idx) ? 1 : 2; + return js_cell_text_codepoint (ctx, JS_NULL, argc, argv); +} + +/* C API: replace(text, pattern, replacement) - replace in text */ +JSValue JS_CellReplace (JSContext *ctx, JSValue text, JSValue pattern, JSValue replacement) { + JSValue argv[3] = { text, pattern, replacement }; + return js_cell_text_replace (ctx, JS_NULL, 3, argv); +} + +/* C API: search(text, pattern, from) - search in text */ +JSValue JS_CellSearch (JSContext *ctx, JSValue text, JSValue pattern, JSValue from) { + JSValue argv[3] = { text, pattern, from }; + int argc = JS_IsNull (from) ? 2 : 3; + return js_cell_text_search (ctx, JS_NULL, argc, argv); +} + +/* C API: extract(text, from, to) - extract substring */ +JSValue JS_CellExtract (JSContext *ctx, JSValue text, JSValue from, JSValue to) { + JSValue argv[3] = { text, from, to }; + int argc = 3; + if (JS_IsNull (to)) argc = 2; + if (JS_IsNull (from)) argc = 1; + return js_cell_text_extract (ctx, JS_NULL, argc, argv); +} + +/* C API: character(codepoint) - create single character text */ +JSValue JS_CellCharacter (JSContext *ctx, JSValue codepoint) { + return js_cell_character (ctx, JS_NULL, 1, &codepoint); +} + +/* Number functions */ + +/* C API: number(val) - convert to number */ +JSValue JS_CellNumber (JSContext *ctx, JSValue val) { + return js_cell_number (ctx, JS_NULL, 1, &val); +} + +/* C API: abs(num) - absolute value */ +JSValue JS_CellAbs (JSContext *ctx, JSValue num) { + return js_cell_number_abs (ctx, JS_NULL, 1, &num); +} + +/* C API: sign(num) - sign of number (-1, 0, 1) */ +JSValue JS_CellSign (JSContext *ctx, JSValue num) { + return js_cell_number_sign (ctx, JS_NULL, 1, &num); +} + +/* C API: floor(num) - floor */ +JSValue JS_CellFloor (JSContext *ctx, JSValue num) { + return js_cell_number_floor (ctx, JS_NULL, 1, &num); +} + +/* C API: ceiling(num) - ceiling */ +JSValue JS_CellCeiling (JSContext *ctx, JSValue num) { + return js_cell_number_ceiling (ctx, JS_NULL, 1, &num); +} + +/* C API: round(num) - round to nearest integer */ +JSValue JS_CellRound (JSContext *ctx, JSValue num) { + return js_cell_number_round (ctx, JS_NULL, 1, &num); +} + +/* C API: trunc(num) - truncate towards zero */ +JSValue JS_CellTrunc (JSContext *ctx, JSValue num) { + return js_cell_number_trunc (ctx, JS_NULL, 1, &num); +} + +/* C API: whole(num) - integer part */ +JSValue JS_CellWhole (JSContext *ctx, JSValue num) { + return js_cell_number_whole (ctx, JS_NULL, 1, &num); +} + +/* C API: fraction(num) - fractional part */ +JSValue JS_CellFraction (JSContext *ctx, JSValue num) { + return js_cell_number_fraction (ctx, JS_NULL, 1, &num); +} + +/* C API: min(a, b) - minimum of two numbers */ +JSValue JS_CellMin (JSContext *ctx, JSValue a, JSValue b) { + JSValue argv[2] = { a, b }; + return js_cell_number_min (ctx, JS_NULL, 2, argv); +} + +/* C API: max(a, b) - maximum of two numbers */ +JSValue JS_CellMax (JSContext *ctx, JSValue a, JSValue b) { + JSValue argv[2] = { a, b }; + return js_cell_number_max (ctx, JS_NULL, 2, argv); +} + +/* C API: remainder(a, b) - remainder after division */ +JSValue JS_CellRemainder (JSContext *ctx, JSValue a, JSValue b) { + JSValue argv[2] = { a, b }; + return js_cell_number_remainder (ctx, JS_NULL, 2, argv); +} + +/* Object functions */ + +/* C API: object(proto, props) - create object */ +JSValue JS_CellObject (JSContext *ctx, JSValue proto, JSValue props) { + JSValue argv[2] = { proto, props }; + int argc = JS_IsNull (props) ? 1 : 2; + if (JS_IsNull (proto)) argc = 0; + return js_cell_object (ctx, JS_NULL, argc, argv); +} + +/* C API: format(text, collection, transformer) - string interpolation */ +JSValue JS_CellFormat (JSContext *ctx, JSValue text, JSValue collection, JSValue transformer) { + JSValue argv[3] = { text, collection, transformer }; + int argc = JS_IsNull (transformer) ? 2 : 3; + return js_cell_text_format (ctx, JS_NULL, argc, argv); +} + +/* ============================================================ + Helper Functions for C API + ============================================================ */ + +/* Create an array from a list of JSValues */ +JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values) { + JSValue arr = JS_NewArray (ctx); + if (JS_IsException (arr)) return arr; + for (int i = 0; i < count; i++) { + if (JS_ArrayPush (ctx, &arr, values[i]) < 0) { + return JS_EXCEPTION; + } + } + return arr; +} + +/* Print a JSValue text to stdout */ +void JS_PrintText (JSContext *ctx, JSValue val) { + if (!JS_IsText (val)) { + /* Try to convert to string first */ + val = JS_ToString (ctx, val); + if (JS_IsException (val) || !JS_IsText (val)) { + printf ("[non-text value]"); + return; + } + } + const char *str = JS_ToCString (ctx, val); + if (str) { + printf ("%s", str); + JS_FreeCString (ctx, str); + } +} + +/* Print a JSValue text to stdout with newline */ +void JS_PrintTextLn (JSContext *ctx, JSValue val) { + JS_PrintText (ctx, val); + printf ("\n"); +} + +/* Format and print - convenience function */ +void JS_PrintFormatted (JSContext *ctx, const char *fmt, int count, JSValue *values) { + JSValue fmt_str = JS_NewString (ctx, fmt); + JSValue arr = JS_NewArrayFrom (ctx, count, values); + JSValue result = JS_CellFormat (ctx, fmt_str, arr, JS_NULL); + JS_PrintText (ctx, result); +} + static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; @@ -24079,6 +24536,8 @@ void JS_AddIntrinsicBaseObjects (JSContext *ctx) { ctx, ctx->global_obj, "search", JS_NewCFunction (ctx, js_cell_text_search, "search", 3)); JS_SetPropertyStr ( ctx, ctx->global_obj, "extract", JS_NewCFunction (ctx, js_cell_text_extract, "extract", 4)); + JS_SetPropertyStr ( + ctx, ctx->global_obj, "format", JS_NewCFunction (ctx, js_cell_text_format, "format", 3)); JS_SetPropertyStr ( ctx, ctx->global_obj, "reduce", JS_NewCFunction (ctx, js_cell_array_reduce, "reduce", 4)); JS_SetPropertyStr (ctx, ctx->global_obj, "arrfor", JS_NewCFunction (ctx, js_cell_array_for, "for", 4)); diff --git a/source/quickjs.h b/source/quickjs.h index fe197e95..e99ed1c4 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -653,6 +653,59 @@ JSValue JS_ArraySort (JSContext *ctx, JSValue arr, JSValue selector); JSValue JS_ArrayFind (JSContext *ctx, JSValue arr, JSValue target_or_fn, JSValue reverse, JSValue from); JSValue JS_ArrFor (JSContext *ctx, JSValue arr, JSValue fn, JSValue reverse, JSValue exit_val); JSValue JS_ArrayReduce (JSContext *ctx, JSValue arr, JSValue fn, JSValue initial, JSValue reverse); + +/* Cell intrinsic functions - C API wrappers */ + +/* Core functions */ +JSValue JS_CellStone (JSContext *ctx, JSValue val); +JSValue JS_CellLength (JSContext *ctx, JSValue val); +JSValue JS_CellReverse (JSContext *ctx, JSValue val); +JSValue JS_CellProto (JSContext *ctx, JSValue obj); +JSValue JS_CellSplat (JSContext *ctx, JSValue val); +JSValue JS_CellMeme (JSContext *ctx, JSValue obj, JSValue deep); +JSValue JS_CellApply (JSContext *ctx, JSValue fn, JSValue args); +JSValue JS_CellCall (JSContext *ctx, JSValue fn, JSValue this_val, JSValue args); +JSValue JS_CellModulo (JSContext *ctx, JSValue a, JSValue b); +JSValue JS_CellNeg (JSContext *ctx, JSValue val); +JSValue JS_CellNot (JSContext *ctx, JSValue val); + +/* Text functions */ +JSValue JS_CellText (JSContext *ctx, JSValue val); +JSValue JS_CellLower (JSContext *ctx, JSValue text); +JSValue JS_CellUpper (JSContext *ctx, JSValue text); +JSValue JS_CellTrim (JSContext *ctx, JSValue text, JSValue chars); +JSValue JS_CellCodepoint (JSContext *ctx, JSValue text, JSValue idx); +JSValue JS_CellReplace (JSContext *ctx, JSValue text, JSValue pattern, JSValue replacement); +JSValue JS_CellSearch (JSContext *ctx, JSValue text, JSValue pattern, JSValue from); +JSValue JS_CellExtract (JSContext *ctx, JSValue text, JSValue from, JSValue to); +JSValue JS_CellCharacter (JSContext *ctx, JSValue codepoint); + +/* Number functions */ +JSValue JS_CellNumber (JSContext *ctx, JSValue val); +JSValue JS_CellAbs (JSContext *ctx, JSValue num); +JSValue JS_CellSign (JSContext *ctx, JSValue num); +JSValue JS_CellFloor (JSContext *ctx, JSValue num); +JSValue JS_CellCeiling (JSContext *ctx, JSValue num); +JSValue JS_CellRound (JSContext *ctx, JSValue num); +JSValue JS_CellTrunc (JSContext *ctx, JSValue num); +JSValue JS_CellWhole (JSContext *ctx, JSValue num); +JSValue JS_CellFraction (JSContext *ctx, JSValue num); +JSValue JS_CellMin (JSContext *ctx, JSValue a, JSValue b); +JSValue JS_CellMax (JSContext *ctx, JSValue a, JSValue b); +JSValue JS_CellRemainder (JSContext *ctx, JSValue a, JSValue b); + +/* Object functions */ +JSValue JS_CellObject (JSContext *ctx, JSValue proto, JSValue props); + +/* Format function */ +JSValue JS_CellFormat (JSContext *ctx, JSValue text, JSValue collection, JSValue transformer); + +/* Helper functions */ +JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values); +void JS_PrintText (JSContext *ctx, JSValue val); +void JS_PrintTextLn (JSContext *ctx, JSValue val); +void JS_PrintFormatted (JSContext *ctx, const char *fmt, int count, JSValue *values); + JSValue JS_GetProperty (JSContext *ctx, JSValue this_obj, JSValue prop); // For records diff --git a/source/suite.c b/source/suite.c index e51d2e48..90ca018c 100644 --- a/source/suite.c +++ b/source/suite.c @@ -12,6 +12,18 @@ static int tests_passed = 0; static int tests_failed = 0; +static const char *js_type_name(JSValue v) { + if (JS_IsNull(v)) return "null"; + if (JS_IsBool(v)) return "bool"; + if (JS_IsInt(v)) return "int"; + if (JS_IsNumber(v)) return "number"; + if (JS_IsText(v)) return "string"; + if (JS_IsArray(v)) return "array"; + if (JS_IsRecord(v)) return "object"; + if (JS_IsObject(v)) return "object"; + return "unknown"; +} + #define TEST(name) static int test_##name(JSContext *ctx) #define RUN_TEST(name) do { \ printf(" %-50s ", #name); \ @@ -24,10 +36,101 @@ static int tests_failed = 0; } \ } while(0) -#define ASSERT(cond) do { if (!(cond)) { printf("[line %d] ", __LINE__); return 0; } } while(0) +#define ASSERT(cond) do { \ + if (!(cond)) { printf("[line %d: ASSERT(%s) failed] ", __LINE__, #cond); return 0; } \ +} while(0) + +#define ASSERT_MSG(cond, msg) do { \ + if (!(cond)) { printf("[line %d: %s] ", __LINE__, msg); 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; } \ + if (!JS_IsInt(v)) { \ + printf("[line %d: expected int %d, got ", __LINE__, (expected)); \ + JS_PrintText(ctx, JS_ToString(ctx, v)); \ + printf(" (type: %s)] ", js_type_name(v)); \ + return 0; \ + } \ + int _actual = JS_VALUE_GET_INT(v); \ + if (_actual != (expected)) { \ + printf("[line %d: expected %d, got %d] ", __LINE__, (expected), _actual); \ + return 0; \ + } \ +} while(0) + +#define ASSERT_FLOAT(v, expected, tol) do { \ + double _d; \ + if (JS_ToFloat64(ctx, &_d, v) < 0) { \ + printf("[line %d: expected float ~%.6f, got non-number: ", __LINE__, (expected)); \ + JS_PrintText(ctx, JS_ToString(ctx, v)); \ + printf("] "); \ + return 0; \ + } \ + if (fabs(_d - (expected)) >= (tol)) { \ + printf("[line %d: expected ~%.6f (±%.6f), got %.6f] ", __LINE__, (expected), (tol), _d); \ + return 0; \ + } \ +} while(0) + +#define ASSERT_STR(v, expected) do { \ + if (!JS_IsText(v)) { \ + printf("[line %d: expected string \"%s\", got ", __LINE__, (expected)); \ + JS_PrintText(ctx, JS_ToString(ctx, v)); \ + printf(" (type: %s)] ", js_type_name(v)); \ + return 0; \ + } \ + const char *_s = JS_ToCString(ctx, v); \ + if (_s == NULL || strcmp(_s, (expected)) != 0) { \ + printf("[line %d: expected \"%s\", got \"%s\"] ", __LINE__, (expected), _s ? _s : "(null)"); \ + if (_s) JS_FreeCString(ctx, _s); \ + return 0; \ + } \ + JS_FreeCString(ctx, _s); \ +} while(0) + +#define ASSERT_TRUE(v) do { \ + if (!JS_IsBool(v) || !JS_VALUE_GET_BOOL(v)) { \ + printf("[line %d: expected true, got ", __LINE__); \ + JS_PrintText(ctx, JS_ToString(ctx, v)); \ + printf("] "); \ + return 0; \ + } \ +} while(0) + +#define ASSERT_FALSE(v) do { \ + if (!JS_IsBool(v) || JS_VALUE_GET_BOOL(v)) { \ + printf("[line %d: expected false, got ", __LINE__); \ + JS_PrintText(ctx, JS_ToString(ctx, v)); \ + printf("] "); \ + return 0; \ + } \ +} while(0) + +#define ASSERT_NULL(v) do { \ + if (!JS_IsNull(v)) { \ + printf("[line %d: expected null, got ", __LINE__); \ + JS_PrintText(ctx, JS_ToString(ctx, v)); \ + printf(" (type: %s)] ", js_type_name(v)); \ + return 0; \ + } \ +} while(0) + +#define ASSERT_TYPE(v, check_fn, type_name) do { \ + if (!check_fn(v)) { \ + printf("[line %d: expected %s, got ", __LINE__, type_name); \ + JS_PrintText(ctx, JS_ToString(ctx, v)); \ + printf(" (type: %s)] ", js_type_name(v)); \ + return 0; \ + } \ +} while(0) + +#define ASSERT_VALUE(cond, v) do { \ + if (!(cond)) { \ + printf("[line %d: ASSERT_VALUE(%s) failed, value: ", __LINE__, #cond); \ + JS_PrintText(ctx, JS_ToString(ctx, v)); \ + printf("] "); \ + return 0; \ + } \ } while(0) /* ============================================================================ @@ -517,7 +620,6 @@ static JSValue cfunc_add(JSContext *ctx, JSValue this_val, int argc, JSValue *ar /* Helper C function: check if value equals 30 */ static JSValue cfunc_eq30(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - printf("HERE\n"); if (argc < 1 || !JS_IsInt(argv[0])) return JS_FALSE; return JS_VALUE_GET_INT(argv[0]) == 30 ? JS_TRUE : JS_FALSE; } @@ -620,8 +722,7 @@ TEST(array_find_predicate) { /* Find by predicate function */ JSValue func = JS_NewCFunction(ctx, cfunc_eq30, "eq30", 1); JSValue idx = JS_ArrayFind(ctx, arr, func, JS_NULL, JS_NULL); - ASSERT(JS_IsInt(idx)); - ASSERT(JS_VALUE_GET_INT(idx) == 2); + ASSERT_INT(idx, 2); return 1; } @@ -745,6 +846,812 @@ TEST(global_object) { return 1; } +/* ============================================================================ + VALUE CONVERSION TESTS + ============================================================================ */ + +TEST(to_bool_true_values) { + ASSERT(JS_ToBool(ctx, JS_TRUE) == 1); + ASSERT(JS_ToBool(ctx, JS_NewInt32(ctx, 1)) == 1); + ASSERT(JS_ToBool(ctx, JS_NewInt32(ctx, -1)) == 1); + ASSERT(JS_ToBool(ctx, JS_NewString(ctx, "hello")) == 1); + return 1; +} + +TEST(to_bool_false_values) { + ASSERT(JS_ToBool(ctx, JS_FALSE) == 0); + ASSERT(JS_ToBool(ctx, JS_NewInt32(ctx, 0)) == 0); + ASSERT(JS_ToBool(ctx, JS_NULL) == 0); + ASSERT(JS_ToBool(ctx, JS_NewString(ctx, "")) == 0); + return 1; +} + +TEST(to_int32_from_int) { + int32_t res; + JSValue v = JS_NewInt32(ctx, 12345); + ASSERT(JS_ToInt32(ctx, &res, v) == 0); + ASSERT(res == 12345); + return 1; +} + +TEST(to_int32_from_float) { + int32_t res; + JSValue v = JS_NewFloat64(ctx, 3.7); + ASSERT(JS_ToInt32(ctx, &res, v) == 0); + ASSERT(res == 3); /* truncates toward zero */ + return 1; +} + +TEST(to_int32_negative_float) { + int32_t res; + JSValue v = JS_NewFloat64(ctx, -3.7); + ASSERT(JS_ToInt32(ctx, &res, v) == 0); + ASSERT(res == -3); + return 1; +} + +TEST(to_int64_large_value) { + int64_t res; + JSValue v = JS_NewInt64(ctx, 9007199254740992LL); /* 2^53 */ + ASSERT(JS_ToInt64(ctx, &res, v) == 0); + ASSERT(res == 9007199254740992LL); + return 1; +} + +TEST(to_float64_from_int) { + double res; + JSValue v = JS_NewInt32(ctx, 42); + ASSERT(JS_ToFloat64(ctx, &res, v) == 0); + ASSERT(fabs(res - 42.0) < 0.00001); + return 1; +} + +TEST(to_float64_from_float) { + double res; + JSValue v = JS_NewFloat64(ctx, 3.14159); + ASSERT(JS_ToFloat64(ctx, &res, v) == 0); + ASSERT(fabs(res - 3.14159) < 0.00001); + return 1; +} + +/* ============================================================================ + NEWINT64 / NEWUINT32 EDGE CASES + ============================================================================ */ + +TEST(new_int64_in_int32_range) { + JSValue v = JS_NewInt64(ctx, 1000); + ASSERT(JS_IsInt(v)); + ASSERT(JS_VALUE_GET_INT(v) == 1000); + return 1; +} + +TEST(new_int64_out_of_int32_range) { + JSValue v = JS_NewInt64(ctx, 3000000000LL); + ASSERT(JS_IsNumber(v)); + double d; + JS_ToFloat64(ctx, &d, v); + ASSERT(fabs(d - 3000000000.0) < 1.0); + return 1; +} + +TEST(new_uint32_in_int32_range) { + JSValue v = JS_NewUint32(ctx, 1000); + ASSERT(JS_IsInt(v)); + ASSERT(JS_VALUE_GET_INT(v) == 1000); + return 1; +} + +TEST(new_uint32_out_of_signed_range) { + JSValue v = JS_NewUint32(ctx, 0x80000000); + ASSERT(JS_IsNumber(v)); + double d; + JS_ToFloat64(ctx, &d, v); + ASSERT(fabs(d - 2147483648.0) < 1.0); + return 1; +} + +/* ============================================================================ + STRING CONVERSION TESTS + ============================================================================ */ + +TEST(to_string_from_int) { + JSValue v = JS_NewInt32(ctx, 42); + JSValue str = JS_ToString(ctx, v); + ASSERT(JS_IsText(str)); + const char *s = JS_ToCString(ctx, str); + ASSERT(strcmp(s, "42") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(to_string_from_float) { + JSValue v = JS_NewFloat64(ctx, 3.5); + JSValue str = JS_ToString(ctx, v); + ASSERT(JS_IsText(str)); + const char *s = JS_ToCString(ctx, str); + ASSERT(strcmp(s, "3.5") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(to_string_from_bool) { + JSValue str_true = JS_ToString(ctx, JS_TRUE); + JSValue str_false = JS_ToString(ctx, JS_FALSE); + const char *s1 = JS_ToCString(ctx, str_true); + const char *s2 = JS_ToCString(ctx, str_false); + ASSERT(strcmp(s1, "true") == 0); + ASSERT(strcmp(s2, "false") == 0); + JS_FreeCString(ctx, s1); + JS_FreeCString(ctx, s2); + return 1; +} + +TEST(to_string_from_null) { + JSValue str = JS_ToString(ctx, JS_NULL); + const char *s = JS_ToCString(ctx, str); + ASSERT(strcmp(s, "null") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(string_len_with_length) { + JSValue v = JS_NewStringLen(ctx, "hello\0world", 11); + ASSERT(JS_IsText(v)); + size_t len; + const char *s = JS_ToCStringLen(ctx, &len, v); + ASSERT(len == 11); + JS_FreeCString(ctx, s); + return 1; +} + +/* ============================================================================ + CELL TEXT FUNCTION TESTS + ============================================================================ */ + +TEST(cell_text_from_int) { + JSValue result = JS_CellText(ctx, JS_NewInt32(ctx, 123)); + ASSERT(JS_IsText(result)); + const char *s = JS_ToCString(ctx, result); + ASSERT(strcmp(s, "123") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(cell_lower) { + JSValue text = JS_NewString(ctx, "HELLO"); + JSValue result = JS_CellLower(ctx, text); + ASSERT(JS_IsText(result)); + const char *s = JS_ToCString(ctx, result); + ASSERT(strcmp(s, "hello") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(cell_upper) { + JSValue text = JS_NewString(ctx, "hello"); + JSValue result = JS_CellUpper(ctx, text); + ASSERT(JS_IsText(result)); + const char *s = JS_ToCString(ctx, result); + ASSERT(strcmp(s, "HELLO") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(cell_trim_whitespace) { + JSValue text = JS_NewString(ctx, " hello "); + JSValue result = JS_CellTrim(ctx, text, JS_NULL); + ASSERT(JS_IsText(result)); + const char *s = JS_ToCString(ctx, result); + ASSERT(strcmp(s, "hello") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(cell_codepoint) { + JSValue text = JS_NewString(ctx, "ABC"); + JSValue result = JS_CellCodepoint(ctx, text, JS_NewInt32(ctx, 0)); + ASSERT(JS_IsInt(result)); + ASSERT(JS_VALUE_GET_INT(result) == 65); /* 'A' */ + return 1; +} + +TEST(cell_character) { + JSValue result = JS_CellCharacter(ctx, JS_NewInt32(ctx, 65)); + ASSERT(JS_IsText(result)); + const char *s = JS_ToCString(ctx, result); + ASSERT(strcmp(s, "A") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(cell_search_found) { + JSValue text = JS_NewString(ctx, "hello world"); + JSValue pattern = JS_NewString(ctx, "world"); + JSValue result = JS_CellSearch(ctx, text, pattern, JS_NULL); + ASSERT(JS_IsInt(result)); + ASSERT(JS_VALUE_GET_INT(result) == 6); + return 1; +} + +TEST(cell_search_not_found) { + JSValue text = JS_NewString(ctx, "hello world"); + JSValue pattern = JS_NewString(ctx, "xyz"); + JSValue result = JS_CellSearch(ctx, text, pattern, JS_NULL); + ASSERT(JS_IsNull(result)); + return 1; +} + +TEST(cell_extract) { + JSValue text = JS_NewString(ctx, "hello world"); + JSValue result = JS_CellExtract(ctx, text, JS_NewInt32(ctx, 0), JS_NewInt32(ctx, 5)); + ASSERT(JS_IsText(result)); + const char *s = JS_ToCString(ctx, result); + ASSERT(strcmp(s, "hello") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(cell_replace) { + JSValue text = JS_NewString(ctx, "hello world"); + JSValue pattern = JS_NewString(ctx, "world"); + JSValue replacement = JS_NewString(ctx, "there"); + JSValue result = JS_CellReplace(ctx, text, pattern, replacement); + ASSERT(JS_IsText(result)); + const char *s = JS_ToCString(ctx, result); + ASSERT(strcmp(s, "hello there") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +/* ============================================================================ + CELL NUMBER FUNCTION TESTS + ============================================================================ */ + +TEST(cell_number_from_string) { + JSValue text = JS_NewString(ctx, "42"); + JSValue result = JS_CellNumber(ctx, text); + ASSERT(JS_IsInt(result)); + ASSERT(JS_VALUE_GET_INT(result) == 42); + return 1; +} + +TEST(cell_abs_positive) { + JSValue result = JS_CellAbs(ctx, JS_NewInt32(ctx, 42)); + ASSERT_INT(result, 42); + return 1; +} + +TEST(cell_abs_negative) { + JSValue result = JS_CellAbs(ctx, JS_NewInt32(ctx, -42)); + ASSERT_INT(result, 42); + return 1; +} + +TEST(cell_sign_positive) { + JSValue result = JS_CellSign(ctx, JS_NewInt32(ctx, 42)); + ASSERT_INT(result, 1); + return 1; +} + +TEST(cell_sign_negative) { + JSValue result = JS_CellSign(ctx, JS_NewInt32(ctx, -42)); + ASSERT_INT(result, -1); + return 1; +} + +TEST(cell_sign_zero) { + JSValue result = JS_CellSign(ctx, JS_NewInt32(ctx, 0)); + ASSERT_INT(result, 0); + return 1; +} + +TEST(cell_floor) { + JSValue result = JS_CellFloor(ctx, JS_NewFloat64(ctx, 3.7)); + ASSERT_INT(result, 3); + return 1; +} + +TEST(cell_floor_negative) { + JSValue result = JS_CellFloor(ctx, JS_NewFloat64(ctx, -3.2)); + ASSERT_INT(result, -4); + return 1; +} + +TEST(cell_ceiling) { + JSValue result = JS_CellCeiling(ctx, JS_NewFloat64(ctx, 3.2)); + ASSERT_INT(result, 4); + return 1; +} + +TEST(cell_ceiling_negative) { + JSValue result = JS_CellCeiling(ctx, JS_NewFloat64(ctx, -3.7)); + ASSERT_INT(result, -3); + return 1; +} + +TEST(cell_round) { + JSValue result1 = JS_CellRound(ctx, JS_NewFloat64(ctx, 3.4)); + JSValue result2 = JS_CellRound(ctx, JS_NewFloat64(ctx, 3.6)); + ASSERT_INT(result1, 3); + ASSERT_INT(result2, 4); + return 1; +} + +TEST(cell_trunc) { + JSValue result1 = JS_CellTrunc(ctx, JS_NewFloat64(ctx, 3.7)); + JSValue result2 = JS_CellTrunc(ctx, JS_NewFloat64(ctx, -3.7)); + ASSERT_INT(result1, 3); + ASSERT_INT(result2, -3); + return 1; +} + +TEST(cell_min) { + JSValue result = JS_CellMin(ctx, JS_NewInt32(ctx, 5), JS_NewInt32(ctx, 3)); + ASSERT_INT(result, 3); + return 1; +} + +TEST(cell_max) { + JSValue result = JS_CellMax(ctx, JS_NewInt32(ctx, 5), JS_NewInt32(ctx, 3)); + ASSERT_INT(result, 5); + return 1; +} + +TEST(cell_remainder) { + JSValue result = JS_CellRemainder(ctx, JS_NewInt32(ctx, 10), JS_NewInt32(ctx, 3)); + ASSERT_INT(result, 1); + return 1; +} + +TEST(cell_modulo) { + JSValue result = JS_CellModulo(ctx, JS_NewInt32(ctx, 10), JS_NewInt32(ctx, 3)); + ASSERT_INT(result, 1); + return 1; +} + +TEST(cell_neg) { + JSValue result1 = JS_CellNeg(ctx, JS_NewInt32(ctx, 42)); + JSValue result2 = JS_CellNeg(ctx, JS_NewInt32(ctx, -42)); + ASSERT_INT(result1, -42); + ASSERT_INT(result2, 42); + return 1; +} + +TEST(cell_not) { + JSValue result1 = JS_CellNot(ctx, JS_TRUE); + JSValue result2 = JS_CellNot(ctx, JS_FALSE); + ASSERT_FALSE(result1); + ASSERT_TRUE(result2); + return 1; +} + +/* ============================================================================ + CELL CORE FUNCTION TESTS + ============================================================================ */ + +TEST(cell_length_array) { + 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 result = JS_CellLength(ctx, arr); + ASSERT_INT(result, 3); + return 1; +} + +TEST(cell_length_string) { + JSValue text = JS_NewString(ctx, "hello"); + JSValue result = JS_CellLength(ctx, text); + ASSERT_INT(result, 5); + return 1; +} + +TEST(cell_reverse_array) { + 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 reversed = JS_CellReverse(ctx, arr); + ASSERT(JS_IsArray(reversed)); + ASSERT_INT(JS_GetPropertyUint32(ctx, reversed, 0), 3); + ASSERT_INT(JS_GetPropertyUint32(ctx, reversed, 1), 2); + ASSERT_INT(JS_GetPropertyUint32(ctx, reversed, 2), 1); + return 1; +} + +TEST(cell_reverse_string) { + JSValue text = JS_NewString(ctx, "abc"); + JSValue reversed = JS_CellReverse(ctx, text); + ASSERT(JS_IsText(reversed)); + const char *s = JS_ToCString(ctx, reversed); + ASSERT(strcmp(s, "cba") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(cell_meme_shallow) { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, 10)); + JSValue copy = JS_CellMeme(ctx, obj, JS_FALSE); + ASSERT(JS_IsRecord(copy)); + JSValue x = JS_GetPropertyStr(ctx, copy, "x"); + ASSERT_INT(x, 10); + return 1; +} + +/* ============================================================================ + JSON TESTS + ============================================================================ */ + +TEST(parse_json_object) { + const char *json = "{\"name\":\"test\",\"value\":42}"; + JSValue obj = JS_ParseJSON(ctx, json, strlen(json), ""); + ASSERT(JS_IsRecord(obj)); + JSValue name = JS_GetPropertyStr(ctx, obj, "name"); + JSValue value = JS_GetPropertyStr(ctx, obj, "value"); + ASSERT_STR(name, "test"); + ASSERT_INT(value, 42); + return 1; +} + +TEST(parse_json_array) { + const char *json = "[1,2,3]"; + JSValue arr = JS_ParseJSON(ctx, json, strlen(json), ""); + ASSERT(JS_IsArray(arr)); + int64_t len; + JS_GetLength(ctx, arr, &len); + ASSERT(len == 3); + ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 0), 1); + ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 1), 2); + ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 2), 3); + return 1; +} + +TEST(parse_json_nested) { + const char *json = "{\"outer\":{\"inner\":99}}"; + JSValue obj = JS_ParseJSON(ctx, json, strlen(json), ""); + ASSERT(JS_IsRecord(obj)); + JSValue outer = JS_GetPropertyStr(ctx, obj, "outer"); + ASSERT(JS_IsRecord(outer)); + JSValue inner = JS_GetPropertyStr(ctx, outer, "inner"); + ASSERT_INT(inner, 99); + return 1; +} + +TEST(stringify_json_object) { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, obj, "a", JS_NewInt32(ctx, 1)); + JSValue str = JS_JSONStringify(ctx, obj, JS_NULL, JS_NULL); + ASSERT(JS_IsText(str)); + const char *s = JS_ToCString(ctx, str); + ASSERT(strstr(s, "\"a\"") != NULL); + ASSERT(strstr(s, "1") != NULL); + JS_FreeCString(ctx, s); + return 1; +} + +TEST(stringify_json_array) { + JSValue arr = JS_NewArray(ctx); + JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1)); + JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2)); + JSValue str = JS_JSONStringify(ctx, arr, JS_NULL, JS_NULL); + ASSERT(JS_IsText(str)); + const char *s = JS_ToCString(ctx, str); + ASSERT(strcmp(s, "[1,2]") == 0); + JS_FreeCString(ctx, s); + return 1; +} + +/* ============================================================================ + C FUNCTION TESTS + ============================================================================ */ + +static JSValue cfunc_return_42(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + return JS_NewInt32(ctx, 42); +} + +static JSValue cfunc_sum_args(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + int sum = 0; + for (int i = 0; i < argc; i++) { + if (JS_IsInt(argv[i])) { + sum += JS_VALUE_GET_INT(argv[i]); + } + } + return JS_NewInt32(ctx, sum); +} + +TEST(new_cfunction_no_args) { + JSValue func = JS_NewCFunction(ctx, cfunc_return_42, "return42", 0); + ASSERT(JS_IsFunction(func)); + JSValue result = JS_Call(ctx, func, JS_NULL, 0, NULL); + ASSERT_INT(result, 42); + return 1; +} + +TEST(new_cfunction_with_args) { + JSValue func = JS_NewCFunction(ctx, cfunc_sum_args, "sum", 3); + ASSERT(JS_IsFunction(func)); + JSValue args[3] = { + JS_NewInt32(ctx, 10), + JS_NewInt32(ctx, 20), + JS_NewInt32(ctx, 30) + }; + JSValue result = JS_Call(ctx, func, JS_NULL, 3, args); + ASSERT_INT(result, 60); + return 1; +} + +TEST(call_function_on_global) { + JSValue global = JS_GetGlobalObject(ctx); + JSValue func = JS_NewCFunction(ctx, cfunc_return_42, "testFunc", 0); + JS_SetPropertyStr(ctx, global, "testFunc", func); + JSValue got = JS_GetPropertyStr(ctx, global, "testFunc"); + ASSERT(JS_IsFunction(got)); + JSValue result = JS_Call(ctx, got, JS_NULL, 0, NULL); + ASSERT_INT(result, 42); + return 1; +} + +/* ============================================================================ + PROPERTY ACCESS TESTS + ============================================================================ */ + +TEST(get_property_with_jsvalue_key) { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, obj, "foo", JS_NewInt32(ctx, 123)); + JSValue key = JS_NewString(ctx, "foo"); + JSValue val = JS_GetProperty(ctx, obj, key); + ASSERT_INT(val, 123); + return 1; +} + +TEST(set_property_with_jsvalue_key) { + JSValue obj = JS_NewObject(ctx); + JSValue key = JS_NewString(ctx, "bar"); + int r = JS_SetProperty(ctx, obj, key, JS_NewInt32(ctx, 456)); + ASSERT(r >= 0); + JSValue val = JS_GetPropertyStr(ctx, obj, "bar"); + ASSERT_INT(val, 456); + return 1; +} + +TEST(get_own_property_names) { + 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 names = JS_GetOwnPropertyNames(ctx, obj); + ASSERT(JS_IsArray(names)); + int64_t len; + JS_GetLength(ctx, names, &len); + ASSERT(len == 3); + return 1; +} + +TEST(property_uint32_on_object) { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyUint32(ctx, obj, 0, JS_NewInt32(ctx, 100)); + JS_SetPropertyUint32(ctx, obj, 1, JS_NewInt32(ctx, 200)); + JSValue v0 = JS_GetPropertyUint32(ctx, obj, 0); + JSValue v1 = JS_GetPropertyUint32(ctx, obj, 1); + ASSERT_INT(v0, 100); + ASSERT_INT(v1, 200); + return 1; +} + +/* ============================================================================ + PROTOTYPE TESTS + ============================================================================ */ + +TEST(get_prototype) { + JSValue proto = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, proto, "inherited", JS_NewInt32(ctx, 999)); + JSValue obj = JS_NewObjectProto(ctx, proto); + JSValue gotProto = JS_GetPrototype(ctx, obj); + ASSERT(JS_IsRecord(gotProto)); + JSValue inherited = JS_GetPropertyStr(ctx, gotProto, "inherited"); + ASSERT_INT(inherited, 999); + return 1; +} + +TEST(cell_proto) { + JSValue proto = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, proto, "value", JS_NewInt32(ctx, 42)); + JSValue obj = JS_NewObjectProto(ctx, proto); + JSValue gotProto = JS_CellProto(ctx, obj); + ASSERT(JS_IsRecord(gotProto)); + JSValue val = JS_GetPropertyStr(ctx, gotProto, "value"); + ASSERT_INT(val, 42); + return 1; +} + +/* ============================================================================ + ARRAY FROM VALUES TEST + ============================================================================ */ + +TEST(new_array_from) { + JSValue values[4] = { + JS_NewInt32(ctx, 10), + JS_NewInt32(ctx, 20), + JS_NewInt32(ctx, 30), + JS_NewInt32(ctx, 40) + }; + JSValue arr = JS_NewArrayFrom(ctx, 4, values); + ASSERT(JS_IsArray(arr)); + int64_t len; + JS_GetLength(ctx, arr, &len); + ASSERT(len == 4); + ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 0), 10); + ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 3), 40); + return 1; +} + +TEST(new_array_len) { + JSValue arr = JS_NewArrayLen(ctx, 5); + ASSERT(JS_IsArray(arr)); + int64_t len; + JS_GetLength(ctx, arr, &len); + ASSERT(len == 5); + return 1; +} + +/* ============================================================================ + CELL APPLY / CALL TESTS + ============================================================================ */ + +TEST(cell_apply) { + JSValue func = JS_NewCFunction(ctx, cfunc_sum_args, "sum", 3); + JSValue args = JS_NewArray(ctx); + JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 5)); + JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 10)); + JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 15)); + JSValue result = JS_CellApply(ctx, func, args); + ASSERT_INT(result, 30); + return 1; +} + +TEST(cell_call) { + JSValue func = JS_NewCFunction(ctx, cfunc_sum_args, "sum", 2); + JSValue this_obj = JS_NewObject(ctx); + JSValue args = JS_NewArray(ctx); + JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 7)); + JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 8)); + JSValue result = JS_CellCall(ctx, func, this_obj, args); + ASSERT_INT(result, 15); + return 1; +} + +/* ============================================================================ + IMMEDIATE STRING EDGE CASES + ============================================================================ */ + +TEST(string_immediate_one_char) { + JSValue v = JS_NewString(ctx, "x"); + ASSERT(JS_IsText(v)); + ASSERT(MIST_IsImmediateASCII(v)); + ASSERT(MIST_GetImmediateASCIILen(v) == 1); + ASSERT(MIST_GetImmediateASCIIChar(v, 0) == 'x'); + return 1; +} + +TEST(string_immediate_special_chars) { + JSValue v = JS_NewString(ctx, "a!@#$%^"); + ASSERT(JS_IsText(v)); + ASSERT(MIST_IsImmediateASCII(v)); + ASSERT(MIST_GetImmediateASCIILen(v) == 7); + return 1; +} + +TEST(try_new_immediate_ascii_success) { + JSValue v = MIST_TryNewImmediateASCII("test", 4); + ASSERT(!JS_IsNull(v)); + ASSERT(MIST_IsImmediateASCII(v)); + ASSERT(MIST_GetImmediateASCIILen(v) == 4); + return 1; +} + +TEST(try_new_immediate_ascii_too_long) { + JSValue v = MIST_TryNewImmediateASCII("12345678", 8); + ASSERT(JS_IsNull(v)); + return 1; +} + +/* ============================================================================ + EXCEPTION / ERROR TESTS + ============================================================================ */ + +TEST(has_exception_initially_false) { + ASSERT(!JS_HasException(ctx)); + return 1; +} + +TEST(new_error) { + JSValue err = JS_NewError(ctx); + ASSERT(JS_IsError(ctx, err)); + return 1; +} + +/* ============================================================================ + FLOAT EDGE CASES + ============================================================================ */ + +TEST(float_small_positive) { + JSValue v = JS_NewFloat64(ctx, 0.001); + ASSERT(JS_IsNumber(v)); + double d; + JS_ToFloat64(ctx, &d, v); + ASSERT(fabs(d - 0.001) < 0.0001); + return 1; +} + +TEST(float_large_value) { + JSValue v = JS_NewFloat64(ctx, 1e30); + ASSERT(JS_IsNumber(v)); + double d; + JS_ToFloat64(ctx, &d, v); + ASSERT(fabs(d - 1e30) < 1e25); + return 1; +} + +/* ============================================================================ + MORE EQUALITY TESTS + ============================================================================ */ + +TEST(strict_eq_floats) { + JSValue a = JS_NewFloat64(ctx, 3.14); + JSValue b = JS_NewFloat64(ctx, 3.14); + JSValue c = JS_NewFloat64(ctx, 2.71); + ASSERT(JS_StrictEq(ctx, a, b)); + ASSERT(!JS_StrictEq(ctx, a, c)); + return 1; +} + +TEST(strict_eq_different_types) { + JSValue num = JS_NewInt32(ctx, 1); + JSValue str = JS_NewString(ctx, "1"); + JSValue boo = JS_TRUE; + ASSERT(!JS_StrictEq(ctx, num, str)); + ASSERT(!JS_StrictEq(ctx, num, boo)); + ASSERT(!JS_StrictEq(ctx, str, boo)); + return 1; +} + +TEST(strict_eq_objects_identity) { + JSValue obj1 = JS_NewObject(ctx); + JSValue obj2 = JS_NewObject(ctx); + /* Different objects are not equal even if empty */ + ASSERT(!JS_StrictEq(ctx, obj1, obj2)); + /* Same object is equal to itself */ + ASSERT(JS_StrictEq(ctx, obj1, obj1)); + return 1; +} + +/* ============================================================================ + TYPE CHECK COMPREHENSIVE + ============================================================================ */ + +TEST(is_function_check) { + JSValue func = JS_NewCFunction(ctx, cfunc_return_42, "test", 0); + JSValue num = JS_NewInt32(ctx, 42); + JSValue obj = JS_NewObject(ctx); + ASSERT(JS_IsFunction(func)); + ASSERT(!JS_IsFunction(num)); + ASSERT(!JS_IsFunction(obj)); + return 1; +} + +TEST(is_integer_vs_number) { + JSValue i = JS_NewInt32(ctx, 42); + JSValue f = JS_NewFloat64(ctx, 3.14); + ASSERT(JS_IsInteger(i)); + ASSERT(JS_IsInt(i)); + ASSERT(!JS_IsInteger(f)); + ASSERT(!JS_IsInt(f)); + ASSERT(JS_IsNumber(i)); + ASSERT(JS_IsNumber(f)); + return 1; +} + /* ============================================================================ MAIN TEST RUNNER ============================================================================ */ @@ -826,6 +1733,121 @@ int run_c_test_suite(JSContext *ctx) printf("\nGlobal Object:\n"); RUN_TEST(global_object); + printf("\nValue Conversions:\n"); + RUN_TEST(to_bool_true_values); + RUN_TEST(to_bool_false_values); + RUN_TEST(to_int32_from_int); + RUN_TEST(to_int32_from_float); + RUN_TEST(to_int32_negative_float); + RUN_TEST(to_int64_large_value); + RUN_TEST(to_float64_from_int); + RUN_TEST(to_float64_from_float); + + printf("\nNewInt64/NewUint32 Edge Cases:\n"); + RUN_TEST(new_int64_in_int32_range); + RUN_TEST(new_int64_out_of_int32_range); + RUN_TEST(new_uint32_in_int32_range); + RUN_TEST(new_uint32_out_of_signed_range); + + printf("\nString Conversions:\n"); + RUN_TEST(to_string_from_int); + RUN_TEST(to_string_from_float); + RUN_TEST(to_string_from_bool); + RUN_TEST(to_string_from_null); + RUN_TEST(string_len_with_length); + + printf("\nCell Text Functions:\n"); + RUN_TEST(cell_text_from_int); + RUN_TEST(cell_lower); + RUN_TEST(cell_upper); + RUN_TEST(cell_trim_whitespace); + RUN_TEST(cell_codepoint); + RUN_TEST(cell_character); + RUN_TEST(cell_search_found); + RUN_TEST(cell_search_not_found); + RUN_TEST(cell_extract); + RUN_TEST(cell_replace); + + printf("\nCell Number Functions:\n"); + RUN_TEST(cell_number_from_string); + RUN_TEST(cell_abs_positive); + RUN_TEST(cell_abs_negative); + RUN_TEST(cell_sign_positive); + RUN_TEST(cell_sign_negative); + RUN_TEST(cell_sign_zero); + RUN_TEST(cell_floor); + RUN_TEST(cell_floor_negative); + RUN_TEST(cell_ceiling); + RUN_TEST(cell_ceiling_negative); + RUN_TEST(cell_round); + RUN_TEST(cell_trunc); + RUN_TEST(cell_min); + RUN_TEST(cell_max); + RUN_TEST(cell_remainder); + RUN_TEST(cell_modulo); + RUN_TEST(cell_neg); + RUN_TEST(cell_not); + + printf("\nCell Core Functions:\n"); + RUN_TEST(cell_length_array); + RUN_TEST(cell_length_string); + RUN_TEST(cell_reverse_array); + RUN_TEST(cell_reverse_string); + RUN_TEST(cell_meme_shallow); + + printf("\nJSON:\n"); + RUN_TEST(parse_json_object); + RUN_TEST(parse_json_array); + RUN_TEST(parse_json_nested); + RUN_TEST(stringify_json_object); + RUN_TEST(stringify_json_array); + + printf("\nC Functions:\n"); + RUN_TEST(new_cfunction_no_args); + RUN_TEST(new_cfunction_with_args); + RUN_TEST(call_function_on_global); + + printf("\nProperty Access:\n"); + RUN_TEST(get_property_with_jsvalue_key); + RUN_TEST(set_property_with_jsvalue_key); + RUN_TEST(get_own_property_names); + RUN_TEST(property_uint32_on_object); + + printf("\nPrototypes:\n"); + RUN_TEST(get_prototype); + RUN_TEST(cell_proto); + + printf("\nArray Creation:\n"); + RUN_TEST(new_array_from); + RUN_TEST(new_array_len); + + printf("\nCell Apply/Call:\n"); + RUN_TEST(cell_apply); + RUN_TEST(cell_call); + + printf("\nImmediate String Edge Cases:\n"); + RUN_TEST(string_immediate_one_char); + RUN_TEST(string_immediate_special_chars); + RUN_TEST(try_new_immediate_ascii_success); + RUN_TEST(try_new_immediate_ascii_too_long); + + printf("\nExceptions/Errors:\n"); + RUN_TEST(has_exception_initially_false); + RUN_TEST(new_error); + + printf("\nFloat Edge Cases:\n"); + RUN_TEST(float_small_positive); + RUN_TEST(float_large_value); + + printf("\nMore Equality Tests:\n"); + RUN_TEST(strict_eq_floats); + RUN_TEST(strict_eq_different_types); + RUN_TEST(strict_eq_objects_identity); + + printf("\nComprehensive Type Checks:\n"); + RUN_TEST(is_function_check); + RUN_TEST(is_integer_vs_number); + printf("\n=================================\n"); printf("Results: %d passed, %d failed\n", tests_passed, tests_failed); printf("=================================\n\n");