diff --git a/include/pocketpy/interpreter/modules.h b/include/pocketpy/interpreter/modules.h index f3871419..a74f56ab 100644 --- a/include/pocketpy/interpreter/modules.h +++ b/include/pocketpy/interpreter/modules.h @@ -5,3 +5,4 @@ void pk__add_module_os(); void pk__add_module_math(); void pk__add_module_dis(); void pk__add_module_random(); +void pk__add_module_json(); diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index 739b476c..5e139a5f 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -454,6 +454,8 @@ bool py_str(py_Ref val) PY_RAISE; bool py_repr(py_Ref val) PY_RAISE; /// Python equivalent to `len(val)`. bool py_len(py_Ref val) PY_RAISE; +/// Python equivalent to `json.dumps(val)`. +bool py_json(py_Ref val) PY_RAISE; /************* Unchecked Functions *************/ diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index bcdabc96..7bb420db 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -197,6 +197,7 @@ void VM__ctor(VM* self) { pk__add_module_math(); pk__add_module_dis(); pk__add_module_random(); + pk__add_module_json(); // add python builtins do { diff --git a/src/modules/json.c b/src/modules/json.c new file mode 100644 index 00000000..5a5ca0bf --- /dev/null +++ b/src/modules/json.c @@ -0,0 +1,111 @@ +#include "pocketpy/pocketpy.h" + +#include "pocketpy/common/utils.h" +#include "pocketpy/objects/object.h" +#include "pocketpy/common/sstream.h" +#include "pocketpy/interpreter/vm.h" +#include + +static bool json_loads(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + PY_CHECK_ARG_TYPE(0, tp_str); + const char* source = py_tostr(argv); + py_TmpRef mod = py_getmodule("json"); + return py_exec(source, "", EVAL_MODE, mod); +} + +static bool json_dumps(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + return py_json(argv); +} + +void pk__add_module_json() { + py_Ref mod = py_newmodule("json"); + + py_setdict(mod, py_name("null"), py_None); + py_setdict(mod, py_name("true"), py_True); + py_setdict(mod, py_name("false"), py_False); + + py_bindfunc(mod, "loads", json_loads); + py_bindfunc(mod, "dumps", json_dumps); +} + +static bool json__write_object(c11_sbuf* buf, py_TValue* obj); + +static bool json__write_array(c11_sbuf* buf, py_TValue* arr, int length) { + c11_sbuf__write_char(buf, '['); + for(int i = 0; i < length; i++) { + if(i != 0) c11_sbuf__write_cstr(buf, ", "); + bool ok = json__write_object(buf, arr + i); + if(!ok) return false; + } + c11_sbuf__write_char(buf, ']'); + return true; +} + +typedef struct { + c11_sbuf* buf; + bool first; +} json__write_dict_kv_ctx; + +static bool json__write_dict_kv(py_Ref k, py_Ref v, void* ctx_) { + json__write_dict_kv_ctx* ctx = ctx_; + if(!ctx->first) c11_sbuf__write_cstr(ctx->buf, ", "); + ctx->first = false; + if(!py_isstr(k)) return TypeError("keys must be strings"); + c11_sbuf__write_quoted(ctx->buf, py_tosv(k), '"'); + c11_sbuf__write_char(ctx->buf, ':'); + return json__write_object(ctx->buf, v); +} + +static bool json__write_object(c11_sbuf* buf, py_TValue* obj) { + switch(obj->type) { + case tp_NoneType: c11_sbuf__write_cstr(buf, "null"); return true; + case tp_int: c11_sbuf__write_int(buf, obj->_i64); return true; + case tp_float: { + if(isnan(obj->_f64)) { + c11_sbuf__write_cstr(buf, "NaN"); + } else if(isinf(obj->_f64)) { + c11_sbuf__write_cstr(buf, obj->_f64 < 0 ? "-Infinity" : "Infinity"); + } else { + c11_sbuf__write_f64(buf, obj->_f64, -1); + } + return true; + } + case tp_bool: { + c11_sbuf__write_cstr(buf, py_tobool(obj) ? "true" : "false"); + return true; + } + case tp_str: { + c11_sbuf__write_quoted(buf, py_tosv(obj), '"'); + return true; + } + case tp_list: { + return json__write_array(buf, py_list_data(obj), py_list_len(obj)); + } + case tp_tuple: { + return json__write_array(buf, py_tuple_data(obj), py_tuple_len(obj)); + } + case tp_dict: { + c11_sbuf__write_char(buf, '{'); + json__write_dict_kv_ctx ctx = {.buf = buf, .first = true}; + bool ok = py_dict_apply(obj, json__write_dict_kv, &ctx); + if(!ok) return false; + c11_sbuf__write_char(buf, '}'); + return true; + } + default: return TypeError("'%t' object is not JSON serializable", obj->type); + } +} + +bool py_json(py_Ref val) { + c11_sbuf buf; + c11_sbuf__ctor(&buf); + bool ok = json__write_object(&buf, val); + if(!ok){ + c11_sbuf__dtor(&buf); + return false; + } + c11_sbuf__py_submit(&buf, py_retval()); + return true; +} \ No newline at end of file diff --git a/src/modules/pkpy.c b/src/modules/pkpy.c index 96dab19f..0c92366c 100644 --- a/src/modules/pkpy.c +++ b/src/modules/pkpy.c @@ -5,7 +5,7 @@ #include "pocketpy/common/sstream.h" #include "pocketpy/interpreter/vm.h" -static bool _py_pkpy__next(int argc, py_Ref argv) { +static bool pkpy_next(int argc, py_Ref argv) { PY_CHECK_ARGC(1); int res = py_next(argv); if(res == -1) return false; @@ -17,5 +17,5 @@ static bool _py_pkpy__next(int argc, py_Ref argv) { void pk__add_module_pkpy() { py_Ref mod = py_newmodule("pkpy"); - py_bindfunc(mod, "next", _py_pkpy__next); + py_bindfunc(mod, "next", pkpy_next); } \ No newline at end of file diff --git a/tests/80_json.py b/tests/80_json.py index 865abf07..3b5f499f 100644 --- a/tests/80_json.py +++ b/tests/80_json.py @@ -14,11 +14,7 @@ a = { 'h': False } -try: - import cjson as json - print('[INFO] cjson is used') -except ImportError: - import json +import json assert json.loads("1") == 1 assert json.loads('"1"') == "1" @@ -29,7 +25,7 @@ assert json.loads("true") == True assert json.loads("false") == False assert json.loads("{}") == {} -assert json.loads(b"false") == False +# assert json.loads(b"false") == False _j = json.dumps(a) _a = json.loads(_j)