From 9e29666bf99c5e5f1cb0246d420b207aacfdf408 Mon Sep 17 00:00:00 2001
From: Paul Sokolovsky <pfalcon@users.sourceforge.net>
Date: Mon, 19 May 2014 20:59:13 +0300
Subject: [PATCH] py: Implement proper separation between io.FileIO and
 io.TextIOWrapper.

io.FileIO is binary I/O, ans actually optional. Default file type is
io.TextIOWrapper, which provides str results. CPython3 explicitly describes
io.TextIOWrapper as buffered I/O, but we don't have buffering support yet
anyway.
---
 py/modio.c          |  9 ++++++
 py/mpconfig.h       |  4 +++
 py/qstrdefs.h       |  2 ++
 stmhal/file.c       |  8 ++---
 unix/file.c         | 73 ++++++++++++++++++++++++++++++---------------
 unix/mpconfigport.h |  1 +
 6 files changed, 69 insertions(+), 28 deletions(-)

diff --git a/py/modio.c b/py/modio.c
index 3a009a7d8..b1f0d880a 100644
--- a/py/modio.c
+++ b/py/modio.c
@@ -32,11 +32,20 @@
 
 #if MICROPY_ENABLE_MOD_IO
 
+extern const mp_obj_type_t mp_type_fileio;
+extern const mp_obj_type_t mp_type_textio;
+
 STATIC const mp_map_elem_t mp_module_io_globals_table[] = {
     { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_io) },
     // Note: mp_builtin_open_obj should be defined by port, it's not
     // part of the core.
     { MP_OBJ_NEW_QSTR(MP_QSTR_open), (mp_obj_t)&mp_builtin_open_obj },
+    #if MICROPY_MOD_IO_FILEIO
+    { MP_OBJ_NEW_QSTR(MP_QSTR_FileIO), (mp_obj_t)&mp_type_fileio },
+    #endif
+    #if MICROPY_CPYTHON_COMPAT
+    { MP_OBJ_NEW_QSTR(MP_QSTR_TextIOWrapper), (mp_obj_t)&mp_type_textio },
+    #endif
     { MP_OBJ_NEW_QSTR(MP_QSTR_StringIO), (mp_obj_t)&mp_type_stringio },
     #if MICROPY_IO_BYTESIO
     { MP_OBJ_NEW_QSTR(MP_QSTR_BytesIO), (mp_obj_t)&mp_type_bytesio },
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 53429f1c1..445fc78b0 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -225,6 +225,10 @@ typedef double mp_float_t;
 #define MICROPY_ENABLE_MOD_IO (1)
 #endif
 
+#ifndef MICROPY_MOD_IO_FILEIO
+#define MICROPY_MOD_IO_FILEIO (0)
+#endif
+
 #ifndef MICROPY_IO_BYTESIO
 #define MICROPY_IO_BYTESIO (1)
 #endif
diff --git a/py/qstrdefs.h b/py/qstrdefs.h
index e33d9967d..cffb64d5e 100644
--- a/py/qstrdefs.h
+++ b/py/qstrdefs.h
@@ -354,6 +354,8 @@ Q(io)
 Q(readall)
 Q(readline)
 Q(readlines)
+Q(FileIO)
+Q(TextIOWrapper)
 Q(StringIO)
 Q(BytesIO)
 Q(getvalue)
diff --git a/stmhal/file.c b/stmhal/file.c
index 93bd49e66..3fea95653 100644
--- a/stmhal/file.c
+++ b/stmhal/file.c
@@ -41,7 +41,7 @@ typedef struct _pyb_file_obj_t {
 } pyb_file_obj_t;
 
 void file_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
