diff --git a/extmod/modujson.c b/extmod/modujson.c new file mode 100644 index 0000000000..2772f7d123 --- /dev/null +++ b/extmod/modujson.c @@ -0,0 +1,71 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "mpconfig.h" +#include "misc.h" +#include "qstr.h" +#include "obj.h" +#include "runtime.h" + +#if MICROPY_PY_UJSON + +STATIC mp_obj_t mod_ujson_dumps(mp_obj_t obj) { + vstr_t vstr; + vstr_init(&vstr, 8); + mp_obj_print_helper((void (*)(void *env, const char *fmt, ...))vstr_printf, &vstr, obj, PRINT_JSON); + mp_obj_t ret = mp_obj_new_str(vstr.buf, vstr.len, false); + vstr_clear(&vstr); + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_ujson_dumps_obj, mod_ujson_dumps); + +STATIC const mp_map_elem_t mp_module_ujson_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_ujson) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_dumps), (mp_obj_t)&mod_ujson_dumps_obj }, +}; + +STATIC const mp_obj_dict_t mp_module_ujson_globals = { + .base = {&mp_type_dict}, + .map = { + .all_keys_are_qstrs = 1, + .table_is_fixed_array = 1, + .used = MP_ARRAY_SIZE(mp_module_ujson_globals_table), + .alloc = MP_ARRAY_SIZE(mp_module_ujson_globals_table), + .table = (mp_map_elem_t*)mp_module_ujson_globals_table, + }, +}; + +const mp_obj_module_t mp_module_ujson = { + .base = { &mp_type_module }, + .name = MP_QSTR_ujson, + .globals = (mp_obj_dict_t*)&mp_module_ujson_globals, +}; + +#endif //MICROPY_PY_UJSON diff --git a/py/builtin.h b/py/builtin.h index 1bb61f6ebc..9c8b2b9be2 100644 --- a/py/builtin.h +++ b/py/builtin.h @@ -89,3 +89,4 @@ extern struct _dummy_t mp_sys_stderr_obj; // extmod modules extern const mp_obj_module_t mp_module_uctypes; extern const mp_obj_module_t mp_module_zlibd; +extern const mp_obj_module_t mp_module_ujson; diff --git a/py/builtintables.c b/py/builtintables.c index 08b6b16493..391077d434 100644 --- a/py/builtintables.c +++ b/py/builtintables.c @@ -200,6 +200,9 @@ STATIC const mp_map_elem_t mp_builtin_module_table[] = { #if MICROPY_PY_ZLIBD { MP_OBJ_NEW_QSTR(MP_QSTR_zlibd), (mp_obj_t)&mp_module_zlibd }, #endif +#if MICROPY_PY_UJSON + { MP_OBJ_NEW_QSTR(MP_QSTR_ujson), (mp_obj_t)&mp_module_ujson }, +#endif // extra builtin modules as defined by a port MICROPY_PORT_BUILTIN_MODULES diff --git a/py/mpconfig.h b/py/mpconfig.h index adbcb0eb71..cf85533395 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -390,6 +390,10 @@ typedef double mp_float_t; #define MICROPY_PY_ZLIBD (0) #endif +#ifndef MICROPY_PY_UJSON +#define MICROPY_PY_UJSON (0) +#endif + /*****************************************************************************/ /* Hooks for a port to add builtins */ diff --git a/py/obj.h b/py/obj.h index c7745dc9eb..9fee0413ae 100644 --- a/py/obj.h +++ b/py/obj.h @@ -187,7 +187,8 @@ typedef enum { PRINT_STR = 0, PRINT_REPR = 1, PRINT_EXC = 2, // Special format for printing exception in unhandled exception message - PRINT_EXC_SUBCLASS = 4, // Internal flag for printing exception subclasses + PRINT_JSON = 3, + PRINT_EXC_SUBCLASS = 0x80, // Internal flag for printing exception subclasses } mp_print_kind_t; typedef void (*mp_print_fun_t)(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o, mp_print_kind_t kind); diff --git a/py/objbool.c b/py/objbool.c index dbe84d92e2..e6b5230f74 100644 --- a/py/objbool.c +++ b/py/objbool.c @@ -41,10 +41,18 @@ typedef struct _mp_obj_bool_t { STATIC void bool_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { mp_obj_bool_t *self = self_in; - if (self->value) { - print(env, "True"); + if (MICROPY_PY_UJSON && kind == PRINT_JSON) { + if (self->value) { + print(env, "true"); + } else { + print(env, "false"); + } } else { - print(env, "False"); + if (self->value) { + print(env, "True"); + } else { + print(env, "False"); + } } } diff --git a/py/objdict.c b/py/objdict.c index a378bf6db8..a624320427 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -60,6 +60,9 @@ STATIC mp_map_elem_t *dict_iter_next(mp_obj_dict_t *dict, mp_uint_t *cur) { STATIC void dict_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { mp_obj_dict_t *self = self_in; bool first = true; + if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { + kind = PRINT_REPR; + } print(env, "{"); mp_uint_t cur = 0; mp_map_elem_t *next = NULL; @@ -68,9 +71,9 @@ STATIC void dict_print(void (*print)(void *env, const char *fmt, ...), void *env print(env, ", "); } first = false; - mp_obj_print_helper(print, env, next->key, PRINT_REPR); + mp_obj_print_helper(print, env, next->key, kind); print(env, ": "); - mp_obj_print_helper(print, env, next->value, PRINT_REPR); + mp_obj_print_helper(print, env, next->value, kind); } print(env, "}"); } diff --git a/py/objlist.c b/py/objlist.c index 59390f371f..789a1600d3 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -49,12 +49,15 @@ STATIC mp_obj_t list_pop(mp_uint_t n_args, const mp_obj_t *args); STATIC void list_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) { mp_obj_list_t *o = o_in; + if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { + kind = PRINT_REPR; + } print(env, "["); for (mp_uint_t i = 0; i < o->len; i++) { if (i > 0) { print(env, ", "); } - mp_obj_print_helper(print, env, o->items[i], PRINT_REPR); + mp_obj_print_helper(print, env, o->items[i], kind); } print(env, "]"); } diff --git a/py/objnone.c b/py/objnone.c index a8d6071435..01536dcb34 100644 --- a/py/objnone.c +++ b/py/objnone.c @@ -38,7 +38,11 @@ typedef struct _mp_obj_none_t { } mp_obj_none_t; STATIC void none_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { - print(env, "None"); + if (MICROPY_PY_UJSON && kind == PRINT_JSON) { + print(env, "null"); + } else { + print(env, "None"); + } } STATIC mp_obj_t none_unary_op(mp_uint_t op, mp_obj_t o_in) { diff --git a/py/objstr.c b/py/objstr.c index b2afcdc98e..130af8a6af 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -92,9 +92,41 @@ void mp_str_print_quoted(void (*print)(void *env, const char *fmt, ...), void *e print(env, "%c", quote_char); } +#if MICROPY_PY_UJSON +STATIC void str_print_json(void (*print)(void *env, const char *fmt, ...), void *env, const byte *str_data, mp_uint_t str_len) { + print(env, "\""); + for (const byte *s = str_data, *top = str_data + str_len; s < top; s++) { + if (*s == '"' || *s == '\\' || *s == '/') { + print(env, "\\%c", *s); + } else if (32 <= *s && *s <= 126) { + print(env, "%c", *s); + } else if (*s == '\b') { + print(env, "\\b"); + } else if (*s == '\f') { + print(env, "\\f"); + } else if (*s == '\n') { + print(env, "\\n"); + } else if (*s == '\r') { + print(env, "\\r"); + } else if (*s == '\t') { + print(env, "\\t"); + } else { + print(env, "\\u%04x", *s); + } + } + print(env, "\""); +} +#endif + STATIC void str_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { GET_STR_DATA_LEN(self_in, str_data, str_len); bool is_bytes = MP_OBJ_IS_TYPE(self_in, &mp_type_bytes); + #if MICROPY_PY_UJSON + if (kind == PRINT_JSON) { + str_print_json(print, env, str_data, str_len); + return; + } + #endif if (kind == PRINT_STR && !is_bytes) { print(env, "%.*s", str_len, str_data); } else { diff --git a/py/objstrunicode.c b/py/objstrunicode.c index 8231eb9b16..0ee7f1dc9a 100644 --- a/py/objstrunicode.c +++ b/py/objstrunicode.c @@ -91,8 +91,44 @@ STATIC void uni_print_quoted(void (*print)(void *env, const char *fmt, ...), voi print(env, "%c", quote_char); } +#if MICROPY_PY_UJSON +STATIC void uni_print_json(void (*print)(void *env, const char *fmt, ...), void *env, const byte *str_data, uint str_len) { + print(env, "\""); + const byte *s = str_data, *top = str_data + str_len; + while (s < top) { + unichar ch; + ch = utf8_get_char(s); + s = utf8_next_char(s); + if (ch == '"' || ch == '\\' || ch == '/') { + print(env, "\\%c", ch); + } else if (32 <= ch && ch <= 126) { + print(env, "%c", ch); + } else if (*s == '\b') { + print(env, "\\b"); + } else if (*s == '\f') { + print(env, "\\f"); + } else if (*s == '\n') { + print(env, "\\n"); + } else if (*s == '\r') { + print(env, "\\r"); + } else if (*s == '\t') { + print(env, "\\t"); + } else { + print(env, "\\u%04x", ch); + } + } + print(env, "\""); +} +#endif + STATIC void uni_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { GET_STR_DATA_LEN(self_in, str_data, str_len); + #if MICROPY_PY_UJSON + if (kind == PRINT_JSON) { + uni_print_json(print, env, str_data, str_len); + return; + } + #endif if (kind == PRINT_STR) { print(env, "%.*s", str_len, str_data); } else { diff --git a/py/objtuple.c b/py/objtuple.c index 6abe7d2c64..2793e4838d 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -43,17 +43,26 @@ STATIC mp_obj_t mp_obj_new_tuple_iterator(mp_obj_tuple_t *tuple, mp_uint_t cur); void mp_obj_tuple_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) { mp_obj_tuple_t *o = o_in; - print(env, "("); + if (MICROPY_PY_UJSON && kind == PRINT_JSON) { + print(env, "["); + } else { + print(env, "("); + kind = PRINT_REPR; + } for (mp_uint_t i = 0; i < o->len; i++) { if (i > 0) { print(env, ", "); } - mp_obj_print_helper(print, env, o->items[i], PRINT_REPR); + mp_obj_print_helper(print, env, o->items[i], kind); } - if (o->len == 1) { - print(env, ","); + if (MICROPY_PY_UJSON && kind == PRINT_JSON) { + print(env, "]"); + } else { + if (o->len == 1) { + print(env, ","); + } + print(env, ")"); } - print(env, ")"); } STATIC mp_obj_t mp_obj_tuple_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) { diff --git a/py/py.mk b/py/py.mk index e2288d3821..fb59b89729 100644 --- a/py/py.mk +++ b/py/py.mk @@ -112,6 +112,7 @@ PY_O_BASENAME = \ pfenv_printf.o \ ../extmod/moductypes.o \ ../extmod/modzlibd.o \ + ../extmod/modujson.o \ # prepend the build destination prefix to the py object files PY_O = $(addprefix $(PY_BUILD)/, $(PY_O_BASENAME)) diff --git a/py/qstrdefs.h b/py/qstrdefs.h index d41029a1fa..41ffa1d201 100644 --- a/py/qstrdefs.h +++ b/py/qstrdefs.h @@ -463,3 +463,8 @@ Q(deleter) Q(zlibd) Q(decompress) #endif + +#if MICROPY_PY_UJSON +Q(ujson) +Q(dumps) +#endif diff --git a/stmhal/mpconfigport.h b/stmhal/mpconfigport.h index bfd399a76e..5e6faa4df2 100644 --- a/stmhal/mpconfigport.h +++ b/stmhal/mpconfigport.h @@ -57,6 +57,7 @@ #define MICROPY_PY_IO_FILEIO (1) #define MICROPY_PY_UCTYPES (1) #define MICROPY_PY_ZLIBD (1) +#define MICROPY_PY_UJSON (1) #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (0) diff --git a/tests/extmod/ujson_dumps.py b/tests/extmod/ujson_dumps.py new file mode 100644 index 0000000000..6e858fd3f9 --- /dev/null +++ b/tests/extmod/ujson_dumps.py @@ -0,0 +1,23 @@ +try: + import ujson as json +except ImportError: + import json + +print(json.dumps(False)) +print(json.dumps(True)) +print(json.dumps(None)) +print(json.dumps(1)) +print(json.dumps(1.2)) +print(json.dumps('abc')) +print(json.dumps('\x01\x7e\x7f\x80\u1234')) +print(json.dumps([])) +print(json.dumps([1])) +print(json.dumps([1, 2])) +print(json.dumps([1, True])) +print(json.dumps(())) +print(json.dumps((1,))) +print(json.dumps((1, 2))) +print(json.dumps((1, (2, 3)))) +print(json.dumps({})) +print(json.dumps({"a":1})) +print(json.dumps({"a":(2,[3,None])})) diff --git a/tests/run-tests b/tests/run-tests index 34f855d08d..a96f3773e4 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -3,6 +3,7 @@ import os import subprocess import sys +import platform import argparse from glob import glob @@ -37,6 +38,11 @@ def run_tests(pyb, tests, args): if pyb is not None: skip_tests.add('float/float_divmod.py') # tested by float/float_divmod_relaxed.py instead + # Some tests are known to fail on 64-bit machines + if pyb is None and platform.architecture()[0] == '64bit': + skip_tests.add('extmod/uctypes_ptr_le.py') + skip_tests.add('extmod/uctypes_ptr_native_le.py') + # Some tests are known to fail with native emitter # Remove them from the below when they work if args.emit == 'native': @@ -153,10 +159,10 @@ def main(): if args.test_dirs is None: if pyb is None: # run PC tests - test_dirs = ('basics', 'micropython', 'float', 'import', 'io', 'misc', 'unicode', 'unix') + test_dirs = ('basics', 'micropython', 'float', 'import', 'io', 'misc', 'unicode', 'extmod', 'unix') else: # run pyboard tests - test_dirs = ('basics', 'micropython', 'float', 'misc', 'pyb', 'pybnative', 'inlineasm') + test_dirs = ('basics', 'micropython', 'float', 'misc', 'extmod', 'pyb', 'pybnative', 'inlineasm') else: # run tests from these directories test_dirs = args.test_dirs diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h index 38da3fcbcb..56f66750f5 100644 --- a/unix/mpconfigport.h +++ b/unix/mpconfigport.h @@ -58,6 +58,7 @@ #define MICROPY_PY_UCTYPES (1) #define MICROPY_PY_ZLIBD (1) +#define MICROPY_PY_UJSON (1) // Define to MICROPY_ERROR_REPORTING_DETAILED to get function, etc. // names in exception messages (may require more RAM).