203 lines
7.0 KiB
C
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_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;
|
|
}
|