From e0f8104c1a0e0e62a4c5f02fa7457f308b27e5dd Mon Sep 17 00:00:00 2001 From: ruki Date: Tue, 20 Jan 2026 23:11:30 +0800 Subject: add utf8 module --- tests/modules/utf8/test.lua | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/modules/utf8/test.lua (limited to 'tests/modules/utf8/test.lua') diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua new file mode 100644 index 000000000..ccf9d0b3a --- /dev/null +++ b/tests/modules/utf8/test.lua @@ -0,0 +1,64 @@ + +function test_len(t) + t:are_equal(utf8.len("A"), 1) + t:are_equal(utf8.len("¢"), 1) + t:are_equal(utf8.len("€"), 1) + t:are_equal(utf8.len("𐍈"), 1) + t:are_equal(utf8.len("ab"), 2) + t:are_equal(utf8.len("A€B"), 3) + t:are_equal(utf8.len("你好"), 2) +end + +function test_char(t) + t:are_equal(utf8.char(65), "A") + t:are_equal(utf8.char(0x20AC), "€") + t:are_equal(utf8.char(65, 66, 67), "ABC") +end + +function test_codepoint(t) + t:are_equal(utf8.codepoint("A"), 65) + t:are_equal(utf8.codepoint("€"), 0x20AC) + local c1, c2, c3 = utf8.codepoint("ABC", 1, 3) + t:are_equal(c1, 65) + t:are_equal(c2, 66) + t:are_equal(c3, 67) + + -- test range + t:are_equal(utf8.codepoint("ABC", 2), 66) + t:are_equal(utf8.codepoint("ABC", 2, 2), 66) +end + +function test_offset(t) + t:are_equal(utf8.offset("ABC", 1), 1) + t:are_equal(utf8.offset("ABC", 2), 2) + t:are_equal(utf8.offset("ABC", 4), 4) + t:are_equal(utf8.offset("ABC", 5), nil) + + -- "€" is 3 bytes (0xE2 0x82 0xAC) + t:are_equal(utf8.offset("€BC", 1), 1) + t:are_equal(utf8.offset("€BC", 2), 4) + t:are_equal(utf8.offset("€BC", 3), 5) + + t:are_equal(utf8.offset("你好", 1), 1) + t:are_equal(utf8.offset("你好", 2), 4) + t:are_equal(utf8.offset("你好", 3), 7) +end + +function test_codes(t) + local s = "A€" + local codes = {} + for p, c in utf8.codes(s) do + table.insert(codes, {p, c}) + end + t:are_equal(#codes, 2) + t:are_equal(codes[1][1], 1) + t:are_equal(codes[1][2], 65) + -- "€" starts at 2? No, byte offset. + -- "A" is 1 byte. "€" starts at 2. + t:are_equal(codes[2][1], 2) + t:are_equal(codes[2][2], 0x20AC) +end + +function test_charpattern(t) + t:require(utf8.charpattern) +end -- cgit v1.3.1 From 92423451b623e79ac464d388bfb1bfed2780b792 Mon Sep 17 00:00:00 2001 From: ruki Date: Tue, 20 Jan 2026 23:28:32 +0800 Subject: format code --- core/src/xmake/utf8/char.c | 18 ++++++++------ core/src/xmake/utf8/codepoint.c | 19 ++++++++------ core/src/xmake/utf8/codes.c | 15 +++++++---- core/src/xmake/utf8/len.c | 11 +++++---- core/src/xmake/utf8/offset.c | 15 ++++++----- core/src/xmake/utf8/utf8.c | 55 ++++++++++++++++++++++++++++++----------- core/src/xmake/utf8/utf8.h | 10 +++++--- tests/modules/utf8/test.lua | 4 +-- 8 files changed, 97 insertions(+), 50 deletions(-) (limited to 'tests/modules/utf8/test.lua') diff --git a/core/src/xmake/utf8/char.c b/core/src/xmake/utf8/char.c index afb0a0e0f..760dab264 100644 --- a/core/src/xmake/utf8/char.c +++ b/core/src/xmake/utf8/char.c @@ -34,24 +34,26 @@ static void xm_utf8_char_push(lua_State *lua, tb_int_t arg) { tb_char_t buf[8]; tb_size_t n = xm_utf8_encode(buf, (xm_utf8_int_t)code); - if (n > 0) + if (n > 0) { lua_pushlstring(lua, buf, n); - else + } else { luaL_error(lua, "value out of range"); + } } /* ////////////////////////////////////////////////////////////////////////////////////// * implementation */ -/* -** utfchar(n1, n2, ...) -> char(n1)..char(n2)... -*/ +/* utfchar(n1, n2, ...) -> char(n1)..char(n2)... + */ tb_int_t xm_utf8_char(lua_State *lua) { - tb_int_t n = lua_gettop(lua); /* number of arguments */ - if (n == 1) /* optimize common case of single char */ + tb_assert_and_check_return_val(lua, 0); + + tb_int_t n = lua_gettop(lua); // number of arguments + if (n == 1) { // optimize common case of single char xm_utf8_char_push(lua, 1); - else { + } else { tb_int_t i; luaL_Buffer b; luaL_buffinit(lua, &b); diff --git a/core/src/xmake/utf8/codepoint.c b/core/src/xmake/utf8/codepoint.c index 7d2f0de24..15828a7ab 100644 --- a/core/src/xmake/utf8/codepoint.c +++ b/core/src/xmake/utf8/codepoint.c @@ -29,6 +29,8 @@ */ static tb_bool_t xm_utf8_codepoint_cb(xm_utf8_int_t code, tb_cpointer_t udata) { lua_State* lua = (lua_State*)udata; + tb_assert_and_check_return_val(lua, tb_false); + lua_pushinteger(lua, code); return tb_true; } @@ -37,10 +39,9 @@ static tb_bool_t xm_utf8_codepoint_cb(xm_utf8_int_t code, tb_cpointer_t udata) { * implementation */ -/* -** codepoint(s, [i, [j [, lax]]]) -> returns codepoints for all -** characters that start in the range [i,j] -*/ +/* codepoint(s, [i, [j [, lax]]]) -> returns codepoints for all + * characters that start in the range [i,j] + */ tb_int_t xm_utf8_codepoint(lua_State *lua) { size_t len; tb_char_t const* s = luaL_checklstring(lua, 1, &len); @@ -51,15 +52,19 @@ tb_int_t xm_utf8_codepoint(lua_State *lua) { luaL_argcheck(lua, posi >= 1, 2, "out of bounds"); luaL_argcheck(lua, pose <= (lua_Integer)len, 3, "out of bounds"); - if (posi > pose) return 0; /* empty interval; return no values */ - if (pose - posi >= INT_MAX) /* (lua_Integer -> int) overflow? */ + if (posi > pose) { + return 0; // empty interval; return no values + } + if (pose - posi >= INT_MAX) { // (lua_Integer -> int) overflow? return luaL_error(lua, "string slice too long"); + } tb_int_t n = (tb_int_t)(pose - posi) + 1; luaL_checkstack(lua, n, "string slice too long"); - if (!xm_utf8_codepoint_impl(s, len, posi, pose, !lax, xm_utf8_codepoint_cb, lua)) + if (!xm_utf8_codepoint_impl(s, len, posi, pose, !lax, xm_utf8_codepoint_cb, lua)) { return luaL_error(lua, XM_UTF8_MSGInvalid); + } return n; } diff --git a/core/src/xmake/utf8/codes.c b/core/src/xmake/utf8/codes.c index 46664e891..420584835 100644 --- a/core/src/xmake/utf8/codes.c +++ b/core/src/xmake/utf8/codes.c @@ -29,19 +29,24 @@ */ static tb_int_t xm_utf8_codes_iter(lua_State *lua, tb_bool_t strict) { + tb_assert_and_check_return_val(lua, 0); + size_t len; tb_char_t const* s = luaL_checklstring(lua, 1, &len); lua_Unsigned n = (lua_Unsigned)lua_tointeger(lua, 2); if (n < len) { - while (n < len && xm_utf8_iscontp(s + n)) n++; /* go to next character */ + while (n < len && xm_utf8_iscontp(s + n)) { + n++; // go to next character + } } - if (n >= len) /* (also handles original 'n' being negative) */ - return 0; /* no more codepoints */ - else { + if (n >= len) { // (also handles original 'n' being negative) + return 0; // no more codepoints + } else { xm_utf8_int_t code; tb_char_t const* next = xm_utf8_decode(s + n, &code, strict); - if (next == NULL || xm_utf8_iscontp(next)) + if (next == NULL || xm_utf8_iscontp(next)) { return luaL_error(lua, XM_UTF8_MSGInvalid); + } lua_pushinteger(lua, n + 1); lua_pushinteger(lua, code); return 2; diff --git a/core/src/xmake/utf8/len.c b/core/src/xmake/utf8/len.c index 410c0a1ef..50fa8e37a 100644 --- a/core/src/xmake/utf8/len.c +++ b/core/src/xmake/utf8/len.c @@ -28,12 +28,13 @@ * implementation */ -/* -** utf8len(s [, i [, j [, lax]]]) --> number of characters that -** start in the range [i,j], or nil + current position if 's' is not -** well formed in that interval -*/ +/* utf8len(s [, i [, j [, lax]]]) --> number of characters that + * start in the range [i,j], or nil + current position if 's' is not + * well formed in that interval + */ tb_int_t xm_utf8_len(lua_State *lua) { + tb_assert_and_check_return_val(lua, 0); + size_t len; tb_char_t const* s = luaL_checklstring(lua, 1, &len); lua_Integer posi = xm_utf8_posrelat(luaL_optinteger(lua, 2, 1), len); diff --git a/core/src/xmake/utf8/offset.c b/core/src/xmake/utf8/offset.c index 89635eac1..51152c7db 100644 --- a/core/src/xmake/utf8/offset.c +++ b/core/src/xmake/utf8/offset.c @@ -28,11 +28,12 @@ * implementation */ -/* -** offset(s, n, [i]) -> index where n-th character counting from -** position 'i' starts; 0 means character at 'i'. -*/ +/* offset(s, n, [i]) -> index where n-th character counting from + * position 'i' starts; 0 means character at 'i'. + */ tb_int_t xm_utf8_offset(lua_State *lua) { + tb_assert_and_check_return_val(lua, 0); + size_t len; tb_char_t const* s = luaL_checklstring(lua, 1, &len); lua_Integer n = luaL_checkinteger(lua, 2); @@ -40,10 +41,12 @@ tb_int_t xm_utf8_offset(lua_State *lua) { posi = xm_utf8_posrelat(luaL_optinteger(lua, 3, posi), len); tb_long_t result = xm_utf8_offset_impl(s, len, n, posi); - if (result == -1) + if (result == -1) { return luaL_argerror(lua, 3, "position out of bounds"); - if (result == -2) + } + if (result == -2) { return luaL_error(lua, "initial position is a continuation byte"); + } if (result == 0) { lua_pushnil(lua); return 1; diff --git a/core/src/xmake/utf8/utf8.c b/core/src/xmake/utf8/utf8.c index 8e164e0a5..d13e7e6a3 100644 --- a/core/src/xmake/utf8/utf8.c +++ b/core/src/xmake/utf8/utf8.c @@ -32,30 +32,37 @@ tb_char_t const* xm_utf8_decode(tb_char_t const* s, xm_utf8_int_t* val, tb_bool_ static const xm_utf8_int_t limits[] = {~(xm_utf8_int_t)0, 0x80, 0x800, 0x10000u, 0x200000u, 0x4000000u}; tb_uint32_t c = (tb_byte_t)s[0]; xm_utf8_int_t res = 0; - if (c < 0x80) + if (c < 0x80) { res = c; - else { + } else { tb_int_t count = 0; for (; c & 0x40; c <<= 1) { tb_uint32_t cc = (tb_byte_t)s[++count]; - if (!xm_utf8_iscont(cc)) + if (!xm_utf8_iscont(cc)) { return tb_null; + } res = (res << 6) | (cc & 0x3F); } res |= ((xm_utf8_int_t)(c & 0x7F) << (count * 5)); - if (count > 5 || res > XM_UTF8_MAXUTF || res < limits[count]) + if (count > 5 || res > XM_UTF8_MAXUTF || res < limits[count]) { return tb_null; + } s += count; } if (strict) { - if (res > XM_UTF8_MAXUNICODE || (0xD800u <= res && res <= 0xDFFFu)) + if (res > XM_UTF8_MAXUNICODE || (0xD800u <= res && res <= 0xDFFFu)) { return tb_null; + } + } + if (val) { + *val = res; } - if (val) *val = res; return s + 1; } tb_size_t xm_utf8_encode(tb_char_t* s, xm_utf8_int_t val) { + tb_assert_and_check_return_val(s, 0); + if (val < 0x80) { s[0] = (tb_char_t)val; return 1; @@ -99,11 +106,15 @@ tb_size_t xm_utf8_encode(tb_char_t* s, xm_utf8_int_t val) { } tb_long_t xm_utf8_len_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb_long_t posj, tb_bool_t strict, tb_size_t* errpos) { + tb_assert_and_check_return_val(s, -1); + tb_long_t n = 0; while (posi <= posj) { tb_char_t const* s1 = xm_utf8_decode(s + posi - 1, tb_null, strict); if (s1 == tb_null) { - if (errpos) *errpos = posi; + if (errpos) { + *errpos = posi; + } return -1; } posi = s1 - s + 1; @@ -113,16 +124,22 @@ tb_long_t xm_utf8_len_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb } tb_long_t xm_utf8_offset_impl(tb_char_t const* s, tb_size_t len, tb_long_t n, tb_long_t posi) { + tb_assert_and_check_return_val(s, -1); + // check - if (1 > posi || --posi > (tb_long_t)len) + if (1 > posi || --posi > (tb_long_t)len) { return -1; // error: position out of bounds + } if (n == 0) { // find beginning of current byte sequence - while (posi > 0 && xm_utf8_iscontp(s + posi)) posi--; + while (posi > 0 && xm_utf8_iscontp(s + posi)) { + posi--; + } } else { - if (xm_utf8_iscontp(s + posi)) + if (xm_utf8_iscontp(s + posi)) { return -2; // error: initial position is a continuation byte + } if (n < 0) { while (n < 0 && posi > 0) { @@ -142,19 +159,29 @@ tb_long_t xm_utf8_offset_impl(tb_char_t const* s, tb_size_t len, tb_long_t n, tb } } - if (n == 0) return posi + 1; + if (n == 0) { + return posi + 1; + } return 0; // nil } tb_bool_t xm_utf8_codepoint_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb_long_t posj, tb_bool_t strict, xm_utf8_codepoint_func_t func, tb_cpointer_t udata) { - if (posi > posj) return tb_true; + tb_assert_and_check_return_val(s, tb_false); + + if (posi > posj) { + return tb_true; + } tb_char_t const* se = s + posj; for (s += posi - 1; s < se;) { xm_utf8_int_t code; s = xm_utf8_decode(s, &code, strict); - if (s == tb_null) return tb_false; - if (func && !func(code, udata)) return tb_false; + if (s == tb_null) { + return tb_false; + } + if (func && !func(code, udata)) { + return tb_false; + } } return tb_true; } diff --git a/core/src/xmake/utf8/utf8.h b/core/src/xmake/utf8/utf8.h index a2ecebca4..3eb1b7428 100644 --- a/core/src/xmake/utf8/utf8.h +++ b/core/src/xmake/utf8/utf8.h @@ -26,9 +26,13 @@ typedef tb_bool_t (*xm_utf8_codepoint_func_t)(xm_utf8_int_t code, tb_cpointer_t * inline interfaces */ static __tb_inline__ tb_long_t xm_utf8_posrelat(tb_long_t pos, tb_size_t len) { - if (pos >= 0) return pos; - else if (0u - (tb_size_t)pos > len) return 0; - else return (tb_long_t)len + pos + 1; + if (pos >= 0) { + return pos; + } else if (0u - (tb_size_t)pos > len) { + return 0; + } else { + return (tb_long_t)len + pos + 1; + } } /* ////////////////////////////////////////////////////////////////////////////////////// diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index ccf9d0b3a..f342c8402 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -22,7 +22,7 @@ function test_codepoint(t) t:are_equal(c1, 65) t:are_equal(c2, 66) t:are_equal(c3, 67) - + -- test range t:are_equal(utf8.codepoint("ABC", 2), 66) t:are_equal(utf8.codepoint("ABC", 2, 2), 66) @@ -33,7 +33,7 @@ function test_offset(t) t:are_equal(utf8.offset("ABC", 2), 2) t:are_equal(utf8.offset("ABC", 4), 4) t:are_equal(utf8.offset("ABC", 5), nil) - + -- "€" is 3 bytes (0xE2 0x82 0xAC) t:are_equal(utf8.offset("€BC", 1), 1) t:are_equal(utf8.offset("€BC", 2), 4) -- cgit v1.3.1 From a9d5fe27bb69cc35022e9c8b80ac9d5f89f570c0 Mon Sep 17 00:00:00 2001 From: ruki Date: Tue, 20 Jan 2026 23:38:02 +0800 Subject: add utf8.sub and lastof --- core/src/xmake/engine.c | 4 ++ core/src/xmake/utf8/lastof.c | 65 ++++++++++++++++++++++++++++++++ core/src/xmake/utf8/sub.c | 88 ++++++++++++++++++++++++++++++++++++++++++++ tests/modules/utf8/test.lua | 44 ++++++++++++++++++++++ xmake/core/base/utf8.lua | 2 + 5 files changed, 203 insertions(+) create mode 100644 core/src/xmake/utf8/lastof.c create mode 100644 core/src/xmake/utf8/sub.c (limited to 'tests/modules/utf8/test.lua') diff --git a/core/src/xmake/engine.c b/core/src/xmake/engine.c index ff7c79061..098a808c8 100644 --- a/core/src/xmake/engine.c +++ b/core/src/xmake/engine.c @@ -285,6 +285,8 @@ tb_int_t xm_utf8_char(lua_State *lua); tb_int_t xm_utf8_codepoint(lua_State *lua); tb_int_t xm_utf8_offset(lua_State *lua); tb_int_t xm_utf8_codes(lua_State *lua); +tb_int_t xm_utf8_sub(lua_State *lua); +tb_int_t xm_utf8_lastof(lua_State *lua); // the string functions tb_int_t xm_string_trim(lua_State *lua); @@ -597,6 +599,8 @@ static luaL_Reg const g_utf8_functions[] = { {"codepoint", xm_utf8_codepoint}, {"len", xm_utf8_len}, {"offset", xm_utf8_offset}, + {"sub", xm_utf8_sub}, + {"lastof", xm_utf8_lastof}, {tb_null, tb_null} }; diff --git a/core/src/xmake/utf8/lastof.c b/core/src/xmake/utf8/lastof.c new file mode 100644 index 000000000..8c5ec7dd3 --- /dev/null +++ b/core/src/xmake/utf8/lastof.c @@ -0,0 +1,65 @@ +/*!A cross-platform build utility based on Lua + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (C) 2015-present, Xmake Open Source Community. + * + * @author ruki + * @file lastof.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +/* utf8.lastof(s, substr) + */ +tb_int_t xm_utf8_lastof(lua_State *lua) { + size_t len; + tb_char_t const* s = luaL_checklstring(lua, 1, &len); + size_t sublen; + tb_char_t const* sub = luaL_checklstring(lua, 2, &sublen); + + if (sublen == 0) { + lua_pushnil(lua); + return 1; + } + + tb_char_t const* p = s; + tb_char_t const* last = tb_null; + + while (1) { + p = tb_strstr(p, sub); + if (!p) break; + last = p; + p += 1; + } + + if (last) { + tb_long_t count = xm_utf8_len_impl(s, len, 1, last - s, tb_true, tb_null); + if (count < 0) { + lua_pushnil(lua); + return 1; + } + lua_pushinteger(lua, count + 1); + } else { + lua_pushnil(lua); + } + return 1; +} diff --git a/core/src/xmake/utf8/sub.c b/core/src/xmake/utf8/sub.c new file mode 100644 index 000000000..c86c60f22 --- /dev/null +++ b/core/src/xmake/utf8/sub.c @@ -0,0 +1,88 @@ +/*!A cross-platform build utility based on Lua + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (C) 2015-present, Xmake Open Source Community. + * + * @author ruki + * @file sub.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +/* utf8.sub(s, i [, j]) + */ +tb_int_t xm_utf8_sub(lua_State *lua) { + size_t len; + tb_char_t const* s = luaL_checklstring(lua, 1, &len); + lua_Integer i = luaL_checkinteger(lua, 2); + lua_Integer j = luaL_optinteger(lua, 3, -1); + + // map i (char index) to byte offset + tb_long_t start_byte = 0; + if (i > 0) { + start_byte = xm_utf8_offset_impl(s, len, i, 1); + } else if (i < 0) { + start_byte = xm_utf8_offset_impl(s, len, i, len + 1); + } else { + start_byte = 1; + } + + if (start_byte == -1) { + if (i > 0) { + lua_pushliteral(lua, ""); + return 1; + } else { + start_byte = 1; + } + } else if (start_byte == 0) { + if (i < 0) { + start_byte = 1; + } else { + lua_pushliteral(lua, ""); + return 1; + } + } + + // map j (char index) to byte offset (end) + tb_long_t end_byte = 0; + if (j >= 0) { + end_byte = xm_utf8_offset_impl(s, len, j + 1, 1); + } else { + end_byte = xm_utf8_offset_impl(s, len, j + 1, len + 1); + } + + if (end_byte == -1) { + if (j >= 0) end_byte = len + 1; + else end_byte = 1; + } else if (end_byte == 0) { + if (j >= 0) end_byte = len + 1; + else end_byte = 1; + } + + if (end_byte <= start_byte) { + lua_pushliteral(lua, ""); + return 1; + } + + lua_pushlstring(lua, s + start_byte - 1, end_byte - start_byte); + return 1; +} diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index f342c8402..51da441d9 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -62,3 +62,47 @@ end function test_charpattern(t) t:require(utf8.charpattern) end + +function test_sub(t) + t:are_equal(utf8.sub("ABC", 1, 1), "A") + t:are_equal(utf8.sub("ABC", 2, 2), "B") + t:are_equal(utf8.sub("ABC", 1, 2), "AB") + t:are_equal(utf8.sub("你好", 1, 1), "你") + t:are_equal(utf8.sub("你好", 2, 2), "好") + t:are_equal(utf8.sub("你好", 1, 2), "你好") + + -- mixed + t:are_equal(utf8.sub("A你好B", 2, 3), "你好") + t:are_equal(utf8.sub("A你好B", 1, 3), "A你好") + t:are_equal(utf8.sub("A你好B", 2, 4), "你好B") + + -- negative + t:are_equal(utf8.sub("ABC", -1), "C") + t:are_equal(utf8.sub("ABC", -2), "BC") + t:are_equal(utf8.sub("你好", -1), "好") + t:are_equal(utf8.sub("你好", -2), "你好") + t:are_equal(utf8.sub("你好", 1, -1), "你好") + t:are_equal(utf8.sub("你好", 1, -2), "你") + + -- out of bounds + t:are_equal(utf8.sub("ABC", 4), "") + t:are_equal(utf8.sub("ABC", 1, 5), "ABC") + t:are_equal(utf8.sub("ABC", 0), "ABC") + t:are_equal(utf8.sub("ABC", -10), "ABC") +end + +function test_lastof(t) + t:are_equal(utf8.lastof("ABC", "A"), 1) + t:are_equal(utf8.lastof("ABC", "B"), 2) + t:are_equal(utf8.lastof("ABC", "C"), 3) + t:are_equal(utf8.lastof("ABCA", "A"), 4) + + t:are_equal(utf8.lastof("你好", "你"), 1) + t:are_equal(utf8.lastof("你好", "好"), 2) + t:are_equal(utf8.lastof("你好你", "你"), 3) + + t:are_equal(utf8.lastof("A你好A", "A"), 4) + t:are_equal(utf8.lastof("A你好A", "好"), 3) + + t:are_equal(utf8.lastof("ABC", "D"), nil) +end diff --git a/xmake/core/base/utf8.lua b/xmake/core/base/utf8.lua index 1a1edc0ea..7351475f8 100644 --- a/xmake/core/base/utf8.lua +++ b/xmake/core/base/utf8.lua @@ -29,6 +29,8 @@ local utf8 = utf8 or {} -- @interface utf8.codepoint(s [, i [, j]]) -- @interface utf8.offset(s, n [, i]) -- @interface utf8.codes(s [, lax]) +-- @interface utf8.sub(s, i [, j]) +-- @interface utf8.lastof(s, substr) -- -- the char pattern -- cgit v1.3.1 From 23710584abfd0bb9c12c207066c145e6feee81e5 Mon Sep 17 00:00:00 2001 From: ruki Date: Tue, 20 Jan 2026 23:43:50 +0800 Subject: fix utf8.find --- core/src/xmake/engine.c | 2 + core/src/xmake/utf8/find.c | 98 +++++++++++++++++++++++++++++++++++++++++++++ tests/modules/utf8/test.lua | 22 ++++++++++ xmake/core/base/utf8.lua | 1 + 4 files changed, 123 insertions(+) create mode 100644 core/src/xmake/utf8/find.c (limited to 'tests/modules/utf8/test.lua') diff --git a/core/src/xmake/engine.c b/core/src/xmake/engine.c index 098a808c8..9ef4ec68f 100644 --- a/core/src/xmake/engine.c +++ b/core/src/xmake/engine.c @@ -287,6 +287,7 @@ tb_int_t xm_utf8_offset(lua_State *lua); tb_int_t xm_utf8_codes(lua_State *lua); tb_int_t xm_utf8_sub(lua_State *lua); tb_int_t xm_utf8_lastof(lua_State *lua); +tb_int_t xm_utf8_find(lua_State *lua); // the string functions tb_int_t xm_string_trim(lua_State *lua); @@ -601,6 +602,7 @@ static luaL_Reg const g_utf8_functions[] = { {"offset", xm_utf8_offset}, {"sub", xm_utf8_sub}, {"lastof", xm_utf8_lastof}, + {"find", xm_utf8_find}, {tb_null, tb_null} }; diff --git a/core/src/xmake/utf8/find.c b/core/src/xmake/utf8/find.c new file mode 100644 index 000000000..2fbe7bf0c --- /dev/null +++ b/core/src/xmake/utf8/find.c @@ -0,0 +1,98 @@ +/*!A cross-platform build utility based on Lua + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (C) 2015-present, Xmake Open Source Community. + * + * @author ruki + * @file find.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +/* utf8.find(s, target [, init]) + */ +tb_int_t xm_utf8_find(lua_State *lua) { + tb_assert_and_check_return_val(lua, 0); + + size_t len; + tb_char_t const* s = luaL_checklstring(lua, 1, &len); + size_t sublen; + tb_char_t const* sub = luaL_checklstring(lua, 2, &sublen); + lua_Integer init = luaL_optinteger(lua, 3, 1); + + if (init > (lua_Integer)len) { + lua_pushnil(lua); + return 1; + } + + if (sublen == 0) { + if (init <= 1) { + lua_pushinteger(lua, 1); + lua_pushinteger(lua, 0); + return 2; + } else { + lua_pushinteger(lua, init); + lua_pushinteger(lua, init - 1); + return 2; + } + } + + tb_long_t start_byte = 0; + if (init > 0) { + start_byte = xm_utf8_offset_impl(s, len, init, 1); + } else if (init < 0) { + start_byte = xm_utf8_offset_impl(s, len, init, len + 1); + } else { + start_byte = 1; + } + + if (start_byte <= 0) { + lua_pushnil(lua); + return 1; + } + + tb_char_t const* p = tb_strstr(s + start_byte - 1, sub); + if (!p) { + lua_pushnil(lua); + return 1; + } + + tb_long_t found_byte_start = p - s + 1; + + tb_long_t char_start = 0; + if (found_byte_start > 1) { + char_start = xm_utf8_len_impl(s, len, 1, found_byte_start - 1, tb_true, tb_null); + if (char_start < 0) { + lua_pushnil(lua); return 1; + } + } + char_start += 1; + + tb_long_t match_char_len = xm_utf8_len_impl(s, len, found_byte_start, found_byte_start + sublen - 1, tb_true, tb_null); + if (match_char_len < 0) { + lua_pushnil(lua); return 1; + } + + lua_pushinteger(lua, char_start); + lua_pushinteger(lua, char_start + match_char_len - 1); + return 2; +} diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index 51da441d9..8ec9495fb 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -106,3 +106,25 @@ function test_lastof(t) t:are_equal(utf8.lastof("ABC", "D"), nil) end + +function test_find(t) + t:are_equal({utf8.find("A", "A")}, {1, 1}) + t:are_equal({utf8.find("ABC", "A")}, {1, 1}) + t:are_equal({utf8.find("ABC", "B")}, {2, 2}) + t:are_equal({utf8.find("ABC", "C")}, {3, 3}) + t:are_equal({utf8.find("ABCA", "A")}, {1, 1}) + t:are_equal({utf8.find("ABCA", "A", 2)}, {4, 4}) + t:are_equal({utf8.find("ABCA", "A", 1)}, {1, 1}) + + t:are_equal({utf8.find("你好", "你")}, {1, 1}) + t:are_equal({utf8.find("你好", "好")}, {2, 2}) + t:are_equal({utf8.find("你好你", "你", 2)}, {3, 3}) + + t:are_equal({utf8.find("A你好A", "A")}, {1, 1}) + t:are_equal({utf8.find("A你好A", "A", 2)}, {4, 4}) + t:are_equal({utf8.find("A你好A", "好")}, {3, 3}) + + t:are_equal(utf8.find("ABC", "D"), nil) + t:are_equal({utf8.find("ABC", "")}, {1, 0}) + t:are_equal({utf8.find("ABC", "", 2)}, {2, 1}) +end diff --git a/xmake/core/base/utf8.lua b/xmake/core/base/utf8.lua index 7351475f8..2878404fb 100644 --- a/xmake/core/base/utf8.lua +++ b/xmake/core/base/utf8.lua @@ -31,6 +31,7 @@ local utf8 = utf8 or {} -- @interface utf8.codes(s [, lax]) -- @interface utf8.sub(s, i [, j]) -- @interface utf8.lastof(s, substr) +-- @interface utf8.find(s, target [, init]) -- -- the char pattern -- cgit v1.3.1 From 6eade15143027afad000a48c941f1e9168104a54 Mon Sep 17 00:00:00 2001 From: ruki Date: Tue, 20 Jan 2026 23:56:57 +0800 Subject: improve utf8.sub and find --- core/src/xmake/utf8/find.c | 130 +++++++++++++++++++++++++++----------------- core/src/xmake/utf8/sub.c | 49 ++--------------- core/src/xmake/utf8/utf8.c | 105 +++++++++++++++++++++++++++++++++++ core/src/xmake/utf8/utf8.h | 2 + tests/modules/utf8/test.lua | 37 +++++++------ 5 files changed, 213 insertions(+), 110 deletions(-) (limited to 'tests/modules/utf8/test.lua') diff --git a/core/src/xmake/utf8/find.c b/core/src/xmake/utf8/find.c index 2fbe7bf0c..8f8a5a97f 100644 --- a/core/src/xmake/utf8/find.c +++ b/core/src/xmake/utf8/find.c @@ -28,71 +28,103 @@ * implementation */ -/* utf8.find(s, target [, init]) - */ -tb_int_t xm_utf8_find(lua_State *lua) { - tb_assert_and_check_return_val(lua, 0); - - size_t len; - tb_char_t const* s = luaL_checklstring(lua, 1, &len); - size_t sublen; - tb_char_t const* sub = luaL_checklstring(lua, 2, &sublen); - lua_Integer init = luaL_optinteger(lua, 3, 1); - - if (init > (lua_Integer)len) { - lua_pushnil(lua); - return 1; - } - - if (sublen == 0) { - if (init <= 1) { - lua_pushinteger(lua, 1); - lua_pushinteger(lua, 0); - return 2; - } else { - lua_pushinteger(lua, init); - lua_pushinteger(lua, init - 1); - return 2; - } +static tb_int_t xm_utf8_find_impl_plain(lua_State* lua, tb_char_t const* s, size_t len, tb_char_t const* sub, size_t sublen, lua_Integer init) { + tb_long_t char_end = 0; + tb_long_t char_start = xm_utf8_find_impl(s, len, sub, sublen, init, &char_end); + if (char_start > 0) { + lua_pushinteger(lua, char_start); + lua_pushinteger(lua, char_end); + return 2; } + lua_pushnil(lua); + return 1; +} - tb_long_t start_byte = 0; +static tb_int_t xm_utf8_find_impl_pattern(lua_State* lua, tb_char_t const* s, size_t len, lua_Integer init) { + int base = lua_gettop(lua); + tb_long_t byte_init = 1; if (init > 0) { - start_byte = xm_utf8_offset_impl(s, len, init, 1); + if (init > 1) { + byte_init = xm_utf8_offset_impl(s, len, init, 1); + if (byte_init <= 0) { + lua_pushnil(lua); + return 1; + } + } } else if (init < 0) { - start_byte = xm_utf8_offset_impl(s, len, init, len + 1); - } else { - start_byte = 1; + byte_init = xm_utf8_offset_impl(s, len, init, len + 1); + if (byte_init <= 0) { + lua_pushnil(lua); + return 1; + } } - if (start_byte <= 0) { + lua_getglobal(lua, "string"); + lua_getfield(lua, -1, "find"); + lua_pushvalue(lua, 1); // s + lua_pushvalue(lua, 2); // pattern + lua_pushinteger(lua, byte_init); // init (byte) + lua_pushboolean(lua, 0); // plain + + lua_call(lua, 4, LUA_MULTRET); + + // Stack: [args, string_table, results...] + int nres = lua_gettop(lua) - (base + 1); + if (nres <= 0 || lua_isnil(lua, base + 2)) { lua_pushnil(lua); + lua_remove(lua, base + 1); return 1; } - tb_char_t const* p = tb_strstr(s + start_byte - 1, sub); - if (!p) { - lua_pushnil(lua); - return 1; - } - - tb_long_t found_byte_start = p - s + 1; + lua_Integer b_start = lua_tointeger(lua, base + 2); + lua_Integer b_end = lua_tointeger(lua, base + 3); - tb_long_t char_start = 0; - if (found_byte_start > 1) { - char_start = xm_utf8_len_impl(s, len, 1, found_byte_start - 1, tb_true, tb_null); - if (char_start < 0) { - lua_pushnil(lua); return 1; + tb_long_t char_start = 1; + if (b_start > 1) { + tb_long_t count = xm_utf8_len_impl(s, len, 1, b_start - 1, tb_true, tb_null); + if (count < 0) { + lua_pushnil(lua); + lua_remove(lua, base + 1); + return 1; } + char_start = count + 1; } - char_start += 1; - tb_long_t match_char_len = xm_utf8_len_impl(s, len, found_byte_start, found_byte_start + sublen - 1, tb_true, tb_null); - if (match_char_len < 0) { - lua_pushnil(lua); return 1; + tb_long_t match_char_len = 0; + if (b_end >= b_start) { + match_char_len = xm_utf8_len_impl(s, len, b_start, b_end, tb_true, tb_null); + if (match_char_len < 0) { + lua_pushnil(lua); + lua_remove(lua, base + 1); + return 1; + } } lua_pushinteger(lua, char_start); + lua_replace(lua, base + 2); lua_pushinteger(lua, char_start + match_char_len - 1); - return 2; + lua_replace(lua, base + 3); + + lua_remove(lua, base + 1); + + return nres; +} + +/* utf8.find(s, target [, init [, plain]]) + */ +tb_int_t xm_utf8_find(lua_State *lua) { + tb_assert_and_check_return_val(lua, 0); + + size_t len; + tb_char_t const* s = luaL_checklstring(lua, 1, &len); + size_t sublen; + tb_char_t const* sub = luaL_checklstring(lua, 2, &sublen); + lua_Integer init = luaL_optinteger(lua, 3, 1); + tb_int_t plain = lua_toboolean(lua, 4); + + if (plain) { + return xm_utf8_find_impl_plain(lua, s, len, sub, sublen, init); + } else { + return xm_utf8_find_impl_pattern(lua, s, len, init); + } } diff --git a/core/src/xmake/utf8/sub.c b/core/src/xmake/utf8/sub.c index c86c60f22..2268708c6 100644 --- a/core/src/xmake/utf8/sub.c +++ b/core/src/xmake/utf8/sub.c @@ -36,53 +36,12 @@ tb_int_t xm_utf8_sub(lua_State *lua) { lua_Integer i = luaL_checkinteger(lua, 2); lua_Integer j = luaL_optinteger(lua, 3, -1); - // map i (char index) to byte offset - tb_long_t start_byte = 0; - if (i > 0) { - start_byte = xm_utf8_offset_impl(s, len, i, 1); - } else if (i < 0) { - start_byte = xm_utf8_offset_impl(s, len, i, len + 1); + tb_size_t sublen = 0; + tb_char_t const* sub = xm_utf8_sub_impl(s, len, i, j, &sublen); + if (sub) { + lua_pushlstring(lua, sub, sublen); } else { - start_byte = 1; - } - - if (start_byte == -1) { - if (i > 0) { - lua_pushliteral(lua, ""); - return 1; - } else { - start_byte = 1; - } - } else if (start_byte == 0) { - if (i < 0) { - start_byte = 1; - } else { - lua_pushliteral(lua, ""); - return 1; - } - } - - // map j (char index) to byte offset (end) - tb_long_t end_byte = 0; - if (j >= 0) { - end_byte = xm_utf8_offset_impl(s, len, j + 1, 1); - } else { - end_byte = xm_utf8_offset_impl(s, len, j + 1, len + 1); - } - - if (end_byte == -1) { - if (j >= 0) end_byte = len + 1; - else end_byte = 1; - } else if (end_byte == 0) { - if (j >= 0) end_byte = len + 1; - else end_byte = 1; - } - - if (end_byte <= start_byte) { lua_pushliteral(lua, ""); - return 1; } - - lua_pushlstring(lua, s + start_byte - 1, end_byte - start_byte); return 1; } diff --git a/core/src/xmake/utf8/utf8.c b/core/src/xmake/utf8/utf8.c index d13e7e6a3..3b54a0f37 100644 --- a/core/src/xmake/utf8/utf8.c +++ b/core/src/xmake/utf8/utf8.c @@ -185,3 +185,108 @@ tb_bool_t xm_utf8_codepoint_impl(tb_char_t const* s, tb_size_t len, tb_long_t po } return tb_true; } + +tb_long_t xm_utf8_find_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen, tb_long_t init, tb_long_t* pchar_end) { + tb_assert_and_check_return_val(s && sub, 0); + + if (sublen == 0) { + if (init > (tb_long_t)len + 1) init = len + 1; + + tb_long_t start_byte = 1; + if (init > 0) { + start_byte = xm_utf8_offset_impl(s, len, init, 1); + } else if (init < 0) { + start_byte = xm_utf8_offset_impl(s, len, init, len + 1); + } + if (start_byte <= 0) start_byte = 1; + + tb_long_t char_pos = 1; + if (start_byte > 1) { + tb_long_t c = xm_utf8_len_impl(s, len, 1, start_byte - 1, tb_true, tb_null); + if (c >= 0) char_pos = c + 1; + } + + if (pchar_end) *pchar_end = char_pos - 1; + return char_pos; + } + + tb_long_t start_byte = 1; + if (init > 0) { + start_byte = xm_utf8_offset_impl(s, len, init, 1); + } else if (init < 0) { + start_byte = xm_utf8_offset_impl(s, len, init, len + 1); + } + if (start_byte <= 0) return 0; + + tb_char_t const* p = tb_strstr(s + start_byte - 1, sub); + if (!p) return 0; + + tb_long_t found_byte_start = p - s + 1; + + tb_long_t char_start = 1; + if (found_byte_start > 1) { + tb_long_t c = xm_utf8_len_impl(s, len, 1, found_byte_start - 1, tb_true, tb_null); + if (c < 0) return 0; + char_start = c + 1; + } + + if (pchar_end) { + tb_long_t match_len = xm_utf8_len_impl(s, len, found_byte_start, found_byte_start + sublen - 1, tb_true, tb_null); + if (match_len < 0) return 0; + *pchar_end = char_start + match_len - 1; + } + + return char_start; +} + +tb_char_t const* xm_utf8_sub_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, tb_size_t* psublen) { + tb_assert_and_check_return_val(s && psublen, tb_null); + *psublen = 0; + + // map i (char index) to byte offset + tb_long_t start_byte = 0; + if (i > 0) { + start_byte = xm_utf8_offset_impl(s, len, i, 1); + } else if (i < 0) { + start_byte = xm_utf8_offset_impl(s, len, i, len + 1); + } else { + start_byte = 1; + } + + if (start_byte == -1) { + if (i > 0) { + return ""; + } else { + start_byte = 1; + } + } else if (start_byte == 0) { + if (i < 0) { + start_byte = 1; + } else { + return ""; + } + } + + // map j (char index) to byte offset (end) + tb_long_t end_byte = 0; + if (j >= 0) { + end_byte = xm_utf8_offset_impl(s, len, j + 1, 1); + } else { + end_byte = xm_utf8_offset_impl(s, len, j + 1, len + 1); + } + + if (end_byte == -1) { + if (j >= 0) end_byte = len + 1; + else end_byte = 1; + } else if (end_byte == 0) { + if (j >= 0) end_byte = len + 1; + else end_byte = 1; + } + + if (end_byte <= start_byte) { + return ""; + } + + *psublen = end_byte - start_byte; + return s + start_byte - 1; +} diff --git a/core/src/xmake/utf8/utf8.h b/core/src/xmake/utf8/utf8.h index 3eb1b7428..8a61afe10 100644 --- a/core/src/xmake/utf8/utf8.h +++ b/core/src/xmake/utf8/utf8.h @@ -45,5 +45,7 @@ tb_size_t xm_utf8_encode(tb_char_t* s, xm_utf8_int_t val); tb_long_t xm_utf8_len_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb_long_t posj, tb_bool_t strict, tb_size_t* errpos); tb_long_t xm_utf8_offset_impl(tb_char_t const* s, tb_size_t len, tb_long_t n, tb_long_t posi); tb_bool_t xm_utf8_codepoint_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb_long_t posj, tb_bool_t strict, xm_utf8_codepoint_func_t func, tb_cpointer_t udata); +tb_long_t xm_utf8_find_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen, tb_long_t init, tb_long_t* pchar_end); +tb_char_t const* xm_utf8_sub_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, tb_size_t* psublen); #endif diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index 8ec9495fb..51b21d1c3 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -108,23 +108,28 @@ function test_lastof(t) end function test_find(t) - t:are_equal({utf8.find("A", "A")}, {1, 1}) - t:are_equal({utf8.find("ABC", "A")}, {1, 1}) + -- plain + t:are_equal({utf8.find("你好", "你", 1, true)}, {1, 1}) + t:are_equal({utf8.find("你好你", "你", 2, true)}, {3, 3}) + t:are_equal({utf8.find("A你好A", "A", 2, true)}, {4, 4}) + t:are_equal(utf8.find("ABC", "D", 1, true), nil) + t:are_equal({utf8.find("ABC", "", 1, true)}, {1, 0}) + + -- pattern matching (default) t:are_equal({utf8.find("ABC", "B")}, {2, 2}) - t:are_equal({utf8.find("ABC", "C")}, {3, 3}) - t:are_equal({utf8.find("ABCA", "A")}, {1, 1}) - t:are_equal({utf8.find("ABCA", "A", 2)}, {4, 4}) - t:are_equal({utf8.find("ABCA", "A", 1)}, {1, 1}) + t:are_equal({utf8.find("ABC", "([BC])")}, {2, 2, "B"}) -- Capture + t:are_equal({utf8.find("ABC", "(.)(.)")}, {1, 2, "A", "B"}) - t:are_equal({utf8.find("你好", "你")}, {1, 1}) + -- UTF-8 pattern matching (byte-based) + -- "你" is 3 bytes. "." matches first byte. + t:are_equal({utf8.find("你好", ".")}, {1, 1}) + + -- "你好", "好" -> bytes 4-6. t:are_equal({utf8.find("你好", "好")}, {2, 2}) - t:are_equal({utf8.find("你好你", "你", 2)}, {3, 3}) - - t:are_equal({utf8.find("A你好A", "A")}, {1, 1}) - t:are_equal({utf8.find("A你好A", "A", 2)}, {4, 4}) - t:are_equal({utf8.find("A你好A", "好")}, {3, 3}) - - t:are_equal(utf8.find("ABC", "D"), nil) - t:are_equal({utf8.find("ABC", "")}, {1, 0}) - t:are_equal({utf8.find("ABC", "", 2)}, {2, 1}) + + -- "你好", "..." (3 dots) -> matches 3 bytes (whole "你"). + t:are_equal({utf8.find("你好", "...")}, {1, 1}) + + -- "A你好", "%w" -> matches "A". + t:are_equal({utf8.find("A你好", "%w")}, {1, 1}) end -- cgit v1.3.1 From 91843ffb82564d29ce9a9e386f2c9292620a5ba9 Mon Sep 17 00:00:00 2001 From: ruki Date: Wed, 21 Jan 2026 00:00:00 +0800 Subject: improve utf8.lastof --- core/src/xmake/utf8/find.c | 6 ++++- core/src/xmake/utf8/lastof.c | 54 +++++++++++++++++++++++++++----------------- core/src/xmake/utf8/utf8.c | 23 +++++++++++++++++++ core/src/xmake/utf8/utf8.h | 1 + tests/modules/utf8/test.lua | 9 ++++++++ xmake/core/base/utf8.lua | 4 ++-- 6 files changed, 73 insertions(+), 24 deletions(-) (limited to 'tests/modules/utf8/test.lua') diff --git a/core/src/xmake/utf8/find.c b/core/src/xmake/utf8/find.c index 8f8a5a97f..cc57f1e69 100644 --- a/core/src/xmake/utf8/find.c +++ b/core/src/xmake/utf8/find.c @@ -25,7 +25,7 @@ #include "utf8.h" /* ////////////////////////////////////////////////////////////////////////////////////// - * implementation + * private implementation */ static tb_int_t xm_utf8_find_impl_plain(lua_State* lua, tb_char_t const* s, size_t len, tb_char_t const* sub, size_t sublen, lua_Integer init) { @@ -110,6 +110,10 @@ static tb_int_t xm_utf8_find_impl_pattern(lua_State* lua, tb_char_t const* s, si return nres; } +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + /* utf8.find(s, target [, init [, plain]]) */ tb_int_t xm_utf8_find(lua_State *lua) { diff --git a/core/src/xmake/utf8/lastof.c b/core/src/xmake/utf8/lastof.c index 8c5ec7dd3..8e22b16ba 100644 --- a/core/src/xmake/utf8/lastof.c +++ b/core/src/xmake/utf8/lastof.c @@ -28,38 +28,50 @@ * implementation */ -/* utf8.lastof(s, substr) +/* utf8.lastof(s, pattern, plain) */ tb_int_t xm_utf8_lastof(lua_State *lua) { size_t len; tb_char_t const* s = luaL_checklstring(lua, 1, &len); size_t sublen; tb_char_t const* sub = luaL_checklstring(lua, 2, &sublen); + tb_int_t plain = lua_toboolean(lua, 3); - if (sublen == 0) { - lua_pushnil(lua); + if (plain) { + tb_long_t char_pos = xm_utf8_lastof_impl(s, len, sub, sublen); + if (char_pos > 0) { + lua_pushinteger(lua, char_pos); + } else { + lua_pushnil(lua); + } return 1; - } + } else { + lua_getglobal(lua, "string"); + lua_getfield(lua, -1, "lastof"); + lua_pushvalue(lua, 1); // s + lua_pushvalue(lua, 2); // pattern + lua_pushboolean(lua, 0); // plain = false - tb_char_t const* p = s; - tb_char_t const* last = tb_null; - - while (1) { - p = tb_strstr(p, sub); - if (!p) break; - last = p; - p += 1; - } + lua_call(lua, 3, 1); - if (last) { - tb_long_t count = xm_utf8_len_impl(s, len, 1, last - s, tb_true, tb_null); - if (count < 0) { - lua_pushnil(lua); - return 1; + // Stack: [args, string_table, result] + if (lua_isnil(lua, -1)) { + return 1; + } + + lua_Integer byte_pos = lua_tointeger(lua, -1); + if (byte_pos > 0) { + tb_long_t count = xm_utf8_len_impl(s, len, 1, byte_pos - 1, tb_true, tb_null); + if (count >= 0) { + if (xm_utf8_iscont(s[byte_pos - 1])) { + lua_pushinteger(lua, count); + } else { + lua_pushinteger(lua, count + 1); + } + return 1; + } } - lua_pushinteger(lua, count + 1); - } else { lua_pushnil(lua); + return 1; } - return 1; } diff --git a/core/src/xmake/utf8/utf8.c b/core/src/xmake/utf8/utf8.c index 3b54a0f37..3b23532c5 100644 --- a/core/src/xmake/utf8/utf8.c +++ b/core/src/xmake/utf8/utf8.c @@ -239,6 +239,29 @@ tb_long_t xm_utf8_find_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* return char_start; } +tb_long_t xm_utf8_lastof_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen) { + tb_assert_and_check_return_val(s && sub, 0); + + if (sublen == 0) return 0; + + tb_char_t const* p = s; + tb_char_t const* last = tb_null; + + while (1) { + p = tb_strstr(p, sub); + if (!p) break; + last = p; + p += 1; + } + + if (last) { + tb_long_t count = xm_utf8_len_impl(s, len, 1, last - s, tb_true, tb_null); + if (count < 0) return 0; + return count + 1; + } + return 0; +} + tb_char_t const* xm_utf8_sub_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, tb_size_t* psublen) { tb_assert_and_check_return_val(s && psublen, tb_null); *psublen = 0; diff --git a/core/src/xmake/utf8/utf8.h b/core/src/xmake/utf8/utf8.h index 8a61afe10..f50b21e44 100644 --- a/core/src/xmake/utf8/utf8.h +++ b/core/src/xmake/utf8/utf8.h @@ -46,6 +46,7 @@ tb_long_t xm_utf8_len_impl(tb_char_t const* s, tb_size_t len, tb_long_ tb_long_t xm_utf8_offset_impl(tb_char_t const* s, tb_size_t len, tb_long_t n, tb_long_t posi); tb_bool_t xm_utf8_codepoint_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb_long_t posj, tb_bool_t strict, xm_utf8_codepoint_func_t func, tb_cpointer_t udata); tb_long_t xm_utf8_find_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen, tb_long_t init, tb_long_t* pchar_end); +tb_long_t xm_utf8_lastof_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen); tb_char_t const* xm_utf8_sub_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, tb_size_t* psublen); #endif diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index 51b21d1c3..af0c98997 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -105,6 +105,15 @@ function test_lastof(t) t:are_equal(utf8.lastof("A你好A", "好"), 3) t:are_equal(utf8.lastof("ABC", "D"), nil) + + -- plain + t:are_equal(utf8.lastof("ABC", "A", true), 1) + t:are_equal(utf8.lastof("ABC", "B", true), 2) + t:are_equal(utf8.lastof("ABC", ".", true), nil) + + -- pattern + t:are_equal(utf8.lastof("ABC", "."), 3) + t:are_equal(utf8.lastof("你好", "."), 2) end function test_find(t) diff --git a/xmake/core/base/utf8.lua b/xmake/core/base/utf8.lua index 2878404fb..dfcfdfbf0 100644 --- a/xmake/core/base/utf8.lua +++ b/xmake/core/base/utf8.lua @@ -30,8 +30,8 @@ local utf8 = utf8 or {} -- @interface utf8.offset(s, n [, i]) -- @interface utf8.codes(s [, lax]) -- @interface utf8.sub(s, i [, j]) --- @interface utf8.lastof(s, substr) --- @interface utf8.find(s, target [, init]) +-- @interface utf8.lastof(s, pattern [, plain]) +-- @interface utf8.find(s, pattern [, init [, plain]]) -- -- the char pattern -- cgit v1.3.1 From 4c08bae6d49e37102c0b889ff7afcc9f319435db Mon Sep 17 00:00:00 2001 From: ruki Date: Wed, 21 Jan 2026 00:38:05 +0800 Subject: add utf8.width --- core/src/xmake/engine.c | 4 ++ core/src/xmake/utf8/utf8.c | 116 ++++++++++++++++++++++++++++++++++++++++++++ core/src/xmake/utf8/utf8.h | 2 + core/src/xmake/utf8/width.c | 45 +++++++++++++++++ tests/modules/utf8/test.lua | 32 ++++++++++++ xmake/core/base/utf8.lua | 1 + 6 files changed, 200 insertions(+) create mode 100644 core/src/xmake/utf8/width.c (limited to 'tests/modules/utf8/test.lua') diff --git a/core/src/xmake/engine.c b/core/src/xmake/engine.c index 9ef4ec68f..146cdd17a 100644 --- a/core/src/xmake/engine.c +++ b/core/src/xmake/engine.c @@ -288,6 +288,7 @@ tb_int_t xm_utf8_codes(lua_State *lua); tb_int_t xm_utf8_sub(lua_State *lua); tb_int_t xm_utf8_lastof(lua_State *lua); tb_int_t xm_utf8_find(lua_State *lua); +tb_int_t xm_utf8_width(lua_State *lua); // the string functions tb_int_t xm_string_trim(lua_State *lua); @@ -603,6 +604,9 @@ static luaL_Reg const g_utf8_functions[] = { {"sub", xm_utf8_sub}, {"lastof", xm_utf8_lastof}, {"find", xm_utf8_find}, + {"width", xm_utf8_width}, + {"wcwidth", xm_utf8_width}, + {"wcswidth", xm_utf8_width}, {tb_null, tb_null} }; diff --git a/core/src/xmake/utf8/utf8.c b/core/src/xmake/utf8/utf8.c index 3748e0975..168ffe60a 100644 --- a/core/src/xmake/utf8/utf8.c +++ b/core/src/xmake/utf8/utf8.c @@ -125,6 +125,122 @@ tb_long_t xm_utf8_charpos(tb_char_t const* s, tb_size_t len, tb_long_t byte_pos) /* ////////////////////////////////////////////////////////////////////////////////////// * implementation interfaces */ +static struct { xm_utf8_int_t first; xm_utf8_int_t last; } const g_non_spacing[] = +{ + {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, + {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, + {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, + {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, + {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, + {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, + {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, + {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, + {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, + {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, + {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, + {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, + {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, + {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, + {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, + {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, + {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, + {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, + {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, + {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, + {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, + {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, + {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, + {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, + {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, + {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, + {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, + {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, + {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, + {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, + {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, + {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, + {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, + {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, + {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, + {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, + {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, + {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, + {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, + {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, + {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, + {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, + {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, + {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, + {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, + {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, + {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, + {0xE0100, 0xE01EF} +}; + +tb_long_t xm_utf8_charwidth(xm_utf8_int_t val) { + + // test for 8-bit control characters + if (val == 0) return 0; + if (val < 32 || (val >= 0x7f && val < 0xa0)) { + if (val == 0x09) return 4; // TAB + if (val == 0x08) return -1; // BS + return 0; // other control chars + } + + // binary search in table of non-spacing characters + tb_long_t min = 0; + tb_long_t max = tb_arrayn(g_non_spacing) - 1; + if (val >= g_non_spacing[0].first && val <= g_non_spacing[max].last) { + while (max >= min) { + tb_long_t mid = (min + max) / 2; + if (val > g_non_spacing[mid].last) { + min = mid + 1; + } else if (val < g_non_spacing[mid].first) { + max = mid - 1; + } else { + return 0; + } + } + } + + if (val >= 0x1100 && (val <= 0x115f || // Hangul Jamo init. consonants + val == 0x2329 || val == 0x232a || + (val >= 0x2e80 && val <= 0xa4cf && + val != 0x303f) || // CJK ... Yi + (val >= 0xac00 && val <= 0xd7a3) || // Hangul Syllables + (val >= 0xf900 && val <= 0xfaff) || // CJK Compatibility Ideographs + (val >= 0xfe10 && val <= 0xfe19) || // Vertical forms + (val >= 0xfe30 && val <= 0xfe6f) || // CJK Compatibility Forms + (val >= 0xff00 && val <= 0xff60) || // Fullwidth Forms + (val >= 0xffe0 && val <= 0xffe6) || + (val >= 0x20000 && val <= 0x2fffd) || + (val >= 0x30000 && val <= 0x3fffd))) { + return 2; + } + + return 1; +} + +tb_long_t xm_utf8_strwidth(tb_char_t const* s, tb_size_t len) { + tb_assert_and_check_return_val(s, -1); + + tb_long_t width = 0; + tb_char_t const* p = s; + tb_char_t const* e = s + len; + while (p < e) { + xm_utf8_int_t val; + tb_char_t const* next = xm_utf8_decode(p, &val, tb_true); + if (next) { + tb_long_t w = xm_utf8_charwidth(val); + if (w < 0) return -1; + width += w; + p = next; + } else { + p++; // invalid byte, skip + } + } + return width; +} tb_long_t xm_utf8_len_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb_long_t posj, tb_bool_t strict, tb_size_t* errpos) { tb_assert_and_check_return_val(s, -1); diff --git a/core/src/xmake/utf8/utf8.h b/core/src/xmake/utf8/utf8.h index f1d374af3..d6d884240 100644 --- a/core/src/xmake/utf8/utf8.h +++ b/core/src/xmake/utf8/utf8.h @@ -42,6 +42,8 @@ static __tb_inline__ tb_long_t xm_utf8_posrelat(tb_long_t pos, tb_size_t len) { tb_char_t const* xm_utf8_decode(tb_char_t const* s, xm_utf8_int_t* val, tb_bool_t strict); tb_size_t xm_utf8_encode(tb_char_t* s, xm_utf8_int_t val); tb_long_t xm_utf8_charpos(tb_char_t const* s, tb_size_t len, tb_long_t byte_pos); +tb_long_t xm_utf8_charwidth(xm_utf8_int_t val); +tb_long_t xm_utf8_strwidth(tb_char_t const* s, tb_size_t len); /* ////////////////////////////////////////////////////////////////////////////////////// * implementation interfaces diff --git a/core/src/xmake/utf8/width.c b/core/src/xmake/utf8/width.c new file mode 100644 index 000000000..615572462 --- /dev/null +++ b/core/src/xmake/utf8/width.c @@ -0,0 +1,45 @@ +/*!A cross-platform build utility based on Lua + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (C) 2015-present, Xmake Open Source Community. + * + * @author ruki + * @file width.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * interfaces + */ + +/* utf8.width(char) + * utf8.width(str) + * utf8.width(codepoint) + */ +tb_int_t xm_utf8_width(lua_State* lua) { + if (lua_isnumber(lua, 1)) { + xm_utf8_int_t val = (xm_utf8_int_t)lua_tointeger(lua, 1); + lua_pushinteger(lua, xm_utf8_charwidth(val)); + } else { + size_t len = 0; + tb_char_t const* s = luaL_checklstring(lua, 1, &len); + lua_pushinteger(lua, xm_utf8_strwidth(s, len)); + } + return 1; +} diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index af0c98997..df09604a4 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -142,3 +142,35 @@ function test_find(t) -- "A你好", "%w" -> matches "A". t:are_equal({utf8.find("A你好", "%w")}, {1, 1}) end + +function test_width(t) + -- char/codepoint width + t:are_equal(utf8.width(string.byte("A")), 1) + t:are_equal(utf8.width(utf8.codepoint("€")), 1) + t:are_equal(utf8.width(utf8.codepoint("你")), 2) + t:are_equal(utf8.width(0), 0) + t:are_equal(utf8.width(0x09), 4) -- TAB + + -- string width + t:are_equal(utf8.width("A"), 1) + t:are_equal(utf8.width("ABC"), 3) + t:are_equal(utf8.width("你好"), 4) + t:are_equal(utf8.width("A你好"), 5) + t:are_equal(utf8.width("A\tB"), 6) -- 1 + 4 + 1 +end + +function test_wcwidth(t) + -- char/codepoint width + t:are_equal(utf8.wcwidth(string.byte("A")), 1) + t:are_equal(utf8.wcwidth(utf8.codepoint("€")), 1) + t:are_equal(utf8.wcwidth(utf8.codepoint("你")), 2) +end + +function test_wcswidth(t) + -- string width + t:are_equal(utf8.wcswidth("A"), 1) + t:are_equal(utf8.wcswidth("ABC"), 3) + t:are_equal(utf8.wcswidth("你好"), 4) + t:are_equal(utf8.wcswidth("A你好"), 5) + t:are_equal(utf8.wcswidth("A\tB"), 6) +end diff --git a/xmake/core/base/utf8.lua b/xmake/core/base/utf8.lua index dfcfdfbf0..fefb3c94d 100644 --- a/xmake/core/base/utf8.lua +++ b/xmake/core/base/utf8.lua @@ -32,6 +32,7 @@ local utf8 = utf8 or {} -- @interface utf8.sub(s, i [, j]) -- @interface utf8.lastof(s, pattern [, plain]) -- @interface utf8.find(s, pattern [, init [, plain]]) +-- @interface utf8.width(s) -- -- the char pattern -- cgit v1.3.1 From b5a506803e4f3c8b11858be2a57c509d479683e1 Mon Sep 17 00:00:00 2001 From: ruki Date: Wed, 21 Jan 2026 00:40:01 +0800 Subject: mark wcswidth deprecated --- tests/modules/utf8/test.lua | 14 ----- xmake/core/base/string.lua | 132 ++++++-------------------------------------- xmake/core/ui/label.lua | 21 ++++--- 3 files changed, 30 insertions(+), 137 deletions(-) (limited to 'tests/modules/utf8/test.lua') diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index df09604a4..bab3ce896 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -159,18 +159,4 @@ function test_width(t) t:are_equal(utf8.width("A\tB"), 6) -- 1 + 4 + 1 end -function test_wcwidth(t) - -- char/codepoint width - t:are_equal(utf8.wcwidth(string.byte("A")), 1) - t:are_equal(utf8.wcwidth(utf8.codepoint("€")), 1) - t:are_equal(utf8.wcwidth(utf8.codepoint("你")), 2) -end -function test_wcswidth(t) - -- string width - t:are_equal(utf8.wcswidth("A"), 1) - t:are_equal(utf8.wcswidth("ABC"), 3) - t:are_equal(utf8.wcswidth("你好"), 4) - t:are_equal(utf8.wcswidth("A你好"), 5) - t:are_equal(utf8.wcswidth("A\tB"), 6) -end diff --git a/xmake/core/base/string.lua b/xmake/core/base/string.lua index 2c7069cb9..c819a5e08 100644 --- a/xmake/core/base/string.lua +++ b/xmake/core/base/string.lua @@ -259,127 +259,31 @@ function string:deserialize() return serialize.load(self) end --- unicode character width in the given index +-- unicode character width in the given index (deprecated) function string:wcwidth(idx) - -- based on Markus Kuhn's implementation of wcswidth() - -- https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c - local non_spacing = - { - {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, - {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, - {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, - {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, - {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, - {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, - {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, - {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, - {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, - {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, - {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, - {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, - {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, - {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, - {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, - {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, - {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, - {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, - {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, - {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, - {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, - {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, - {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, - {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, - {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, - {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, - {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, - {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, - {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, - {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, - {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, - {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, - {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, - {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, - {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, - {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, - {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, - {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, - {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, - {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, - {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, - {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, - {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, - {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, - {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, - {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, - {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, - {0xE0100, 0xE01EF}, - } - - idx = idx or 1 - - -- turn codepoint into unicode - local c = self:byte(idx) - local seq = c < 0x80 and 1 or c < 0xE0 and 2 or c < 0xF0 and 3 or - c < 0xF8 and 4 or error("invalid UTF-8 sequence") - local val = seq == 1 and c or bit.band(c, (2^(8 - seq) - 1)) - - for aux = 2, seq do - c = self:byte(idx + aux - 1) - val = val * 2 ^ 6 + bit.band(c, 0x3F) - end - - -- test for 8-bit control characters - if val == 0 then return 0 end - - if val < 32 or (val >= 0x7f and val < 0xa0) then - return -1 - end - - -- binary search in table of non-spacing characters - local min, max = 1, #non_spacing - if val >= non_spacing[1][1] and val <= non_spacing[max][2] then - while max >= min do - local mid = math.floor((min + max) / 2) - if val > non_spacing[mid][2] then - min = mid + 1 - elseif val < non_spacing[mid][1] then - max = mid - 1 - else - return 0 - end - end - end - - if val >= 0x1100 and (val <= 0x115f or -- Hangul Jamo init. consonants - val == 0x2329 or val == 0x232a or - (val >= 0x2e80 and val <= 0xa4cf and - val ~= 0x303f) or -- CJK ... Yi - (val >= 0xac00 and val <= 0xd7a3) or -- Hangul Syllables - (val >= 0xf900 and val <= 0xfaff) or -- CJK Compatibility Ideographs - (val >= 0xfe10 and val <= 0xfe19) or -- Vertical forms - (val >= 0xfe30 and val <= 0xfe6f) or -- CJK Compatibility Forms - (val >= 0xff00 and val <= 0xff60) or -- Fullwidth Forms - (val >= 0xffe0 and val <= 0xffe6) or - (val >= 0x20000 and val <= 0x2fffd) or - (val >= 0x30000 and val <= 0x3fffd)) then - return 2 - end + -- deprecated + deprecated.add("utf8.width(char)", "string:wcwidth(idx)") - return 1 + -- get codepoint and width + local utf8 = utf8 or require("base/utf8") + local code = utf8.codepoint(self, idx) + return utf8.width(code) end --- unicode string width in given start index +-- unicode string width in given start index (deprecated) function string:wcswidth(idx) - local width = 0 - idx = idx or 1 - while idx <= #self do - if bit.band(self:byte(idx), 0xc0) ~= 0x80 then - width = width + self:wcwidth(idx) - end - idx = idx + 1 + + -- deprecated + deprecated.add("utf8.width(str)", "string:wcswidth(idx)") + + -- get width + local utf8 = utf8 or require("base/utf8") + if idx and idx > 1 then + return utf8.width(self:sub(idx)) + else + return utf8.width(self) end - return width end -- compute the Levenshtein distance between two strings diff --git a/xmake/core/ui/label.lua b/xmake/core/ui/label.lua index 33a35b16f..45330c80c 100644 --- a/xmake/core/ui/label.lua +++ b/xmake/core/ui/label.lua @@ -129,17 +129,20 @@ function label:splitext(text, width) local line = lines[idx] while #line > width do local size = 0 - for i = 1, #line do - if bit.band(line:byte(i), 0xc0) ~= 0x80 then - size = size + line:wcwidth(i) - if size > width then - table.insert(result, line:sub(1, i - 1)) - line = line:sub(i) - break - end + local split_idx = 0 + for p, c in utf8.codes(line) do + local w = utf8.width(c) + size = size + w + if size > width then + split_idx = p + break end end - if size <= width then + + if split_idx > 0 then + table.insert(result, line:sub(1, split_idx - 1)) + line = line:sub(split_idx) + else break end end -- cgit v1.3.1 From f10baca5c10b1d21c6d9900ad3d6b382c37258c6 Mon Sep 17 00:00:00 2001 From: ruki Date: Wed, 21 Jan 2026 00:46:38 +0800 Subject: add utf8.byte --- core/src/xmake/engine.c | 2 ++ core/src/xmake/utf8/byte.c | 52 +++++++++++++++++++++++++++++++++++++++++++++ core/src/xmake/utf8/utf8.c | 25 ++++++++++++++++++++++ core/src/xmake/utf8/utf8.h | 1 + tests/modules/utf8/test.lua | 19 +++++++++++++++++ xmake/core/base/utf8.lua | 1 + 6 files changed, 100 insertions(+) create mode 100644 core/src/xmake/utf8/byte.c (limited to 'tests/modules/utf8/test.lua') diff --git a/core/src/xmake/engine.c b/core/src/xmake/engine.c index 146cdd17a..bca1cce65 100644 --- a/core/src/xmake/engine.c +++ b/core/src/xmake/engine.c @@ -282,6 +282,7 @@ tb_int_t xm_winos_short_path(lua_State *lua); // the utf8 functions tb_int_t xm_utf8_len(lua_State *lua); tb_int_t xm_utf8_char(lua_State *lua); +tb_int_t xm_utf8_byte(lua_State *lua); tb_int_t xm_utf8_codepoint(lua_State *lua); tb_int_t xm_utf8_offset(lua_State *lua); tb_int_t xm_utf8_codes(lua_State *lua); @@ -597,6 +598,7 @@ static luaL_Reg const g_bloom_filter_functions[] = { // the utf8 functions static luaL_Reg const g_utf8_functions[] = { {"char", xm_utf8_char}, + {"byte", xm_utf8_byte}, {"codes", xm_utf8_codes}, {"codepoint", xm_utf8_codepoint}, {"len", xm_utf8_len}, diff --git a/core/src/xmake/utf8/byte.c b/core/src/xmake/utf8/byte.c new file mode 100644 index 000000000..036f04334 --- /dev/null +++ b/core/src/xmake/utf8/byte.c @@ -0,0 +1,52 @@ +/*!A cross-platform build utility based on Lua + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (C) 2015-present, Xmake Open Source Community. + * + * @author ruki + * @file byte.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * private implementation + */ +static tb_bool_t xm_utf8_byte_cb(xm_utf8_int_t code, tb_cpointer_t udata) { + lua_State* lua = (lua_State*)udata; + tb_assert_and_check_return_val(lua, tb_false); + + luaL_checkstack(lua, 1, "too many results"); + lua_pushinteger(lua, code); + return tb_true; +} + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +/* utf8.byte(s, i [, j]) + */ +tb_int_t xm_utf8_byte(lua_State* lua) { + size_t len; + tb_char_t const* s = luaL_checklstring(lua, 1, &len); + lua_Integer i = luaL_optinteger(lua, 2, 1); + lua_Integer j = luaL_optinteger(lua, 3, i); + + return (tb_int_t)xm_utf8_byte_impl(s, len, i, j, xm_utf8_byte_cb, lua); +} diff --git a/core/src/xmake/utf8/utf8.c b/core/src/xmake/utf8/utf8.c index 168ffe60a..ff6c33a1a 100644 --- a/core/src/xmake/utf8/utf8.c +++ b/core/src/xmake/utf8/utf8.c @@ -399,6 +399,31 @@ tb_long_t xm_utf8_lastof_impl(tb_char_t const* s, tb_size_t len, tb_char_t const return 0; } +tb_long_t xm_utf8_byte_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, xm_utf8_codepoint_func_t func, tb_cpointer_t udata) { + tb_size_t sublen = 0; + tb_char_t const* sub = xm_utf8_sub_impl(s, len, i, j, &sublen); + if (sub && sublen > 0) { + + // decode and push codepoints + tb_long_t n = 0; + tb_char_t const* p = sub; + tb_char_t const* e = sub + sublen; + while (p < e) { + xm_utf8_int_t val; + tb_char_t const* next = xm_utf8_decode(p, &val, tb_true); + if (next) { + if (func && !func(val, udata)) break; + n++; + p = next; + } else { + p++; + } + } + return n; + } + return 0; +} + tb_char_t const* xm_utf8_sub_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, tb_size_t* psublen) { tb_assert_and_check_return_val(s && psublen, tb_null); *psublen = 0; diff --git a/core/src/xmake/utf8/utf8.h b/core/src/xmake/utf8/utf8.h index d6d884240..fff81f1e5 100644 --- a/core/src/xmake/utf8/utf8.h +++ b/core/src/xmake/utf8/utf8.h @@ -53,6 +53,7 @@ tb_long_t xm_utf8_offset_impl(tb_char_t const* s, tb_size_t len, tb_lo tb_bool_t xm_utf8_codepoint_impl(tb_char_t const* s, tb_size_t len, tb_long_t posi, tb_long_t posj, tb_bool_t strict, xm_utf8_codepoint_func_t func, tb_cpointer_t udata); tb_long_t xm_utf8_find_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen, tb_long_t init, tb_long_t* pchar_end); tb_long_t xm_utf8_lastof_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen); +tb_long_t xm_utf8_byte_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, xm_utf8_codepoint_func_t func, tb_cpointer_t udata); tb_char_t const* xm_utf8_sub_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, tb_size_t* psublen); #endif diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index bab3ce896..329672d50 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -159,4 +159,23 @@ function test_width(t) t:are_equal(utf8.width("A\tB"), 6) -- 1 + 4 + 1 end +function test_byte(t) + t:are_equal({utf8.byte("A")}, {65}) + t:are_equal({utf8.byte("€")}, {0x20AC}) + t:are_equal({utf8.byte("ABC")}, {65}) + t:are_equal({utf8.byte("ABC", 2)}, {66}) + t:are_equal({utf8.byte("ABC", 2, 2)}, {66}) + t:are_equal({utf8.byte("ABC", 1, 3)}, {65, 66, 67}) + t:are_equal({utf8.byte("你好")}, {utf8.codepoint("你好", 1, 1)}) + t:are_equal({utf8.byte("你好", 1, 2)}, {20320, 22909}) + + -- negative indices + t:are_equal({utf8.byte("ABC", -1)}, {67}) + t:are_equal({utf8.byte("ABC", -2)}, {66}) + t:are_equal({utf8.byte("ABC", -2, -1)}, {66, 67}) + t:are_equal({utf8.byte("ABC", 1, -1)}, {65, 66, 67}) + -- out of bounds + t:are_equal({utf8.byte("ABC", 4)}, {}) + t:are_equal({utf8.byte("ABC", 1, 0)}, {}) +end diff --git a/xmake/core/base/utf8.lua b/xmake/core/base/utf8.lua index fefb3c94d..c31f4dfc2 100644 --- a/xmake/core/base/utf8.lua +++ b/xmake/core/base/utf8.lua @@ -33,6 +33,7 @@ local utf8 = utf8 or {} -- @interface utf8.lastof(s, pattern [, plain]) -- @interface utf8.find(s, pattern [, init [, plain]]) -- @interface utf8.width(s) +-- @interface utf8.byte(s [, i [, j]]) -- -- the char pattern -- cgit v1.3.1 From ae8b2c49195e4f9e28f8ab06f8169e1bca68d1fc Mon Sep 17 00:00:00 2001 From: ruki Date: Wed, 21 Jan 2026 00:49:57 +0800 Subject: add utf8.reverse --- core/src/xmake/engine.c | 2 ++ core/src/xmake/utf8/reverse.c | 57 +++++++++++++++++++++++++++++++++++++++++++ core/src/xmake/utf8/utf8.c | 28 +++++++++++++++++++++ core/src/xmake/utf8/utf8.h | 1 + tests/modules/utf8/test.lua | 6 +++++ xmake/core/base/utf8.lua | 1 + 6 files changed, 95 insertions(+) create mode 100644 core/src/xmake/utf8/reverse.c (limited to 'tests/modules/utf8/test.lua') diff --git a/core/src/xmake/engine.c b/core/src/xmake/engine.c index bca1cce65..9ceabd156 100644 --- a/core/src/xmake/engine.c +++ b/core/src/xmake/engine.c @@ -287,6 +287,7 @@ tb_int_t xm_utf8_codepoint(lua_State *lua); tb_int_t xm_utf8_offset(lua_State *lua); tb_int_t xm_utf8_codes(lua_State *lua); tb_int_t xm_utf8_sub(lua_State *lua); +tb_int_t xm_utf8_reverse(lua_State *lua); tb_int_t xm_utf8_lastof(lua_State *lua); tb_int_t xm_utf8_find(lua_State *lua); tb_int_t xm_utf8_width(lua_State *lua); @@ -604,6 +605,7 @@ static luaL_Reg const g_utf8_functions[] = { {"len", xm_utf8_len}, {"offset", xm_utf8_offset}, {"sub", xm_utf8_sub}, + {"reverse", xm_utf8_reverse}, {"lastof", xm_utf8_lastof}, {"find", xm_utf8_find}, {"width", xm_utf8_width}, diff --git a/core/src/xmake/utf8/reverse.c b/core/src/xmake/utf8/reverse.c new file mode 100644 index 000000000..0c0b7aaf7 --- /dev/null +++ b/core/src/xmake/utf8/reverse.c @@ -0,0 +1,57 @@ +/*!A cross-platform build utility based on Lua + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (C) 2015-present, Xmake Open Source Community. + * + * @author ruki + * @file reverse.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +/* utf8.reverse(s) + */ +tb_int_t xm_utf8_reverse(lua_State *lua) { + size_t len; + tb_char_t const* s = luaL_checklstring(lua, 1, &len); + if (len == 0) { + lua_pushliteral(lua, ""); + return 1; + } + + // do reverse + if (len < 1024) { + tb_char_t buf[1024 + 1]; + xm_utf8_reverse_impl(s, len, buf); + lua_pushlstring(lua, buf, len); + } else { + tb_char_t* buf = (tb_char_t*)tb_malloc_bytes(len + 1); + if (buf) { + xm_utf8_reverse_impl(s, len, buf); + lua_pushlstring(lua, buf, len); + tb_free(buf); + } else { + lua_pushnil(lua); + } + } + return 1; +} diff --git a/core/src/xmake/utf8/utf8.c b/core/src/xmake/utf8/utf8.c index ff6c33a1a..ace3a213c 100644 --- a/core/src/xmake/utf8/utf8.c +++ b/core/src/xmake/utf8/utf8.c @@ -476,3 +476,31 @@ tb_char_t const* xm_utf8_sub_impl(tb_char_t const* s, tb_size_t len, tb_long_t i return s + start_byte - 1; } +tb_char_t* xm_utf8_reverse_impl(tb_char_t const* s, tb_size_t len, tb_char_t* buf) { + tb_assert_and_check_return_val(s && len && buf, tb_null); + + tb_char_t const* p = s; + tb_char_t const* e = s + len; + tb_char_t* d = buf + len; + + while (p < e) { + xm_utf8_int_t code; + tb_char_t const* next = xm_utf8_decode(p, &code, tb_false); + + // invalid utf8? treat as 1 byte + tb_size_t n = 1; + if (next) { + n = next - p; + } + + // safety check + if (p + n > e) n = e - p; + + d -= n; + tb_memcpy(d, p, n); + p += n; + } + buf[len] = '\0'; + return buf; +} + diff --git a/core/src/xmake/utf8/utf8.h b/core/src/xmake/utf8/utf8.h index fff81f1e5..fe83e8e86 100644 --- a/core/src/xmake/utf8/utf8.h +++ b/core/src/xmake/utf8/utf8.h @@ -55,5 +55,6 @@ tb_long_t xm_utf8_find_impl(tb_char_t const* s, tb_size_t len, tb_char tb_long_t xm_utf8_lastof_impl(tb_char_t const* s, tb_size_t len, tb_char_t const* sub, tb_size_t sublen); tb_long_t xm_utf8_byte_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, xm_utf8_codepoint_func_t func, tb_cpointer_t udata); tb_char_t const* xm_utf8_sub_impl(tb_char_t const* s, tb_size_t len, tb_long_t i, tb_long_t j, tb_size_t* psublen); +tb_char_t* xm_utf8_reverse_impl(tb_char_t const* s, tb_size_t len, tb_char_t* buf); #endif diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index 329672d50..f7c8dbd2e 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -179,3 +179,9 @@ function test_byte(t) t:are_equal({utf8.byte("ABC", 4)}, {}) t:are_equal({utf8.byte("ABC", 1, 0)}, {}) end + +function test_reverse(t) + t:are_equal(utf8.reverse("hello"), "olleh") + t:are_equal(utf8.reverse("你好"), "好你") + t:are_equal(utf8.reverse("hello 你好"), "好你 olleh") +end diff --git a/xmake/core/base/utf8.lua b/xmake/core/base/utf8.lua index c31f4dfc2..67b52e7ec 100644 --- a/xmake/core/base/utf8.lua +++ b/xmake/core/base/utf8.lua @@ -30,6 +30,7 @@ local utf8 = utf8 or {} -- @interface utf8.offset(s, n [, i]) -- @interface utf8.codes(s [, lax]) -- @interface utf8.sub(s, i [, j]) +-- @interface utf8.reverse(s) -- @interface utf8.lastof(s, pattern [, plain]) -- @interface utf8.find(s, pattern [, init [, plain]]) -- @interface utf8.width(s) -- cgit v1.3.1 From 197dfb660a35825d0bb46f8ef69884d7ec00c410 Mon Sep 17 00:00:00 2001 From: ruki Date: Wed, 21 Jan 2026 22:09:09 +0800 Subject: fix path.filename and lastof --- core/src/xmake/utf8/utf8.c | 4 +--- tests/modules/path/test.lua | 17 +++++++++++++++++ tests/modules/string/test.lua | 7 +++++++ tests/modules/utf8/test.lua | 10 ++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) (limited to 'tests/modules/utf8/test.lua') diff --git a/core/src/xmake/utf8/utf8.c b/core/src/xmake/utf8/utf8.c index 1e76c2a7e..f4da25935 100644 --- a/core/src/xmake/utf8/utf8.c +++ b/core/src/xmake/utf8/utf8.c @@ -395,9 +395,7 @@ tb_long_t xm_utf8_lastof_impl(tb_char_t const* s, tb_size_t len, tb_char_t const } if (last) { - tb_long_t count = xm_utf8_len_impl(s, len, 1, last - s, tb_true, tb_null); - if (count < 0) return 0; - return count + 1; + return (tb_long_t)(last - s + 1); } return 0; } diff --git a/tests/modules/path/test.lua b/tests/modules/path/test.lua index f68a57e5f..57f7d06bc 100644 --- a/tests/modules/path/test.lua +++ b/tests/modules/path/test.lua @@ -34,6 +34,23 @@ function test_extension(t) t:are_equal(path.extension("\\home\\foo.so"), ".so") end +function test_filename(t) + t:are_equal(path.filename("foo"), "foo") + t:are_equal(path.filename("foo.so"), "foo.so") + t:are_equal(path.filename("/tmp/foo.so"), "foo.so") + t:are_equal(path.filename("c:\\tmp\\foo.so"), "foo.so") + t:are_equal(path.filename("/tmp/.."), "..") + t:are_equal(path.filename("/tmp/."), ".") + t:are_equal(path.filename("/"), "") + t:are_equal(path.filename(""), "") + + -- unicode + t:are_equal(path.filename("Unicode 测试/test.lua"), "test.lua") + t:are_equal(path.filename("Unicode 测试/foo/test.lua"), "test.lua") + t:are_equal(path.filename("测试/test.lua"), "test.lua") + t:are_equal(path.filename("测试\\test.lua"), "test.lua") +end + function test_directory(t) t:are_equal(path.directory(""), nil) t:are_equal(path.directory("."), nil) diff --git a/tests/modules/string/test.lua b/tests/modules/string/test.lua index 94b3c1a04..4b093de45 100644 --- a/tests/modules/string/test.lua +++ b/tests/modules/string/test.lua @@ -94,6 +94,13 @@ function test_lastof(t) t:are_equal(("/home/file.txt"):lastof('/', true), 6) t:are_equal(("/home/file.txt"):lastof('/home', true), 1) t:are_equal(("/home/file.txt"):lastof('[/\\]home'), 1) + + -- long string + local longstr = ("a"):rep(1000) .. "b" + t:are_equal(longstr:lastof("b"), 1001) + t:are_equal(longstr:lastof("a"), 1000) + t:are_equal(longstr:lastof("b", true), 1001) + t:are_equal(longstr:lastof("a", true), 1000) end function test_replace(t) diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index f7c8dbd2e..ddbd6b639 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -110,6 +110,16 @@ function test_lastof(t) t:are_equal(utf8.lastof("ABC", "A", true), 1) t:are_equal(utf8.lastof("ABC", "B", true), 2) t:are_equal(utf8.lastof("ABC", ".", true), nil) + t:are_equal(utf8.lastof("你好", "好", true), 2) + t:are_equal(utf8.lastof("C你好D", "好", true), 3) + t:are_equal(utf8.lastof("C你好D", "D", true), 4) + + -- long string + local longstr = ("你"):rep(1000) .. "好" + t:are_equal(utf8.lastof(longstr, "好"), 1001) + t:are_equal(utf8.lastof(longstr, "你"), 1000) + t:are_equal(utf8.lastof(longstr, "好", true), 1001) + t:are_equal(utf8.lastof(longstr, "你", true), 1000) -- pattern t:are_equal(utf8.lastof("ABC", "."), 3) -- cgit v1.3.1 From e36eec65251b48fb5188691e86006ea8266ebe9a Mon Sep 17 00:00:00 2001 From: ruki Date: Wed, 21 Jan 2026 22:09:14 +0800 Subject: format code --- tests/modules/string/test.lua | 2 +- tests/modules/utf8/test.lua | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'tests/modules/utf8/test.lua') diff --git a/tests/modules/string/test.lua b/tests/modules/string/test.lua index 4b093de45..1ec4b52a1 100644 --- a/tests/modules/string/test.lua +++ b/tests/modules/string/test.lua @@ -94,7 +94,7 @@ function test_lastof(t) t:are_equal(("/home/file.txt"):lastof('/', true), 6) t:are_equal(("/home/file.txt"):lastof('/home', true), 1) t:are_equal(("/home/file.txt"):lastof('[/\\]home'), 1) - + -- long string local longstr = ("a"):rep(1000) .. "b" t:are_equal(longstr:lastof("b"), 1001) diff --git a/tests/modules/utf8/test.lua b/tests/modules/utf8/test.lua index ddbd6b639..e1fced3a0 100644 --- a/tests/modules/utf8/test.lua +++ b/tests/modules/utf8/test.lua @@ -70,7 +70,7 @@ function test_sub(t) t:are_equal(utf8.sub("你好", 1, 1), "你") t:are_equal(utf8.sub("你好", 2, 2), "好") t:are_equal(utf8.sub("你好", 1, 2), "你好") - + -- mixed t:are_equal(utf8.sub("A你好B", 2, 3), "你好") t:are_equal(utf8.sub("A你好B", 1, 3), "A你好") @@ -105,7 +105,7 @@ function test_lastof(t) t:are_equal(utf8.lastof("A你好A", "好"), 3) t:are_equal(utf8.lastof("ABC", "D"), nil) - + -- plain t:are_equal(utf8.lastof("ABC", "A", true), 1) t:are_equal(utf8.lastof("ABC", "B", true), 2) @@ -142,13 +142,13 @@ function test_find(t) -- UTF-8 pattern matching (byte-based) -- "你" is 3 bytes. "." matches first byte. t:are_equal({utf8.find("你好", ".")}, {1, 1}) - + -- "你好", "好" -> bytes 4-6. t:are_equal({utf8.find("你好", "好")}, {2, 2}) - + -- "你好", "..." (3 dots) -> matches 3 bytes (whole "你"). t:are_equal({utf8.find("你好", "...")}, {1, 1}) - + -- "A你好", "%w" -> matches "A". t:are_equal({utf8.find("A你好", "%w")}, {1, 1}) end -- cgit v1.3.1