blueloveTH 3 месяцев назад
Родитель
Сommit
4827e6dea3

+ 1 - 0
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();

+ 5 - 0
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."""

+ 0 - 3
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."""
 

+ 1 - 0
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 {

+ 98 - 0
src/modules/picoterm.c

@@ -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;
+}

+ 0 - 9
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);
 

+ 23 - 0
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