From 612045f53f7e5edf9faa359ee9ee52d490d58000 Mon Sep 17 00:00:00 2001
From: Damien George <damien.p.george@gmail.com>
Date: Wed, 17 Sep 2014 22:56:34 +0100
Subject: [PATCH] py: Add native json printing using existing print framework.

Also add start of ujson module with dumps implemented.  Enabled in unix
and stmhal ports.  Test passes on both.
---
 extmod/modujson.c           | 71 +++++++++++++++++++++++++++++++++++++
 py/builtin.h                |  1 +
 py/builtintables.c          |  3 ++
 py/mpconfig.h               |  4 +++
 py/obj.h                    |  3 +-
 py/objbool.c                | 14 ++++++--
 py/objdict.c                |  7 ++--
 py/objlist.c                |  5 ++-
 py/objnone.c                |  6 +++-
 py/objstr.c                 | 32 +++++++++++++++++
 py/objstrunicode.c          | 36 +++++++++++++++++++
 py/objtuple.c               | 19 +++++++---
 py/py.mk                    |  1 +
 py/qstrdefs.h               |  5 +++
 stmhal/mpconfigport.h       |  1 +
 tests/extmod/ujson_dumps.py | 23 ++++++++++++
 tests/run-tests             | 10 ++++--
 unix/mpconfigport.h         |  1 +
 18 files changed, 227 insertions(+), 15 deletions(-)
 create mode 100644 extmod/modujson.c
 create mode 100644 tests/extmod/ujson_dumps.py

diff --git a/extmod/modujson.c b/extmod/modujson.c
new file mode 100644
index 000000000..2772f7d12
--- /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 <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#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 1bb61f6eb..9c8b2b9be 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 08b6b1649..391077d43 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 adbcb0eb7..cf8553339 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 c7745dc9e..9fee0413a 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 dbe84d92e..e6b5230f7 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 a378bf6db..a62432042 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 59390f371..789a1600d 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 a8d607143..01536dcb3 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 b2afcdc98..130af8a6a 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 8231eb9b1..0ee7f1dc9 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 6abe7d2c6..2793e4838 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 e2288d382..fb59b8972 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 d41029a1f..41ffa1d20 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 bfd399a76..5e6faa4df 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 000000000..6e858fd3f
--- /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 34f855d08..a96f3773e 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 38da3fcbc..56f66750f 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).
-- 
GitLab