// json_playdate.c - Cell integration for Playdate JSON API // Wraps pd_api_json.h functions for JavaScript access #include "cell.h" #include "common.h" #include #include // --- JSON Decoder Context --- typedef struct { JSContext *js; JSValue result; JSValue stack[32]; int stack_depth; } JsonDecodeCtx; static void json_decode_error(json_decoder *decoder, const char *error, int linenum) { JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; ctx->result = JS_ThrowSyntaxError(ctx->js, "JSON parse error at line %d: %s", linenum, error); } static void json_decode_will_sublist(json_decoder *decoder, const char *name, json_value_type type) { JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; JSValue container = (type == kJSONArray) ? JS_NewArray(ctx->js) : JS_NewObject(ctx->js); if (ctx->stack_depth < 32) { ctx->stack[ctx->stack_depth++] = container; } } static void* json_decode_did_sublist(json_decoder *decoder, const char *name, json_value_type type) { JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; if (ctx->stack_depth > 0) { ctx->stack_depth--; JSValue completed = ctx->stack[ctx->stack_depth]; if (ctx->stack_depth == 0) { ctx->result = completed; } return NULL; } return NULL; } static void json_decode_table_value(json_decoder *decoder, const char *key, json_value value) { JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; if (ctx->stack_depth <= 0) return; JSValue container = ctx->stack[ctx->stack_depth - 1]; JSValue jsval; switch (value.type) { case kJSONNull: jsval = JS_NULL; break; case kJSONTrue: jsval = JS_TRUE; break; case kJSONFalse: jsval = JS_FALSE; break; case kJSONInteger: jsval = JS_NewInt32(ctx->js, value.data.intval); break; case kJSONFloat: jsval = JS_NewFloat64(ctx->js, value.data.floatval); break; case kJSONString: jsval = JS_NewString(ctx->js, value.data.stringval); break; case kJSONArray: case kJSONTable: if (ctx->stack_depth > 1) { jsval = ctx->stack[ctx->stack_depth - 1]; } else { jsval = JS_NULL; } break; default: jsval = JS_NULL; break; } JS_SetPropertyStr(ctx->js, container, key, jsval); } static void json_decode_array_value(json_decoder *decoder, int pos, json_value value) { JsonDecodeCtx *ctx = (JsonDecodeCtx*)decoder->userdata; if (ctx->stack_depth <= 0) return; JSValue container = ctx->stack[ctx->stack_depth - 1]; JSValue jsval; switch (value.type) { case kJSONNull: jsval = JS_NULL; break; case kJSONTrue: jsval = JS_TRUE; break; case kJSONFalse: jsval = JS_FALSE; break; case kJSONInteger: jsval = JS_NewInt32(ctx->js, value.data.intval); break; case kJSONFloat: jsval = JS_NewFloat64(ctx->js, value.data.floatval); break; case kJSONString: jsval = JS_NewString(ctx->js, value.data.stringval); break; default: jsval = JS_NULL; break; } JS_SetPropertyUint32(ctx->js, container, pos, jsval); } // --- JSON Encoder Context --- typedef struct { char *buffer; size_t len; size_t cap; } JsonEncodeCtx; static void json_encode_write(void *userdata, const char *str, int len) { JsonEncodeCtx *ctx = (JsonEncodeCtx*)userdata; if (ctx->len + len + 1 > ctx->cap) { ctx->cap = (ctx->cap + len + 1) * 2; ctx->buffer = realloc(ctx->buffer, ctx->cap); } memcpy(ctx->buffer + ctx->len, str, len); ctx->len += len; ctx->buffer[ctx->len] = '\0'; } static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val); static void encode_js_object(json_encoder *enc, JSContext *js, JSValue obj) { enc->startTable(enc); JSPropertyEnum *props; uint32_t len; if (JS_GetOwnPropertyNames(js, &props, &len, obj, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) == 0) { for (uint32_t i = 0; i < len; i++) { const char *key = JS_AtomToCString(js, props[i].atom); JSValue val = JS_GetProperty(js, obj, props[i].atom); enc->addTableMember(enc, key, strlen(key)); encode_js_value(enc, js, val); JS_FreeValue(js, val); JS_FreeCString(js, key); JS_FreeAtom(js, props[i].atom); } js_free(js, props); } enc->endTable(enc); } static void encode_js_array(json_encoder *enc, JSContext *js, JSValue arr) { enc->startArray(enc); JSValue lenVal = JS_GetPropertyStr(js, arr, "length"); int len = (int)js2number(js, lenVal); JS_FreeValue(js, lenVal); for (int i = 0; i < len; i++) { enc->addArrayMember(enc); JSValue val = JS_GetPropertyUint32(js, arr, i); encode_js_value(enc, js, val); JS_FreeValue(js, val); } enc->endArray(enc); } static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val) { if (JS_IsNull(val)) { enc->writeNull(enc); } else if (JS_IsBool(val)) { if (JS_ToBool(js, val)) enc->writeTrue(enc); else enc->writeFalse(enc); } else if (JS_IsNumber(val)) { double d = js2number(js, val); if (d == (int)d) enc->writeInt(enc, (int)d); else enc->writeDouble(enc, d); } else if (JS_IsString(val)) { size_t len; const char *str = JS_ToCStringLen(js, &len, val); enc->writeString(enc, str, len); JS_FreeCString(js, str); } else if (JS_IsArray(js, val)) { encode_js_array(enc, js, val); } else if (JS_IsObject(val)) { encode_js_object(enc, js, val); } else { enc->writeNull(enc); } } // --- API Functions --- JSC_SCALL(json_decodeString, if (!pd_json) return JS_ThrowInternalError(js, "json not initialized"); JsonDecodeCtx ctx = { js, JS_NULL, {}, 0 }; json_decoder decoder = {0}; decoder.decodeError = json_decode_error; decoder.willDecodeSublist = json_decode_will_sublist; decoder.didDecodeSublist = json_decode_did_sublist; decoder.didDecodeTableValue = json_decode_table_value; decoder.didDecodeArrayValue = json_decode_array_value; decoder.userdata = &ctx; json_value outval; pd_json->decodeString(&decoder, str, &outval); ret = ctx.result; ) JSC_CCALL(json_encode, if (!pd_json) return JS_ThrowInternalError(js, "json not initialized"); JsonEncodeCtx ctx = { malloc(256), 0, 256 }; if (!ctx.buffer) return JS_ThrowInternalError(js, "malloc failed"); ctx.buffer[0] = '\0'; json_encoder enc; int pretty = argc > 1 ? JS_ToBool(js, argv[1]) : 0; pd_json->initEncoder(&enc, json_encode_write, &ctx, pretty); encode_js_value(&enc, js, argv[0]); JSValue result = JS_NewStringLen(js, ctx.buffer, ctx.len); free(ctx.buffer); return result; ) static const JSCFunctionListEntry js_json_funcs[] = { MIST_FUNC_DEF(json, decodeString, 1), MIST_FUNC_DEF(json, encode, 2), }; JSValue js_json_use(JSContext *js) { JSValue mod = JS_NewObject(js); JS_SetPropertyFunctionList(js, mod, js_json_funcs, countof(js_json_funcs)); return mod; }