feat: Add support for MicroPython

This commit is contained in:
Mike Wang
2025-10-08 04:51:20 +08:00
parent 46a38c3056
commit 702006488a
22 changed files with 5882 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/.vscode
/micropython/build
__pycache__

119
micropython/Makefile Normal file
View File

@@ -0,0 +1,119 @@
include deps.mk
PROG := natter
BUILD_DIR := $(CURDIR)/build
PATCH_DIR := $(CURDIR)/patch
COMP_DIR := $(BUILD_DIR)/compressed
MP_TAR := $(BUILD_DIR)/$(MICROPYTHON_TARNAME)
MP_SRC := $(BUILD_DIR)/$(MICROPYTHON_DIRNAME)
MP_PACTHED := $(MP_SRC)/.patched
LXP_TAR := $(BUILD_DIR)/$(LXPACK_TARNAME)
LXP_SRC := $(BUILD_DIR)/$(LXPACK_DIRNAME)
PROG_BIN := $(BUILD_DIR)/$(PROG)
COMP_BIN := $(COMP_DIR)/$(PROG)
MP_MAKE_EXTRA := \
MICROPY_STANDALONE=1 \
MICROPY_PY_BTREE=0 \
MICROPY_PY_FFI=0 \
MICROPY_PY_SSL=0 \
MICROPY_PY_TERMIOS=0 \
MICROPY_SSL_MBEDTLS=0 \
MICROPY_USE_READLINE=0 \
MICROPY_VFS_FAT=0 \
MICROPY_VFS_LFS1=0 \
MICROPY_VFS_LFS2=0 \
MICROPY_GIT_TAG='v$(MICROPYTHON_VERSION)-natter' \
MICROPY_GIT_HASH='<no hash>' \
SOURCE_DATE_EPOCH=0 \
HELP_BUILD_ERROR=
ifdef CROSS_COMPILE
MP_MAKE_EXTRA := $(MP_MAKE_EXTRA) CROSS_COMPILE='$(CROSS_COMPILE)'
endif
ifdef DEBUG
MP_MAKE_EXTRA := $(MP_MAKE_EXTRA) DEBUG='$(DEBUG)'
endif
ifdef V
MP_MAKE_EXTRA := $(MP_MAKE_EXTRA) V='$(V)'
endif
MP_CFLAGS_EXTRA := \
-frandom-seed=natter \
-Wdate-time \
-Wno-error=clobbered \
-DMICROPY_PY_SYS_TRACEBACKLIMIT=1 \
-DMICROPY_PY_RE_MATCH_GROUPS=1 \
-DMICROPY_PY_RE_MATCH_SPAN_START_END=1 \
-DMICROPY_NLR_SETJMP=1
ifeq ($(findstring m68k,$(shell $(CROSS_COMPILE)gcc -dumpmachine)),m68k)
MP_CFLAGS_EXTRA := $(MP_CFLAGS_EXTRA) -malign-int
endif
ifeq ($(STATIC),1)
MP_LDFLAGS_EXTRA := -static
endif
all: $(PROG_BIN)
download: $(MP_TAR) $(LXP_TAR)
compress: $(COMP_BIN)
clean:
$(RM) -r '$(MP_SRC)' '$(LXP_SRC)' '$(COMP_DIR)' '$(PROG_BIN)'
distclean:
$(RM) -r '$(BUILD_DIR)'
$(MP_TAR):
mkdir -p '$(BUILD_DIR)'
curl -Lfo '$@' '$(MICROPYTHON_SRCURL)'
echo '$(MICROPYTHON_SHA256) *$@' | sha256sum -c -
$(LXP_TAR):
mkdir -p '$(BUILD_DIR)'
curl -Lfo '$@' '$(LXPACK_SRCURL)'
echo '$(LXPACK_SHA256) *$@' | sha256sum -c -
untar: $(MP_TAR) $(LXP_TAR)
rm -rf '$(MP_SRC)' '$(LXP_SRC)'
cd '$(BUILD_DIR)' && tar xJf '$(MP_TAR)'
cd '$(BUILD_DIR)' && tar xJf '$(LXP_TAR)'
$(MP_PACTHED): $(wildcard $(PATCH_DIR)/*.patch)
test -d '$(MP_SRC)' || $(MAKE) untar
find '$(MP_SRC)' -type f -name '*.orig' -exec sh -c 'mv "$$1" "$${1%.orig}"' _ {} \;
for p in '$(PATCH_DIR)'/*.patch; do \
cd '$(MP_SRC)' && patch -bp1 < "$$p"; \
done
touch '$@'
patch: $(MP_PACTHED)
mpy_cross: $(MP_PACTHED)
$(MAKE) -C '$(MP_SRC)/mpy-cross' \
PROG=mpy-cross \
CROSS_COMPILE= \
CFLAGS_EXTRA= \
LDFLAGS_EXTRA=
$(PROG_BIN): mpy_cross
$(MAKE) -C '$(MP_SRC)/ports/unix' install \
VARIANT=standard \
BINDIR='$(BUILD_DIR)' \
PROG='$(PROG)' \
FROZEN_MANIFEST='$(CURDIR)/pymodule/_manifest.py' \
USER_C_MODULES='$(CURDIR)/cmodule' \
CFLAGS_EXTRA='$(MP_CFLAGS_EXTRA)' \
LDFLAGS_EXTRA='$(MP_LDFLAGS_EXTRA)' \
$(MP_MAKE_EXTRA)
$(COMP_BIN): $(PROG_BIN)
test -d '$(LXP_SRC)' || $(MAKE) untar
$(MAKE) -C '$(LXP_SRC)' install \
BINDIR='$(COMP_DIR)' \
PAYLOAD='$(PROG_BIN)'
.PHONY: all download untar patch mpy_cross compress clean

View File

@@ -0,0 +1,3 @@
UNIXAPI_MOD_DIR := $(USERMOD_DIR)
SRC_USERMOD += $(UNIXAPI_MOD_DIR)/mod_posix.c
CFLAGS_USERMOD += -I$(UNIXAPI_MOD_DIR)

File diff suppressed because it is too large Load Diff

11
micropython/deps.mk Normal file
View File

@@ -0,0 +1,11 @@
MICROPYTHON_VERSION = 1.26.1
MICROPYTHON_DIRNAME = micropython-$(MICROPYTHON_VERSION)
MICROPYTHON_TARNAME = $(MICROPYTHON_DIRNAME).tar.xz
MICROPYTHON_SRCURL = https://github.com/micropython/micropython/releases/download/v$(MICROPYTHON_VERSION)/$(MICROPYTHON_TARNAME)
MICROPYTHON_SHA256 = 12be6514df6272c0fcb328122b534af6b12abdd52435c19f40ee1707cc43ac98
LXPACK_VERSION = 1.0.0
LXPACK_DIRNAME = lxpack-$(LXPACK_VERSION)
LXPACK_TARNAME = $(LXPACK_DIRNAME).tar.xz
LXPACK_SRCURL = https://github.com/MikeWang000000/lxpack/releases/download/$(LXPACK_VERSION)/$(LXPACK_TARNAME)
LXPACK_SHA256 = 639414c7525b40dff12e4791e53e2d8d58d76acb9dc2ea660c371aae8eefe775

View File

@@ -0,0 +1,82 @@
diff --git a/ports/unix/main.c b/ports/unix/main.c
index 530e20a..c926e54 100644
--- a/ports/unix/main.c
+++ b/ports/unix/main.c
@@ -37,6 +37,7 @@
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
+#include <libgen.h>
#include "py/compile.h"
#include "py/runtime.h"
@@ -509,7 +510,25 @@ MP_NOINLINE int main_(int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
#endif
- pre_process_options(argc, argv);
+ int disable_natter = 0;
+
+ size_t argv0_len = strlen(argv[0]);
+ char *basebuf = malloc(argv0_len + 1);
+ if (!basebuf) {
+ perror(argv[0]);
+ return 1;
+ }
+ memcpy(basebuf, argv[0], argv0_len + 1);
+ char *progname = basename(basebuf);
+ if (strncmp(progname, "micropython", strlen("micropython")) == 0 ||
+ strncmp(progname, "python", strlen("python")) == 0) {
+ disable_natter = 1;
+ }
+ free(basebuf);
+
+ if (disable_natter) {
+ pre_process_options(argc, argv);
+ }
#if MICROPY_ENABLE_GC
#if !MICROPY_GC_SPLIT_HEAP
@@ -643,7 +662,41 @@ MP_NOINLINE int main_(int argc, char **argv) {
const int NOTHING_EXECUTED = -2;
int ret = NOTHING_EXECUTED;
bool inspect = false;
- for (int a = 1; a < argc; a++) {
+
+ if (!disable_natter) {
+ // Run Natter from frozen module 'natter'.
+ mp_sys_path = mp_obj_new_list(0, NULL);
+ #ifdef MICROPY_MODULE_FROZEN
+ mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__dot_frozen));
+ #endif
+
+ mp_obj_t import_args[4];
+ import_args[0] = MP_OBJ_NEW_QSTR(MP_QSTR_natter);
+ import_args[1] = mp_const_none;
+ import_args[2] = mp_const_none;
+ import_args[3] = mp_const_false;
+ set_sys_argv(argv, argc, 0);
+
+ #if MICROPY_GC_ALLOC_THRESHOLD
+ MP_STATE_MEM(gc_alloc_threshold) = 8 * 1024 * 1024 /
+ MICROPY_BYTES_PER_GC_BLOCK;
+ #endif
+
+ mp_hal_set_interrupt_char(CHAR_CTRL_C);
+ nlr_buf_t nlr;
+ if (nlr_push(&nlr) == 0) {
+ mp_builtin___import__(MP_ARRAY_SIZE(import_args), import_args);
+ mp_hal_set_interrupt_char(-1);
+ mp_handle_pending(true);
+ nlr_pop();
+ ret = 0;
+ } else {
+ // uncaught exception
+ mp_hal_set_interrupt_char(-1);
+ mp_handle_pending(false);
+ ret = handle_uncaught_exception(nlr.ret_val) & 0xff;
+ }
+ } else for (volatile int a = 1; a < argc; a++) {
if (argv[a][0] == '-') {
if (strcmp(argv[a], "-i") == 0) {
inspect = true;

View File

@@ -0,0 +1,19 @@
diff --git a/py/moderrno.c b/py/moderrno.c
index 58a141c1..995086db 100644
--- a/py/moderrno.c
+++ b/py/moderrno.c
@@ -102,6 +102,14 @@ const mp_obj_module_t mp_module_errno = {
MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_errno, mp_module_errno);
qstr mp_errno_to_str(mp_obj_t errno_val) {
+ // Query strerror() first to get a human-readable message
+ mp_int_t errno_int;
+ if (mp_obj_get_int_maybe(errno_val, &errno_int)) {
+ char *msg = strerror(errno_int);
+ if (msg) {
+ return qstr_from_str(msg);
+ }
+ }
#if MICROPY_PY_ERRNO_ERRORCODE
// We have the errorcode dict so can do a lookup using the hash map
mp_map_elem_t *elem = mp_map_lookup((mp_map_t *)&errorcode_dict.map, errno_val, MP_MAP_LOOKUP);

View File

@@ -0,0 +1,255 @@
A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
year, the PythonLabs team moved to Digital Creations (now Zope
Corporation, see http://www.zope.com). In 2001, the Python Software
Foundation (PSF, see http://www.python.org/psf/) was formed, a
non-profit organization created specifically to own Python-related
Intellectual Property. Zope Corporation is a sponsoring member of
the PSF.
All Python releases are Open Source (see http://www.opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
Release Derived Year Owner GPL-
from compatible? (1)
0.9.0 thru 1.2 1991-1995 CWI yes
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
1.6 1.5.2 2000 CNRI no
2.0 1.6 2000 BeOpen.com no
1.6.1 1.6 2001 CNRI yes (2)
2.1 2.0+1.6.1 2001 PSF no
2.0.1 2.0+1.6.1 2001 PSF yes
2.1.1 2.1+2.0.1 2001 PSF yes
2.1.2 2.1.1 2002 PSF yes
2.1.3 2.1.2 2002 PSF yes
2.2 and above 2.1.1 2001-now PSF yes
Footnotes:
(1) GPL-compatible doesn't mean that we're distributing Python under
the GPL. All Python licenses, unlike the GPL, let you distribute
a modified version without making your changes open source. The
GPL-compatible licenses make it possible to combine Python with
other software that is released under the GPL; the others don't.
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
because its license has a choice of law clause. According to
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
is "not incompatible" with the GPL.
Thanks to the many outside volunteers who have worked under Guido's
direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Python Software
Foundation; All Rights Reserved" are retained in Python alone or in any
derivative version prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
-------------------------------------------
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
Individual or Organization ("Licensee") accessing and otherwise using
this software in source or binary form and its associated
documentation ("the Software").
2. Subject to the terms and conditions of this BeOpen Python License
Agreement, BeOpen hereby grants Licensee a non-exclusive,
royalty-free, world-wide license to reproduce, analyze, test, perform
and/or display publicly, prepare derivative works, distribute, and
otherwise use the Software alone or in any derivative version,
provided, however, that the BeOpen Python License is retained in the
Software, alone or in any derivative version prepared by Licensee.
3. BeOpen is making the Software available to Licensee on an "AS IS"
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
5. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
6. This License Agreement shall be governed by and interpreted in all
respects by the law of the State of California, excluding conflict of
law provisions. Nothing in this License Agreement shall be deemed to
create any relationship of agency, partnership, or joint venture
between BeOpen and Licensee. This License Agreement does not grant
permission to use BeOpen trademarks or trade names in a trademark
sense to endorse or promote products or services of Licensee, or any
third party. As an exception, the "BeOpen Python" logos available at
http://www.pythonlabs.com/logos.html may be used according to the
permissions granted on that web page.
7. By copying, installing or otherwise using the software, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
---------------------------------------
1. This LICENSE AGREEMENT is between the Corporation for National
Research Initiatives, having an office at 1895 Preston White Drive,
Reston, VA 20191 ("CNRI"), and the Individual or Organization
("Licensee") accessing and otherwise using Python 1.6.1 software in
source or binary form and its associated documentation.
2. Subject to the terms and conditions of this License Agreement, CNRI
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 1.6.1
alone or in any derivative version, provided, however, that CNRI's
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
1995-2001 Corporation for National Research Initiatives; All Rights
Reserved" are retained in Python 1.6.1 alone or in any derivative
version prepared by Licensee. Alternately, in lieu of CNRI's License
Agreement, Licensee may substitute the following text (omitting the
quotes): "Python 1.6.1 is made available subject to the terms and
conditions in CNRI's License Agreement. This Agreement together with
Python 1.6.1 may be located on the Internet using the following
unique, persistent identifier (known as a handle): 1895.22/1013. This
Agreement may also be obtained from a proxy server on the Internet
using the following URL: http://hdl.handle.net/1895.22/1013".
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 1.6.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 1.6.1.
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. This License Agreement shall be governed by the federal
intellectual property law of the United States, including without
limitation the federal copyright law, and, to the extent such
U.S. federal law does not apply, by the law of the Commonwealth of
Virginia, excluding Virginia's conflict of law provisions.
Notwithstanding the foregoing, with regard to derivative works based
on Python 1.6.1 that incorporate non-separable material that was
previously distributed under the GNU General Public License (GPL), the
law of the Commonwealth of Virginia shall govern this License
Agreement only as to issues arising under or with respect to
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
License Agreement shall be deemed to create any relationship of
agency, partnership, or joint venture between CNRI and Licensee. This
License Agreement does not grant permission to use CNRI trademarks or
trade name in a trademark sense to endorse or promote products or
services of Licensee, or any third party.
8. By clicking on the "ACCEPT" button where indicated, or by copying,
installing or otherwise using Python 1.6.1, Licensee agrees to be
bound by the terms and conditions of this License Agreement.
ACCEPT
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
--------------------------------------------------
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
The Netherlands. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,13 @@
def __manifest_add_modules():
import os
for name in os.listdir('.'):
if name.endswith('.py') and \
not name.startswith('.') and \
not name.startswith('_'):
module(name)
module('natter.py', base_path='../..')
__manifest_add_modules()
del __manifest_add_modules

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
# This module provides partial functionality of the CPython "atexit" module for
# MicroPython.
import sys as _usys
__all__ = [
'register', 'unregister'
]
class _AtExit(object):
def __init__(self):
self.regs = []
_usys.atexit(self._onexit)
def register(self, func, *args, **kwargs):
self.regs.append((func, args, kwargs))
return func
def unregister(self, func):
regs_new = []
for reg in self.regs:
if reg[0] != func:
regs_new.append(reg)
self.regs = regs_new
def _onexit(self):
last_ex = None
for func, args, kwargs in reversed(self.regs):
try:
func(*args, **kwargs)
except Exception as ex:
last_ex = ex
_usys.print_exception(ex, _usys.stderr)
if last_ex:
raise last_ex from None
_atexit = _AtExit()
register = _atexit.register
unregister = _atexit.unregister

View File

@@ -0,0 +1,38 @@
# This module provides partial functionality of the CPython "codecs" module for
# MicroPython.
__all__ = [
'CodecInfo', 'lookup', 'register', 'ascii_encode', 'ascii_decode'
]
class CodecInfo(object):
def __init__(self, *args, **kwargs):
pass
_dummy = CodecInfo()
def lookup(encoding):
return _dummy
def register(search_function):
pass
def ascii_encode(string, errors=None):
if errors is not None:
data = string.encode('ascii', errors)
else:
data = string.encode('ascii')
return data, len(data)
def ascii_decode(data, errors=None):
if errors is not None:
string = data.decode('ascii', errors)
else:
string = data.decode('ascii')
return string, len(string)

View File

@@ -0,0 +1,93 @@
# This module provides partial functionality of the CPython "errno" module for
# MicroPython.
import _posix
E2BIG = _posix.E2BIG
EACCES = _posix.EACCES
EADDRINUSE = _posix.EADDRINUSE
EADDRNOTAVAIL = _posix.EADDRNOTAVAIL
EAFNOSUPPORT = _posix.EAFNOSUPPORT
EAGAIN = _posix.EAGAIN
EALREADY = _posix.EALREADY
EBADF = _posix.EBADF
EBADMSG = _posix.EBADMSG
EBUSY = _posix.EBUSY
ECANCELED = _posix.ECANCELED
ECHILD = _posix.ECHILD
ECONNABORTED = _posix.ECONNABORTED
ECONNREFUSED = _posix.ECONNREFUSED
ECONNRESET = _posix.ECONNRESET
EDEADLK = _posix.EDEADLK
EDESTADDRREQ = _posix.EDESTADDRREQ
EDOM = _posix.EDOM
EDQUOT = _posix.EDQUOT
EEXIST = _posix.EEXIST
EFAULT = _posix.EFAULT
EFBIG = _posix.EFBIG
EHOSTUNREACH = _posix.EHOSTUNREACH
EIDRM = _posix.EIDRM
EILSEQ = _posix.EILSEQ
EINPROGRESS = _posix.EINPROGRESS
EINTR = _posix.EINTR
EINVAL = _posix.EINVAL
EIO = _posix.EIO
EISCONN = _posix.EISCONN
EISDIR = _posix.EISDIR
ELOOP = _posix.ELOOP
EMFILE = _posix.EMFILE
EMLINK = _posix.EMLINK
EMSGSIZE = _posix.EMSGSIZE
EMULTIHOP = _posix.EMULTIHOP
ENAMETOOLONG = _posix.ENAMETOOLONG
ENETDOWN = _posix.ENETDOWN
ENETRESET = _posix.ENETRESET
ENETUNREACH = _posix.ENETUNREACH
ENFILE = _posix.ENFILE
ENOBUFS = _posix.ENOBUFS
ENODATA = _posix.ENODATA
ENODEV = _posix.ENODEV
ENOENT = _posix.ENOENT
ENOEXEC = _posix.ENOEXEC
ENOLCK = _posix.ENOLCK
ENOLINK = _posix.ENOLINK
ENOMEM = _posix.ENOMEM
ENOMSG = _posix.ENOMSG
ENOPROTOOPT = _posix.ENOPROTOOPT
ENOSPC = _posix.ENOSPC
ENOSR = _posix.ENOSR
ENOSTR = _posix.ENOSTR
ENOSYS = _posix.ENOSYS
ENOTCONN = _posix.ENOTCONN
ENOTDIR = _posix.ENOTDIR
ENOTEMPTY = _posix.ENOTEMPTY
ENOTRECOVERABLE = _posix.ENOTRECOVERABLE
ENOTSOCK = _posix.ENOTSOCK
ENOTSUP = _posix.ENOTSUP
ENOTTY = _posix.ENOTTY
ENXIO = _posix.ENXIO
EOPNOTSUPP = _posix.EOPNOTSUPP
EOVERFLOW = _posix.EOVERFLOW
EOWNERDEAD = _posix.EOWNERDEAD
EPERM = _posix.EPERM
EPIPE = _posix.EPIPE
EPROTO = _posix.EPROTO
EPROTONOSUPPORT = _posix.EPROTONOSUPPORT
EPROTOTYPE = _posix.EPROTOTYPE
ERANGE = _posix.ERANGE
EROFS = _posix.EROFS
ESPIPE = _posix.ESPIPE
ESRCH = _posix.ESRCH
ESTALE = _posix.ESTALE
ETIME = _posix.ETIME
ETIMEDOUT = _posix.ETIMEDOUT
ETXTBSY = _posix.ETXTBSY
EWOULDBLOCK = _posix.EWOULDBLOCK
EXDEV = _posix.EXDEV
errorcode = dict()
for name, value in locals().items():
if name.startswith('E') and isinstance(value, int):
errorcode[value] = name

View File

@@ -0,0 +1,134 @@
# Modified from CPython 3.4.10 "genericpath" module for MicroPython
"""
Path operations common to more than one OS
Do not use directly. The OS specific modules import the appropriate
functions from this module themselves.
"""
import _posix
import os as _zos
__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile',
'samestat']
# Does a path exist?
# This is false for dangling symbolic links on systems that support them.
def exists(path):
"""Test whether a path exists. Returns False for broken symbolic links"""
try:
_zos.stat(path)
except OSError:
return False
return True
# This follows symbolic links, so both islink() and isdir() can be true
# for the same path on systems that support symlinks
def isfile(path):
"""Test whether a path is a regular file"""
try:
st = _zos.stat(path)
except OSError:
return False
return _posix.S_ISREG(st.st_mode)
# Is a path a directory?
# This follows symbolic links, so both islink() and isdir()
# can be true for the same path on systems that support symlinks
def isdir(s):
"""Return true if the pathname refers to an existing directory."""
try:
st = _zos.stat(s)
except OSError:
return False
return _posix.S_ISDIR(st.st_mode)
def getsize(filename):
"""Return the size of a file, reported by os.stat()."""
return _zos.stat(filename).st_size
def getmtime(filename):
"""Return the last modification time of a file, reported by os.stat()."""
return _zos.stat(filename).st_mtime
def getatime(filename):
"""Return the last access time of a file, reported by os.stat()."""
return _zos.stat(filename).st_atime
def getctime(filename):
"""Return the metadata change time of a file, reported by os.stat()."""
return _zos.stat(filename).st_ctime
# Return the longest prefix of all list elements.
def commonprefix(m):
"Given a list of pathnames, returns the longest common leading component"
if not m: return ''
s1 = min(m)
s2 = max(m)
for i, c in enumerate(s1):
if c != s2[i]:
return s1[:i]
return s1
# Are two stat buffers (obtained from stat, fstat or lstat)
# describing the same file?
def samestat(s1, s2):
"""Test whether two stat buffers reference the same file"""
return (s1.st_ino == s2.st_ino and
s1.st_dev == s2.st_dev)
# Are two filenames really pointing to the same file?
def samefile(f1, f2):
"""Test whether two pathnames reference the same actual file"""
s1 = _zos.stat(f1)
s2 = _zos.stat(f2)
return samestat(s1, s2)
# Are two open files really referencing the same file?
# (Not necessarily the same file descriptor!)
def sameopenfile(fp1, fp2):
"""Test whether two open file objects reference the same file"""
s1 = _zos.fstat(fp1)
s2 = _zos.fstat(fp2)
return samestat(s1, s2)
# Split a path in root and extension.
# The extension is everything starting at the last dot in the last
# pathname component; the root is everything before that.
# It is always true that root + ext == p.
# Generic implementation of splitext, to be parametrized with
# the separators
def _splitext(p, sep, altsep, extsep):
"""Split the extension from a pathname.
Extension is everything from the last dot to the end, ignoring
leading dots. Returns "(root, ext)"; ext may be empty."""
# NOTE: This code must work for text and bytes strings.
sepIndex = p.rfind(sep)
if altsep:
altsepIndex = p.rfind(altsep)
sepIndex = max(sepIndex, altsepIndex)
dotIndex = p.rfind(extsep)
if dotIndex > sepIndex:
# skip all leading dots
filenameIndex = sepIndex + 1
while filenameIndex < dotIndex:
if p[filenameIndex:filenameIndex+1] != extsep:
return p[:dotIndex], p[dotIndex:]
filenameIndex += 1
return p, p[:0]

163
micropython/pymodule/os.py Normal file
View File

@@ -0,0 +1,163 @@
# This module provides partial functionality of the CPython "os" module for
# MicroPython.
import sys as _sys
_path = _sys.path
_sys.path = ()
try:
import os as _uos
finally:
_sys.path = _path
del _path
import _posix
import posixpath as path
__all__ = [
'_Environ', 'altsep', 'chdir', 'curdir', 'defpath', 'devnull', 'environ',
'extsep', 'getcwd', 'getcwdb', 'getenv', 'linesep', 'listdir', 'lstat',
'mkdir', 'mount', 'name', 'pardir', 'path', 'pathsep', 'putenv', 'remove',
'rename', 'rmdir', 'sep', 'stat', 'stat_result', 'system', 'umount',
'uname', 'uname_result', 'unlink', 'unsetenv', 'urandom'
]
class _Environ(dict):
def __setitem__(self, key, value):
_uos.putenv(key, value)
super().__setitem__(key, value)
def __delitem__(self, key):
_uos.unsetenv(key)
super().__delitem__(key)
def __repr__(self):
return 'environ({%s})' % (
', '.join(
'%s: %s' % (repr(key), repr(value))
for key, value in self.items()
)
)
def _createenviron():
envdict = dict()
envlist = _posix._environ()
for env in envlist:
kv = env.split('=', 1)
if len(kv) < 2:
envdict[kv[0]] = ''
else:
envdict[kv[0]] = kv[1]
return _Environ(envdict)
# unicode environ
environ = _createenviron()
del _createenviron
class stat_result(tuple):
# MicroPython note:
# tuple subclass data is passed to __init__ in MicroPython, not __new__,
# so attributes are set here.
def __init__(self, t):
super().__init__(t)
self.st_mode = t[0]
self.st_ino = t[1]
self.st_dev = t[2]
self.st_nlink = t[3]
self.st_uid = t[4]
self.st_gid = t[5]
self.st_size = t[6]
self.st_atime = t[7]
self.st_mtime = t[8]
self.st_ctime = t[9]
self.__frozen__ = True
def __setattr__(self, name, value):
if hasattr(self, '__frozen__') and self.__frozen__:
raise AttributeError("Assignment is not allowed")
def __repr__(self):
return "stat_result(%s)" % repr(super())
class uname_result(tuple):
# MicroPython note:
# tuple subclass data is passed to __init__ in MicroPython, not __new__,
# so attributes are set here.
def __init__(self, t):
super().__init__(t)
self.sysname = t[0]
self.nodename = t[1]
self.release = t[2]
self.version = t[3]
self.machine = t[4]
self.__frozen__ = True
def __setattr__(self, name, value):
if hasattr(self, '__frozen__') and self.__frozen__:
raise AttributeError("Assignment is not allowed")
def __repr__(self):
return "uname_result(%s)" % repr(super())
name = 'posix'
linesep = '\n'
curdir = path.curdir
pardir = path.pardir
extsep = path.extsep
sep = path.sep
pathsep = path.pathsep
defpath = path.defpath
altsep = path.altsep
devnull = path.devnull
chdir = _uos.chdir
getcwd = _uos.getcwd
listdir = _uos.listdir
mkdir = _uos.mkdir
mount = _uos.mount
putenv = _uos.putenv
remove = _uos.remove
rename = _uos.rename
rmdir = _uos.rmdir
system = _uos.system
umount = _uos.umount
unlink = _uos.unlink
unsetenv = _uos.unsetenv
urandom = _uos.urandom
isatty = _posix.isatty
getuid = _posix.getuid
def getcwdb():
return _uos.getcwd().encode()
def getenv(key, default=None):
return _uos.getenv(key, default)
def stat(path):
result = _posix.stat(path)
return stat_result(result)
def lstat(path):
result = _posix.lstat(path)
return stat_result(result)
def uname():
result = _posix.uname()
return uname_result(result)
def strerror(code):
return _posix.strerror(code)

View File

@@ -0,0 +1,361 @@
# Modified from CPython 3.4.10 "posixpath" module for MicroPython
"""Common operations on Posix pathnames.
Instead of importing this module directly, import os and refer to
this module as os.path. The "os.path" name is an alias for this
module on Posix systems; on other systems (e.g. Mac, Windows),
os.path provides the same operations in a manner specific to that
platform, and is an alias to another module (e.g. macpath, ntpath).
Some of this can actually be useful on non-Posix systems too, e.g.
for manipulation of the pathname component of URLs.
"""
import sys as _usys
import _posix
import os as _zos
import genericpath as _zgenericpath
from genericpath import *
__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
"basename","dirname","commonprefix","getsize","getmtime",
"getatime","getctime","islink","exists","lexists","isdir","isfile",
"ismount", "normpath","abspath", "samefile","sameopenfile",
"samestat","curdir","pardir","sep","pathsep","defpath","altsep",
"extsep","devnull","realpath","supports_unicode_filenames",
"relpath"]
# Strings representing various path-related bits and pieces.
# These are primarily for export; internally, they are hardcoded.
curdir = '.'
pardir = '..'
extsep = '.'
sep = '/'
pathsep = ':'
defpath = ':/bin:/usr/bin'
altsep = None
devnull = '/dev/null'
def _get_sep(path):
if isinstance(path, bytes):
return b'/'
else:
return '/'
# Normalize the case of a pathname. Trivial in Posix, string.lower on Mac.
# On MS-DOS this may also turn slashes into backslashes; however, other
# normalizations (such as optimizing '../' away) are not allowed
# (another function should be defined to do that).
def normcase(s):
"""Normalize case of pathname. Has no effect under Posix"""
if not isinstance(s, (bytes, str)):
raise TypeError("normcase() argument must be str or bytes, "
"not '{}'".format(s.__class__.__name__))
return s
# Return whether a path is absolute.
# Trivial in Posix, harder on the Mac or MS-DOS.
def isabs(s):
"""Test whether a path is absolute"""
sep = _get_sep(s)
return s.startswith(sep)
# Join pathnames.
# Ignore the previous parts if a part is absolute.
# Insert a '/' unless the first part is empty or already ends in '/'.
def join(a, *p):
"""Join two or more pathname components, inserting '/' as needed.
If any component is an absolute path, all previous path components
will be discarded. An empty last part will result in a path that
ends with a separator."""
sep = _get_sep(a)
path = a
try:
for b in p:
if b.startswith(sep):
path = b
elif not path or path.endswith(sep):
path += b
else:
path += sep + b
except TypeError:
if all(isinstance(s, (str, bytes)) for s in (a,) + p):
# Must have a mixture of text and binary data
raise TypeError("Can't mix strings and bytes in path "
"components") from None
raise
return path
# Split a path in head (everything up to the last '/') and tail (the
# rest). If the path ends in '/', tail will be empty. If there is no
# '/' in the path, head will be empty.
# Trailing '/'es are stripped from head unless it is the root.
def split(p):
"""Split a pathname. Returns tuple "(head, tail)" where "tail" is
everything after the final slash. Either part may be empty."""
sep = _get_sep(p)
i = p.rfind(sep) + 1
head, tail = p[:i], p[i:]
if head and head != sep*len(head):
head = head.rstrip(sep)
return head, tail
# Split a path in root and extension.
# The extension is everything starting at the last dot in the last
# pathname component; the root is everything before that.
# It is always true that root + ext == p.
def splitext(p):
if isinstance(p, bytes):
sep = b'/'
extsep = b'.'
else:
sep = '/'
extsep = '.'
return _zgenericpath._splitext(p, sep, None, extsep)
# Split a pathname into a drive specification and the rest of the
# path. Useful on DOS/Windows/NT; on Unix, the drive is always empty.
def splitdrive(p):
"""Split a pathname into drive and path. On Posix, drive is always
empty."""
return p[:0], p
# Return the tail (basename) part of a path, same as split(path)[1].
def basename(p):
"""Returns the final component of a pathname"""
sep = _get_sep(p)
i = p.rfind(sep) + 1
return p[i:]
# Return the head (dirname) part of a path, same as split(path)[0].
def dirname(p):
"""Returns the directory component of a pathname"""
sep = _get_sep(p)
i = p.rfind(sep) + 1
head = p[:i]
if head and head != sep*len(head):
head = head.rstrip(sep)
return head
# Is a path a symbolic link?
# This will always return false on systems where os.lstat doesn't exist.
def islink(path):
"""Test whether a path is a symbolic link"""
try:
st = _zos.lstat(path)
except (OSError, AttributeError):
return False
return _posix.S_ISLNK(st.st_mode)
# Being true for dangling symbolic links is also useful.
def lexists(path):
"""Test whether a path exists. Returns True for broken symbolic links"""
try:
_zos.lstat(path)
except OSError:
return False
return True
# Is a path a mount point?
# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
def ismount(path):
"""Test whether a path is a mount point"""
try:
s1 = _zos.lstat(path)
except OSError:
# It doesn't exist -- so not a mount point. :-)
return False
else:
# A symlink can never be a mount point
if _posix.S_ISLNK(s1.st_mode):
return False
if isinstance(path, bytes):
parent = join(path, b'..')
else:
parent = join(path, '..')
try:
s2 = _zos.lstat(parent)
except OSError:
return False
dev1 = s1.st_dev
dev2 = s2.st_dev
if dev1 != dev2:
return True # path/.. on a different device as path
ino1 = s1.st_ino
ino2 = s2.st_ino
if ino1 == ino2:
return True # path/.. is the same i-node as path
return False
# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
# It should be understood that this may change the meaning of the path
# if it contains symbolic links!
def normpath(path):
"""Normalize path, eliminating double slashes, etc."""
if isinstance(path, bytes):
sep = b'/'
empty = b''
dot = b'.'
dotdot = b'..'
else:
sep = '/'
empty = ''
dot = '.'
dotdot = '..'
if path == empty:
return dot
initial_slashes = path.startswith(sep)
# POSIX allows one or two initial slashes, but treats three or more
# as single slash.
if (initial_slashes and
path.startswith(sep*2) and not path.startswith(sep*3)):
initial_slashes = 2
comps = path.split(sep)
new_comps = []
for comp in comps:
if comp in (empty, dot):
continue
if (comp != dotdot or (not initial_slashes and not new_comps) or
(new_comps and new_comps[-1] == dotdot)):
new_comps.append(comp)
elif new_comps:
new_comps.pop()
comps = new_comps
path = sep.join(comps)
if initial_slashes:
path = sep*initial_slashes + path
return path or dot
def abspath(path):
"""Return an absolute path."""
if not isabs(path):
if isinstance(path, bytes):
cwd = _zos.getcwdb()
else:
cwd = _zos.getcwd()
path = join(cwd, path)
return normpath(path)
# Return a canonical path (i.e. the absolute location of a file on the
# filesystem).
def realpath(filename):
"""Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path."""
path, ok = _joinrealpath(filename[:0], filename, {})
return abspath(path)
# Join two paths, normalizing ang eliminating any symbolic links
# encountered in the second path.
def _joinrealpath(path, rest, seen):
if isinstance(path, bytes):
sep = b'/'
curdir = b'.'
pardir = b'..'
else:
sep = '/'
curdir = '.'
pardir = '..'
if isabs(rest):
rest = rest[1:]
path = sep
while rest:
name, _, rest = rest.partition(sep)
if not name or name == curdir:
# current dir
continue
if name == pardir:
# parent dir
if path:
path, name = split(path)
if name == pardir:
path = join(path, pardir, pardir)
else:
path = pardir
continue
newpath = join(path, name)
if not islink(newpath):
path = newpath
continue
# Resolve the symbolic link
if newpath in seen:
# Already seen this path
path = seen[newpath]
if path is not None:
# use cached value
continue
# The symlink is not resolved, so we must have a symlink loop.
# Return already resolved part + rest of the path unchanged.
return join(newpath, rest), False
seen[newpath] = None # not resolved symlink
linkdst = _posix.readlink(newpath)
if isinstance(path, bytes) and isinstance(linkdst, str):
linkdst = linkdst.encode()
path, ok = _joinrealpath(path, linkdst, seen)
if not ok:
return join(path, rest), False
seen[newpath] = path # resolved symlink
return path, True
supports_unicode_filenames = (_usys.platform == 'darwin')
def relpath(path, start=None):
"""Return a relative version of a path"""
if not path:
raise ValueError("no path specified")
if isinstance(path, bytes):
curdir = b'.'
sep = b'/'
pardir = b'..'
else:
curdir = '.'
sep = '/'
pardir = '..'
if start is None:
start = curdir
start_list = [x for x in abspath(start).split(sep) if x]
path_list = [x for x in abspath(path).split(sep) if x]
# Work out how much of the filepath is shared by start and path.
i = len(commonprefix([start_list, path_list]))
rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
if not rel_list:
return curdir
return join(*rel_list)

138
micropython/pymodule/re.py Normal file
View File

@@ -0,0 +1,138 @@
# This module provides partial functionality of the CPython "re" module for
# MicroPython.
# Notes:
# This module uses MicroPython's builtin regex engine "re1.5".
# Regular expression syntax supported is a subset of CPython re module.
import sys as _sys
_path = _sys.path
_sys.path = ()
try:
import re as _ure
finally:
_sys.path = _path
del _path
__all__ = [
'Match', 'Pattern', 'compile', 'findall', 'match', 'search', 'split', 'sub'
]
_default = object()
class Pattern(object):
_Pattern = type(_ure.compile(''))
def __init__(self, p, pstring, flags):
if not isinstance(p, Pattern._Pattern):
raise TypeError
self._p = p
self.pattern = pstring
self.flags = flags
def search(self, string, pos=0, endpos=_default):
if endpos is _default:
m = self._p.search(string, pos)
else:
m = self._p.search(string, pos, endpos)
if m:
return Match(m, self, string)
def match(self, string, pos=0, endpos=_default):
if endpos is _default:
m = self._p.match(string, pos)
else:
m = self._p.match(string, pos, endpos)
if m:
return Match(m, self, string)
def split(self, string, maxsplit=0):
return self._p.split(string, maxsplit)
def sub(self, repl, string, count=0):
if callable(repl):
return self._p.sub(lambda m: repl(Match(m, self, string)), string, count)
else:
return self._p.sub(repl, string, count)
def findall(self, string, pos=0, endpos=_default):
all = []
def cb(m):
groups = m.groups()
if groups and len(groups) > 2:
all.append(groups)
else:
all.append(m.group(0))
return type(m.string)()
if endpos is not _default:
if endpos < pos:
endpos = pos
if pos < 0:
pos = 0
string = string[pos:endpos]
elif pos > 0:
string = string[pos:]
self.sub(cb, string)
return all
class Match(object):
_Match = type(_ure.match('',''))
def __init__(self, m, re, string):
if not isinstance(m, Match._Match):
raise TypeError
if not isinstance(re, Pattern):
raise TypeError
self._m = m
self.re = re
self.string = string
def group(self, *args):
if not args:
return self._m.group(0)
elif len(args) == 1:
return self._m.group(args[0])
glist = []
for idx in args:
glist.append(self._m.group(idx))
return tuple(glist)
def groups(self, default=None):
if default is None:
return self._m.groups()
else:
glist = list(self._m.groups())
for i, value in enumerate(glist):
if value is None:
glist[i] = default
return tuple(glist)
def compile(pattern, flags=0):
if isinstance(pattern, Pattern):
return compile(pattern.pattern, flags)
p = _ure.compile(pattern, flags)
return Pattern(p, pattern, flags)
def search(pattern, string, flags=0):
return compile(pattern, flags).search(string)
def match(pattern, string, flags=0):
return compile(pattern, flags).match(string)
def split(pattern, string, maxsplit=0, flags=0):
return compile(pattern, flags).split(string, maxsplit)
def sub(pattern, repl, string, count=0, flags=0):
return compile(pattern, flags).sub(repl, string, count)
def findall(pattern, string, flags=0):
return compile(pattern, flags).findall(string)

View File

@@ -0,0 +1,39 @@
# This module provides partial functionality of the CPython "signal" module for
# MicroPython.
import _posix
def signal(signalnum, handler):
return _posix.signal(signalnum, handler)
SIG_IGN = _posix.SIG_IGN
SIG_DFL = _posix.SIG_DFL
SIGABRT = _posix.SIGABRT
SIGALRM = _posix.SIGALRM
SIGBUS = _posix.SIGBUS
SIGCHLD = _posix.SIGCHLD
SIGCONT = _posix.SIGCONT
SIGFPE = _posix.SIGFPE
SIGHUP = _posix.SIGHUP
SIGILL = _posix.SIGILL
SIGINT = _posix.SIGINT
SIGKILL = _posix.SIGKILL
SIGPIPE = _posix.SIGPIPE
SIGQUIT = _posix.SIGQUIT
SIGSEGV = _posix.SIGSEGV
SIGSTOP = _posix.SIGSTOP
SIGTERM = _posix.SIGTERM
SIGTSTP = _posix.SIGTSTP
SIGTTIN = _posix.SIGTTIN
SIGTTOU = _posix.SIGTTOU
SIGUSR1 = _posix.SIGUSR1
SIGUSR2 = _posix.SIGUSR2
SIGPOLL = _posix.SIGPOLL
SIGPROF = _posix.SIGPROF
SIGSYS = _posix.SIGSYS
SIGTRAP = _posix.SIGTRAP
SIGURG = _posix.SIGURG
SIGVTALRM = _posix.SIGVTALRM
SIGXCPU = _posix.SIGXCPU
SIGXFSZ = _posix.SIGXFSZ

View File

@@ -0,0 +1,266 @@
# This module provides partial functionality of the CPython "socket" module for
# MicroPython.
import sys as _sys
_path = _sys.path
_sys.path = ()
try:
import socket as _usocket
finally:
_sys.path = _path
del _path
import _posix
class TimeoutError(OSError):
pass
class gaierror(OSError):
def __init__(self, *args):
if len(args) == 1 and isinstance(args[0], int):
errno = args[0]
msg = "[Errno %d] %s" %(errno, _posix.gai_strerror(args[0]))
super().__init__(msg)
self.errno = errno
else:
super().__init__(*args)
class socket(object):
def __init__(self, family=_posix.AF_INET, type=_posix.SOCK_STREAM,
proto=0, *, _sock=None):
if _sock is not None:
self._s = _sock
else:
self._s = _usocket.socket(family, type, proto)
self.family = family
self.type = type
self.proto = proto
self._closed = False
def __enter__(self):
return self
def __exit__(self, *args):
if not self._closed:
self.close()
def _is_timeouterror(self, oserr):
return oserr.errno in [_posix.ETIMEDOUT, _posix.EAGAIN]
def _arg_addr(self, addr):
host, port = addr
if host == '':
if self.family == _posix.AF_INET:
host = '0.0.0.0'
elif self.family == _posix.AF_INET6:
host = '::'
if isinstance(port, int):
port = str(port)
return _getaddrinfo(
host, port, self.family, self.type, self.proto,
_posix.AI_NUMERICSERV
)[0][4]
def _ret_addr(self, addr):
host, serv = _getnameinfo(
addr, _posix.NI_NUMERICHOST | _posix.NI_NUMERICSERV
)
return host, int(serv)
def fileno(self):
if self._closed:
return -1
return self._s.fileno()
def bind(self, addr):
addr = self._arg_addr(addr)
self._s.bind(addr)
def listen(self, backlog):
self._s.listen(backlog)
def accept(self):
s, addr = self._s.accept()
s = socket(self.family, self.type, self.proto, _sock=s)
addr = self._ret_addr(addr)
return s, addr
def connect(self, addr):
addr = self._arg_addr(addr)
try:
self._s.connect(addr)
except OSError as ex:
if self._is_timeouterror(ex):
raise timeout(ex) from None
raise
def connect_ex(self, addr):
try:
self.connect(addr)
except OSError as ex:
return ex.errno
else:
return 0
def send(self, data):
try:
return self._s.send(data)
except OSError as ex:
if self._is_timeouterror(ex):
raise timeout(ex) from None
raise
def sendto(self, data, addr):
addr = self._arg_addr(addr)
try:
return self._s.sendto(data, addr)
except OSError as ex:
if self._is_timeouterror(ex):
raise timeout(ex) from None
raise
def sendall(self, data):
try:
tosend = data
while tosend:
size = self.send(tosend)
tosend = tosend[size:]
except OSError as ex:
if self._is_timeouterror(ex):
raise timeout(ex) from None
raise
def recv(self, bufsize):
try:
return self._s.recv(bufsize)
except OSError as ex:
if self._is_timeouterror(ex):
raise timeout(ex) from None
raise
def recvfrom(self, bufsize):
try:
data, addr = self._s.recvfrom(bufsize)
except OSError as ex:
if self._is_timeouterror(ex):
raise timeout(ex) from None
raise
addr = self._ret_addr(addr)
return data, addr
def shutdown(self, how):
_posix.shutdown(self.fileno(), how)
def close(self):
if not self._closed:
self._s.close()
self._closed = True
def getsockname(self):
addr = _posix.getsockname(self.fileno())
return self._ret_addr(addr)
def getpeername(self):
addr = _posix.getpeername(self.fileno())
return self._ret_addr(addr)
def setsockopt(self, level, optname, value):
return self._s.setsockopt(level, optname, value)
def settimeout(self, value):
return self._s.settimeout(value)
def setblocking(self, flag):
return self._s.setblocking(flag)
def inet_aton(ip_addr):
return _usocket.inet_pton(_posix.AF_INET, ip_addr)
def inet_ntoa(packed_ip):
return _usocket.inet_ntop(_posix.AF_INET, packed_ip)
def _getaddrinfo(*args, **kwargs):
try:
return _posix.getaddrinfo(*args, **kwargs)
except OSError as ex:
raise gaierror(ex.errno) from None
def _getnameinfo(*args, **kwargs):
try:
return _posix.getnameinfo(*args, **kwargs)
except OSError as ex:
raise gaierror(ex.errno) from None
error = OSError
timeout = TimeoutError
inet_pton = _usocket.inet_pton
inet_ntop = _usocket.inet_ntop
SOCK_DGRAM = _posix.SOCK_DGRAM
SOCK_RAW = _posix.SOCK_RAW
SOCK_SEQPACKET = _posix.SOCK_SEQPACKET
SOCK_STREAM = _posix.SOCK_STREAM
SOL_SOCKET = _posix.SOL_SOCKET
SO_ACCEPTCONN = _posix.SO_ACCEPTCONN
if hasattr(_posix, 'SO_BINDTODEVICE'):
SO_BINDTODEVICE = _posix.SO_BINDTODEVICE
SO_BROADCAST = _posix.SO_BROADCAST
SO_DEBUG = _posix.SO_DEBUG
SO_DONTROUTE = _posix.SO_DONTROUTE
SO_ERROR = _posix.SO_ERROR
SO_KEEPALIVE = _posix.SO_KEEPALIVE
SO_LINGER = _posix.SO_LINGER
SO_OOBINLINE = _posix.SO_OOBINLINE
SO_RCVBUF = _posix.SO_RCVBUF
SO_RCVLOWAT = _posix.SO_RCVLOWAT
SO_RCVTIMEO = _posix.SO_RCVTIMEO
SO_REUSEADDR = _posix.SO_REUSEADDR
if hasattr(_posix, 'SO_REUSEPORT'):
SO_REUSEPORT = _posix.SO_REUSEPORT
SO_SNDBUF = _posix.SO_SNDBUF
SO_SNDLOWAT = _posix.SO_SNDLOWAT
SO_SNDTIMEO = _posix.SO_SNDTIMEO
SO_TYPE = _posix.SO_TYPE
SOMAXCONN = _posix.SOMAXCONN
MSG_CTRUNC = _posix.MSG_CTRUNC
MSG_DONTROUTE = _posix.MSG_DONTROUTE
MSG_EOR = _posix.MSG_EOR
MSG_OOB = _posix.MSG_OOB
MSG_PEEK = _posix.MSG_PEEK
MSG_TRUNC = _posix.MSG_TRUNC
MSG_WAITALL = _posix.MSG_WAITALL
AF_INET = _posix.AF_INET
AF_INET6 = _posix.AF_INET6
AF_UNIX = _posix.AF_UNIX
AF_UNSPEC = _posix.AF_UNSPEC
SHUT_RD = _posix.SHUT_RD
SHUT_RDWR = _posix.SHUT_RDWR
SHUT_WR = _posix.SHUT_WR
IPPROTO_IP = _posix.IPPROTO_IP
IPPROTO_IPV6 = _posix.IPPROTO_IPV6
IPPROTO_ICMP = _posix.IPPROTO_ICMP
IPPROTO_RAW = _posix.IPPROTO_RAW
IPPROTO_TCP = _posix.IPPROTO_TCP
IPPROTO_UDP = _posix.IPPROTO_UDP
INADDR_ANY = _posix.INADDR_ANY
INADDR_BROADCAST = _posix.INADDR_BROADCAST
INET_ADDRSTRLEN = _posix.INET_ADDRSTRLEN
INET6_ADDRSTRLEN = _posix.INET6_ADDRSTRLEN
IPV6_JOIN_GROUP = _posix.IPV6_JOIN_GROUP
IPV6_LEAVE_GROUP = _posix.IPV6_LEAVE_GROUP
IPV6_MULTICAST_HOPS = _posix.IPV6_MULTICAST_HOPS
IPV6_MULTICAST_IF = _posix.IPV6_MULTICAST_IF
IPV6_MULTICAST_LOOP = _posix.IPV6_MULTICAST_LOOP
IPV6_UNICAST_HOPS = _posix.IPV6_UNICAST_HOPS
IPV6_V6ONLY = _posix.IPV6_V6ONLY
TCP_NODELAY = _posix.TCP_NODELAY

View File

@@ -0,0 +1,57 @@
# This module provides partial functionality of the CPython "struct" module for
# MicroPython.
import sys as _sys
_path = _sys.path
_sys.path = ()
try:
import struct as _ustruct
finally:
_sys.path = _path
del _path
__all__ = [
'calcsize', 'pack', 'pack_into', 'unpack', 'unpack_from'
]
class _StructError(Exception):
pass
error = _StructError
def pack(format, *args):
try:
return _ustruct.pack(format, *args)
except Exception as ex:
raise error(ex) from None
def unpack(format, buffer):
try:
return _ustruct.unpack(format, buffer)
except Exception as ex:
raise error(ex) from None
def pack_into(format, buffer, offset, *args):
try:
return _ustruct.pack_into(format, buffer, offset, *args)
except Exception as ex:
raise error(ex) from None
def unpack_from(format, buffer, offset=0):
try:
return _ustruct.unpack_from(format, buffer, offset)
except Exception as ex:
raise error(ex) from None
def calcsize(format):
try:
return _ustruct.calcsize(format)
except Exception as ex:
raise error(ex) from None

View File

@@ -0,0 +1,254 @@
# This module provides partial functionality of the CPython "subprocess" module
# for MicroPython.
# Note: Thread safety is not guaranteed.
import sys as _usys
import _posix
__all__ = [
'CalledProcessError', 'Popen', 'STDOUT', 'SubprocessError', 'check_output'
]
STDOUT = -2
class SubprocessError(Exception):
pass
class CalledProcessError(SubprocessError):
def __init__(self, returncode, cmd, output=None):
self.returncode = returncode
self.cmd = cmd
self.output = output
def __str__(self):
return "Command '%s' returned non-zero exit status %d" % (
self.cmd, self.returncode
)
def _handle_exitstatus(sts):
if _posix.WIFSIGNALED(sts):
return -(_posix.WTERMSIG(sts))
elif _posix.WIFEXITED(sts):
return _posix.WEXITSTATUS(sts)
else:
# Should never happen
raise SubprocessError("Unknown child exit status!")
def _close_fd(fd):
if fd < 0:
return
try:
_posix.close(fd)
fd = -1
except OSError as ex:
_usys.stderr.write('close(): %s\n' % str(ex))
return fd
def _kill_child(pid, signal):
if pid <= 0:
return
_, sts = _posix.waitpid(pid, _posix.WNOHANG)
if not sts:
try:
_posix.kill(pid, signal)
except OSError as ex:
if ex.errno != _posix.ESRCH:
raise
_posix.waitpid(pid, 0)
def _set_fd_cloexec(fd):
flags = _posix.fcntl(fd, _posix.F_GETFD, None)
_posix.fcntl(fd, _posix.F_SETFD, flags | _posix.FD_CLOEXEC)
def check_output(args, *, stderr=None):
if stderr is not None and stderr != STDOUT:
raise NotImplementedError("Unsupported stderr redirection")
executable = args[0]
r, w = _posix.pipe()
er, ew = _posix.pipe()
_set_fd_cloexec(er)
_set_fd_cloexec(ew)
try:
pid = _posix.fork()
except Exception:
_close_fd(r)
_close_fd(w)
raise
if pid == 0:
# child
try:
r = _close_fd(r)
er = _close_fd(er)
_posix.dup2(w, _posix.STDOUT_FILENO)
if stderr == STDOUT:
_posix.dup2(w, _posix.STDERR_FILENO)
w = _close_fd(w)
_posix.execvp(executable, args)
except OSError as ex:
_posix.write(ew, str(ex.errno).encode())
finally:
_close_fd(r)
_close_fd(er)
_close_fd(w)
_close_fd(ew)
_posix._exit(1)
else:
# parent
try:
w = _close_fd(w)
ew = _close_fd(ew)
output_l = []
while True:
chunk = _posix.read(r, _posix.BUFSIZ)
if not chunk:
break
output_l.append(chunk)
errno_l = []
while True:
chunk = _posix.read(er, _posix.BUFSIZ)
if not chunk:
break
errno_l.append(chunk)
r = _close_fd(r)
er = _close_fd(er)
output = b''.join(output_l)
errno_b = b''.join(errno_l)
_, status = _posix.waitpid(pid, 0)
pid = -1
if errno_b:
errno = int(errno_b.decode())
raise OSError(errno)
retcode = _handle_exitstatus(status)
if retcode:
raise CalledProcessError(retcode, args, output)
return output
finally:
_close_fd(r)
_close_fd(er)
_close_fd(w)
_close_fd(ew)
_kill_child(pid, _posix.SIGKILL)
class Popen(object):
def __init__(self, args):
self.args = args
self.pid = None
self.returncode = None
self._start_child()
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.wait()
def _start_child(self):
executable = self.args[0]
er, ew = _posix.pipe()
_set_fd_cloexec(er)
_set_fd_cloexec(ew)
try:
pid = _posix.fork()
except Exception:
_close_fd(er)
_close_fd(ew)
raise
if pid == 0:
# child
try:
er = _close_fd(er)
_posix.execvp(executable, self.args)
except OSError as ex:
_posix.write(ew, str(ex.errno).encode())
finally:
_close_fd(er)
_close_fd(ew)
_posix._exit(1)
else:
# parent
self.pid = pid
try:
ew = _close_fd(ew)
errno_l = []
while True:
chunk = _posix.read(er, _posix.BUFSIZ)
if not chunk:
break
errno_l.append(chunk)
er = _close_fd(er)
errno_b = b''.join(errno_l)
if errno_b:
errno = int(errno_b.decode())
raise OSError(errno)
except Exception:
_kill_child(pid, _posix.SIGKILL)
raise
finally:
_close_fd(er)
_close_fd(ew)
def poll(self):
if self.returncode is not None:
return self.returncode
try:
pid, sts = _posix.waitpid(self.pid, _posix.WNOHANG)
if pid == self.pid:
self.returncode = _handle_exitstatus(sts)
except OSError as e:
if e.errno == _posix.ECHILD:
self.returncode = 0
return self.returncode
def wait(self):
if self.returncode is not None:
return self.returncode
try:
_, sts = _posix.waitpid(self.pid, 0)
self.returncode = _handle_exitstatus(sts)
except OSError as ex:
if ex.errno != _posix.ECHILD:
raise
self.returncode = 0
return self.returncode
def send_signal(self, sig):
self.poll()
if self.returncode is not None:
return
try:
_posix.kill(self.pid, sig)
except OSError as ex:
if ex.errno != _posix.ESRCH:
raise
def terminate(self):
self.send_signal(_posix.SIGTERM)
def kill(self):
self.send_signal(_posix.SIGKILL)

View File

@@ -0,0 +1,106 @@
# This module provides partial functionality of the CPython "threading" module
# for MicroPython.
import _thread
__all__ = [
'Thread', 'active_count', 'current_thread', 'main_thread'
]
class Thread(object):
_lock = _thread.allocate_lock()
_active = dict()
def __init__(self, group=None, target=None, name=None, args=(),
kwargs=None):
if group is not None:
raise NotImplementedError("group argument must be None for now")
if kwargs is None:
kwargs = {}
self.name = str(name or "unnamed thread")
self.ident = None
self._target = target
self._args = args
self._kwargs = kwargs
self._start_called = False
self._is_started = False
self._is_stopped = False
self._initialized = True
self._join_lck = _thread.allocate_lock()
def _pre_run(self):
# this method is internally called inside the child thread
self._join_lck.acquire()
self.ident = _thread.get_ident()
Thread._lock.acquire()
Thread._active[self.ident] = self
Thread._lock.release()
self._is_started = True
def _post_run(self):
# this method is internally called inside the child thread
self._is_stopped = True
Thread._lock.acquire()
del Thread._active[self.ident]
Thread._lock.release()
self._join_lck.release()
def run(self):
try:
if self._target:
self._target(*self._args, **self._kwargs)
finally:
del self._target, self._args, self._kwargs
def start(self):
if not self._initialized:
raise RuntimeError("thread.__init__() not called")
if self._start_called:
raise RuntimeError("threads can only be started once")
def _entry():
self._pre_run()
try:
self.run()
finally:
self._post_run()
self._start_called = True
_thread.start_new_thread(_entry, ())
def is_alive(self):
if not self._initialized:
raise RuntimeError("thread.__init__() not called")
return self._is_started and not self._is_stopped
def join(self):
if not self._initialized:
raise RuntimeError("thread.__init__() not called")
if not self._is_started:
raise RuntimeError("cannot join thread before it is started")
if self.ident == _thread.get_ident():
raise RuntimeError("cannot join current thread")
self._join_lck.acquire()
self._join_lck.release()
class _MainThread(Thread):
def __init__(self):
super().__init__(name="MainThread")
self._start_called = True
self._pre_run()
_main_thread = _MainThread()
def active_count():
return len(Thread._active)
def current_thread():
ident = _thread.get_ident()
return Thread._active[ident]
def main_thread():
return _main_thread