-    printf("<io.FileIO %p>", self_in);
+    printf("<io.%s %p>", mp_obj_get_type_str(self_in), self_in);
 }
 
 STATIC machine_int_t file_read(mp_obj_t self_in, void *buf, machine_uint_t size, int *errcode) {
@@ -94,7 +94,7 @@ STATIC const mp_stream_p_t file_obj_stream_p = {
     .write = file_write,
 };
 
-STATIC const mp_obj_type_t file_obj_type = {
+const mp_obj_type_t mp_type_textio = {
     { &mp_type_type },
     .name = MP_QSTR_FileIO,
     .make_new = file_obj_make_new,
@@ -113,7 +113,7 @@ STATIC mp_obj_t file_obj_make_new(mp_obj_t type_in, uint n_args, uint n_kw, cons
         mode = mp_obj_str_get_str(args[1]);
     }
     pyb_file_obj_t *self = m_new_obj_with_finaliser(pyb_file_obj_t);
-    self->base.type = &file_obj_type;
+    self->base.type = &mp_type_textio;
     if (mode[0] == 'r') {
         // open for reading
         FRESULT res = f_open(&self->fp, filename, FA_READ);
@@ -138,7 +138,7 @@ STATIC mp_obj_t file_obj_make_new(mp_obj_t type_in, uint n_args, uint n_kw, cons
 // Factory function for I/O stream classes
 STATIC mp_obj_t pyb_io_open(uint n_args, const mp_obj_t *args) {
     // TODO: analyze mode and buffering args and instantiate appropriate type
-    return file_obj_make_new((mp_obj_t)&file_obj_type, n_args, 0, args);
+    return file_obj_make_new((mp_obj_t)&mp_type_textio, n_args, 0, args);
 }
 
 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_open_obj, 1, 2, pyb_io_open);
diff --git a/unix/file.c b/unix/file.c
index ada841ea2..7f45ac4a3 100644
--- a/unix/file.c
+++ b/unix/file.c
@@ -54,11 +54,12 @@ void check_fd_is_open(const mp_obj_fdfile_t *o) {
 #define check_fd_is_open(o)
 #endif
 
-STATIC const mp_obj_type_t rawfile_type;
+extern const mp_obj_type_t mp_type_fileio;
+extern const mp_obj_type_t mp_type_textio;
 
 STATIC void fdfile_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
     mp_obj_fdfile_t *self = self_in;
-    print(env, "<io.FileIO %d>", self->fd);
+    print(env, "<io.%s %d>", mp_obj_get_type_str(self), self->fd);
 }
 
 STATIC machine_int_t fdfile_read(mp_obj_t o_in, void *buf, machine_uint_t size, int *errcode) {
@@ -103,23 +104,10 @@ STATIC mp_obj_t fdfile_fileno(mp_obj_t self_in) {
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_1(fdfile_fileno_obj, fdfile_fileno);
 
-STATIC mp_obj_fdfile_t *fdfile_new(int fd) {
-    mp_obj_fdfile_t *o = m_new_obj(mp_obj_fdfile_t);
-    o->base.type = &rawfile_type;
-    o->fd = fd;
-    return o;
-}
-
 STATIC mp_obj_t fdfile_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
     mp_obj_fdfile_t *o = m_new_obj(mp_obj_fdfile_t);
-    o->base.type = type_in;
-
-    if (MP_OBJ_IS_SMALL_INT(args[0])) {
-        o->fd = MP_OBJ_SMALL_INT_VALUE(args[0]);
-        return o;
-    }
+    mp_const_obj_t type = type_in;
 
-    const char *fname = mp_obj_str_get_str(args[0]);
     const char *mode_s;
     if (n_args > 1) {
         mode_s = mp_obj_str_get_str(args[1]);
@@ -143,14 +131,32 @@ STATIC mp_obj_t fdfile_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const
             case '+':
                 mode |= O_RDWR;
                 break;
+            #if MICROPY_MOD_IO_FILEIO
+            // If we don't have io.FileIO, then files are in text mode implicitly
+            case 'b':
+                type = &mp_type_fileio;
+                break;
+            case 't':
+                type = &mp_type_textio;
+                break;
+            #endif
         }
     }
 
+    o->base.type = type;
+
+    if (MP_OBJ_IS_SMALL_INT(args[0])) {
+        o->fd = MP_OBJ_SMALL_INT_VALUE(args[0]);
+        return o;
+    }
+
+    const char *fname = mp_obj_str_get_str(args[0]);
     int fd = open(fname, mode, 0644);
     if (fd == -1) {
         nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT((machine_int_t)errno)));
     }
-    return fdfile_new(fd);
+    o->fd = fd;
+    return o;
 }
 
 STATIC const mp_map_elem_t rawfile_locals_dict_table[] = {
@@ -167,29 +173,48 @@ STATIC const mp_map_elem_t rawfile_locals_dict_table[] = {
 
 STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table);
 
-STATIC const mp_stream_p_t rawfile_stream_p = {
+#if MICROPY_MOD_IO_FILEIO
+STATIC const mp_stream_p_t fileio_stream_p = {
     .read = fdfile_read,
     .write = fdfile_write,
+    .is_bytes = true,
 };
 
-STATIC const mp_obj_type_t rawfile_type = {
+const mp_obj_type_t mp_type_fileio = {
     { &mp_type_type },
     .name = MP_QSTR_FileIO,
     .print = fdfile_print,
     .make_new = fdfile_make_new,
     .getiter = mp_identity,
     .iternext = mp_stream_unbuffered_iter,
-    .stream_p = &rawfile_stream_p,
+    .stream_p = &fileio_stream_p,
+    .locals_dict = (mp_obj_t)&rawfile_locals_dict,
+};
+#endif
+
+STATIC const mp_stream_p_t textio_stream_p = {
+    .read = fdfile_read,
+    .write = fdfile_write,
+};
+
+const mp_obj_type_t mp_type_textio = {
+    { &mp_type_type },
+    .name = MP_QSTR_TextIOWrapper,
+    .print = fdfile_print,
+    .make_new = fdfile_make_new,
+    .getiter = mp_identity,
+    .iternext = mp_stream_unbuffered_iter,
+    .stream_p = &textio_stream_p,
     .locals_dict = (mp_obj_t)&rawfile_locals_dict,
 };
 
 // Factory function for I/O stream classes
 mp_obj_t mp_builtin_open(uint n_args, const mp_obj_t *args) {
     // TODO: analyze mode and buffering args and instantiate appropriate type
-    return fdfile_make_new((mp_obj_t)&rawfile_type, n_args, 0, args);
+    return fdfile_make_new((mp_obj_t)&mp_type_textio, n_args, 0, args);
 }
 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_open_obj, 1, 2, mp_builtin_open);
 
-const mp_obj_fdfile_t mp_sys_stdin_obj  = { .base = {&rawfile_type}, .fd = STDIN_FILENO };
-const mp_obj_fdfile_t mp_sys_stdout_obj = { .base = {&rawfile_type}, .fd = STDOUT_FILENO };
-const mp_obj_fdfile_t mp_sys_stderr_obj = { .base = {&rawfile_type}, .fd = STDERR_FILENO };
+const mp_obj_fdfile_t mp_sys_stdin_obj  = { .base = {&mp_type_textio}, .fd = STDIN_FILENO };
+const mp_obj_fdfile_t mp_sys_stdout_obj = { .base = {&mp_type_textio}, .fd = STDOUT_FILENO };
+const mp_obj_fdfile_t mp_sys_stderr_obj = { .base = {&mp_type_textio}, .fd = STDERR_FILENO };
diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h
index a78caf050..6cac4f4cc 100644
--- a/unix/mpconfigport.h
+++ b/unix/mpconfigport.h
@@ -45,6 +45,7 @@
 #define MICROPY_MOD_SYS_EXIT        (1)
 #define MICROPY_MOD_SYS_STDFILES    (1)
 #define MICROPY_ENABLE_MOD_CMATH    (1)
+#define MICROPY_MOD_IO_FILEIO       (1)
 // Define to MICROPY_ERROR_REPORTING_DETAILED to get function, etc.
 // names in exception messages (may require more RAM).
 #define MICROPY_ERROR_REPORTING     (MICROPY_ERROR_REPORTING_DETAILED)
-- 
GitLab