Skip to content

Commit

Permalink
py/modsys: Add optional mutable attributes sys.ps1/ps2 and use them.
Browse files Browse the repository at this point in the history
This allows customising the REPL prompt strings.

Signed-off-by: Damien George <[email protected]>
  • Loading branch information
dpgeorge committed Mar 9, 2022
1 parent cac939d commit ac22931
Show file tree
Hide file tree
Showing 15 changed files with 94 additions and 12 deletions.
6 changes: 6 additions & 0 deletions docs/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
8 changes: 4 additions & 4 deletions ports/unix/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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");
Expand All @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions ports/unix/variants/coverage/mpconfigvariant.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions py/modsys.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion py/mpconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions py/mpstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions py/qstrdefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions py/repl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down
26 changes: 26 additions & 0 deletions py/repl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions py/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions shared/runtime/pyexec.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
}

Expand All @@ -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;
}
}
Expand Down Expand Up @@ -598,7 +598,7 @@ int pyexec_friendly_repl(void) {
}

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) {
Expand Down Expand Up @@ -651,7 +651,7 @@ int pyexec_friendly_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
mp_hal_stdout_tx_str("\r\n");
Expand Down
6 changes: 6 additions & 0 deletions tests/cmdline/repl_sys_ps1_ps2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# test changing ps1/ps2
import usys
usys.ps1 = "PS1"
usys.ps2 = "PS2"
(1 +
2)
10 changes: 10 additions & 0 deletions tests/cmdline/repl_sys_ps1_ps2.py.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
MicroPython \.\+ version
Use \.\+
>>> # test changing ps1/ps2
>>> import usys
>>> usys.ps1 = "PS1"
PS1usys.ps2 = "PS2"
PS1(1 +
PS22)
3
PS1
1 change: 1 addition & 0 deletions tests/run-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
4 changes: 2 additions & 2 deletions tests/unix/extra_coverage.py.exp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit ac22931

Please sign in to comment.