diff --git a/include/pocketpy/interpreter/modules.h b/include/pocketpy/interpreter/modules.h index f9a572ad..80134e80 100644 --- a/include/pocketpy/interpreter/modules.h +++ b/include/pocketpy/interpreter/modules.h @@ -26,6 +26,7 @@ void pk__add_module_colorcvt(); void pk__add_module_conio(); void pk__add_module_lz4(); void pk__add_module_pkpy(); +void pk__add_module_picoterm(); #ifdef PK_BUILD_MODULE_CUTE_PNG void pk__add_module_cute_png(); diff --git a/include/typings/picoterm.pyi b/include/typings/picoterm.pyi new file mode 100644 index 00000000..24b4a831 --- /dev/null +++ b/include/typings/picoterm.pyi @@ -0,0 +1,5 @@ +def enable_full_buffering_mode() -> None: + """Enable full buffering mode for ASCII drawings (32KB).""" + +def split_ansi_escaped_string(s: str) -> list[str]: + """Perform split on ANSI escaped string.""" diff --git a/include/typings/pkpy.pyi b/include/typings/pkpy.pyi index 4714d03e..50e05129 100644 --- a/include/typings/pkpy.pyi +++ b/include/typings/pkpy.pyi @@ -20,9 +20,6 @@ def memory_usage() -> str: def is_user_defined_type(t: type) -> bool: """Check if a type is user-defined. This means the type was created by executing python `class` statement.""" -def enable_full_buffering_mode() -> None: - """Enable full buffering mode for ASCII drawings.""" - def currentvm() -> int: """Return the current VM index.""" diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index a9a562d6..526f165e 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -265,6 +265,7 @@ void VM__ctor(VM* self) { pk__add_module_cute_png(); // optional pk__add_module_msgpack(); // optional pk__add_module_pkpy(); + pk__add_module_picoterm(); // add python builtins do { diff --git a/src/modules/picoterm.c b/src/modules/picoterm.c new file mode 100644 index 00000000..54cfc4f5 --- /dev/null +++ b/src/modules/picoterm.c @@ -0,0 +1,98 @@ +#include "pocketpy/pocketpy.h" +#include "pocketpy/objects/base.h" +#include +#include "pocketpy/common/vector.h" + +static bool picoterm_enable_full_buffering_mode(int argc, py_Ref argv) { + PY_CHECK_ARGC(0); + static char buf[1024 * 32]; // 32KB + setvbuf(stdout, buf, _IOFBF, sizeof(buf)); + py_newnone(py_retval()); + return true; +} + +// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 +typedef struct { + c11_sv text; + char suffix; +} AnsiEscapedToken; + +static bool split_ansi_escaped_string(c11_sv sv, c11_vector* out_tokens); + +static bool picoterm_split_ansi_escaped_string(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + PY_CHECK_ARG_TYPE(0, tp_str); + c11_sv s = py_tosv(argv); + c11_vector /*T=AnsiEscapedToken*/ tokens; + c11_vector__ctor(&tokens, sizeof(AnsiEscapedToken)); + if(!split_ansi_escaped_string(s, &tokens)) { + c11_vector__dtor(&tokens); + return ValueError("invalid ANSI escape sequences"); + } + py_newlistn(py_retval(), tokens.length); + for(int i = 0; i < tokens.length; i++) { + AnsiEscapedToken t = c11__getitem(AnsiEscapedToken, &tokens, i); + py_ItemRef item = py_list_getitem(py_retval(), i); + py_newstrv(item, t.text); + } + c11_vector__dtor(&tokens); + return true; +} + +void pk__add_module_picoterm() { + py_Ref mod = py_newmodule("picoterm"); + + py_bindfunc(mod, "enable_full_buffering_mode", picoterm_enable_full_buffering_mode); + py_bindfunc(mod, "split_ansi_escaped_string", picoterm_split_ansi_escaped_string); +} + +static bool split_ansi_escaped_string(c11_sv sv, c11_vector* out_tokens) { + const char* p = sv.data; + int i = 0; + while(i < sv.size) { + if(p[i] == '\x1b') { + i++; // skip '\x1b' + if(i >= sv.size || p[i] != '[') { + return false; // invalid escape sequence + } + + int esc_start = i - 1; + i++; // skip '[' + + c11_sv content; + content.data = p + i; + while(i < sv.size && !((p[i] >= 'A' && p[i] <= 'Z') || (p[i] >= 'a' && p[i] <= 'z'))) { + i++; + } + content.size = p + i - content.data; + if(i >= sv.size) { + return false; // invalid escape sequence + } + + char suffix = p[i]; + i++; // skip suffix + + AnsiEscapedToken token; + token.text = (c11_sv){p + esc_start, i - esc_start}; + token.suffix = suffix; + c11_vector__push(AnsiEscapedToken, out_tokens, token); + } else if(p[i] == '\n') { + AnsiEscapedToken token; + token.text = (c11_sv){p + i, 1}; + token.suffix = '\n'; + c11_vector__push(AnsiEscapedToken, out_tokens, token); + i++; + } else { + int text_start = i; + while(i < sv.size && p[i] != '\x1b' && p[i] != '\n') { + i++; + } + AnsiEscapedToken token; + token.text = (c11_sv){p + text_start, i - text_start}; + token.suffix = '\0'; + c11_vector__push(AnsiEscapedToken, out_tokens, token); + } + } + + return true; +} diff --git a/src/modules/pkpy.c b/src/modules/pkpy.c index d8c89b90..0d99ecb3 100644 --- a/src/modules/pkpy.c +++ b/src/modules/pkpy.c @@ -65,14 +65,6 @@ static bool pkpy_is_user_defined_type(int argc, py_Ref argv) { return true; } -static bool pkpy_enable_full_buffering_mode(int argc, py_Ref argv) { - PY_CHECK_ARGC(0); - static char buf[1024 * 128]; - setvbuf(stdout, buf, _IOFBF, sizeof(buf)); - py_newnone(py_retval()); - return true; -} - static bool pkpy_currentvm(int argc, py_Ref argv) { PY_CHECK_ARGC(0); py_newint(py_retval(), py_currentvm()); @@ -539,7 +531,6 @@ void pk__add_module_pkpy() { py_bindfunc(mod, "memory_usage", pkpy_memory_usage); py_bindfunc(mod, "is_user_defined_type", pkpy_is_user_defined_type); - py_bindfunc(mod, "enable_full_buffering_mode", pkpy_enable_full_buffering_mode); py_bindfunc(mod, "currentvm", pkpy_currentvm); diff --git a/tests/92_picoterm.py b/tests/92_picoterm.py new file mode 100644 index 00000000..1e648d3f --- /dev/null +++ b/tests/92_picoterm.py @@ -0,0 +1,23 @@ +import picoterm +from vmath import rgb + +picoterm.enable_full_buffering_mode() + +bg = rgb(78, 118, 164) +fg = rgb(200, 200, 0) +text = "hello, \nworld" +text = bg.ansi_bg(text) +text = fg.ansi_fg(text) + +def ansi_italic(text: str): + return f'\x1b[3m{text}\x1b[0m' + +text = ansi_italic(text) + '123' +print(text) + +cpnts = picoterm.split_ansi_escaped_string(text) + +assert cpnts == ['\x1b[3m', '\x1b[38;2;200;200;0m', '\x1b[48;2;78;118;164m', 'hello, ', '\n', 'world', '\x1b[0m', '\x1b[0m', '\x1b[0m', '123'] + +cpnts_join = ''.join(cpnts) +assert cpnts_join == text \ No newline at end of file