From ac2293161e98e73d39434628f995e85bd97e52c2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 27 Jul 2021 00:43:35 +1000 Subject: [PATCH] py/modsys: Add optional mutable attributes sys.ps1/ps2 and use them. This allows customising the REPL prompt strings. Signed-off-by: Damien George --- docs/library/sys.rst | 6 +++++ ports/unix/main.c | 8 +++--- .../unix/variants/coverage/mpconfigvariant.h | 1 + py/modsys.c | 4 +++ py/mpconfig.h | 7 ++++- py/mpstate.h | 4 +++ py/qstrdefs.h | 4 +++ py/repl.c | 10 +++++++ py/repl.h | 26 +++++++++++++++++++ py/runtime.c | 5 ++++ shared/runtime/pyexec.c | 10 +++---- tests/cmdline/repl_sys_ps1_ps2.py | 6 +++++ tests/cmdline/repl_sys_ps1_ps2.py.exp | 10 +++++++ tests/run-tests.py | 1 + tests/unix/extra_coverage.py.exp | 4 +-- 15 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 tests/cmdline/repl_sys_ps1_ps2.py create mode 100644 tests/cmdline/repl_sys_ps1_ps2.py.exp diff --git a/docs/library/sys.rst b/docs/library/sys.rst index f4ff8786a..a2d55fecb 100644 --- a/docs/library/sys.rst +++ b/docs/library/sys.rst @@ -132,6 +132,12 @@ Constants If you need to check whether your program runs on MicroPython (vs other Python implementation), use `sys.implementation` instead. +.. data:: ps1 + ps2 + + Mutable attributes holding strings, which are used for the REPL prompt. The defaults + give the standard Python prompt of ``>>>`` and ``...``. + .. data:: stderr Standard error `stream`. diff --git a/ports/unix/main.c b/ports/unix/main.c index bde867f8c..c2a6c8c6b 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -193,7 +193,7 @@ STATIC int do_repl(void) { input_restart: vstr_reset(&line); - int ret = readline(&line, ">>> "); + int ret = readline(&line, mp_repl_get_ps1()); mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; if (ret == CHAR_CTRL_C) { @@ -240,7 +240,7 @@ STATIC int do_repl(void) { // got a line with non-zero length, see if it needs continuing while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) { vstr_add_byte(&line, '\n'); - ret = readline(&line, "... "); + ret = readline(&line, mp_repl_get_ps2()); if (ret == CHAR_CTRL_C) { // cancel everything printf("\n"); @@ -265,13 +265,13 @@ STATIC int do_repl(void) { // use simple readline for (;;) { - char *line = prompt(">>> "); + char *line = prompt((char *)mp_repl_get_ps1()); if (line == NULL) { // EOF return 0; } while (mp_repl_continue_with_input(line)) { - char *line2 = prompt("... "); + char *line2 = prompt((char *)mp_repl_get_ps2()); if (line2 == NULL) { break; } diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index 9b6b40775..e2640f71f 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -45,6 +45,7 @@ #define MICROPY_PY_BUILTINS_HELP (1) #define MICROPY_PY_BUILTINS_HELP_MODULES (1) #define MICROPY_PY_SYS_GETSIZEOF (1) +#define MICROPY_PY_SYS_PS1_PS2 (1) #define MICROPY_PY_SYS_TRACEBACKLIMIT (1) #define MICROPY_PY_MATH_CONSTANTS (1) #define MICROPY_PY_MATH_FACTORIAL (1) diff --git a/py/modsys.c b/py/modsys.c index c44c7ed45..ac9077622 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -185,6 +185,10 @@ MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_settrace_obj, mp_sys_settrace); #if MICROPY_PY_SYS_ATTR_DELEGATION STATIC const uint16_t sys_mutable_keys[] = { + #if MICROPY_PY_SYS_PS1_PS2 + MP_QSTR_ps1, + MP_QSTR_ps2, + #endif #if MICROPY_PY_SYS_TRACEBACKLIMIT MP_QSTR_tracebacklimit, #endif diff --git a/py/mpconfig.h b/py/mpconfig.h index be967e698..47c16ed96 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1356,6 +1356,11 @@ typedef double mp_float_t; #define MICROPY_PY_SYS_ATEXIT (0) #endif +// Whether to provide sys.{ps1,ps2} mutable attributes, to control REPL prompts +#ifndef MICROPY_PY_SYS_PS1_PS2 +#define MICROPY_PY_SYS_PS1_PS2 (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + // Whether to provide "sys.settrace" function #ifndef MICROPY_PY_SYS_SETTRACE #define MICROPY_PY_SYS_SETTRACE (0) @@ -1385,7 +1390,7 @@ typedef double mp_float_t; // Whether the sys module supports attribute delegation // This is enabled automatically when needed by other features #ifndef MICROPY_PY_SYS_ATTR_DELEGATION -#define MICROPY_PY_SYS_ATTR_DELEGATION (MICROPY_PY_SYS_TRACEBACKLIMIT) +#define MICROPY_PY_SYS_ATTR_DELEGATION (MICROPY_PY_SYS_PS1_PS2 || MICROPY_PY_SYS_TRACEBACKLIMIT) #endif // Whether to provide "uerrno" module diff --git a/py/mpstate.h b/py/mpstate.h index 499d86351..a493b780a 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -41,6 +41,10 @@ // variable, but in the future it is hoped that the state can become local. enum { + #if MICROPY_PY_SYS_PS1_PS2 + MP_SYS_MUTABLE_PS1, + MP_SYS_MUTABLE_PS2, + #endif #if MICROPY_PY_SYS_TRACEBACKLIMIT MP_SYS_MUTABLE_TRACEBACKLIMIT, #endif diff --git a/py/qstrdefs.h b/py/qstrdefs.h index 405813941..5003636df 100644 --- a/py/qstrdefs.h +++ b/py/qstrdefs.h @@ -39,6 +39,10 @@ Q() Q(*) Q(_) Q(/) +#if MICROPY_PY_SYS_PS1_PS2 +Q(>>> ) +Q(... ) +#endif #if MICROPY_PY_BUILTINS_STR_OP_MODULO Q(%#o) Q(%#x) diff --git a/py/repl.c b/py/repl.c index 822e385ab..4e47cf784 100644 --- a/py/repl.c +++ b/py/repl.c @@ -33,6 +33,16 @@ #if MICROPY_HELPER_REPL +#if MICROPY_PY_SYS_PS1_PS2 +const char *mp_repl_get_psx(unsigned int entry) { + if (mp_obj_is_str(MP_STATE_VM(sys_mutable)[entry])) { + return mp_obj_str_get_str(MP_STATE_VM(sys_mutable)[entry]); + } else { + return ""; + } +} +#endif + STATIC bool str_startswith_word(const char *str, const char *head) { size_t i; for (i = 0; str[i] && head[i]; i++) { diff --git a/py/repl.h b/py/repl.h index a7a4136ca..9e8f7f1dd 100644 --- a/py/repl.h +++ b/py/repl.h @@ -31,8 +31,34 @@ #include "py/mpprint.h" #if MICROPY_HELPER_REPL + +#if MICROPY_PY_SYS_PS1_PS2 + +const char *mp_repl_get_psx(unsigned int entry); + +static inline const char *mp_repl_get_ps1(void) { + return mp_repl_get_psx(MP_SYS_MUTABLE_PS1); +} + +static inline const char *mp_repl_get_ps2(void) { + return mp_repl_get_psx(MP_SYS_MUTABLE_PS2); +} + +#else + +static inline const char *mp_repl_get_ps1(void) { + return ">>> "; +} + +static inline const char *mp_repl_get_ps2(void) { + return "... "; +} + +#endif + bool mp_repl_continue_with_input(const char *input); size_t mp_repl_autocomplete(const char *str, size_t len, const mp_print_t *print, const char **compl_str); + #endif #endif // MICROPY_INCLUDED_PY_REPL_H diff --git a/py/runtime.c b/py/runtime.c index 665c9f220..ba3fbe7fa 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -135,6 +135,11 @@ void mp_init(void) { MP_STATE_VM(sys_exitfunc) = mp_const_none; #endif + #if MICROPY_PY_SYS_PS1_PS2 + MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_PS1]) = MP_OBJ_NEW_QSTR(MP_QSTR__gt__gt__gt__space_); + MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_PS2]) = MP_OBJ_NEW_QSTR(MP_QSTR__dot__dot__dot__space_); + #endif + #if MICROPY_PY_SYS_SETTRACE MP_STATE_THREAD(prof_trace_callback) = MP_OBJ_NULL; MP_STATE_THREAD(prof_callback_is_executing) = false; diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index ae6dd770b..9fde987a4 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -433,7 +433,7 @@ STATIC int pyexec_friendly_repl_process_char(int c) { vstr_add_byte(MP_STATE_VM(repl_line), '\n'); repl.cont_line = true; - readline_note_newline("... "); + readline_note_newline(mp_repl_get_ps2()); return 0; } else { @@ -454,7 +454,7 @@ STATIC int pyexec_friendly_repl_process_char(int c) { if (mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { vstr_add_byte(MP_STATE_VM(repl_line), '\n'); - readline_note_newline("... "); + readline_note_newline(mp_repl_get_ps2()); return 0; } @@ -468,7 +468,7 @@ STATIC int pyexec_friendly_repl_process_char(int c) { vstr_reset(MP_STATE_VM(repl_line)); repl.cont_line = false; repl.paste_mode = false; - readline_init(MP_STATE_VM(repl_line), ">>> "); + readline_init(MP_STATE_VM(repl_line), mp_repl_get_ps1()); return 0; } } @@ -598,7 +598,7 @@ friendly_repl_reset: } vstr_reset(&line); - int ret = readline(&line, ">>> "); + int ret = readline(&line, mp_repl_get_ps1()); mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; if (ret == CHAR_CTRL_A) { @@ -651,7 +651,7 @@ friendly_repl_reset: // got a line with non-zero length, see if it needs continuing while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) { vstr_add_byte(&line, '\n'); - ret = readline(&line, "... "); + ret = readline(&line, mp_repl_get_ps2()); if (ret == CHAR_CTRL_C) { // cancel everything mp_hal_stdout_tx_str("\r\n"); diff --git a/tests/cmdline/repl_sys_ps1_ps2.py b/tests/cmdline/repl_sys_ps1_ps2.py new file mode 100644 index 000000000..4f96057c4 --- /dev/null +++ b/tests/cmdline/repl_sys_ps1_ps2.py @@ -0,0 +1,6 @@ +# test changing ps1/ps2 +import usys +usys.ps1 = "PS1" +usys.ps2 = "PS2" +(1 + +2) diff --git a/tests/cmdline/repl_sys_ps1_ps2.py.exp b/tests/cmdline/repl_sys_ps1_ps2.py.exp new file mode 100644 index 000000000..e4a802d34 --- /dev/null +++ b/tests/cmdline/repl_sys_ps1_ps2.py.exp @@ -0,0 +1,10 @@ +MicroPython \.\+ version +Use \.\+ +>>> # test changing ps1/ps2 +>>> import usys +>>> usys.ps1 = "PS1" +PS1usys.ps2 = "PS2" +PS1(1 + +PS22) +3 +PS1 diff --git a/tests/run-tests.py b/tests/run-tests.py index dfe0a8e55..9c298dae3 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -433,6 +433,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): if not has_coverage: skip_tests.add("cmdline/cmd_parsetree.py") + skip_tests.add("cmdline/repl_sys_ps1_ps2.py") # Some tests shouldn't be run on a PC if args.target == "unix": diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 67d299bca..f6681f4ac 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -45,8 +45,8 @@ utime utimeq argv atexit byteorder exc_info exit getsizeof implementation maxsize modules path platform print_exception -stderr stdin stdout tracebacklimit -version version_info +ps1 ps2 stderr stdin +stdout tracebacklimit version version_info ementation # attrtuple (start=1, stop=2, step=3)