跳转至

字符串处理

一、字符串处理概述

Air8101工业引擎模组通过luatos提供的强大且灵活的字符串处理函数,包括lua5.3标准string库,及自定义扩展的string库函数,来支持复杂的字符串操作,包括支持模式匹配的查找替换功能,灵活的在不同格式不同数据类型间的字符串转换,提供pack/unpack函数支持灵活的二进制数据打包解包,支持Base64/Base32与url编码等字符编解码功能;这些字符串处理函数都大大方便了在客户具体应用场景下的设计开发和功能验证。

二、准备硬件环境

“古人云:‘工欲善其事,必先利其器。’在深入介绍本功能示例之前,我们首先需要确保以下硬件环境的准备工作已经完成。”

参考:硬件环境清单,准备以及组装好硬件环境。

三、准备软件环境

“凡事预则立,不预则废。”在详细阐述本功能示例之前,我们需先精心筹备好以下软件环境。

1. Luatools 工具

2. 内核固件文件(底层 core 固件文件):LuatOS-SoC_V10001_Air8101.soc;参考项目使用的内核固件

3. luatos 需要的脚本和资源文件

脚本和资源文件:https://gitee.com/openLuat/LuatOS-Air8101/tree/master/demo/string

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8101 开发板,将本篇文章中演示使用的脚本和资源文件烧录到 Air8101 开发板中。

四、字符串处理的基本用法

4.1 字符串定义与特点

  • 单行字符串可以通过单引号或双引号来定义

        local str0 = 'Hello "LuatOS"' --单引号
        local str1 = "Hello 'LuatOS'" --双引号
        local str2 = "Hello \"LuatOS\"" --双引号,内部双引号通过反斜杠转义
    

    注:
    若字符串中包含了单引号,使用双引号来定义字符串会避免转义字符的需要。
    若字符串中包含了双引号,使用单引号来定义字符串会避免转义字符的需要。

  • 多行字符串可以通过双方括号来定义,使用双方括号时不会识别转义序列

       local str_lines = [[Hello Air8101!
    Hello "LuatOS"!]]
    

  • 字符串序列索引

    支持正向索引(从1开始)和负向索引(最后一个为-1)。例如:

    H e l l o
    正向 1 2 3 4 5
    负向 -5 -4 -3 -2 -1
  • lua字符串的特点

    • lua的字符串是带长度, 不依赖0x00作为结束字符串, 可以包含任意数据
    • lua字符串是不可变的。这意味着任何对字符串的操作都会返回一个新的字符串,而不是修改原字符串

4.2 文章内容引用

文章所使用的文件系统(io)接口函数不做详细介绍,可通过以下API接口说明参考链接查看具体介绍:

4.3 字符串接口应用介绍

4.3.1 字符串基本操作

1. 字符串长度获取

  • 使用内置#操作符来获取字符串长度
  • 使用string.len函数来获取字符串长度
        local text = "Hello LuatOS"
        local utf8_text = "你好 LuatOS"
        local multi_text = [[Hello LuatOS!
    Hello Air8101!\r\n
    ]]
        local len0 = string.len(text) -- 这里长度为12字节
        -- 使用UTF-8字符编码的情况下一个中文字符一般占3个字节
        local len1 = #utf8_test -- 这里长度为13字节
        -- 多行字符串长度统计包括空格换行等不可见字符(`[[`后面紧跟的一个换行符不包含在字符串内,不识别转义序列)
        local len_lines = #multi_texts -- 这里长度为33字节
    

2. 字符串拼接

  • 使用内置..操作符来拼接字符串
  • 只能为数字或字符串(其他类型如nil/boolean可以用tostring强制转换)
  • 左右两边建议保留有空格(与数字拼接时必需)
        local text = "hello " .. "luat " .. 2025 .. tostring(nil)
    

3. 字符串格式化输出

  • 使用string.format函数字符串格式化输出,它也可以达到拼接字符串的目的
        local text = string.format("%s %s %d %f", "hello", "luat", 2025, 1.253)
        print(text) -- 输出 hello luat 2025 1.253000
    
        -- str0和str1 对比 %q 和 %s 输出区别
        local str0 = string.format("return str is %q", '\rHello\n2025 "Lua"\0')
        print(str0)
        -- 输出  
        -- return str is "\13Hello\
        -- 2025 \"Lua\"\0"
        local str1 = string.format("return str is %s", '\rHello\n2025 "Lua"\0')
        print(str1)
        -- 输出
        -- return str is
        -- Hello
        -- 2025 "Lua" 
    

常用输出格式选项说明:

  • %q选项接受一个字符串转化为可安全被 Lua 编译器读入的格式 (显式显示特殊字符,忽略转义)返回一个带双引号的新字符串
  • %d选项接受一个整型,
  • %s选项接受一个字符串,
  • %c选项接受一个字符,
  • %f选项接受一个单浮点数,默认6位小数表示,%.xf方式x可以指定保留几位小数
  • %x选项接受一个整型十六进制hex格式表示

4.3.2 字符串常用转换操作

1. 大小写转换

  • 使用string.upper对输入的字符串中所有字母转换成大写
  • 使用string.lower对输入的字符串中所有字母转换成小写
    local text1 = string.lower("Hello LuatOS 2025!")
    log.info("str", text1) --转换为"hello luatos 2025!"
    local text2 = string.upper("Hello LuatOS 2025!")
    log.info("str", text2) --转换为"HELLO LUATOS 2025!"

2. 数字转字符串

  • 使用string.char多个数值,返回和参数数量相同长度的字符串
    local str = string.char(72, 101, 108, 108, 111)
    log.info("str", str) --输出"Hello"
  • 使用string.char也可以组成二进制数据字符串
    --例如:"\x00\x17\xFF\x7C\xAB"
    local bindat = string.char(0, 23, 255, 124, 171)

3. 字符串转数字

  • 使用string.byte获取字符串指定位置的字符对应数值
    -- 获取指定开始结束位置的数值
    local dat0,dat1,dat2,dat3,dat4 = string.byte("hello luatos", 6, 10)
    log.info("str", dat0,dat1,dat2,dat3,dat4) -- 输出 32 108  117  97  116
    -- 获取指定位置的一个数值
    local dat5 = string.byte("\x01\xAB", 2)
    log.info("str", dat5) -- 输出 171

4. 字符串与十六进制HEX字符串之间的转换

luatos扩展string库API增加string.toHex, string.fromHex支持字符串与十六进制HEX字符串的转换

  • string.toHex输入字符串转换成hex字符串,参数指定hex字符串的分隔符(默认为空字符串),返回hex字符串和字符串长度(长度不含分隔符)
    -- 默认分隔符为"" 空字符串
    local text = "Hello Luatos123"
    local hex_str, hex_len = string.toHex(text)
    log.info("str", text, hex_str, hex_len)
    -- 指定分隔符为"," 逗号
    local data = "\x01\xAA\x02\xFE\x23"
    local hex_data, hex_data_len = string.toHex(data, ",")
    log.info("str", hex_data, hex_data_len) -- 01,AA,02,FE,23, 长度为10
  • string.fromHex输入hex字符串转换返回字符串,忽略除0-9,a-f,A-F的其他字符(分隔符)
    local ret_data = string.fromHex("01 AA 02 FE 23 ")
    -- 二进制数据不可见,可以格式化输出
    log.info("str", string.format("%02X%02X%02X%02X%02X",ret_data:byte(1),ret_data:byte(2),ret_data:byte(3),ret_data:byt(4),ret_data:byte(5)))
    -- 输出 01AA02FE23

5. 字符串转二进制(BIN)数据字符串

luatos通过扩展string API接口,提供string.toValue函数来支持字符串转二进制字符串

    local bin_str,count = string.toValue("123abc") 
    -- 转换后返回二进制数据字符串 bin_str="\x0x01\x02\x03\x0A\x0B\x0C", 和已转换的字符数 count=6

4.3.3 二进制数据字符串打包与解包

在 Lua5.3 标准string库提供string.pack 和 string.unpack 接口来进行二进制数据打包和解包操作,支持多种数据类型和字节序格式;

这两个函数非常适合在需要与外部二进制协议或文件交互时使用, 例如:在网络通信中传输二进制数据;与外部系统进行二进制数据交换(如文件格式、数据库等);用于压缩或加密前的数据准备,尤其在需要固定大小的二进制格式时。

注:
字节序指在计算机存储或传输多字节数据时,字节的排列顺序,通常分为大端序(big-endian,最高有效字节在前)和小端序(little-endian,最低有效字节在前)。 大端也常被称作叫“网络序”因为 TCP、UDP 网络数据传输和存储都使用这种格式,而一些像 STM32 等 ARM 单片机则常用小端存储格式。

  • string.pack: 负责将不同的变量按照格式化选项规则打包在一起,成为一个二进制数据字符串
  • string.unpack: 将二进制数据字符串按照格式化选项规则来解包成为不同的变量
  • string.packsize: 可以按照格式化选项规则来计算二进制数据字符串的大小(不能包含变长选项如's','z')

    注:详细的格式化选项请参考lua5.3对此描述的链接 https://wiki.luatos.com/_static/lua53doc/manual.html#6.4.2

举例:按照这个(1, 4, 0xCD56AB12, 0x0A, 0x0104)数据以小端格式打包
对应的字符序列十六进制表示为 01 00 04 00 12 AB 56 CD 0A 04 01

    local pack_str = string.pack("<HHI4BH", 1, 4, 0xCD56AB12, 0x0A, 0x0104)
    local pack_size = string.packsize("<HHI4BH") -- 计算格式化选项对应输出数据的长度等于11字节

解包需以相同的格式化选项规则,否则会出现解包失败

    -- 可以一次性解包,这里第6个参数返回位置可以忽略
    local dat1,dat2,dat3,dat4,dat5 = string.unpack("<HHI4BH", test_bin_str)
    -- 可以指定开始位置来解包,返回已解数据及未解析的开始位置
    local pos
    dat1,pos = string.unpack("<H", test_bin_str)
    dat2,dat3,dat4,dat5 = string.unpack("<HI4BH", test_bin_str, pos)

此外,可以指定固定长度或变长来打包或解包,例如带长度存储的变长打包
使用选项sn,存储时会在字符串前加上该字符串的长度,其中n是用于保存字符串长度的无符号整型数的大小(n取值范围1-16)

    -- 测试依次打包数值,字符串,浮点数,string.packsize不能用于含s,z的变长选项
    -- 选项s6表示把字符串长度保存在6个字节中
    local pack_str = string.pack("i4s6f", 42, "Lua", 3.14159)

4.3.4 字符串模式匹配

这里对字符串复杂操作做简单应用举例说明,具体的api接口和参数描述请详见4.2文章内容引用中的lua5.3 string API描述和luatos扩展string API描述

注:这里模式匹配定义的pattern,因为lua小巧简洁的设计目标,不是支持全部posix标准正则表达式规则,lua对其规则有所调整和简化,详见参考链接: https://wiki.luatos.com/_static/lua53doc/manual.html#6.4.1

lua规定的pattern魔法字符有().%+-*?[]^$(12个)。

支持模式匹配的主要有四个API接口:string.find,string.match,string.gmatch,string.gsub

以下章节举例定义的text 为 "hello-air8101!hello luatos 2025!"

4.3.4.1 基于模式匹配的字符串查找

1. 使用string.find接口查找字符串中是否包含匹配的pattern,并返回查找匹配的起始和结束位置

  • 仅指定pattern(第二参数),默认打开模式匹配,获取起始和结束位置
    如果pattern含lua支持匹配的魔法字符,需用%符号,转义为原生字符才能匹配
    local s,e = string.find(text, "hello") 
    -- 返回值:s=1, e=5
    s,e = string.find(text, "hello%-air")
    -- 返回值:s=1, e=9
  • 仅指定pattern(第二参数),默认打开模式匹配,获取起始和结束位置,和捕获的匹配字符子串(仅返回第一个捕获的匹配子串),不带括号则没有捕获返回nil
    local s, e, sub_str = string.find(text, "(%d%d%d%d)") -- 需带括号才被捕获,否则返回nil
    -- 返回值: s=10,e=13,sub_str="8101"
    local s, e, sub_str = string.find(text, "%d(%d%d)%d") -- 需带括号才被捕获,否则返回nil
    -- 返回值: s=10,e=13,sub_str="10"
  • 指定起始位置init(第三参数), 从指定位置开始查找
    local s, e= string.find(text, "hello", 13)
    -- 返回值:s=15, e=19
  • 指定起始位置init(第三参数),关闭模式匹配(plain=true)(第四参数),只匹配普通字符串
    local s, e, sub_str = string.find(text, "%d%d%d%d", 15, true)
    -- 返回值:s=nil, e=nil, sub_str=nil

2. 使用string.match接口查找字符串中第一个匹配pattern的子串,匹配失败返回nil

  • 默认从索引1开始,pattern不带括号时返回匹配的全字符串
    local sub_str = string.match(text, "%d%d%d%d")
    -- 返回值:sub_str="8101"
  • 指定起始位置init(第三个参数),pattern带括号时只返回括号内捕获的匹配字符子串
    local sub_str = string.match(text, "%d(%d%d)%d", 15)
    -- 返回值:sub_str="02"

3. 使用string.gmatch接口,返回一个迭代器,依次匹配字符串中的所有符合模式的子串

    local str_table = {}
    local iter_func = string.gmatch(text, "%d%d%d%d")
    for val in iter_func do
        table.insert(str_table, val)
    end
    -- 执行结果 str_table = {"8101", "2025"}

4.3.4.2 基于模式匹配的字符串替换

1. 使用string.gsub接口来将字符串中的所有或部分匹配的pattern按指定方式替换,返回一个新字符串 可以指定替换的方式(接口的第三个参数),有三种:字符串,表,函数
可以指定替换的次数(接口的第四个参数,默认全部匹配替换)

  • 替换方式为字符串,若包含%d的串表示第d个捕获到的匹配子串,d可以是1到9(表示引用第1到9组捕获的匹配字串)。 串%0表示整个匹配(不要求捕获)
    -- 第二参数指定匹配"hello"字符串,第三参数指定替换为"hi", 第四个参数指定替换次数,替换1次,将hello替换成hi
    local new_text = string.gsub(text, "hello", "hi", 1)
    -- 返回值: new_text为"hi air8101!hello luatos 2025!"

    -- 第二参数指定pattern为(%w+)匹配所有字母及数字,把匹配到的子串作为第一组捕获(这里一对括号共一组捕获),通过%1来引用
    -- 第三参数替换方式为字符串,重复第一组捕获中间加","间隔
    -- 第四参数没指定则全部匹配替换
    local new_text, times = string.gsub(text, "(%w+)", "%1,%1")
    -- 返回值: new_text为"hello,hello-air8101,air8101!hello,hello luatos,luatos 2025,2025!",times=5(共替换5次)
  • 替换方式为table,匹配到的字符串当做键在表中查找,找到后用键对应值做替换, 取值第二个返回参数为替换次数
    local str_tab = {hello = "hi"}
    local new_text, times = string.gsub(text, "hello", str_tab)
    -- 返回值:new_text为"hi air8101!hi luatos 2025!",times=2(共替换2次)
  • 替换方式为function,查找到的字符当做函数的参数传入替换函数,由函数行为定义变更字符方式
    -- 这里定义替换函数将匹配内容的所有字母全部转换为大写字母
    local new_text, times = string.gsub(text, "hello", function (str)
        return string.upper(str)
    end)
    -- 返回值:new_text为"HELLO air8101!HELLO luatos 2025!",times=2(共替换2次)

4.3.5 字符串截取、复制与反转操作

1. 字符串截取

使用string.sub接口从字符串中提取一个子串,指定起始位置和结束位置。如果不指定结束位置,默认提取从起始位置到字符串的末尾

    local sub_str = string.sub("hello luatos!!", 7)
    -- 返回值:sub_str为luatos!!

2. 字符串复制

使用string.rep接口将字符串重复指定次数,返回一个新的字符串,第二个参数指定重复次数,第三个参数指定分分隔符(默认为空字符串)

    local new_str = string.rep("hello", 3)
    -- 返回值 new_str == "hellohellohello"
    new_str = string.rep("hello", 3, "!")
    -- 返回值 new_str == "hello!hello!hello"

3. 字符串反转

使用string.reverse将字符串的字符顺序反转

    local text = "Lua"
    print(string.reverse(text))  -- 输出:auL

4.3.6 字符串分隔符拆分

luatos通过扩展string库API,增加string.split接口来支持字符串拆分
string.split接口功能说明:将一个字符串可以按照指定分隔符(delimiter)拆分成多个子字符串

  • 函数第二参数为分隔符指定(默认","),第三个参数为是否保留空白片段(默认不保留),返回参数为拆分后的字符串表
    local text = "hello,world"
    -- 第二参数默认分隔符',',第三参数默认不保留空白片段
    local table_str = string.split(text)  --可选默认,第二三参数不填,
  • 若指定保留空白片段时,字符串中有连续的分隔符视为分隔两个空字符串,开始和结束有分隔符则分别都视为空字符串,选择保留空白则这些空字符串都会加入返回的字符串表里
    local text = "/hello//world/2025"
    -- 第二参数指定分隔符'/',第三参数保留空白片段
    local table_str = string.split(text,'/', true)
    print(#table_str) -- 输出5,有5个元素
    -- table_str = {"","hello","","world","2025"}

4.3.7 字符串编解码

luatos通过扩展string库API,增加Base64和Base32编解码和url编码的支持。

4.3.7.1 Base64/Base32编码
  • Base64 编码是将每3个字节重组为4组,每组6位
  • Base64 字符集: 字母和数字:A-Z(26 个字母)、a-z(26 个字母)、0-9(10 个数字) 符号:+ 和 / 填充符号:=(用于填充不足的字节),确保数据的长度是4的倍数

使用string.toBase64编码和string.fromBase64解码,编解码失败返回空字符串""。

    local text = "Hello"
    local base64_str = string.toBase64(text)
    print(base64_str) -- 输出 SGVsbG8=
    local ret_text = string.fromBase64(base64_str)
    print(ret_text) -- 输出 Hello

  • Base32 编码会将每5个字节重组为8组,每组5位
  • Base32 字符集: 字母和数字:A-Z(26 个字母)和 2-7(6 个数字) 填充符号:=(用于填充不足的字节),确保数据的长度是8的倍数

使用string.toBase32编码和string.fromBase32解码,编解码失败返回空字符串""。

    local text = "Luatos"
    local base32_str = string.toBase32(text)
    print(base32_str) -- 输出 JR2WC5DPOM======
    local ret_text = string.fromBase32(base32_str)
    print(ret_text) -- 输出 Luatos

4.3.7.1 URL编码

luatos扩展的url编码api接口不支持整个url路径的编码,使用字符串拼接方式结合分隔符来组合实现整个url路径编码

使用string.urlEncode接口实现url编码。

例子:
以下定义urltext为Hello world&luat/123*A_B

  • 第二个参数0:默认参考标准php(可不填),不转换的字符序列".-*_",空格用'+'
    local urlencode = string.urlEncode(urltext) 
    print(urlencode) -- 输出 Hello+world%26luat%2F123*A_B
  • 第二个参数1:参考rfc3986, 不转换的字符序列".-_", 空格用'%20'
    local urlencode = string.urlEncode(urltext, 1) 
    print(urlencode) -- 输出 Hello%20world%26luat%2F123%2AA_B
  • 第二个参数-1:表示自定义, 第三参数0:空格用'+',1:空格用'%20';第四参数指定不用转换的字符序列
    -- 指定"&/ "不转换,所以这里第三参数也不起作用
    local urlencode = string.urlEncode(urltext, -1, 0, "&/ ")
    print(urlencode) -- 输出 Hello world&luat/123%2AA%5FB

4.3.8 字符串前后缀操作

luatos通过扩展string库API,增加字符串前后缀判断和前后缀空白字符的裁剪

1. 字符串前后缀判断

使用string.startsWith判断字符串匹配指定前缀字符串,匹配返回true否则返回false

    local rc = string.startsWith("Hello", "He")
    -- 返回值 rc=true

使用string.endsWith判断字符串匹配指定后缀字符串,匹配返回true否则返回false

    local rc = string.startsWith("Hello\r\n", "\r\n")
    -- 返回值 rc=true

2. 字符串前后缀去除空白字符(包括空格、制表符(\t)、回车(\r)和换行(\n)符)

使用string.trim来去除前后缀空白字符,第二参数指定是否去除前缀空白字符(默认true为去除,可不填),第三参数指定是否去除后缀空白字符(默认true为去除,可不填)

    -- 默认去除前缀空白字符,默认去除后缀空白字符
    local ret_str = string.trim(" \tHello LuatOS2025\r\n")
    print(ret_str) -- 输出Hello LuatOS2025

4.4 Demo功能介绍

对于字符串处理的demo功能测试设计了一系列的应用测试用例来测试验证库函数接口,可以尽可能覆盖所定义函数接口的使用,让使用该模块功能的读者通过测试用例可以更清晰了解具体库函数与注意事项。

string 的demo test包括以下两部分:

1)Test用例函数:接口功能测试部分与异常与边界测试部分; 每部分都定义一系列测试函数用于覆盖测试支持的string库函数接口,让使用该模块功能的读者通过用例函数可以更清晰了解具体库函数与注意事项;测试函数主体实现中使用函数assert()来指示测试结果失败信息,详情参考5.1.2章节和具体demo代码实现。

2)Test用例运行:main.lua 定义了string测试执行的用例函数集(str_test_suite);定义了一个test_run函数来轮询执行该用例函数集,使用pcall调用来完成测试函数的执行,可以捕获执行函数的assert异常错误且不影响下个用例函数调用的执行,详情参考5.1.2章节和具体demo代码实现。

下表展示了Test用例测试内容和函数定义的对string库接口函数测试覆盖情况。

用例 测试函数定义 API接口覆盖
用例0101 字符串定义与基本操作 tc0101_str_base_operation string.len
用例0102 字符串模式匹配-查找 tc0102_str_pattern_matching string.find, string.gmatch, string.match
用例0103 字符串模式匹配-替换操作 tc0103_str_replace string.gsub
用例0104 字符截取、复制与反转操作 tc0104_str_truncate_copy_reverse string.rep, string.reverse, string.sub
用例0105 字符格式化与转换操作 tc0105_str_conversion string.byte, string.char, string.format, tring.lower, string.upper
用例0106 格式化二进制数据打包与解包 tc0106_str_bin_pack_unpack string.pack, string.unpack, string.packsize
用例0107 字符转换输出操作(扩展API) tc0107_str_ext_conversion string.toHex, string.fromHex, string.toValue
用例0108 字符编解码-Base64/Base32(扩展API) tc0108_str_ext_base64_and_base32 string.fromBase64, string.toBase64, string.fromBase32, string.toBase32
用例0109 字符编解码-URL编码(扩展API) tc0109_str_ext_urlcode string.urlEncode
用例0110 字符串分隔符拆分子字符串(扩展API) tc0110_str_ext_split string.split
用例0111 字符串前后缀判断与操作(扩展API) tc0111_str_ext_prefix_suffix string.startsWith, string.endsWith, string.trim
用例0201 测试空字符串参数 tc0201_str_ext_nullstr_param
用例0202 测试异常范围参数 tc0202_str_ext_over_range_param
用例0203 测试异常范围Base64或Base32参数 tc0203_str_ext_base64_base32_illegal_param

注:用例0101-0111 对应接口功能测试,用例0201-0203对应异常与边界测试

代码实现通过assert来指示具体demo测试哪个函数失败,失败定位在函数哪个位置;也便于自动化测试的集成,用例执行后输出执行结果Test pass数量和fail数量(如5.1.1截图展示),后续执行可以先关注测试结果的报告在结合log信息定位执行失败测试项。

五、字符串操作的整体演示

5.1 成果演示与深度解析

5.1.1 成果运行精彩呈现

按照下图所示下载string demo测试脚本

Alt text

注:调试遇到问题时可以勾选图中"脚本调试信息"选项(可以增加assert指示具体函数位置行号等调试信息)

String demo 测试运行结果:

1)展示共测试14项,当有测试项失败也会继续测试完所有测试才结束,如下图指示测试结果”Test run complete - Success: 12, Failures: 2"(此时可以根据fail提示定位到对应测试项的详细log分析问题)。

Alt text

2)展示共测试14项,测试项都pass则如下图所示。

Alt text

5.1.2 完整Demo实例深度剖析

关键代码实现与注释说明

  • main.lua:demo测试主函数实现,包括string接口测试函数实现,测试函数集定义及测试运行任务函数定义
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "demo_str_test"
VERSION = "1.0.0"

LOG_TAG = "str"
-- 引入必要的库文件(lua编写), 内部库不需要require
sys = require("sys")

