Files
cell/playdate/json_playdate.c
2026-02-01 23:03:01 -06:00

203 lines
7.0 KiB
C

// 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 <string.h>
#include <stdlib.h>
// --- 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_IsText(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(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;
}