Skip to content
Snippets Groups Projects
Verified Commit fc83ae6e authored by rahix's avatar rahix
Browse files

feat: Merge initial filesystem support


We decided to merge the implementation ASAP so we can build other
features ontop of it.  Further cleanup and refactoring of the modules
will now happen in separate patches.

The Epicardium API for this module will not change that much from what
it looks like now.

Signed-off-by: default avatarRahix <rahix@rahix.de>
parents 4e8f909f c1578952
No related branches found
No related tags found
No related merge requests found
......@@ -48,6 +48,15 @@ typedef unsigned int size_t;
#define API_DISP_RECT 0x16
#define API_DISP_CIRC 0x17
#define API_DISP_PIXEL 0x18
#define API_FILE_OPEN 0x30
#define API_FILE_CLOSE 0x31
#define API_FILE_READ 0x32
#define API_FILE_WRITE 0x34
#define API_FILE_FLUSH 0x35
#define API_FILE_SEEK 0x36 //NYI
#define API_FILE_TELL 0x37 //NYI
#define API_FILE_STAT 0x38
/* clang-format on */
typedef uint32_t api_int_id_t;
......@@ -439,4 +448,68 @@ API(API_LIGHT_SENSOR_GET, int epic_light_sensor_get(uint16_t* value));
*/
API(API_LIGHT_SENSOR_STOP, int epic_light_sensor_stop());
/**
* File
* ====
* Except for :c:func:`epic_file_open`, which models C stdio's ``fopen``
* function, ``close``, ``read`` and ``write`` model `close(2)`_, `read(2)`_ and
* `write(2)`_. All file-related functions return >= ``0`` on success and
* ``-Exyz`` on failure, with error codes from errno.h (``EIO``, ``EINVAL``
* etc.)
*
* .. _close(2): http://man7.org/linux/man-pages/man2/close.2.html
* .. _read(2): http://man7.org/linux/man-pages/man2/read.2.html
* .. _write(2): http://man7.org/linux/man-pages/man2/write.2.html
*/
/** */
API(
API_FILE_OPEN,
int epic_file_open(const char* filename, const char* modeString)
);
/** */
API(API_FILE_CLOSE, int epic_file_close(int fd));
/** */
API(API_FILE_READ, int epic_file_read(int fd, void* buf, size_t nbytes));
/** */
API(
API_FILE_WRITE,
int epic_file_write(int fd, const void* buf, size_t nbytes)
);
/** */
API(API_FILE_FLUSH, int epic_file_flush(int fd));
/** */
enum epic_stat_type {
/** */
EPICSTAT_FILE,
/** */
EPICSTAT_DIR,
};
/** */
typedef struct epic_stat_t {
/** */
enum epic_stat_type type;
} epic_stat_t;
/**
* stat path
*
* This does not follow posix convention, but rather takes
* a path as parameter. This aligns more with libff's API and
* also this has been implemented for python import support, which
* passes the filename as well.
*
* :param const char* filename: path to stat
* :param epic_stat_t* stat: pointer to result
*
* :return: `0` on success, negative on error
*/
API(API_FILE_STAT, int epic_file_stat(const char* path, epic_stat_t* stat));
#endif /* _EPICARDIUM_H */
......@@ -2,24 +2,74 @@
* support routines for FatFs
*/
#include <stddef.h> //NULL
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <ff.h>
#include <FreeRTOS.h>
#include <semphr.h>
#include "modules.h"
#include "epicardium.h"
#ifndef EPIC_FAT_STATIC_SEMAPHORE
#define EPIC_FAT_STATIC_SEMAPHORE 0
#endif
static const TCHAR *rcstrings =
_T("OK\0DISK_ERR\0INT_ERR\0NOT_READY\0NO_FILE\0NO_PATH\0INVALID_NAME\0")
_T("DENIED\0EXIST\0INVALID_OBJECT\0WRITE_PROTECTED\0INVALID_DRIVE\0")
_T("NOT_ENABLED\0NO_FILESYSTEM\0MKFS_ABORTED\0TIMEOUT\0LOCKED\0")
_T("NOT_ENOUGH_CORE\0TOO_MANY_OPEN_FILES\0INVALID_PARAMETER\0");
// this table converts from FRESULT to POSIX errno
const int fresult_to_errno_table[20] = {
[FR_OK] = 0,
[FR_DISK_ERR] = EIO,
[FR_INT_ERR] = EIO,
[FR_NOT_READY] = EBUSY,
[FR_NO_FILE] = ENOENT,
[FR_NO_PATH] = ENOENT,
[FR_INVALID_NAME] = EINVAL,
[FR_DENIED] = EACCES,
[FR_EXIST] = EEXIST,
[FR_INVALID_OBJECT] = EINVAL,
[FR_WRITE_PROTECTED] = EROFS,
[FR_INVALID_DRIVE] = ENODEV,
[FR_NOT_ENABLED] = ENODEV,
[FR_NO_FILESYSTEM] = ENODEV,
[FR_MKFS_ABORTED] = EIO,
[FR_TIMEOUT] = EIO,
[FR_LOCKED] = EIO,
[FR_NOT_ENOUGH_CORE] = ENOMEM,
[FR_TOO_MANY_OPEN_FILES] = EMFILE,
[FR_INVALID_PARAMETER] = EINVAL,
};
enum FatObjectType { FO_Nil, FO_File, FO_Dir };
struct FatObject {
enum FatObjectType type;
union {
FIL file;
DIR dir;
};
};
static bool mount(void);
static int
get_fat_object(int i, enum FatObjectType expected, struct FatObject **res);
DIR dir;
FATFS FatFs;
static struct FatObject s_openedObjects[EPIC_FAT_MAX_OPENED];
#if (EPIC_FAT_STATIC_SEMAPHORE == 1)
StaticSemaphore_t xSemaphoreBuffer;
#endif
static volatile struct {
bool initiaized;
......@@ -64,6 +114,7 @@ static bool mount()
return true;
}
/*------------------------------------------------------------------------*/
/* Create a Synchronization Object */
/*------------------------------------------------------------------------*/
......@@ -79,7 +130,12 @@ static bool mount()
*/
int ff_cre_syncobj(BYTE vol, FF_SYNC_t *sobj)
{
#if (EPIC_FAT_STATIC_SEMAPHORE == 1)
*sobj = xSemaphoreCreateMutexStatic(&xSemaphoreBuffer);
#else
*sobj = xSemaphoreCreateMutex();
#endif //EPIC_FAT_STATIC_SEMAPHORE
return (int)(*sobj != NULL);
}
......@@ -98,7 +154,6 @@ int ff_cre_syncobj(BYTE vol, FF_SYNC_t *sobj)
*/
int ff_del_syncobj(FF_SYNC_t sobj)
{
printf("%s\n", __PRETTY_FUNCTION__);
/* FreeRTOS */
vSemaphoreDelete(sobj);
return 1;
......@@ -133,3 +188,158 @@ void ff_rel_grant(FF_SYNC_t sobj)
/* FreeRTOS */
xSemaphoreGive(sobj);
}
int get_fat_object(int i, enum FatObjectType expected, struct FatObject **res)
{
if (i < 0 || i >= EPIC_FAT_MAX_OPENED) {
*res = NULL;
return EBADF;
}
if (s_openedObjects[i].type != expected) {
*res = NULL;
return EBADF;
}
*res = &s_openedObjects[i];
return 0;
}
int epic_file_open(const char *filename, const char *modeString)
{
struct FatObject *o = NULL;
const char *mode_s = modeString;
int i;
int mode = 0;
//find free object to use
for (i = 0; i < EPIC_FAT_MAX_OPENED; ++i) {
if (s_openedObjects[i].type == FO_Nil) {
break;
}
}
if (i == EPIC_FAT_MAX_OPENED) {
return -fresult_to_errno_table[FR_TOO_MANY_OPEN_FILES];
}
o = &s_openedObjects[i];
while (*mode_s) {
switch (*mode_s++) {
case 'r':
mode |= FA_READ;
break;
case 'w':
mode |= FA_WRITE | FA_CREATE_ALWAYS;
break;
case 'x':
mode |= FA_WRITE | FA_CREATE_NEW;
break;
case 'a':
mode |= FA_WRITE | FA_OPEN_ALWAYS;
break;
case '+':
mode |= FA_READ | FA_WRITE;
break;
}
}
int res = f_open(&o->file, filename, mode);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
o->type = FO_File;
// for 'a' mode, we must begin at the end of the file
if ((mode & FA_OPEN_ALWAYS) != 0) {
f_lseek(&o->file, f_size(&o->file));
}
return i;
}
int epic_file_close(int fd)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -res;
}
res = f_close(&o->file);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
o->type = FO_Nil;
return 0;
}
int epic_file_read(int fd, void *buf, size_t nbytes)
{
unsigned int nread = 0;
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -res;
}
res = f_read(&o->file, buf, nbytes, &nread);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return nread;
}
int epic_file_write(int fd, const void *buf, size_t nbytes)
{
unsigned int nwritten = 0;
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -res;
}
res = f_write(&o->file, buf, nbytes, &nwritten);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return nwritten;
}
int epic_file_flush(int fd)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -res;
}
res = f_sync(&o->file);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return 0;
}
int epic_file_stat(const char *filename, epic_stat_t *stat)
{
int res;
FILINFO finfo;
res = f_stat(filename, &finfo);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
if (finfo.fattrib & AM_DIR) {
stat->type = EPICSTAT_DIR;
} else {
stat->type = EPICSTAT_FILE;
}
return 0;
}
#ifndef MODULES_H
#define MODULES_H
/* FatFS */
/* ---------- FAT fs ------------------------------------------------------ */
/* max no. of descriptors (file & directory) that can be open at a time */
#define EPIC_FAT_MAX_OPENED 16
#define EPIC_FAT_STATIC_SEMAPHORE 1
void fatfs_init(void);
/* ---------- Serial ------------------------------------------------------- */
......
......@@ -6,7 +6,9 @@ modsrc = files(
'modules/sys_display.c',
'modules/utime.c',
'modules/vibra.c',
'modules/light_sensor.c'
'modules/light_sensor.c',
'modules/fat_file.c',
'modules/fat_reader_import.c',
)
#################################
......
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 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 "py/mpconfig.h"
#include "py/runtime.h"
#include "py/builtin.h"
#include "py/stream.h"
#include "py/mperrno.h"
#include "epicardium.h"
extern const mp_obj_type_t mp_type_fat_textio;
#if MICROPY_PY_IO_FILEIO
extern const mp_obj_type_t mp_type_fat_fileio;
#endif
typedef struct _pyb_file_obj_t {
mp_obj_base_t base;
int fd;
} pyb_file_obj_t;
STATIC void
file_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind)
{
(void)kind;
mp_printf(
print,
"<io.%s %p>",
mp_obj_get_type_str(self_in),
MP_OBJ_TO_PTR(self_in)
);
}
STATIC mp_uint_t
file_obj_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode)
{
pyb_file_obj_t *self = MP_OBJ_TO_PTR(self_in);
int res = epic_file_read(self->fd, buf, size);
if (res < 0) {
*errcode = -res;
return MP_STREAM_ERROR;
}
return res;
}
STATIC mp_uint_t
file_obj_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode)
{
pyb_file_obj_t *self = MP_OBJ_TO_PTR(self_in);
int res = epic_file_write(self->fd, buf, size);
if (res < 0) {
*errcode = -res;
return MP_STREAM_ERROR;
}
return res;
}
STATIC mp_obj_t file_obj___exit__(size_t n_args, const mp_obj_t *args)
{
(void)n_args;
return mp_stream_close(args[0]);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
file_obj___exit___obj, 4, 4, file_obj___exit__
);
STATIC mp_uint_t
file_obj_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode)
{
pyb_file_obj_t *self = MP_OBJ_TO_PTR(o_in);
int res;
switch (request) {
case MP_STREAM_FLUSH:
res = epic_file_flush(self->fd);
if (res < 0) {
*errcode = -res;
return MP_STREAM_ERROR;
}
return 0;
case MP_STREAM_CLOSE:
res = epic_file_close(self->fd);
if (res < 0) {
*errcode = -res;
return MP_STREAM_ERROR;
}
return 0;
}
//every valid case returns either success or error, so this is EINVAL land:
*errcode = MP_EINVAL;
return MP_STREAM_ERROR;
// if (request == MP_STREAM_SEEK) {
// struct mp_stream_seek_t *s = (struct mp_stream_seek_t*)(uintptr_t)arg;
// switch (s->whence) {
// case 0: // SEEK_SET
// f_lseek(&self->fp, s->offset);
// break;
// case 1: // SEEK_CUR
// f_lseek(&self->fp, f_tell(&self->fp) + s->offset);
// break;
// case 2: // SEEK_END
// f_lseek(&self->fp, f_size(&self->fp) + s->offset);
// break;
// }
// s->offset = f_tell(&self->fp);
// return 0;
}
// Note: encoding is ignored for now; it's also not a valid kwarg for CPython's FileIO,
// but by adding it here we can use one single mp_arg_t array for open() and FileIO's constructor
STATIC const mp_arg_t file_open_args[] = {
{ MP_QSTR_file,
MP_ARG_OBJ | MP_ARG_REQUIRED,
{ .u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_mode, MP_ARG_OBJ, { .u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_r) } },
{ MP_QSTR_encoding,
MP_ARG_OBJ | MP_ARG_KW_ONLY,
{ .u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
};
#define FILE_OPEN_NUM_ARGS MP_ARRAY_SIZE(file_open_args)
STATIC mp_obj_t file_open(const mp_obj_type_t *type, mp_arg_val_t *args)
{
const char *modeString = mp_obj_str_get_str(args[1].u_obj);
const char *mode_s = modeString;
// modes r w x a + are handled on epicardium side, binary / text
// is relevant for python type so look for these here
while (*mode_s) {
switch (*mode_s++) {
#if MICROPY_PY_IO_FILEIO
case 'b':
type = &mp_type_fat_fileio;
break;
#endif
case 't':
type = &mp_type_fat_textio;
break;
}
}
pyb_file_obj_t *o = m_new_obj_with_finaliser(pyb_file_obj_t);
o->base.type = type;
const char *fname = mp_obj_str_get_str(args[0].u_obj);
int res = epic_file_open(fname, modeString);
if (res < 0) {
m_del_obj(pyb_file_obj_t, o);
mp_raise_OSError(-res);
}
o->fd = res;
return MP_OBJ_FROM_PTR(o);
}
STATIC mp_obj_t file_obj_make_new(
const mp_obj_type_t *type,
size_t n_args,
size_t n_kw,
const mp_obj_t *args
) {
mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS];
mp_arg_parse_all_kw_array(
n_args,
n_kw,
args,
FILE_OPEN_NUM_ARGS,
file_open_args,
arg_vals
);
return file_open(type, arg_vals);
}
// TODO gc hook to close the file if not already closed
STATIC const mp_rom_map_elem_t rawfile_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_readline),
MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
{ MP_ROM_QSTR(MP_QSTR_readlines),
MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) },
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
{ MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) },
{ MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) },
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) },
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&file_obj___exit___obj) },
};
STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table);
#if MICROPY_PY_IO_FILEIO
STATIC const mp_stream_p_t fileio_stream_p = {
.read = file_obj_read,
.write = file_obj_write,
.ioctl = file_obj_ioctl,
};
const mp_obj_type_t mp_type_fat_fileio = {
{ &mp_type_type },
.name = MP_QSTR_FileIO,
.print = file_obj_print,
.make_new = file_obj_make_new,
.getiter = mp_identity_getiter,
.iternext = mp_stream_unbuffered_iter,
.protocol = &fileio_stream_p,
.locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict,
};
#endif
STATIC const mp_stream_p_t textio_stream_p = {
.read = file_obj_read,
.write = file_obj_write,
.ioctl = file_obj_ioctl,
.is_text = true,
};
const mp_obj_type_t mp_type_fat_textio = {
{ &mp_type_type },
.name = MP_QSTR_TextIOWrapper,
.print = file_obj_print,
.make_new = file_obj_make_new,
.getiter = mp_identity_getiter,
.iternext = mp_stream_unbuffered_iter,
.protocol = &textio_stream_p,
.locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict,
};
// Factory function for I/O stream classes
mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs)
{
// TODO: analyze buffering args and instantiate appropriate type
mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS];
mp_arg_parse_all(
n_args,
args,
kwargs,
FILE_OPEN_NUM_ARGS,
file_open_args,
arg_vals
);
return file_open(&mp_type_fat_textio, arg_vals);
}
MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
#include "epicardium.h"
#include <py/runtime.h>
#include <py/reader.h>
#include <py/lexer.h>
/** ported from picropython's posic implementation */
typedef struct _mp_reader_epicfat_t {
bool close_fd;
int fd;
size_t len;
size_t pos;
byte buf[20];
} mp_reader_epicfat_t;
STATIC mp_uint_t mp_reader_epicfat_readbyte(void *data)
{
mp_reader_epicfat_t *reader = (mp_reader_epicfat_t *)data;
if (reader->pos >= reader->len) {
if (reader->len == 0) {
return MP_READER_EOF;
} else {
int n = epic_file_read(
reader->fd, reader->buf, sizeof(reader->buf)
);
if (n <= 0) {
reader->len = 0;
return MP_READER_EOF;
}
reader->len = n;
reader->pos = 0;
}
}
return reader->buf[reader->pos++];
}
STATIC void mp_reader_epicfat_close(void *data)
{
mp_reader_epicfat_t *reader = (mp_reader_epicfat_t *)data;
epic_file_close(reader->fd);
m_del_obj(mp_reader_epicfat_t, reader);
}
void mp_reader_new_file(mp_reader_t *reader, const char *filename)
{
int fd = epic_file_open(filename, "r");
if (fd < 0) {
mp_raise_OSError(-fd);
}
mp_reader_epicfat_t *rp = m_new_obj(mp_reader_epicfat_t);
rp->fd = fd;
int n = epic_file_read(rp->fd, rp->buf, sizeof(rp->buf));
if (n < 0) {
epic_file_close(fd);
}
rp->len = n;
rp->pos = 0;
reader->data = rp;
reader->readbyte = mp_reader_epicfat_readbyte;
reader->close = mp_reader_epicfat_close;
}
mp_lexer_t *mp_lexer_new_from_file(const char *filename)
{
mp_reader_t reader;
mp_reader_new_file(&reader, filename);
return mp_lexer_new(qstr_from_str(filename), reader);
}
mp_import_stat_t mp_import_stat(const char *path)
{
struct epic_stat_t stat;
if (epic_file_stat(path, &stat) == 0) {
if (stat.type == EPICSTAT_FILE) {
return MP_IMPORT_STAT_FILE;
} else {
return MP_IMPORT_STAT_DIR;
}
}
return MP_IMPORT_STAT_NO_EXIST;
}
......@@ -46,3 +46,24 @@ Q(light_sensor)
Q(start)
Q(get_reading)
Q(stop)
/* file */
Q(__del__)
Q(__enter__)
Q(__exit__)
Q(close)
Q(encoding)
Q(file)
Q(FileIO)
Q(flush)
Q(mode)
Q(r)
Q(read)
Q(readinto)
Q(readline)
Q(readlines)
Q(seek)
Q(tell)
Q(TextIOWrapper)
Q(write)
......@@ -76,3 +76,4 @@ typedef long mp_off_t;
/* For some reason, we need to define readline history manually */
#define MICROPY_PORT_ROOT_POINTERS \
const char *readline_hist[16];
......@@ -135,26 +135,3 @@ void NORETURN nlr_jump_fail(void *val)
Reset_Handler();
}
/******************************************************************************
* Stubs
*/
mp_lexer_t *mp_lexer_new_from_file(const char *filename)
{
/* TODO: Do we need an implementation for this? */
mp_raise_OSError(MP_ENOENT);
}
mp_import_stat_t mp_import_stat(const char *path)
{
return MP_IMPORT_STAT_NO_EXIST;
}
mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs)
{
/* TODO: Once fs is implemented, get this working as well */
mp_raise_NotImplementedError("FS is not yet implemented");
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment