diff options
| author | ruki <[email protected]> | 2026-01-21 11:59:57 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-01-21 11:59:57 +0800 |
| commit | f8d44002372ca91bbb29cf3548c306f226e6ef4e (patch) | |
| tree | bdc4a15863537a347084a5e85bf75beb75056c00 | |
| parent | 9e58120568d395b5a636daa7d0f23150fe87bb95 (diff) | |
| parent | 6ff8486cd3297ca0e93518c33e32678c97128a3f (diff) | |
Merge pull request #7246 from xmake-io/utf8
Add utf8 module
27 files changed, 1720 insertions, 180 deletions
diff --git a/core/src/xmake/engine.c b/core/src/xmake/engine.c index d81828f1f..9ceabd156 100644 --- a/core/src/xmake/engine.c +++ b/core/src/xmake/engine.c @@ -279,6 +279,19 @@ tb_int_t xm_winos_registry_values(lua_State *lua); tb_int_t xm_winos_short_path(lua_State *lua); #endif +// 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); +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); + // the string functions tb_int_t xm_string_trim(lua_State *lua); tb_int_t xm_string_split(lua_State *lua); @@ -583,6 +596,24 @@ static luaL_Reg const g_bloom_filter_functions[] = { { tb_null, tb_null }, }; +// 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}, + {"offset", xm_utf8_offset}, + {"sub", xm_utf8_sub}, + {"reverse", xm_utf8_reverse}, + {"lastof", xm_utf8_lastof}, + {"find", xm_utf8_find}, + {"width", xm_utf8_width}, + {"wcwidth", xm_utf8_width}, + {"wcswidth", xm_utf8_width}, + {tb_null, tb_null} +}; + // the string functions static luaL_Reg const g_string_functions[] = { { "trim", xm_string_trim }, @@ -722,6 +753,9 @@ static luaL_Reg const g_thread_functions[] = { { tb_null, tb_null }, }; +// the utf8 functions + + // the lua global instance for signal handler static lua_State *g_lua = tb_null; @@ -1547,6 +1581,9 @@ xm_engine_ref_t xm_engine_init(tb_char_t const *name, xm_engine_lni_initalizer_c // bind base64 functions xm_lua_register(engine->lua, "base64", g_base64_functions); + // bind utf8 functions + xm_lua_register(engine->lua, "utf8", g_utf8_functions); + // bind string functions xm_lua_register(engine->lua, "string", g_string_functions); diff --git a/core/src/xmake/prefix.h b/core/src/xmake/prefix.h index 4b159741e..7cd2ea418 100644 --- a/core/src/xmake/prefix.h +++ b/core/src/xmake/prefix.h @@ -43,6 +43,15 @@ #endif /* ////////////////////////////////////////////////////////////////////////////////////// + * types + */ + +// define lua_Unsigned for luajit/lua5.1 +#if defined(USE_LUAJIT) || LUA_VERSION_NUM < 503 +typedef size_t lua_Unsigned; +#endif + +/* ////////////////////////////////////////////////////////////////////////////////////// * private interfaces */ diff --git a/core/src/xmake/string/lastof.c b/core/src/xmake/string/lastof.c index 9ac6c6784..912647a9c 100644 --- a/core/src/xmake/string/lastof.c +++ b/core/src/xmake/string/lastof.c @@ -29,39 +29,7 @@ * includes */ #include "prefix.h" - -/* ////////////////////////////////////////////////////////////////////////////////////// - * private implementation - */ -static tb_void_t xm_string_lastof_str( - lua_State *lua, tb_char_t const *cstr, tb_size_t nstr, tb_char_t const *csubstr, tb_size_t nsubstr) { - // find it - tb_char_t const *curr = tb_null; - tb_char_t const *next = cstr; - do { - next = tb_strstr(next, csubstr); // faster than tb_strnstr() - if (next) { - curr = next; - next += nsubstr; - } - - } while (!next); - - // found? - if (curr) { - lua_pushinteger(lua, curr - cstr + 1); - } else { - lua_pushnil(lua); - } -} -static tb_void_t xm_string_lastof_chr(lua_State *lua, tb_char_t const *cstr, tb_size_t nstr, tb_char_t ch) { - tb_char_t const *pos = tb_strrchr(cstr, ch); // faster than tb_strnrchr() - if (pos) { - lua_pushinteger(lua, pos - cstr + 1); - } else { - lua_pushnil(lua); - } -} +#include "../utf8/utf8.h" /* ////////////////////////////////////////////////////////////////////////////////////// * implementation @@ -84,11 +52,11 @@ tb_int_t xm_string_lastof(lua_State *lua) { tb_char_t const *csubstr = luaL_checklstring(lua, 2, &nsubstr); // lastof it - lua_newtable(lua); - if (nsubstr == 1) { - xm_string_lastof_chr(lua, cstr, (tb_size_t)nstr, csubstr[0]); + tb_long_t char_pos = xm_utf8_lastof_impl(cstr, nstr, csubstr, nsubstr); + if (char_pos > 0) { + lua_pushinteger(lua, char_pos); } else { - xm_string_lastof_str(lua, cstr, (tb_size_t)nstr, csubstr, nsubstr); + lua_pushnil(lua); } return 1; } diff --git a/core/src/xmake/utf8/byte.c b/core/src/xmake/utf8/byte.c new file mode 100644 index 000000000..18cd5d7c9 --- /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, (tb_long_t)i, (tb_long_t)j, xm_utf8_byte_cb, lua); +} diff --git a/core/src/xmake/utf8/char.c b/core/src/xmake/utf8/char.c new file mode 100644 index 000000000..760dab264 --- /dev/null +++ b/core/src/xmake/utf8/char.c @@ -0,0 +1,67 @@ +/*!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 char.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * private implementation + */ + +static void xm_utf8_char_push(lua_State *lua, tb_int_t arg) { + lua_Unsigned code = (lua_Unsigned)luaL_checkinteger(lua, arg); + luaL_argcheck(lua, code <= XM_UTF8_MAXUTF, arg, "value out of range"); + + tb_char_t buf[8]; + tb_size_t n = xm_utf8_encode(buf, (xm_utf8_int_t)code); + if (n > 0) { + lua_pushlstring(lua, buf, n); + } else { + luaL_error(lua, "value out of range"); + } +} + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +/* utfchar(n1, n2, ...) -> char(n1)..char(n2)... + */ +tb_int_t xm_utf8_char(lua_State *lua) { + 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 { + tb_int_t i; + luaL_Buffer b; + luaL_buffinit(lua, &b); + for (i = 1; i <= n; i++) { + xm_utf8_char_push(lua, i); + luaL_addvalue(&b); + } + luaL_pushresult(&b); + } + return 1; +} diff --git a/core/src/xmake/utf8/codepoint.c b/core/src/xmake/utf8/codepoint.c new file mode 100644 index 000000000..a8fff91dd --- /dev/null +++ b/core/src/xmake/utf8/codepoint.c @@ -0,0 +1,71 @@ +/*!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 codepoint.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * private implementation + */ +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; +} + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +/* 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); + lua_Integer posi = xm_utf8_posrelat((tb_long_t)luaL_optinteger(lua, 2, 1), len); + lua_Integer pose = xm_utf8_posrelat((tb_long_t)luaL_optinteger(lua, 3, posi), len); + tb_bool_t lax = lua_toboolean(lua, 4); + + 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? + 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"); + + int nresults = lua_gettop(lua); + if (!xm_utf8_codepoint_impl(s, len, (tb_long_t)posi, (tb_long_t)pose, !lax, xm_utf8_codepoint_cb, lua)) { + return luaL_error(lua, XM_UTF8_MSGInvalid); + } + + return lua_gettop(lua) - nresults; +} diff --git a/core/src/xmake/utf8/codes.c b/core/src/xmake/utf8/codes.c new file mode 100644 index 000000000..bfae6397d --- /dev/null +++ b/core/src/xmake/utf8/codes.c @@ -0,0 +1,76 @@ +/*!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 codes.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * private implementation + */ + +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 + } + } + 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 || (next < s + len && xm_utf8_iscontp(next))) { + return luaL_error(lua, XM_UTF8_MSGInvalid); + } + lua_pushinteger(lua, n + 1); + lua_pushinteger(lua, code); + return 2; + } +} + +static int xm_utf8_codes_iter_strict(lua_State *lua) { + return xm_utf8_codes_iter(lua, tb_true); +} + +static int xm_utf8_codes_iter_lax(lua_State *lua) { + return xm_utf8_codes_iter(lua, tb_false); +} + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +tb_int_t xm_utf8_codes(lua_State *lua) { + tb_bool_t lax = lua_toboolean(lua, 2); + tb_char_t const* s = luaL_checkstring(lua, 1); + luaL_argcheck(lua, !xm_utf8_iscontp(s), 1, XM_UTF8_MSGInvalid); + lua_pushcfunction(lua, lax ? xm_utf8_codes_iter_lax : xm_utf8_codes_iter_strict); + lua_pushvalue(lua, 1); + lua_pushinteger(lua, 0); + return 3; +} diff --git a/core/src/xmake/utf8/find.c b/core/src/xmake/utf8/find.c new file mode 100644 index 000000000..f2268f9b4 --- /dev/null +++ b/core/src/xmake/utf8/find.c @@ -0,0 +1,134 @@ +/*!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" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * 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) { + tb_long_t char_end = 0; + tb_long_t char_start = xm_utf8_find_impl(s, len, sub, sublen, (tb_long_t)init, &char_end); + if (char_start > 0) { + lua_pushinteger(lua, char_start); + lua_pushinteger(lua, char_end); + return 2; + } + lua_pushnil(lua); + return 1; +} + +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) { + if (init > 1) { + byte_init = xm_utf8_offset_impl(s, len, (tb_long_t)init, 1); + if (byte_init <= 0) { + lua_pushnil(lua); + return 1; + } + } + } else if (init < 0) { + byte_init = xm_utf8_offset_impl(s, len, (tb_long_t)init, len + 1); + if (byte_init <= 0) { + lua_pushnil(lua); + return 1; + } + } + + 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; + } + + lua_Integer b_start = lua_tointeger(lua, base + 2); + lua_Integer b_end = lua_tointeger(lua, base + 3); + + tb_long_t char_start = 1; + if (b_start > 1) { + tb_long_t count = xm_utf8_len_impl(s, len, 1, (tb_long_t)b_start - 1, tb_true, tb_null); + if (count < 0) { + lua_pushnil(lua); + lua_remove(lua, base + 1); + return 1; + } + char_start = count + 1; + } + + tb_long_t match_char_len = 0; + if (b_end >= b_start) { + match_char_len = xm_utf8_len_impl(s, len, (tb_long_t)b_start, (tb_long_t)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); + lua_replace(lua, base + 3); + + lua_remove(lua, base + 1); + + return nres; +} + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +/* 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/lastof.c b/core/src/xmake/utf8/lastof.c new file mode 100644 index 000000000..165da3b69 --- /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, 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); + tb_long_t byte_pos = 0; + + if (plain) { + byte_pos = xm_utf8_lastof_impl(s, len, sub, sublen); + } 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 + lua_call(lua, 3, 1); + if (!lua_isnil(lua, -1)) { + byte_pos = (tb_long_t)lua_tointeger(lua, -1); + } + } + + if (byte_pos > 0) { + tb_long_t char_pos = xm_utf8_charpos(s, len, byte_pos); + if (char_pos > 0) { + lua_pushinteger(lua, char_pos); + return 1; + } + } + + lua_pushnil(lua); + return 1; +} diff --git a/core/src/xmake/utf8/len.c b/core/src/xmake/utf8/len.c new file mode 100644 index 000000000..3e1f35bf5 --- /dev/null +++ b/core/src/xmake/utf8/len.c @@ -0,0 +1,55 @@ +/*!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 len.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * 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 + */ +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((tb_long_t)luaL_optinteger(lua, 2, 1), len); + lua_Integer posj = xm_utf8_posrelat((tb_long_t)luaL_optinteger(lua, 3, -1), len); + tb_bool_t lax = lua_toboolean(lua, 4); + luaL_argcheck(lua, 1 <= posi && --posi <= (lua_Integer)len, 2, "initial position out of bounds"); + luaL_argcheck(lua, --posj < (lua_Integer)len, 3, "final position out of bounds"); + + tb_size_t errpos = 0; + tb_long_t n = xm_utf8_len_impl(s, len, (tb_long_t)posi + 1, (tb_long_t)posj + 1, !lax, &errpos); + if (n < 0) { + lua_pushnil(lua); + lua_pushinteger(lua, errpos); + return 2; + } + lua_pushinteger(lua, n); + return 1; +} diff --git a/core/src/xmake/utf8/offset.c b/core/src/xmake/utf8/offset.c new file mode 100644 index 000000000..fcd155af7 --- /dev/null +++ b/core/src/xmake/utf8/offset.c @@ -0,0 +1,56 @@ +/*!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 offset.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +/* 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); + lua_Integer posi = (n >= 0) ? 1 : len + 1; + posi = xm_utf8_posrelat((tb_long_t)luaL_optinteger(lua, 3, posi), len); + + tb_long_t result = xm_utf8_offset_impl(s, len, (tb_long_t)n, (tb_long_t)posi); + if (result == -1) { + return luaL_argerror(lua, 3, "position out of bounds"); + } + if (result == -2) { + return luaL_error(lua, "initial position is a continuation byte"); + } + if (result == 0) { + lua_pushnil(lua); + return 1; + } + lua_pushinteger(lua, result); + return 1; +} diff --git a/core/src/xmake/utf8/prefix.h b/core/src/xmake/utf8/prefix.h new file mode 100644 index 000000000..7a0881248 --- /dev/null +++ b/core/src/xmake/utf8/prefix.h @@ -0,0 +1,9 @@ +#ifndef XM_UTF8_PREFIX_H +#define XM_UTF8_PREFIX_H + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "../prefix.h" + +#endif 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/sub.c b/core/src/xmake/utf8/sub.c new file mode 100644 index 000000000..e1561f0f7 --- /dev/null +++ b/core/src/xmake/utf8/sub.c @@ -0,0 +1,47 @@ +/*!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); + + tb_size_t sublen = 0; + tb_char_t const* sub = xm_utf8_sub_impl(s, len, (tb_long_t)i, (tb_long_t)j, &sublen); + if (sub) { + lua_pushlstring(lua, sub, sublen); + } else { + lua_pushliteral(lua, ""); + } + return 1; +} diff --git a/core/src/xmake/utf8/utf8.c b/core/src/xmake/utf8/utf8.c new file mode 100644 index 000000000..4deba3224 --- /dev/null +++ b/core/src/xmake/utf8/utf8.c @@ -0,0 +1,512 @@ +/*!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 utf8.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "utf8.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +tb_char_t const* xm_utf8_decode(tb_char_t const* s, xm_utf8_int_t* val, tb_bool_t strict) { + 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) { + res = c; + } else { + if (xm_utf8_iscont(c)) { + return tb_null; + } + tb_int_t count = 0; + for (; c & 0x40; c <<= 1) { + tb_uint32_t cc = (tb_byte_t)s[++count]; + 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]) { + return tb_null; + } + s += count; + } + if (strict) { + if (res > XM_UTF8_MAXUNICODE || (0xD800u <= res && res <= 0xDFFFu)) { + return tb_null; + } + } + 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; + } + if (val < 0x800) { + s[0] = (tb_char_t)(0xc0 | ((val >> 6) & 0x1f)); + s[1] = (tb_char_t)(0x80 | (val & 0x3f)); + return 2; + } + if (val < 0x10000) { + s[0] = (tb_char_t)(0xe0 | ((val >> 12) & 0x0f)); + s[1] = (tb_char_t)(0x80 | ((val >> 6) & 0x3f)); + s[2] = (tb_char_t)(0x80 | (val & 0x3f)); + return 3; + } + if (val <= 0x10FFFF) { + s[0] = (tb_char_t)(0xf0 | ((val >> 18) & 0x07)); + s[1] = (tb_char_t)(0x80 | ((val >> 12) & 0x3f)); + s[2] = (tb_char_t)(0x80 | ((val >> 6) & 0x3f)); + s[3] = (tb_char_t)(0x80 | (val & 0x3f)); + return 4; + } + if (val <= 0x3FFFFFF) { + s[0] = (tb_char_t)(0xf8 | ((val >> 24) & 0x03)); + s[1] = (tb_char_t)(0x80 | ((val >> 18) & 0x3f)); + s[2] = (tb_char_t)(0x80 | ((val >> 12) & 0x3f)); + s[3] = (tb_char_t)(0x80 | ((val >> 6) & 0x3f)); + s[4] = (tb_char_t)(0x80 | (val & 0x3f)); + return 5; + } + if (val <= 0x7FFFFFFF) { + s[0] = (tb_char_t)(0xfc | ((val >> 30) & 0x01)); + s[1] = (tb_char_t)(0x80 | ((val >> 24) & 0x3f)); + s[2] = (tb_char_t)(0x80 | ((val >> 18) & 0x3f)); + s[3] = (tb_char_t)(0x80 | ((val >> 12) & 0x3f)); + s[4] = (tb_char_t)(0x80 | ((val >> 6) & 0x3f)); + s[5] = (tb_char_t)(0x80 | (val & 0x3f)); + return 6; + } + return 0; +} + +tb_long_t xm_utf8_charpos(tb_char_t const* s, tb_size_t len, tb_long_t byte_pos) { + if (byte_pos <= 0) return 0; + if (byte_pos > len + 1) byte_pos = len + 1; + + // adjust byte_pos to the start of the character + // + // performance: + // 0(1) complexity, because utf8 sequence is max 4 bytes + while (byte_pos > 1 && xm_utf8_iscont(s[byte_pos - 1])) { + byte_pos--; + } + + // get character position + tb_long_t count = xm_utf8_len_impl(s, len, 1, byte_pos - 1, tb_true, tb_null); + return count >= 0? count + 1 : -1; +} + +/* ////////////////////////////////////////////////////////////////////////////////////// + * 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); + + 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; + } + return -1; + } + posi = s1 - s + 1; + n++; + } + return n; +} + +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) { + 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--; + } + } else { + if (xm_utf8_iscontp(s + posi)) { + return -2; // error: initial position is a continuation byte + } + + if (n < 0) { + while (n < 0 && posi > 0) { + do { + posi--; + } while (posi > 0 && xm_utf8_iscontp(s + posi)); + n++; + } + } else { + n--; + while (n > 0 && posi < (tb_long_t)len) { + do { + posi++; + } while (xm_utf8_iscontp(s + posi)); + n--; + } + } + } + + 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) { + 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; + } + } + 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_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); + tb_check_return_val(sublen, 0); + + // optimize for single character search + if (sublen == 1) { + tb_char_t const* p = tb_strrchr(s, sub[0]); + return p ? (tb_long_t)(p - s + 1) : 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) { + return (tb_long_t)(last - s + 1); + } + 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; + + // 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; +} + +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 new file mode 100644 index 000000000..fe83e8e86 --- /dev/null +++ b/core/src/xmake/utf8/utf8.h @@ -0,0 +1,60 @@ +#ifndef XM_UTF8_UTF8_H +#define XM_UTF8_UTF8_H + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "prefix.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * macros + */ +#define xm_utf8_iscont(c) (((c) & 0xC0) == 0x80) +#define xm_utf8_iscontp(p) xm_utf8_iscont(*(p)) + +#define XM_UTF8_MAXUNICODE 0x10FFFFu +#define XM_UTF8_MAXUTF 0x7FFFFFFFu +#define XM_UTF8_MSGInvalid "invalid UTF-8 code" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * types + */ +typedef tb_uint32_t xm_utf8_int_t; +typedef tb_bool_t (*xm_utf8_codepoint_func_t)(xm_utf8_int_t code, tb_cpointer_t udata); + +/* ////////////////////////////////////////////////////////////////////////////////////// + * 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; + } +} + +/* ////////////////////////////////////////////////////////////////////////////////////// + * interfaces + */ + +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 + */ +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_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/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/core/src/xmake/xmake.sh b/core/src/xmake/xmake.sh index 291001306..dfe533aa3 100755 --- a/core/src/xmake/xmake.sh +++ b/core/src/xmake/xmake.sh @@ -70,6 +70,7 @@ target "xmake" add_files "sandbox/*.c" add_files "semver/*.c" add_files "string/*.c" + add_files "utf8/*.c" add_files "utils/*.c" add_files "tty/*.c" add_files "binutils/*.c" 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..1ec4b52a1 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 new file mode 100644 index 000000000..e1fced3a0 --- /dev/null +++ b/tests/modules/utf8/test.lua @@ -0,0 +1,197 @@ + +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 + +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) + + -- 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) + 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) + t:are_equal(utf8.lastof("你好", "."), 2) +end + +function test_find(t) + -- 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", "([BC])")}, {2, 2, "B"}) -- Capture + t:are_equal({utf8.find("ABC", "(.)(.)")}, {1, 2, "A", "B"}) + + -- 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 + +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_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 + +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/tests/runner.lua b/tests/runner.lua index 5d154674d..f60bf37d8 100644 --- a/tests/runner.lua +++ b/tests/runner.lua @@ -8,7 +8,7 @@ function main(script, opt) script = path.join(script, "test.lua") end script = path.absolute(script) - assert(path.filename(script) == "test.lua", "file should named `test.lua`") + assert(path.filename(script) == "test.lua", "file(%s) should named `test.lua`", script) assert(os.isfile(script), "should be a file") -- disable statistics diff --git a/xmake/core/base/string.lua b/xmake/core/base/string.lua index 2c7069cb9..21166cde7 100644 --- a/xmake/core/base/string.lua +++ b/xmake/core/base/string.lua @@ -131,16 +131,6 @@ function string:rtrim(trimchars) return string._trim(self, trimchars, 1) end --- encode: ' ', '=', '\"', '<' -function string:encode() - return (self:gsub("[%s=\"<]", function (w) return string.format("%%%x", w:byte()) end)) -end - --- decode: ' ', '=', '\"' -function string:decode() - return (self:gsub("%%(%x%x)", function (w) return string.char(tonumber(w, 16)) end)) -end - -- replace text function string:replace(old, new, opt) if opt and opt.plain then @@ -259,129 +249,6 @@ function string:deserialize() return serialize.load(self) end --- unicode character width in the given index -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 - - return 1 -end - --- unicode string width in given start index -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 - end - return width -end - -- compute the Levenshtein distance between two strings -- -- @param str2 the string to compare against @@ -425,5 +292,44 @@ function string:levenshtein(str2, opt) return row1[len2 + 1] end +-- encode: ' ', '=', '\"', '<' (deprecated) +function string:encode() + deprecated.add(nil, "string:encode()") + return (self:gsub("[%s=\"<]", function (w) return string.format("%%%x", w:byte()) end)) +end + +-- decode: ' ', '=', '\"' (deprecated) +function string:decode() + deprecated.add(nil, "string:decode()") + return (self:gsub("%%(%x%x)", function (w) return string.char(tonumber(w, 16)) end)) +end + +-- unicode character width in the given index (deprecated) +function string:wcwidth(idx) + + -- deprecated + deprecated.add("utf8.width(char)", "string:wcwidth(idx)") + + -- 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 (deprecated) +function string:wcswidth(idx) + + -- 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 +end + -- return module: string return string diff --git a/xmake/core/base/utf8.lua b/xmake/core/base/utf8.lua new file mode 100644 index 000000000..67b52e7ec --- /dev/null +++ b/xmake/core/base/utf8.lua @@ -0,0 +1,46 @@ +--!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 utf8.lua + +-- define module: utf8 +local utf8 = utf8 or {} + +-- @desc The utf8 module +-- It provides basic support for UTF-8 encoding. +-- It is compatible with Lua 5.3+ utf8 library. +-- +-- @interface utf8.len(s [, i [, j [, lax]]]) +-- @interface utf8.char(...) +-- @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.reverse(s) +-- @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 +if not utf8.charpattern then + utf8.charpattern = "[\0-\x7F\xC2-\xFD][\x80-\xBF]*" +end + +-- return module: utf8 +return utf8 diff --git a/xmake/core/sandbox/modules/interpreter/utf8.lua b/xmake/core/sandbox/modules/interpreter/utf8.lua new file mode 100644 index 000000000..0b8ef72a4 --- /dev/null +++ b/xmake/core/sandbox/modules/interpreter/utf8.lua @@ -0,0 +1,22 @@ +--!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 utf8.lua +-- + +-- load module +return require("sandbox/modules/utf8") diff --git a/xmake/core/sandbox/modules/utf8.lua b/xmake/core/sandbox/modules/utf8.lua new file mode 100644 index 000000000..a8450dc87 --- /dev/null +++ b/xmake/core/sandbox/modules/utf8.lua @@ -0,0 +1,21 @@ +--!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 utf8.lua +-- + +return require("base/utf8") 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 |
