From 0b91c2968b759abf5902c3a69744dd42b4dac71c Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Thu, 27 Nov 2025 14:41:13 +0800 Subject: [PATCH] add `picoterm.sscanf` --- include/typings/picoterm.pyi | 2 ++ src/modules/picoterm.c | 51 ++++++++++++++++++++++++++++++++++++ tests/92_picoterm.py | 12 +++++++++ 3 files changed, 65 insertions(+) diff --git a/include/typings/picoterm.pyi b/include/typings/picoterm.pyi index a29e4299..14592fb9 100644 --- a/include/typings/picoterm.pyi +++ b/include/typings/picoterm.pyi @@ -6,3 +6,5 @@ def split_ansi_escaped_string(s: str) -> list[str]: def wcwidth(c: int) -> int: ... def wcswidth(s: str) -> int: ... + +def sscanf(s: str, fmt: str, out_list: list) -> bool: ... \ No newline at end of file diff --git a/src/modules/picoterm.c b/src/modules/picoterm.c index 2024058c..ab3331f8 100644 --- a/src/modules/picoterm.c +++ b/src/modules/picoterm.c @@ -89,6 +89,56 @@ static bool picoterm_wcswidth(int argc, py_Ref argv) { return true; } +static bool picoterm_sscanf(int argc, py_Ref argv) { + PY_CHECK_ARGC(3); + PY_CHECK_ARG_TYPE(0, tp_str); + PY_CHECK_ARG_TYPE(1, tp_str); + PY_CHECK_ARG_TYPE(2, tp_list); + const char* input = py_tostr(py_arg(0)); + const char* format = py_tostr(py_arg(1)); + py_Ref output_list = py_arg(2); + py_list_clear(output_list); + + const char* p1 = input; + const char* p2 = format; + + while(*p1 != '\0' && *p2 != '\0') { + if(*p2 == '%') { + p2++; + if(*p2 == 'd' || *p2 == 'i') { + bool negative = false; + if(*p1 == '-') { + negative = true; + p1++; + } + const char* start = p1; + while(*p1 >= '0' && *p1 <= '9') + p1++; + c11_sv num_sv = {.data = start, .size = p1 - start}; + if(num_sv.size == 0) break; + + int64_t value = 0; + IntParsingResult res = c11__parse_uint(num_sv, &value, 10); + if(res != IntParsing_SUCCESS) break; + + if(negative) value = -value; + py_ItemRef item = py_list_emplace(output_list); + py_newint(item, value); + p2++; + } else { + return ValueError("unsupported format specifier: %%%c", *p2); + } + } else { + if(*p1 != *p2) break; + p1++; + p2++; + } + } + + py_newbool(py_retval(), *p2 == '\0'); + return true; +} + void pk__add_module_picoterm() { py_Ref mod = py_newmodule("picoterm"); @@ -96,6 +146,7 @@ void pk__add_module_picoterm() { py_bindfunc(mod, "split_ansi_escaped_string", picoterm_split_ansi_escaped_string); py_bindfunc(mod, "wcwidth", picoterm_wcwidth); py_bindfunc(mod, "wcswidth", picoterm_wcswidth); + py_bindfunc(mod, "sscanf", picoterm_sscanf); } static bool split_ansi_escaped_string(c11_sv sv, c11_vector* out_tokens) { diff --git a/tests/92_picoterm.py b/tests/92_picoterm.py index 0eaaef91..75065f1f 100644 --- a/tests/92_picoterm.py +++ b/tests/92_picoterm.py @@ -28,3 +28,15 @@ assert picoterm.wcwidth(ord('测')) == 2 assert picoterm.wcwidth(ord('👀')) == 2 assert picoterm.wcswidth("hello, 测试a测试👀测\n") == 7 + 1 + 12 + +text = rgb(12, 34, 56).ansi_fg("hello") +out_list = [] +assert picoterm.sscanf(text, "\x1b[38;2;%d;%d;%dm", out_list) +assert out_list == [12, 34, 56] + +assert picoterm.sscanf(text, "\x1b[38;2;%d;%d;%dmhello", out_list) +assert out_list == [12, 34, 56] + +assert picoterm.sscanf(text, "\x1b[38;2;%d;%d;%d", out_list) +assert not picoterm.sscanf(text, "\x1b[38;2;%d;%d;%dm???", out_list) +assert not picoterm.sscanf(text, "\x1b[77;2;%d;%d;%dm", out_list) \ No newline at end of file