#include "port.h" #include #include #include #include #include #ifdef __EMSCRIPTEN__ #include __attribute__((noinline)) void python_error_start(const char* type) { EM_ASM({ Module.___temp_python_error = new Object(); Module.___temp_python_error["stacktrace"] = new Array(); Module.___temp_python_error["type"] = Module.UTF8ToString($0); }, type); } __attribute__((noinline)) void python_error_add_trace(const char* file, int line, const char* block) { EM_ASM({ var temp_obj = new Object(); temp_obj["file"] = Module.UTF8ToString($0); temp_obj["line"] = $1; temp_obj["block"] = Module.UTF8ToString($2); Module.___temp_python_error.stacktrace.push(temp_obj); }, file, line, block); } __attribute__((noinline)) void python_error_end() { EM_ASM({ if (typeof Module.onPythonError === "function") { Module.onPythonError(Module.___temp_python_error); } delete Module.___temp_python_error; }); } __attribute__((noinline)) void python_execution_end() { EM_ASM({ Module.___temp_storage_dump = []; }); for(size_t i = 0; i < (size_t)Ion::Storage::sharedStorage()->numberOfRecords(); i++) { Ion::Storage::Record record = Ion::Storage::sharedStorage()->recordAtIndex(i); const char * name = record.fullName(); const char * content = (const char *)record.value().buffer; size_t size = record.value().size; EM_ASM({ const filename = Module.UTF8ToString($0); const name = filename.replace(/\\.[^/.]+$/, ""); const type = filename.split('.').pop(); const code = type == "py" ? Module.UTF8ToString($1 + 1, $2) : Module.UTF8ToString($1, $2); Module.___temp_storage_dump.push({ name, type, code, }); }, name, content, size); } // window isn't defined on Node (CI) EM_ASM({ if (typeof variable !== 'undefined') { window.postMessage({ type: 'epsilon_micropython_executionEnvironment_runCode_finished', value: Module.___temp_storage_dump, }); } delete Module.___temp_storage_dump; }); } #endif #if defined _FXCG || defined NSPIRE_NEWLIB #ifdef _FXCG #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #endif /* py/parsenum.h is a C header which uses C keyword restrict. * It does not exist in C++ so we define it here in order to be able to include * py/parsenum.h header. */ #ifdef __cplusplus #define restrict // disable #endif extern "C" { #include "py/builtin.h" #include "py/compile.h" #include "py/gc.h" #include "py/lexer.h" #include "py/mperrno.h" #include "py/mphal.h" #include "py/nlr.h" #include "py/parsenum.h" #include "py/pystack.h" #include "py/repl.h" #include "py/runtime.h" #include "py/stackctrl.h" #include "mphalport.h" #include "mod/turtle/modturtle.h" #include "mod/matplotlib/pyplot/modpyplot.h" #if defined(INCLUDE_ULAB) #include "mod/ulab/ulab.h" #endif } #include static MicroPython::ScriptProvider * sScriptProvider = nullptr; static MicroPython::ExecutionEnvironment * sCurrentExecutionEnvironment = nullptr; MicroPython::ExecutionEnvironment * MicroPython::ExecutionEnvironment::currentExecutionEnvironment() { return sCurrentExecutionEnvironment; } bool MicroPython::ExecutionEnvironment::runCode(const char * str) { assert(sCurrentExecutionEnvironment == nullptr); sCurrentExecutionEnvironment = this; /* Set the user interruption now, as it is needed for the normal execution and * for the exception handling (because of print). */ mp_hal_set_interrupt_char((int)Ion::Keyboard::Key::Back); bool runSucceeded = true; nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_lexer_t *lex = mp_lexer_new_from_str_len(0, str, strlen(str), false); /* The input type is "single input" because the Python console is supposed * to be fed lines and not files. */ // TODO: add a parameter when other input types (file, eval) are required mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_SINGLE_INPUT); mp_obj_t module_fun = mp_compile(&pt, lex->source_name, true); mp_call_function_0(module_fun); nlr_pop(); } else { // Uncaught exception runSucceeded = false; /* mp_obj_print_exception is supposed to handle error printing. However, * because we want to print custom information, we copied and modified the * content of mp_obj_print_exception instead of calling it. */ if (mp_obj_is_exception_instance((mp_obj_t)nlr.ret_val)) { #ifdef __EMSCRIPTEN__ mp_obj_exception_t* the_exception = (mp_obj_exception_t*) MP_OBJ_TO_PTR((mp_obj_t)nlr.ret_val); python_error_start(qstr_str(the_exception->base.type->name)); #endif size_t n, *values; mp_obj_exception_get_traceback((mp_obj_t)nlr.ret_val, &n, &values); if (n > 0) { assert(n % 3 == 0); for (int i = n - 3; i >= 0; i -= 3) { if (values[i] != 0 || i == 0) { if (values[i] == 0) { mp_printf(&mp_plat_print, " Last command\n"); } else { #ifdef __EMSCRIPTEN__ python_error_add_trace((const char*) qstr_str(values[i]), (int) values[i + 1], (const char*) qstr_str(values[i+2])); #endif #if MICROPY_ENABLE_SOURCE_LINE mp_printf(&mp_plat_print, " File \"%q\", line %d", values[i], (int)values[i + 1]); #else mp_printf(&mp_plat_print, " File \"%q\"", values[i]); #endif // the block name can be NULL if it's unknown qstr block = values[i + 2]; if (block == MP_QSTRnull) { mp_print_str(&mp_plat_print, "\n"); } else { mp_printf(&mp_plat_print, ", in %q\n", block); } } } } } #ifdef __EMSCRIPTEN__ python_error_end(); #endif } mp_obj_print_helper(&mp_plat_print, (mp_obj_t)nlr.ret_val, PRINT_EXC); mp_print_str(&mp_plat_print, "\n"); /* End of mp_obj_print_exception. */ // Flush the store if an error is encountered to avoid being stuck with a full memory modpyplot_flush_used_heap(); // TODO: do the same for other modules? } // Disable the user interruption mp_hal_set_interrupt_char(-1); assert(sCurrentExecutionEnvironment == this); sCurrentExecutionEnvironment = nullptr; #ifdef __EMSCRIPTEN__ python_execution_end(); #endif return runSucceeded; } void MicroPython::ExecutionEnvironment::interrupt() { mp_keyboard_interrupt(); } extern "C" { extern const void * _stack_start; extern const void * _stack_end; } void MicroPython::init(void * heapStart, void * heapEnd) { #if __EMSCRIPTEN__ static mp_obj_t pystack[1024]; mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); #endif /* We delimit the stack part that will be used by Python. The stackTop is the * address of the first object that can be allocated on Python stack. This * boundaries are used: * - by gc_collect to determine where to collect roots of the objects that * must be kept on the heap * - to check if the maximal recursion depth has been reached. * Current stack pointer could go backward after initialization. A stack start * pointer defined in main is therefore used. */ void * stackTopAddress = Ion::stackStart(); #if MP_PORT_USE_STACK_SYMBOLS mp_stack_set_top(stackTopAddress); size_t stackLimitInBytes = (char *)stackTopAddress - (char *)&_stack_end; mp_stack_set_limit(stackLimitInBytes); #else mp_stack_set_top(stackTopAddress); /* The stack limit is set to roughly mimic the maximal recursion depth of the * device - and actually to be slightly less to be sure not to beat the device * performance. */ mp_stack_set_limit(29152); #endif gc_init(heapStart, heapEnd); mp_init(); } void MicroPython::deinit() { mp_deinit(); } void MicroPython::registerScriptProvider(ScriptProvider * s) { sScriptProvider = s; } void MicroPython::collectRootsAtAddress(char * address, int byteLength) { /* The given address is not necessarily aligned on sizeof(void *). However, * any pointer stored in the range [address, address + byteLength] will be * aligned on sizeof(void *). This is a consequence of the alignment * requirements of compilers (Cf http://www.catb.org/esr/structure-packing/). * Micropython gc_collect_root scans looking for pointers jumping every * sizeof(void *). It has to be provided with a sizeof(uintptr_t)-aligned * address. */ // Compute the aligned address // 0b000...00011 with 2 (or 3 for x64 arch) 1s uintptr_t bitMaskOnes = sizeof(uintptr_t) - 1; // 0b111...11100 with sizeof(uintptr_t)-1 0s uintptr_t bitMaskZeros = ~bitMaskOnes; uintptr_t alignedAddress = reinterpret_cast(address) & bitMaskZeros; /* Increase the length consequently with the new alignment * (We don't need to increase the byteLength to a sizeof(uintptr_t)-aligned * length because no pointer can be stored on less than sizeof(uintptr_t) * bytes.) */ int alignedByteLength = byteLength; alignedByteLength += reinterpret_cast(address) & bitMaskOnes; assert(alignedAddress % ((uintptr_t)sizeof(uintptr_t)) == 0); gc_collect_root((void **)alignedAddress, alignedByteLength / sizeof(uintptr_t)); } KDColor MicroPython::Color::Parse(mp_obj_t input, Mode mode){ static constexpr int maxColorIntensity = static_cast(Mode::MaxIntensity255); if (mp_obj_is_str(input)) { size_t l; const char * color = mp_obj_str_get_data(input, &l); constexpr NamedColor pairs[] = { NamedColor("blue", KDColorBlue), NamedColor("b", KDColorBlue), NamedColor("red", KDColorRed), NamedColor("r", KDColorRed), NamedColor("green", Palette::Green), NamedColor("g", Palette::Green), NamedColor("yellow", KDColorYellow), NamedColor("y", KDColorYellow), NamedColor("brown", Palette::Brown), NamedColor("black", KDColorBlack), NamedColor("k", KDColorBlack), NamedColor("white", KDColorWhite), NamedColor("w", KDColorWhite), NamedColor("pink", Palette::Pink), NamedColor("orange", Palette::Orange), NamedColor("purple", Palette::Purple), NamedColor("gray", Palette::GrayDark), NamedColor("grey", Palette::GrayDark), NamedColor("cyan", Palette::Cyan), NamedColor("magenta", Palette::Magenta) }; for (NamedColor p : pairs) { if (strcmp(p.name(), color) == 0) { return p.color(); } } if (color[0] == '#') { // TODO handle #abc as #aabbcc (see matplotlib spec) if (l != 7) { mp_raise_ValueError("RGB hex values are 6 bytes long"); } uint32_t colorInt = mp_obj_get_int(mp_parse_num_integer(color+1, strlen(color+1), 16, NULL)); return KDColor::RGB24(colorInt); } mp_float_t grayLevel = mp_obj_float_get(mp_parse_num_decimal(color, strlen(color), false, false, NULL)); if (grayLevel >= 0.0 && grayLevel <= 1.0) { uint8_t color = maxColorIntensity * (float) grayLevel; return KDColor::RGB888(color, color, color); } mp_raise_ValueError("Gray levels are between 0.0 and 1.0"); } else if(mp_obj_is_int(input)) { mp_raise_TypeError("Int are not colors"); //See https://github.com/numworks/epsilon/issues/1533#issuecomment-618443492 } else { size_t len; mp_obj_t * elem; mp_obj_get_array(input, &len, &elem); if (len != 3) { mp_raise_TypeError("Color needs 3 components"); } int intensityFactor = maxColorIntensity/static_cast(mode); return KDColor::RGB888( intensityFactor * mp_obj_get_float(elem[0]), intensityFactor * mp_obj_get_float(elem[1]), intensityFactor * mp_obj_get_float(elem[2]) ); } mp_raise_TypeError("Color couldn't be parsed"); } void gc_collect_regs_and_stack(void) { // get the registers and the sp jmp_buf regs; uintptr_t sp = Ion::collectRegisters(regs); void * python_stack_top = MP_STATE_THREAD(stack_top); assert(python_stack_top != NULL); /* On the device, the stack is stored in reverse order, but it might not be * the case on a computer. We thus have to take the absolute value of the * addresses difference. */ size_t stackLengthInByte; void ** scanStart; if ((uintptr_t)python_stack_top > sp) { /* To compute the stack length: * registers * <-----------> * STACK <- ...| | | | | |--|--|--|--| | | | | | | * ^sp ^python_stack_top * */ stackLengthInByte = (uintptr_t)python_stack_top - sp; scanStart = (void **)sp; } else { /* When computing the stack length, take into account regs' size. * registers * <-----------> * STACK -> | | | | | | | | | | | |--|--|--|--| | | |... * ^python_stack_top ^sp * */ stackLengthInByte = sp - (uintptr_t)python_stack_top + sizeof(regs); scanStart = (void **)python_stack_top; } /* Memory error detectors might find an error here as they might split regs * and stack memory zones. */ MicroPython::collectRootsAtAddress((char *)scanStart, stackLengthInByte); } void gc_collect(void) { gc_collect_start(); modturtle_gc_collect(); modpyplot_gc_collect(); gc_collect_regs_and_stack(); gc_collect_end(); } void nlr_jump_fail(void *val) { while (1); } #if defined _FXCG || defined NSPIRE_NEWLIB void do_mp_lexer_new_from_file(const char * filename,mp_lexer_t ** res) { mp_reader_t reader; mp_reader_new_file(&reader, filename); *res=mp_lexer_new(qstr_from_str(filename), reader); } // Code from MicroPython's reader.c #include #include #include typedef struct _mp_reader_posix_t { bool close_fd; int fd; size_t len; size_t pos; byte buf[20]; } mp_reader_posix_t; STATIC mp_uint_t mp_reader_posix_readbyte(void *data) { mp_reader_posix_t *reader = (mp_reader_posix_t *)data; if (reader->pos >= reader->len) { if (reader->len == 0) { return MP_READER_EOF; } else { MP_THREAD_GIL_EXIT(); int n = read(reader->fd, reader->buf, sizeof(reader->buf)); MP_THREAD_GIL_ENTER(); 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_posix_close(void *data) { mp_reader_posix_t *reader = (mp_reader_posix_t *)data; if (reader->close_fd) { MP_THREAD_GIL_EXIT(); close(reader->fd); MP_THREAD_GIL_ENTER(); } m_del_obj(mp_reader_posix_t, reader); } void mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd) { mp_reader_posix_t *rp = m_new_obj(mp_reader_posix_t); rp->close_fd = close_fd; rp->fd = fd; MP_THREAD_GIL_EXIT(); int n = read(rp->fd, rp->buf, sizeof(rp->buf)); if (n == -1) { if (close_fd) { close(fd); } MP_THREAD_GIL_ENTER(); mp_raise_OSError(errno); } MP_THREAD_GIL_ENTER(); rp->len = n; rp->pos = 0; reader->data = rp; reader->readbyte = mp_reader_posix_readbyte; reader->close = mp_reader_posix_close; } void mp_reader_new_file(mp_reader_t *reader, const char *filename) { MP_THREAD_GIL_EXIT(); int fd = open(filename, O_RDONLY, 0644); MP_THREAD_GIL_ENTER(); if (fd < 0) { mp_raise_OSError(errno); } mp_reader_new_file_from_fd(reader, fd, true); } #endif mp_lexer_t * mp_lexer_new_from_file(const char * filename) { if (sScriptProvider != nullptr) { const char * script = sScriptProvider->contentOfScript(filename, true); if (script != nullptr) { return mp_lexer_new_from_str_len(qstr_from_str(filename), script, strlen(script), 0 /* size_t free_len*/); } } #ifdef _FXCG mp_lexer_t * res=0; gint_world_switch(GINT_CALL(do_mp_lexer_new_from_file,filename,&res)); return res; #endif #ifdef NSPIRE_NEWLIB mp_lexer_t * res=0; do_mp_lexer_new_from_file(filename,&res); return res; #endif mp_raise_OSError(MP_ENOENT); } mp_import_stat_t mp_import_stat(const char *path) { if (sScriptProvider && sScriptProvider->contentOfScript(path, false)) { return MP_IMPORT_STAT_FILE; } #if defined _FXCG || defined NSPIRE_NEWLIB FILE * f=fopen(path,"rb"); if (f) { fclose(f); return MP_IMPORT_STAT_FILE; } #endif return MP_IMPORT_STAT_NO_EXIST; } void mp_hal_stdout_tx_strn_cooked(const char * str, size_t len) { assert(sCurrentExecutionEnvironment != nullptr); sCurrentExecutionEnvironment->printText(str, len); } const char * mp_hal_input(const char * prompt) { assert(sCurrentExecutionEnvironment != nullptr); return sCurrentExecutionEnvironment->inputText(prompt); }