add picoterm module

This commit is contained in:
blueloveTH 2025-11-23 00:14:14 +08:00
parent d43b85442e
commit 4827e6dea3
7 changed files with 128 additions and 12 deletions

View File

@ -26,6 +26,7 @@ void pk__add_module_colorcvt();
void pk__add_module_conio(); void pk__add_module_conio();
void pk__add_module_lz4(); void pk__add_module_lz4();
void pk__add_module_pkpy(); void pk__add_module_pkpy();
void pk__add_module_picoterm();
#ifdef PK_BUILD_MODULE_CUTE_PNG #ifdef PK_BUILD_MODULE_CUTE_PNG
void pk__add_module_cute_png(); void pk__add_module_cute_png();

View File

@ -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."""

View File

@ -20,9 +20,6 @@ def memory_usage() -> str:
def is_user_defined_type(t: type) -> bool: 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.""" """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: def currentvm() -> int:
"""Return the current VM index.""" """Return the current VM index."""

View File

@ -265,6 +265,7 @@ void VM__ctor(VM* self) {
pk__add_module_cute_png(); // optional pk__add_module_cute_png(); // optional
pk__add_module_msgpack(); // optional pk__add_module_msgpack(); // optional
pk__add_module_pkpy(); pk__add_module_pkpy();
pk__add_module_picoterm();
// add python builtins // add python builtins
do { do {

98
src/modules/picoterm.c Normal file
View File

@ -0,0 +1,98 @@
#include "pocketpy/pocketpy.h"
#include "pocketpy/objects/base.h"
#include <stdio.h>
#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;
}

View File

@ -65,14 +65,6 @@ static bool pkpy_is_user_defined_type(int argc, py_Ref argv) {
return true; 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) { static bool pkpy_currentvm(int argc, py_Ref argv) {
PY_CHECK_ARGC(0); PY_CHECK_ARGC(0);
py_newint(py_retval(), py_currentvm()); 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, "memory_usage", pkpy_memory_usage);
py_bindfunc(mod, "is_user_defined_type", pkpy_is_user_defined_type); 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); py_bindfunc(mod, "currentvm", pkpy_currentvm);

23
tests/92_picoterm.py Normal file
View File

@ -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