--[[
用例0101 字符串定义与基本操作
1. lua的字符串是带长度, 不依赖0x00作为结束字符串, 可以包含任意数据
2. lua的字符串是不可变的, 就不能直接修改字符串的一个字符, 修改字符会返回一个新的字符串
]]--
function tc0101_str_base_operation()
    -- 双引号定义字符串,若字符串中包含了单引号,使用双引号来定义字符串会避免转义字符的需要
    local str1 = "Hello 'LuatOS'"
    -- 单引号定义字符串,若字符串中包含了双引号,使用单引号来定义字符串会避免转义字符的需要
    local str2 = '你好"LuatOS"'
    -- 双方括号定义多行字符串
    local str_lines = [[
Hello Air8101!
Hello "LuatOS"!\r\n
]]

    log.info(LOG_TAG, "双括号定义单行string:", str1)
    log.info(LOG_TAG, "单引号定义单行string:", str2)
    log.info(LOG_TAG, "双方括号定义多行string:", str_lines)

    -- 反斜杠转义字符
    local str3 = "Hello\nLuatOS"
    log.info(LOG_TAG, "string转义换行:", str3)

    -- 字符串长度获取的方式: #操作符或string.len
    local len1 = string.len(str1)
    log.info(LOG_TAG, str1 .. " len=", len1)
    assert(len1 == 14)
    -- 使用UTF-8字符编码的情况下一个中文字符一般占3个字节
    local len2 = #str2
    log.info(LOG_TAG, str2 .. " len=", len2)
    assert(len2 == 14)
    -- 多行字符串长度统计包括空格换行等不可见字符,(`[[`后面紧跟的一个换行符不包含在字符串内,不识别转义序列)
    local len_lines = #str_lines
    log.info(LOG_TAG, str_lines .. " len=", len_lines)
    assert(len_lines == 35)

    -- 字符串拼接, 使用".."来拼接, 
    -- 只能为数字或字符串(nil/boolean可以用tostring转换)
    -- 左右两边有空格(与数字拼接时必需)
    local str4 = "hello " .. "luat " .. 2025
    log.info(LOG_TAG, str4 .. " len=", #str4)
    assert(str4 == "hello luat 2025")
    str4 = "display " .. tostring(nil) .. " and " .. tostring(false)
    log.info(LOG_TAG, str4 .. " len=", #str4)
    assert(str4 == "display nil and false")
end


--[[
用例0102 字符串模式匹配-查找
]]--
function tc0102_str_pattern_matching()
    local text = "hello-air8101!hello luatos 2025!"
    local s,e,sub_str
    --[[
    string.find
    查找字符串中是否包含匹配的pattern,并返回匹配的位置
    ]]--
    -- 仅指定pattern, 默认打开模式匹配,如果pattern含lua支持匹配的魔法字符,需用%符号,转义为原生字符才能匹配
    s,e = string.find(text, "hello%-air")
    log.info(LOG_TAG, "match hello-air", s, e)
    assert(s == 1 and e == 9)
    -- 注意:这里需要带括号()返回捕获匹配子串,否则返回nil
    s, e, sub_str = string.find(text, "(%d%d%d%d)") 
    log.info(LOG_TAG, "match %d%d%d%d", s, e, sub_str)
    assert(s == 10 and e == 13 and sub_str == "8101")
    -- 指定起始位置init
    s, e= string.find(text, "hello", 13)
    log.info(LOG_TAG, "match hello", s, e)
    assert(s == 15 and e == 19)
    -- 指定起始位置init,关闭模式匹配(plain=true)
    s, e, sub_str = string.find(text, "%d%d%d%d", 15, true)
    log.info(LOG_TAG, "match %d%d%d%d", s, e, sub_str)
    assert(s == nil and e == nil and sub_str == nil)

    --[[
    string.match,
    查找字符串中第一个匹配pattern的字符串
    ]]-- 
    -- 不带括号时返回匹配的全字符串
    sub_str = string.match(text, "%d%d%d%d")
    log.info(LOG_TAG, "match %d%d%d%d", sub_str)
    assert(sub_str == "8101")
    -- 指定起始位置init,带括号时只返回括号中的捕获匹配子串
    sub_str = string.match(text, "%d(%d%d)%d", 15)
    log.info(LOG_TAG, "match %d(%d%d)%d", sub_str)
    assert(sub_str == "02")
    --[[
    string.gmatch,
    返回一个迭代器,依次匹配字符串中的所有符合模式的子串
    ]]-- 
    local str_table = {}
    local iter_func = string.gmatch(text, "%d%d%d%d")
    for val in iter_func do
        log.info(LOG_TAG, "gmatch ", val)
        table.insert(str_table, val)
    end
    assert(str_table[1] == "8101" and str_table[2] == "2025")
end

--[[
用例0103 字符串模式匹配-替换操作
]]--
function tc0103_str_replace()
    local text = "hello air8101!hello luatos 2025!"
    local new_text,times
    --[[
    string.gsub,
    替换字符串中的所有或部分匹配的模式,
    ]]-- 
    -- 替换参数为字符串, 指定匹配"hello"字符串,第三参数指定替换为"hi", 第四个参数指定替换次数,替换1次,将hello替换成hi
    local new_text = string.gsub(text, "hello", "hi", 1)
    -- 返回值: new_text为"hi air8101!hello luatos 2025!"
    log.info(LOG_TAG, text .. " change to ", new_text)
    assert(new_text == "hi air8101!hello luatos 2025!")
    -- 指定pattern为(%w+)匹配所有字母及数字,把匹配到的子串作为第一组捕获(这里一对括号共一组捕获),通过%1来引用
    -- 替换动作为重复第一组捕获中间加","间隔,全部匹配替换
    local new_text, times = string.gsub(text, "(%w+)", "%1,%1")
    log.info(LOG_TAG, text .. " change to ", new_text)
    assert(new_text == "hello,hello air8101,air8101!hello,hello luatos,luatos 2025,2025!" and times == 5)
    -- 替换参数为table,匹配到的字符串当做键在表中查找,找到后用键对应值做替换, 取值第二个返回参数为替换次数
    local str_tab = {hello = "hi"}
    new_text, times = string.gsub(text, "hello", str_tab)
    log.info(LOG_TAG, text .. " change to ", new_text)
    assert(new_text == "hi air8101!hi luatos 2025!" and times == 2)
    -- 替换参数为function,查找到的字符当做函数的参数传入替换函数,变更字符
    new_text, times = string.gsub(text, "hello", function (str)
        return string.upper(str)
    end)
    log.info(LOG_TAG, text .. " change to ", new_text)
    assert(new_text == "HELLO air8101!HELLO luatos 2025!" and times == 2)
end

--[[
用例0104 字符截取、复制与反转操作
]]--
function tc0104_str_truncate_copy_reverse()
    local text = "hello luatos!!"
    --[[
    string.sub,
    从一个字符串中截取指定范围的子字符串
    ]]--
    -- 截取起始位置7到末尾的子字符串
    local sub_str = string.sub(text, 7)
    log.info(LOG_TAG, "sub_str", sub_str)
    assert(sub_str == "luatos!!")
    -- 截取起始位置11到倒数第三位置
    local sub_str = string.sub(text, 11, -3)
    log.info(LOG_TAG, "sub_str", sub_str)
    assert(sub_str == "os")

    --[[
    string.rep
    字符串重复指定次数(可指定分隔符),返回一个新的字符串
    ]]--
    local new_str = string.rep("hello", 3)
    log.info(LOG_TAG, "new_str", new_str)
    assert(new_str == "hellohellohello")
    local new_str = string.rep("hello", 3, "!")
    log.info(LOG_TAG, "new_str", new_str)
    assert(new_str == "hello!hello!hello")

    --[[
    string.reverse
    返回字符串的反转字符串
    ]]--
    local new_str = string.reverse("hello*123")
    log.info(LOG_TAG, "new_str", new_str)
    assert(new_str == "321*olleh")
end

--[[
用例0105 字符格式化与转换操作
]]--
function tc0105_str_conversion()
    --[[
    string.lower
    字符串中的所有字母转换为小写
    ]]--
    local text1 = string.lower("Hello LuatOS 2025!")
    log.info(LOG_TAG, text1)
    assert(text1 == "hello luatos 2025!")
    --[[
    string.upper
    字符串中的所有字母转换为大写
    ]]--
    local text2 = string.upper("Hello LuatOS 2025!")
    log.info(LOG_TAG, text2)
    assert(text2 == "HELLO LUATOS 2025!")
    --[[
    string.char
    接收零或更多的整数,返回和参数数量相同长度的字符串
    ]]--
    local str = string.char(72, 101, 108, 108, 111)
    log.info(LOG_TAG, str)
    assert(str == "Hello")
    --二进制数据
    local bindat = string.char(0, 23, 255, 124, 171)
    assert(bindat == "\x00\x17\xFF\x7C\xAB")
    --[[
    string.byte
    返回字符串指定位置的内部数字编码值
    ]]--
    -- 获取指定开始结束位置的数值
    local dat0,dat1,dat2,dat3,dat4 = string.byte("hello luatos", 6, 10)
    log.info(LOG_TAG, dat0,dat1,dat2,dat3,dat4)
    assert(dat0==32 and dat1==108 and dat2==117 and dat3==97 and dat4==116)
    -- 获取指定位置的一个数值
    local dat5 = string.byte("\x01\xAB", 2)
    log.info(LOG_TAG, dat5)
    assert(dat5 == 171)

    --[[
    string.format
    格式化字符串并返回新字符串
    ]]--
    local str1 = '\t\rHello \n2025\\ "Lua"\0'    -- "\t\rHello \n2025\\ \"Lua\"\0"
    -- %q选项接受一个字符串转化为可安全被 Lua 编译器读入的格式
    -- (显式显示几乎所有特殊字符,忽略转义, 返回一个带双引号的新字符串)
    local fmt_str = string.format("return str is %q", str1)
    log.info(LOG_TAG, fmt_str) -- 返回带双引号的字符串
    assert(fmt_str == 'return str is "\\9\\13Hello \\\n2025\\\\ \\"Lua\\"\\0"')
    -- %d选项接受一个整型,%s选项接受一个字符串,%f选项接受一个单浮点数(默认6位小数)
    fmt_str = string.format("%d,%s,%f",123,"test",1.253)
    log.info(LOG_TAG, fmt_str)
    assert(fmt_str == "123,test,1.253000")
end

--[[
用例0106 格式化二进制数据打包与解包
]]--
function tc0106_str_bin_pack_unpack()
    -- 假设按照这个(1, 4, 0xCD56AB12, 0x0A, 0x0104)数据格式打包,
    -- 小端格式十六进制表示为:01 00 | 04 00 | 12 AB 56 CD | 0A | 04 01  
    local test_bin_str = "\x01\x00\x04\x00\x12\xAB\x56\xCD\x0A\x04\x01"
    local pack_str = string.pack("<HHI4BH", 1, 4, 0xCD56AB12, 0x0A, 0x0104)
    -- 这里也可以使用string.toHex(pack_str) 直接转换成十六进制数据字符串
    local fmt_str = string.format("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
                                    pack_str:byte(1),pack_str:byte(2),pack_str:byte(3),pack_str:byte(4),
                                    pack_str:byte(5),pack_str:byte(6),pack_str:byte(7),pack_str:byte(8),
                                    pack_str:byte(9),pack_str:byte(10),pack_str:byte(11))
    log.info(LOG_TAG, fmt_str, pack_str:len(), string.packsize("<HHI4BH"))
    assert(pack_str == test_bin_str and pack_str:len() == string.packsize("<HHI4BH"))
    -- 解包
    local dat1,dat2,dat3,dat4,dat5 = string.unpack("<HHI4BH", test_bin_str)
    log.info(LOG_TAG, string.format("%X,%X,%04X,%X,%X",dat1,dat2,dat3,dat4,dat5))
    assert(dat1 == 1 and dat2 == 4 and dat3 == 0xCD56AB12 and dat4 == 0x0A and dat5 == 0x104)

    -- 测试依次打包数值,字符串,浮点数,string.packsize不能用于含s,z的变长选项
    -- 选项s6表示把字符串长度保存在6个字节中
    pack_str = string.pack("i4s6f", 42, "Lua", 3.14159)
    log.info(LOG_TAG, pack_str:len())
    -- 可以分次解包,返回已解数据及未解析的开始位置
    local data1, pos = string.unpack("i4", pack_str)
    -- 可以分次解包,指定下次解析开始位置
    local data2, data3 = string.unpack("s6f", pack_str, pos)
    log.info(LOG_TAG, string.format("%d,%s,%.5f",data1,data2,data3))
    assert(data1 == 42 and data2 == "Lua" and string.format("%.5f",data3) == "3.14159")
end

--[[
用例0107 字符转换输出操作(扩展API)
]]--
function tc0107_str_ext_conversion()
    --[[
    string.toHex
    输入字符串转换,可指定分隔符,返回hex字符串和字符串长度(长度不含分隔符)
    ]]--
    -- 默认分隔符为"" 空字符串
    local text = "Hello Luatos123"
    local hex_str, hex_len = string.toHex(text)
    log.info(LOG_TAG, text, hex_str, hex_len)
    -- 指定分隔符为" " 空格
    local data = "\x01\xAA\x02\xFE\x23"
    local hex_data, hex_data_len = string.toHex(data, " ")
    log.info(LOG_TAG, hex_data, hex_data_len)
    local utf8_data = "你好。"
    local hex_utf8_data = string.toHex(utf8_data)
    log.info(LOG_TAG, hex_utf8_data)
    --[[
    string.fromHex
    输入hex字符串转换返回字符串,忽略除0-9,a-f,A-F的其他字符
    ]]--
    local ret_text = string.fromHex(hex_str)
    local ret_data = string.fromHex(hex_data)
    local ret_utf8_data = string.fromHex(hex_utf8_data)
    log.info(LOG_TAG, 
            ret_text, 
            string.format("%02X%02X%02X%02X%02X",ret_data:byte(1),ret_data:byte(2),
                            ret_data:byte(3),ret_data:byte(4),ret_data:byte(5)),    
            ret_utf8_data)
    assert(ret_text == text and ret_data == data and ret_utf8_data == utf8_data)

    --[[
    string.toValue
    输入返回二进制字符串,及转换的字符数
    ]]--
    local bin_str,count = string.toValue("123456abc")
    log.info(LOG_TAG, 
        -- 这里也可以使用string.toHex(bin_str) 直接转换成十六进制数据字符串
            string.format("%02X%02X%02X%02X%02X%02X%02X%02X%02X",
                        bin_str:byte(1),bin_str:byte(2),bin_str:byte(3),
                        bin_str:byte(4),bin_str:byte(5),bin_str:byte(6),
                        bin_str:byte(7),bin_str:byte(8),bin_str:byte(9)),
            count) -- count 为转换的字符数
    assert(bin_str == "\x01\x02\x03\x04\x05\x06\x0a\x0b\x0c" and count == 9)
end



--[[
用例0108 字符编解码-Base64/Base32(扩展API)
]]--
function tc0108_str_ext_base64_and_base32()
    -- 测试普通字符串base64编解码
    local text = "Hello"
    local base64_str = string.toBase64(text)
    log.info(LOG_TAG, base64_str)
    local ret_text = string.fromBase64(base64_str)
    assert(ret_text == text)
    -- 测试二进制数据字符串base64编解码
    local data = "\x01\xAB\x34\xFF"
    local base64_data = string.toBase64(data)
    log.info(LOG_TAG, base64_data)
    local ret_data = string.fromBase64(base64_data)
    assert(ret_data == data)
    -- 测试utf8数据字符串base64编解码
    local utf8_data = "你好"
    local base64_utf8data = string.toBase64(utf8_data)
    log.info(LOG_TAG, base64_utf8data)
    local ret_utf8_data = string.fromBase64(base64_utf8data)
    assert(utf8_data == utf8_data)
    -- 测试普通字符串base32编解码
    local text = "Luatos"
    local base64_str = string.toBase32(text)
    log.info(LOG_TAG, base64_str)
    local ret_text = string.fromBase32(base64_str)
    assert(ret_text == text)
    -- 测试二进制数据字符串base32编解码
    local data = "\x01\xAB\x34\xFF"
    local base64_data = string.toBase32(data)
    log.info(LOG_TAG, base64_data)
    local ret_data = string.fromBase32(base64_data)
    assert(ret_data == data)
    -- 测试utf8数据字符串base32编解码
    local utf8_data = "你好"
    local base64_utf8data = string.toBase32(utf8_data)
    log.info(LOG_TAG, base64_utf8data)
    local ret_utf8_data = string.fromBase32(base64_utf8data)
    assert(utf8_data == utf8_data)
end


--[[
用例0109 字符编解码-URL编码(扩展API)
]]--
function tc0109_str_ext_urlcode()
    local text = "Hello world&luat/123*A_B"
    -- 第二个参数0:默认参考标准php(可不填),不转换的字符序列".-*_",空格用'+'
    local urlencode = string.urlEncode(text) 
    log.info(LOG_TAG, "defalut php", urlencode)
    assert(urlencode == "Hello+world%26luat%2F123*A_B")
    -- 第二个参数1:参考rfc3986,不转换的字符序列".-_",空格用'%20'
    urlencode = string.urlEncode(text, 1) 
    log.info(LOG_TAG, "rfc3986", urlencode)
    assert(urlencode == "Hello%20world%26luat%2F123%2AA_B")
    -- 第二个参数-1:表示自定义, 第三参数0:空格用'+',1:空格用'%20';第四参数指定不用转换的字符序列
    urlencode = string.urlEncode(text, -1, 0, "&/ ") 
    log.info(LOG_TAG, "self defined", urlencode)
    assert(urlencode == "Hello world&luat/123%2AA%5FB")
end


--[[
用例0110 字符串分隔符拆分子字符串(扩展API)
]]--
function tc0110_str_ext_split()
    local text = "/hello,///world,**/luatos//2025*/"
    -- 默认分隔符',',默认不保留空白片段
    local table1 = string.split(text)  -- 这里第二三参数不填使用默认值
    log.info(LOG_TAG, #table1, table1[1], table1[2], table1[3])
    assert(table1[1] == "/hello" and table1[2] == "///world" and table1[3] == "**/luatos//2025*/")

    -- 分隔符'/',保留空白片段,字符串中有连续的分隔符视为分隔两个空字符串,开始和结束有分隔符则分别都有空字符串
    local table2 = string.split(text,'/', true)
    log.info(LOG_TAG, #table2, json.encode(table2))
    assert(table2[1] == "" and table2[2] == "hello," and table2[3] == ""
            and table2[4] == "" and table2[5] == "world,**"
            and table2[6] == "luatos" and table2[7] == ""
            and table2[8] == "2025*" and table2[9] == "")

    -- 分隔符'*',保留空白片段,采用':'调用函数,text当作第一个实参传递给函数,等同string.split(text)
    local table3 = text:split('*', true)
    log.info(LOG_TAG, #table3, json.encode(table3))
    assert(table3[1] == "/hello,///world," and table3[2] == "" and table3[3] == "/luatos//2025" and table3[4] == "/")

    -- 分隔符'o', 不保留空白片段
    local table4 = text:split('o')
    log.info(LOG_TAG, #table4, json.encode(table4))
    assert(table4[1] == "/hell" and table4[2] == ",///w" and table4[3] == "rld,**/luat" and table4[4] == "s//2025*/")
end

--[[
用例0111 字符串前后缀判断与操作(扩展API)
]]--
function tc0111_str_ext_prefix_suffix()
    local text = " \tHello LuatOS2025\r\n"
    --[[
    string.trim
    去除字符串头尾的空白字符包括空格、制表符(\t)、回车(\r)和换行(\n)符
    ]]--
    -- 第二参数true表示去除前缀(默认true可不填),第三参数true表示去除后缀(默认true可不填)
    local ret_str1 = string.trim(text)
    log.info(LOG_TAG, #ret_str1, ret_str1)
    assert(ret_str1 == "Hello LuatOS2025")
    -- 第二参数true表示去除前缀,第三参数false表示保留后缀
    local ret_str2 = string.trim(text, true, false)
    log.info(LOG_TAG, #ret_str2, ret_str2)
    assert(ret_str2 == "Hello LuatOS2025\r\n")
    --[[
    string.startsWith
    判断字符串前缀,匹配前缀返回true
    ]]--
    local rc = string.startsWith(ret_str1, "He")
    log.info(LOG_TAG, "startwith \"He\", rc=", rc)
    assert(rc)
    rc = string.startsWith(text, " \t")
    log.info(LOG_TAG, "startwith \" \\t\", rc=", rc)
    assert(rc)
    --[[
    string.endsWith
    判断字符串后缀,匹配后缀返回true
    ]]--
    rc = string.endsWith(ret_str1, "25")
    log.info(LOG_TAG, "endwith \"25\",rc=", rc)
    assert(rc)
    rc = string.endsWith(text, "\r\n")
    log.info(LOG_TAG, "endwith \"\\r\\n\",rc=", rc)
    assert(rc)
end


--[[
用例0201 测试空字符串参数(仅测试扩展API接口)
]]--
function tc0201_str_ext_nullstr_param()
    local ret_str, ret_len = string.toHex("")
    log.info(LOG_TAG,"toHex:", ret_str, ret_len)
    assert(ret_str == "")
    ret_str = string.fromHex("")
    log.info(LOG_TAG,"fromHex:", ret_str)
    assert(ret_str == "")
    ret_str,ret_len = string.toValue("")
    log.info(LOG_TAG,"toValue:", ret_str, ret_len)
    assert(ret_str == "")
    ret_str = string.toBase64("")
    log.info(LOG_TAG,"toBase64:", ret_str)
    assert(ret_str == "")
    ret_str = string.fromBase64("")
    log.info(LOG_TAG,"fromBase64:", ret_str)
    assert(ret_str == "")
    ret_str = string.toBase32("")
    log.info(LOG_TAG,"toBase32:", ret_str)
    assert(ret_str == "")
    ret_str = string.fromBase32("")
    log.info(LOG_TAG,"fromBase32:", ret_str)
    assert(ret_str == "")
    ret_str = string.urlEncode("")
    log.info(LOG_TAG,"urlEncode:", ret_str)
    assert(ret_str == "")
    local ret_tab = string.split("")
    log.info(LOG_TAG,"split:", ret_tab)
    assert(type(ret_tab) == "table" and #ret_tab == 0)
    local rc = string.startsWith("","")
    log.info(LOG_TAG,"startsWith:", rc)
    assert(rc == true)
    rc = string.endsWith("","")
    log.info(LOG_TAG,"endsWith:", rc)
    assert(rc == true)
    ret_str = string.trim("")
    log.info(LOG_TAG,"trim:", ret_str)
    assert(ret_str == "")
end

--[[
用例0202 测试异常范围参数(扩展API接口)
]]--
function tc0202_str_ext_over_range_param()
    -- 字符处理方法:char >'9'则(char+9)&0x0f, 否则char&0x0f
    local ret_str,ret_len = string.toValue("1qWe")
    log.info(LOG_TAG, 
            string.format("%02X%02X%02X%02X",
                ret_str:byte(1),ret_str:byte(2),ret_str:byte(3),ret_str:byte(4)),
            ret_len)
    assert(ret_str == "\x01\x0A\x00\x0E")
    ret_str,ret_len = string.toValue(" \r\n")
    log.info(LOG_TAG, 
            string.format("%02X%02X%02X",
                ret_str:byte(1),ret_str:byte(2),ret_str:byte(3)),
            ret_len)
    assert(ret_str == "\x00\x0D\x0A")
    -- 指定异常前后缀的判断比较
    local rc = string.startsWith("hello","12")
    log.info(LOG_TAG,"startsWith:", rc)
    assert(rc == false)
    rc = string.startsWith("hello","hello1")
    log.info(LOG_TAG,"startsWith:", rc)
    assert(rc == false)
    rc = string.startsWith("hello","\0")
    log.info(LOG_TAG,"startsWith:", rc)
    assert(rc == false)
    rc = string.startsWith("hello","")
    log.info(LOG_TAG,"startsWith:", rc)
    assert(rc == true)
    rc = string.endsWith("hello","hello")
    log.info(LOG_TAG,"endsWith:", rc)
    assert(rc == true)
    rc = string.endsWith("hello","")
    log.info(LOG_TAG,"endsWith:", rc)
    assert(rc == true)
end

--[[
用例0203 测试异常范围Base64或Base32参数(扩展API接口)
]]--
function tc0203_str_ext_base64_base32_illegal_param()
    -- Base64/Base32解码测试自动补全填充符'='
    -- 不支持补全填充符?不检查长度?
    local encoded = "SGVsbG8"  -- 缺少填充符
    local decoded = string.fromBase64(encoded)
    log.info(LOG_TAG,"decoded:", decoded)
    assert(decoded == "Hel", "Base64 decode fail")

    encoded = "JR2WC5DPOM"  -- 缺少填充符
    decoded = string.fromBase32(encoded)
    log.info(LOG_TAG,"decoded:", decoded)
    assert(decoded == "Luatos", "Base32 decode fail")

    -- Base64/Base32解码测试异常字符参数
    encoded = "SGsb*G8!"  -- 非法字符
    decoded = string.fromBase64(encoded)
    log.info(LOG_TAG,"decoded:", decoded)
    -- 解码失败应该返回空字符串
    assert(decoded == "", "Base64 igllegal param")

    encoded = "JR2W*08C5DP!"  -- 非法字符 
    decoded = string.fromBase32(encoded)
    log.info(LOG_TAG,"decoded:", decoded)
    -- 解码失败应该返回空字符串
    assert(decoded == "", "Base32 igllegal param")
end

-- 测试用例的table,以函数名为键,函数为值,实际运行无序可以增加测试的随机性
-- string的测试函数集
local str_test_suite = {
    --[[
    接口功能测试
    ]]--
    ["tc0101_str_base_operation"] = tc0101_str_base_operation,
    ["tc0102_str_pattern_matching"] = tc0102_str_pattern_matching,
    ["tc0103_str_replace"] = tc0103_str_replace,
    ["tc0104_str_truncate_copy_reverse"] = tc0104_str_truncate_copy_reverse,
    ["tc0105_str_conversion"] = tc0105_str_conversion,
    ["tc0106_str_bin_pack_unpack"] = tc0106_str_bin_pack_unpack,
    ["tc0107_str_ext_conversion"] = tc0107_str_ext_conversion,
    ["tc0108_str_ext_base64_and_base32"] = tc0108_str_ext_base64_and_base32,
    ["tc0109_str_ext_urlcode"] = tc0109_str_ext_urlcode,
    ["tc0110_str_ext_split"] = tc0110_str_ext_split,
    ["tc0111_str_ext_prefix_suffix"] = tc0111_str_ext_prefix_suffix,
    --[[
    异常与边界测试
    ]]--
    ["tc0201_str_ext_nullstr_param"] = tc0201_str_ext_nullstr_param,
    ["tc0202_str_ext_over_range_param"] = tc0202_str_ext_over_range_param,
    ["tc0203_str_ext_base64_base32_illegal_param"] = tc0203_str_ext_base64_base32_illegal_param,
}

-- test_run函数接受一个table参数,table中包含测试用例函数
-- 调用了sys.wait()需要在任务中调用
function test_run(test_cases)
    local successCount = 0  -- 成功的测试用例计数
    local failureCount = 0  -- 失败的测试用例计数

    -- 遍历table中的每个测试用例函数
    for name, testCase in pairs(test_cases) do
        -- 检查testCase是否为函数
        if type(testCase) == "function" then
            -- 使用pcall来捕获测试用例执行中的错误, 若有错误也继续往下执行
            local success, err = pcall(testCase)  
            if success then
                -- 测试成功,增加成功计数
                successCount = successCount + 1  
                log.info(LOG_TAG, "Test case passed: " .. name)
            else
                -- 测试失败,增加失败计数
                failureCount = failureCount + 1  
                log.info(LOG_TAG, "Test case failed: " .. name .. " - Error: " .. err)
            end
        else
            log.info(LOG_TAG, "Skipping non-function entry: " .. name)
        end
        -- 稍等一会在继续测试
        sys.wait(1000)
    end
    -- 打印测试结果
    log.info(LOG_TAG, "Test run complete - Success: " .. successCount .. ", Failures: " .. failureCount)
end


-- 定义任务来开始demo测试流程
sys.taskInit(function()
    -- 为了显示日志,这里特意延迟一秒
    -- 正常使用不需要delay
    sys.wait(1000)

    -- 运行测试套件
    test_run(str_test_suite)
end)


-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

六、总结

lua的string库提供了丰富的函数来处理字符串数据。它的基本操作包括字符串的查询、替换、格式化、子串提取、字符转换操作等,string.pack 和 string.unpack 为处理二进制数据提供了强大的支持,此外luatos也扩展string api增加hex字串转换,Base64/Base32编解码,url编码,字符串分隔拆分等接口为物联网及网络应用开发提供了灵活与便利性。在实际开发中,我们需要特别注意空字符串、索引越界、模式匹配和格式化等边界情况,以确保代码的健壮性和稳定性。

七、扩展

后续扩展在此补充,敬请期待......