From dd1012924520c1d3fa08be62d27155cc28687984 Mon Sep 17 00:00:00 2001 From: Lamdonn Date: Mon, 22 Apr 2024 00:09:51 +0800 Subject: [PATCH] varch init --- .gitignore | 29 + README.md | 212 ++- doc/varch:calculate运算表达式.md | 91 + doc/varch:check校验算法.md | 108 ++ doc/varch:command命令解析.md | 112 ++ doc/varch:csv解析器.md | 283 +++ doc/varch:deque容器.md | 187 ++ doc/varch:dict容器(哈希表).md | 369 ++++ doc/varch:dict容器(红黑树).md | 155 ++ doc/varch:encrypt加解密算法.md | 140 ++ doc/varch:hash算法.md | 58 + doc/varch:ini解析器.md | 649 +++++++ doc/varch:json解析器.md | 379 ++++ doc/varch:kern定时任务调度内核.md | 96 + doc/varch:list容器.md | 255 +++ doc/varch:map容器.md | 232 +++ doc/varch:queue容器.md | 183 ++ doc/varch:set容器.md | 254 +++ doc/varch:sort算法.md | 372 ++++ doc/varch:stack容器.md | 177 ++ doc/varch:str字符串变量容器.md | 874 +++++++++ doc/varch:txls解析器.md | 506 ++++++ doc/varch:vector容器.md | 353 ++++ doc/varch:xml解析器.md | 272 +++ image/logo.png | Bin 0 -> 49459 bytes makefile | 101 ++ run.sh | 23 + source/00_application/console/console.c | 131 ++ source/00_application/console/console.h | 36 + source/00_application/init.c | 37 + source/00_application/init.h | 71 + source/00_application/main.c | 70 + source/01_general/arg.h | 311 ++++ source/01_general/cPatten.c | 311 ++++ source/01_general/cPatten.h | 29 + source/01_general/cQueue.c | 43 + source/01_general/cQueue.h | 119 ++ source/01_general/calculate.c | 440 +++++ source/01_general/calculate.h | 40 + source/01_general/command.c | 517 ++++++ source/01_general/command.h | 117 ++ source/01_general/dList.c | 462 +++++ source/01_general/dList.h | 227 +++ source/01_general/fsm.c | 50 + source/01_general/fsm.h | 134 ++ source/01_general/kern.c | 157 ++ source/01_general/kern.h | 75 + source/01_general/oscp.c | 105 ++ source/01_general/oscp.h | 55 + source/01_general/sList.c | 368 ++++ source/01_general/sList.h | 216 +++ source/01_general/tool.c | 138 ++ source/01_general/tool.h | 108 ++ source/01_general/valloc.c | 524 ++++++ source/01_general/valloc.h | 74 + source/01_general/vlog.c | 112 ++ source/01_general/vlog.h | 45 + source/02_vstd/vctype.c | 124 ++ source/02_vstd/vctype.h | 43 + source/02_vstd/vmath.c | 215 +++ source/02_vstd/vmath.h | 37 + source/02_vstd/vmem.c | 146 ++ source/02_vstd/vmem.h | 20 + source/02_vstd/vstddef.h | 33 + source/02_vstd/vstdint.h | 119 ++ source/02_vstd/vstdlib.c | 356 ++++ source/02_vstd/vstdlib.h | 33 + source/02_vstd/vstring.c | 619 +++++++ source/02_vstd/vstring.h | 43 + source/03_container/deque.c | 230 +++ source/03_container/deque.h | 158 ++ source/03_container/dict.c | 388 ++++ source/03_container/dict.h | 169 ++ source/03_container/heap.c | 243 +++ source/03_container/heap.h | 110 ++ source/03_container/list.c | 212 +++ source/03_container/list.h | 144 ++ source/03_container/map.c | 748 ++++++++ source/03_container/map.h | 163 ++ source/03_container/map_cfg.c | 89 + source/03_container/map_cfg.h | 46 + source/03_container/queue.c | 193 ++ source/03_container/queue.h | 142 ++ source/03_container/set.c | 645 +++++++ source/03_container/set.h | 145 ++ source/03_container/stack.c | 182 ++ source/03_container/stack.h | 142 ++ source/03_container/str.c | 840 +++++++++ source/03_container/str.h | 292 +++ source/03_container/tree.c | 424 +++++ source/03_container/tree.h | 203 +++ source/03_container/vector.c | 192 ++ source/03_container/vector.h | 162 ++ source/04_algorithm/check.c | 84 + source/04_algorithm/check.h | 29 + source/04_algorithm/crc.c | 596 ++++++ source/04_algorithm/crc.h | 103 ++ source/04_algorithm/encrypt.c | 375 ++++ source/04_algorithm/encrypt.h | 47 + source/04_algorithm/hash.c | 283 +++ source/04_algorithm/hash.h | 42 + source/04_algorithm/sort.c | 442 +++++ source/04_algorithm/sort.h | 102 ++ source/05_parser/csv.c | 2208 +++++++++++++++++++++++ source/05_parser/csv.h | 123 ++ source/05_parser/ini.c | 1400 ++++++++++++++ source/05_parser/ini.h | 96 + source/05_parser/json.c | 2013 +++++++++++++++++++++ source/05_parser/json.h | 209 +++ source/05_parser/txls.c | 1396 ++++++++++++++ source/05_parser/txls.h | 101 ++ source/05_parser/xml.c | 1621 +++++++++++++++++ source/05_parser/xml.h | 93 + test/file/read.csv | 5 + test/file/read.ini | 28 + test/file/read.json | 15 + test/file/read.md | 3 + test/file/read.xml | 15 + test/file/write.csv | 4 + test/file/write.ini | 25 + test/file/write.json | 17 + test/file/write.md | 7 + test/file/write.xml | 5 + test/test_cQueue.c | 73 + test/test_calculate.c | 54 + test/test_check.c | 38 + test/test_command.c | 52 + test/test_crc.c | 65 + test/test_csv.c | 188 ++ test/test_dList.c | 337 ++++ test/test_deque.c | 33 + test/test_dict.c | 131 ++ test/test_encrypt.c | 100 + test/test_hash.c | 26 + test/test_heap.c | 49 + test/test_ini.c | 86 + test/test_init.c | 38 + test/test_json.c | 121 ++ test/test_kern.c | 48 + test/test_list.c | 67 + test/test_map.c | 73 + test/test_oscp.c | 56 + test/test_pthread.c | 32 + test/test_queue.c | 34 + test/test_rbtree.c | 25 + test/test_sList.c | 334 ++++ test/test_set.c | 65 + test/test_sort.c | 219 +++ test/test_stack.c | 33 + test/test_str.c | 17 + test/test_tool.c | 57 + test/test_tree.c | 214 +++ test/test_txls.c | 122 ++ test/test_valloc.c | 28 + test/test_vector.c | 33 + test/test_vlog.c | 20 + test/test_vstd.c | 30 + test/test_xml.c | 66 + 158 files changed, 34055 insertions(+), 38 deletions(-) create mode 100644 .gitignore create mode 100644 doc/varch:calculate运算表达式.md create mode 100644 doc/varch:check校验算法.md create mode 100644 doc/varch:command命令解析.md create mode 100644 doc/varch:csv解析器.md create mode 100644 doc/varch:deque容器.md create mode 100644 doc/varch:dict容器(哈希表).md create mode 100644 doc/varch:dict容器(红黑树).md create mode 100644 doc/varch:encrypt加解密算法.md create mode 100644 doc/varch:hash算法.md create mode 100644 doc/varch:ini解析器.md create mode 100644 doc/varch:json解析器.md create mode 100644 doc/varch:kern定时任务调度内核.md create mode 100644 doc/varch:list容器.md create mode 100644 doc/varch:map容器.md create mode 100644 doc/varch:queue容器.md create mode 100644 doc/varch:set容器.md create mode 100644 doc/varch:sort算法.md create mode 100644 doc/varch:stack容器.md create mode 100644 doc/varch:str字符串变量容器.md create mode 100644 doc/varch:txls解析器.md create mode 100644 doc/varch:vector容器.md create mode 100644 doc/varch:xml解析器.md create mode 100644 image/logo.png create mode 100644 makefile create mode 100644 run.sh create mode 100644 source/00_application/console/console.c create mode 100644 source/00_application/console/console.h create mode 100644 source/00_application/init.c create mode 100644 source/00_application/init.h create mode 100644 source/00_application/main.c create mode 100644 source/01_general/arg.h create mode 100644 source/01_general/cPatten.c create mode 100644 source/01_general/cPatten.h create mode 100644 source/01_general/cQueue.c create mode 100644 source/01_general/cQueue.h create mode 100644 source/01_general/calculate.c create mode 100644 source/01_general/calculate.h create mode 100644 source/01_general/command.c create mode 100644 source/01_general/command.h create mode 100644 source/01_general/dList.c create mode 100644 source/01_general/dList.h create mode 100644 source/01_general/fsm.c create mode 100644 source/01_general/fsm.h create mode 100644 source/01_general/kern.c create mode 100644 source/01_general/kern.h create mode 100644 source/01_general/oscp.c create mode 100644 source/01_general/oscp.h create mode 100644 source/01_general/sList.c create mode 100644 source/01_general/sList.h create mode 100644 source/01_general/tool.c create mode 100644 source/01_general/tool.h create mode 100644 source/01_general/valloc.c create mode 100644 source/01_general/valloc.h create mode 100644 source/01_general/vlog.c create mode 100644 source/01_general/vlog.h create mode 100644 source/02_vstd/vctype.c create mode 100644 source/02_vstd/vctype.h create mode 100644 source/02_vstd/vmath.c create mode 100644 source/02_vstd/vmath.h create mode 100644 source/02_vstd/vmem.c create mode 100644 source/02_vstd/vmem.h create mode 100644 source/02_vstd/vstddef.h create mode 100644 source/02_vstd/vstdint.h create mode 100644 source/02_vstd/vstdlib.c create mode 100644 source/02_vstd/vstdlib.h create mode 100644 source/02_vstd/vstring.c create mode 100644 source/02_vstd/vstring.h create mode 100644 source/03_container/deque.c create mode 100644 source/03_container/deque.h create mode 100644 source/03_container/dict.c create mode 100644 source/03_container/dict.h create mode 100644 source/03_container/heap.c create mode 100644 source/03_container/heap.h create mode 100644 source/03_container/list.c create mode 100644 source/03_container/list.h create mode 100644 source/03_container/map.c create mode 100644 source/03_container/map.h create mode 100644 source/03_container/map_cfg.c create mode 100644 source/03_container/map_cfg.h create mode 100644 source/03_container/queue.c create mode 100644 source/03_container/queue.h create mode 100644 source/03_container/set.c create mode 100644 source/03_container/set.h create mode 100644 source/03_container/stack.c create mode 100644 source/03_container/stack.h create mode 100644 source/03_container/str.c create mode 100644 source/03_container/str.h create mode 100644 source/03_container/tree.c create mode 100644 source/03_container/tree.h create mode 100644 source/03_container/vector.c create mode 100644 source/03_container/vector.h create mode 100644 source/04_algorithm/check.c create mode 100644 source/04_algorithm/check.h create mode 100644 source/04_algorithm/crc.c create mode 100644 source/04_algorithm/crc.h create mode 100644 source/04_algorithm/encrypt.c create mode 100644 source/04_algorithm/encrypt.h create mode 100644 source/04_algorithm/hash.c create mode 100644 source/04_algorithm/hash.h create mode 100644 source/04_algorithm/sort.c create mode 100644 source/04_algorithm/sort.h create mode 100644 source/05_parser/csv.c create mode 100644 source/05_parser/csv.h create mode 100644 source/05_parser/ini.c create mode 100644 source/05_parser/ini.h create mode 100644 source/05_parser/json.c create mode 100644 source/05_parser/json.h create mode 100644 source/05_parser/txls.c create mode 100644 source/05_parser/txls.h create mode 100644 source/05_parser/xml.c create mode 100644 source/05_parser/xml.h create mode 100644 test/file/read.csv create mode 100644 test/file/read.ini create mode 100644 test/file/read.json create mode 100644 test/file/read.md create mode 100644 test/file/read.xml create mode 100644 test/file/write.csv create mode 100644 test/file/write.ini create mode 100644 test/file/write.json create mode 100644 test/file/write.md create mode 100644 test/file/write.xml create mode 100644 test/test_cQueue.c create mode 100644 test/test_calculate.c create mode 100644 test/test_check.c create mode 100644 test/test_command.c create mode 100644 test/test_crc.c create mode 100644 test/test_csv.c create mode 100644 test/test_dList.c create mode 100644 test/test_deque.c create mode 100644 test/test_dict.c create mode 100644 test/test_encrypt.c create mode 100644 test/test_hash.c create mode 100644 test/test_heap.c create mode 100644 test/test_ini.c create mode 100644 test/test_init.c create mode 100644 test/test_json.c create mode 100644 test/test_kern.c create mode 100644 test/test_list.c create mode 100644 test/test_map.c create mode 100644 test/test_oscp.c create mode 100644 test/test_pthread.c create mode 100644 test/test_queue.c create mode 100644 test/test_rbtree.c create mode 100644 test/test_sList.c create mode 100644 test/test_set.c create mode 100644 test/test_sort.c create mode 100644 test/test_stack.c create mode 100644 test/test_str.c create mode 100644 test/test_tool.c create mode 100644 test/test_tree.c create mode 100644 test/test_txls.c create mode 100644 test/test_valloc.c create mode 100644 test/test_vector.c create mode 100644 test/test_vlog.c create mode 100644 test/test_vstd.c create mode 100644 test/test_xml.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf1bb21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Build and Release Folders +bin-debug/ +bin-release/ +[Oo]bj/ +[Bb]in/ + +# Other files and folders +.settings/ + +# Executables +*.swf +*.air +*.ipa +*.apk + +# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` +# should NOT be excluded as they contain compiler settings and other important +# information for Eclipse / Flash Builder. +.lightly +.c_bin +.classes +logs +*.log +node_modules +dist +dist-ssr +*.local +.vscode/ +built/ \ No newline at end of file diff --git a/README.md b/README.md index af31dff..9941473 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,174 @@ -# varch - -#### 介绍 -嵌入式C语言常用代码模块库,包含了嵌入式中常用的算法库、数据结构(容器)库、解析器库、独立C语言std库、工具库等等。 -具有简单、通用、高效的特点,目的为了学习以及在开发中拿来就用,提高开发效率以及代码可靠稳定性。 - -#### 软件架构 -软件架构说明 - - -#### 安装教程 - -1. xxxx -2. xxxx -3. xxxx - -#### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +# varch + +![替代文字](/image/logo.png) + +## 介绍 + +varch(we-architecture,意为我们的框架库)是嵌入式C语言常用代码模块库,包含了嵌入式中常用的算法库、数据结构(容器)库、解析器库、独立C语言std库、工具库等等。 +具有**简单、通用、高效**的特点,目的为了**学习**以及在开发中**拿来就用**,提高开发效率以及代码可靠稳定性。 + +## 内容 + +### 一级目录结构 +``` +> varch +|---> built // 编译相关的文件夹 +|---> doc // varch模块介绍文档 +|---> source // varch源码目录 +|---> LICENSE // 开源许可 GPL3 +'---> README.md // 本文件 +``` + +### 二级目录source +``` +> source +|---> general // 存放varch一般的代码 +|---> application // 存放应用层的代码 +|---> platform // 存放平台相关的代码 +|---> template // 存放模板型代码,一般是相同模块不同实现形式的代码 +|---> lib // 通用库代码 +|---> tool // 工具库代码 +'---> devices // 模拟设备代码 +``` + +### 三级目录 + +#### general +``` +> source/general +|---> calculate // 计算式模块,输入计算表达式得到计算结果 +|---> command // 命令解析模块,输入字符串命令(类似shell命令),执行相应命令函数 +|---> vlog // 日志输出模块 +|---> kern // 周期任务调度内核模块,在varch测试中基本都是使用此周期任务 +|---> vserdes // 虚拟串行解串器模块 +|---> vcan // 虚拟CAN总线通信模块 +|---> vuart // 虚拟uart串口通信模块 +|---> vio // 虚拟IO口模块 +'---> std_types.h // varch标准类型头文件 +``` + +#### lib +``` +> source/lib +|---> container // 数据结构容器库 +|---> algorithm // 算法库 +|---> vstd // std库 +'---> parser // 解析器库 +``` + +* container +``` +> source/lib/container +|---> queue.c // 通用队列容器 +|---> queue.h +|---> stack.c // 通用栈容器 +|---> stack.h +|---> deque.c // 通用双端队列容器 +|---> deque.h +|---> vector.c // 通用向量(数组)容器 +|---> vector.h +|---> list.c // 通用链表容器 +|---> list.h +|---> heap.c // 通用堆容器 +|---> heap.h +|---> str.c // 字符串类 +|---> str.h +|---> set.c // 通用集合容器 +|---> set.h +|---> map.c // 通用映射容器 +|---> map.h +|---> map_cfg.c // 映射容器的配置文件 +|---> map_cfg.h +|---> tree.c // 通用树容器 +|---> tree.h +'---> tree // 基于通用树实现的各种树结构 +``` + +* algorithm +``` +> source/lib/algorithm +|---> encrypt.c // 加解密算法,DES、3DES +|---> encrypt.h +|---> checkcode.c // 校验算法,求和校验、奇偶校验、异或校验、lrc校验、标准及通用crc校验 +|---> checkcode.h +|---> hash.c // 哈希算法,bkdr、ap、djb、js、rs、sdbm、pjw、elf、dek、bp、fnv、jdk6 +|---> hash.h +|---> sort.c // 通用排序算法(各种数据结构),冒泡排序、选择排序、插入排序、希尔排序、快速排序、堆排序 +'---> sort.h +``` + +* parser +``` +> source/lib/parser +|---> ini.c // ini配置文件解析生成器 +|---> ini.h +|---> json.c // json文件解析生成器 +|---> json.h +|---> xml.c // xml文件解析生成器 +|---> xml.h +|---> txls.c // 文本表格(类markdown表格)文件解析生成器 +'---> txls.h +``` + +* vstd +``` +> source/lib/vstd +|---> vstddef.h // +|---> vmath.c +|---> vmath.h // +|---> vstdlib.c +|---> vstdlib.h // +|---> vstring.c +|---> vstring.h // +|---> vctype.c +'---> vctype.h // +``` + +#### tool +``` +> source/tool +|---> arg.h // 不定参数,获取不定参数个数和指定参数 +|---> valloc.c +|---> valloc.h // 动态内存使用情况测试工具 +|---> tool.c +'---> tool.h // 通用工具 +``` + +#### platform +``` +> source/platform +|---> bsp_io // 模拟IO物理层 +|---> tstamp // 在linux平台获取的时间戳 +|---> platform_types.h // 平台的基本数据类型 +'---> init // 初始化导出模块 +``` + +#### application +``` +> source/application +|---> main.c // 主函数所在文件 +|---> console // 控制台命令输入,与'command'模块搭配,在控制台输入命令进行解析 +'---> test // 各个模块的测试代码 +``` + +#### devices +``` +> source/devices +|---> ecu // 仿真模拟ECU +|---> dtool // 仿真模拟DTOOL +'---> lidar // 仿真模拟LIDAR +``` + +## 使用说明 + +代码在linux环境下编写编译测试,在`built`目录下的`makefile`配置需要编译的文件进行编译即可,也可以直接运行`run.sh`文件编译加运行。varch模块尽可能的保持独立,为了减少对其他模块的依赖,大部分的文件是可以直接单独拎出来就可以直接使用。如果编译存在对其他模块的依赖解决依赖问题,只是数据类型依赖的问题,完全可以参考定义所需类型即可。 + +## 开源协议 + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + +## 联系方式 + +Lamdonn@163.com + diff --git a/doc/varch:calculate运算表达式.md b/doc/varch:calculate运算表达式.md new file mode 100644 index 0000000..40f56a3 --- /dev/null +++ b/doc/varch:calculate运算表达式.md @@ -0,0 +1,91 @@ +## 介绍 + +这个模块就是一个简单的数学运算表达式的解析计算,通过传入简单的运算表达式,计算出结果。 + +## 接口 + +### 表达式运算 +```c +double calculate(const char *expression); +``` +这个函数用法很简单,传入表达式,返回运算结果。默认返回的数据为双精度浮点型,表示的范围会比较广,也适合浮点运算。当计算出错时候,就会返回`NAN`。 +所支持的运算符包括:加法`+`,减法`-`,乘法`*`,除法`/`,次方`^`,取模`%`。运算的优先级有 +| 运算符 | 优先级 | +|:------:|:-----:| +| ^ | 0 | +| * / % | 1 | +| + - | 2 | + +除了,基本的运算符运算之外,还支持内置函数的运算。函数名后面跟着`()`,括号内传入函数参数,多个参数就通过逗号`,`隔开。 +| 函数 | 参数 | 描述 | +|:-----:|:-----:|:------:| +| abs | 1 | 绝对值 | +| sqrt | 1 | 开平方 | +| exp | 1 | 自然指数 | +| ln | 1 | 自然对数 | +| sin | 1 | 正弦 | +| cos | 1 | 余弦 | +| tan | 1 | 正切 | +| cot | 1 | 余切 | +| asin | 1 | 反正弦 | +| acos | 1 | 反余弦 | +| atan | 1 | 反正切 | +| acot | 1 | 反余切 | +| ceil | 1 | 向上取整 | +| floor | 1 | 向下取整 | +| min | 2 | 两者中较小者 | +| max | 2 | 两者中较大者 | +| pow | 2 | 次方函数,与`^`一致 | +| log | 2 | 对数函数 | + +除外,还有常量,也就是`pi`和`e`,不区分大小写。 +符号和数字之间可以为了简洁美观保留空格,**但数字必须紧密结合成一个数字**。 + +简单的例子: +```c +void test(void) +{ + printf("mul %lf\r\n", calculate(" ( 99 * 3 ) ")); + printf("min %lf\r\n", calculate(" min (12, 3)")); + printf("sin %lf\r\n", calculate("sin ( 11 / 2 * pi ) + 100 ")); +} +``` +运算结果: +``` +mul 297.000000 +min 3.000000 +sin 99.000000 +``` + +可以将这个计算模块作为命令导出,以命令形式来计算。这个命令函数,已经将小数点后面没用的`0`去掉,更美观的显示。 +```c +int command_calculate(int argc, char *argv[]) +{ + double r = NAN; + if (argc < 2) return 0; + r = calculate(argv[1]); + if (fabs(floor(r) - r) <= DBL_EPSILON && fabs(r) < 1.0e60) printf("%.0lf\r\n", r); + else if (fabs(r) < 1.0e-6 || fabs(r) > 1.0e9) printf("%e\r\n", r); + else + { + char p[64]; + int len = 0; + len = sprintf(p, "%lf", r); + while (len > 0 && p[len-1] == '0' && p[len-2] != '.') {p[--len] = 0;} + printf("%s\r\n", p); + } + return 1; +} +void test(void) +{ + command_export("cal", command_calculate); +} +``` +输入命令: +``` +cal "sin( 11 / 2 * pi ) + 2 ^ 3" +``` +运算结果: +``` +7 +``` diff --git a/doc/varch:check校验算法.md b/doc/varch:check校验算法.md new file mode 100644 index 0000000..5d6bbe3 --- /dev/null +++ b/doc/varch:check校验算法.md @@ -0,0 +1,108 @@ +## 介绍 + +数据在传输过程中可能会因为各种原因导致产生了差错,为了能够控制传输过程的差错,通信系统往往会采用数据校验来保证数据的完整性。 +常见的数据校验算法就包含,求和校验、奇偶校验、异或校验、LRC校验、CRC校验,这里也给出了常用的校验算法的代码。 + +## 接口 + +```c +uint8_t check_sum(uint8_t* data, uint32_t len); // 求和校验算法 +uint8_t check_parity(uint8_t* data, uint32_t len); // 奇偶校验 +uint8_t check_lrc(uint8_t* data, uint32_t len); // LRC校验 +uint8_t check_xor(uint8_t* data, uint32_t len); // 异或校验 +``` +这几种校验算法使用方法一致,都是传入数据地址和数据长度,返回计算出来的校验值。 + +### 通用crc +```c +// 通用CRC算法 +uint32_t check_crc(uint8_t* data, uint32_t len, uint8_t width, uint32_t poly, uint32_t init, uint8_t refin, uint8_t refout, uint32_t xorout); +``` +这个CRC算法是32byte以内的通用CRC算法,除了传入数据地址和数据长度,后面依次跟着CRC校验码宽度、多项式、初始值、输入翻转、输出翻转、异或输出。 +这个算法和 [CRC(循环冗余校验)在线计算](http://www.ip33.com/crc.html) 的使用方法一致。 +在`check_crc`随机计算个crc,与在线工具对比 +```c +printf("crc 0x%X\r\n", check_crc("Hello", 5, 8, 0x5A, 0xAA, 1, 0, 0x4A)); +``` +结果都是 +``` +crc 0xE0 +``` + +根据常用的crc算法,可以通过`check_crc`宏定义实现 +```c +/* +|-------------------------------------------------------------------------------------------| +|CRC name | width | poly | init | xorout | refin | refout | +|-------------------------------------------------------------------------------------------| +|CRC-4/ITU | 4 | 03 | 00 | 00 | true | true | +|CRC-5/EPC | 5 | 9 | 09 | 00 | false | false | +|CRC-5/ITU | 5 | 5 | 00 | 00 | true | true | +|CRC-5/USB | 5 | 5 | 1F | 1F | true | true | +|CRC-6/ITU | 6 | 3 | 00 | 00 | true | true | +|CRC-7/MMC | 7 | 9 | 00 | 00 | false | false | +|CRC-8 | 8 | 7 | 00 | 00 | false | false | +|CRC-8/ITU | 8 | 7 | 00 | 55 | false | false | +|CRC-8/ROHC | 8 | 7 | FF | 00 | true | true | +|CRC-8/MAXIM | 8 | 1 | 00 | 00 | true | true | +|CRC-16/IBM | 16 | 005 | 0000 | 0000 | true | true | +|CRC-16/MAXIM | 16 | 005 | 0000 | FFFF | true | true | +|CRC-16/USB | 16 | 005 | FFFF | FFFF | true | true | +|CRC-16/MODBUS | 16 | 005 | FFFF | 0000 | true | true | +|CRC-16/CCITT | 16 | 021 | 0000 | 0000 | true | true | +|CRC-16/CCITT-FALSE | 16 | 021 | FFFF | 0000 | false | false | +|CRC-16/X25 | 16 | 021 | FFFF | FFFF | true | true | +|CRC-16/XMODEM | 16 | 021 | 0000 | 0000 | false | false | +|CRC-16/DNP | 16 | D65 | 0000 | FFFF | true | true | +|CRC-32 | 32 | 4C11DB7 | FFFFFFFF | FFFFFFFF | true | true | +|CRC-32/MPEG-2 | 32 | 4C11DB7 | FFFFFFFF | 00000000 | false | false | +|-------------------------------------------------------------------------------------------| + */ +#define check_crc4_itu(data, len) check_crc(data, len, 4, 0x03, 0x00, 1, 1, 0x00) +#define check_crc5_epc(data, len) check_crc(data, len, 5, 0x09, 0x09, 0, 0, 0x00) +#define check_crc5_usb(data, len) check_crc(data, len, 5, 0x05, 0x1F, 1, 1, 0x1F) +#define check_crc6_itu(data, len) check_crc(data, len, 6, 0x03, 0x00, 1, 1, 0x00) +#define check_crc7_mmc(data, len) check_crc(data, len, 7, 0x09, 0x00, 0, 0, 0x00) +#define check_crc8(data, len) check_crc(data, len, 8, 0x07, 0x00, 0, 0, 0x00) +#define check_crc8_itu(data, len) check_crc(data, len, 8, 0x07, 0x00, 0, 0, 0x55) +#define check_crc8_rohc(data, len) check_crc(data, len, 8, 0x07, 0xFF, 1, 1, 0x00) +#define check_crc8_maxim(data, len) check_crc(data, len, 8, 0x31, 0x00, 1, 1, 0x00) +#define check_crc16_ibm(data, len) check_crc(data, len, 16, 0x8005, 0x0000, 1, 1, 0x0000) +#define check_crc16_maxim(data, len) check_crc(data, len, 16, 0x8005, 0x0000, 1, 1, 0xFFFF) +#define check_crc16_usb(data, len) check_crc(data, len, 16, 0x8005, 0xFFFF, 1, 1, 0xFFFF) +#define check_crc16_modbus(data, len) check_crc(data, len, 16, 0x8005, 0xFFFF, 1, 1, 0x0000) +#define check_crc16_ccitt(data, len) check_crc(data, len, 16, 0x1021, 0x0000, 1, 1, 0x0000) +#define check_crc16_ccitt_false(data, len) check_crc(data, len, 16, 0x1021, 0xFFFF, 0, 0, 0x0000) +#define check_crc16_x25(data, len) check_crc(data, len, 16, 0x1021, 0xFFFF, 1, 1, 0xFFFF) +#define check_crc16_xmodem(data, len) check_crc(data, len, 16, 0x1021, 0x0000, 0, 0, 0x0000) +#define check_crc16_dnp(data, len) check_crc(data, len, 16, 0x3D65, 0x0000, 1, 1, 0xFFFF) +#define check_crc32(data, len) check_crc(data, len, 32, 0x04C11DB7, 0xFFFFFFFF, 1, 1, 0xFFFFFFFF) +#define check_crc32_mpeg_2(data, len) check_crc(data, len, 32, 0x04C11DB7, 0xFFFFFFFF, 0, 0, 0x00000000) +``` + +### 标准crc + +如上述通用的crc也能实现标准的crc,但是运算起来效率必然有所下降,下面是根据标准crc进行的专有的实现 +```c +uint8_t crc4_itu(uint8_t* data, uint32_t len); +uint8_t crc5_epc(uint8_t* data, uint32_t len); +uint8_t crc5_itu(uint8_t* data, uint32_t len); +uint8_t crc5_usb(uint8_t* data, uint32_t len); +uint8_t crc6_itu(uint8_t* data, uint32_t len); +uint8_t crc7_mmc(uint8_t* data, uint32_t len); +uint8_t crc8(uint8_t* data, uint32_t len); +uint8_t crc8_itu(uint8_t* data, uint32_t len); +uint8_t crc8_rohc(uint8_t* data, uint32_t len); +uint8_t crc8_maxim(uint8_t* data, uint32_t len); +uint16_t crc16_ibm(uint8_t* data, uint32_t len); +uint16_t crc16_maxim(uint8_t* data, uint32_t len); +uint16_t crc16_usb(uint8_t* data, uint32_t len); +uint16_t crc16_modbus(uint8_t* data, uint32_t len); +uint16_t crc16_ccitt(uint8_t* data, uint32_t len); +uint16_t crc16_ccitt_false(uint8_t* data, uint32_t len); +uint16_t crc16_x25(uint8_t* data, uint32_t len); +uint16_t crc16_xmodem(uint8_t* data, uint32_t len); +uint16_t crc16_dnp(uint8_t* data, uint32_t len); +uint32_t crc32(uint8_t* data, uint32_t len); +uint32_t crc32_mpeg_2(uint8_t* data, uint32_t len); +``` diff --git a/doc/varch:command命令解析.md b/doc/varch:command命令解析.md new file mode 100644 index 0000000..7457be4 --- /dev/null +++ b/doc/varch:command命令解析.md @@ -0,0 +1,112 @@ +## 介绍 + +命令提示符是在操作系统中,提示进行命令输入的一种工作提示符,是一个便利人机交互接口。 +该命令解析模块,暂不涉及接收从控制台传入的命令,而是处理字符串命令。 + +## 接口 + +### 命令导出 +```c +int command_export(const char *name, command_handle_t handle); +``` +在介绍命令解析函数之前,先看一下命令导出函数。 +这个函数就是添加支持的函数,通过名字和相应的回调函数对应起来。`name`函数识别名(一般就是用函数名当作命令解析的识别名),`handle`就是相应的回调处理函数。导出成功了就返回1,失败返回0。 +```c +typedef int (*command_handle_t)(int argc, char *argv[]); +``` +`command_handle_t`函数类型的定义就是和我们常用的`int main(int argc, char *argv[])`是一致的,参数通过`argc`和`argv`传入。 + +### 命令执行 +```c +int command(const char *line); +``` +命令执行函数也很简单,也就是传入命令行就OK,返回相应命令识别的函数的返回值,-1是解析失败的时候的返回值,所以在定义处理函数时候,**返回值不能使用-1**。 +命令的输入格式: +``` +argv[0] argv[1] argv[2] ... +``` +其中`argv[0]`就是命令识别符,后面可以跟若干个参数,个数通过`argc`传给函数。 +命令参数通过空格分开,而当想在参数内传入空格时候,就需要用到转义字符反斜杠`\`进行转义,"\ "表示空格,除此,还支持的**转义字符**包含:双引号`"`和反斜杠`\`。 +为了简化转义字符的使用,可以用**一对双引号**来括住不需要转义解析的命令。 +使用例子: +```c +int func1(int argc, char *argv[]) +{ + printf("I am func1!\r\n"); + printf("argc = %d\r\n", argc); + for (int i = 0; i < argc; i++) + { + printf("argv[%d] = %s\r\n", i, argv[i]); + } + return 1; +} + +int func2(int argc, char *argv[]) +{ + printf("I am func2!\r\n"); + return 1; +} + +int func3(int argc, char *argv[]) +{ + printf("I am func3!\r\n"); + return 1; +} + +static void test(void) +{ + /* 导出命令 */ + command_export("func1", func1); + command_export("func2", func2); + command_export("func3", func3); + + command("cmd -l"); // 模块内置的命令,用于查看当前支持的命令 + printf("--------------------------\r\n"); + command("func2"); + printf("--------------------------\r\n"); + command("func3"); + printf("--------------------------\r\n"); + command("func1 1 2 3"); // 不转义,每个空格分隔的都将成为单独的参数 + printf("--------------------------\r\n"); + command("func1 1\\ 2 3"); // 1后面的空格进行转义,1和2之前将会和空格连在一起 + printf("--------------------------\r\n"); + command("func1 \"1 2 3\""); // 双引号之间 "1 2 3" 会被解析为一个参数 +} +``` +结果: +``` +cmd +func1 +func2 +func3 +count = 4 +-------------------------- +I am func2! +-------------------------- +I am func3! +-------------------------- +I am func1! +argc = 4 +argv[0] = func1 +argv[1] = 1 +argv[2] = 2 +argv[3] = 3 +-------------------------- +I am func1! +argc = 3 +argv[0] = func1 +argv[1] = 1 2 +argv[2] = 3 +-------------------------- +I am func1! +argc = 2 +argv[0] = func1 +argv[1] = 1 2 3 +``` +在其中`cmd`为内置命令,统计当前支持的命令,返回支持的命令个数。 + +### 命令清空 +```c +void command_clear(void); +``` +清空所有导出的命令。 diff --git a/doc/varch:csv解析器.md b/doc/varch:csv解析器.md new file mode 100644 index 0000000..a8ad304 --- /dev/null +++ b/doc/varch:csv解析器.md @@ -0,0 +1,283 @@ +## 介绍 + +### 什么是CSV文件? + +CSV(逗号分隔值)是一种常见的文件格式,用于存储和交换简单的数据表格。CSV文件由文本行组成,每行代表表格中的一行数据,每个数据字段之间使用逗号分隔。 + +### CSV文件的特点 + +- **简单易用**:CSV文件使用纯文本格式,易于创建和编辑。几乎所有的电子表格软件和文本编辑器都支持CSV文件的读写操作。 +- **跨平台兼容**:CSV文件是一种通用的数据交换格式,可以在不同操作系统上进行读取和处理,如Windows、Mac和Linux。 +- **灵活性**:CSV文件可以包含任意数量的行和列,可以存储各种类型的数据,如文本、数字和日期。 +- **可读性**:由于CSV文件采用纯文本格式,因此易于人类阅读和理解,也方便数据分析和处理。 + +### CSV文件的用途 + +CSV文件广泛应用于数据导入、导出和交换的场景,包括: + +- **数据导入和导出**:CSV文件常用于将数据从一个应用程序导出到另一个应用程序,或从数据库导出到电子表格软件,反之亦然。 +- **数据交换**:CSV文件作为一种通用的数据交换格式,常用于不同系统之间的数据交换,如数据集成、数据同步等。 +- **数据备份和存储**:CSV文件可以作为一种简单的数据备份和存储格式,方便将数据以纯文本形式保存,并在需要时进行恢复。 +- **数据分析和处理**:CSV文件可以方便地进行数据分析和处理,可以使用各种数据分析工具(如Excel、Python等)对CSV文件进行操作和计算。 + +### 如何创建和编辑CSV文件? + +创建和编辑CSV文件可以使用文本编辑器、电子表格软件或编程语言来实现。以下是一些常见的方法: + +- **文本编辑器**:可以使用文本编辑器(如Notepad++、Sublime Text等)创建和编辑CSV文件,按照逗号分隔每个数据字段。 +- **电子表格软件**:常见的电子表格软件(如Microsoft Excel、Google Sheets等)提供导入、导出和编辑CSV文件的功能。可以使用电子表格软件创建和编辑CSV文件,并保存为CSV格式。 +- **编程语言**:使用编程语言(如Python、Java等)可以读取、写入和处理CSV文件。许多编程语言提供了专门的CSV库和函数,方便对CSV文件进行操作和处理。 + +### 注意事项 + +在创建和处理CSV文件时,需要注意以下事项: + +- **数据格式**:确保CSV文件中的数据按照正确的格式进行存储,如日期、数字等需要按照约定的格式进行输入。 +- **数据编码**:根据需要选择适当的字符编码,在不同操作系统和应用程序中确保CSV文件的编码一致性。 +- **数据转义**:当数据字段中包含逗号、换行符等特殊字符时,需要进行适当的转义或引用,以确保数据的正确性。 + +### C语言版CSV库 + +varch提供的CSV库,简便易用,能完成大部分对于表格的基础操作,包含对csv的加载和保存,针对行、列、单元格的增删改查。 + +## 接口 + +### 创建和删除csv对象 +```c +csv_t csv_create(unsigned int row, unsigned int col, const void *array); +void csv_delete(csv_t csv); +``` +其中**csv_t**为csv的结构体,创建方法会生成一个指定行列的表格,同时初始化为指定的array。删除方法则删除指定的csv对象。 + +### csv对象加载 +```c +csv_t csv_loads(const char* text); +csv_t csv_file_load(const char* filename); +``` +csv对象可以从字符串文本中加载,也可以从文件中加载。加载成功则会返回一个csv对象,失败则返回NULL。 + +当csv对象加载失败的时候,可以调用`int csv_error_info(int* line, int* column);`函数进行定位错误。 +错误类型包含 +``` +#define CSV_E_OK (0) /* no error */ +#define CSV_E_MEMORY (1) /* memory allocation failed */ +#define CSV_E_OPEN (2) /* fail to open file */ +``` + +### csv对象转储 +```c +char* csv_dumps(csv_t csv, int* len); +int csv_file_dump(csv_t csv, const char* filename); +``` +首先**csv_dumps**方法,将csv对象按格式转储为字符串。*len则是转换出来的字符串长度,传入NULL时候就是不获取长度。返回值则是转换出来的字符串,这个字符串是函数分配的,**在结束使用需要free掉**。 +**csv_file_dump**方法则是在**csv_dumps**的基础上将csv转储到文件当中,filename传入文件名,返回值为转储的长度,负值表示转储失败。 + +### csv获取行、列、单元格计数 +```c +unsigned int csv_row(csv_t csv); +unsigned int csv_col(csv_t csv); +unsigned int csv_cell(csv_t csv); +``` +分别获取csv表格中的行列数以及非空的单元格计数。 + +### csv深拷贝 +```c +csv_t csv_duplicate(csv_t csv); +``` +根据源csv对象深拷贝出一份新的csv对象。 + +### csv转数组 +```c +int csv_to_array(csv_t csv, unsigned int o_row, unsigned int o_col, void *array, unsigned int row_size, unsigned int col_size); +``` +以[o_row, o_col]为起点,将(row_size, col_size)大小的选区内容转到array。 + +### csv压缩 +```c +void csv_minify(csv_t csv); +``` +该方法不会影响csv实际的存储内容,会将行末无效的空单元格去除掉,从而缩小存储空间。 + +### csv设置单元格内容 +```c +int csv_set_text(csv_t csv, unsigned int row, unsigned int col, const char* text); +``` +在(row, col)单元格覆盖性写入text文本,当单元格不存在的时候,也会新建单元格进行写入。 + +### csv获取单元格内容 +```c +const char* csv_get_text(csv_t csv, unsigned int row, unsigned int col); +``` +获取(row, col)单元格内容,返回NULL表示不存在这个单元格。 + +### csv清空单元格内容 +```c +void csv_clean_text(csv_t csv, unsigned int row, unsigned int col); +``` +清空(row, col)单元格内容。 + +### csv插入行列 +```c +int csv_insert_row(csv_t csv, unsigned int pos, const char **array, unsigned int count); +int csv_insert_col(csv_t csv, unsigned int pos, const char **array, unsigned int count); +``` +在pos的位置插入行或列(pos为0时,默认尾插),如果指定array和count,则会把插入的行或列初始为array,count指定初始化的个数。 + +### csv删除行列 +```c +int csv_delete_row(csv_t csv, unsigned int pos); +int csv_delete_col(csv_t csv, unsigned int pos); +``` +删除pos位置的行或列(pos为0时,默认尾删)。 + +### csv移动行列 +```c +int csv_move_row_to(csv_t csv, unsigned int pos, unsigned int dest); +int csv_move_col_to(csv_t csv, unsigned int pos, unsigned int dest); +``` +将pos位置的行或列(pos为0时,默认尾删)移动到dest的位置。 + +### csv复制行列 +```c +int csv_copy_row_to(csv_t csv, unsigned int pos, unsigned int dest); +int csv_copy_col_to(csv_t csv, unsigned int pos, unsigned int dest); +``` +将pos位置的行或列(pos为0时,默认尾删)复制到dest的位置。 + +### csv插入单元格 +```c +int csv_insert_cell(csv_t csv, unsigned int row, unsigned int col, int move_down); +``` +在(row, col)的位置插入空单元格,如果指定move_down为非0后面内容向下移动,否则向右移动。 + +### csv删除单元格 +```c +int csv_delete_cell(csv_t csv, unsigned int row, unsigned int col, int move_up); +``` +删除(row, col)的位置单元格,如果指定move_up为非0后面内容向上移动,否则向左移动。 + +### csv复制单元格 +```c +int csv_copy_cell_to(csv_t csv, unsigned int s_row, unsigned int s_col, unsigned int d_row, unsigned int d_col); +``` +将(s_row, s_col)单元格内容复制到(d_row, d_col)单元格。 + +### csv剪切单元格 +```c +int csv_cut_cell_to(csv_t csv, unsigned int s_row, unsigned int s_col, unsigned int d_row, unsigned int d_col); +``` +将(s_row, s_col)单元格内容剪切到(d_row, d_col)单元格。 + +### csv查找 +```c +int csv_find(csv_t csv, const char* text, int flag, unsigned int* row, unsigned int* col); +``` +在整个表格表格中查找`text`,匹配上单元格后返回1,匹配位置为(row, col)。查找到结束后,返回-1。 +查找规则通过`flag`来控制 +```c +#define CSV_F_FLAG_MatchCase (0x01) /* match case sensitive */ +#define CSV_F_FLAG_MatchEntire (0x02) /* match the entire cell content */ +#define CSV_F_FLAG_MatchByCol (0x04) /* match by column */ +#define CSV_F_FLAG_MatchForward (0x08) /* match from back to front */ +``` + +### csv遍历非空单元格 +```c +#define csv_for_each(csv, row, col, text) +``` +按行从上往下遍历所有非空单元格。 +```c +const char *text = NULL; +csv_for_each(csv, row, col, text) +{ + printf("[%d, %d]: %s\r\n", row, col, text); +} +``` + +## 参考例子 + +### 生成csv文件 +```c +static void dump_demo(void) +{ + csv_t csv; + const char *array[3][5] = { + {"ID", "Name", "Gender", "Age", "Height"}, + {"20240107001", "ZhangSan", "Man", "18", "178"}, + {"20240107002", "LiSi", "Woman", "24", "162"}, + }; + + csv = csv_create(3, 5, array); + if (!csv) + { + printf("create csv fail!\r\n"); + return; + } + + if (csv_file_dump(csv, "info.csv") < 0) + { + printf("csv dump fail!\r\n"); + } + else + { + printf("csv dump success!\r\n"); + } + + csv_delete(csv); +} +``` +转储的文件 **info.csv** +```csv +ID,Name,Gender,Age,Height +20240107001,ZhangSan,Man,18,178 +20240107002,LiSi,Woman,24,162 +``` + +### 加载csv文件 +同样加载csv文件 **info.csv** +```csv +ID,Name,Gender,Age,Height +20240107001,ZhangSan,Man,18,178 +20240107002,LiSi,Woman,24,162 +``` + +```c +static void load_demo(void) +{ + csv_t csv; + + csv = csv_file_load("info.csv"); + if (!csv) + { + printf("csv load fail!\r\n"); + return; + } + + unsigned int row, col; + const char *text = NULL; + csv_for_each(csv, row, col, text) + { + printf("[%u, %u]: %s\r\n", row, col, text); + } + + csv_delete(csv); +} +``` +运行结果: +``` +[1, 1]: ID +[1, 2]: Name +[1, 3]: Gender +[1, 4]: Age +[1, 5]: Height +[2, 1]: 20240107001 +[2, 2]: ZhangSan +[2, 3]: Man +[2, 4]: 18 +[2, 5]: 178 +[3, 1]: 20240107002 +[3, 2]: LiSi +[3, 3]: Woman +[3, 4]: 24 +[3, 5]: 162 +``` diff --git a/doc/varch:deque容器.md b/doc/varch:deque容器.md new file mode 100644 index 0000000..8cbf1e1 --- /dev/null +++ b/doc/varch:deque容器.md @@ -0,0 +1,187 @@ +## 介绍 + +双端队列,是有两个出入口,兼备队列和堆栈的特性,两个端口都可以进出数据。 +varch双端队列、队列、堆栈的实现原理都是一样的,都是连续的地址空间,环形连接起来,高效的在两端出入数据,并且支持随机访问。 +* 容量 +容量也就是在使用过程中最多能存多少个队列项,比如,容量为10的队列,最多能存10个队列项,存满后,再想入队就入不了。varch的队列存储是连续地址的,是有限容量的队列。 +* 访问机制 +一般情况下,队列只有出队和入队两种方式,可以根据连续地址快速的对队列进行遍历访问。 + +## 接口 + +### 创建和删除deque对象 +```c +deque_t deque_create(int dsize, int capacity, void *base); +void deque_delete(deque_t deque); +#define deque(type, capacity) // 为了更简便的使用,对deque_create套一层宏定义 +#define _deque(deque) // 对deque_delete套一层宏定义,并在deque删除后置为空 +``` +其中**deque_t**为deque的结构体,创建方法则会返回一个deque对象,创建失败则返回NULL,其中`dsize`传入数据的大小,`capacity`传入队列容量,`*base`传入缓冲区地址(可以不传入,不传入的话就会自动分配capacity大小的空间以来存储队列数据)。删除方法则是删除传入的deque对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + deque_t deque = deque(int, 10); // 定义并创建一个int型容量为10的deque + _deque(deque); // 成对使用,用完即删除 +} +``` + +### deque的入队和出队 +```c +int deque_push_front(deque_t deque, void* data); +int deque_push_back(deque_t deque, void* data); +int deque_pop_front(deque_t deque, void* data); +int deque_pop_back(deque_t deque, void* data); +``` +这四个方法可以很方便的把数据添加到队列和从队列弹出,`push`方法`data`传入需要入队数据的地址,`pop`方法`data`传入需要接收出队数据的地址,这两个方法`data`都可以传入NULL,那只是一个占位。操作成功返回1,失败返回0。 +```c +void test(void) +{ + deque_t deque = deque(int, 10); + int i = 0; + + for (i = 0; i < deque_capacity(deque); i++) + { + deque_push_back(deque, &i); + } + deque_pop_front(deque, NULL); + deque_pop_back(deque, NULL); + + _deque(deque); // 成对使用,用完即删除 +} +``` + + +### deque的大小、容量和数据大小 +```c +int deque_size(deque_t deque); +int deque_capacity(deque_t deque); +int deque_dsize(deque_t deque); +``` +deque的`capacity`就是创建时候指定的容量,能存多少个队列元素,`size`是队列有多少个元素,`dsize`也就是创建时候传入的数据的大小,比如`int`,`dsize`就是`sizeof(int)`。 +```c +void test(void) +{ + deque_t deque = deque(int, 10); + int i = 0; + + for (i = 0; i < deque_capacity(deque); i++) + { + deque_push_back(deque, &i); + } + deque_pop_front(deque, NULL); + deque_pop_back(deque, NULL); + printf("deque capacity=%d, size=%d, dsize=%d\r\n", deque_capacity(deque), deque_size(deque), deque_dsize(deque)); + + _deque(deque); +} +``` +结果: +``` +deque capacity=10, size=8, dsize=4 +``` + +### deque数据的读写 +```c +void* deque_data(deque_t deque, int index); +#define deque_at(deque, type, i) +``` +`deque_data`方法就是根据索引来获取数据的地址,返回的则是指定的数据的地址,NULL则是失败。而`deque_at`则是在`deque_data`的基础上加多类型。 + +```c +void test(void) +{ + deque_t deque = deque(int, 10); + int i = 0; + + for (i = 0; i < deque_capacity(deque); i++) + { + deque_push_back(deque, &i); + } + deque_pop_front(deque, NULL); + deque_pop_back(deque, NULL); + for (i = 0; i < deque_size(deque); i++) + { + printf("deque[%d] = %d\r\n", i, deque_at(deque, int, i)); + } + + _deque(deque); +} +``` +结果: +``` +deque[0] = 1 +deque[1] = 2 +deque[2] = 3 +deque[3] = 4 +deque[4] = 5 +deque[5] = 6 +deque[6] = 7 +deque[7] = 8 +``` + +### deque数据存储索引 + +```c +int deque_index(deque_t deque, int index); +``` +这个队列存储结构为环形队列,也就是在连续地址的存储空间首尾相接形成环形,队列数据进出就在这个环形中进行。如此,队列的索引并不直接是缓冲区的索引,`deque_index`方法就是将队列的索引对照为缓冲区的索引,失败返回-1。 +一般情况下,这个方法应用不多,也是在`deque_create`方法是传入了`base`,在`base`地址上来使用`deque_index`获取队列数据。 + +### deque空队和满队 +```c +int deque_empty(deque_t deque); +int deque_full(deque_t deque); +``` +这两个方法实际就是deque的`size`的大小关系,等于0为空,等于容量则满。 + +## 源码解析 + +### deque结构体 + +deque容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致deque存储结构的破坏。所以deque解析器只留了唯一一个deque的声明在头文件,然后结构体的定义都在源文件。只能使用deque容器提供的方法对deque对象进行操作。 +deque类型声明 +```c +typedef struct DEQUE *deque_t; +``` +使用时候,只是用`deque_t`即可。 + +```c +typedef struct DEQUE +{ + void* base; /* base address of data */ + int cst; /* base const */ + int dsize; /* size of deque data */ + int capacity; /* capacity of deque */ + int size; /* size of deque */ + int head; /* index of deque head */ + int tail; /* index of deque tail */ +} DEQUE; +``` +`QUEUE`结构体中包含了7个成员,`base`(队列结构数据缓冲区的基地址),`cst`(指示base的空间是否是create方法时候传进来),`size`(deque的大小,也就是deque的长度),`dsize`(每个数据的大小),`capacity`(队列的容量),`head`和`tail`分别是环形缓冲区,队头和队尾所指向的索引。 + +deque容器最主要的问题就是解决环形队列,数据先进先出的问题,其他创建删除是完成空间的初始化等基本初始化操作,`deque_push_back`和`deque_pop_front`其实与queue是一致的,这里不在赘述,主要说明下`deque_push_front`和`deque_pop_back`。 + +```c +int deque_push_front(deque_t deque, void* data) +{ + if (!deque) return 0; + if (deque_full(deque)) return 0; // 在入队之前先判断一下队列是否满了 + // 这里就不是直接head自减就行了,考虑到head为0,自减就是-1了,超出capacity范围了 + // 而我们要的是head为0,push head后,head应该回到缓冲区末端了,保证这个环形的连续 + // 所以这里的做法是,让head+capacity后再来减1,最后对capacity求余保证环形 + deque->head = (deque->head + deque->capacity - 1) % deque->capacity; + if (data) memcpy(at(deque->head), data, deque->dsize); + deque->size++; + return 1; +} +int deque_pop_back(deque_t deque, void* data) +{ + if (!deque) return 0; + if (deque_empty(deque)) return 0; // 在出队之前先判断一下队列是否为空 + // 这里的tail的操作就如同deque_push_front方法的head操作了 + deque->tail = (deque->tail + deque->capacity - 1) % deque->capacity; + if (data) memcpy(data, at(deque->tail), deque->dsize); + deque->size--; + return 1; +} +``` \ No newline at end of file diff --git a/doc/varch:dict容器(哈希表).md b/doc/varch:dict容器(哈希表).md new file mode 100644 index 0000000..e1e8f46 --- /dev/null +++ b/doc/varch:dict容器(哈希表).md @@ -0,0 +1,369 @@ +## 介绍 + +dict字典是逻辑上离散的容器,与set容器很相似,set容器是以`index - data`形式存在,而dict容器是以`key - value`形式存在,set的index是整型数,dict的key为字符串(这里和python的dict不太一样,python的key除了可以字符串还可以整型或者元组等,varch类似python的dict叫做map映射),set的data和dict的value本质是同一个东西。 +varch的dict容器,在底层实现上采用了**哈希表**,查找迅速,而且也支持随机访问,占用空间相对较小。可以通过迭代器遍历dict。 + +## 接口 + +### 创建和删除dict对象 +```c +dict_t dict_create(int dsize); +void dict_delete(dict_t dict); +#define dict(type) // 为了更简便的使用,对dict_create套一层宏定义 +#define _dict(dict) // 对dict_delete套一层宏定义,并在dict删除后置为空 +``` +其中**dict_t**为dict的结构体,创建方法则会返回一个空的dict对象,创建失败则返回NULL,其中`dsize`传入数据的大小。删除方法则是删除传入的dict对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + dict_t dict = dict(int); // 定义并创建一个int型的dict + _dict(dict); // 成对使用,用完即删除 +} +``` + +### dict的插入和移除 +```c +void* dict_insert(dict_t dict, const char *key, void *value); +int dict_erase(dict_t dict, const char *key); +``` +该dict通过hash值来定位的,可以很快的通过hash值定位到数据存储的位置。 +插入的方法是添加指定key并将数据复制到这个键(在其中,value传入NULL时则只是开辟空间,不进行赋值),在插入key的过程中会进行查重,保证key的唯一性,插入成功后返回插入后的数据的地址,插入失败则是返回NULL。而移除则是移除指定键的数据,成功返回1,失败返回0。 + +### dict数据的读写 +```c +void* dict_value(dict_t dict, const char *key); +void* dict_error(dict_t dict); +#define dict_at(dict, type, key) +``` +`dict_value`方法就是根据键来获取数据的地址,返回的则是指定的数据的地址,`dict_error()`则是失败。而`dict_at`则是在`dict_value`的基础上加多类型,`dict_value`具备读写保护机制,因为返回的是`dict_error()`而不是NULL,所以在使用`dict_at`方法`i`写错了就会修改`dict_error()`指向的内容,而不会导致奔溃。 +dict的随机访问是通过计算出键的哈希值,根据哈希值定位在哈希表中的数据。 + +```c +void test(void) +{ + dict_t dict = dict(int); + int value; + + value = 100; dict_insert(dict, "hello", &value); + value = 1; dict_insert(dict, "ZhangSan", &value); + value = 2; dict_insert(dict, "LiSi", &value); + value = 3; dict_insert(dict, "WangWu", &value); + value = 4; dict_insert(dict, "SunLiu", &value); + value = 5; dict_insert(dict, "QianQi", &value); + + printf("dict[hello] = %d\r\n", dict_at(dict, int, "hello")); + printf("dict[SunLiu] = %d\r\n", dict_at(dict, int, "SunLiu")); + + _dict(dict); +} +``` +结果: +``` +dict[hello] = 100 +dict[SunLiu] = 4 +``` + +### dict的大小和和数据大小 +```c +int dict_size(dict_t dict); +int dict_dsize(dict_t dict); +``` +dict的`size`很好理解,也就是像数组那样的大小,`dsize`也就是创建时候传入的数据的大小。 +```c +void test(void) +{ + dict_t dict = dict(int); + int value; + + value = 100; dict_insert(dict, "hello", &value); + value = 1; dict_insert(dict, "ZhangSan", &value); + value = 2; dict_insert(dict, "LiSi", &value); + value = 3; dict_insert(dict, "WangWu", &value); + value = 4; dict_insert(dict, "SunLiu", &value); + value = 5; dict_insert(dict, "QianQi", &value); + + printf("size = %d, value size = %d\r\n", dict_size(dict), dict_dsize(dict)); + + _dict(dict); +} +``` +结果: +``` +size = 6, value size = 4 +``` + +### dict查找 +```c +int dict_find(dict_t dict, const char *key); +``` +这个方法其实套`dict_value`实现,只是find成功返回1失败返回0。 + +### dict迭代器 + +```c +void dict_it_init(dict_t dict); +void* dict_it_get(dict_t dict, char **key); +``` + +dict也支持内置的迭代器,但主要dict的迭代器用于遍历。因为数组要遍历的时候是知道键从0开始逐一递增遍历的。但是dict是离散型的key,无法通过这种逐一递增的方式进行遍历,所以这里给定了两个迭代器函数用于遍历dict。 +`dict_it_init`初始化迭代器。`dict_it_get`获取迭代,更新迭代位置,`*key`为输出的key(当前所在的key,也可以传入NULL不接收),返回迭代位置的数据。 +通过`dict_size`来把控迭代次数。 + +```c +void test(void) +{ + dict_t dict = dict(int); + int value; + char *key; + void *data; + int i; + + value = 100; dict_insert(dict, "hello", &value); + value = 1; dict_insert(dict, "ZhangSan", &value); + value = 2; dict_insert(dict, "LiSi", &value); + value = 3; dict_insert(dict, "WangWu", &value); + value = 4; dict_insert(dict, "SunLiu", &value); + value = 5; dict_insert(dict, "QianQi", &value); + + dict_it_init(dict, DICT_HEAD); + i = dict_size(dict); + while (i--) + { + data = dict_it_get(dict, &key); + printf("dict[%s] = %d\r\n", key, *(int *)data); + } + + _dict(dict); +} +``` + +结果: +``` +dict[LiSi] = 2 +dict[QianQi] = 5 +dict[SunLiu] = 4 +dict[WangWu] = 3 +dict[ZhangSan] = 1 +dict[hello] = 100 +``` + +## 源码解析 + +### dict结构体 +dict容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致dict存储结构的破坏。所以dict解析器只留了唯一一个dict的声明在头文件,然后结构体的定义都在源文件。只能使用dict容器提供的方法对dict对象进行操作。 +dict类型声明 +```c +typedef struct DICT *dict_t; +``` +使用时候,只是用`dict_t`即可。 +```c +/* dict type define */ +typedef struct DICT +{ + groove_t *base; /* base address for groove data */ + void *error; /* error space */ + int vsize; /* size of value */ + unsigned int size; /* size of dict */ + unsigned int capacity; /* capacity of dict */ + unsigned int it; /* iterator index */ +} DICT; +``` +`DICT`结构体中包含了6个成员,`base`(哈希表的基地址),`error`(dict的错误区,当随机访问到不存在的key时候就会返回这个错误的地址,如此在使用`at`方法时候不会操作到无效的内存地址),`size`(dict的大小,也就是dict的长度),`vsize`(每个数据的大小),`capacity`(哈希表的容量),`it`(迭代器遍历的时候,记录当前访问的哈希表索引)。 +dict容器最主要的问题就是解决哈希冲突以及哈希表容量调整的问题。 +```c +/* dict node type define */ +typedef struct +{ + unsigned int hash; /* hash value */ + char *key; /* key */ + void *value; /* value */ +} GROOVE, *groove_t; +``` +在dict里面,数据通过数组进存储,每个数组项只是存储每个槽(groove)的地址,每个槽里面就存储3部分的内容,当前这个槽在当前的hash表中的hash值(为了更快的查找hash冲突的键值对),以及实际存储的键值对。 +``` ++------+------------------------+------------------------+ +| hash | key | value | ++------+------------------------+------------------------+ +| ... | ... | ... | ++------+------------------------+------------------------+ +| ... | ... | ... | ++------+------------------------+------------------------+ +``` + +### dict创建及删除 + +```c +dict_t dict_create(int vsize) +{ + dict_t dict; + if (vsize <= 0) return NULL; + dict = (dict_t)malloc(sizeof(DICT)); + if (!dict) return NULL; + dict->error = malloc(vsize); + if (!dict->error) { free(dict); return NULL; } + dict->base = NULL; + dict->vsize = vsize; + dict->size = 0; + dict->capacity = 0; + dict->it = 0; + return dict; +} +``` +dict的创建,只是创建了一个空的dict,对基本参数进行了初始化。而删除方法就是释放在对dict操作的过程中分配的内存空间。 + +### dict的插入 +```c +void* dict_insert(dict_t dict, const char *key, void *value) +{ + groove_t groove = NULL; + unsigned int hash = 0, index; + int len = 0; + + /* + 对传入的形参有效性的检查 + */ + if (!dict) return NULL; + if (!key) return NULL; + + /* + 检查大小有没有超过哈希表容量的 3/4 了,超过 3/4 就对哈希表进行扩容 + 因为超过 3/4 说明哈希表已经很满了,查找的效率会大大降低,所以就要保持哈希表一些空闲的空间 + 这里扩容,是呈2的指数依次变化的,也就是 4、8、16、32 ... 这样子增长 + */ + /* the current capacity affects the search rate and needs to be expanded */ + if (dict->size >= ((dict->capacity >> 2) + (dict->capacity >> 1))) /* size exceeds 3/4 of capacity */ + { + /* + 在扩容的过程中,先是重新分配一块新容量的空间 + 然后把旧的哈希表里面的成员,一个个重新哈希插回到新的哈希表中 + */ + /* allocate new hash table space */ + if (!dict_resize(dict, dict->capacity < MIN_CAPACITY ? MIN_CAPACITY : dict->capacity << 1)) return NULL; + } + + /* + 计算插进来的key的哈希值(默认使用了bkdr哈希算法),让hash值对哈希表容量进行取模 + 然后在检查这个hash值有没有冲突了,冲突了的话,通过开放定址法,线性+1向后寻找空闲空间 + 在向后寻找的过程中,检查相应槽中的hash值,hash值为-1表示被erase方法移除了而实际槽没有被删除的标识,也是视为这个槽可以用 + */ + /* find a free groove */ + len = strlen(key); + hash = hash_bkdr((void *)key, len) % dict->capacity; + index = hash; + while (dict->base[index] && dict->base[index]->hash != -1) + { + index = (index + 1) % dict->capacity; + if (index == hash) return NULL; + } + + /* + 分配槽的空间 + */ + /* space allocation */ + groove = dict->base[index]; + if (!groove) groove = (groove_t)malloc(sizeof(GROOVE)); + if (!groove) return NULL; + groove->key = (char *)malloc(len + 1); + if (!groove->key) { if (!dict->base[index]) free(groove); return NULL; } + groove->value = malloc(dict->vsize); + if (!groove->value) { free(groove->key), groove->key = NULL; if (!dict->base[index]) free(groove); return NULL; } + + /* assign */ + groove->hash = hash; // 记录当前槽的哈希值 + strcpy(groove->key, key); + if (value) memcpy(groove->value, value, dict->vsize); + + /* insert */ + dict->base[index] = groove; + dict->size++; + + return groove->value; +} +``` +实际存储例子: +```c +value = 100; dict_insert(dict, "hello", &value); +value = 1; dict_insert(dict, "ZhangSan", &value); +value = 2; dict_insert(dict, "LiSi", &value); +value = 3; dict_insert(dict, "WangWu", &value); +value = 4; dict_insert(dict, "SunLiu", &value); +value = 5; dict_insert(dict, "QianQi", &value); +value = 8; dict_insert(dict, "WangBa", &value); +value = 9; dict_insert(dict, "LiuJiu", &value); +``` +哈希表 +``` ++------+------------------------+------------------------+ +| hash | key | value | ++------+------------------------+------------------------+ +| 0 | ZhangSan | 1 | ++------+------------------------+------------------------+ +| 1 | QianQi | 5 | ++------+------------------------+------------------------+ +| | | | ++------+------------------------+------------------------+ +| | | | ++------+------------------------+------------------------+ +| 4 | SunLiu | 4 | ++------+------------------------+------------------------+ +| | | | ++------+------------------------+------------------------+ +| 6 | WangBa | 8 | ++------+------------------------+------------------------+ +| 7 | LiSi | 2 | ++------+------------------------+------------------------+ +| | | | ++------+------------------------+------------------------+ +| 9 | WangWu | 3 | ++------+------------------------+------------------------+ +| | | | ++------+------------------------+------------------------+ +| | | | ++------+------------------------+------------------------+ +| | | | ++------+------------------------+------------------------+ +| | | | ++------+------------------------+------------------------+ +| 14 | hello | 100 | ++------+------------------------+------------------------+ +| 14 | LiuJiu | 9 | ++------+------------------------+------------------------+ +``` + + +### dict的移除 +```c +int dict_erase(dict_t dict, const char *key) +{ + groove_t groove; + unsigned int index, next; + + if (!dict) return 0; + if (!key) return 0; + + /* + 要移除就得先找到这个key所在哈希表的位置 + find_index就是根据类似前面的插入的方法同逻辑,没找到就返回-1 + */ + index = find_index(dict, key); + if (index == -1) return 0; + groove = dict->base[index]; + + if (groove->key) { free(groove->key); groove->key = NULL; } // 把key释放并置为NULL + if (groove->value) { free(groove->value); groove->value = NULL; } // 把value释放并置为NULL + groove->hash = -1; // 槽就不真正释放,而是把hash值标志为-1(-1哈希值在哈希表中用不上) + dict->size--; + + /* + 如同插入方法,插入要适配大小容量,移除也是 + 当大小小于容量 1/4 时候,就调整容量,缩小到一般,这样就占用 1/2 了 + */ + if (dict->capacity > MIN_CAPACITY && dict->size <= (dict->capacity >> 2)) /* size less than 1/4 of capacity */ + { + dict_resize(dict, dict->capacity >> 1); + } + + return 1; +} +``` + diff --git a/doc/varch:dict容器(红黑树).md b/doc/varch:dict容器(红黑树).md new file mode 100644 index 0000000..aac7513 --- /dev/null +++ b/doc/varch:dict容器(红黑树).md @@ -0,0 +1,155 @@ +## 介绍 + +dict字典是逻辑上离散的容器,与set容器很相似,set容器是以`index - data`形式存在,而dict容器是以`key - value`形式存在,set的index是整型数,dict的key为字符串(这里和python的dict不太一样,python的key除了可以字符串还可以整型或者元组等,varch类似python的dict叫做map映射),set的data和dict的value本质是同一个东西。 +varch的dict容器,也是具备的集合的基本属性,在底层实现上采用了**红黑树**,增删操作的效率高,而且也支持随机访问。同样作为链式结构,dict集合也支持迭代器。 + +## 接口 + +### 创建和删除dict对象 +```c +dict_t dict_create(int dsize); +void dict_delete(dict_t dict); +#define dict(type) // 为了更简便的使用,对dict_create套一层宏定义 +#define _dict(dict) // 对dict_delete套一层宏定义,并在dict删除后置为空 +``` +其中**dict_t**为dict的结构体,创建方法则会返回一个空的dict对象,创建失败则返回NULL,其中`dsize`传入数据的大小。删除方法则是删除传入的dict对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + dict_t dict = dict(int); // 定义并创建一个int型的dict + _dict(dict); // 成对使用,用完即删除 +} +``` + +### dict的插入和移除 +```c +void* dict_insert(dict_t dict, const char *key, void *value); +int dict_erase(dict_t dict, const char *key); +``` +dict有着较好插入和移除的效率,不用数据移位只需修改链表指向。 +dict不是通过hash值来定位的,而是从底层就对key进行比较查找,所以不存在hash冲突的问题 +插入的方法是添加指定key并将数据复制到这个键(在其中,value传入NULL时则只是开辟空间,不进行赋值),在插入key的过程中会进行查重,保证key的唯一性,插入成功后返回插入后的数据的地址,插入失败则是返回NULL。而移除则是移除指定键的数据,成功返回1,失败返回0。 + +### dict数据的读写 +```c +void* dict_value(dict_t dict, const char *key); +void* dict_error(dict_t dict); +#define dict_at(dict, type, key) +``` +`dict_value`方法就是根据键来获取数据的地址,返回的则是指定的数据的地址,`dict_error()`则是失败。而`dict_at`则是在`dict_value`的基础上加多类型,`dict_value`具备读写保护机制,因为返回的是`dict_error()`而不是NULL,所以在使用`dict_at`方法`i`写错了就会修改`dict_error()`指向的内容,而不会导致奔溃。 +dict的随机访问和连续地址的数组或者非连续地址的list都不太一样,数组是可以直接定位到指定键的地址,而链表要链式一步步指向进行随机访问。dict采用了**红黑树**,二分查找就可以很快的访问到指定键。 + +```c +void test(void) +{ + dict_t dict = dict(int); + int value; + + value = 100; dict_insert(dict, "hello", &value); + value = 1; dict_insert(dict, "ZhangSan", &value); + value = 2; dict_insert(dict, "LiSi", &value); + value = 3; dict_insert(dict, "WangWu", &value); + value = 4; dict_insert(dict, "SunLiu", &value); + value = 5; dict_insert(dict, "QianQi", &value); + + printf("dict[hello] = %d\r\n", dict_at(dict, int, "hello")); + printf("dict[SunLiu] = %d\r\n", dict_at(dict, int, "SunLiu")); + + _dict(dict); +} +``` +结果: +``` +dict[hello] = 100 +dict[SunLiu] = 4 +``` + +### dict的大小和和数据大小 +```c +int dict_size(dict_t dict); +int dict_dsize(dict_t dict); +``` +dict的`size`很好理解,也就是像数组那样的大小,`dsize`也就是创建时候传入的数据的大小。 +```c +void test(void) +{ + dict_t dict = dict(int); + int value; + + value = 100; dict_insert(dict, "hello", &value); + value = 1; dict_insert(dict, "ZhangSan", &value); + value = 2; dict_insert(dict, "LiSi", &value); + value = 3; dict_insert(dict, "WangWu", &value); + value = 4; dict_insert(dict, "SunLiu", &value); + value = 5; dict_insert(dict, "QianQi", &value); + + printf("size = %d, value size = %d\r\n", dict_size(dict), dict_dsize(dict)); + + _dict(dict); +} +``` +结果: +``` +size = 6, value size = 4 +``` + +### dict查找 +```c +int dict_find(dict_t dict, const char *key); +``` +这个方法其实套`dict_value`实现,只是find成功返回1失败返回0。 + +### dict迭代器 + +```c +void dict_it_init(dict_t dict, int orgin); +void* dict_it_get(dict_t dict, char **key); +``` + +dict也支持内置的迭代器,但主要dict的迭代器用于遍历。因为向list要遍历的时候是知道键从0开始逐一递增遍历的。但是dict是离散型的key,无法通过这种逐一递增的方式进行遍历,所以这里给定了两个迭代器函数用于遍历dict。 +`dict_it_init`初始化迭代器,`orgin`指定为`SET_HEAD`或者`SET_TAIL`,就分别是正向迭代和反向迭代。 +`dict_it_get`获取迭代,更新迭代位置,`*key`为输出的key(当前所在的key,也可以传入NULL不接收),返回迭代位置的数据。 +通过`dict_size`来把控迭代次数。 + +```c +void test(void) +{ + dict_t dict = dict(int); + int value; + char *key; + void *data; + int i; + + value = 100; dict_insert(dict, "hello", &value); + value = 1; dict_insert(dict, "ZhangSan", &value); + value = 2; dict_insert(dict, "LiSi", &value); + value = 3; dict_insert(dict, "WangWu", &value); + value = 4; dict_insert(dict, "SunLiu", &value); + value = 5; dict_insert(dict, "QianQi", &value); + + dict_it_init(dict, DICT_HEAD); + i = dict_size(dict); + while (i--) + { + data = dict_it_get(dict, &key); + printf("dict[%s] = %d\r\n", key, *(int *)data); + } + + _dict(dict); +} +``` + +结果: +``` +dict[LiSi] = 2 +dict[QianQi] = 5 +dict[SunLiu] = 4 +dict[WangWu] = 3 +dict[ZhangSan] = 1 +dict[hello] = 100 +``` + +## 源码解析 + +dict与set容器保持一致。 + diff --git a/doc/varch:encrypt加解密算法.md b/doc/varch:encrypt加解密算法.md new file mode 100644 index 0000000..2d5a671 --- /dev/null +++ b/doc/varch:encrypt加解密算法.md @@ -0,0 +1,140 @@ +## 介绍 + +在数据传输当中有些敏感的数据需要进行加密来保证安全,比如:用户名密码。 +加解密算法一般分为:**对称加密算法** 和 **非对称加密算法**。 +这个模块提供了一些基本的加解密算法: +- [x] DES 加解密 +- [x] DES3 加解密 +- [ ] AES 加解密 +- [ ] SHA1 加密 +- [ ] MD5 加密 +- [ ] HMAC 计算 +- [ ] blowfish 加解密 +- [ ] RSA 加解密、公钥、签名、转换 + +## 接口 + +### DES +```c +int des_set_key(const uint8_t key[8]); +int des_crypt_ecb(const uint8_t input[8], uint8_t output[8], uint8_t mode); +int des_crypt_cbc(const uint8_t *input, uint8_t *output, uint32_t length, uint8_t mode); +``` +DES算法常见ECB和CBC模式,ECB模式下每一个加密块独立进行运算,而CBC模式是会依赖前一个加密块进行运算。 +在进行加解密之前,需要先设置密钥`des_set_key`,密钥是默认8个字节。 +`des_crypt_ecb`加解密算法都是用这个函数,通过`mode`参数设置为加密还是解密。 +`des_crypt_cbc`与`des_crypt_ecb`不同,`input`和`output`长度需一样而是8的倍数,通过`length`传入。 + +例子: +```c +void test_des(void) +{ + uint8_t key[8] = "hello"; + uint8_t data_block[8] = {1, 2, 3, 4, 5, 6, 7, 9}; + uint8_t processed_block[8]; + int i; + + des_set_key(key); + + printf("des ecb encrypt: "); + des_crypt_ecb(data_block, processed_block, DES_ENCRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", processed_block[i]); + } + printf("\r\n"); + + printf("des ecb decrypt: "); + des_crypt_ecb(processed_block, data_block, DES_DECRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", data_block[i]); + } + printf("\r\n"); + + /////////////////////////////////////// + printf("des cbc encrypt: "); + des_crypt_cbc(data_block, processed_block, 8, DES_ENCRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", processed_block[i]); + } + printf("\r\n"); + + printf("des cbc decrypt: "); + des_crypt_cbc(processed_block, data_block, 8, DES_DECRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", data_block[i]); + } + printf("\r\n"); +} +``` +结果: +``` +des ecb encrypt: 156 151 171 0 235 148 83 44 +des ecb decrypt: 1 2 3 4 5 6 7 9 +des cbc encrypt: 156 151 171 0 235 148 83 44 +des cbc decrypt: 1 2 3 4 5 6 7 9 +``` + +### DES3 +```c +int des3_set_key2(const uint8_t key[16]); +int des3_set_key3(const uint8_t key[24]); +int des3_crypt_ecb(const uint8_t input[8], uint8_t output[8], uint8_t mode); +int des3_crypt_cbc(const uint8_t *input, uint8_t *output, uint32_t length, uint8_t mode); +``` +在用法上`DES3`和`DES`是一致的,是对des三次加密,密钥的长度也相应增长,可以设置为16字节和24字节的密钥。其他`ECB`和`CBC`的加解密模式用法和`DES`是一样的。 +例子: +```c +void test_des3(void) +{ + uint8_t key[24] = "hello world"; + uint8_t data_block[8] = {1, 2, 3, 4, 5, 6, 7, 9}; + uint8_t processed_block[8]; + int i; + + des3_set_key2(key); + + printf("des3 ecb encrypt: "); + des3_crypt_ecb(data_block, processed_block, DES_ENCRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", processed_block[i]); + } + printf("\r\n"); + + printf("des3 ecb decrypt: "); + des3_crypt_ecb(processed_block, data_block, DES_DECRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", data_block[i]); + } + printf("\r\n"); + + /////////////////////////////////////// + printf("des3 cbc encrypt: "); + des3_crypt_cbc(data_block, processed_block, 8, DES_ENCRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", processed_block[i]); + } + printf("\r\n"); + + printf("des3 cbc decrypt: "); + des3_crypt_cbc(processed_block, data_block, 8, DES_DECRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", data_block[i]); + } + printf("\r\n"); +} +``` +结果: +``` +des3 ecb encrypt: 48 251 201 178 251 3 6 54 +des3 ecb decrypt: 1 2 3 4 5 6 7 9 +des3 cbc encrypt: 48 251 201 178 251 3 6 54 +des3 cbc decrypt: 1 2 3 4 5 6 7 9 +``` \ No newline at end of file diff --git a/doc/varch:hash算法.md b/doc/varch:hash算法.md new file mode 100644 index 0000000..188ef5d --- /dev/null +++ b/doc/varch:hash算法.md @@ -0,0 +1,58 @@ +## 介绍 + +Hash 算法能将将任意长度的数据离散映射到较短指定长度的数据的算法,并且一般不可逆。 + +## 接口 + +```c +uint32_t hash_bkdr(void *data, uint32_t size); +uint32_t hash_ap(void *data, uint32_t size); +uint32_t hash_djb(void *data, uint32_t size); +uint32_t hash_js(void *data, uint32_t size); +uint32_t hash_rs(void *data, uint32_t size); +uint32_t hash_sdbm(void *data, uint32_t size); +uint32_t hash_pjw(void *data, uint32_t size); +uint32_t hash_elf(void *data, uint32_t size); +uint32_t hash_dek(void *data, uint32_t size); +uint32_t hash_bp(void *data, uint32_t size); +uint32_t hash_fnv(void *data, uint32_t size); +uint32_t hash_jdk6(void *data, uint32_t size); +``` +这几种校验算法使用方法一致,都是传入数据地址和数据大小,返回计算出来的32bits哈希值。 + +## 测试 + +```c +static void test(void) +{ + printf("hash_bkdr 0x%X\r\n", hash_bkdr("Hello", 5)); + printf("hash_ap 0x%X\r\n", hash_ap("Hello", 5)); + printf("hash_djb 0x%X\r\n", hash_djb("Hello", 5)); + printf("hash_js 0x%X\r\n", hash_js("Hello", 5)); + printf("hash_rs 0x%X\r\n", hash_rs("Hello", 5)); + printf("hash_sdbm 0x%X\r\n", hash_sdbm("Hello", 5)); + printf("hash_pjw 0x%X\r\n", hash_pjw("Hello", 5)); + printf("hash_elf 0x%X\r\n", hash_elf("Hello", 5)); + printf("hash_dek 0x%X\r\n", hash_dek("Hello", 5)); + printf("hash_bp 0x%X\r\n", hash_bp("Hello", 5)); + printf("hash_fnv 0x%X\r\n", hash_fnv("Hello", 5)); + printf("hash_jdk6 0x%X\r\n", hash_jdk6("Hello", 5)); +} +``` + +结果 + +``` +hash_bkdr 0x7D80646E +hash_ap 0x646C7322 +hash_djb 0xD4F2079 +hash_js 0x6060CD85 +hash_rs 0x15794872 +hash_sdbm 0x2B45B912 +hash_pjw 0x4EC32F +hash_elf 0x4EC32F +hash_dek 0xEB33DEF +hash_bp 0xCBB366F +hash_fnv 0x2EA7AFEE +hash_jdk6 0x42628B2 +``` \ No newline at end of file diff --git a/doc/varch:ini解析器.md b/doc/varch:ini解析器.md new file mode 100644 index 0000000..a4b69d0 --- /dev/null +++ b/doc/varch:ini解析器.md @@ -0,0 +1,649 @@ +## 介绍 + +ini是常用的工程配置文件,语法简单。varch提供的ini解析器给定了简单可靠的api,可以很轻松的加载和生成ini文件。 + +简单介绍下ini规范,ini的组成部分包含了以下的部分。 + +**注释** +``` +ini支持注释,注释独占一行 +注释以'#'或';'为标识 +注释标识在行首,前面允许有空格,但不允许为其他字符 +``` +例子: +``` +########################### +# 这是注释 +########################### + +aaa ; 这后面的不算注释 +``` + +**节(section)** +``` +每一个section独占一行 +section名称用"[]"方括号括起来,以最外围方括号包含的内容作为section的名称,其中方括号内也可以包含方括号在内的任意字符 +section名对大小写敏感 +section不能重复 +``` +例子: +``` +[section] +``` + +**键(key)** +``` +键与值组成键值对 +键值对以':'号或者'='号分隔,分割符两端的允许有空格不参与解析(但建议不留空格) +键对大小写不敏感 +键在同一个section内不允许重复,但是可以在不同的section内重复 +``` + +**值(value)** +``` +在键值分隔符后面的内容即为值 +值允许有任意字符(包含'='和':'分割符等字符纳入值内) +值可以跨行,前提是换行的缩进得大于值的缩进,才纳入换行值得解析 +``` +例子: +``` +# 下面这个例子是允许的 +value1 = =aaaa;#[] + +# 值允许分行,此[]括起来的将不被解析为section +value2 = 1 + 2 + 3 + 4 + [sss] +``` +windows系统下的**system.ini**文件 +```ini +; for 16-bit app support +[386Enh] +woafont=dosapp.fon +EGA80WOA.FON=EGA80WOA.FON +EGA40WOA.FON=EGA40WOA.FON +CGA80WOA.FON=CGA80WOA.FON +CGA40WOA.FON=CGA40WOA.FON + +[drivers] +wave=mmdrv.dll +timer=timer.drv + +[mci] + +``` + +## 接口 + +### 创建和删除ini对象 +```c +ini_t ini_create(void); +void ini_delete(ini_t ini); +``` +其中**ini_t**为ini的结构体,创建方法则会返回一个空的ini对象,删除方法则是删除传入的ini对象。 + +### 添加和移除section +```c +int ini_add_section(ini_t ini, const char* section); +int ini_remove_section(ini_t ini, const char* section); +``` +首先是添加section方法,会在ini对象后面添加指定名称的section,添加成功返回1,失败返回0,比如名字重复就会导致添加失败。 +然后是移除section方法,同样也是传入指定的section名,移除成功返回1,失败返回0,移除section会将该section下的所有键值对也会被移除。 + +### 获取section +```c +int ini_section_index(ini_t ini, const char* section); +const char* ini_section_name(ini_t ini, int index); +``` +这两个获取section的方法刚好是对称的方法,第一个是通过section名获取section所在索引(索引就是从ini文件从头到尾的section排序,索引从0开始),当找不到指定section则返回负数-1,而第二个则相反是通过索引来获取section名,当不存在这个索引的section时候则返回NULL。 +varch的ini解析器底层对于section的存储采取了内置迭代器的单向链表,在每次调用的时候都会记录下当前的位置,然后在下一次调用时候就会先判断是否和前一次的位置一样,所以对于与前一次相同的调用时间复杂度为**O(1)**,以及采用**ini_section_name**方法遍历时候,时间复杂度为**O(n)**。 + +### 设置和获取value +```c +int ini_set_value(ini_t ini, const char* section, const char* key, const char* value); +const char* ini_get_value(ini_t ini, const char* section, const char* key); +``` +设置value方法,就是将ini指定的section对应的key的value设为指定的value,这个方法除了修改已经存在的键值对之外,还用于添加键值对。首先是找有没有这个section,没有这个section就添加这个section,然后找这个key,找不到这个key就添加这个key,把value设置为指定value。成功返回1,失败返回0。 +获取value则是查找有没有这个section下的key-value对,存在就返回value,不存在则返回NULL。 + +### 移除key-value +```c +int ini_remove_key(ini_t ini, const char* section, const char* key); +``` +此方法将移除指定section下指定key的key-value对,移除成功返回1,失败返回0。 + +### 获取key +```c +int ini_key_index(ini_t ini, const char* section, const char* key); +const char* ini_key_name(ini_t ini, const char* section, int index); +``` +获取key的方法和获取section方法很类似,分别通过key名找索引和通过索引找key名。找索引时候,失败就返回负数,找key名失败就返回NULL。 +同样,存储键值对也是采用内置迭代器的单向链表,当key名或者索引和上次调用的一致的时候,时间复杂度为O(1),通过index正向遍历时间复杂度为O(n)。 + +### 获取计数 +```c +int ini_section_count(ini_t ini); +int ini_pair_count(ini_t ini, const char* section); +``` +这个两个方法分别获取ini下section的计数和指定section下键值对的计数。 + +### ini对象转储 +```c +char* ini_dumps(ini_t ini, int preset, int *len); +int ini_file_dump(ini_t ini, char* filename); +``` +首先**ini_dumps**方法,将ini对象按格式转储为字符串。其中传入的preset参数,是预测转出来的字符串的长度,因为在将ini对象转为字符串的过程中,是一边转换一边重新分配空间的,如果预测的这个长度准的话,预先分配的空间足够,就可以提高重新分配空间的次数,提高转换效率。*len则是转换出来的字符串长度,传入NULL时候就是不获取长度。返回值则是转换出来的字符串,这个字符串是函数分配的,**在结束使用需要free掉**。 +**ini_file_dump**方法则是在**ini_dumps**的基础上将ini转储到文件当中,filename传入文件名,返回值为转储的长度,负值表示转储失败。 + +### ini对象加载 +```c +ini_t ini_loads(const char* text); +ini_t ini_file_load(const char* filename); +``` +类似转储的方法,ini对象可以从字符串文本中加载,也可以从文件中加载。加载成功则会返回一个ini对象,失败则返回NULL。 + +### ini加载错误 +```c +int ini_error_info(int* line, int* type); +``` +varch的ini解析器提供了精准的错误识别,在ini加载的失败的时候可以调用此方法去定位错误位置和错误的类型。返回值为1表示当前解析出错了,0则为未出错。line输出错误所在行,type输出错误的类型。错误类型定义如下: +```c +#define INI_E_OK (0) // ok +#define INI_E_BRACKETS (1) // missing brackets ']' +#define INI_E_DELIM (2) // missing delimiter +#define INI_E_KEY (3) // missing key +#define INI_E_SECTION (4) // missing section +#define INI_E_REKEY (5) // key repeat +#define INI_E_RESECTION (6) // section repeat +#define INI_E_MEMORY (7) // memory allocation failed +#define INI_E_OPEN (8) // fail to open file +``` + +## 参考例子 + +### 生成ini文件 +```c +static void test_dump(void) +{ + ini_t ini = NULL; // 定义ini对象,习惯初始化为NULL + + ini = ini_create(); // 创建空ini对象 + if (ini == NULL) + { + printf("ini create fail!\r\n"); + return; + } + + /* 添加section */ + ini_add_section(ini, "Zhang San"); + ini_add_section(ini, "Li Si"); + ini_add_section(ini, "Wang Wu"); + + /* 添加键值 */ + ini_set_value(ini, "Zhang San", "age", "18"); + ini_set_value(ini, "Zhang San", "height", "178"); + ini_set_value(ini, "Zhang San", "email", "123456@qq.com"); + + ini_set_value(ini, "Li Si", "age", "20"); + ini_set_value(ini, "Li Si", "gender", "man"); + ini_set_value(ini, "Li Si", "weight", "65"); + + ini_set_value(ini, "Wang Wu", "age", "22"); + + /* 转储ini到文件 */ + ini_file_dump(ini, WRITE_FILE); + + ini_delete(ini); // 用完之后需要删除 +} +``` +转储的文件 +```ini +[Zhang San] +age = 18 +height = 178 +email = 123456@qq.com + +[Li Si] +age = 20 +gender = man +weight = 65 + +[Wang Wu] +age = 22 + +``` +例子里面使用函数很多没有对返回值进行判断,实际应用需对返回值进行判断。 + +### 加载ini文件 +测试的文件如前面提到的windows系统下找到的一个ini文件 **system.ini** +```ini +; for 16-bit app support +[386Enh] +woafont=dosapp.fon +EGA80WOA.FON=EGA80WOA.FON +EGA40WOA.FON=EGA40WOA.FON +CGA80WOA.FON=CGA80WOA.FON +CGA40WOA.FON=CGA40WOA.FON + +[drivers] +wave=mmdrv.dll +timer=timer.drv + +[mci] + +``` +加在测试代码 +```c +void test(void) +{ + ini_t ini = NULL; // 定义ini对象,习惯初始化为NULL + + /* 加载ini文件 */ + ini = ini_file_load("system.ini"); + if (ini == NULL) + { + int line, type; + ini_error_info(&line, &type); + printf("ini parse error! line %d, error %d.\r\n", line, type); + return; + } + + /* 遍历ini对象 */ + int section_count = ini_section_count(ini); // 获取section计数 + for (int i = 0; i < section_count; i++) + { + char *section_name = ini_section_name(ini, i); // 获取section名称 + printf("section: [%s]\r\n", section_name); + int pair_count = ini_pair_count(ini, section_name); // 获取pair计数 + for (int j = 0; j < pair_count; j++) + { + char *key = ini_key_name(ini, section_name, j); // 获取key名称 + printf("key[%s], value[%s]\r\n", key, ini_get_value(ini, section_name, key)); // 打印键值对 + } + } + + ini_delete(ini); // 用完之后需要删除 +} +``` +运行结果: +``` +section: [386Enh] +key[woafont], value[dosapp.fon] +key[EGA80WOA.FON], value[EGA80WOA.FON] +key[EGA40WOA.FON], value[EGA40WOA.FON] +key[CGA80WOA.FON], value[CGA80WOA.FON] +key[CGA40WOA.FON], value[CGA40WOA.FON] +section: [drivers] +key[wave], value[mmdrv.dll] +key[timer], value[timer.drv] +section: [mci] +``` + +### 加载错误 +在上面例子的基础上把**system.ini**文件修改一下,把section **[mci]** 右边的括号"]"删掉,再加载。 +```ini +; for 16-bit app support +[386Enh] +woafont=dosapp.fon +EGA80WOA.FON=EGA80WOA.FON +EGA40WOA.FON=EGA40WOA.FON +CGA80WOA.FON=CGA80WOA.FON +CGA40WOA.FON=CGA40WOA.FON + +[drivers] +wave=mmdrv.dll +timer=timer.drv + +[mci + +``` +运行结果: +``` +ini parse error! line 13, error 1. +``` +如此能定位到13行出现1号错误,也就是 +``` +#define INI_E_BRACKETS (1) // missing brackets ']' +``` + +## 源码解析 + +### ini解析器结构体 + +ini解析器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致ini存储结构的破坏。所以ini解析器只留了唯一一个ini的声明在头文件,然后结构体的定义都在源文件。只能使用ini解析器提供的方法对ini对象进行操作。 +ini类型声明 +```c +typedef struct INI* ini_t; +``` +使用时候,只是用`ini_t`即可。 + +```c +typedef struct INI +{ + SECTION* sections; /* sections base */ + ITERATOR iterator; /* section iterator */ + int count; /* section count */ +} INI; +``` +INI结构体中包含了5个成员,sections(section的链表),count(section的计数),iterator(section的迭代器)。特别说明这个**iterator**,就是这个迭代器记录section访问时候的位置,当再一次访问检查到是同位置时候,就可以快速返回,而不用从头到尾再遍历。迭代器后续再说明。 + +```c +typedef struct SECTION +{ + struct SECTION *next; /* link */ + char* name; /* section name */ + PAIR* pairs; /* pairs base */ + ITERATOR iterator; /* pair iterator */ + int count; /* pair count */ +} SECTION; +``` +看SECTION结构体,包含了5个成员,next(指向下一个SECTION,形成单向链表),name(section名),pairs(键值对链表),iterator(pair的迭代器),count(pair的计数)。 + +```c +typedef struct PAIR +{ + struct PAIR *next; /* link */ + char* key; /* key */ + char* value; /* value */ +} PAIR; +``` +再看PAIR结构体,包含3个成员,next(指向下一个PAIR,形成单向链表),key(键),value(值)。 + +```c +typedef struct +{ + void *p; /* iteration pointer */ + int i; /* iteration index */ +} ITERATOR; +``` +最后看看这个迭代器,其实这个迭代器很简单,就是记录当前所指向的单向链表的结点(成员p),以及记录当前所在单向链表的索引。具体是怎么记录的下文再聊。 + +结构体介绍到这里,INI类的存储结构已经很明了 +``` +INI + + section[0] --> pair[0] --> pair[1] --> pair[2] + | k v k v k v + | + v + section[1] --> pair[0] --> pair[1] --> pair[2] + | k v k v k v + | + v + section[2] --> pair[0] --> pair[1] + | k v k v + | + v + section[3] --> pair[0] + . k v + . + . +``` + +### 单向链表的迭代 + +单向链表的操作不是这里的重点,这里内置的迭代器为了提高单向链表的访问效率,迭代过程着重说明下。以section迭代为说明,说明下这个迭代器获取的链表结点的过程: +```c +static SECTION* ini_section(ini_t ini, int index) // 传入索引 +{ + if (index >= ini->count) return NULL; // 判断索引有没有越界 + + /* + 这个一步是重置迭代,也就是将迭代器定位回到链表首位 + 满足其中3个条件之一都重置迭代器 + 1、因为单向链表,不能反向指向,所以目标索引小于迭代器的索引时候,就要重置,然后从链表首位迭代到指定索引 + 2、迭代器指针(p成员)为空时,为空则没有指向具体的结点,当然得重置迭代,所以外部想重置迭代器,只需将p成员置NULL即可 + 3、目标索引index为0时,主动获取第0位,也就是首位 + */ + if (index < ini->iterator.i || !ini->iterator.p || index == 0) + { + ini->iterator.i = 0; + ini->iterator.p = ini->sections; + } + + /* + 循环将迭代器迭代到指定的索引位置 + 单向链表索引正向递增,所以正向遍历时候时间复杂度O(n),反向遍历还是O(n^2) + */ + while (ini->iterator.p && ini->iterator.i < index) + { + ini->iterator.p = ((SECTION *)(ini->iterator.p))->next; + ini->iterator.i++; + } + + /* 返回迭代器指向的结点 */ + return ini->iterator.p; +} +``` +迭代器在对链表进行调整的时候需要相应调整,最简单的方式就是把成员p设为NULL重置迭代器。 + +这个迭代器在通过索引访问时候提高了效率,通过section名的访问呢?通过section名随机访问,则是首先判断当前迭代器指向section名称时候匹配,匹配上则直接返回,不匹配还是得从头到尾遍历来匹配。 + +### ini转储说明 + +转储就是按格式将ini“打印”出来,不过的是,不是打印在控制台,而是打印在指定内存空间。那这个空间哪来的呢?是动态内存分配来的,分配多少呢?这就回到前文说到**preset**形参了,如果preset设置的够好,就可以一次性分配到合适的空间,不然得在转储的时候动态的调整空间去存放这些“打印”的字符了。 +现在先来看维护这个打印空间的结构体: +```c +typedef struct +{ + char* address; /**< buffer base address */ + unsigned int size; /**< size of buffer */ + unsigned int end; /**< end of buffer used */ +} BUFFER; +``` +一共也就是3个成员,address(空间的基地址),size(空间的大小),end(已使用的结尾,索引)。 +动态调整空间的过程: +```c +static int expansion(BUFFER* buf, unsigned int needed) // neede为所需的追加的容量 +{ + char* address; + int size; + + if (!buf || !buf->address) return 0; + + /* 计算当前已使用的加上所需的一共所需多大的空间 */ + needed += buf->end; + + /* 计算当前的空间还能满足需要 */ + if (needed <= buf->size) return 1; /* there is still enough space in the current buf */ + + /* + 当前空间大小满足不了所需,重新计算能满足所需的空间 + 新的空间不是满足当前所需就行了的,还得留有余量,让下次追加时候用 + 不然每次追加一点容量都要重新分配空间,很浪费效率 + 而新分配的空间要多大才好,2的平方,也就是比所需大的最小的2的次方 + 为什么选择2的次方? + 1、算法好实现,计算比指定值大的最小2的次方数好实现 + 2、空间利用率好,为 (1 + 2)/ 2 = 75% + 3、空间重分配的次数小,比如最终需要128时候,每次定量10的空间新增需要13次,而2的次方只需7次。 + */ + size = pow2gt(needed); + address = (char*)realloc(buf->address, size); + if (!address) return 0; + + buf->size = size; + buf->address = address; + + return 1; +} +``` +以其中一段转储的代码为例,看看这个是怎么使用的 +```c +olen = strlen(sect->name); // 先计算section名多长 +if (expansion(&p, olen + 3) == 0) goto FAIL; // 追加相应的空间,+3是放其他字符 +/* 将字符复制到打印空间 */ +p.address[p.end++] = '['; +for (k = 0; k < olen; k++) +{ + p.address[p.end++] = sect->name[k]; +} +p.address[p.end++] = ']'; +p.address[p.end++] = '\n'; +``` + +在转储value的时候有个点需要注意的,就是value是允许换行的,只是换行的缩进要大于其key的缩进,由于打印key时候是没有添加缩进的,所以value有换行的时候,就需要在换行后面插入一个缩进表明后面那行是归属于前面的键值对的。 +```c +/* get length of value */ +olen = 0; +k = 0; +while (1) +{ + if (!pair->value[olen]) break; + if (pair->value[olen] == '\n') k++; // 记录value换行数,以便插入缩进 + olen++; // 记录value实际长度 +} +if (expansion(&p, olen + k + 1) == 0) goto FAIL; + +/* dump value */ +for (k = 0; k < olen; k++) +{ + p.address[p.end++] = pair->value[k]; + if (pair->value[k] == '\n') p.address[p.end++] = '\t'; // 插入缩进 +} +p.address[p.end++] = '\n'; +``` + +最终打印时候就是将ini遍历,动态调整空间把每个section和键值对按顺序打印到指定空间。 + +### ini加载说明 + +ini解析器最重要以及最复杂的部分就这个加载解析的部分。 + +* 1、首先创建一个ini空对象,然后在随着解析把解析到的section、key、value这些逐个添加到ini对象,等到解析完就可以得到一个完整的ini对象 + +* 2、在解析之前的首先得将解析的行号以及错误重置,然后随着解析遇到换行符就让行号相应的递增,碰到解析出错了,就记录下错误的类型 + +* 3、然后就到了真正的解析过程,解析的过程就是逐个字符的遍历判断,判断相应的字符段是什么范围的,是section?是key?是value?是注释?得到相应的范围就执行相应的解析动作。 + +整个解析就是在一个大循环当中 +```c +while (*text) +{ + ... + ... + ... +} +``` +因为ini语法规定的内容基本都是以行来划分(value特殊情况可以换行),所以基本在这个循环里面就是在处理一行行的信息。 + +在**while**循环的第一步 +```c +s = skip(text); +depth = s - text; +text = s; +``` +跳过无用的字符,这里无用的字符就比如空格或者tab这些没有实际意义的字符。在跳过字符的同时,记录下来缩进,也就是后面判断下一行是归属于新的一行还是上一行的value的范围的依据。 + +紧接着,就判断是不是注释了,因为注释是独占一行的,碰到注释标识了,后面的内容就不管了,跳过注释。 +```c +/* skip comments */ +if (iscomment(*text)) +{ + text = lend(text); // 直接定位到行末 + if (*text == '\n') + { + text++; + eline++; + continue; + } +} +``` + +剩下的就是规定section、key、value范围以及相应解析了。大体结构如下 +```c +/* +这个scope表示的是上一个value的界值范围 +这个text解析的文本大于scope,也是超过上个value范围了,不属于上个value了 +就可以解析新的section和key了 +*/ +if (text >= scope) +{ + /* 以 [ 开头的表示section的范围 */ + if (*text == '[') + { + /* + section是独占一行的,以最外围的 [] 括起来的section名 + 所以在这里就找最外围的 [] ,左边已经找到了,就找右边的 + 找最右的,首先就直接定位到行末,再从行末往回找 + 跳过无用的字符,找到右面 ] + */ + ... + ... + ... + } + else + { + /* + key是必须在行首,不是section部分那就是key部分了 + key范围的确定也就是找到第一个分割符 = 或者 : 了 + 找到了分割符,分割符前面的就是key的范围,后面就是value的范围 + 因为value的范围是可以换行的,所以这里要另外算value的范围 + value换行的前提是,新行的缩进需要大于key的缩进(这里新行是空白行另算) + 然后找个哨兵往前面探,探到不属于value的范围位置 + */ + ... + ... + ... + } +} +else +{ + /* + 还是属于上个value范围的时候 + 规定接下来每一行哪些字符块是有用的,也就是在这一行掐头去尾,把无用字符去掉 + 掐头去尾完之后,就把真正有意义的字符串追加到value后面 + */ + ... + ... + ... +} +``` +这里特别说明下获取value的范围 +```c +static const char* value_scope(const char* text, int depth) +{ + const char *s = text; + const char *sentinel = NULL; + + s = lend(s); // 范围直接包含分割符 : = 后面的这行 + while (*s) + { + sentinel = skip(s + 1); // 让哨兵走到新行的有意义字符 + /* skip comments */ + if (iscomment(*sentinel)) + { + s = lend(sentinel); + continue; + } + /* 当前行的缩进大于key的缩进,这一行也纳入范围 */ + if (sentinel - s - 1 > depth) + { + s = sentinel; + s = lend(s); + } + /* 缩进变小了,但不一定不属于范围 */ + else + { + /* 碰到了有意义字符了,就不属于value范围了 */ + if (*sentinel != 0 && *sentinel != '\n') break; + /* 那就直接到行末了,这行是空白行,还是属于value范围 */ + else s = sentinel; + } + } + /* 倒退回去,去掉那些尾部是空白行的,不把这些空白行归属value范围 */ + while (*s == 0 || *s == '\n') + { + sentinel = rskip(s - 1, text); + if (*sentinel != 0 && *sentinel != '\n') break; + s = sentinel; + } + + return s; +} +``` + +### ini的增删改查说明 +剩下的针对ini增删改查的其实已经没什么特别说明的了,就是对链表数据结构的基本操作,这里不着重说明了 + diff --git a/doc/varch:json解析器.md b/doc/varch:json解析器.md new file mode 100644 index 0000000..bfdaced --- /dev/null +++ b/doc/varch:json解析器.md @@ -0,0 +1,379 @@ +# json + +## 介绍 +C语言json解释器。包含json文本文件解析和生成,占用空间小、安全高效、简洁灵活,能无差别或者小修改移植到大部分的C语言平台。 + +## 使用例子 + +### 生成 + +**测试代码** +```c +void test_dump(void) +{ + json_t json, t; + + /* create root node */ + json = json_create_object(NULL); + + /* Add to root node */ + json_add_string_to_object(json, "name", "json parser"); + json_add_string_to_object(json, "version", "1.6.0"); + json_add_string_to_object(json, "description", "This is a C language version of json streamlined parser."); + json_add_string_to_object(json, "repository", "https://gitee.com/Lamdonn/json"); + + /* Add an empty array to the root node */ + t = json_add_array_to_object(json, "keywords"); /* t receive added array */ + json_add_string_to_array(t, "json"); + json_add_string_to_array(t, "streamlined"); + json_add_string_to_array(t, "parser"); + + /* Add an empty object to the root node */ + t = json_add_object_to_object(json, "others"); /* t receive added object */ + json_add_bool_to_object(t, "open", JSON_TRUE); + json_add_string_to_object(t, "license", "GPL3.0"); + + /* Dump JSON objects to a file */ + json_file_dump(json, "test.json"); + + /* Delete after end of use */ + json_delete(json); +} +``` + +生成文件名: **test.json** +```json +{ + "name": "json parser", + "version": "1.6.0", + "description": "This is a C language version of json streamlined parser.", + "repository": "https://gitee.com/Lamdonn/json", + "keywords": ["json", "streamlined", "parser"], + "others": { + "open": true, + "license": "GPL3.0" + } +} +``` + +### 解析 + +解析前面生成的文件: **test.json** + +**测试代码** +```c +void test_load(void) +{ + json_t json, t; + + /* Load json file */ + json = json_file_load("test.json"); + if (!json) return; + + t = json_to_key(json, "name"); + if (json_isstring(t)) printf("module name: %s\r\n", json_value_string(t)); + + t = json_to_key(json, "others", "open"); + if (json_isbool(t)) printf("open: %s\r\n", json_value_bool(t) ? "yes" : "no"); + + t = json_to_index(json, 4, 1); + if (json_isstring(t)) printf("keywords[1]: %s\r\n", json_value_string(t)); + + /* Delete after end of use */ + json_delete(json); +} +``` + +**打印结果** +``` +module name: json parser +open: yes +keywords[1]: streamlined +``` + +## json语法 + +json语法是**JavaScript**对象表示语法的子集。 + +1. 数据在`键值对`中,键值用`:`指示 +2. 数据由逗号`,`分隔 +3. 使用斜杆`\`来转义字符 +4. 中括号`[]`保存数组,数组可以包含多个不同类型的值 +5. 大括号`{}`保存对象,对象可以包含多个键值对 + +### json数据类型 +```c +#define JSON_TYPE_NULL (1) /* base type, null */ +#define JSON_TYPE_BOOL (2) /* base type, bool */ +#define JSON_TYPE_NUMBER (3) /* base type, number */ +#define JSON_TYPE_STRING (4) /* base type, string */ +#define JSON_TYPE_ARRAY (5) /* extension type, array */ +#define JSON_TYPE_OBJECT (6) /* extension type, object */ +``` + +### json键值对 + +键值对书写方式 +```json +"key" : "value" +``` + +其中键的类型为字符串类型,需要用双引号`""`括住。 +而值可以是以下基本类型的任意一种 + +* 空类型(`null`) +* 布尔类型(`true`、`false`) +* 数字类型(整数或浮点数) +* 字符串类型(在双引号`""`中) +* 数组类型(在中括号`[]`中) +* 对象类型(在大括号`{}`中) + +### 语法例子 + +```json +{ + "information": { + "module": "json", + "history": [1.0, 1.1, 1.2, 1.21, 1.3, 1.31, 1.32, 1.42], + "paser": true, + "print": false + }, + "other": null +} +``` + +## 操作方法 + +### 常用方法 + +#### json解析 + +方法原型 +```c +json_t json_loads(const char* text); // 加载文本 +json_t json_file_load(char* filename); // 加载文件 +``` + +`json_loads`函数传进json文本信息,则可以返回解析出来的json对象句柄。 `json_file_load`函数则是直接传入文件名即可加载文件返回json对象,函数内部通过C语言标准文件操作函数集对文件进行读取,然后套用`json_loads`函数进行解析,支持utf8编码文件。 + +#### json生成 + +方法原型 +```c +char* json_dumps(json_t json, int preset, int unformat, int* len); // 生成文本 +int json_file_dump(json_t json, char* filename); // 生成文件 +``` + +`json_dumps`函数将json对象转换成文本信息,其中`preset`为预置的文本长度,预置的长度和最终输出文本长度接近则可以减小内存重分配的次数而提高转换效率;`unformat`是否不采用格式化输出,不采用格式化则文本会挤在一行;`len`是转换的输出长度。 +`json_file_dump`函数套用了`json_dumps`函数将文本信息存储到指定名字的文件。 + +#### json获取子对象 + +方法原型 +```c +json_t json_get_child(json_t json, const char* key, int index); +#define json_to_index(json, i, ...) +#define json_to_key(json, key, ...) +``` +在json对象中,key是不具备查重的,也就是在同一个层级的json中,可能存在多个同名的key,`json_get_child`方法则是可以用于匹配特定的key。此函数,当`key`传入NULL时,则只有index起作用,按照索引来匹配子对象,当`key`不为NULL的时候,则只会匹配相应key的子对象,并通过index来指示匹配第几个名为`key`的对象。 +```c +t = json_get_child(json, NULL, 3); // 找索引为3的子对象 +t = json_get_child(json, "a", 3); // 找键为"a"索引为3的子对象 +``` +`json_to_index`和`json_to_key`这两个方法都可以很方便的获取到子对象,同时可以当做查找方法查找是否存在相应的子对象。 +`json_to_index`方法通过索引的方式去获取子对象,不管对象类型是数组的还是对象的,此方法都能使用。 +`json_to_key`方法通过键的方式去获取子对象,但是此方法只适用于是对象类型的对象,因为数组没有键。 +这两个方法的参数都带有了不定参数,这个不定参数可以输入若干个索引或者键去连续获取下个层级的子对象。如下例子: +```c +t = json_to_key(json, "information", "module", "name"); +``` +等同 +```c +t = json_to_key(json, "information"); +t = json_to_key(t, "module"); +t = json_to_key(t, "name"); +``` + +#### json获取对象的值类型 + +方法原型 +```c +#define json_type(json) // 获取类型 +#define json_isnull(json) // 判断是不是空类型 +#define json_isbool(json) // 判断是不是布尔类型 +#define json_isnumber(json) // 判断是不是数字类型 +#define json_isint(json) // 判断是不是数字整型 +#define json_isfloat(json) // 判断是不是数字浮点型 +#define json_isstring(json) // 判断是不是字符串型 +#define json_isarray(json) // 判断是不是数组型 +#define json_isobject(json) // 判断是不是对象型 +``` + +#### json获取对象的键和值 + +方法原型 +```c +const char* json_key(json_t json); +int json_value_bool(json_t json); +int json_value_int(json_t json); +double json_value_float(json_t json); +const char* json_value_string(json_t json); +json_t json_value_array(json_t json); +json_t json_value_object(json_t json); +``` + +在获取值对值操作之前,建议先判断一下是不是期待的类型,如果操作不对的类型,可能破坏json的存储结构甚至导致程序奔溃。 + +#### json创建对象和删除对象 + +方法原型 +```c +json_t json_create(void); +void json_delete(json_t json); +#define json_create_null_for_object(key) (json_set_key(json_set_null(json_create()),(key))) +#define json_create_true_for_object(key) (json_set_key(json_set_bool(json_create(),JSON_TRUE),(key))) +#define json_create_false_for_object(key) (json_set_key(json_set_bool(json_create(),JSON_FALSE),(key))) +#define json_create_bool_for_object(key, b) (json_set_key(json_set_bool(json_create(),(b)),(key))) +#define json_create_int_for_object(key, n) (json_set_key(json_set_int(json_create(),(n)),(key))) +#define json_create_float_for_object(key, n) (json_set_key(json_set_float(json_create(),(n)),(key))) +#define json_create_string_for_object(key, s) (json_set_key(json_set_string(json_create(),(s)),(key))) +#define json_create_array_for_object(key) (json_set_key(json_set_array(json_create(),NULL),(key))) +#define json_create_object_for_object(key) (json_set_key(json_set_object(json_create(),NULL),(key))) +#define json_create_null_for_array() (json_set_null(json_create())) +#define json_create_true_for_array() (json_set_bool(json_create(),JSON_TRUE)) +#define json_create_false_for_array() (json_set_bool(json_create(),JSON_FALSE)) +#define json_create_bool_for_array(b) (json_set_bool(json_create(),(b))) +#define json_create_int_for_array(n) (json_set_int(json_create(),(n))) +#define json_create_float_for_array(n) (json_set_float(json_create(),(n))) +#define json_create_string_for_array(s) (json_set_string(json_create(),(s))) +#define json_create_array_for_array() (json_set_array(json_create(),NULL)) +#define json_create_object_for_array() (json_set_object(json_create(),NULL)) +``` + +此类方法可以创建json的基本类型,数组和对象又可以存储json对象,所以默认都创建为空的数组和空的对象,除了这两个,其他方法都可以在创建时候指定初始化值。 +创建对象指定了`key`,如果传入key则创建出来的对象可以添加到对象类型中,如果传入空则创建出来的对象可以添加到数组类型中。 +数组可以存储任意类型的数据,但是一般都是存储同种类型的数据,因此数组的创建方法额外提供了初始化的方法。 + +```c +json_t json_create_array_int(char* key, const int* numbers, int count); +json_t json_create_array_float(char* key, const float* numbers, int count); +json_t json_create_array_double(char* key, const double* numbers, int count); +json_t json_create_array_string(char* key, const char** strings, int count); +``` + +按照C语言数组,初始化数据到json数组。 + + +#### json链结和断链对象 + +方法原型 +```c +json_t json_attach(json_t json, int index, json_t ins); +json_t json_detach(json_t json, const char* key, int index); +``` + +`json_attach`方法是将创建后的对象按照索引链结到另一个对象中,而被链结的对象其值类型必须为数组型或者对象型才可以。成功返回item自身,失败则是NULL。 +`json_detach`方法是将数组或者对象中,按照`json_get_child`同样的`key`和`index`配合匹配逻辑,将指定的子对象断链出来,返回其子对象,失败则返回NULL。 +这两个方法都不涉及对象的创建或者删除,只是存储结构的调整,通过配合其他方法实现添加或者移除的操作。 + +#### json添加对象 +```c +#define json_add_null_to_array(json) +#define json_add_bool_to_array(json, b) +#define json_add_int_to_array(json, n) +#define json_add_float_to_array(json, n) +#define json_add_string_to_array(json, s) +#define json_add_array_to_array(json) +#define json_add_object_to_array(json) + +#define json_add_null_to_object(json, key) +#define json_add_bool_to_object(json, key, b) +#define json_add_int_to_object(json, key, n) +#define json_add_float_to_object(json, key, n) +#define json_add_string_to_object(json, key, s) +#define json_add_array_to_object(json, key) +#define json_add_object_to_object(json, key) +``` +这些方法是通过创建方法和链结方法配合而成,将特定类型的数据添加到array或者object型的json对象中。 + +#### json移除子对象 +```c +#define json_erase(json, key, index) +#define json_erase_by_index(json, index) +#define json_erase_by_key(json, key) +``` +这些方法是通过删除方法和断链方法配合而成,移除array或者object中特定的子对象。 + +#### json对象修改 + +方法原型 +```c +json_t json_set_key(json_t json, const char* key); +json_t json_set_null(json_t json); +json_t json_set_bool(json_t json, int b); +json_t json_set_int(json_t json, int num); +json_t json_set_float(json_t json, double num); +json_t json_set_string(json_t json, const char* string); +json_t json_set_object(json_t json, json_t object); +json_t json_set_array(json_t json, json_t array); +json_t json_set_array_int(json_t json, const int* numbers, int count); +json_t json_set_array_float(json_t json, const float* numbers, int count); +json_t json_set_array_double(json_t json, const double* numbers, int count); +json_t json_set_array_string(json_t json, const char** strings, int count); +``` +修改对象会把原来的内容覆盖掉 + + +#### json对象复制 + +方法原型 +```c +json_t json_copy(json_t json); +``` + +根据源json对象深拷贝出一份json对象 + +#### json解析报错 + +方法原型 +```c +int json_error_info(int* line, int* column); +``` + +此json解析器具备较为精准的报错机制,在执行`json_loads`类加载函数时候,返回空值表明解析出错时候则可以调用`json_error_info`方法来查看具体的错误信息,`json_file_load`函数内部已经输出错误信息。 +参数中,`*line`为输出的错误行;`*column`为输出的错误列,返回值则为错误类型。 + +如下例子,在`true`前面多了一个负号的错误 +```json +{ + "name": "json parser", + "version": "1.6.0", + "description": "This is a C language version of json streamlined parser.", + "repository": "https://gitee.com/Lamdonn/json", + "keywords": ["json", "streamlined", "parser"], + "others": { + "open": -true, + "license": "GPL3.0" + } +} +``` + +加载该文件时,出现错误,表明第 8 行第 12 列的“true”附近出现错误代码为 7 的错误。 + +``` +Parsing error, code 7 line 8 column 12, near [true]. +``` + +错误类型包含以下几种 + +```c +#define JSON_E_OK (0) // 没有错误 +#define JSON_E_INVALID (1) // 无效 +#define JSON_E_GRAMMAR (2) // 常见语法错误 +#define JSON_E_END (3) // 文本末尾额外的无效文本 +#define JSON_E_KEY (4) // 解析密钥时发生错误 +#define JSON_E_VALUE (5) // 解析值时发生错误 +#define JSON_E_MEMORY (6) // 内存分配失败 +#define JSON_E_NUMBER (7) // 无效数字 +#define JSON_E_INDICATOR (8) // 缺少指示符 ':' +``` diff --git a/doc/varch:kern定时任务调度内核.md b/doc/varch:kern定时任务调度内核.md new file mode 100644 index 0000000..1085511 --- /dev/null +++ b/doc/varch:kern定时任务调度内核.md @@ -0,0 +1,96 @@ +## 介绍 + +在嵌入式开发中,现在很多设备都会都会跑专门的操作系统,比如linux、rt_thread、freertos、ucos等,但还是会有不少设备用不上的操作系统,就会采取裸跑的形式(又叫前后台系统)。像在这种不带操作系统的软件开发中,很常见的形式就是把所有要执行的函数全部放在循环当中,按顺序循环的执行每一个函数,然后加入对函数执行的周期有要求还可以采用定时器来确定多久来执行某个函数。但是这对于我们要来管理多个这样的函数时候,在编程上就显得很冗余繁琐了,也不方便我们管理。 +这里写了一个方便我们管理定时任务管理内核。需要用到的硬件资源也就一个定时器(用于得到时间片),当然也可以在电脑上运行,只要能获取到时间片就可以。这个内核最主要的功能就是实现函数的周期调用,说白了,这个内核就是实现软件定时器的功能。 + +## 接口 + +### 内核初始化 +```c +int kern_init(kern_tick_t tick_func, unsigned short time_slice); +``` +在进行任务调度之前,必须先进行内核的初始化。初始化很简单,传入能够获取时间片的时钟函数`tick_func`,以及时钟函数具体的时钟周期`time_slice`(一般是毫秒)。初始化成功就返回`KE_OK`,其他值失败。 + +### 创建任务和删除任务 +```c +task_t task_create(unsigned short period, task_handler_t handler); +int task_delete(task_t task); +``` +创建任务很简单,一个是传入任务的周期`period`(单位是内核初始化时传入的时间片),另一个是任务的执行函数`handler`,创建成功返回任务的id(唯一),失败返回0。删除的话,就把任务id传进去即可,返回`KE_OK`,其他值失败。 + +### 任务调度 +```c +void kern_schedule(void); +``` +在初始化完内核后,就可以调用内核调度函数了,不过还没有任务。一般是先创建了任务,再调度。 +这个调度函数,没有函数出口,会一值运行,直至程序结束,所以调度函数后面的代码都执行不了,不要把代码写在调度函数后面。 + +### 运行中的任务 +```c +task_t task_running(void); +``` +不同的任务是可以相同的执行函数的,这个函数就是区分是哪个任务调用的执行函数。 + +## 例子 +```c +static unsigned int get_msec(void) +{ + struct timeval mstime; + unsigned int ms = 0; + gettimeofday(&mstime, NULL); + ms = mstime.tv_sec * 1000 + mstime.tv_usec / 1000; + return ms; +} + +void task1(void) +{ + static int count = 0; + printf("task1 running! %d\r\n", ++count); +} + +void task2(void) +{ + static int count = 0; + printf("task2 running! %d\r\n", ++count); +} + +int main(int argc, char *argv[]) +{ + if (kern_init(get_msec, 1) == KE_OK) + { + printf("kern init success!\r\n"); + } + else + { + printf("*** kern init fail!\r\n"); + return; + } + + printf("create task %d\r\n", task_create(1000, task1)); + printf("create task %d\r\n", task_create(500, task2)); + + kern_schedule(); + + return 0; +} +``` +运行结果: +``` +kern init success! +create task 1 +create task 2 +task1 running! 1 +task2 running! 1 +task2 running! 2 +task1 running! 2 +task2 running! 3 +task2 running! 4 +task1 running! 3 +task2 running! 5 +task2 running! 6 +task1 running! 4 +task2 running! 7 +task2 running! 8 +``` +任务2运行了2次,相应任务1运行1次。 + diff --git a/doc/varch:list容器.md b/doc/varch:list容器.md new file mode 100644 index 0000000..00f91de --- /dev/null +++ b/doc/varch:list容器.md @@ -0,0 +1,255 @@ +## 介绍 + +list容器是对C语言的链表进行通用化的封装,list支持任意数据类型(int,char,struct等等),封装了链表常用的增删改查方法(这里的查是随机访问),可以直接当作普通的容器来使用,也可以在此基础上二次封装。 + +## 接口 + +### 创建和删除list对象 +```c +list_t list_create(int dsize); +void list_delete(list_t list); +#define list(type) // 为了更简便的使用,对list_create套一层宏定义 +#define _list(list) // 对list_delete套一层宏定义,并在list删除后置为空 +``` +其中**list_t**为list的结构体,创建方法则会返回一个空的list对象,创建失败则返回NULL,其中`dsize`传入数据的大小。删除方法则是删除传入的list对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + list_t list = list(int); // 定义并创建一个int型的list + _list(list); // 成对使用,用完即删除 +} +``` + +### list的插入和移除 +```c +void* list_insert(list_t list, int index, void* data); +int list_erase(list_t list, int index, int num); +``` +list的巨大优势就是其插入和移除的效率,不用数据移位只需修改链表指向。 +插入的方法是在指定索引的位置插入指定地址的数据(在其中,data传入NULL时则只是开辟空间,不进行赋值),插入成功后返回插入后的数据的地址,插入失败则是返回NULL。而移除则是移除指定索引的num个数据,返回实际移除了的个数。 +```c +void test(void) +{ + list_t list = list(int); // 定义并创建一个int型的list + int i = 0; + void *data; + + /* 插入数据 */ + for (i = 0; i < 6; i++) + { + data = list_push_back(list, &i); // 尾插 0~5 + if (data) printf("insert %d\r\n", *(int*)data); // 插入后打印出来 + } + + _list(list); // 成对使用,用完即删除 +} +``` +结果: +``` +insert 0 +insert 1 +insert 2 +insert 3 +insert 4 +insert 5 +``` +通过插入和移除方法还延伸出了,以下的宏定义方法 +```c +#define list_push_front(list, data) +#define list_push_back(list, data) +#define list_pop_front(list) +#define list_pop_back(list) +#define list_clear(list) +``` + +### list数据的读写 +```c +void* list_data(list_t list, int index); +#define list_at(list, type, i) +``` +`list_data`方法就是根据索引来获取数据的地址,返回的则是指定的数据的地址,NULL则是失败。而`list_at`则是在`list_data`的基础上加多类型。 +list的随机访问和连续地址的数组或者vector不太一样,数组是可以直接定位到指定索引的地址,而链表要进行随机访问,就得从表头开始通过链接指针一步步的指向到指定位置,而在此过程就会花费比较多的时间去一步步指向。varch的list增加了内置迭代器,可以记录当前访问的位置,当下次再访后面的位置时,就不必再从链表头开始指向了,而从当前位置开始指向到指定位置,如此有着很高的正向遍历效率。 +```c +void test(void) +{ + list_t list = list(int); + int i = 0; + + for (i = 0; i < 6; i++) + { + list_push_back(list, &i); + } + + for (i = 0; i < 6; i++) // 正向遍历 + { + printf("list[%d] = %d\r\n", i, list_at(list, int, i)); + } + + _list(list); +} +``` +结果: +``` +list[0] = 0 +list[1] = 1 +list[2] = 2 +list[3] = 3 +list[4] = 4 +list[5] = 5 +``` + +### list的大小和和数据大小 +```c +int list_size(list_t list); +int list_dsize(list_t list); +``` +list的`size`很好理解,也就是像数组那样的大小,`dsize`也就是创建时候传入的数据的大小。 +```c +void test(void) +{ + list_t list = list(int); + int i = 5; + while (i--) list_push_back(list, NULL); // 尾插5个空数据,也就是只开辟空间,但不赋值 + printf("size = %d, data size = %d\r\n", list_size(list), list_dsize(list)); + _list(list); +} +``` +结果: +``` +size = 5, data size = 4 +``` + +## 参考例子 + +```c +typedef struct { + char *name; + int age; +} STUDENT; + +void test(void) +{ + list_t list_int = list(int); // 定义并创建int型list + list_t list_student = list(STUDENT); // 定义并创建结构体STUDENT型list + char *name[3] = { // 定义三个名字 + "ZhangSan", + "LiSi", + "WangWu", + }; + int i = 0; + + for (i = 0; i < 3; i++) + { + STUDENT s = {name[i], 18 + i}; + list_push_back(list_student, &s); // 插入三个STUDENT + list_push_back(list_int, &i); // 依次插入 0 1 2 + } + + i= 1024; list_insert(list_int, 1, &i); // 在索引1的位置插入1024 + for (i = 0; i < list_size(list_int); i++) + { + printf("list_int[%d] = %d\r\n", i, list_at(list_int, int, i)); // 正向遍历 + } + + for (i = 0; i < list_size(list_student); i++) + { + printf("list_student[%d]: name=%s, age=%d\r\n", i, list_at(list_student, STUDENT, i).name, list_at(list_student, STUDENT, i).age); + } + + // 结束使用该list后就删除 + _list(list_int); + _list(list_student); +} +``` +结果: +``` +list_int[0] = 0 +list_int[1] = 1024 +list_int[2] = 1 +list_int[3] = 2 +list_student[0]: name=ZhangSan, age=18 +list_student[1]: name=LiSi, age=19 +list_student[2]: name=WangWu, age=20 +``` +例子里面使用函数很多没有对返回值进行判断,实际应用需对返回值进行判断。 + +## 源码解析 + +### list结构体 + +list容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致list存储结构的破坏。所以list解析器只留了唯一一个list的声明在头文件,然后结构体的定义都在源文件。只能使用list容器提供的方法对list对象进行操作。 +list类型声明 +```c +typedef struct LIST *list_t; +``` +使用时候,只是用`list_t`即可。 + +```c +/* type of list */ +typedef struct LIST +{ + NODE* base; /* address of base node */ + NODE* iterator; /* iterator of list */ + int size; /* size of list */ + int dsize; /* data size */ + int index; /* index of iterator */ +} LIST; +``` +`LIST`结构体中包含了5个成员,base(链式结构的基结点,也就是表头),iterator(当前指向的结点),size(list的大小,也就是list的长度),dsize(每个数据的大小),index(iterator所在的索引)。 +```c +/* type of list node */ +typedef struct _NODE_ +{ + struct _NODE_ *next; /* next node */ +} NODE; +#define data(node) ((node)+1) /* data of node */ +``` +在`NODE`结构体当中,显式的成员也就只有next(指向下一个结点,形成链式结构),那数据存在哪里呢?这是varch的list的一个兼容所有数据结构的一个特性,因为每种数据类型的大小不太一致,如果规定结构体里面跟着的固定长度,那么就兼容不了不同长度的数据类型。然而,在这个`NODE`结构体当中,把实际的数据分配了在结构体末尾的空间,具体多长就由`LIST`的`dsize`来决定,然后把这个`data`成员称为隐式成员(不直接在结构体中体现)。那么要获取`data`成员的地址也很简单,就是在结点地址`+1`偏移`NODE`的size即可,如此,可以减少再一级指针指向data的指针空间了。 +当创建`int`型list时候,`NODE`的数据可以理解为如下了: +```c +typedef struct _NODE_ +{ + struct _NODE_ *next; /* next node */ + char data[sizeof[int]]; +} NODE; +``` + +### 迭代器的随机访问 + +前面说到`list`内置了迭代器,那这个迭代器是怎么回事呢? +看下源码: +```c +static NODE* list_node(list_t list, int index) // 传入list和索引 +{ + if (!list) return NULL; + if (index < 0 || index >= list->size) return NULL; // 判断索引有没有越界 + + /* + 这个一步是重置迭代,也就是将迭代器定位回到链表首位 + 满足其中3个条件之一都重置迭代器 + 1、因为单向链表,不能反向指向,所以目标索引小于迭代器的索引时候,就要重置,然后从链表首位迭代到指定索引 + 2、迭代器指针为空时,为空则没有指向具体的结点,当然得重置迭代,所以外部想重置迭代器,只需将迭代器指向置NULL即可 + 3、目标索引index为0时,主动获取第0位,也就是首位 + */ + if (index < list->index || !list->iterator || index == 0) + { + list->index = 0; + list->iterator = list->base; + } + + /* + 循环将迭代器迭代到指定的索引位置 + 单向链表索引正向递增,所以正向遍历时候时间复杂度O(n),反向遍历还是O(n^2) + */ + while (list->iterator && list->index < index) + { + list->iterator = list->iterator->next; + list->index++; + } + + /* 返回迭代器指向的结点 */ + return list->iterator; +} +``` +在varch的list提供的所有api中,只要是传入索引的,都会调用上述访问进行链表的定位,所以在同一个索引操作时,是不需要重新进行指向定位的,而可以快速返回。 + diff --git a/doc/varch:map容器.md b/doc/varch:map容器.md new file mode 100644 index 0000000..e96ce00 --- /dev/null +++ b/doc/varch:map容器.md @@ -0,0 +1,232 @@ +## 介绍 + +map映射与set集合很类似,是逻辑上离散的容器,与set不同的是,set的数据存储方式是以`index-data`的形式存在,而map映射则是以`key-value`键值对的形式存在,set的index是整型data可以是任意型,而map的key可以任意型value也可以是任意型。 +varch的map容器,在底层实现上采用了“红黑树”,与set一致,增删操作的效率高,而且也支持随机访问。map也支持迭代器。 + +## 接口 + +### 创建和删除map对象 +```c +map_t map_create(int dsize, int ksize, void *trans); +void map_delete(map_t map); +#define map(ktype, dtype) // 为了更简便的使用,对map_create套一层宏定义 +#define _map(map) // 对map_delete套一层宏定义,并在map删除后置为空 +``` +其中**map_t**为map的结构体,创建方法则会返回一个空的map对象,创建失败则返回NULL,其中`dsize`传入数据的大小,`ksize`传入key的大小,`trans`是key的转换句柄函数。 +`ksize`和`trans`在map_cfg配置文件中定义,直接使用`map(ktype, dtype)`更方便。 +map默认支持了一些key的类型,包括`char`、`int`、`float`、`double`、`string(字符串)`,其他类型也可以在`cfg`文件中进行添加。 +删除方法则是删除传入的map对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + map_t map_char = map(char, int); + map_t map_int = map(int, int); + map_t map_float = map(float, int); + map_t map_double = map(double, int); + map_t map_string = map(string, int); + + _map(map_char); + _map(map_int); + _map(map_float); + _map(map_double); + _map(map_string); +} +``` +这例子里面,value的类型都定为了int,其他任意类型都可以。 + +### map的插入和移除 +```c +#define map_insert(map, key, value) +#define map_erase(map, key) +``` +map有着较好插入和移除的效率,不用数据移位只需修改链表指向。 +插入的方法是添加指定key并将数据复制到这个键(在其中,data传入NULL时则只是开辟空间,不进行赋值),在插入key的过程中会进行查重,保证key的唯一性,插入成功后返回插入后的数据的地址,插入失败则是返回NULL。而移除则是移除指定键的数据,成功返回1,失败返回0。 +因为保证key是可以传入不同数据结构,所以用了宏定义传入。 + +### map数据的读写 +```c +#define map_data(map, key) +#define map_at(map, vtype, key) +void* map_error(map_t map); +``` +`map_data`方法就是根据键来获取数据的地址,返回的则是指定的数据的地址,`map_error()`则是失败。而`map_at`则是在`map_data`的基础上加多类型,`map_data`具备读写保护机制,因为返回的是`map_error()`而不是NULL,所以在使用`map_at`方法`key`写错了就会修改`map_error()`指向的内容,而不会导致奔溃。 + +```c +void test(void) +{ + int value; + map_t map = map(string, int); + if (!map) return; + + value = 100; map_insert(map, "hello", &value); + value = 110; map_insert(map, "Zhang", &value); + printf("map[hello] = %d\r\n", map_at(map, int, "hello")); + printf("map[Zhang] = %d\r\n", map_at(map, int, "Zhang")); + map_erase(map, "hello"); + + _map(map); +} +``` +结果: +``` +map[hello] = 100 +map[Zhang] = 110 +``` + +### map的大小和和数据大小 +```c +int map_size(map_t map); +int map_ksize(map_t map); +int map_vsize(map_t map); +``` +map的`size`很好理解,也就是像数组那样的大小,`ksize`也就是创建时候传入的key的大小(string类型的key,因为是指针,不是实体,ksize为0),`vsize`也就是值得大小。 +```c +void test(void) +{ + int value; + map_t map = map(string, int); + if (!map) return; + + value = 100; map_insert(map, "hello", &value); + value = 110; map_insert(map, "Zhang", &value); + + printf("size = %d, key size = %d, value size = %d\r\n", map_size(map), map_ksize(map), map_vsize(map)); + + _map(map); +} +``` +结果: +``` +size = 2, key size = 0, value size = 4 +``` + +### map查找 +```c +#define map_find(map, key) +``` +这个方法其实套`map_data`实现,只是find成功返回1失败返回0。 + +### map迭代器 + +```c +void map_it_init(map_t map, int orgin); +void* map_it_get(map_t map, void **kaddress, int *ksize); +``` + +map也支持内置的迭代器,但主要map的迭代器用于遍历。map是离散型的key,无法通过这种逐一递增的方式进行遍历,所以这里给定了两个迭代器函数用于遍历map。 +`map_it_init`初始化迭代器,`orgin`指定为`MAP_HEAD`或者`MAP_TAIL`,就分别是正向迭代和反向迭代。 +`map_it_get`获取迭代,更新迭代位置,`**kaddress`为输出的key(当前所在的key,也可以传入NULL不接收),`*ksize`为输出的key的大小(当前所在的key,也可以传入NULL不接收),返回迭代位置的数据。 +通过`map_size`来把控迭代次数。 + +```c +void test(void) +{ + map_t map = map(string, int); + int value; + char *key; + void *data; + int i; + + value = 100; map_insert(map, "hello", &value); + value = 1; map_insert(map, "ZhangSan", &value); + value = 2; map_insert(map, "LiSi", &value); + value = 3; map_insert(map, "WangWu", &value); + value = 4; map_insert(map, "SunLiu", &value); + value = 5; map_insert(map, "QianQi", &value); + + map_it_init(map, MAP_HEAD); + i = map_size(map); + while (i--) + { + data = map_it_get(map, &key, NULL); + printf("map[%s] = %d\r\n", key, *(int *)data); + } + + _map(map); +} +``` + +结果: +``` +map[LiSi] = 2 +map[QianQi] = 5 +map[SunLiu] = 4 +map[WangWu] = 3 +map[ZhangSan] = 1 +map[hello] = 100 +``` + +## 源码解析 + +在红黑树存储部分和set是一致的,不同之处是支持了多种的key的类型。 +比如在原来set传入index的地方,都换成如下的key类型 +```c +typedef struct +{ + void* address; + int size; +} MKEY; +``` + +而在红黑树二叉查找,由比较index的大小,换成了 +```c +static int key_compare(MKEY key0, MKEY key1) +{ + unsigned char *add0 = (unsigned char *)key0.address; + unsigned char *add1 = (unsigned char *)key1.address; + while (key0.size && key1.size) + { + key0.size--; + key1.size--; + if (*add0 < *add1) return -1; + else if (*add0 > *add1) return 1; + add0++; + add1++; + } + if (key0.size < key1.size) return -1; + else if (key0.size > key1.size) return 1; + return 0; +} +``` +这个函数会实际去比较key的存储空间里面的存储内容,一个byte一个byte的比较。 + +那怎么实现把形参的key转成key的地址和大小呢? +这个就是前面`map_create`方法中的`trans`参数了,这个就是将形参的key转换为key地址和大小的转换函数句柄,都是定义在`map_cfg.c`文件中的函数,所以要添加支持的key类型,就在`map_cfg`文件中添加。 + +来看查找方法的一个具体流程。 +```c +#define map_find(map, key) map_find((map), (key)) +int map_find(map_t map, ...); +``` +这里`map_find`第二个参数定义为不定参数,就是为了支持传入不同的数据类型,比如传入`int`、`float`、`string`,而在承接参数时候,就在`trans`函数中针对不同类型进行承接。 + +```c +int map_find(map_t map, ...) +{ + va_list args; + MKEY key; + if (!map) return 0; + va_start(args, map); + key = *(MKEY *)(map->trans(map, args)); + va_end(args); + return map_find_node(map, key)==map->nil?0:1; +} +``` +而`map_find`函数内部,就不不定参数传给`trans`函数,就可以返回相应的key了。 +剩下的,就交由`key_compare`函数去比较二分查找。 + +## 添加key支持的类型 + +* 1、 在`map_cfg.c`文件中添加`trans`函数。命名定义`void* map_key_trans__xxx(void* map, va_list args)`固定格式,`xxx`就是要的命名类型。 +* 2、 `trans`函数内部实现 +```c +int key; // 根据类型定义key +key = va_arg(args, int); // key接收相应类型的不定参数 +return map_trans_key(map, &key, sizeof(int)); // 统一返回key的地址和长度,字符串返回字符串长 +``` +* 3、 在`map_cfg.h`文件中声明`void* map_key_trans__xxx(void* map, va_list args);` +* 4、 在`map_cfg.h`文件中定义key的type,`xxx`同上为key的类型,后面可以定义为`MAP_KEY_TYPE_ENTITY`或者`MAP_KEY_TYPE_POINTER`,像字符串这种本身就是指针的,就定义为指针,int就定义为实体 +```c +#define MAP_KEY_TYPE__xxx MAP_KEY_TYPE_ENTITY // MAP_KEY_TYPE_POINTER // +``` +* 5、 添加完就可以使用`map(ktype, vtype)`来创建map了 diff --git a/doc/varch:queue容器.md b/doc/varch:queue容器.md new file mode 100644 index 0000000..8f0dcbb --- /dev/null +++ b/doc/varch:queue容器.md @@ -0,0 +1,183 @@ +## 介绍 + +队列是一种先进先出(First In First Out,FIFO)的特殊数据结构,一般情况下它只有一个出口一个入口,从队尾进入从队头出,入队push,出队pop。 +* 容量 +容量也就是在使用过程中最多能存多少个队列项,比如,容量为10的队列,最多能存10个队列项,存满后,再想入队就入不了。varch的队列存储是连续地址的,是有限容量的队列。 +* 访问机制 +一般情况下,队列只有出队和入队两种方式,可以根据连续地址快速的对队列进行遍历访问。 + +## 接口 + +### 创建和删除queue对象 +```c +queue_t queue_create(int dsize, int capacity, void *base); +void queue_delete(queue_t queue); +#define queue(type, capacity) // 为了更简便的使用,对queue_create套一层宏定义 +#define _queue(queue) // 对queue_delete套一层宏定义,并在queue删除后置为空 +``` +其中**queue_t**为queue的结构体,创建方法则会返回一个queue对象,创建失败则返回NULL,其中`dsize`传入数据的大小,`capacity`传入队列容量,`*base`传入缓冲区地址(可以不传入,不传入的话就会自动分配capacity大小的空间以来存储队列数据)。删除方法则是删除传入的queue对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + queue_t queue = queue(int, 10); // 定义并创建一个int型容量为10的queue + _queue(queue); // 成对使用,用完即删除 +} +``` + +### queue的入队和出队 +```c +int queue_push(queue_t queue, void* data); +int queue_pop(queue_t queue, void* data); +``` +这两个方法可以很方便的把数据添加到队列和从队列弹出,`push`方法`data`传入需要入队数据的地址,`pop`方法`data`传入需要接收出队数据的地址,这两个方法`data`都可以传入NULL,那只是一个占位。操作成功返回1,失败返回0。 +```c +void test(void) +{ + queue_t queue = queue(int, 10); + int i = 0; + + for (i = 0; i < queue_capacity(queue); i++) + { + queue_push(queue, &i); + } + queue_pop(queue, NULL); + queue_pop(queue, NULL); + + _queue(queue); // 成对使用,用完即删除 +} +``` + + +### queue的大小、容量和数据大小 +```c +int queue_size(queue_t queue); +int queue_capacity(queue_t queue); +int queue_dsize(queue_t queue); +``` +queue的`capacity`就是创建时候指定的容量,能存多少个队列元素,`size`是队列有多少个元素,`dsize`也就是创建时候传入的数据的大小,比如`int`,`dsize`就是`sizeof(int)`。 +```c +void test(void) +{ + queue_t queue = queue(int, 10); + int i = 0; + + for (i = 0; i < queue_capacity(queue); i++) + { + queue_push(queue, &i); + } + queue_pop(queue, NULL); + queue_pop(queue, NULL); + printf("queue capacity=%d, size=%d, dsize=%d\r\n", queue_capacity(queue), queue_size(queue), queue_dsize(queue)); + + _queue(queue); +} +``` +结果: +``` +queue capacity=10, size=8, dsize=4 +``` + +### queue数据的读写 +```c +void* queue_data(queue_t queue, int index); +#define queue_at(queue, type, i) +``` +`queue_data`方法就是根据索引来获取数据的地址,返回的则是指定的数据的地址,NULL则是失败。而`queue_at`则是在`queue_data`的基础上加多类型。 + +```c +void test(void) +{ + queue_t queue = queue(int, 10); + int i = 0; + + for (i = 0; i < queue_capacity(queue); i++) + { + queue_push(queue, &i); + } + queue_pop(queue, NULL); + queue_pop(queue, NULL); + for (i = 0; i < queue_size(queue); i++) + { + printf("queue[%d] = %d\r\n", i, queue_at(queue, int, i)); + } + + _queue(queue); +} +``` +结果: +``` +queue[0] = 2 +queue[1] = 3 +queue[2] = 4 +queue[3] = 5 +queue[4] = 6 +queue[5] = 7 +queue[6] = 8 +queue[7] = 9 +``` + +### queue数据存储索引 + +```c +int queue_index(queue_t queue, int index); +``` +这个队列存储结构为环形队列,也就是在连续地址的存储空间首尾相接形成环形,队列数据进出就在这个环形中进行。如此,队列的索引并不直接是缓冲区的索引,`queue_index`方法就是将队列的索引对照为缓冲区的索引,失败返回-1。 +一般情况下,这个方法应用不多,也是在`queue_create`方法是传入了`base`,在`base`地址上来使用`queue_index`获取队列数据。 + +### queue空队和满队 +```c +int queue_empty(queue_t queue); +int queue_full(queue_t queue); +``` +这两个方法实际就是queue的`size`的大小关系,等于0为空,等于容量则满。 + +## 源码解析 + +### queue结构体 + +queue容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致queue存储结构的破坏。所以queue解析器只留了唯一一个queue的声明在头文件,然后结构体的定义都在源文件。只能使用queue容器提供的方法对queue对象进行操作。 +queue类型声明 +```c +typedef struct QUEUE *queue_t; +``` +使用时候,只是用`queue_t`即可。 + +```c +typedef struct QUEUE +{ + void* base; /* base address of data */ + int cst; /* base const */ + int dsize; /* size of queue data */ + int capacity; /* capacity of queue */ + int size; /* size of queue */ + int head; /* index of queue head */ + int tail; /* index of queue tail */ +} QUEUE; +``` +`QUEUE`结构体中包含了7个成员,`base`(队列结构数据缓冲区的基地址),`cst`(指示base的空间是否是create方法时候传进来),`size`(queue的大小,也就是queue的长度),`dsize`(每个数据的大小),`capacity`(队列的容量),`head`和`tail`分别是环形缓冲区,队头和队尾所指向的索引。 + +queue容器最主要的问题就是解决环形队列,数据先进先出的问题,其他创建删除是完成空间的初始化等基本初始化操作。 + +```c +#define at(i) (((unsigned char *)(queue->base))+(i)*(queue->dsize)) /* address of void array */ +int queue_push(queue_t queue, void* data) +{ + if (!queue) return 0; + if (queue_full(queue)) return 0; // 在入队之前先判断一下队列是否满了 + if (data) memcpy(at(queue->tail), data, queue->dsize); // 如果指定了data地址,就将data地址的数据复制到尾部tail指向的地址 + queue->tail++; // 让tail+1,也就是表明在尾部push了数据 + queue->tail %= queue->capacity; // 让tail对capacity求余,保证tail不会超过capacity,形成环形 + queue->size++; // size + 1 + return 1; +} +int queue_pop(queue_t queue, void* data) +{ + if (!queue) return 0; + if (queue_empty(queue)) return 0; // 在出队之前先判断一下队列是否为空 + if (data) memcpy(data, at(queue->head), queue->dsize); // 如果指定了data地址,就将队头数据复制到data指向的地址 + queue->head++; // 让head+1,也就是表明在头部pop了数据 + queue->head %= queue->capacity; // 让head对capacity求余,保证head不会超过capacity,形成环形 + queue->size--; // size - 1 + return 1; +} +``` \ No newline at end of file diff --git a/doc/varch:set容器.md b/doc/varch:set容器.md new file mode 100644 index 0000000..1a27fd0 --- /dev/null +++ b/doc/varch:set容器.md @@ -0,0 +1,254 @@ +## 介绍 + +set集合是逻辑上离散的容器,与vector、list容器不太相同,set的访问索引是离散的并不一定是来连续的,在插入的时候就进行了排序和索引去重。 +varch的set容器,也是具备的集合的基本属性,在底层实现上采用了**红黑树**,增删操作的效率比vector的高,而且也支持随机访问,随机访问的时间复杂度比list要好很多。同样作为链式结构,set集合也支持迭代器。 + +## 接口 + +### 创建和删除set对象 +```c +set_t set_create(int dsize); +void set_delete(set_t set); +#define set(type) // 为了更简便的使用,对set_create套一层宏定义 +#define _set(set) // 对set_delete套一层宏定义,并在set删除后置为空 +``` +其中**set_t**为set的结构体,创建方法则会返回一个空的set对象,创建失败则返回NULL,其中`dsize`传入数据的大小。删除方法则是删除传入的set对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + set_t set = set(int); // 定义并创建一个int型的set + _set(set); // 成对使用,用完即删除 +} +``` + +### set的插入和移除 +```c +void* set_insert(set_t set, int index, void* data); +int set_erase(set_t set, int index); +``` +set有着较好插入和移除的效率,不用数据移位只需修改链表指向。 +插入的方法是添加指定index并将数据复制到这个索引(在其中,data传入NULL时则只是开辟空间,不进行赋值),在插入index的过程中会进行查重,保证index的唯一性,插入成功后返回插入后的数据的地址,插入失败则是返回NULL。而移除则是移除指定索引的数据,成功返回1,失败返回0。 + +### set数据的读写 +```c +void* set_data(set_t set, int index); +void* set_error(set_t set); +#define set_at(set, type, i) +``` +`set_data`方法就是根据索引来获取数据的地址,返回的则是指定的数据的地址,`set_error()`则是失败。而`set_at`则是在`set_data`的基础上加多类型,`set_data`具备读写保护机制,因为返回的是`set_error()`而不是NULL,所以在使用`set_at`方法`i`写错了就会修改`set_error()`指向的内容,而不会导致奔溃。 +set的随机访问和连续地址的数组或者非连续地址的list都不太一样,数组是可以直接定位到指定索引的地址,而链表要链式一步步指向进行随机访问。set采用了**红黑树**,二分查找就可以很快的访问到指定索引。 + +```c +void test(void) +{ + set_t set = set(int); + int i; + + for (i = 0; i < 100; i++) + { + set_insert(set, i, &i); + } + i = -100; set_insert(set, i, &i); + i = 1024; set_insert(set, i, &i); + + printf("set[6] = %d\r\n", set_at(set, int, 6)); + printf("set[-100] = %d\r\n", set_at(set, int, -100)); + printf("set[1024] = %d\r\n", set_at(set, int, 1024)); + + set_at(set, int, 6) = 11111; + printf("set[6] = %d\r\n", set_at(set, int, 6)); + + _set(set); +} +``` +结果: +``` +set[6] = 6 +set[-100] = -100 +set[1024] = 1024 +set[6] = 11111 +``` + +### set的大小和和数据大小 +```c +int set_size(set_t set); +int set_dsize(set_t set); +``` +set的`size`很好理解,也就是像数组那样的大小,`dsize`也就是创建时候传入的数据的大小。 +```c +void test(void) +{ + set_t set = set(int); + int i; + + for (i = 0; i < 100; i++) + { + set_insert(set, i, &i); + } + printf("size = %d, data size = %d\r\n", set_size(set), set_dsize(set)); + + _set(set); +} +``` +结果: +``` +size = 100, data size = 4 +``` + +### set查找 +```c +int set_find(set_t set, int index); +``` +这个方法其实套`set_data`实现,只是find成功返回1失败返回0。 + +### set迭代器 + +```c +void set_it_init(set_t set, int orgin); +void* set_it_get(set_t set, int *out_index); +``` + +set也支持内置的迭代器,但主要set的迭代器用于遍历。因为向list要遍历的时候是知道索引从0开始逐一递增遍历的。但是set是离散型的index,无法通过这种逐一递增的方式进行遍历,所以这里给定了两个迭代器函数用于遍历set。 +`set_it_init`初始化迭代器,`orgin`指定为`SET_HEAD`或者`SET_TAIL`,就分别是正向迭代和反向迭代。 +`set_it_get`获取迭代,更新迭代位置,`*out_index`为输出的index(当前所在的index,也可以传入NULL不接收),返回迭代位置的数据。 +通过`set_size`来把控迭代次数。 + +```c +void test(void) +{ + set_t set = set(int); + int i, index; + void *data; + + i = -100; set_insert(set, i, &i); + i = 1024; set_insert(set, i, &i); + i = 0; set_insert(set, i, &i); + i = 7; set_insert(set, i, &i); + i = -2; set_insert(set, i, &i); + i = -2; set_insert(set, i, &i); + + set_at(set, int, 3) = 100; + + set_it_init(set, SET_HEAD); + i = set_size(set); + while (i--) + { + data = set_it_get(set, &index); + printf("set[%d] = %d\r\n", index, *(int *)data); + } + + _set(set); +} +``` + +结果: +``` +set[-100] = -100 +set[-2] = -2 +set[0] = 0 +set[7] = 7 +set[1024] = 1024 +``` + +## 源码解析 + +### set结构体 + +set容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致set存储结构的破坏。所以set解析器只留了唯一一个set的声明在头文件,然后结构体的定义都在源文件。只能使用set容器提供的方法对set对象进行操作。 +set类型声明 +```c +typedef struct SET *set_t; +``` +使用时候,只是用`set_t`即可。 + +```c +/* type of set */ +typedef struct SET +{ + NODE* root; /* root node */ + NODE* nil; /* nil node */ + NODE* iterator; /* iterator of set */ + int orgin; /* iterator orgin */ + int size; /* set size */ + int dsize; /* data size */ +} SET; +``` +`SET`结构体中包含了6个成员,root(红黑树根节点),nil(红黑树nil结点),iterator(迭代器当前指向的结点),orgin(迭代器的起始位置),size(set的大小,也就是set红黑树的结点总数),dsize(每个数据的大小)。 +```c +/* set node type define */ +typedef struct NODE +{ + struct NODE *parent; + struct NODE *left; + struct NODE *right; + int color; + int index; +} NODE; +#define data(node) ((node)+1) /* data of node */ +``` +在`NODE`结构体当中,显式的成员包含形成树结构的`parent`、`left`、`right`指向结点,红黑树的结点颜色`color`,索引`index`。数据域就和`list`的数据域一样,跟在node空间的后面,动态分配大小。 + +### 红黑树 + +红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。 +set的存储结构就完全是红黑树,这里不细说红黑树了。 + +### 迭代器 + +前面说到`set`内置了迭代器,那这个迭代器是怎么回事呢? +看下源码: +```c +void set_it_init(set_t set, int orgin) +{ + if (!set) return; + set->orgin = (orgin==SET_HEAD)?SET_HEAD:SET_TAIL; + set->iterator = (set->orgin==SET_HEAD)?(NODE*)node_min(set, set->root):(NODE*)node_max(set, set->root); // 根据起始点,将迭代器迭代到根节点的最小结点或者最大结点 +} +void* set_it_get(set_t set, int *out_index) +{ + NODE *node; + if (!set) return NULL; + node = set->iterator; + set->iterator = (set->orgin==SET_HEAD)?node_next(set, set->iterator):node_prev(set, set->iterator); // 根据迭代方向,选择往下一个迭代还是上一个迭代 + if (out_index) *out_index = node->index; // 输出迭代的index + return data(node); +} +``` +而在红黑树中,node的prev和next是怎么实现的呢? +红黑树中,当前结点比其左节点的所有结点都大,比右节点的任意结点都小 +所以在获取下一个结点时候,就是找比当前结点大的最小的结点,那么寻找的优先级就有如下: +1、右分支的结点,而在看右分支的结点的时候,右分支的最小结点就在右分支的最左边 +2、父节点,看父节点也要区分当前结点是父节点的左结点还是右节点,要是父节点的左节点的话,那父节点就比当前结点大,就直接返回父节点;要是父节点的右结点的话,那父节点是比当前结点要小的,所以要返回"爷结点"才比当前结点大并且是最小的。 +同理,反过来就是prev的逻辑 +怎么结束迭代呢?就是通过set的size控制迭代的次数就行了,一直迭代下去的话,最终就是一直在nil的位置。 +```c +static NODE* node_next(set_t set, NODE* node) +{ + if (node->right != set->nil) // 有右分支 + { + node = node->right; // 先到右节点 + node = node_min(set, node); // 再到右分支的最左边(最小结点) + } + else // 没有右分支 + { + if (node == node->parent->left) node = node->parent; // 当前结点是父结点的节点,返回父节点就OK了 + else node = node->parent->parent; // 当前结点是父节点的右结点,就需要返回“爷结点” + } + return node; +} + +static NODE* node_prev(set_t set, NODE* node) +{ + if (node->left != set->nil) + { + node = node->left; + node = node_max(set, node); + } + else + { + if (node == node->parent->right) node = node->parent; + else node = node->parent->parent; + } + return node; +} +``` diff --git a/doc/varch:sort算法.md b/doc/varch:sort算法.md new file mode 100644 index 0000000..7ed547e --- /dev/null +++ b/doc/varch:sort算法.md @@ -0,0 +1,372 @@ +## 介绍 + +排序(Sorting) 是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个关键字有序的序列。排序就是把集合中的元素按照一定的次序排序在一起,一般来说有升序排列和降序排列2种排序。 + +排序算法的逻辑是固定的,所要排序的数据集结构和数据项结构会不同,当针对每种数据集结构或者数据项结构都写一套排序算法函数,那无疑会增加冗余的代码。 + +varch提供sort模块就为了解决这个问题,把算法模型所需的数据集和数据项类型的不同进行抽象化,只要针对不同数据以及排序规则提供对应的操作函数,就可以统一调用排序算法进行排序操作。 + +## 接口 + +```c +int sort_bubble(void *array, int begin, int end, SOPS* ops); +int sort_select(void *array, int begin, int end, SOPS* ops); +int sort_insert(void *array, int begin, int end, SOPS* ops); +int sort_shell(void *array, int begin, int end, SOPS* ops); +int sort_quick(void *array, int begin, int end, SOPS* ops); +int sort_heap(void *array, int begin, int end, SOPS* ops); +``` +这里给出了6种常用的排序法,分别是冒泡排序、选择排序、插入排序、希尔排序、快速排序、堆排序,这几个排序法在使用上一致。 +`array`传入数组或者其他任意的数据容器,`begin`和`end`指定需要排序的区间,`ops`则是指定排序所需的一些函数操作集。 + +```c +/* Sorting algorithm operation function set structure definition */ +typedef struct +{ + /** + * \brief ordering rules, e.g front < back ascending and front > back descending + * \param[in] front: address of front data + * \param[in] back: address of back data + * \return positive: following sorting rules + * negative: violating sorting rules + * 0: does't affect sorting rules + */ + int (*order)(void *front, void *back); + + /** + * \brief get the address of the space where the element is located + * \param[in] array: data handle + * \param[in] index: item data index + * \return address of item data + */ + void* (*addr)(void *array, int index); + + /** + * \brief Swap data between two spaces + * \param[in] array: data handle + * \param[in] index0: item data index0 + * \param[in] index1: item data index1 + * \return none + */ + void (*swap)(void *array, int index0, int index1); + +} SOPS; +``` +首先需要在`ops`中`order`指定排序的规则(升序还是降序),`front`的项大于`back`的项,那就是降序的规则,反之亦然。 +因为`array`支持任意类型的数据结构,所以要在`ops`中`addr`成员指定获取特定数据结构项的指定成员地址,比如`char`数组和`int`数组返回相应地址不一样。 +排序就涉及数据的位置交换,`ops`的`swap`成员就指定交换数据的方法(因为并不是所有数据结构的交换数据都是直接交换亮着数据的内容,比如链表就是交换数据指向就可以了)。 + +常用的数据结构排序的`ops`已经封装好了,根据类型选择实际`ops`即可。 +```c +extern SOPS sops_char_ascend; +extern SOPS sops_char_descend; +extern SOPS sops_uchar_ascend; +extern SOPS sops_uchar_descend; +extern SOPS sops_short_ascend; +extern SOPS sops_short_descend; +extern SOPS sops_ushort_ascend; +extern SOPS sops_ushort_descend; +extern SOPS sops_int_ascend; +extern SOPS sops_int_descend; +extern SOPS sops_uint_ascend; +extern SOPS sops_uint_descend; +extern SOPS sops_float_ascend; +extern SOPS sops_float_descend; +extern SOPS sops_double_ascend; +extern SOPS sops_double_descend; +``` + +## 实测例子 + +### 基本升降序用法 + +针对基本数据类型的升降序排序,以以冒泡排序算法举例。 + +```c +static void test_basic_usage(void) +{ + /* 针对int型数组,进行升序 + */ + int array_int[] = {1, 3, 6, 5, 0, 2, 9, 8, 7, -4}; // 定义时候任意乱序 + + // 传入数组基地址,排序的区间,指定int型的升序ops + sort_bubble(array_int, 0, sizeof(array_int)/sizeof(array_int[0]) - 1, &sops_int_ascend); + + // 输出排序后的数组 + for (int i = 0; i < sizeof(array_int)/sizeof(array_int[0]); i++) + { + printf("%d, ", array_int[i]); + } + printf("\r\n"); + + /* ---------------------------------------- 代码分割线 ---------------------------------------- */ + + /* 针对float型数组,进行降序 + */ + float array_float[] = {-1.1, 2.1, 6.6, 5.0, 0.1, 2.8, 9.9, 9.8, 7.0, -4.5}; // 定义时候任意乱序 + + // 传入数组基地址,排序的区间,指定float型的降序ops + sort_bubble(array_float, 0, sizeof(array_float)/sizeof(array_float[0]) - 1, &sops_float_descend); + + // 输出排序后的数组 + for (int i = 0; i < sizeof(array_float)/sizeof(array_float[0]); i++) + { + printf("%.2f, ", array_float[i]); + } + printf("\r\n"); +} +``` + +测试结果: + +``` +-4, 0, 1, 2, 3, 5, 6, 7, 8, 9, +9.90, 9.80, 7.00, 6.60, 5.00, 2.80, 2.10, 0.10, -1.10, -4.50, +``` + +### 针对指定区间进行排序 + +除了一般常用的全区间进行排序,还可以指定某段区间内进行。 + +```c +static void test_interval(void) +{ + int array_int[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 先定义升序的数组 + + // 传入数组基地址,指定在[3, 7]区间内进行排序,指定int型的降序ops + sort_bubble(array_int, 3, 7, &sops_int_descend); + + // 输出排序后的数组 + for (int i = 0; i < sizeof(array_int)/sizeof(array_int[0]); i++) + { + printf("%d, ", array_int[i]); + } + printf("\r\n"); +} +``` + +测试结果: + +``` +0, 1, 2, 7, 6, 5, 4, 3, 8, 9, +``` + +### 针对不是数组的其他容器排序 + +下面以list容器为例子,测试int型list升序排序。 +这个测试代码依赖`list`容器。 + +```c +// 获取list数据项地址 +static void* list_int_addr(void *array, int index) +{ + return list_data((list_t)array, index); +} + +// 交换list两个成员数据 +static void list_int_swap(void *array, int index0, int index1) +{ + int temp = list_at((list_t)array, int, index0); + list_at((list_t)array, int, index0) = list_at((list_t)array, int, index1); + list_at((list_t)array, int, index1) = temp; +} + +// 显示list存储的数据 +static void list_int_show(list_t list) +{ + for (int i = 0; i < list_size(list); i++) + { + printf("%d, ", list_at(list, int, i)); + } + printf("\r\n"); +} + +static void test_list_sort(void) +{ + list_t list = list(int); // 定义一个int型的list + SOPS ops; // 定义一个新的ops + int array[] = {1, 3, 6, 5, 0, 2, 9, 8, 7, -4}; // 定义要给list装载的数据 + + // 将数据装载到list + for (int i = 0; i < sizeof(array)/sizeof(array[0]); i++) + { + list_push_back(list, &array[i]); + } + + ops.order = sops_int_ascend.order; // 复用int升序规则 + ops.addr = list_int_addr; // 使用list获取元素的地址 + ops.swap = list_int_swap; // list交换数据 + + // 输出排序前的list + list_int_show(list); + + // 进行排序 + sort_bubble(list, 0, list_size(list) - 1, &ops); + + // 输出排序后的list + list_int_show(list); + + // 释放list + _list(list); +} +``` + +测试结果: + +``` +1, 3, 6, 5, 0, 2, 9, 8, 7, -4, +-4, 0, 1, 2, 3, 5, 6, 7, 8, 9, +``` + +### 针对自定义数据结构进行排序 + +前面提供的例子都是基础数据类型的,接下来的例子是针对自定义数据类型的,比如结构体,根据结构体的某些成员项进行排序。 + +```c +typedef struct +{ + char *name; + int age; + char gender; /* 1 表示男,0 表示女 */ + float weight; +} STUDENT; + +static void student_print(STUDENT *s, int size) +{ + for (int i = 0; i < size; i++) + { + printf("name: %s, \t\t age = %d, \t\t gender = %d, \t\t weight = %.2f\r\n", s[i].name, s[i].age, s[i].gender, s[i].weight); + } +} + +static void* student_addr(void *array, int index) +{ + return ((STUDENT *)array) + index; +} + +static void student_swap(void *array, int index0, int index1) +{ + STUDENT temp = ((STUDENT *)array)[index0]; + ((STUDENT *)array)[index0] = ((STUDENT *)array)[index1]; + ((STUDENT *)array)[index1] = temp; +} + +static int student_name_ascend(void *front, void *back) +{ + return -strcmp(((STUDENT *)front)->name, ((STUDENT *)back)->name); +} + +static int student_gender_descend(void *front, void *back) +{ + if (((STUDENT *)front)->gender > ((STUDENT *)back)->gender) return 1; + else if (((STUDENT *)front)->gender < ((STUDENT *)back)->gender) return -1; + else return 0; +} + +static int student_age_descend(void *front, void *back) +{ + if (((STUDENT *)front)->age > ((STUDENT *)back)->age) return 1; + else if (((STUDENT *)front)->age < ((STUDENT *)back)->age) return -1; + else return 0; +} + +static int student_weight_ascend(void *front, void *back) +{ + if (((STUDENT *)front)->weight < ((STUDENT *)back)->weight) return 1; + else if (((STUDENT *)front)->weight > ((STUDENT *)back)->weight) return -1; + else return 0; +} + +static void test_struct(void) +{ + SOPS ops; + STUDENT table[] = { + {"ZhanSan", 18, 1, 56.3}, + {"LiSi", 17, 1, 62.2}, + {"WangWu", 21, 0, 58.9}, + {"ZhaoLiu", 22, 1, 65.5}, + {"SunQi", 18, 1, 71.7}, + {"ZhouBa", 30, 0, 48.8}, + {"WuJiu", 16, 1, 56.3}, + {"ZhenShi", 19, 0, 52.1}, + }; + + ops.addr = student_addr; + ops.swap = student_swap; + + // 指定为按照名字升序排序 + ops.order = student_name_ascend; + sort_bubble(table, 0, sizeof(table)/sizeof(table[0])-1, &ops); + printf("sorting by name in ascending order\r\n"); + student_print(table, sizeof(table)/sizeof(table[0])); + printf("---------------------------------\r\n"); + + // 指定为按照年龄降序排序 + ops.order = student_age_descend; + sort_bubble(table, 0, sizeof(table)/sizeof(table[0])-1, &ops); + printf("sorting by age in descending order\r\n"); + student_print(table, sizeof(table)/sizeof(table[0])); + printf("---------------------------------\r\n"); + + // 指定为按照性别降序排序 + ops.order = student_gender_descend; + sort_bubble(table, 0, sizeof(table)/sizeof(table[0])-1, &ops); + printf("sorting by gender in descending order\r\n"); + student_print(table, sizeof(table)/sizeof(table[0])); + printf("---------------------------------\r\n"); + + // 指定为按照体重升序排序 + ops.order = student_weight_ascend; + sort_bubble(table, 0, sizeof(table)/sizeof(table[0])-1, &ops); + printf("sorting by weight in ascending order\r\n"); + student_print(table, sizeof(table)/sizeof(table[0])); + printf("---------------------------------\r\n"); +} +``` + +测试结果: + +``` +sorting by name in ascending order +name: LiSi, age = 17, gender = 1, weight = 62.20 +name: SunQi, age = 18, gender = 1, weight = 71.70 +name: WangWu, age = 21, gender = 0, weight = 58.90 +name: WuJiu, age = 16, gender = 1, weight = 56.30 +name: ZhanSan, age = 18, gender = 1, weight = 56.30 +name: ZhaoLiu, age = 22, gender = 1, weight = 65.50 +name: ZhenShi, age = 19, gender = 0, weight = 52.10 +name: ZhouBa, age = 30, gender = 0, weight = 48.80 +--------------------------------- +sorting by age in descending order +name: ZhouBa, age = 30, gender = 0, weight = 48.80 +name: ZhaoLiu, age = 22, gender = 1, weight = 65.50 +name: WangWu, age = 21, gender = 0, weight = 58.90 +name: ZhenShi, age = 19, gender = 0, weight = 52.10 +name: ZhanSan, age = 18, gender = 1, weight = 56.30 +name: SunQi, age = 18, gender = 1, weight = 71.70 +name: LiSi, age = 17, gender = 1, weight = 62.20 +name: WuJiu, age = 16, gender = 1, weight = 56.30 +--------------------------------- +sorting by gender in descending order +name: ZhaoLiu, age = 22, gender = 1, weight = 65.50 +name: ZhanSan, age = 18, gender = 1, weight = 56.30 +name: SunQi, age = 18, gender = 1, weight = 71.70 +name: LiSi, age = 17, gender = 1, weight = 62.20 +name: WuJiu, age = 16, gender = 1, weight = 56.30 +name: WangWu, age = 21, gender = 0, weight = 58.90 +name: ZhenShi, age = 19, gender = 0, weight = 52.10 +name: ZhouBa, age = 30, gender = 0, weight = 48.80 +--------------------------------- +sorting by weight in ascending order +name: ZhouBa, age = 30, gender = 0, weight = 48.80 +name: ZhenShi, age = 19, gender = 0, weight = 52.10 +name: ZhanSan, age = 18, gender = 1, weight = 56.30 +name: WuJiu, age = 16, gender = 1, weight = 56.30 +name: WangWu, age = 21, gender = 0, weight = 58.90 +name: LiSi, age = 17, gender = 1, weight = 62.20 +name: ZhaoLiu, age = 22, gender = 1, weight = 65.50 +name: SunQi, age = 18, gender = 1, weight = 71.70 +--------------------------------- +``` + + diff --git a/doc/varch:stack容器.md b/doc/varch:stack容器.md new file mode 100644 index 0000000..2d500db --- /dev/null +++ b/doc/varch:stack容器.md @@ -0,0 +1,177 @@ +## 介绍 + +栈是一种先进后出(First In Last Out,FILO)的数据结构,一般情况下只有一个出入口,从栈顶进从栈顶出。入栈push,出栈pop。 +varch的堆栈和队列实现逻辑很类似,队列有两个端口,循环模式数据在指定空间内循环的存储,而堆栈只有一个端口,一般情况从栈顶出入栈,栈底数据在指定空间的低地址段而栈顶数据在高地址端。 +* 容量 +容量也就是在使用过程中最多能存多少个栈数据,比如,容量为10的栈,最多能存10个栈数据,存满后,再想入栈就入不了。varch的栈存储是连续地址的,是有限容量的栈。 + + +## 接口 + +### 创建和删除stack对象 +```c +stack_t stack_create(int dsize, int capacity, void *base); +void stack_delete(stack_t stack); +#define stack(type, capacity) // 为了更简便的使用,对stack_create套一层宏定义 +#define _stack(stack) // 对stack_delete套一层宏定义,并在stack删除后置为空 +``` +其中**stack_t**为stack的结构体,创建方法则会返回一个stack对象,创建失败则返回NULL,其中`dsize`传入数据的大小,`capacity`传入栈容量,`*base`传入缓冲区地址(可以不传入,不传入的话就会自动分配capacity大小的空间以来存储栈数据)。删除方法则是删除传入的stack对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + stack_t stack = stack(int, 10); // 定义并创建一个int型容量为10的stack + _stack(stack); // 成对使用,用完即删除 +} +``` + +### stack的入栈和出栈 +```c +int stack_push(stack_t stack, void* data); +int stack_pop(stack_t stack, void* data); +``` +这两个方法可以很方便的把数据添加到栈和从栈弹出,`push`方法`data`传入需要入队数据的地址,`pop`方法`data`传入需要接收出队数据的地址,这两个方法`data`都可以传入NULL,那只是一个占位。操作成功返回1,失败返回0。 +```c +void test(void) +{ + stack_t stack = stack(int, 10); + int i = 0; + + for (i = 0; i < stack_capacity(stack); i++) + { + stack_push(stack, &i); + } + stack_pop(stack, NULL); + stack_pop(stack, NULL); + + _stack(stack); // 成对使用,用完即删除 +} +``` + + +### stack的大小、容量和数据大小 +```c +int stack_size(stack_t stack); +int stack_capacity(stack_t stack); +int stack_dsize(stack_t stack); +``` +stack的`capacity`就是创建时候指定的容量,能存多少个栈元素,`size`是栈有多少个元素,`dsize`也就是创建时候传入的数据的大小,比如`int`,`dsize`就是`sizeof(int)`。 +```c +void test(void) +{ + stack_t stack = stack(int, 10); + int i = 0; + + for (i = 0; i < stack_capacity(stack); i++) + { + stack_push(stack, &i); + } + stack_pop(stack, NULL); + stack_pop(stack, NULL); + printf("stack capacity=%d, size=%d, dsize=%d\r\n", stack_capacity(stack), stack_size(stack), stack_dsize(stack)); + + _stack(stack); +} +``` +结果: +``` +stack capacity=10, size=8, dsize=4 +``` + +### stack数据的读写 +```c +void* stack_data(stack_t stack, int index); +#define stack_at(stack, type, i) +``` +`stack_data`方法就是根据索引来获取数据的地址,返回的则是指定的数据的地址,NULL则是失败。而`stack_at`则是在`stack_data`的基础上加多类型。 + +```c +void test(void) +{ + stack_t stack = stack(int, 10); + int i = 0; + + for (i = 0; i < stack_capacity(stack); i++) + { + stack_push(stack, &i); + } + stack_pop(stack, NULL); + stack_pop(stack, NULL); + for (i = 0; i < stack_size(stack); i++) + { + printf("stack[%d] = %d\r\n", i, stack_at(stack, int, i)); + } + + _stack(stack); +} +``` +结果: +``` +stack[0] = 0 +stack[1] = 1 +stack[2] = 2 +stack[3] = 3 +stack[4] = 4 +stack[5] = 5 +stack[6] = 6 +stack[7] = 7 +``` + +### stack数据存储索引 + +```c +#define stack_index(stack, index) +``` +其实`stack_index`实际就是与`index`相对应,超出范围失败返回-1。 +一般情况下,这个方法应用不多,也是在`stack_create`方法是传入了`base`,在`base`地址上来使用`stack_index`获取栈数据。 + +### stack空栈和满栈 +```c +int stack_empty(stack_t stack); +int stack_full(stack_t stack); +``` +这两个方法实际就是stack的`size`的大小关系,等于0为空,等于容量则满。 + +## 源码解析 + +### stack结构体 + +stack容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致stack存储结构的破坏。所以stack解析器只留了唯一一个stack的声明在头文件,然后结构体的定义都在源文件。只能使用stack容器提供的方法对stack对象进行操作。 +stack类型声明 +```c +typedef struct STACK *stack_t; +``` +使用时候,只是用`stack_t`即可。 + +```c +typedef struct STACK +{ + void* base; /* base address of data */ + int cst; /* base const */ + int dsize; /* size of stack data */ + int capacity; /* capacity of stack */ + int top; /* index of stack top */ +} STACK; +``` +`STACK`结构体中包含了5个成员,`base`(栈结构数据缓冲区的基地址),`cst`(指示base的空间是否是create方法时候传进来),`dsize`(每个数据的大小),`capacity`(栈的容量),`top`(指示下一个栈数据索引,也就是`top`就表示`size`)。 + +stack容器最主要的问题就是解决数据先进后出的问题,其他创建删除是完成空间的初始化等基本初始化操作。 + +```c +#define at(i) (((unsigned char *)(stack->base))+(i)*(stack->dsize)) /* address of void array */ +int stack_push(stack_t stack, void* data) +{ + if (!stack) return 0; + if (stack_full(stack)) return 0; // 入栈之前先判断栈是否满了 + if (data) memcpy(at(stack->top), data, stack->dsize); // 将数据复制到栈顶 + stack->top++; // 栈顶增加 + return 1; +} +int stack_pop(stack_t stack, void* data) +{ + if (!stack) return 0; + if (stack_empty(stack)) return 0; // 出栈之前先判断栈是否为空 + stack->top--; // 因为top指向下一个数据的索引,所以这里要先减1了 + if (data) memcpy(data, at(stack->top), stack->dsize); // 再将数据复制出去 + return 1; +} +``` diff --git a/doc/varch:str字符串变量容器.md b/doc/varch:str字符串变量容器.md new file mode 100644 index 0000000..5060cf8 --- /dev/null +++ b/doc/varch:str字符串变量容器.md @@ -0,0 +1,874 @@ +## 介绍 + +字符串实际是一串字符的集合,在C语言中,字符串是为char类型的数组,在结尾加上结束符`\0`则为字符串,没有结束符则为字符数组。 +在C语言中,没有专门的字符串变量,而varch中的str则专门针对数组字符串进行了封装,封装成不需要关注底层存储结构的字符串变量。str实际还是通过连续地址的空间进行存储字符集,但是这些存储的工作不需要额外去关注,str提供的方法已经实现存储的工作,而且str也提供符合字符串操作的方法,方便字符串的灵活操作。方便说明,后面的传统的C语言字符串称为数组字符串,varch提供的成为str字符串。 + +## 接口 + +### 创建和删除str字符串对象 +```c +str_t str_create(void *string); +void str_delete(str_t str); +#define str(str) // 为了更简便的使用,对str_create套一层宏定义 +#define _str(str) // 对str_delete套一层宏定义,并在str删除后置为空 +``` +其中**str_t**为str的结构体,创建方法则会返回一个str对象,创建失败则返回NULL,其中`string`传入初始化的字符串,这个`string`定义为了`void *`的类型,所以可以支持数组字符串和str字符串的两种传参初始化(后续介绍中的`void *string`均是可以支持这两种字符串)。删除方法则是删除传入的str对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + str_t ss = str("Hello str!"); // 用数组字符串进行赋值构造 + str_t copy = str(ss); // 用str字符串进行拷贝构造 + _str(ss); // 删除str + _str(copy); // 删除str +} +``` + +### str字符串的数组字符串 +```c +#define str_c_str(str) +#define _S(str) +``` +str存储字符串也是通过连续地址空间进行存储,获取此地址即可访问到实际存储的字符串。但在使用中,建议不要通过此方法把字符串当成数组字符串来修改。有同样作用的方法还有`char* str_data(str_t str, int pos);`,`str_c_str()`也是套用`str_data`实现的,此方法不建议替代`str_c_str()`来用。而`_S()`则是简短版的。 +```c +void test(void) +{ + str_t ss = str("Hello str!"); // 用数组字符串进行赋值构造 + str_t copy = str(ss); // 用str字符串进行拷贝构造 + printf("ss: %s\r\n", _S(ss)); + printf("copy: %s\r\n", _S(copy)); + _str(ss); // 删除str + _str(copy); // 删除str +} +``` +结果: +``` +ss: Hello str! +copy: Hello str! +``` + +### str赋值 +```c +str_t str_assign(str_t str, void *string); +``` +str抽象字符串为一个类了,给str进行赋值操作,类似于=号的赋值操作。如同前面所说的`void *string`可以支持数组字符串和str字符串。 +```c +void test(void) +{ + str_t ss = str(""); // 初始化为空字符串 + printf("ss: %s\r\n", _S(ss)); + str_assign(ss, "Hello str!"); // 赋值为 Hello str! + printf("ss: %s\r\n", _S(ss)); + str_assign(ss, "This is the assignment method!"); // 再赋值为 This is the assignment method! + printf("ss: %s\r\n", _S(ss)); + _str(ss); +} +``` +结果: +``` +ss: +ss: Hello str! +ss: This is the assignment method! +``` +从中可以看到str不需要像数组字符串那样去关注定义的空间的大小问题,而由str内部去进行适应。 + +### str拼接 +```c +#define str_append(str, ...) +``` +str除了有类似`=`号的赋值操作,还有类似`+=`号的拼接操作。 +形参后面不定参数,**可以拼接至少一个任意数量的字符串**。同样,可以是数组字符串也可以是str字符串。 +`str_append`方法的原型是`str_t str_append_series(str_t str, ...);`,原型函数不定参数需要接`NULL`结尾,不建议使用原型方法。 +```c +void test(void) +{ + str_t name = str("123456789"); + str_t ident = str("qq"); + str_t email = str(""); + + str_append(email, name, "@", ident, ".com"); // 拼接若干个字符串 + printf("%s\r\n", str_c_str(email)); + + _str(name); + _str(ident); + _str(email); +} +``` +结果: +``` +123456789@qq.com +``` + +### str插入 +```c +str_t str_insert(str_t str, int pos, void *string); +``` +在str中插入一个字符串,并返回自己。`pos`是插入的位置,`string`是插入的源字符串。 +```c +void test(void) +{ + str_t ss = str("0123456"); + str_insert(ss, 3, "|insert|"); // 在位置3插入字符串 + printf("ss: %s\r\n", str_c_str(ss)); + _str(ss); +} +``` +结果: +``` +ss: 012|insert|3456 +``` + +### str移除 +```c +str_t str_erase(str_t str, int pos, int len); +``` +从str中移除一段字符,并返回自己。`pos`是移除的位置,`len`是移除的长度。 +```c +void test(void) +{ + str_t ss = str("0123456789"); + str_erase(ss, 3, 3); // 移除位置3后面三个字符,也就是移除 345 + printf("ss: %s\r\n", str_c_str(ss)); + _str(ss); +} +``` +结果: +``` +ss: 0126789 +``` + +### str尾部推入和弹出 +```c +int str_push_back(str_t str, char c); +char str_pop_back(str_t str); +``` +这两个方法分别从str字符串尾部推入和弹出一个字符,推入方法的返回值1成功0失败,弹出方法返回弹出的字符。 +`str`存储结构和`vector`类似,在尾部操作具有比较高的效率。 +```c +void test(void) +{ + str_t ss = str("0123456789"); + str_push_back(ss, 'A'); + printf("ss: %s\r\n", str_c_str(ss)); + printf("pop %c\r\n", str_pop_back(ss)); + printf("ss: %s\r\n", str_c_str(ss)); + _str(ss); +} +``` +结果: +``` +ss: 0123456789A +pop A +ss: 0123456789 +``` + +### str字符串比较 +```c +int str_compare(str_t str, void *string); +``` +比较str与另外一个字符串,0为相等,-1为str小于string,1为str大于string。 +```c +void test(void) +{ + str_t ss = str("01234"); + str_t copy = str(ss); + printf("compare: %d\r\n", str_compare(ss, copy)); + printf("compare: %d\r\n", str_compare(ss, "01233")); + printf("compare: %d\r\n", str_compare(ss, "012345")); + _str(ss); + _str(copy); +} +``` +结果: +``` +compare: 0 +compare: 1 +compare: -1 +``` + +### str创建子串 +```c +str_t str_substr(str_t str, int pos, int len); +``` +这个方法是从str中创建一个新的子串,**切记,在结束使用后,调用`_str(str)`方法来删除**。。 +`pos`传入起始位置,`len`传入创建的长度。 +```c +void test(void) +{ + str_t ss = str("0123456789"); + str_t sub = str_substr(ss, 4, 2); + printf("ss: %s\r\n", str_c_str(ss)); + printf("sub: %s\r\n", str_c_str(sub)); + _str(ss); + _str(sub); +} +``` +结果: +``` +ss: 0123456789 +sub: 45 +``` + +### str字符串长度 +```c +int str_length(str_t str); +``` +str标准的获取长度的方法是这个,当然也可以用`strlen`方法去量,但是对于`str`对象没必要,因为这个标准的方法是不用耗费时间去量的,返回记录好了的长度。 +```c +void test(void) +{ + str_t ss = str("0123456789"); + printf("length: %d\r\n", str_length(ss)); + _str(ss); +} +``` +结果: +``` +length: 10 +``` + +### str字符读写 +```c +#define str_at(str, i) +``` +str提供了针对其中的字符进行读写操作,当作数组的元素使用即可。 +但切记,不要把字符修改成`\0`了。 +```c +void test(void) +{ + str_t ss = str("0123456789"); + printf("ss[3] = %c\r\n", str_at(ss, 3)); + str_at(ss, 3) = 'A'; + printf("ss: %s\r\n", _S(ss)); + _str(ss); +} +``` +结果: +``` +ss[3] = 3 +ss: 012A456789 +``` + +### str字符串查找 +```c +int str_find(str_t str, void *string, int pos); +int str_rfind(str_t str, void *string, int pos); +``` +这两个方法分别从正向和反向查找字符子串,`string`传入字符串,`pos`传入开始查找的起始索引,返回则是返回子串所在的位置索引,没找到就返回`str_npos`。 +```c +void test(void) +{ + str_t ss = str("0123456789"); + printf("find = %d\r\n", str_find(ss, "3456", 0)); // 从0位置开始找 + printf("rfind = %d\r\n", str_rfind(ss, "23", str_length(ss) - 1)); // 从尾端开始往回找 + printf("find = %d\r\n", str_find(ss, "0000", 0)); // 找一个不存在的字符串 + _str(ss); +} +``` +结果: +``` +find = 3 +rfind = 2 +find = 2147483647 +``` + +### str查找匹配字符集 +```c +int str_find_first_of(str_t str, void *string, int pos); +int str_find_first_not_of(str_t str, void *string, int pos); +int str_find_last_of(str_t str, void *string, int pos); +int str_find_last_not_of(str_t str, void *string, int pos); +``` +这四个方法和`str_find`、`str_rfind`方法不一样,find和rfind需要匹配全部字符,而这四个方法只要匹配上字符集的任意之一就匹配成功。 +同样,`string`传入字符串(匹配字符集),`pos`传入开始查找的起始索引,返回则是返回子串所在的位置索引,没找到就返回`str_npos`。这四个函数分别找到**“第一个包含”**、**“第一个不包含”**、**“最后一个包含”**、**“最后一个不包含”** +例子:实际应用获取一个文件路径的文件和所在盘符 +```c +void test(void) +{ + str_t ss = str("C:/workspace/project/C/00 - vlib/Project/main.c"); // 先给定一个文件路径 + str_t pan = NULL; // 初始化为NULL,不构建 + str_t filename = NULL; // 初始化为NULL,不构建 + pan = str_substr(ss, 0, str_find_first_of(ss, ":", 0)); // 先找到第一个 : + filename = str_substr(ss, str_find_last_of(ss, "\\/", str_length(ss)-1) + 1, str_length(ss)); // 找到最后一个 \ 或者 / 路径分隔符 + printf("pan: %s\r\n", str_c_str(pan)); + printf("filename: %s\r\n", str_c_str(filename)); + _str(ss); + _str(pan); + _str(filename); +} +``` +结果: +``` +pan: C +filename: main.c +``` + +### str字符串翻转 +```c +str_t str_reverse(str_t str, int begin, int end); +``` +这个方法将str指定区间的字符串整体翻转。`begin`为区间起始位置,`end`为区间结束位置。 +```c +void test(void) +{ + str_t ss = str("0123456789"); + str_reverse(ss, 2, 8); + printf("ss = %s\r\n", str_c_str(ss)); + _str(ss); +} +``` +结果: +``` +ss = 0187654329 +``` + +### str字符替换 +```c +str_t str_replace(str_t str, int pos, int len, void *string); +``` +这个方法把指定位置的字符按长度替换成指定的字符串。 +```c +void test(void) +{ + str_t ss = str("My name is ZhangSan!"); + printf("ss = %s\r\n", str_c_str(ss)); + str_replace(ss, 11, 8, "Lisi"); // 把ZhangSan替换成Lisi + printf("ss = %s\r\n", str_c_str(ss)); + _str(ss); +} +``` +结果: +``` +ss = My name is ZhangSan! +ss = My name is Lisi! +``` + +### str字符串交换 +```c +void str_swap(str_t str, str_t swap); +``` +将两个字符串进行交换。 +```c +void test(void) +{ + str_t s1 = str("This s1!"); + str_t s2 = str("This s2!"); + str_swap(s1, s2); + printf("s1 = %s\r\n", str_c_str(s1)); + printf("s2 = %s\r\n", str_c_str(s2)); + _str(s1); + _str(s2); +} +``` +结果: +``` +s1 = This s2! +s2 = This s1! +``` + +### str字符串复制到数组 +```c +int str_copy(str_t str, int pos, int len, char *buf); +``` +将str字符串指定位置的字符复制到指定的数组。其中,`pos`复制的起始位置。`len`复制的最大长度。`buf`就是接收复制数据的数组。函数返回实际复制的长度。 +```c +void test(void) +{ + str_t ss = str("0123456789"); + char array[32] = { 0 }; + int length = 0; + length = str_copy(ss, 5, sizeof(array), array); // 从位置开始复制,复制最大长度为数组的长度 + array[length] = '\0'; // 在后面添加结束符,方便打印 + printf("length = %d, copy: %s\r\n", length, array); + _str(ss); +} +``` +结果: +``` +length = 5, copy: 56789 +``` + +### str字符串格式化 +```c +str_t str_format(str_t str, char *format, ...); +``` +str字符串格式化操作,套用`sprintf`函数进行了重写,与其用法基本一致。**增加了%s对str类型的支持**,`str_format`对格式化进行分段的动态扩容,在使用上不需要像`sprintf`那样关注buf长度是否超出的问题,使用起来更加的简便。 +```c +void test(void) +{ + str_t ss = str(""); + str_t pp = str("format"); + str_format(ss, "Hello str! %s %s! int:%d, float:%.2f, char:%c", pp, "function", 18, 175.5, 'A'); + printf("ss: %s\r\n", str_c_str(ss)); + _str(ss); + _str(pp); +} +``` +结果: +``` +ss: Hello str! format function! int:18, float:175.50, char:A +``` +从这个例子看,`str_format`不用去关注空间的问题,`%s`除了对`char*`支持也可以对`str_t`对象支持,做到了两种字符串的兼容。 + +## 参考例子 + +### 案例1 +输入一个字符串,判断是否为回文字符串,即从左到右和从右到左完全一致。 +```c +void test(void) +{ + str_t s = str(""); + str_t r = str(""); + char buf[64] = {0}; + while (1) + { + scanf("%s", buf); // 输入一个字符串 + str_assign(s, buf); // 将s赋值为输入的字符串 + str_assign(r, s); // 将r赋值为s + str_reverse(r, 0, str_length(r) - 1); // 将r进行翻转 + if (str_compare(s, r) == 0) // 比较s和r是否一致 + { + printf("这是一个回文数字符串!\r\n"); + } + else + { + printf("这不是一个回文数字符串!\r\n"); + } + } + _str(s); + _str(r); +} +``` +结果: +``` +qwewq +这是一个回文数字符串! +qwerrewq +这是一个回文数字符串! +zxcvfd +这不是一个回文数字符串! +``` + +### 案例2 +输入一个字符串,删除字符串中重复出现的字符。 +```c +void test(void) +{ + str_t s = str(""); + str_t r = str(""); + char buf[64] = {0}; + + while (1) + { + scanf("%s", buf); // 输入一个字符串 + str_assign(s, buf); // 将s赋值为输入的字符串 + str_assign(r, ""); // 将r设为空 + for (int i = 0; i < str_length(s); i++) // 遍历s + { + char t[2] = {str_at(s, i),0}; // 取出s的字符,组成一个短的数组字符串 + if (str_find(r, t, 0) == str_npos) // 判断这个数组字符串是否在r当中了,也就是判断s的字符是否在r里面 + { + str_append(r, t); // 不在里面则追加到里面 + } + } + printf("%s\r\n", _S(r)); + } + + _str(s); + _str(r); +} +``` +结果: +``` +16783679816488742135468794 +167839425 + +asdfgsaf +asdfg + +hijief145ahya21 +hijef145ay2 +``` + +## 源码解析 + +### str结构体 + +str容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致str存储结构的破坏。所以str解析器只留了唯一一个str的声明在头文件,然后结构体的定义都在源文件。只能使用str容器提供的方法对str对象进行操作。 +str类型声明 +```c +typedef struct STR *str_t; +``` +使用时候,只是用`str_t`即可。 + +```c +/* str define */ +typedef struct STR +{ + char ident; /* ident of str */ + char* base; /* base address of string */ + int length; /* length of str */ + int capacity; /* capacity of str */ +} STR; +``` +`STR`结构体中包含了4个成员,ident(识别标志,区分是数组字符串还是str字符串的依据),base(真正存储字符串的基地址),length(str的大小,也就是str的长度),capacity(分配的空间的容量,用来实际存储字符串,一般情况下不用去关注容量的事情)。 + +### 区分数组字符串和str字符串 + +在`STR`结构体当中,`ident`成员就是用于区分数组字符串和str字符串的标志,那是怎么区分的呢? +看下区分的代码: +```c +/* str info define */ +typedef struct +{ + char *base; + int length; +} str_info_t; + +static str_info_t string_info(void *string) +{ + str_info_t info; + + if (((char *)string)[0] == 0) /* empty array string */ + { + info.base = (char *)string; + info.length = 0; + } + else if (((str_t)string)->ident == ident()) /* str type */ + { + info.base = ((str_t)string)->base; + info.length = ((str_t)string)->length; + } + else /* array string type */ + { + info.base = (char *)string; + info.length = strlen((char *)string); + } + + return info; +} +``` +首先,`ident`定义在结构体的首位,也就是结构体地址首位存储的内容就是`ident`的内容,所以当传入`void *string`,先判断一下这个地址首个字节的内容是什么。当为`\0`时候,就表示是一个空的数组字符串,当为`ident()`的时候,就代表是`str`字符串了,其他的值则为数组字符串了。 +那这个`ident()`到底是什么?为什么其可以区分。 +```c +#define ident() (-1) +``` +其实就是`-1`,**那就是只要传入的地址的首个字节内容为`-1`就是str字符串,否则就是数组字符串**。为什么`-1`可以用来区分。 +首先先看ASCII编码的规则,字符只在`0 ~ 127`之间,用不上`-1`,那-1是可以用来区分的,在ASCII编码中区分没问题。 +再看Unicode编码的,Unicode编码范围在`0x0000 ~ 0x10FFFF`,`-1`对应的无符号值是`0xFF`,在Unicode编码范围内没有以`0xFF`开头的,所以`-1`是可以用来区分的,在Unicode编码中区分没问题。 +然后在UTF-8编码中,UTF-8编码是将Unicode编码调整为可变长的,在1~4个字节不等。在为ASCII字符的时候,采用一个字节编码保持和ASCII一致,超过了ASCII编码范围就变成了多字节,多字节是以第一个字节作为起始,标志这个字符需要多少字节,然后后面的字节归属这个字符。比如2字节的为`110xxxxx 10xxxxxx`,第一个字节前面多少个`1`就代表多少字节编码,以`0`分隔,后面的字节以`10`开头,其余`x`部分就是编码数据部分,如此,3字节的为`1110xxxx 10xxxxxx 10xxxxxx`,4字节`11110xxx 10xxxxxx 10xxxxxx 10xxxxxx`。说明UTF-8的编码规则,也就是说明在UTF-8编码中也是没有以`0xFF`开头的,所以`-1`是可以用来区分的,在UTF-8编码中区分没问题。 +选择`-1`作为区分,是因为在常用的编码里面,都不会用到,所以用`-1`来作为区分。 +回到`str_info_t string_info(void *string)`函数,在`str`的api中,涉及到需要区分字符串类型的,都调用了这个函数进行区分,返回的`str_info_t`就包含字符串的基地址个长度。 + +### str自适应长度 +在str的所有api当中,使用时候都没有涉及需要自己去分配或者定义多少空间,因为在str内部会自适应字符串长度来动态调整存储空间的大小。 +以`str_assign`赋值函数为例: +```c +str_t str_assign(str_t str, void *string) +{ + str_info_t info; + if (!str) return NULL; + if (!string) return NULL;; + info = string_info(string); // 获取字符串的信息,基地址和长度 + if (!str_alter_capacity(str, info.length)) return NULL; // 根据长度进行容量调整 + strcpy(str->base, info.base); // 将字符串复制到str空间 + str->length = info.length; // 更新长度 + return str; +} +``` +其代码长度也就是这几行。其中调整容量`str_alter_capacity`函数。 +```c +static int str_alter_capacity(str_t str, int length) +{ + int capacity = 0; + char *base = NULL; + + /* check whether the capacity changes and need to adjust the capacity space */ + capacity = gradient_capacity(length); + if (str->capacity != 0 && str->capacity == capacity) return 1; + if (!str->base) /* allocate new space */ + { + str->base = (char *)malloc(capacity + 1); + if (!str->base) return 0; + } + else /* reallocate space */ + { + base = (char *)realloc(str->base, capacity + 1); + if (!base) return 0; + str->base = base; + } + str->capacity = capacity; + return 1; +} +``` + +### str获取实际存储数据 +```c +char* str_data(str_t str, int pos) +{ + if (!str || pos < 0 || pos >= str->length) + { + error = 0; /* reset error area */ + return &error; + } + return &str->base[pos]; +} +``` +`str_at`、`str_c_str`、`_S`都是基于`str_data`实现的,`str_data`实现的内容其实也很简单,就是将结构体中`base`指定索引的地址进行返回。在其中加入了`error`机制,也就是当传入的添加不满足的时候,会返回`error`的地址,`error`为模块定义的一个静态变量。防止返回NULL,对空地址进行操作时候导致的程序崩溃。 + +### str字符串替换原理 +```c +str_t str_replace(str_t str, int pos, int len, void *string) +{ + str_info_t info; + char *overlap = NULL; + if (!str) return NULL; + if (!string) return NULL; + if (pos < 0 || pos > str->length) return NULL; + if (len > str->length - pos) len = str->length - pos; + info = string_info(string); + + /* check if addresses overlap + 这一步是检查地址有没有重叠,就是string信息的地址有没有和原来的str的地址重叠 + 重叠的处理机制是,分配一块空间出来,把要替换的内容复制一份 + 到后面再把复制出来的替换进去 + */ + if (str->base <= info.base && info.base <= str->base + str->length && pos < str->length) + { + overlap = (char *)malloc(info.length + 1); + if (!overlap) return NULL; + strcpy(overlap, info.base); + info.base = overlap; + } + + /* + 这一步,判断被替换的字符串与替换字符串的长度关系,也就是判断需不需要调整容量大小 + */ + if (info.length > len) // 替换的字符串长了,需要扩容 + { + /* 先扩容,扩大的容量为这个长度差值 */ + if (str_alter_capacity(str, str->length + (info.length - len)) == 0) + { + if (overlap) free(overlap); + return NULL; + } + /* 把后部分数据往后移动,腾出点空间来存放替换进来较长的字符串 */ + memmove(&str->base[pos + info.length], &str->base[pos + len], str->length - (pos + len)); + /* 腾出空间后,把替换的字符串复制进来 */ + memcpy(&str->base[pos], info.base, info.length); + } + else if (info.length < len) /* 长度变小 */ + { + /* 把后面的数据往前面移,留info的长度给替换进来的字符串即可 */ + memmove(&str->base[pos + info.length], &str->base[pos + len], str->length - (pos + len)); + /* 把替换的字符串复制进来 */ + memcpy(&str->base[pos], info.base, info.length); + /* 缩容的,后面调整容量 */ + str_alter_capacity(str, str->length + (info.length - len)); + } + else /* 长度没发生变化 */ + { + /* 直接覆盖即可 */ + memcpy(&str->base[pos], info.base, info.length); + } + str->length += (info.length - len); + str->base[str->length] = 0; + if (overlap) free(overlap); + + return str; +} +``` +不管是插入还是移除的方法,都是基于替换的方法进行实现的。 +```c +str_t str_insert(str_t str, int pos, void *string) +{ + return str_replace(str, pos, 0, string); +} + +str_t str_erase(str_t str, int pos, int len) +{ + return str_replace(str, pos, len, ""); +} +``` + +### str拼接原理 +```c +#define str_append(str, ...) str_append_series((str), ##__VA_ARGS__, NULL) +str_t str_append_series(str_t str, ...) +{ + va_list args; + void* s = NULL; + if (!str) return NULL; + va_start(args, str); + s = va_arg(args, void*); + while (s) + { + /* insert at the end */ + if (!str_insert(str, str->length, s)) + { + va_end(args); + return NULL; + } + s = va_arg(args, void*); + } + va_end(args); + return str; +} +``` +实际就是调用`str_append_series`函数进行传参,在不定参数后面传入个`NULL`作为结尾。 +而在`str_append_series`函数内部,处理不定参数,依次将一段段字符串通过`str_insert`插到尾端,完成拼接。 + +### str格式化源码 + +```c +str_t str_format(str_t str, char *format, ...) +{ + va_list args; + int i = 0; + int len = 0; /* length of sub string */ + char *s = NULL; /* temp string */ + str_info_t info; /* str info */ + double dbl = 0.0; /* double precision floating point */ + char *begin = NULL; /* begin of format */ + char qualifier = 0; /* conversion qualifier for integer as 'h','l','L' */ + int width = 0; /* transition field width */ + int precision = 0; /* minimum digits of integers and maximum digits of strings */ + char tfmt[16] = "%"; /* temporary format */ + + if (!str) return NULL; + str_assign(str, ""); + va_start(args, format); + for (; *format; format++) + { + /* 为普通字符时 */ + if (*format != '%') + { + if (!begin) begin = format; // 记录下普通字符段的起始位置 + continue; // 不再执行这个循环跳到下一个 + } + + /* 记录了普通字符的起始位置,也就表示当前位置的前段部分为普通的字符 */ + if (begin) + { + len = format - begin; // 记录下长度给后面扩容 + if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY; + while (len--) str->base[str->length++] = *begin++; // 复制普通的字符进去 + begin = NULL; + } + + /* 从这里开始处理格式化的数据段 */ + begin = format; + while (1) // 记录前置符号标志 + { + /* skips the first '%' also */ + format++; + if ((*format != '0') && + (*format != ' ') && + (*format != '+') && + (*format != '-') && + (*format != '#')) break; + } + + /* get field width */ + width = -1; + if (is_digit(*format)) + { + format += get_digit(format, &width); + } + else if (*format == '*') + { + format++; + width = va_arg(args, int); + if (width < 0) width = -width; + } + + /* get the precision */ + precision = -1; + if (*format == '.') + { + format++; + if (is_digit(*format)) format += get_digit(format, &precision); + else if (*format == '*') + { + format++; + precision = va_arg(args, int); + } + if (precision < 0) precision = 0; + } + + /* get the conversion qualifier */ + qualifier = 0; + if (*format == 'h' || *format == 'l' || *format == 'L') + { + qualifier = *format; + format++; + if (qualifier == 'l' && *format == 'l') + { + qualifier = 'L'; + format++; + } + } + + /* format distribution */ + switch (*format) + { + case 'c': + // 预计需要多长空间 + // 调用sprintf尾插一个字符 + ... + case 's': + // 预计需要多长空间 + // 调用sprintf尾插一段字符串 + ... + case 'p': + // 预计需要多长空间 + // 调用sprintf尾插一个指针 + ... + + case 'a': + case 'A': + case 'e': + case 'E': + case 'g': + case 'G': + case 'f': + // 预计需要多长空间 + // 调用sprintf尾插一个浮点数 + ... + case 'o': + case 'X': + case 'x': + case 'd': + case 'i': + case 'u': + // 预计需要多长空间 + // 调用sprintf尾插一个整形数 + ... + case '%': + // 推入 % + ... + default: + // 推入 % + ... + } + } + + /* copy tail string to str */ + if (begin) + { + len = format - begin; + if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY; + while (len--) str->base[str->length++] = *begin++; + } + + if (!str_alter_capacity(str, str->length)) goto FAIL_CAPACITY; + str->base[str->length] = '\0'; + va_end(args); + return str; + +FAIL_CAPACITY: + str->base[str->length] = '\0'; + va_end(args); + return NULL; +} +``` +源码比较长,大体的流程是,遍历`format`,在遇到`%`符号时进行具体的格式操作,以`%`为分隔符,将整段`format`分成一段段进行动态扩容、赋值。 diff --git a/doc/varch:txls解析器.md b/doc/varch:txls解析器.md new file mode 100644 index 0000000..adf7900 --- /dev/null +++ b/doc/varch:txls解析器.md @@ -0,0 +1,506 @@ +## 介绍 + +txls是varch的文本表格,语法参考markdown表格的语法,为了更好的文本直观性,txls语法规则比markdown表格语法稍微严格一点。通过txls,可以很方便的访问一个txls文件的行列内容,以及生成一个整齐规范可读性高的文本形式的表格文件。 + +简单介绍下txls规范,以及对比下markdown表格的语法。 + +**表头** +``` +表头和markdown表格表头很类似,只不过稍微严格点 +markdown表格不要求当前行两端一定需要`|`分隔符,但是txls为了更整齐的格式,要求行的两端必须包含`|`分割符。后面的每一行内容也有如此要求。 +表头后面跟随分割行,分割行的列数需与表头列数一致 +分割行的内容必须包含连续的`-` +``` +例子: +```md +| col 1 | col 2 | col 3 | col 4 | col 5 | +|-------|-------|--------------------|-------|-------| +``` + +**行** +``` +在一行中,以`|`分隔符作为区分列 +在`|`分隔符之间,包含的即为单元格内容,单元格内容不包含两端的空格部分 +单元格内容内容可以包换转义字符 "\|" 表示 '|',"
" 表示 '\n' +``` +例子: +``` +| col 1 | Zhang san | col 3 | col 4 | col 5 | +|-------|----------:|----------------|-------|-------| +| 11 | 21 | 31 | 41 | 51 | +| 12 | 22 | 1234\|
5678 | 42 | 52 | +| 13 | 23 | 33 | 43 | 53 | +| 14 | 24 | 34 | 44 | 54 | +| 15 | 25 | 35 | 45 | 55 | +``` + +**对齐方式** +``` +txls的对齐方式一共三种,左对齐、右对齐、居中对齐。在表格中的标识为':',在表格分割行对应列单元格两端位置。 +':'在左为左对齐,在右为右对齐,左右都有则为居中对齐,都没有则为默认对齐(目前txls默认对齐为左对齐) +':'在两端的位置最多仅能为1个 +``` +例子: +``` +| left align | right align | center align | default align | +|:-----------------|-----------------:|:----------------:|------------------| +| 0 | 0 | 0 | 0 | +| 0123456789abcdef | 0123456789abcdef | 0123456789abcdef | 0123456789abcdef | +``` + + +## 接口 + +### 创建和删除txls对象 +```c +txls_t txls_create(int col, int row); +void txls_delete(txls_t txls); +``` +其中**txls_t**为txls的结构体,创建方法会创建一个col列row行的表格,创建成功则返回txls对象的句柄,删除方法则是删除一个txls对象 + +### 获取行列数 +```c +int txls_col(txls_t txls); +int txls_row(txls_t txls); +``` +这两个方法分别返回txls表格的列数和行数 + +### 插入和删除一列 +```c +int txls_insert_column(txls_t txls, int col); +int txls_delete_column(txls_t txls, int col); +``` +这两个方法分别向txls插入和删除一列,列的索引从1开始,成功返回1失败返回0 + +### 插入和删除一行 +```c +int txls_insert_row(txls_t txls, int row); +int txls_delete_row(txls_t txls, int row); +``` +这两个方法分别向txls插入和删除一行,行的索引从1开始,成功返回1失败返回0 + +### 设置和获取单元格内容 +```c +int txls_set_text(txls_t txls, int col, int row, const char* text); +const char* txls_get_text(txls_t txls, int col, int row); +``` +设置方法,就是将txls指定的单元格内容设定为指定的text,文本内容的两端不建议设为空格,在写入表格后将会忽略,同时也不能带有换行符除外的不可视字符。成功返回1,失败返回0。 +获取方法则是返回指定单元的内容,返回空即代表获取失败。 +当row传入0时候,则是相应设置和获取表头。 +```c +#define txls_set_head(txls, col, head) +#define txls_get_head(txls, col) +``` + +### 设置对齐方式 +```c +int txls_set_align(txls_t txls, int col, int align); +``` +设置指定列的对齐方式,方式包括有TXLS_ALIGN_LEFT,TXLS_ALIGN_RIGHT,TXLS_ALIGN_CENTER三种,其他的均为未知对齐方式。 + +### txls对象转储 +```c +char* txls_dumps(txls_t txls, int neat, int* len); +int txls_file_dump(txls_t txls, const char* filename); +``` +首先**txls_dumps**方法,将txls对象按格式转储为字符串。其中传入的neat参数,是否整齐的转储的控制变量,0为不整齐输出,其他为整齐输出。整齐输出,就是每一列都是对齐同宽度的输出,不整齐输出则是单元格实际多长就输出多长,整齐输出保持美观可读性,但是会占用比较多空间来存放空格。len则是转换出来的字符串长度,传入NULL时候就是不获取长度。返回值则是转换出来的字符串,这个字符串是函数分配的,**在结束使用需要free掉**。 +**txls_file_dump**方法则是在**txls_dumps**的基础上将txls转储到文件当中,filename传入文件名,返回值为转储的长度,负值表示转储失败。 + +### txls对象加载 +```c +txls_t txls_loads(const char* text); +txls_t txls_file_load(const char* filename); +``` +类似转储的方法,txls对象可以从字符串文本中加载,也可以从文件中加载。加载成功则会返回一个txls对象,失败则返回NULL。 + +### txls加载错误 +```c +int txls_error_info(int* line); +``` +varch的txls解析器提供了精准的错误识别,在txls加载的失败的时候可以调用此方法去定位错误位置和错误的类型。返回值为1表示当前解析出错了,0则为未出错。line输出错误所在行,type输出错误的类型。错误类型定义如下: +```c +#define TXLS_E_OK (0) /* no error */ +#define TXLS_E_HEAD (1) /* irregular header format */ +#define TXLS_E_ALLOC (2) /* failed to allocate space */ +#define TXLS_E_BEGIN (3) /* missing "|" separator at the begin */ +#define TXLS_E_END (4) /* missing "|" separator at the end */ +#define TXLS_E_IDENT (5) /* missing "-" separator at split row */ +#define TXLS_E_BRANK (6) /* there are extra blank lines */ +#define TXLS_E_MEMORY (7) // memory allocation failed +#define TXLS_E_OPEN (8) // fail to open file +``` + +## 参考例子 + +### 生成txls文件 +```c +void test(void) +{ + txls_t x = NULL; // 定义txls对象,习惯初始化为NULL + + x = txls_create(4, 5); // 创建4x5的表格 + if (!x) + { + return; + } + + /* 设置表头,第一列留空 */ + txls_set_head(x, 2, "Zhang San"); + txls_set_head(x, 3, "Li Si"); + txls_set_head(x, 4, "Wang Wu"); + + /* 设置对齐方式 */ + txls_set_align(x, 2, TXLS_ALIGN_LEFT); + txls_set_align(x, 3, TXLS_ALIGN_CENTER); + txls_set_align(x, 4, TXLS_ALIGN_RIGHT); + + /* 第一列作为信息类别 */ + txls_set_text(x, 1, 1, "age"); + txls_set_text(x, 1, 2, "gender"); + txls_set_text(x, 1, 3, "height"); + txls_set_text(x, 1, 4, "weight"); + txls_set_text(x, 1, 5, "email"); + + /* 写入每个人信息 */ + // Zhang San + txls_set_text(x, 2, 1, "18"); + txls_set_text(x, 2, 2, "man"); + txls_set_text(x, 2, 3, "178.5"); + txls_set_text(x, 2, 4, "65"); + txls_set_text(x, 2, 5, "123321@qq.com"); + // Li Si + txls_set_text(x, 3, 1, "24"); + txls_set_text(x, 3, 2, "woman"); + txls_set_text(x, 3, 3, "165"); + txls_set_text(x, 3, 4, "48"); + txls_set_text(x, 3, 5, "lisi@163.com"); + // Wang Wu + txls_set_text(x, 4, 1, "20"); + txls_set_text(x, 4, 2, "man"); + txls_set_text(x, 4, 3, "175"); + txls_set_text(x, 4, 4, "75"); + txls_set_text(x, 4, 5, "ww1234567890@qq.com"); + + txls_file_dump(x, "info.txls"); + + txls_delete(x); +} +``` +转储的文件 **info.txls** +```txls +| | Zhang San | Li Si | Wang Wu | +|--------|:--------------|:------------:|--------------------:| +| age | 18 | 24 | 20 | +| gender | man | woman | man | +| height | 178.5 | 165 | 175 | +| weight | 65 | 48 | 75 | +| email | 123321@qq.com | lisi@163.com | ww1234567890@qq.com | + +``` +通过markdown阅读器的显示效果 + +| | Zhang San | Li Si | Wang Wu | +|--------|:--------------|:------------:|--------------------:| +| age | 18 | 24 | 20 | +| gender | man | woman | man | +| height | 178.5 | 165 | 175 | +| weight | 65 | 48 | 75 | +| email | 123321@qq.com | lisi@163.com | ww1234567890@qq.com | + +例子里面使用函数很多没有对返回值进行判断,实际应用需对返回值进行判断。 + +### 加载txls文件 +在上面生成的文件的基础上,进行加载该文件。 +加载测试代码 +```c +void test(void) +{ + txls_t x = NULL; // 定义txls对象,习惯初始化为NULL + + /* 加载txls文件 */ + x = txls_file_load("info.txls"); + if (!x) // 加载失败,定位错误 + { + int line, type; + type = txls_error_info(&line); + printf("txls parse error! line %d, error %d.\r\n", line, type); + return; + } + + /* 遍历表头,定位所在列 */ + int col = 0; + for (col = 1; col <= txls_col(x); col++) + { + if (strcmp("Li Si", txls_get_head(x, col)) == 0) + { + break; + } + } + if (col > txls_col(x)) // 没有查找到 + { + printf("Lookup failed\r\n"); + return; + } + + /* 打印信息 */ + printf("name: %s, age=%s, gender: %s, height=%s, weight=%s, email:%s\r\n", + txls_get_text(x, col, 0), + txls_get_text(x, col, 1), + txls_get_text(x, col, 2), + txls_get_text(x, col, 3), + txls_get_text(x, col, 4), + txls_get_text(x, col, 5)); + + txls_delete(x); // 用完之后需要删除 +} +``` +运行结果: +``` +name: Li Si, age=24, gender: woman, height=165, weight=48, email:lisi@163.com +``` + +### 加载错误 +在上面例子的基础上把文件修改一下,把在第4行行末的分割符'|'删掉再加载。 +``` +| | Zhang San | Li Si | Wang Wu | +|--------|:--------------|:------------:|--------------------:| +| age | 18 | 24 | 20 | +| gender | man | woman | man +| height | 178.5 | 165 | 175 | +| weight | 65 | 48 | 75 | +| email | 123321@qq.com | lisi@163.com | ww1234567890@qq.com | +``` +运行结果: +``` +txls parse error! line 4, error 4. +``` +如此能定位到4行出现4号错误,也就是 +``` +#define TXLS_E_END (4) /* missing "|" separator at the end */ +``` + +## 源码解析 + +### txls解析器结构体 + +txls解析器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致txls存储结构的破坏。所以txls解析器只留了唯一一个txls的声明在头文件,然后结构体的定义都在源文件。只能使用txls解析器提供的方法对txls对象进行操作。 +txls类型声明 +```c +typedef struct TXLS *txls_t; +``` +使用时候,只是用`txls_t`即可。 + +```c +/* type of txls */ +typedef struct TXLS +{ + COLUMN *columns; /* columns base */ + ITERATOR iterator; /* column iterator */ + int col; /* column count */ + int row; /* row count */ +} TXLS; +``` +TXLS结构体中包含了4个成员,columns(columns的链表),iterator(columns的迭代器),col和row就分别是表格的列和行数。特别说明这个**iterator**,就是这个迭代器记录列访问时候的位置,当再一次访问检查到是同位置时候,就可以快速返回,而不用从头到尾再遍历。迭代器后续再说明。 + +```c +/* column storage */ +typedef struct COLUMN +{ + struct COLUMN *next; /* next column */ + CELL *cells; /* the cell base address of this column */ + ITERATOR iterator; /* cell list iterator */ + int align; /* alignment */ + int width; /* the output width of this column when neatly outputting */ +} COLUMN; +``` +看COLUMN结构体,包含了5个成员,next(指向下一个COLUMN,形成单向链表),cells(单元格链表),iterator(pair的迭代器),align(对齐方式),width(整齐输出时候的列的宽度)。 + +```c +/* smallest memory cell */ +typedef struct CELL +{ + struct CELL *next; /* next cell */ + char* address; /* address of cell content */ +} CELL; +``` +再看CELL结构体,包含2个成员,next(指向下一个CELL,形成单向链表),address(单元格内容,字符串)。 + +```c +typedef struct +{ + void *p; /* iteration pointer */ + int i; /* iteration index */ +} ITERATOR; +``` +最后看看这个迭代器,其实这个迭代器很简单,就是记录当前所指向的单向链表的结点(成员p),以及记录当前所在单向链表的索引。具体是怎么记录的下文再聊。 + +结构体介绍到这里,TXLS类的存储结构已经很明了,首先一个链表串起来每一列,每一列下面又串一个链表串起一列的单元格。 +``` +INI + col[1] --> col[2] --> col[3] --> col[4] + | | | | + V V V V + cell[0] cell[0] cell[0] cell[0] + | | | | + V V V V + cell[1] cell[1] cell[1] cell[1] + | | | | + V V V V + cell[2] cell[2] cell[2] cell[2] +``` + +### 单向链表的迭代 + +单向链表的操作不是这里的重点,这里内置的迭代器为了提高单向链表的访问效率,迭代过程着重说明下。以column链表迭代为说明,说明下这个迭代器获取的链表结点的过程: +```c +static COLUMN *txls_column(txls_t txls, int index, int col) // 传入索引和限定的列数 +{ + if (index >= col) return NULL; // 判断索引有没有越界 + + /* + 这个一步是重置迭代,也就是将迭代器定位回到链表首位 + 满足其中3个条件之一都重置迭代器 + 1、因为单向链表,不能反向指向,所以目标索引小于迭代器的索引时候,就要重置,然后从链表首位迭代到指定索引 + 2、迭代器指针(p成员)为空时,为空则没有指向具体的结点,当然得重置迭代,所以外部想重置迭代器,只需将p成员置NULL即可 + 3、目标索引index为0时,主动获取第0位,也就是首位 + */ + if (index < txls->iterator.i || !txls->iterator.p || index == 0) + { + txls->iterator.i = 0; + txls->iterator.p = txls->columns; + } + + /* + 循环将迭代器迭代到指定的索引位置 + 单向链表索引正向递增,所以正向遍历时候时间复杂度O(n),反向遍历还是O(n^2) + */ + while (txls->iterator.p && txls->iterator.i < index) + { + txls->iterator.p = ((COLUMN *)(txls->iterator.p))->next; + txls->iterator.i++; + } + + /* 返回迭代器指向的结点 */ + return txls->iterator.p; +} +``` +迭代器在对链表进行调整的时候需要相应调整,最简单的方式就是把成员p设为NULL重置迭代器。 + +### txls转储说明 + +转储就是按格式将txls“打印”出来,不过的是,不是打印在控制台,而是打印在指定内存空间。那这个空间哪来的呢?是动态内存分配来的,分配多少呢?如果是整齐输出的(neat不为0),那么就可以根据列宽预测到需要多少的空间,不整齐输出则需要在转储时候动态调整大小。 +现在先来看维护这个打印空间的结构体: +```c +typedef struct +{ + char* address; /* buffer base address */ + int size; /* size of buffer */ + int end; /* end of buffer used */ +} BUFFER; +``` +一共也就是3个成员,address(空间的基地址),size(空间的大小),end(已使用的结尾,索引)。 +动态调整空间的过程: +```c +static int buf_append(BUFFER *buf, int needed) // neede为所需的追加的容量 +{ + char* address; + int size; + + if (!buf || !buf->address) return 0; + + /* 计算当前已使用的加上所需的一共所需多大的空间 */ + needed += buf->end; + + /* 计算当前的空间还能满足需要 */ + if (needed <= buf->size) return 1; /* there is still enough space in the current buf */ + + /* + 当前空间大小满足不了所需,重新计算能满足所需的空间 + 新的空间不是满足当前所需就行了的,还得留有余量,让下次追加时候用 + 不然每次追加一点容量都要重新分配空间,很浪费效率 + 而新分配的空间要多大才好,2的平方,也就是比所需大的最小的2的次方 + 为什么选择2的次方? + 1、算法好实现,计算比指定值大的最小2的次方数好实现 + 2、空间利用率好,为 (1 + 2)/ 2 = 75% + 3、空间重分配的次数小,比如最终需要128时候,每次定量10的空间新增需要13次,而2的次方只需7次。 + */ + size = pow2gt(needed); + address = (char*)realloc(buf->address, size); + if (!address) return 0; + + buf->size = size; + buf->address = address; + + return 1; +} +``` +以其中一段转储的代码为例,看看这个是怎么使用的 +```c +if (!buf_append(buf, 2)) return 0; // 先扩展空间 +buf_push(buf, '|'); // 调用函数把字符push到buf里面 +buf_push(buf, '\n'); +``` + +在转储cell的时候有个点需要注意的,就是cell是允许换行和分隔符'|'的,换行符用`
`来代替,分隔符'|'则转移`\|`来代替。 +```c +while (addr && *addr) +{ + if (*addr == '\n') + { + buf_push(buf, '<'); + buf_push(buf, 'b'); + buf_push(buf, 'r'); + buf_push(buf, '>'); + } + else if (*addr == '|') + { + buf_push(buf, '\\'); + buf_push(buf, '|'); + } + else + { + buf_push(buf, *addr); + } + addr++; +} +``` + +最终打印时候就是将txls遍历,动态调整空间把每个每一列每一个单元格按顺序打印到指定空间。 + +### txls加载说明 + +txls解析器最重要以及最复杂的部分就这个加载解析的部分。 + +* 1、首先创建一个`0x0`txls表格对象,然后在随着解析把解析到的表头、行、列这些逐个添加到txls对象,等到解析完就可以得到一个完整的txls对象 + +* 2、在解析之前的首先得将解析的行号以及错误重置,然后随着解析遇到换行符就让行号相应的递增,碰到解析出错了,就记录下错误的类型 + +* 3、然后就到了真正的解析过程,解析分为两步走,第一步先解析表头和表格分割行,这一步是确认这个表格一共有多少列,对齐方式,以及语法符不符合表头语法。第二步,就在确定的列的基础上,逐行的解析,解析每一行所区分的单元格并把单元格内容归类到指定行列里面。 + +整个解析过程如下 +```c +/* 创建一个空表格 */ +txls = txls_create(0, 0); +... + +/* 重置错误信息 */ +etype = TXLS_E_OK; +eline = 1; + +/* 解析表头 */ +s = parse_head(text, txls); +if (etype) goto FAIL; +while (1) +{ + /* 解析每一行 */ + s = parse_line(s, txls); + if (etype) goto FAIL; + if (!*s) break; +} +return txls; +``` +因为txls语法规定的内容基本都是以行来划分(value特殊情况可以换行),所以基本在这个循环里面就是在处理一行行的信息。 + +### txls的增删改查说明 +剩下的针对txls增删改查的其实已经没什么特别说明的了,就是对链表数据结构的基本操作,这里不着重说明了 + diff --git a/doc/varch:vector容器.md b/doc/varch:vector容器.md new file mode 100644 index 0000000..361f30c --- /dev/null +++ b/doc/varch:vector容器.md @@ -0,0 +1,353 @@ +## 介绍 + +vector容器与数组非常类似,封装了数组常用的方法,大部分场合可以用vector来代替普通的数组使用,更加的安全。数组是静态的空间,定义好了就不能改变大小,而vector是动态的,在使用的过程中可以动态的调整大小。 +同时varch的vector也可以像数组一样随机直接访问。 + +## 接口 + +### 创建和删除vector对象 +```c +vector_t vector_create(int dsize, int size); +void vector_delete(vector_t vector); +#define vector(type, size) // 为了更简便的使用,对vector_create套一层宏定义 +#define _vector(vector) // 对vector_delete套一层宏定义,并在vector删除后置为空 +``` +其中**vector_t**为vector的结构体,创建方法则会返回一个vector对象,创建失败则返回NULL,其中`dsize`传入数据的大小,`size`传入vector数组的大小(数据的数量)。删除方法则是删除传入的vector对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 +```c +void test(void) +{ + vector_t vt = vector(int, 16); // 创建长度为16的int型vector + int array[16]; + + _vector(vt); // 删除vt +} +``` + +### vector数据的读写 +```c +void* vector_data(vector_t vector, int index); +#define vector_at(vector, type, i) +#define v2a(vector, type) +``` +`vector_data`方法就是根据索引来获取数据的地址,返回的则是指定的数据的地址,NULL则是失败。而`vector_at`则是在`vector_data`的基础上加多类型,`v2a`是将vector当作普通数组使用,通过`[]`下标进行访问。 +```c +void test(void) +{ + vector_t vt = vector(int, 8);; // 定义并创建长度为8的int型vector + int i = 0; + + for (i = 0; i < 8; i++) + { + vector_at(vt, int, i) = i; // 使用at方法访问 + } + + for (i = 0; i < 8; i++) + { + printf("vt[%d] = %d\r\n", i, v2a(vt, int)[i]); // 使用下标访问 + } + + _vector(vt); // 用完即删除 +} +``` +结果: +``` +vt[0] = 0 +vt[1] = 1 +vt[2] = 2 +vt[3] = 3 +vt[4] = 4 +vt[5] = 5 +vt[6] = 6 +vt[7] = 7 +``` + +### vector的大小和容量 +```c +int vector_size(vector_t vector); +int vector_capacity(vector_t vector); +``` +vector的大小很好理解,也就是像数组那样的大小,但是容量是什么呢?容量是用来存储vector大小的空间,因为是动态的数组,为了更好的扩容,一般是会预留一些空间给到后续的扩展。如此,容量一定是大于或等于大小的。**在实际使用过程中,不必多关注容量,关注大小就足够了** +```c +void test(void) +{ + vector_t vt = vector(int, 10); + printf("size=%d, capacity=%d\r\n", vector_size(vt), vector_capacity(vt)); + _vector(vt); +} +``` +结果: +``` +size=10, capacity=12 +``` + +### vector调整大小 +```c +int vector_resize(vector_t vector, int size); +#define vector_clear(vector) +``` +此方法是重新调整vector容器的方法,可以扩容也可以缩容,扩容就是在原有的基础上,在尾部追加空间,追加的空间不赋值(也就是不一定为0)。缩容则会将尾部多出来的部分舍弃掉。`vector_clear`则套用`vector_resize`调整大小为0。 +```c +void test(void) +{ + vector_t vt = vector(int, 10); + printf("size=%d\r\n", vector_size(vt)); + vector_resize(vt, 32); + printf("size=%d\r\n", vector_size(vt)); + vector_resize(vt, 14); + printf("size=%d\r\n", vector_size(vt)); + _vector(vt); +} +``` +结果: +``` +size=10 +size=32 +size=14 +``` + +### vector的插入和移除 +```c +int vector_insert(vector_t vector, int index, void* data, int num); +int vector_erase(vector_t vector, int index, int num); +``` +插入的方法是在指定索引的位置插入指定地址的num个数据,而移除则是移除指定索引的num个数据。 +```c +void test(void) +{ + vector_t vt = vector(int, 0); // 创建0长度的vector容器 + int array[10] = {0,1,2,3,4,5,6,7,8,9}; + int i = 0; + + printf("insert:\r\n"); + vector_insert(vt, 0, array, 10); // 把array插入到vt当中,相当于用array给vt赋值 + for (i = 0; i < vector_size(vt); i++) + { + printf("vt[%d]=%d\r\n", i, v2a(vt, int)[i]); + } + + printf("erase:\r\n"); + vector_erase(vt, 5, 2); // 移除索引5后面的两个数据,也就是索引5和索引6 + for (i = 0; i < vector_size(vt); i++) + { + printf("vt[%d]=%d\r\n", i, v2a(vt, int)[i]); + } + + _vector(vt); +} +``` +结果: +``` +insert: +vt[0]=0 +vt[1]=1 +vt[2]=2 +vt[3]=3 +vt[4]=4 +vt[5]=5 +vt[6]=6 +vt[7]=7 +vt[8]=8 +vt[9]=9 +erase: +vt[0]=0 +vt[1]=1 +vt[2]=2 +vt[3]=3 +vt[4]=4 +vt[5]=7 +vt[6]=8 +vt[7]=9 +``` +通过插入和移除方法还延伸出了,以下的宏定义方法 +```c +#define vector_push_front(vector, data) +#define vector_push_back(vector, data) +#define vector_pop_front(vector) +#define vector_pop_back(vector) +``` + + +## 参考例子 + +```c +void test(void) +{ + vector_t vt_vector = vector(vector_t, 3); // 类型为vector_t的vector + int i = 0; + char *name[3] = { // 将这三个名字作为数据源,生成新的vector + "ZhangSan", + "LiSi", + "WangWu" + }; + + // 遍历vt_vector + for (i = 0; i < vector_size(vt_vector); i++) + { + v2a(vt_vector, vector_t)[i] = vector(char, 0); // 给vt_vector每一项新建一个char型的vector + vector_insert(v2a(vt_vector, vector_t)[i], 0, name[i], strlen(name[i]) + 1); // 将名字插入到vector当中 + } + + for (i = 0; i < vector_size(vt_vector); i++) + { + printf("vt_vector[%d]: %s\r\n", i, &vector_at(v2a(vt_vector, vector_t)[i], char, 0)); // 打印vt_vector记录下的名字 + _vector(v2a(vt_vector, vector_t)[i]); // 删除vt_vector下的vector + } + + _vector(vt_vector); +} +``` +结果: +``` +vt_vector[0]: ZhangSan +vt_vector[1]: LiSi +vt_vector[2]: WangWu +``` +例子里面使用函数很多没有对返回值进行判断,实际应用需对返回值进行判断。 + +## 源码解析 + +### vector结构体 + +vector容器的所有结构体都是隐式的,也就是不能直接访问到结构体成员的,这样子的方式保证了模块的独立与安全,防止外部调用修改结构体的成员导致vector存储结构的破坏。所以vector解析器只留了唯一一个vector的声明在头文件,然后结构体的定义都在源文件。只能使用vector容器提供的方法对vector对象进行操作。 +vector类型声明 +```c +typedef struct VECTOR *vector_t; +``` +使用时候,只是用`vector_t`即可。 + +```c +/* type of vector */ +typedef struct VECTOR +{ + void* base; /* base address for storing data */ + int dsize; /* size of item */ + int size; /* size of vector */ + int capacity; /* capacity of vector */ +} VECTOR; +``` +VECTOR结构体中包含了4个成员,base(实际存储数据的基地址),dsize(每个数据的大小),size(vector的大小,也就是vector的长度),capacity(vector的容量)。 + +### vector动态大小 + +从结构体成员看来,其实vector实现的逻辑并不难,实现动态调整大小并不复杂。把要存的数据,计算好大小,调用`malloc`动态分配指定空间即可,而当要调整的时候,则使用`realloc`重新分配一下空间。 +看下源码: +```c +int vector_resize(vector_t vector, int size) +{ + void* base = NULL; + int capacity; + if (!vector) return 0; + if (size < 0) return 0; + capacity = gradient_capacity(size); // 计算新的空间需要多大的容量 + if (capacity != vector->capacity) // 如果算出来的容量和当前的容量不一样了,则重新分配空间 + { + base = realloc(vector->base, capacity * vector->dsize); + if (!base) return 0; + vector->base = base; + vector->capacity = capacity; + } + vector->size = size; // 更新新的大小 + return 1; +} +``` +如上代码,实现动态大小就是这么简单。 +但是,这个计算容量的大小是怎么计算的呢? +```c +#define up_multiple(x, mul) ((x)+((mul)-((x)-1)%(mul))-1) /* get the smallest 'mul' multiple larger than 'x' */ +static int gradient_capacity(int size) +{ + int capacity = 1; + if (size <= 1) return 1; + while (capacity < size) capacity <<= 1; // 比size大的最小的2的次方 + capacity >>= 1; // 比size小的最大的2的次方 + if (capacity < 4) capacity = capacity << 1; + else if (capacity < 16) capacity = up_multiple(size, capacity >> 1); + else if (capacity < 256) capacity = up_multiple(size, capacity >> 2); + else capacity = up_multiple(size, 64); + return capacity; +} +``` +这个函数是将size按梯度进行划分,在这个梯度范围的size就会得到这个梯度的capacity。开始找到比size小的最大的2的次方,然后以(4、16,256)分级梯度作为梯度往上增,到后面最大一次增64。 +```c +int size = 0, capacity = 0; +for (size = 1; size < 1024; ) +{ + capacity = gradient_capacity(size); + printf("+%d\t%d\r\n", capacity - size + 1, capacity); + size = capacity + 1; +} +``` +用这段代码可以测试到这个梯度算法区分出来的梯度为: +``` ++1 1 ++1 2 ++2 4 ++2 6 ++2 8 ++4 12 ++4 16 ++4 20 ++4 24 ++4 28 ++4 32 ++8 40 ++8 48 ++8 56 ++8 64 ++16 80 ++16 96 ++16 112 ++16 128 ++32 160 ++32 192 ++32 224 ++32 256 ++64 320 ++64 384 ++64 448 ++64 512 ++64 576 ++64 640 ++64 704 ++64 768 ++64 832 ++64 896 ++64 960 ++64 1024 +``` +到后面最多只能增加64了。 + +### vector的插入和移除原理 +插入的原理就是将指定位置后面的数据整体往后挪,给插入的数据留出空位,再把要插入的数据复制进来;同理,移除数据则是将后面的数据整体往前移,覆盖要移除的数据段。 +先看插入的源码: +```c +int vector_insert(vector_t vector, int index, void* data, int num) +{ + int i = 0; + int size; + if (!vector) return 0; + if (index < 0 || index > vector->size) return 0; + if (num == 0) return 0; + size = vector->size; // 先记录下原来的大小,后面移数据需要用到,扩容之后vector->size就变了,找不到原来的尾部索引了 + if (!vector_resize(vector, vector->size + num)) return 0; // 扩容,给插入的数据预留空间 + if (index < size) memmove(at(index + num), at(index), vector->dsize * (size - index)); // 将数据整体往后移 + if (data) memcpy(at(index + i), data, vector->dsize * num); // 把新的数据复制进去 + return 1; +} +``` +从中可以看出,插入的位置越靠前,后面需要移动的数据就越多,插入的效率就越低。 +再看移除的源码: +```c +int vector_erase(vector_t vector, int index, int num) +{ + unsigned char *op_ptr = NULL; + if (!vector) return 0; + if (vector->size == 0) return 0; + if (index < 0 || index >= vector->size) return 0; + if (num <= 0) return 0; + if (num > vector->size - index) num = vector->size - index; // num超过尾端的数据数量了,就移除掉尾部全部即可 + memmove(at(index), at(index + num), vector->dsize * (vector->size - (index + num))); // 将数据往前移 + vector_resize(vector, vector->size - num); // 缩容去掉后半部分 + return 1; +} +``` diff --git a/doc/varch:xml解析器.md b/doc/varch:xml解析器.md new file mode 100644 index 0000000..6df4ccd --- /dev/null +++ b/doc/varch:xml解析器.md @@ -0,0 +1,272 @@ + +## 介绍 +C语言xml解释器。包含xml文本文件解析和生成,适合大部分的C语言平台。 + +## 使用例子 + +### 生成 + +**测试代码** +```c +void test_write(void) +{ + xml_t root, x; + + root = xml_create("root"); + if (!root) return; + + x = xml_create("name"); + xml_set_text(x, "xml parser"); + xml_insert(root, 0, x); + + x = xml_create("description"); + xml_set_text(x, "This is a C language version of xml parser."); + xml_insert(root, 1, x); + + x = xml_create("license"); + xml_set_text(x, "GPL3.0"); + xml_insert(root, 2, x); + + xml_file_dump(root, "write.xml"); + + xml_delete(root); +} +``` + +生成文件名: **write.xml** +```xml + + xml parser + This is a C language version of xml parser. + GPL3.0 + + +``` + +### 解析 + +文件名: **read.xml** +```xml + + + + Harry Potter + J K.Rowling + 2005 + 29.99 + + + Learning XML + Erik T.Ray + 2004 + 39.95 + + +``` + +**测试代码** +```c +void test_read(void) +{ + xml_t root, x; + + root = xml_file_load(READ_FILE); + if (!root) return; + + printf("load success!\r\n"); + + x = xml_to(root, "book", 1); + printf("x attr: %s\r\n", xml_get_attribute(x, NULL, 0)); + + x = xml_to(x, "author", 0); + printf("author: %s\r\n", xml_get_text(x)); + + xml_delete(root); +} +``` + +**打印结果** +``` +load success! +x attr: WEB +author: Erik T.Ray +``` + +## xml语法 + +### xml文档必须有根元素 +xml必须包含根元素,它是所有其他元素的父元素,比如以下实例中 root 就是根元素: +```xml + + + ..... + + +``` + +### XML声明 +XML 声明文件的可选部分,如果存在需要放在文档的第一行,如下所示: +```xml + +``` +* 该xml只是支持这个声明的解析,没实际应用解析版本和编码 + +### 所有的 XML 元素都必须有一个关闭标签 +在 XML 中,省略关闭标签是非法的。所有元素都必须有关闭标签: +```xml +

This is a paragraph.

+``` + +### XML 标签对大小写敏感 +XML 标签对大小写敏感。标签 与标签 是不同的。 +必须使用相同的大小写来编写打开标签和关闭标签: +```xml +这是错误的 +这是正确的 +``` + +### XML 必须正确嵌套 +在 XML 中,所有元素都必须彼此正确地嵌套: +```xml +This text is bold and italic +``` + +### XML 属性值必须加引号 +在 XML 中,XML 的属性值必须加引号。 +```xml + +Tove +Jani + +``` +```xml + +Tove +Jani + +``` +在第一个文档中的错误是,note 元素中的 date 属性没有加引号。 + +### 实体引用 +在 XML 中,一些字符拥有特殊的意义。 +如果您把字符 "<" 放在 XML 元素中,会发生错误,这是因为解析器会把它当作新元素的开始。 +这样会产生 XML 错误: +```xml +if salary < 1000 then +``` +为了避免这个错误,请用实体引用来代替 "<" 字符: +```xml +if salary < 1000 then +``` +在 XML 中,有 5 个预定义的实体引用: +| | | | +|:--------:|:-:|:--------------:| +| `<` | < | less than | +| `>` | > | greater than | +| `&` | & | ampersand | +| `'` | ' | apostrophe | +| `"` | " | quotation mark | + +注释:在 XML 中,只有字符 "<" 和 "&" 确实是非法的。大于号是合法的,但是用实体引用来代替它是一个好习惯。 + +## 操作方法 + +### 常用方法 + +#### xml解析 + +方法原型 +```c +xml_t xml_loads(const char* text); +xml_t xml_file_load(const char* filename); +``` +`xml_loads`函数传进xml文本信息,则可以返回解析出来的xml对象句柄。 `xml_file_load`函数则是直接传入文件名即可加载文件返回xml对象,函数内部通过C语言标准文件操作函数集对文件进行读取,然后套用`xml_loads`函数进行解析,支持utf8编码文件。 + +#### xml生成 + +方法原型 +```c +char* xml_dumps(xml_t xml, int preset, int unformat, int* len); +int xml_file_dump(xml_t xml, char* filename); +``` + +`xml_dumps`函数将xml对象转换成文本信息,其中`preset`为预置的文本长度,预置的长度和最终输出文本长度接近则可以减小内存重分配的次数而提高转换效率;`unformat`是否不采用格式化输出,不采用格式化则文本会挤在一行;`len`是转换的输出长度。 +`xml_file_dump`函数套用了`xml_dumps`函数将文本信息存储到指定名字的文件。 + +#### xml创建对象和删除对象 + +方法原型 +```c +xml_t xml_create(void); +void xml_delete(xml_t xml); +``` +`xml_create`创建返回一个空xml对象,返回NULL即失败。`xml_delete`则是删除xml对象。 + +#### xml获取子对象 + +方法原型 +```c +xml_t xml_to(xml_t xml, const char *name, int index); +``` +在xml对象中,name是不查重的,也就是在同一个层级的xml中,可能存在多个同名的name,`xml_to`方法则是可以用于匹配特定的name。此函数,当`name`传入NULL时,则只有index起作用,按照索引来匹配子对象,当`name`不为NULL的时候,则只会匹配相应key的子对象,并通过index来指示匹配第几个名为`name`的对象。 +```c +t = xml_to(xml, NULL, 3); // 找索引为3的子对象 +t = xml_to(xml, "a", 3); // 找键为"a"索引为3的子对象 +``` + +#### xml设置和获取文本 + +方法原型 +```c +int xml_set_text(xml_t xml, const char *text); +const char* xml_get_text(xml_t xml); +``` +这两个方法分别设置xml的文本和获取xml文本。 + +#### xml添加和移除属性 + +方法原型 +```c +int xml_add_attribute(xml_t xml, const char *name, const char *value); +int xml_remove_attribute(xml_t xml, const char *name, int index); +``` +`xml_add_attribute`会在xml的首位置添加一个`name`对应`value`的属性。 +`xml_remove_attribute`则类似`xml_to`的匹配逻辑,移除特定的属性。 +这两个方法操作成功返回1,失败返回0。 + +### xml获取属性 +```c +const char* xml_get_attribute(xml_t xml, const char *name, int index); +``` +类似`xml_to`的匹配逻辑获取相应的属性值。 + +#### xml插入和删除子对象 + +方法原型 +```c +int xml_insert(xml_t xml, int index, xml_t ins); +int xml_remove(xml_t xml, const char *name, int index); +``` +`xml_insert`方法是将创建后的对象按照索引插入到另一个对象中。 +`xml_remove`方法和`xml_remove_attribute`类似移除特定的子对象。 +这两个方法操作成功返回1,失败返回0。 + +#### xml解析报错 + +错误类型包含以下几种 + +```c +#define XML_E_OK 0 // ok +#define XML_E_TEXT 1 // empty text +#define XML_E_MEMORY 2 // memory +#define XML_E_LABEL 3 // label +#define XML_E_VERSION 4 // version +#define XML_E_ENCODING 5 // encoding +#define XML_E_ILLEGAL 6 // illegal character +#define XML_E_END 7 // end +#define XML_E_VALUE 8 // missing value +#define XML_E_QUOTE 9 // missing quete +#define XML_E_COMMENT 10 // missing comment tail --> +#define XML_E_NOTES 11 // head notes error +#define XML_E_CDATA 12 // missing comment tail ]]> +``` diff --git a/image/logo.png b/image/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..48b4fb693eeed57c6ab1aa420e3da960c26a3706 GIT binary patch literal 49459 zcmZs?cRbc@A2)7Ac5xZmo2=};=ZR!*LN?i(ke%!;dy~B-St&^}PAjq{dt~M$p5sf` z{oMES`~7i+UR}QD_c%Vs=ks1il=>q@Tr4syBqStUC77Hh5)vvo5)yLPZ4~e~T{R|Z z;0v<5rlJf|%@D-~_yNsET2&eesXiY2!u%Ha8Pf%3;EsfZ*9-lJ{K>i85(%lhUrA0{ z+vo944rVrm{MqH_-Jg$c(_-F%3CXv~qe*LU{^};b%6*aVL$i1+tMLoz-Dn;OmQV0< zbv{i#1s%;Iu4Eg&FiLQCLNY_Uu$SPK&B<%+c@I53y1P zrDZlBGPo@SDjDe&V$^H2*5BPFVjPlUAV{n-?Rwm{q`dZ3>O8Lec(C2y&u;{KezPHE zr?{5lCsT*~^h3nV`|J{L?OeJ5qh41f)4~R$4f@0-smVjr%Pm`(X{EG1Bq7ZW?E7j} z!f6+c+BpXJ?7qrY78@#MasJw*b#9c>k+@|nz0ZCp!am#$+H-dVKX(O0jw$~%`JE@$ zh1d9;;&w+7!G;5h<`dno_Y*riMl5I>O6?foigb5_EdR78U$JJX7m7d3qDwA&P+S}l z^o)*em4`=-Ew~nyhY(?0VwdT+qu~h;>~opViaOU!gcTDRKC^Ie5V70d5l#EaGBB~W;fTf3=zBj z9icQCVdm3vuuYbQ7_!b90?Ge>&tzDRuL|-$Fts8acvS4HB}BL3o*G+HOWQZNQ!!M9 zktSBsP=BPVHBCj-pjY#CBO_RdXSUDCX4&Fy?xd+Zy%qN?Rdn1vXHmQl+-rS*%2|=m z?DRdx-xj#jH!$gazndeO$RC~pqjr^hP<-@^z?+)CnqjK;=i1QUE(|u6&%Y;G9b?E!uQ<4Rk0ZygxUPe#Lij{;6W(0|08FVHbd|+QtaFK(r zv68Ep5@r-Ydk5FM-IxTet6j*h2Cc>F%&Q^NaO_^2Vj==&-`^vMEl-B@CEm|D+;A_7 zaK^R#DGgeD+~_z%wuGdi*b!0I#LgBPrC4^jV1XRswlzJr@Az&}gx>Npe>ug^ekIts zjB+t^`D@9+#)PD>n`&fy%Ou8AGx(v9VBGSqZF~jlMU+EuvTD zeaO^;8ZjLw%k^{iO`P)dW_K2Dk~?(7R=EUk%7&OLXd_EKmownQ-Jyc$_{|!(`PqQt zI~jsQkKLiIq!+@v*-Feob%e3iN~YOoE%4rX(Wm09mPN+j;mET6Rzfk{IIc|eR!f&6 zRQ0?R)(xnA-@IgD%oNKata6M;afZ|h_yykDbODY_WZC)OvI#aKnf)HYW~St#@E`or zSa_SR3_tEkAr&9wQXE!rS6=*n%i0Kr`E1Tiw@ukYwYmQxyYk|g&uo$XspRC!r1pnL z*&Q&ShQ|Z+oH&uw4yW8LCYL=?{SptF7(ZV|!vyc3fV?kz@I zb=UVrrwpUSGg!ki@z?2HHoc@(O#29Y3gI_g#O<_q6TAQOM7bXwn4`+iGE0G99%k7psL9 zEt${CWZsI4knSrKWKEPn#tTk5EnnW%c8oG&4}UR6WM_J`<^LfZFSo{v+VK0T=>Zi{ z-|&4GJ-UE!8eHq;8aaetp*vGUX#&B9C{aveaR3q{rvyQ6Vf@_=J;t}_9{Z-Z{9>fy z-eQ)#78Wt`X59CbvX;rKMC)X=^gtNjD%sI!KJJ=-!_Pk$)%efG)8B>E^~27{50i{IlVJ?Vi?&xw-+zXA{CV=#;S{tN!g(-1bZPYXyGk zLz*x6gaq!I+S5@-{GWud+ADaskZ%sjP4mfOF^|XMPH~W(5%I;&5vkF9kxUr%ZGkjG zI7dX-QM{%F))}@{&h-c{6@A1g1!hXrA3P#haE#WL*aX$mr{~gkmfSlk+RMxIIgx){ zPb=j7LNbR!S6GYX{GLClPPjixqKW%5wU^Hg?5YlzUrk8b3ACDXFWMg{EH}x~dHt@r zqcoB@2A>SA@I823Q}muZgG%6f77ds?!HrVB7T0nN8X%Zb%2XB-UEUdV%2~B?N@xac z#~0~Y<+CrCyNts@cV!9q${qKTb>@%;*#H$JO&#D#n9l=be>VUq{91b;((GgTDM;Asj#MGsagZJ4+$rdz7Y9{ z5tISJFX_ocRS9wVt&e6Rnhj^`ga*|7y=@D z5|vG1zJ*8pVbjWal~36N!nxR>KEM|C3g)aWFw*&ris+G6O||VasPZ(A|{V8>=CVQ-WE z>aSg{R}*0SDLd9v5Xx63BLSZfr^);)_p{I7g!h15sYAm_*x;_nu zTbl5;yHF=>!k3Ldm`FxNF3oE{gO#uU^iDa`{8 zvH~!(v#4L}tTCN>l-Tb=^XKA${B!~6pNb3tH2^*E2jBZ!iln>`*~nF>Gdy&&wlxaM zjk89)Fv&M!G=zAn(8;6Z*tL%p^f7a|zqOFdFNP(9Q7cx^%91D{X3%B@U>_b0wai|* z9Ks7(m=Hmn3Zuq+ILT@-NphT2g@AFvuO>5f!IojaYAYi6{g$?|5ZbBHSO35h%Ec>M z{J(BKCz)P4l(@Ta_j|~rfQa=}~3W$MC2PefQPoD2Z^t0n5oZcO)a?#J>QbnRF=#Z)zX}JGQdE4WJSA%~-`UXM*qqM#N|dHMiHce%kk2;?GY?P$L%K zbKSGfiNcrGsPMCkvyJ#HJA&N%w4D|W_6D1qkEPu~XPx3j)#r)K=aziz0tLqyzDZ5c z*hw_zG9k7M{rOItpftB>xltJ<;!jTjl#tV!y>z480~(iLldI z8^`>M-dmoZsVo#Wux-%iE@oGTNKNXk(m(DIvuG)CHUtyEG0auYOUGss)td2oH;JAM za#e%A!Zl+NkEK;)><`~PTZ8{Xtx6DT-I4j7zPBX%Y1pwHzpmrpG~IC}PR;8AKtPFMK2B*-&ZbHjN~V~7vklrgTFieD;(w+BwQHFBx0mUeI;N=u&kRo!Wz6P<{J-88 zP3-*1<^Dgo39J4`8fY=<)RX&;$|g$1?kaf$e#eD||;OnJ8P zdh!&&rW9}t0ghRJ21v`~JxdmIRYK)rY9_OOOiQboDPyJIh#A(;2L$8zf$`|suH)LCXz4rt18pj4I1nxDuRQNt&{qBRi;Cau1lk$SKN_g&lK3gYqJ=J9aI+AU*Oz7Dp@xk)hm>n@(^%~aT||Q zUfhZqOpwsSe3t_j#dW36v3;jSbf-y+@XP(@jV_#iQj+Vc6HYG&!{*Dchwz6&z7qAK z>uyfH9H+wR`jSpZW0_p444$ZENujN#He9TfT>2>IW$OX4Y05~Dsp&$Ye016a)5pul zI*v#BO}X}`{fYFW*i=#MR-zkhJsC!6|23yS< z&S>M08ZquKQ+KR6ua8#T;*S47%6%i}j$=T^UEWqW5+6OsYx~^bwKLPxBW=-Qx4J>} zcBR`=ZKvnSlIA_m+&?3OI6;!LN3x1L+Mew+9CNF%^( z``PxZJ&;|+6!XOpg)ulo{DX>q(G9PTnm=ZAMIU%@K;EETC{A*r&I`?1* zyiod7z_a8w9A0p{1KcS1d;3MBbN;PM_1d# z1T{3&p}x?td2@q^jhNF|$a!~my3vL_T~%NEYo27#2E3$CC!JC4HE26#F$N`opgL#1 zJT2!!U!8d=peh=TNB-#k2jQru2i0BN9#oV9%swl*!tOucxUOg$Jhz&B?7zcr(HFzP zq_(@UtJ{1Zfq|aJVIpEPli z@tK;p&T?dWl>NfG!X6bA;dg9237@qb?_4f(naS3Z+g-Fmo-<8$Mvl!+b8qFNiC?3V zFRDt{7cL8=f;SawmC1-YQm{hZP$_5-XN{3-YbjJ66M!pO*yRY3Cq62fJ&o}yo3lZ! z)lOzoov|y+StPWW_TTHk7g~JIQTFiukzoX98vEmCTh*=e0jF~n%KPg@@tmfe?`D-8 z(j)Vu#2g*;Yiaawqlp3OLCGEH56)k&0TBHfJ9wk}#mG&j6f%5K6d^x%&-Var$%E9- zV;3AR9Ns1eJ-w;%*IFBl;I)jPgLNW=y;g{!!S7%63)G^EQm%DuiX7Jg;heY?Pu*koG1;dkEe&N_U*Rnyv zKlt&T@1fJXrHCVbokr`ghMmvaSX1QM9|n$!IZkQlptvUhD55J2s#0lTy3}lZ1JaLgi9(THlW=0CB`^(O3Tdj<~ou29*y@ zn!#Ktc3I1UdF0&btb1+8$&AIYxc7|j+G``niHV8S$F$~szB%VB@shc}1E~r0Tb|x) ze4X9ZO9AKJCdlU2mP>x8eGhr<%LO>6JjeWTHLJppC9tV$W8pSdD)pWkwF012v zyQ&0F_(uZheT?WlcF`QU9m5hv4|A&qLabEM=Y6M4zNB+#t>Us9Hm(26at{2FsHpAx z0`&Fsy{@hg{#tf3RzK$saRMlbc<2E+r-exYga>UPHcw(*Dd5Vj*!{1H1EQ20o-riJ zBZReHCb8qIKNYo{0q+kc4WWmwfr!(r(Bp|>hyBRqmhX;{R)h|G`j5n~&V~g!%aAr* z|8dmzMJ-o!j(ULk1li`Dsw`PfstG@fE{g101-iWA`|9%jZWaBN^3>-en3jxx8#KHQ zBI;B3e!Nnc97`_;WkY9BoX1-NuqUQVIY+{4K(^?_rRv=>BIKDLW+arq67k%eG;$Vi zD8tCjrxNqe3s8t9lXpf_9919^7#W+3oiBa(s&IhZ*nV~9T*iTo=k)`sDzYPK&fh;AYXldc6Sk2sDE&QYH zn)&gFefE>+w^4~!E_O!Ae#~a6d=x%T*@Pbz19KkLC{mEdbm)txmUO!CnY3-0a}uTS zq@5$^D3M%IxtPS2VM!n0QgN5&t>u!dF_a25mU*^WHt926DO042?M4G__!L_4hpJ31 zx`6iQ*-A5l1<;D?BnCc8D@5z0Onfy)qPhHg{vLs){LaS4Xs(F;4_M$^=B?HYKFm#- zw~T(|Ifyr0t{m^Mqr@7uL!U5mdDOsvEC@c-lj=hlvM#;JskwC&5*;KTp6hbg8V0q|@e|j$6`APU17k`Pca;_VjSpd;T1tVvT zE^8DB6no?!+#+|N_tc?@{F$%@hw2CRA-rlW{+U-Z_@1eIBb9y(P(Qj3dLc$q9Gg*a zCgr})cy86Ph9t7PcYgE*t7?>NXl>x65biP%RRvcr9!7C|)}8A(#BTdPD+sPIDrw+2%}gKhxBc_GuIEZp5-ms}euf)wZV< z3}c5IH4)qqL1v=_OGz5`h=y&0viXu`n6DwXQ~sL7Gi~{@@AJOC^7W8}32R z2g}YEDV7ekD@eombHNTEX53=RK%(8q8t4>whLDCAd2(`c(BNvqDtYT{UxTOA-q~UxS;M{lRc{%1W?@%GwuaM~Vyb`4e?qF1K9T-b47Ft7UF@=g|00RRw6tXc(Ib`-n&NDr)G$wjYw+O4=vxFwtl~THAKDYd-!wC zrx*8dZCFo-JcHk1(rxVle%hlyc`7`zKzB7*bR?*!1jy;u|GqYGh`KLIZiE6@dqN{pHQI$Mwi1oZs2lorMC@_~t50cC#abL(B$C;YnP^35K% zAkS#+N1r71moQ!gdH=;s+6Hx}^Zt8XSC?RUJ(jU~G zxK>>?!fe(-_fXVX(?-6A9)DF3pvgO$4*~sO%;c|wlUSj2&`;e2bv*ANjCSUJKB7}_=|2Zwxetq<7T4yWbaZs6iT#oz^e#M* z`fqEP9_$Sm>3a(PkhZ>T#hsOg__Kk-eXWdyW{X$ zbob55Mp>S24wlPuvYNze=b#C?D4fQW*b@90?#iZ$NhZU&A{nIBP1W`vuSx@WTgP6n zR1mo)ML!8@i%F3wd*rt0dVF;B@D-000MiEF=}b&aZ0pX7l~z8*Q}cVAqE*#Q))=({ zo?4dv!1SF@edoMbqHvm)n)vZQWcqlxc)A{j0A$%PRN<;Ki@C z%9g9&ebfsDw7#>m6VL+=0NtJ1wUR_69hb$&qF3)pIfSAPlgt*sS z`-hs{vri&!`eU2dxbZe6n&q$ZG2 z)UAJzSYeoD>lTmohgHvFhVC<;V`fWE9ECP)8Ex|3dGu3;^tkNq?ocvew;wKjFKfSN}x{x%D{+g`REiR9YpN&uI4B5=! z%L?({DDt@GE!PGeHI>}n?rz^(`-2~!_4U9W=fg8NJk89I9A1~Gq`R47@ozJe4$A%e ziC?CL>uC#VU9YY347ZJFjv(E_hkIKLzv;+b%MPupD(;#K^E=JH!6al_wu_j=E<6OhQa$xXb?%t<>+E^Mmfj~I>N#9X)kTUaL ztmL{NdWnaW*aC&hDQerPf0|Kok^hgUuxWO!4C=jL6ntIbsn31})2S9ze<~l&nNrE^ zm}nW|H8?F3iTjDrH7$a9LvIO|iI-L4Me)e!hjA-I`}kFR&-Pq1!5zb&i-oD7e3lZN zK#0c|QKNHkOZ}^XuxZ2Zb-FQPN>VJsuaDEymaS{$IBmxQ!A|m&D%uF0Zc49&VzJaIr4F;k-9~cn#)_TO7=7ovJI4CV>>g z*`YBl=FuRjEYKH`bG>jjz@OglfD3KzPrF7vK>dVWF2FE)xqxEALT67&~ z$2nmXcLQiA^7Yv=G;4s@ily+N7)^p9CIgr)VbXm`qpyYiwH<$Xd-*$}f2e*P$1tSu zftq%KYg6shpn^YcF-JqS*yn6<)*m*(xa?%;AC&30HaN%#$an`KtAMO z89fifrd3lq$4YHlW|Y`8I!$FsoTtp5c+9m1++G0tDndrFU*F=p{TxTm8T@+`ii4Lp z(%?JrDJ2rb$H=>AO2lsCU&KHKps8_G?Q8%W?^MB4`>oLO3BssFXk zn7DXYIy5k!!7Szc4yklzb6C@dz73Q9Z0B*+V%1c8-<3H2a)>n0Keq64S~t~v>0Dze zKz>ab1O+P5RCIwttR2hP+XS>Mv>W>Z*i9?}NLE6i1+s`RA3aVDE(XC45h&P^(#gan zd!w4(u>j(7btS!EE7l>Ob;$&nR?4fJ%71vrn=~p6Ji6~>S_{RC!{ZjMOxl(dF=9Gm zBh;_7**=fBP!b)Ou>upgFj!jsT9$}Jb5)vwP#i6c{u;wBD9|`sKSEts(N0>W-wFT2 z%^@H4v6X)C-Cs+?4*)#mm#N(&koW9k=@Cufv(V3S^jW+j^Gaj>u|N7wXD>I7x9xJN zc5(9U!%j`MnSlPC)S$18xOur_VDM#)z5;yUD{D`Zjv*L)-4w;wBC_zHDx@*MTlp!{ z0loXHEu_O!b5s_PT|EBg31X&abaD(R(`Jh+CU1;H$}|rq^3(D~JA0Lz_qUr7Ib$Mq zT8YI`tMHq8{j!D1nViw|N?X?Y4S*I3;RFU2tJWa z3}4#+F@nMC|E@4QGA_Z4m?^3_%iD1EX2t2XX@wlB2;SfYYu4gw#TkXqQ(_Zl8$@s7 z1~evw3(Sl+zz|8j5r##~KMPa?5v3*6A`!Ao4J6>5#nfY(DU{Y{0*u@WB>&|D1OwxP zJQcV2@0>PQvVngq3aMCWF8;Y;H^Vs0x0B^d8}XToK2b*d*y*2)6H4oL%g2Xey~R^+ z;;<5dIF63GWNA!NjTSa3dehk2h)4)VV3k(he;8VsXo|E90s*baY|4zp# ziNw{}eXX(@gGL1%-~7r-?5!4qM(d81-z$}5cJ58`E&OV52ftV%B9ui+7M1Bo2s6^d z&%k9P(r-|%{EwSBgYxgVzjhI4fKp%KbC|Vo5lVfjP9)X{NR#;Kw<16fQ&Hbfr$Nyc z;3X>QRQEpltXN^cliZiOaoZZOF6TSZBck>`chbS1%`_NECvk>02?cVKz^VuAx7evT zuTs?kMLWV0@Q{Hs8!#~qNIHHiJYo;1u~)A`ix|?uec5C7#&{xsJ&KckHfAxyB9*4KCM@3duNXQsw&rvaTFfhMFavfUD`(!wWX4Kfq=^LrubCE$6k ziJ6MxF>c`-8Y5t6Bue1C(ErDmH~^?F@p6W=TfU5Wo`BnS-VquVd_2F!qh53%%sDL_ zP$Lr3mut4xwuxex7QLMeJ%LZikaJsWE`9)z)>@i`2zgMCz0Y4jF8E)cP9zkEvC0lI z=wPBrff9n%a{i$v7c^$HAQLS7Vn|6788$j6K;!vrvL%a!?G2eSU7Wc0RgwEA`xj!MZGZvihMg*;(T|S zhCcAS_bpe`O5_lR8tcAE;Oh%)KRl&sv6$i`z0X6kkH`W*rz-($ygWOIY)Ye~kKd!1 z#}Wm&W`v}ab2Xp~@X&Q=@Ux`7I1{NzSbrloNeW8t|Dp&ORODcFm}nTS;$ z&MM=qL7l(G{zJnr+V9qld<7LcsCS@Zp%+jl#)5D8Cz8ni8)Ym76ZrTB64*`m z!xN@7kl$rhAU}O6vWluo@4ii~v{MtjLwu>D}|KWWI9CD-e z;(BeF?C!#p^8*naJ69Cb%Y*c*pUcrYJ-jf_TJh6tUYj`9GTU}$**}CejuBRjy1zWN zZYq%E;XFBAm+38OChmR`ilCud#r4NchU_*C(Z_l}d z(3@0+ZM1W=Pz0E+srgJeJj5WI_d9{BPy;MQiTWyt-pRw~3wxyApjCN56HCcou^JQB zcN?D{^bR~!SqBu~!nxKW0+KzH8E;lM51JW-sCA5;9jiR{<18$VRx?t5Ber_C zaB?c`)c45mg-hu1v!I7A#Q-nVbI@N~^H1>QSlzrvN5~!GZ{4607iiKbTU^Jnq*2=H z3_uy~A#r6BBvPhrl@Kg+vX9s>_uS>LhR*T~nrhxRiibq7yyqOf-7eS4(7a$wQl)}R zKO0UWc-(@>gt4N|q~8ej@vu7l@wD~my`p{9>=K0fWTlIZmTlVOqe7BDmj_=!-c@^= z8Mf_`yZQy)5oamb-Oboir+$7Kv(%6dkJq~gLm0sa}b`eSCH zT7q@CWKHVcSdRWt*=+sBO;&7W3qFmF0dX-tRjnRR?e$3%^8Y>I)cHZ8K+y)0eq4Xw z5eWP^V<))W@2}Z@%&Iii8l6NpWNig}d;<6*(| zZv4ossK7q}QZ8nazjk%i&^Au>#o&H9kR3}xhhp~=iXxEfx53B+&xM{6H}jZg4bq?$ zaf=wfh=yp*puA!YpanA#o*(!%lfUB5^LG~|YR_e%(fbwUTG{@0?e9|9hy0+@8B^gE zm38QR2%Q;(oAj{3et8iBUBej6N?i2!!gSj?~pGovs~< zDY1+FUg_G03rD#dn!tb@p+Uk)sr=i_p=R~Xy#~mw@^)3_DETA-yJQ6ZOm;Ucg$Snf zQ&&6cXy^6;^HDD36p)?2ORbTcYI?vEBuuBsxSt864B%ty?5lZux4Mxx(18Z7MGFL6 zIGO@frTCciZ*4VXs>;Xx9{7jbj&sov7kFO-V(;<4F&Q}oBJ?R^L&SV?5<9h$MP4gB z6;>;4MNUfyij!a*>0PW&b0J(92?1bJ=b9 z#z3@!FO2YWq#LM$6Y>+d0nn6?psCbN5FgPiFYb88#RwO~6?sj|!+m2ss5Ch37_MIM zx~=2o#W;-`j|@Wn-gX<) zD;GQq!YU1F(6h!CZQJUGjhjOSc73kK)vAvo%trq&eL}v=2o-jxOaeM@0Wzp8FB-=j zNu_h*R6rfKzOA+W!$`cq+5Z$UxM2Lxlz;2B4np`Jfq4)uP8&j3uuG1L~Z-mTeVXh@nb{13=szVjZ1T*w}F zha18p06+30--l{A9gHqT__wuc4@P+V^Ak^5Yk3eoL@)>uzZ@-6WJ^y3haQ4B3JL`v zlKVKskGem10}R;o{CSLRVLgC?=s|{aAcUsucrxH4j zpwHYX9z{El1AY`(wwsbh6fVw+(C`)jLnt65p-ZNBtNgZ1I}izQ;}EhZYB!{xm2#S+ z1AJZx0DW_WP7UX8#JC#APJ>@v=y_R^5k#*eHf`a@RTW2yz!ZtDUQK&*ym z#T|8$r6nxE&LoyCM_wl6`|W|^^op$MxQ%oM!m?2{nDH@eyh<#=*Pfy_3N?X(hX%>J zs`59b^pJ+KhcA~{*H;L7x(Eh%j!6C?EfmN{Db57y$+clPgMDsc__9Ru0Hx&lgHa4IA_q z9T6X6$>^J*D&k_&Sp(YzIzciC5%dkNGp*1T8Ge!&hKyj&`~b>?SCGj&&wkzZc+`xRfeLX_#iT~F3j5N9EYHj(r;ZT^Evhyfhx|P@x6<{uuoVEAX zab!qn1M&hzkRW4r=Pl-sD2OwXP}@2jvp zqyan(%pw3sf!8EqHVhfO%5q*X=W%eBWq^59v+^b%6=2dHDIv=A5hFgX+ZB)}iiT-x znFx8JWFbcxw7B`s8cJ#L9#kivI_v-!waF+jkxe>0$#|dJrh8*_DlZ1m7lGGRMe>kq zw^wgcKX|G9VA&R_L%#R9KEnaGpmVO72hC>b(siJx z@`puDdpdTXYs6CVDhLa1zj9E0KG4l&3pm&jP?bOi{2JJRy965G0oBWB(BT2aY-cIn zAs-OR0yTd?wAt%|A@}RnD~KxHY=8O@*nYr-_@$X-_X^GHx5a}7E2rIAQkpyQ!O z`o3Jw4|6jBn3(+76kiLQh=`_~?4ZOv0!iEka@fYN>4K0w(j69Hvw@xPxzoXNz<%Qs zpC<)AR2gOJj)GFNty@yvY-5Tdv*JCu)suhQ=Ji;a{g-|5>H+^O5e}@&Ujk^+#JEv$ zDj=uZ8~P?}UZe8XjYO~&HT0@K6R2$QaxaqtIf*m@;rco{Ei+YPz^hFXvb*cF`jXzg zg(Vose<1$Zy}oV-u3O;n6w2Ghq%;<4erdr4hvMI@Wq8429$Tb~BC~Z9HETwRva?LQ z@do9UB-63(C+6mt=5h&cO7)*>G}jd-2^(Fe_q@JX+c2Ez>5ICNu; zSPFjSwx=0Hp9g(W;pEb&YITQDZ~wPS!zh0Oc%`KE{-rXCRd zfJuG=3q}P5*a~I4@%2%yGF7eckpArd7j(UICS?RbpUKu!z9h{)t7c=JW0M?eRvM_! zrN|75TWuL>JWzB+uKS1r0CxZL`yYoOK<70e+0Wgmrg1MF)&ZxjTfZ$6DHYi)AJQU- zBKpDKF;v%-k83(=U7v#Ixs8h&V6}kbPo5aL&OwF~)(AGpP#D%5@>J^P*B2J&sQ{7! zmO>k}4K)+O&GtYa(QJu;w_h-VGOMyVKmr62`e&`;7T-Ra=&%kwkR+gi588TCny$8S zQ3>>$4MqN6bp1CbQi(EZlE4ANmLOiPnR4X*8r<}7mtN`a&p)bQI_6xBA6@qTW86P5 zUHJdOTB~&D#e(3mBf!~|cLu{ZJ0=TIWG*N^%nd0wfREAz1dR9+fHeEHa$_m(&jD27 z*O;_>`_p4%$v%otDa9+jN-$&Mob;|I#VFS|_Yviyd)y!lWQDDXAguq#L$`=|@7u%* zB1_dF?JJe>R2f#5qkz|p)4*zdpo`h@a-=qGe1rAVqcl-(&$Xb9^s2`^4u8505gN(X zUh!mVd0*a^t>IOfyvEv563;8_eOZVvy}Wv#Aa{T|1SF>}vz>#Ch{O(0yRQD^?D>2G zcBgh5*5*Zat&J1^pmN#x#mfPd#|n45fhcLk>)7S}aAws-r+?-3_n^xo`k=|P?cba3 z?TTuP1;*A_zk(3wX%#XKq-)FtbM&8f_cIR_Vq9j7KNxP-vpq79>}L~K+}_`G_KtU> zoLbS#$>is>rn|ml0NpLPG4o(R-m?IyFlAF&%b`zH{@ZP9Ic0c0KTWR?Q?AZI{_bq7 z|M8Wf93HCKAVYOUa}cS9Ur~cJr)uD4Bq^Y!8Y*a%_I@O@1@@GrT|4giLeU@R&#nqw z%`d!$b%wAS8+xt1C|h^jDbls_y?ac^)3mZ;Uo}LZyV>tFOrut&(`Mi0Q|A;_tG=eU zfAy6;Qm%3V8OAw-ephf6=5Odk5%%0ol!}6!s zJr=<|=0&{}sER0zL_gOXJB`T_v0rXEYCAgRFljvEmP^T>Hvx$$4v=DkJ{AL4jd?hk z*QcHWqU%dygh>et$1Cl00cb@1dS#14ZH*9g_9Xs{6>7Oj_Lc z=?5SKkN^cxRId$%xSX}ZS{@!`CXUvIA2GHI~hu!tqb^N7#ovL4{oHXmDsajvvXlN?rI zW$~ow0YnCfh|uFkdEKuas%?FmRozi+k$v;Zxl7U#V01?vBXN=s&S2 z?$nr*Qc!3QNvfKr?Y;<*Xa>?!|BEAqiF{I=2zRJ8B^Yj>eb6CIw~(47;cMc=~! zrU1?@jL8~<_f@+_R#v`#A7c>3cym)gvPo1m#(ib1>Wx9%9ONv+>#z>p%IH3EWP=dvfJf&d)YVp~=~0+}=N z*LOVYl3}G){-s~+0J*@nUBvWq0-u+Rf`jDb6`+vrIOk%ks*&)1ak7Xh^!B#Wyz~jk6NGu6-??%B1aQjQC ze@HA<5KK)&G`(Q`Jr8@NM#$(0i+~V_Fdq3v2vmRvcI@B+Q{m9xi|9Z1_4g)sf(kCILd@UtZb`BsO^6EPhmutGdIE!x!ml4>|l|!(quZ*7v?7T3$d@rkts<@pZ6H!w6B|`)O_ul2MpF5GB+@Gn zu>qGp0>2eRB8)UtvjiNk{+urCwGE4#UHz#~$OIww4^f05{yMP~QuVmQ@2tTpUh>z6 zuB+qPu33*U$;-=A5actY#8!#I_cj=3V4NaKeUuK8fj^1*0AaIjJR6Es`@ZMXGHms6 zyGz`63tz|F*4=h?(!WB63j6X`&b;qc{TjVaPTHriR~U}R?+A5hb7DwL!_r&4z*z-sAzI7mQg z%W9A=gKYVlsOq03SP6bU&KeY|hU`nqa!vB8 zZEHvr#KnW=eQj38o}Ff^=y~O?W<6IaC+S$12Ul$fAEy~@D2JxxgKQbVeZPTQLE>|c zq7px3GVy9P_}-fX{2=vP!&Sy@2MMxxzCX-8C+rMjC`G-%Q$>FT?0xr~GWqqeFFHv? zovQz$2ZQ?JSA5V#I=OSUxPJ?{5+jj$ec`SpGDtk za`3p_({DVeXOn#HYvBs7d60wHwpe#J;6ATaoAWAQP5GpYk;`Y^cL)HjeCySZ4TIS9 zM_j(~V1!(fA&k=`J-#NjKhO=Ip`mu-EI66Bp?+rw?Av*8g+v1b>tMeZ-!$NOBB*os zZI*MVflhPZC&^!!tA_M{W&_uIFdlRqY3kIu^}VtQ{M}2fdn@k(tXtsOm&c`8)vn#h z81F91iyi~9d%HP($2&WQ-Q?|2AG!0ppPQAnZ#P-1NVOgRbZ=F;-7}*Nw>kJ?dg)e0 zAFwdnP+93P-C*T2V_~vtvB&CN$N6qQ{J}*p^;J1mZVxX8_M1p*KUk^}dURPRp2#}L zeTp8{^)fxo$hQwT=tjjiK3btR4LnLg62jQyATt#xqNm>6&z7SY`Vrle~yFRp3F%8 z*-(ond7#^Tt7q#cp}Vm&x>3qjP5Z8@^RpsWec{8Z#*pgr$0zV~vOMy*mlM4Xrl%t8 z#n62K5l3Y9>cIU!=v*gnysVrJIz8$V-n*ZdJP6o`6Wt{};9Sb=Io`TD??w$e|Gt1Y zKVSQrm7WpXR55npzTbmkAs?P!v7HP=Xp)ZoaQxKz!uJioOCI=7pUW$ycT1qob|c>iN;FTFI!{e6wA=2y z^sPuxJYC1MDBI@wM-nq`CKaZJ6=2}}6|Q*fR$;SkdAd4ocD2bLO+7H63ZyO!aP>ry z!u5mqkof76BGuW>UZD4O{in3fY!pJ~N6+TA9(!MUcbzX_P>bzd9NG+K6yS|V{95^B zQDLIa{97^7L1y6~t##J#yJHvPxYk_R;8F1BWagj?!n`-6xULTb&f?VXp2Y92-gk1I!JKW33&{OnhFjE=a}J<80E;Ga#l{ zj1yZ5Biwy+IHdBReS60_NObYtYV?=>oVRr0s~iNZjzuxZ$6x zjDq8^vFPT1h--&^`ua?V+3&K8Lv2kg#5rKgh(oKVjn2;WB|*_A!%gu$-{C0c+}~9; z;@CRnwWzw3bIXeK0)!(lK^339i0iY z0;JS%6#Ilpb|@ z7vm2H+7tjUp`=>!b@LaSfyG#lXGL0#on6kj_e*LBKo9hOKPf z#s$k_Hpx5E5D3A}9=$`Xd!k<3@qrf>@3?!*D>fIsJAYUtmRy_~v-$b?NnA#$U7R7d z$c=cAr-QEkT)B;@=3NfjD8$-7GafplHE5gy86p1fPVGA?pq~<|iMfJIbJZrQgm=Oc zxLXwabm3Q=w@Vk6Y+dCoKHCsvhP(EKWUbkxGevY2lgMJ3>3=Rysjq&2>YWX4Wclvg z_2(AJ08>(b&t=vtiG{l5OJD#|CQ(Rhi}tNynvT9tfheBc1FoMaPrsP{+1&3n*~>P@1VihrUr`bf z>l`>{rT#Rz(%{B30@mS|@PMDJ{m;f$L!#?!A(;TAUdT5559W_UYr07Jy(-1u`YBY( z^k9GgV*#PY3Mb-0y)2hud2TM|B&_G@l^kB{C|UFL?sk) zDn*$Uk{v=3viC|73fcRFD1?+z)(P2rl)W>uM@H5md+&LU<9EIE{(OJm+wb?+yL5Zs zyg1L-^ZC3U*W+=&tWHbS&395_ihlJ^J3n7q z>tW}lsNgoqT;fu1S+1cjs=Rn^=xd2e_HO;7!OZlA&7}y7aFG#gVL_O1Kpx~3v2M+r z$+0eMgg44+n##5Wb;g3lKa=B#~SvjGWMdB10zT-gC5`Yu+J804q{Y1Q5iS0N+NCKTlfZh_7is_MX2e%- zbOw8DBt==($IGgHC(U*pp6A+Kw2G>bb7j9fVw`FdBsh0nIIrDpibt*ZncLruZUaF( zV`a4wyOFN-KX2MT-x|-UrIb8ZQR8s_{O`S9&9A~19zIIr? z8lH1(mGEHTidgETu7C7r$9-8%s)FsVWt97ucOoeAy2^~gS5j&V3aXOh=7Nfj^+AjQ zVrv`xIRlhgO z+wX6>meV)4vIck=^tu}6p!q0j0wa0zJ5JH{a=23@%?dLj@lZ@wY1VKyhrsCT;{>N3 zHBSSb&D&C<(iJFf$DGkxiS?hWp$@Bp4Q5?kxOQpVOZ! zuQ{#!Y57lJq8=GI>^g14F^3H`>Wmj-Q?u;TFUmy3xpV_3r#?x=1>-kb8BfQL%6K>? z+OmfxlS2*E9f;nQ^AjSX$(xRK)%U2Qalu|c;>1@dCY>mzzw`(ry5HVsfbI|{Nwo>w zo#aLrzxZ=o&AMK6`u7(e(~9k%)PvhXz$P+j3f+d+C)`zC>)vs4h#0FXKg@})*&9zt zj_FNuos&iiiL!&^sK-7OHy!Bsyd)EAE&q3Pb;-khL@I_9Y1Llyq6}=5+@O`udG;&g z7{4NhS8{24gHq3C;-rRg@$UHxx8g0j8Ju4~@BA=Qi6|+uA_> z-YxY``JCknocZQ49aeKk z)uA*=P}WsP(+rQ*I(GiQq9jP%`MkEl{v+{5_n>pGWZJvUb$tcY9m`n4@8dp}u|YkW z)kEiu3kQbxn_WO99BT-9*CX;Qp!1XClS37-1gj7)pugLsq3_4ddsnV$JWdD6wUm9MWQRz!!U$ryGvmw6p5{aS#T{c+<4 zuaAmK1j$CVV;JCmMJtz`xsA#MAji(1cC^Pvr%YjUL|-bn}I=6(BvF8o1B_0Ctp_Y^?WtBq`}LtY>i3Hy?6i8;}@nD zV zWC>5lxMtZ*-m;x)p;ovnPUCy4bZI@_7VS1U(msMYIBf$5#-}5SIfmPhNHp)vx_yPb z)IlfI*|46T&KR1mMfR1|d+-o5+k|t-!pYK^#T)w}26^x?=V;hP}30?3`Z$V+P4lCf!-ugZib30HpXOUgY1#UtGZg`ogtyE<~3&&Iv z?`UYf|L$OSLIbm!A)=IEpa{Y-efbNrXPZcjjQGIL-jV0jbpjsOq*1xo6;ToTR`ByS zdh|GM$%0U-*X|1;O>w`RTEw4ot%fq+?vqFwa!TIcqZQZq=Q4a~%daS7W%!(?>+@YY z-=%^-nt`{d&AAc^`$u459kprMk&F8Enj$K1P<)1cCxV0$WT0(V8s%)3iB)St1(2FyZd(IeK+nh{2vAO-(i>t zG$kd!j9Y0MrzIM$Y|wJvkIQ87IMHzZxP_|*w}(@TAS&Zk{Y#Y{a?84{FH;25eV5II zQ*Z$yKcj_do~sKkGKe!cSvLMeK&>4!w_Y70!;~70lJLmRS^q4CCYVI%C6SrDp8O3W z=bpko#~@>I^p$y;_xSz`rtST4tEI}beWnTKkAG({O3>iDd2Ftpts3~tjM{(RU8(*% z=%W2^r3RYa+SgPn|CLK!P7)3Z+3KpA`tTOS6{d)Fh&^--pncLaG#F}Yh~IFRq1cxY z5H9fR(Y#DHOn|-_;{(59zSR(e*-M`SJk~ymY`<7IkdZag)m8dl<=8G(`Llg0Ct47! zYsm;Upf+s7#{WGN|86K%Opp|fL5Pi-7yWI(eSBa~TguAvqh^hD`s)W*8@NC+8MY}3 zE%I)2^Q_%!0uz$H(L3F1D@aTyou;Ql){KEHU4XD!iwlfP#%_@~pu*DpuB{PAZB&84$N+jdtfzm>wd`>Oxrfx2Gaw8Lt z$?a!|jLD+vAG7GnKhG{N%WTF`xzGF#cKha6N@{lm9RAA%9tL5-50DgkNne7wR$7UgDP)iYGXuju%6=m+k`^gXPX&St+HoY0fG$F@&b?jCk=z)^?Oq$oncO+v^Gk}T0@I?zAikP9w&{&!c50;P0}Hvh`(=s0n`kK{F; zDB?6aU)Vti$0e#Vm&gQuncACgbRu${=38=7sBiCgV(rp()4gvGy5Ab>jVqI9w^Jg= z*P6KjUsJ(W&;mu=O(Z0FKD-EiS2!^jmTcvQ^iKh@5+lLppLrY>`V)dhwP~WOjYAW` zhkaz`omtq|delN?Ky%^0(8GV%`TKjqy>tWS^o5HaGtnC&n^k*9`z%!PQyRL_6r*DK6#hV9Nx$Vz4eQ$Yx zfk%HUFCw)j=i7`8+4~u$bzgo|FuXUWkV)vy(8^Ehl)8Fy*=<@Vf2lX1p+@SzE%aXr zDa(idcBc9knE7>x^t29LDYj;N%4w~=RJ>V6HqXiiO`XS~1$FIC3d_0O4D08=0~?rF z^OB^1LR5L+jSB72xhk%!#8)i_qA~~`8Z_F+n^TR0MS2^WUzK+JwDDi~jtzL$@_;ah z3K4BB@d&P*JpC{SV$CM|f~-vBbl{LNsUEQb6Kv>7q0o|FOf*dnU`adFojAKnN4d76 zl7qkOWtg(-qOmj;y-9h(lp+DB31xLM5#?_}3hiE`AL!gh7j&Zq1vo^XYXm3~EJzn$Ktkj4viR=Q!u;K9L56}Jb zfZojKy3AgWN@V2E^VDrJ3_E4c=~zdwg;sA3S{?XxD{A<>Bk^4ybhd@72o4VE1h#^D z_|z5*xR+mO>neCwx0VDs<6NOPtFip%?B4gImGg8*vt4@9lW2-X^~97N4j1>$f6q3Q zBO?V)6|Nbcz>MU==e8MaPJseXLOl7{`#79c85?WiF5**hwkYjYx?NqziLiO{ zvl|@8kUd>w8RZVQxPvh&bkR(FK*iSg>==-0%Ch>5A3pqyMUC#;Plsx9#u1NwrxEbF0oe> z+uwl8;w})2{`yQVqYrSkpanp%=|>R@1^T?$cCmmMOEbweaH$=7ZQ=ie7(iEW$}dwg zeY#*;mwNf(F&YCEAK@JT$o;oePv$hE%WxJf%VT?>MCTvu{eJla^?Hy%gS9>osNCg8 zn}B@XWo6%iW6bV87O;$;><-3j(P%#d7}vJgN>z%Iz@SyNX1U9zJpZs=YmbI8=lAEG zSJa|T#oDVRD~NzhYOvN7!Sw3q82}3?lg!<>vRbBii`hWLY~LQM=TCEA{K*q>z%sTI zsabkn;CU7e=a0RPlD)lsCQz?(8<=#8tj9p`_}K*Vz$G1*9IOX!v3NRGGk`zFO{@9F z?d@Eg$Ih@-%NG&3u=Sn(1~Cs8p#?WFIPtcJ9kF_jBhJkR2PMGh;7EgeHAvqAi%#Fb zqc7m{F4wrhvV5}_3i{=&_7@-Qhn0QYR>=)^nlvin$xKxCl#^)6Sh_WpE*}}u&Qs;v zAT@pn8Bq3+FWk7U6u&G39LBYFx{o8K?vb>7qyV%EQja@dRW~Muzukhr!Jgy2pN^_r z+xPYkbal176Ytv47af5MiJ$UI7yIks+NCRsD{k+Z(8EKEgHQ+8!Y+V)cim#)9bM!z*KQQUlE?R&fXjwf#EiDkyEsYfDEh^=skw3?qJXqSTM zy&5qX$Ujm9@BuZcr7NYA;X0Ner~>*yWT8Vc*rkqrYov6As?z1}-vDlp?b4A%sAGhK zl+`T6Zu%oEu57ztdOrWur)Eb^Kd6nDk~5vyxnZ2NkVfWAk)_qS{d>RVsbOn3wWDBx z#kXz~E@)r6(2aA66=1sPI%jxo6-mlw3lIZeqaBuuMctZ7+KX1aGYOL>=6l9dXd2S# zTUTqei87Eoa-@Y*F3R0{DYL?gqJ63;OxWQBJmBK@zP1=4$N85(s_EqzvnVNu$bXtN z-x0kRbRH$10N!W}4I|Xp)Wd5H?@vEG_>=&dOJ|?lZ}1OD{UsO6n2;F++PD4Gl& zI#OGdz+%bcvQ}@E*MC8GombsBE$4>Hma|-YKPQOJCRDm{=um5D@S#MUW%hK{g&l0M zpSHi>*7ykb4oMj%=WlGPM+y6wosn%1sNA%33_a7>j%TEJ*p2xF0O(Etx2W;S;&a78 z`La>iSF#HiU1s857RGDCiw<(KCu!woug>ao!dMeKdJ{TvGcsZ&3te*Ad*i4ByT$MP zL)NvN{8&8f$Hy(aAlHC*$98uoDBM{EC4TB-0CW(0`$1NA80y?K2xo=Y(gSP)SdhP* zu&pk`-2fWxGc>cux^M4;DJ}*i4_cjmeN$o1gWtwL~44gUFh1~gnKZ$MJvwjuKM56F@OD5--_X@Z}W~PgsUHR zNFk8yVR&vfy)sCaRh5z1!DZNSxYBfcM=g1ds;&Oz`oc>}!cBKBsF}Y2(Iv&}tLef7 z(p*<-G~VB<{-Y6HHb#hN{(y+|#U4~0SKB`+7EZ^CvFnvPM@xe|*uOJv*b8h6*Q&`vYCzBDSa2|5~D1igo6&v$+y>8pQN%H+vk1)pjhB zo$ew9;BhL!8F?!gmA&utqjY6Q=6TetFU=DdQffQ~vW6JiHD-MiD#nPloC>+m^mrW>gF(67v7XqiU8st?o9PidO2SE2dOr&z<(^=C1_D=98Qfb8Xi0v>x1L)f&dYn^eTtEJ zma+OXcU03cCFq>W)yXsWQRR~gOUYOlcE$3ezI;283#*J2~4n?Vge^a-3fvI zG6%5tz)BO%o9Bsr)%1(?02bRlkzH7RUX!7g5A%f0qMsc@XKNHk)8G0FwZk41W#$G7 zzwltQ74j0pk5QrAa#qtx&tjWztVAED99!->Ef>3kdL&=8TF(GVC7r>mI+_(fVZSUh zV*Ia-lD>+qXxp~b6*?!R(>>{b$+Rl|u-0dQq6r1OH8;*SiM_yivk?&j~0oL7jVGf+y$8u*|L`xTtGF+!C11yeDhr9~@)yr+J*6v4C2Zpdk0Zz}3NZQGgM7g`6+XF(&c zdCUyG$Z>$o9x@srwERhP3IXTv!)D7^HdC&Tl9u63Q=JXcjaDJm?KI_wFtEVk=f0cUNc`Yk;ryws^ zLc4ZD9yj%YjjCcMC>zb1y-JPaLs<<=n{NAdey=$`W z#-iXC$z}X8vBKyI=e?rYcImg^I~tf&q{z_sEi#L%&CXIcOoNlP_z7% zLOt!?nJQw(QiJ8NW_E*3wn>TASjS2RbVa&^_)Wd&TaMSd#+QE=mhHBxE!GzT`VDbB z7;F#pHDwz;YkfH&cWrjB3u2#Zr+T4Jr(vS^rcDU|2$U2QZo3)5c7e|UXJGz**UJ08 z`h4$N6o1$%BD}y0QaupI`zZ5@0k=RizaZbMep@o>J#wpk{Y4!X>fC@yRi%w|R~b2O7waML+6q+Z3Js zYBlpy`LnR|+B)XIP3>gO7t`of(BSlG{9pTZ0pR|=%SwkNRr&HyFdlJ)i?CfyRZ`;{ z<*(drO+8o_yLp@4kvr_$yqt#eFBuQ`$WU%KOSRqY@1vaWp;kBsO8|*L3&Y z<@%tdAYT1$sWTy2ScS3C)ZH&45%HyLt=@0>3+Ln*+@fBpT|YV9L>VP82j_(|p<}Y$ zGdCmU!)IwVwbsN4{XK$;!WfBZOWIi^#m$j?3_amE4i`z6`OLJ(L{HPB&wmHDCr{jX=_?{$@ZeZ#$CST9* z?uwCCxG!I=s$*KHDcrF+xs@|Dk=OEvy7Rp|E{m;|?#-o(5psi5CC=QHgO@MCtKEGo z-|hP;Km2d1FHhsNRmlix9@vhU=+BBi5ICdtv^r@A)r1({+EdS*G*3<8{Xo`zm)qN` zG6!z0DD`Y@XfJf|9YgbNQDx{A+x!6!8GhTdl(5=2&s{oUI_)tpmz+v_;ev8{Nf4Qp zzrlW!1_8V{;qV?xcYL*9*+9DMX=LKo_{@`(H#PFBI&F+F(hxI|_0&^yV5B)_rda@; zTH=DWF}>xoV^Q}s18?xZ8Na6cAUOXhqsD|UdAh&um1iVz-sO`BAr)@BRhQ|AO;`W1 zg33d{cIdHhktLR?M{8UM0G-fA`a3BfqAW{h-#AvNj66<$uCBFcQeJ8$kt`o|g4!3H zKxo1`#zarl)z`X zgw%Xdf+gOuzF}lPtFUb2=eYj**mn7*N^;D`cW(E&c$CNVfSA5ZyWW8*zWnLby>C=4 z%)!D>ux}LFqtxN_xq)8>%SltahtTb;?{arGDZar;i*6*$20}{sep>OrL~@G8X|D_| z{EA`ayez5fUlv8 zEIR8p6QbG(kq5sGtO^H9#JM(jb`QzAQJ?I0j?$mpk{T$u%>`B+))GAxal|hy5|ESbke~aDU@mh9U zS_gb8c@p)=+Q)4sTpCryw0tsVFE-naFiz*O9HpVZH9u&zPeS_$PqklEd+{WGfNd+K zH$%(2?;^e9vT)a5L>0p~0$RJft9#!e=g2pQE3%P4;a1~it8!~=;gA(C$CO<(Anu?8(BT=XK zzVaRjSWr1(QY6m!GCe%LcDg(vNvqUhk-u_mFI_zGwHn9AUsB(*-esc~E!NsZzUeNj zJ=`xMFwb zmbrKTh#hRv6_`D#4keG5)6=PA6wNN4iUjA*BN`&Vp*-GGb8=&>bb99&j5}C7euXl( z0@s!U>19I(;6B1TkRuspfph~cC*8&kEwveCw$)CC?GLKeo`+~l~mMuZ&nI3&sIkPW^WljuS$AJOBF4Em!;X$v|z*zyvVr>DQ|zW zDc2wdt-^=uo`yz5nTd1p%$Yx752&U@3D*x)CNFo?^S_V(zOh}bOUteAHbzXBQj>Jm zrdgT!?r%ZZ6AYHxUBc_Y@e87zTJ~lP`zqL<$cc@aD50MZ2@T-L zhK1d`2kq~;%dmfNTyk+{Wh>?nm%BlyG`mZhFSCi%tpgU>YQ&}=V%xfvqMiZe5x!)l zRxO6F?G&XoS94p+$7I&(U9b(w58VPQ(~*D*=b5;-Zs?H+7LgIq=RHXe1|15AhVIei zh2#TT>zDy;wZ}<|iPLG1FAA8$K>@vJ|A6p17`dgq1Xv;q*ee>&g^;fhgq-R&B(oWQU zcw?ZX=Q5kvq!pq9&l+!`JzdXWdD7N(3@FquK$<}wT3_J!KMA@~EmHX;P!IMVApkP% zD>FCcbED+=EH_i1>r#Hlld3f`=h91Zg%_2RFFpQ6gAX@|<1FM*9o?K~Rfs-6%4rI} zO?kIV@yac}FE94^R1!9`tFwwP095lVk5@vIfT_NqDfk*H5}FC{+P_@Q({()GMxD35 zaUO{hy_VEBI+^KdRb$u8S%?Q4o{G;oWbKk)d>I29saa>dRNCk#2v{!z3^Z-@M8o0g zJ=d>>fQpq*x-ilR@&cVgr+}j)D&#L}t$ay0&j@Z3B0LU>W-&6RVfcMn zW)Ko1%O=B-rJ;%NgvP11VClf&K~x@O{+u%fg8mSZXAJ02A>XgZ4Do8~O)O2=@b#V) zDQeq!S*^Ct5Mg?Ye|ND)x0hot8@Kqq>ry-spU#V1Vk+lJ5aQIisKWmE$^;^52`uk{ z`{>b0VU_9m6fUbdR=RoVoIm7vXLVYwb_|3&kNQ`T3djKnRU_+Cv_HIloOw1qY?H#> zO^hUy4-n*VpjdyU(!-iUg^u}@j<_>Sd4~pB;=;&@);j@uzKI&f?=8xHlsi6`@=56U zfL$||{n)yH;I)9?U8m!)-nW+mU?31kwq`28jeGyPSBNa`<)=fZ%J; z&B2Y+esG|X0>kCx8K>b{4;>9k>|sP%=jo(BO_Os{SQxR4`S1Tc1CVT=NAMoyCe;bZ zmTG2dA2J*RPDM+b^^F=7{$)$D)$`S>7c@(4TK~!z(!VW*u8#U>WVQOzeZEkIk4Gwh z1#dGzuJ_FvkTYI-8M3qK|1oUG(0`WWD!yNQ2Oh1a8Kt<<01(YHa=w%&yr=$U*pJ0T ztWQlzZ=Z%R83_{KId;+9#}MEjRYw0694%lg{Cx%R1NiFek`~n`@}w=!(~rIVs>n`| zX0r<{F4HsZoqR0qw7wDd?v!r@XVEWgwk9T*{~}{yt0;4c?m@VO*ph~KqLC=CHt1PX z?U?N7Fgr_!&HFhtY5?blffSRPWkJm`*dK%bO*Nr6R6^H(LE>_yzh`2M`St}EiMZOZ z_u5r;r=e$0CpGpj^gL6>bXq%jG$EVb1QKdvZPE#D=y-o(x~8_HF)B zmn$vXUq~yLlF4ElCL2M_$7+dZg`e^s&t57#sjlj@aCPpUed9$W(&Ku}%F?8~cCxGg ztGP2r^r?iidfmKRsEZsSSzgspbVwTR(+e;z*Fa+H)6Qvo@B%PnyzA$&877|#sk=NucnQoq9KB}&| zg=$uD)->XQCx-4d+cb($fO@Yd36@y?8+`=Bt(r39%a3W(q)r3XM(zaV^?r0?1&oFVUo(Fsmj=DtB16y1PEvydfePBmmtgUKY@?A`ev@XMtjh6}=~M#;>lw*a3^^2mY}E)`D3TL}^yldKsQUSj+Lc z<>GVNZGKegu${M`}hrUla*8GDer0e+boN0U+=r|X7@A97ICOCp7s)~7JDC-Ui?){t6(Qf z{aw~&)wq)vZL;XA;|9PU`(V<)Hg58e-LcS}0!c-)NM{xruK>}ScqssQ-Bu5mjVwL7 zciP7)mdkb~25o9JuAs_>k*Ex6NzaZvGIsu!9PDHc{jX#uiQXw3MkSk;To>Y^(Z_Uu zs=sp;w3MPbv7}Ue9@_QhAp(sVXJtnrdVQiaC~cEp>5}PdSVR?VcKqKf3Tl-Y!e)?y z%H-#pF5bXx8X8RY8>s+b5>mSa$U;k|w4$T6yWwToL&Ivh)L2eiO}=TYMTD3*R^(aN z45yZT`#yR2GWnJR89-CIv*94u+S_(AW^Oq<1}j1?sIe$gug|r0*_$cJA7CnQSCEK-8)s}XT%R8>n%@H*e%?_@oE?4CcRqs zshMXwATYF!~A(Zca8OmAt{bL6Y=8E`JB&={+Ud&}hi_G2)MJeP1M z^`Xu)3uDjJ%@*}CPY1qVD`1K`ah$vQu#rU+ zr{1UJgBP#;&|<%H=9}`}ol)<__NOUlE~XLr-a)1?-hDB)5-5At<8D<5ec08q!O3os1Q_DK&G1b%L9tR2Mn1Isysk&{%G=z*bGzZ# zvICX23vY6&PH5NazAr-(7T+UE{M?RRRO1TM(Jo(u#Ejs?sc!Ldb=6*-!9s2zCRB#5 zx5UCUCGdP#UyUt9fLIeW2;dM}^z0OrlqUugYFtg{F*sP;Lb;E&T%2RB#gZHFlNiq*I1SsxF^;+aDH z={m~6=kAc4a&dKKFm;(a1%9c=_^Wrn>9Q3TxJ-WgqzFnhXdfC`Cph%WG>hA=RPBqO zRHs13{yh5YEccDy>jYwKM7SQ$Da$aKdZp^%VRMUT7&U}?2;f!7#tU;TnvuXW0M?EL zWA;9zxEnuqvFd5_}=5wy#;nIFO$Rpg%|-3!V(~ zwS&YQUV;5eDXZ8lGV{{ru!<}E7Dvu!z z)-?FbQUXl%&^#@T41EeT{dlt10k_AA3MB^_hdwBJWV%?3XD)T3hUOH(AaX~ zN(iQjcsi-P!7TRXCKQlz<*dKm85Kn*&y~w3ikFwVJORIm2$GPxdB$JQVWIVdpeHoTklX3Fas;^KqtLH;$hx1J0!TUij<=wq3hIOG##BX&64 zI>I2^m+}nUAXzBu{c-yZ+QIGjF&S{;3yv4QT(G+A z(q5DbJ?Z8qUHbVSSCl%?v}@PPe}jrm$8rSJmHipBWWJB5fd~O?+RClX>~slDb9=@f zC_#NvQyAEady61-6-ItceVXT4i{Ton{kP5l6pRdkN0#px!fDy^#(eV?0eC6QtP!FL zVn;;MUL!F?Af~Nrwq)&dgo6yAqjnq;7(mH*ZF=vQgY}s!>pDt%{^?e<;lvbA5j{iY zFd^fWQS0BgCt}c?B!=J4_G+ryLg-{;v)JR^pDQTB%G>RM^jF+m4MWFP;N>NN={bb$ zO`<4fFH^VpBP!5{;8z+UyRiAQ?)W(!)1EZ7YOcg%5G!lAXiGt>wsKJ-i{+k)){Dwo z9*>oEF~Z8UMvUv2Ri#T?u-ImCHU=NpZE%n@9(z#3Z2j&a?gGqiI#I`R($?zc-(~gk zixo=OQf6OFoc}+SVqB4$tXN1&RPulkc=>39Aj5Dyaa4MxB_%X*aZt zT#^gk{0p^@fIDZtqV57#5=dsR+}^Gw=bD+Febv&+kC?FkMpS$wt4?kAHwVGO<^TLJ zz%PgggY9%`&bDtAvnx1DOczxX;qBHTNl$c)-Kjn^Y)NyKGn;+M|L%JD3m==4|S zV8Oi|YK7h3rfmdKg3vlUwsJ)Sy;t3SVBJRLu|8>qUopmr;`Ekr7>$apR@{=~B?YWe zgI-+ATR*e&PtMomGC(s?=)Aza|JCuoLHE0FEY&ll-EyqY(G0fYNiR_#oq|KNtMqG? zy2=_ju()a?2@Bv^Yk1_I#3uB3hUwIKZGa(JbwW;{zbtf2@fGr;g# z!L%r@t^eWv5mj37BQp$TlmdUH$WJ91ET~zoOe^?W{r!@{vh95oZq4^v-!|7y&vz{D z*Ch7t!88Wfw0p3g+s=e*`xN9fn{K+0!*ig*IC<;t-Tn$!Rk!=^Wxw@(&0~3=^*44F zE*uD71QyO^`UBr6&39U(O>HNGwGZng=y^v4rUx;=K72~)UY+j91G}6 zH%F*{h@G5HwPCWr#OjQS#phuiJ)BX!Y3ejhk{=hzK)W!190 zKw-Nc;n{cL0|f=erEv1+8M?244bhlbSjay?f}T*b5}i!UzUz|IKOS-j(rNXa{O!O~ zh~B@&nrcH%k%h|j$d6r8qGnNu6?Mm%F0Mw?G3|Bnmu<$NMCaM*Ml0JrHoX+xduMOo zFRRJW*BiaXxr( zT#s{T{|=F;j7%2$A^DE4nViF=YC=RmJNmf0l;u7Dr!WIEU$e|<6}Gn|GD0;bxDpSc z>fJ}*jZNEIsgXQYvgp2e{SFZdiPsVlM)tv6E99(8B0SV^+vhwK z!ibGvz73?gE{MHqZq3v^y#yh2JY*x7?q-GT>B?TiuzpuVhPc1irCzxZH2HhL-h6ZA zqel56pG@Tqi{z#(q7g=pWLQ+}BRgBx+ZwL;TZJzPoAVmJC1`hVuk|TPR0t?h|DOlV zjb|$%%da(wIm7h*xgWx3c?ub%XNzmB{-8;(1>Kw>J6Dnr20rMpEEu9rYoLjzgQrQM((}GG*#v-YYAT+w7*q$F+XE(8_t4mgTh^ z>9BJhc6WI57-!ZT+U*klyUHHofE32n5@y(7OR&>IM2&xECH#weEBi`j$*b;dDY2)m ze@TXypHC{p>F_`p2#NF%Z)4F1pzX|f=dL4QhQ0aiq5M~?FRq|2A+J8fM(`i8gge{E zk_>j6-%!hiWuXHIR7o8DQHNh` z4EcCK*=-?+jPpQ!H6^v+m{aIvGna;Gd6TeNROwd#PVf}+nYF9+W11FNAEZ0ip!@fsf zc`)6m!Du@-i*enurHU?gACE}=94B=x<<~JV6e~Jmg%kwct=&Dh{ggE;oJOi#l3ynC zNv22$)ixkuXZ4`vN67RpCsfWf0+M_1i~GXAp>1==H#oo#KCHF>dmm0)QJbOglhxOK zDpsc@jKh9(Y`)$4?CZ-%`+Xf5PBjzp>ds9M{Ek(OZQHfVeNJ>mEeUMRJ2n$q`@4QC_ENV3sYFk`XIJ=Lq0?7k-_Q?bD3 zx6gK^Jn7ZcY0U>?m5vqLk{H5Y0hzMDt=z-d@$Qmb#J#L4N?yX z<^`$|$hBIB908)Y7jLX@N^nm9V{{dsERlu5b)1oM;LWHHkV`$$m!+LQp=AG+V#q2i z>vrSoQyk_K`1Lk&=S)d2fFNL8N1V|mOM(Pt`T2`Zqn`X&n`QwB(SW4{`uQ{OMbZp` z>q8Fon;5?4O(F@67vp%wDHpE^{wy4nZVdRVWG*WEG)LeR$#g zLsPDV@McX!KmE4ha@|bsVLy4<*u<>++}^JUz>8rsEWQN|>P(C%6>o4B$rTF&Ey}P{ z0x^3-f)!~yS@Rx|oVU{Z!{DTmhGW?Rpu+$Qih+Ku89l$E!CXlXFA&}EcUIJt^O-1Ie?#p4i$1L65OlOhfXsnRMWq8r9Lx*L zNz1$y&fXp6x+8c*V5PuHn-o7(Y>RRFOZvIj%v~Vt>CZ+#ofr8%4(peaUC_!L6-Syn)mF__>yBm-gt#t z(i2l)O1J)n09z5bi&f*ykxUPgAGU({`&26i-TJ-HV=TY`vJ_Znh=7AZS>bL8MA_jS zkJEHT0@M-U&oj<9#RA&H6g|n#W9myd=G?6b8t~gxyS!LdWxw*+{=o=vW(Xmp-R3n^ z!YZT9Q6&k>#JN1L|7dMV4=7s7eScb8sx_9FIVnnT7ml(<l$;1MRq zz%;UBwSU6;=l#mvXJx+?@;ELH0s4h|8O?_}f4c;QC?l`4}N7sn4u6QL&p;@1ok4{4B-q0qu^EFMlsttkHUwcn4 zqHS{XP1@#h9Kxp~AVA}R+KIo&DwU+=Jag!f8XzL(PBho$YSjb)-5%`^m-P+(LR78z zPbBcT#9>f@Z{cZs>;sPiqmN#9t#nvRQVK5uc6c9wX+Ucsu0EzHx?~;8&!LxhzjZWe zJ=Lh0_X~;JR~xgh=CWQ8#s~F@EUfx;TAFZwG933rd=Tpj1fsId4O6K)n&|)c7S_q~ zd`Pd`Io<5PlA3v|&1#m33}N$mG|iI8oMDG7>GR77>HrUJH)Wr9CN>9y2H`6N#nxXa7dA2SIT@%U!Oln}?Y?f-c0-Whg z08siNZevQQejNV1=;arWv)b*1K?2x|$V^X&MeC`(bGo;zRGs|a_m1ppyD%O^3tM{HP=CdVG$mdto)T=yAtUe^hAUHKXq~uNU~uN z;)NzXzyr-Yr!@JN4?=BUBlgyxao8P;pw062=pRL=8<92^X~oKJ`{7v1(>EA0QdT;i z#?edpen3_&Z#AUWT&82bpk?bO&uQ|adnY6I_g4Q<8J9eMloC>J7L|8rXFr8E?FYp_ zugF-nzpM}Twcz8ka7#SI8OABWRDqbY2=zJqD15j7J<|1p9$t{b>w}|jKEX=#p*}p! zff$?j{w~%_{G8AW<4zXD3?GHvNJ^AzF7`3IDqa@U#3*pt%-{hv@-Bfr0Wvk0gIL~y z9~hW}j_td$e{t^0`Bh54V9b5!x?*rqkblZ?&e|*+*bYwx`*S`ffi;b6Hlf6>JmAI4 zMM%rp!wb;fqGJqbq)!N!h>Mc@WrfewU%O8GRI;be8lQ4T2`V(djm(>10x-sI{csWw zqjK6N!i}FdqZD|n#gjp!xCR-h+pox*iV)<|iFU$*nTwhy{;*Z*fi4yzix@iN#S0bHIHFI@O?0uIe{WbRJegjmM5sTl4H!9McvIqu-<>BI>A z86;x@a#t-V<3PKXZ#0Ozz~{mnD)8K?ks5Y3(#IFhpXibG4GN`#ksimEkN@F;fpTRr zUEnUt`zRWh*!`~H2J#1(kgQqy;s;J$1rS((QTt{)qocV4uQ$Sb(ECRA=c_GLVx4(A zgzZZ7uWJ!065p=ErnAEP+GED)+j*OjiARESu<5KOp!QD&q!mS95feOU-X6&$$QHCP zgy?(Awz3enr>sQ92ZfQ~T9A3*@5#z;%xPXgZjArQ$ROh^7L>#*vlq(40ZqyoBmGT2 zBy0l)gzz_{IqybEN51;YfQFPkCv1dj^22O;wXju&Y*$Q+Xz1C8vDpdI8Mx)zh*n4O zGqLPDo!N^U4|&q){E25a#c*rk&jGITU=WL1^2ZFU4Pr3cKNlKQFI$bgh8Y8&2(SB) z9lF%xtbK+sXdG~teIV^8)M)WL_x6YKP6i~Jd#tnxy8ch&dSHq|u}St?0myhyGdMQI z<6N&BN+n)Ywy97`PO|cxH_`iJKet0LvW zT}so5A$n7f$)+#^!SULtQ@*%&X_j_@{R=g=mDUF$j;l*5W;U?MMt?;DJ98qr@I6-# zD(}EfHxtGs>GIy>qCO%igGY9Wl?tPC5Z^9%JitU#E(Jq(U}tpi08;OEW)j#Xwhk3g z?iVPegK=fuBK&@E1iTD;#xRRBAg!6ZQa4N}2u9VG&4pz{;E!DGj~EEqLr6xqlI@I1_r2RRz}3hc>=rWQhpSYXP{z@p6K}LCyt-6%KXLWVLNxU8M1|is z6P=vjhcov7ez>svz1=zm_J<8K=$r|_^%A|NuV!ty&~b-mT#z7)n$@#rZBy}i(P4{h zYtRLs@6B}WBW1Olh@XS*guuoJNy}1s_o=9zsR&j;rZ7z9GFUbWv_y_UYO>*($j)TG zg3j90^}@xMa=qEOt_!G=1E$)5&xk<+278=7Pf-*FdbX`mduinwebeeFEdA#(1#qv2+ry<+=?w6^5Fu`6tL7J#n- zr`HE)lvIF_IF}>U15V`KE?_Z?lsTteG?${;P7ywQk*Q@1FzY+aSdr-kEvj=x^Hw_f zPSqLh7y?0tiTB`EswvCA2}mewsl@om$DB>C%P)YOeB`ZHoJ7*&w>rLhNRxIzaDf%k zmN~Swl~VZzPlE<^cxV4j?ZcHw4`5Mf7&a6Xh4l!(k~Z0_>rDji`nvy10Fo#LA$dA3cWC9fS-6w{ovrduTvg?+mUc?=)ZE%R-oXC91=k`~3S5W`4pCl7vrj-b_X(|7Uw5HP5>a z+u;9hSShSP|IX5bOwq(G1Qrd9Os{qG7n>^E*{DV=arMnjCs@HEo^5>BpIlz6pjP~; zeHGtsAJzzNBZFOk4T!r4q;Z_gm#G^}s(tLYaR&WuS~$~|2|ba%KLtR*1o1QOVr;43UiBY`AtErY%2)gX>?5&o0XxiVV^fex z3k2#0QwA(rm8zcINa)#WLjf{ zH2BQfxr!$~PNVEPDi8Lp)J8&Y@?U)qGohs0UiP-I+HECJ>FweS(6jHUM9ZDV-`2!b zOKy+s`6zzjj+U+ZbE#enplGD#5(KhS%(v~1-MP-V^+J{T?zyuxd_ID@$aUzD|D}j< z>$RNV$UH>DtbCAyUiud2^%Ws^pn|j7XkzEB72k-yr2!ja?P1wNQf~4JdMu21FFJ(B zsVYC{2~5W_`0TNcfBkNzu}OWVQF{_BYyOkO#xnnzXxv2S+vS5ceo=IfiP-G>R=BFB zqxbf+@q4CpcP-_OPJ}jpz6KJhF&fJj5h5b;nC33=U?_PhAFNJ#3Yy#h|EDb|h(D&A zcl6&X)O-ZZc#`ztACV`K<`yLipQ(4Jw37Q^8fYs(hr#wIaKi<6v4IH}NA>?S_9f6% zuHXO1aFd3UNXe`?x`skr9al1igF-nODszc$%2X*r2t}rj3}rlWkRj$k)vSm-rgqZmC#aDdKDKnXrwDM$_ zDs0-cP;9|_z%-F7HS8CzTB`G`v&^?QnqM)0rsx49Y3m4-ByFWmNI?YjQFxz$x_?em6Z28W8aJQnktnDLw$zk0B6!m54$$8jT-WgTNtk57HCHaa!- z{#pBuY7Gl(t!93D?KI0EGsHyT{?;nH`oLN~wX=I&)YCJcGinXsQ{Gu_2u*W>lH>Lw zpY7C1uY2@F}X&5WWjb7`_5b5y{Y~)=+f4PQ$vd{ z=U-NMJtib%VLrBYaCO1h&;!w#&O-m;7f(#ezTJ9#<6&sPxm6lRzb1;^>AJYhCNL#r zUp$j{(+ZB}z3rQ2G7f5Iyr;Y|6D#sWopktUOlXuumXzpA0ow*0G$@%^%bGXBO1knZ zhg00Xy(t{pdVk-yV`OvM5}4J*GZpuDxBxv3p|T6T?7Fp4te1vmaKDaxs&cW$NWV$p zp8iS2^xCFolO1FMMJU4+s49Pe(ZALrBgGi72oTE#^Y~a zVvqk8W4J*3(9Rd*EX~XqRTyvt5wXf>cPaG&4%o__u{lE8>p}@dGb?7fMnR*dy4*Xt z+?U^~c{^>%a0Qs@+*s=!E2UO)?A4)zsk>e;xIf_f?tEyowB^WX$E&hOhu=Q;49GpI zVK3JoNd6>l` z5JS-Wwc^Jo)h$)e4PReuND|A*9b54CC8chdUlvnweV;$|9lN{gC!LbeR4G=wTA?YZ z=Q-Hqosp)Uu*en)v-_!~W#2AnJACN(Vf_JHoV`UK6|#8r$_B!y(P~|q){l`|XrXVn z?CXgusv>ZA`%e3%!_b@ehr-VH^X=Ers*l`~VtZk+|7d3W-A~@RZLFGEC*z8tIB;MdKzVbh(w?1uaYl{2sM*8h0)@#gek_|nBWYQ`!vS8w&c zIyvN;)}ZnBtRcS=%XfQGh8k=FU(9QyofxJUK#*Q?HGYeD6f!zZVDV3)`p;AEPpy_ZzW-!8qa@qTsc z#KeQ$uw+%C-R$Ytomyzk-Z&ZWl(^KYK99!|&M z56ASh{d)Q54(H_4YK0GA%l&ZQjZiMr=U3ftu5F{Q8KSb)BwM} zNY)~TgU{ZB8#YBJ{;BSm-2cRO>c^|UA4NDXIU)4o)>D($W2yc&$3BWZ%io))n$_B7 z?{@R0YhM39Zs#i2=tT)Wg{3a=|C7#0i`6UKy}IgR_L3NzH^XMx2;p2|6^|}fOQu<5 z!>8J}>z|EWNQqh7vTVTqi$h2)tHWWq3vE1x>j?ojO}_=hesPJ^?`_#%!JAujjvXJb zPEt9DhPksZd7PC=W>tA+{!OhOWT`uZYb^1ME@ud*j9qnpyKi5+vxX_r+@*Bom09^)@fJ*2;;cgys5MH!*n0a;H=^8Y^diS6-gmy6DB)I-TB zG4GRX?7SnN|7F)XkY%*8DP^pA^RemPYx>R-ozkNIOj8YchfA<|7`u3l76>0$4_geL zvu#k?w|Dpch;omox%#4~68sPK^gL0k6=ttyboH37OD)xZ%B#q=ZCfj8tX_9#)rAh1 zi(Q?zW3FLRK5|8ATpB+@{%Lw{|M2&n(r)E7&)xa-&kJtOILNedc`G4!?S=SCle>Al z)bIMfh+EvF@wKn&SZ7PAPRhQu4cq;Cu2T}*Bq9?yat@I}-{Ec6u49I&rlP}V72VXh z`i(-bF-_Jj^Qln`z4y)1{KiPQ)U8IX6NL%|qbv7ax3279F3VAJd)dypUl!>d^Bu{` zs^3es@`%p0o|0A#C4Cdf%%ED)rc3ir?^)yY+C=bmfN1g?473K<_1B zG#NY`Z4u%Aglb@`79r)Q?Xyk*j;z-)d_9Ob%hn1Pn_u2(k&u1vnAr=Bj|aCq$XUV1 zdVuc-FQanSJrLNOpL;`0&K(vyql@tHK2nOR8Sq7cW=vHXp{~|OQ3lyoLXm4nsIVq> z_`z-RVt;;Q){1s_Z~!4hu(cD|>E{@zRC#y=>IvUq50A9TU$0 zh0^hDyFeaMn3+Bz0m9+eZvGByh(zot>jf8_B=103nHb*zZ2*wJ0PP|LnLrRN5g^02KkH5w8=b8lH_~RTrF&{*>l}TH5qQ3wB^62t4sRz zE5wg-cc+u6mY{OIK?E(AhaK}qqqn-n?^;Aq4fbv9mklkW*)kf0`wpH~{0B^Q=319H zygU;(GTX4Co=KkTM=qm1u%MqfF#Mz{c`b5XFYE9AWJXURg;5VU2)ej3jA6fxQ-5j2 zrd2ngy`RDMhnaaHaeJH})G)C4*Ja_^kyPKLA(+XJ-gf z+^1bF{{lI+i=`z%^kFoJ8===Sdm0Y?b#C*fMTWTMXipeO!gQEiO^r>DN9B_J+iQgl z8nt*gx@XU$wNiWoF41}{Dq*iavrG%qYM{rRRW~0QpInN}bfhxWKns zd$d2p+C{bKCV8Z#6BXfRmV|aeVP){DvCz#0=Kjy$8{)sPe8;=@{j$|k>A0Dk*~HDH z2#KoDINGG+l)anst&44h2Q~dX@@N!qgS9=RcaqV%WZ0DzdrdcrY91Q|uD!9)GK1@( z#NrF zE)|<|8~8}`+(&oEJ{9Q_9g1gunD5t6T~mF3gN2`l%Sgqd_IV$4d?r zU5P(MbsLnUvJ2B4LHBh5DNtDsNr5#nJ)2;L9JcyeU%;KlD{_m|gw0;c6tGejz{ zDa~fBpb9b+d_ES0_`X-ppxY^upUhNr&>NJ*!>a4{NvUFwfw&zaO7JohI9P8bA+;C= zOdH`QQP3_dV07YKaiWD>%V2!aXr)3=T$cJ){Jp%`q!8~5D>Y{7A4zCx)b_c+K|~AP zd?}39Y}7>xd78=(ulpOWCNT2OH+Y7{X%0klgt<7GRb6<=K5p^Yu1zv4;S2@6jsRWu znu5%C^+!Q7&S=-392|7x)6xVx6uDUq6ToR9wqCCBla3M1G()=7EnBB0rXHUq&$1@i z2>l#MhmC*2UtzRw-?}oH@RQ~t;c=P{fE1Z$t|RN?JVPVveIEcd5%UI>{gL z*bty*%pT^#%%LkCiky0Q06fLmIH=8D)PbDl7H2vE!RjC{aH`9_9;UdE{J=w`PP@Ih z4p*r0pK(}^p909%N?rvMdBq0}cV(zcs<@`q4xRCt)fk({|yqD1`p%80z-ep9bOSF#iF_ zMc1&@*?1sCgmT_6yyAGoUva6XNGxW9Vcev|pu#CLA9jou#5!7dU+cd=g4{UxhTeyf zwJJ;Eg%{!%Vu<$-JIL^+CFr5U{&tuR^%FmNMO7wJ+Nl>-1GKDoo_TgRJ3MVzTv`rI zFij)TOEi4dqeVF=N@NZTgIR9k)}-)!)~B<7u!Tpq>bG3eP}+Kt>`40+FreK%HN2bi z2QHECYuG@B#T8wMv-lMYU?5;iS8XJfwbBQIs6#c4e?zQ%TR!z2 zGmsTO*&`wfpgU@_b|SdKNdKr2TpYzjxNxg+QT|w>&wo=&^AaboXLFlmc!20c=2WK zo}N&D)_{k>;bN(yR-tmnK`=s%Y34(A=Y?-*n#bw8SHBxei3F%XNmFqyW4v~%Vazrn z^4ShlzapJm8R=x7sG4!`yTsJR<$Q+kxkYMVOKY>4$5XNRQr-3m9X*hTosT9k3lRiJ zA)|LGcw$|2LeSbzG>T2~08Jz|0*e9M@B)+qn<~Wkm-##jBru_XD#U}LUh(N8;s^iW zMToCQGq(W)1RuHdP@;Gf-chQ^^>j^CsXjdbG6D8TJK~gEOHvZ1j4bV3V=cQ~AOc{v zb>qZ}m;I;A`{F!_3@CfaE(U9X?a(Rm&J#y9sV#z>O#7K9w#bhg#>GBQx8TW+E)76Ui zQ_}y1jwDpw8nteGA#9#=J`C1{$#{?FA@d6sqq(Z-Ez)ts7_f~_|% z8aicOgQJ8a;j(^Z@TzfM2(lo|iR8dq*V=|wk{va~lCy@FP`-MDFe9<72ByF@zZxGl zN4=x6fn_K6AoGmSg-%#=~G#Xz)PHx+yKiFKsQ0fXM=n~+2p!9Zgs zG0XmV{m^|TaKo!P|HBre@X>vN8o=z%B?z^Z>Er3(yT#5YpnoBU=`DkUBiRPruaP&j z!U$E0UGFSCi`xW@Be&Y$EHivVLvP2Jggt+b4A-|wI=>{5I5_nAFyl8*0F z3{~s8r(@%3C)dx{fJcB5R_@Lpv!Lh;@zQnOIue3h-x7Yojk^aohNr`kcO+=~3I-&Q zfIOKxXDt9>g#tkV&ccr(VWUr9f|U~-Cv^ew=r+rr{5ubO_dBq=Ewh0Rk9x=Crg;$j zt10-;yPg6uSBUps7xs*WN)=!eEOW^Ke_(NqrSR0|$w9u{d1oQ_BdbtkGfk!SMNgSo zVU>d;lmM4a3~0pHrB# z3A_--B7iY=@7v)RC|Ts@EY2af0y{*E8- zAZt+o2J-QUA5XsZ6cs`6Gh^EM_n30IW@%`E;uoi|p&Bpq*smZlirq4oVqw+atOH|D zd=D&M2oN5mK&gB@^ReIyVmi^KPKW-Ye_&D=Y5;z53Qy_+02n_#9YP=HyC-uc) z-Moo_uTiAWgi#PF*#dLeu@=F|!i(g&QhD{mJZzT*wB6CI=bx|zt(pZF$MfDiOPFO2 zQUGMJ6EW66d;c2SvfM0k;_Mqo!5*M4gLA{xzP1eH=k9lZdbp5h7}Y!>DA@g@hJwc* za**-=ANg$=4(&=r+r6U}rP+4Ntf83tVEB@1aNp_OOh!+#niH=l!UV=?yG%IR2U(=1 zw=PEsREq~Qq7A{(xdoQugPL`600U}c5YpcFSUEr=-Nr@tQJ5?P=%2nlut#nyh-;w; zfW*(S;6Kmz%McEd%11k7fTaJQ5dyP>PsR|1!>G)mi&4Inxg6eg(sLl-w~C6v2Y85I zk|+GU0l{*#$V>{ zNY33V3f3=xgIbJDJEBkdFr!B2bB#SB(h9*JgZKcY%L_7N30H>&@V`2I3G{ug2dk_c zV>=NK`mQ*QWX%;F4lB8Wjp42@Z`(xB^qk8RZyQ13OiA<8NggqNxaT9*=$HJE)iPIn zXyV1kb=n=TT?QgbIX^!cz%eO1;E2MgZB(xQP!21&s{Tva`>!+y-r+`u+Tl0M#o-{y z)PN*r%g}RMS`auscCrw3cf~btAJ1gRAl>{Y#xEvgfe!0xqTyK_3@NXP@r+e*G{xQ_ z&_7b^v5fWsy;uBU2`+@F@dC#KKP0|lDJKEah``P{M~BRW3Qs1y9V$E>3Sb*#h9nH* z(*yox+!zBapn5rIG^@(8b@zowcR-sIep2I}I(}o0C;Y|kaUKXScXa6PQaIgu*R%uw z1d#D0(xHQx@hB(K6VV^|0D;<(53*LLxMY~1|n z7mpBl4qW3`hTS1BWQviU9_eZGrM2>XP=`J|s&qkv8dJn3GTzd&NDKwVe`Ezv2H#78 z>#-O=;G+E7SC(c+A__{4T07&EHT{(OC3y=I_;eb&LBcM0|6~QOn+6*HNrw zvQEfIjO0i`gxHgnIPb2xhlbP2%XlnWS5r$~i1K40K(a?X$ArRk`&P2*(}pyAmWhUU zog`6a8%W|Lk=(%l&IhkDvVz?Bij-jNY<4P+C?wH>Co722G36q(C@Bjm7B`3EYP4Lf z=m#JGK!$+$=K%(|BuzXz?I64PiPM+S-T(l|u4fP(mKN?}$#UN1O_}Dk;&P3p8|&;u z#kxVKXc(YYZlWS0Yy;;FPjq@jYY5;((Wtk$?Cx{98><}Z^`Rb0P3lD?yN+9jt0(H3 z;2E_p^SLO$=H~{wE>wsK9N|>pOT(EdR!{lH+aE=@N#~)|3BS}C$h-GVa3ypV z@NvZa^7^wVC$3imB^hhKIDQK#8VD*CYfA|7id?Kq+H+`wQe0kjc7PU#HsnxErG=euIxKqR$ zn$pNv)NnwJnR4EUGJg6|dcg*!y4mDCf$N6QSP;VrW!yh@(#crj5GT>Q@d$!DNnO@7 zcqcRx>;W>Y)$&uK?dYw6GtJVm5|gAd?`He}$Yd-MnUs30pR6 zI3dN+AgojV0lGb?O(s!U#bkp(?W*cY)4Wl_dx2Iqkmf1Olv!;wnxG?vcN-RAyVdhJ zd&K>2vl_`f>UfunBo2o{lG+y7H>nmma=X)caJM(m^$yDK9ihL*SeRJnMFNZ@+Bk zX7wtxVdhCWMbe{b$Lt#GnH(RiBk@bxAzZ3=`8X>{x(D1=0O;+NFV{V#9P!t>dg)e{ zb)2t*U=o)C^=GB~q%*7FcgF>|PHMoK-u+UXkG@~#%R?ftfPjvOgS7;`d zv_Q!f3m3fvwFH@F?d>dN7;`1ZJm3MHDIWM_*%be04|2e#`F7m&^>DczEH7X?JYd|( z3%yV8Iws}%^6b;*DM5;X&5(5d+x47(M0i*B<$d+4iKHO)#6#9E#J^ZZBr*{L{huZt zW=Mh3^UCtxmPRnCCEdBeYeH{=g^|F~x4PeN|8xJRnU@x-1Mxn7!x$e7U*Vdpv$5Tb zZM5dohhK6Ko^f9HSpq@x6r*mirl-gavN#_oxEC6Rj|!SU*&OI zGa5Ms)Lxf@W#;)zRyIIXd9%1J?;HiWAsAjc^E-b=(>Le4w{9#}X{^xr9lC6&eWDpb z4ZbbLFek_vLyu^p(P>9D5U@Z@{bCTg(%d?=%%ZV#S;{}^I>1Pw;Lb49T ze8whz=!`&HU`dX>;kKP0Et5`O%ac|SHp<_TOH%?8czei|Vc}xlb#K*VV9y8;72KAI zKvS0CCx9Cceg%Y(o5Xupu1mEX%A8bWty8bWXvy2VVRfw2k5Pi8xJFQx+X4(tibhaV z0~%McKXVn5xL0iLg0xvB)&Zjd#%s6b$4aOe=j>?^N$Y0hZDhSA9c1j-78~wz0NhMA z^tcE^U#SZG>f{#XFcWbO?HC`)#ifz}}>M0!r|Ci!DDbRjlNC|f!TR=s=vwHYG5~ft0HK$jKcHIJPyVL&%vShCr literal 0 HcmV?d00001 diff --git a/makefile b/makefile new file mode 100644 index 0000000..8012f8e --- /dev/null +++ b/makefile @@ -0,0 +1,101 @@ +################################################################################## +### config +################################################################################## +CC = gcc +BUILT_DIR = built +TARGET = app + +################################################################################## +### source locations +################################################################################## +WORKSPACE = source +TESTSPACE = test +APPLICATION_PATH = $(WORKSPACE)/00_application +GENDATA_PATH = $(WORKSPACE)/01_general +VSTD_PATH = $(WORKSPACE)/02_vstd +CONTAINER_PATH = $(WORKSPACE)/03_container +ALGORITHM_PATH = $(WORKSPACE)/04_algorithm +PARSER_PATH = $(WORKSPACE)/05_parser + +################################################################################## +### sources and head path +################################################################################## +INCLUDE += -I $(APPLICATION_PATH) +INCLUDE += -I $(GENDATA_PATH) +INCLUDE += -I $(VSTD_PATH) +INCLUDE += -I $(CONTAINER_PATH) +INCLUDE += -I $(ALGORITHM_PATH) +INCLUDE += -I $(PARSER_PATH) + +SOURCES += $(APPLICATION_PATH)/init.c +SOURCES += $(APPLICATION_PATH)/main.c +SOURCES += $(wildcard $(APPLICATION_PATH)/console/*.c) +SOURCES += $(wildcard $(GENDATA_PATH)/*.c) +SOURCES += $(wildcard $(VSTD_PATH)/*.c) +SOURCES += $(wildcard $(CONTAINER_PATH)/*.c) +SOURCES += $(wildcard $(ALGORITHM_PATH)/*.c) +SOURCES += $(wildcard $(PARSER_PATH)/*.c) + +#---------------------------------------- +# test sources +#---------------------------------------- +# SOURCES += $(TESTSPACE)/test_init.c +# SOURCES += $(TESTSPACE)/test_kern.c +# SOURCES += $(TESTSPACE)/test_valloc.c +# SOURCES += $(TESTSPACE)/test_vstd.c +# SOURCES += $(TESTSPACE)/test_vlog.c +# SOURCES += $(TESTSPACE)/test_ini.c +# SOURCES += $(TESTSPACE)/test_txls.c +# SOURCES += $(TESTSPACE)/test_xml.c +# SOURCES += $(TESTSPACE)/test_csv.c +# SOURCES += $(TESTSPACE)/test_json.c +# SOURCES += $(TESTSPACE)/test_vector.c +# SOURCES += $(TESTSPACE)/test_list.c +# SOURCES += $(TESTSPACE)/test_str.c +# SOURCES += $(TESTSPACE)/test_queue.c +# SOURCES += $(TESTSPACE)/test_stack.c +# SOURCES += $(TESTSPACE)/test_deque.c +# SOURCES += $(TESTSPACE)/test_set.c +# SOURCES += $(TESTSPACE)/test_dict.c +# SOURCES += $(TESTSPACE)/test_map.c +# SOURCES += $(TESTSPACE)/test_heap.c +# SOURCES += $(TESTSPACE)/test_tree.c +# SOURCES += $(TESTSPACE)/test_rbtree.c +# SOURCES += $(TESTSPACE)/test_pthread.c +# SOURCES += $(TESTSPACE)/test_encrypt.c +# SOURCES += $(TESTSPACE)/test_calculate.c +# SOURCES += $(TESTSPACE)/test_command.c +# SOURCES += $(TESTSPACE)/test_check.c +# SOURCES += $(TESTSPACE)/test_crc.c +# SOURCES += $(TESTSPACE)/test_sort.c +# SOURCES += $(TESTSPACE)/test_hash.c +# SOURCES += $(TESTSPACE)/test_oscp.c +SOURCES += $(TESTSPACE)/test_tool.c +# SOURCES += $(TESTSPACE)/test_sList.c +# SOURCES += $(TESTSPACE)/test_dList.c +# SOURCES += $(TESTSPACE)/test_cQueue.c + +################################################################################## +### targets and recipes +################################################################################## +OBJS = $(patsubst %.c, $(BUILT_DIR)/%.o, $(SOURCES)) +TAR_PATH = $(BUILT_DIR)/$(TARGET) + +# link +${TAR_PATH}:$(OBJS) +# @ $(CC) $(OBJS) -o $(TAR_PATH) -lm -lX11 -lpthread + @ $(CC) $(OBJS) -o $(TAR_PATH) -lm -lpthread + +# compile +$(BUILT_DIR)/%.o:%.c + $(shell mkdir -p $(dir $@)) + @ echo "compiling $(notdir $<)" + @ $(CC) $(INCLUDE) -c $< -o $@ + +.PHONY:clean +clean: + @echo "remove app and objs files ..." + $(shell rm $(BUILT_DIR)/$(WORKSPACE) -rf) + $(shell rm $(BUILT_DIR)/* -rf) + + diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..f58efef --- /dev/null +++ b/run.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +exe_file="built/app" + +echo "cleaning..." +if [ -f $exe_file ]; then + rm $exe_file +fi + +echo "compilling..." +make + +if [ -f $exe_file ]; then + echo "executing..." + ./$exe_file +else + echo "compile fail!!!" +fi + +# enter key to continue +read -p "Press enter to continue" + +# find . -name *.h -o -name *.c | xargs wc -l diff --git a/source/00_application/console/console.c b/source/00_application/console/console.c new file mode 100644 index 0000000..1b3221b --- /dev/null +++ b/source/00_application/console/console.c @@ -0,0 +1,131 @@ +#include "console.h" +#include +#include +#include +#include +#include "kern.h" +#include "command.h" +#include "init.h" +#if defined(_WIN32) +#include +#elif defined(__unix) +#include +#endif + +#define CONSOLE_TASK_PERIOD 5 +static task_t console_task = 0; + +static char input_line[CONSOLE_LINE_MAX] = {0}; +static int base = 0; +static int end = 0; + +#if defined(__unix) +static int kbhit(void) +{ + struct termios oldt, newt; + int ch; + int oldf; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + // newt.c_lflag &= ~ICANON; + // newt.c_lflag |= ICANON; // Enable canonical mode + // newt.c_lflag &= ~ECHO; // Cancel echo + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + oldf = fcntl(STDIN_FILENO, F_GETFL, 0); + fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); + ch = getchar(); + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + fcntl(STDIN_FILENO, F_SETFL, oldf); + if (ch != EOF) + { + ungetc(ch, stdin); + return 0; + } + return 1; +} +#endif + +static int delete_input_string(unsigned char num) +{ + unsigned char i = 0; + for (i = 0; i < num; i++) putchar('\b'); + for (i = 0; i < num; i++) putchar(' '); + for (i = 0; i < num; i++) putchar('\b'); +} + +static int into(int argc, char *argv[]) +{ + int len = 0; + if (base != 0) return 0; + if (argc < 2) return 0; + len = strlen(argv[1]); + memcpy(input_line, argv[1], len); + input_line[len++] = ' '; + base = len; + return 1; +} + +static void console_main(void) +{ + int c; + if (!kbhit()) + { + c = getchar(); +#if CONSOLE_DEBUG == 1 + printf("key = %d\r\n", c); +#else + switch (c) + { + case 10: // line + case 13: // return + { + if (end != 0) printf("\r\n"); + input_line[end] = 0; + // printf("input: %s\r\n", input_line); + command(input_line); + input_line[base] = 0; + end = base; + printf("%s>> ", input_line); + }break; + case 8: // backspace + { + if (end > base) + { + input_line[--end] = 0; + delete_input_string(1); + } + }break; + case 9: // tab + { + + }break; + case 27: // esc + { + base = 0; + input_line[0] = 0; + }break; + case 68: // left + { + + }break; + default: + { + input_line[end++] = c; + }break; + } +#endif + } +} + +void console_init(void) +{ + /* Create task */ + console_task = task_create(CONSOLE_TASK_PERIOD, console_main); + if (!console_task) return; + + printf("console init! task<%d>!\r\n", console_task); + + /* Export into command */ + command_export("into", into); +} +init_export_app(console_init); diff --git a/source/00_application/console/console.h b/source/00_application/console/console.h new file mode 100644 index 0000000..637de86 --- /dev/null +++ b/source/00_application/console/console.h @@ -0,0 +1,36 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file console.h + * \unit console + * \brief This is a C language console input receiving module. + * \author Lamdonn + * \version 1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __console_H +#define __console_H + +/* version infomation */ + +#define CONSOLE_V_MAJOR 1 +#define CONSOLE_V_MINOR 0 +#define CONSOLE_V_PATCH 0 + +/* Maximum console command line length */ +#define CONSOLE_LINE_MAX (256) + +/* Debugging mode */ +#define CONSOLE_DEBUG (0) + +/** + * \brief The initialization function of the console module. + * If the 'init' module is used, export the initialization function through the 'init' module + * or call it for initialization on your own + * \return none. + */ +void console_init(void); + +#endif diff --git a/source/00_application/init.c b/source/00_application/init.c new file mode 100644 index 0000000..d3458e6 --- /dev/null +++ b/source/00_application/init.c @@ -0,0 +1,37 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file init.h + * \brief This is a C language initialize export module. + * \author Lamdonn + * \version 1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "init.h" + +#define NULL ((void*)0) + +/* The following defines the first address of each level of section. The order cannot be modified. */ +static void init_nop_process(void) {} +INIT_SECTION("init.0") static const init_item init_item_start = {init_nop_process}; +INIT_SECTION("init.1") static const init_item init_item1 = {init_nop_process}; +INIT_SECTION("init.2") static const init_item init_item2 = {init_nop_process}; +INIT_SECTION("init.3") static const init_item init_item3 = {init_nop_process}; +INIT_SECTION("init.4") static const init_item init_item4 = {init_nop_process}; +INIT_SECTION("init.5") static const init_item init_item5 = {init_nop_process}; +INIT_SECTION("init.6") static const init_item init_item_end = {init_nop_process}; + +void init_execute(void) +{ + const init_item *it = &init_item_start + 1; /* Get address from starting range */ + init_handler_t handler = NULL; + + /* Traverse the initialization function of the entire range and execute */ + while (it < &init_item_end) + { + handler = it++->handler; + if (handler != NULL) handler(); + } +} diff --git a/source/00_application/init.h b/source/00_application/init.h new file mode 100644 index 0000000..921f9f7 --- /dev/null +++ b/source/00_application/init.h @@ -0,0 +1,71 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file init.h + * \brief This is a C language initialize export module. + * \author Lamdonn + * \version 1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __init_H +#define __init_H + +/* version infomation */ + +#define INIT_V_MAJOR 1 +#define INIT_V_MINOR 0 +#define INIT_V_PATCH 0 + +/* ***** Important!!! ***** + * Please make sure that the init module is compiled in a higher order than the files using the init module. + */ + +/* Define anonymous variable format */ +#define __INIT_ANONY(type, var, line) type var##line +#define INIT_ANONY_DEFINE(type, var) __INIT_ANONY(type, var, __LINE__) + +/* Special instructions for compilation */ +#if defined(__CC_ARM) || defined(__GNUC__) /* ARM,GCC*/ + #define INIT_SECTION(x) __attribute__((section(x))) + #define INIT_USED __attribute__((used)) +#elif defined (__ICCARM__) /*IAR */ + #define INIT_SECTION(x) @ x + #define INIT_USED __root +#else + #error "Current tool chain haven't supported yet!" +#endif + +/* Initialization function format */ +typedef void (*init_handler_t)(void); + +/* Initialization structure definition */ +typedef struct { + init_handler_t handler; +} init_item; + +/* Initialize export instead of macro definition */ +#define __initialize(func, level) \ + INIT_USED INIT_ANONY_DEFINE(const init_item, init_item_##func) \ + INIT_SECTION("init."level) = {func} + +/** + * \brief different levels of initialization are exported. The smaller the number, the more priority the initialization will be executed. + * \param[in] func: initialization function that needs to be executed + * \return none + */ +#define init_export_hardware(func) __initialize(func, "1") +#define init_export_driver(func) __initialize(func, "2") +#define init_export_system(func) __initialize(func, "3") +#define init_export_module(func) __initialize(func, "4") +#define init_export_app(func) __initialize(func, "5") + +/** + * \brief All initialization functions are called in this function, + this function needs to be called as early as possible to execute each initialization function. + * \return none + */ +void init_execute(void); + +#endif diff --git a/source/00_application/main.c b/source/00_application/main.c new file mode 100644 index 0000000..27acce6 --- /dev/null +++ b/source/00_application/main.c @@ -0,0 +1,70 @@ +#include +#include +#include "init.h" +#include "kern.h" +#include "cPatten.h" + +static unsigned int get_msec(void) +{ + struct timeval mstime; + unsigned int ms = 0; + gettimeofday(&mstime, NULL); + ms = mstime.tv_sec * 1000 + mstime.tv_usec / 1000; + return ms; +} + +unsigned long long reckon_usec(void) +{ + struct timeval mstime; + unsigned long long us = 0; + gettimeofday(&mstime, NULL); + us = mstime.tv_sec * 1000000 + mstime.tv_usec; + return us; +} + +static void show(void) +{ + printf("Hello varch!\r\n"); + cPatten_setMask('`'); + cPatten_showString("Varch"); + cPatten_setMask('*'); + cPatten_showString("Varch"); + cPatten_setMask('0'); + cPatten_showString("Varch"); + cPatten_setMask('#'); + cPatten_showString("Varch"); +} + +int main(int argc, char *argv[]) +{ + show(); + + if (kern_init(get_msec, 1) == KE_OK) + { + printf("################################################\r\n"); + printf("##### Kern #####\r\n"); + printf("################################################\r\n"); + } + else + { + printf("kern init fail!\r\n"); + return 0; + } + + init_execute(); + kern_schedule(); + + return 0; +} + +#ifdef _WIN32 +void task_fflush(void) +{ + fflush(stdout); +} +static void fflush_init(void) +{ + task_create(1, task_fflush); +} +init_export_app(fflush_init); +#endif \ No newline at end of file diff --git a/source/01_general/arg.h b/source/01_general/arg.h new file mode 100644 index 0000000..81b5761 --- /dev/null +++ b/source/01_general/arg.h @@ -0,0 +1,311 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file arg.h + * \unit arg + * \brief Get the number of indeterminate parameters and the corresponding parameters,up to 128 maximum parameters are supported + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __arg_H +#define __arg_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/* Version infomation */ + +#define ARG_V_MAJOR 1 +#define ARG_V_MINOR 0 +#define ARG_V_PATCH 0 + +#ifndef ARG_MAX + +/* The maximum number of args currently supported */ +#define ARG_MAX 124 + +/* Obtain indefinite parameter count, the macro definition does not recognize the 0 parameter */ +#define ARGC(...) __ARGC(__VA_ARGS__) + +/* Obtain indefinite parameter count, this macro definition can recognize 0 parameters, but compilation requires additional space */ +#define ARGC0(...) __ARGC0(__VA_ARGS__) + +/* Obtain the parameter value at the specified position in an indefinite parameter */ +#define ARGS(x, ...) __VA##x(__VA_ARGS__) + +/* !!!!! The following macro definitions do not require attention */ +/* !!!!! The following macro definitions do not require attention */ +/* !!!!! The following macro definitions do not require attention */ + +#define __ARGS(X) (X) + +#define __ARGC_N(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,_119,_120,_121,_122,_123,N,...) N +#define __ARGC_N0(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,_119,_120,_121,_122,_123,N,...) N==1?(#_0)[0]!=0:N + +#define __ARGC(...) __ARGS(__ARGC_N(__VA_ARGS__,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)) +#define __ARGC0(...) __ARGS(__ARGC_N0(__VA_ARGS__,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)) + +#define __ARG0(_0,...) _0 +#define __ARG1(_0,_1,...) _1 +#define __ARG2(_0,_1,_2,...) _2 +#define __ARG3(_0,_1,_2,_3,...) _3 +#define __ARG4(_0,_1,_2,_3,_4,...) _4 +#define __ARG5(_0,_1,_2,_3,_4,_5,...) _5 +#define __ARG6(_0,_1,_2,_3,_4,_5,_6,...) _6 +#define __ARG7(_0,_1,_2,_3,_4,_5,_6,_7,...) _7 +#define __ARG8(_0,_1,_2,_3,_4,_5,_6,_7,_8,...) _8 +#define __ARG9(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,...) _9 +#define __ARG10(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,...) _10 +#define __ARG11(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,...) _11 +#define __ARG12(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,...) _12 +#define __ARG13(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,...) _13 +#define __ARG14(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,...) _14 +#define __ARG15(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,...) _15 +#define __ARG16(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,...) _16 +#define __ARG17(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,...) _17 +#define __ARG18(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,...) _18 +#define __ARG19(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,...) _19 +#define __ARG20(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,...) _20 +#define __ARG21(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,...) _21 +#define __ARG22(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,...) _22 +#define __ARG23(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,...) _23 +#define __ARG24(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,...) _24 +#define __ARG25(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,...) _25 +#define __ARG26(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,...) _26 +#define __ARG27(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,...) _27 +#define __ARG28(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,...) _28 +#define __ARG29(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,...) _29 +#define __ARG30(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,...) _30 +#define __ARG31(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,...) _31 +#define __ARG32(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,...) _32 +#define __ARG33(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,...) _33 +#define __ARG34(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,...) _34 +#define __ARG35(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,...) _35 +#define __ARG36(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,...) _36 +#define __ARG37(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,...) _37 +#define __ARG38(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,...) _38 +#define __ARG39(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,...) _39 +#define __ARG40(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,...) _40 +#define __ARG41(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,...) _41 +#define __ARG42(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,...) _42 +#define __ARG43(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,...) _43 +#define __ARG44(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,...) _44 +#define __ARG45(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,...) _45 +#define __ARG46(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,...) _46 +#define __ARG47(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,...) _47 +#define __ARG48(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,...) _48 +#define __ARG49(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,...) _49 +#define __ARG50(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,...) _50 +#define __ARG51(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,...) _51 +#define __ARG52(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,...) _52 +#define __ARG53(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,...) _53 +#define __ARG54(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,...) _54 +#define __ARG55(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,...) _55 +#define __ARG56(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,...) _56 +#define __ARG57(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,...) _57 +#define __ARG58(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,...) _58 +#define __ARG59(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,...) _59 +#define __ARG60(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,...) _60 +#define __ARG61(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,...) _61 +#define __ARG62(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,...) _62 +#define __ARG63(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,...) _63 +#define __ARG64(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,...) _64 +#define __ARG65(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,...) _65 +#define __ARG66(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,...) _66 +#define __ARG67(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,...) _67 +#define __ARG68(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,...) _68 +#define __ARG69(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,...) _69 +#define __ARG70(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,...) _70 +#define __ARG71(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,...) _71 +#define __ARG72(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,...) _72 +#define __ARG73(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,...) _73 +#define __ARG74(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,...) _74 +#define __ARG75(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,...) _75 +#define __ARG76(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,...) _76 +#define __ARG77(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,...) _77 +#define __ARG78(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,...) _78 +#define __ARG79(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,...) _79 +#define __ARG80(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,...) _80 +#define __ARG81(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,...) _81 +#define __ARG82(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,...) _82 +#define __ARG83(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,...) _83 +#define __ARG84(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,...) _84 +#define __ARG85(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,...) _85 +#define __ARG86(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,...) _86 +#define __ARG87(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,...) _87 +#define __ARG88(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,...) _88 +#define __ARG89(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,...) _89 +#define __ARG90(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,...) _90 +#define __ARG91(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,...) _91 +#define __ARG92(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,...) _92 +#define __ARG93(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,...) _93 +#define __ARG94(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,...) _94 +#define __ARG95(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,...) _95 +#define __ARG96(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,...) _96 +#define __ARG97(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,...) _97 +#define __ARG98(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,...) _98 +#define __ARG99(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,...) _99 +#define __ARG100(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,...) _100 +#define __ARG101(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,...) _101 +#define __ARG102(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,...) _102 +#define __ARG103(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,...) _103 +#define __ARG104(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,...) _104 +#define __ARG105(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,...) _105 +#define __ARG106(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,...) _106 +#define __ARG107(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,...) _107 +#define __ARG108(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,...) _108 +#define __ARG109(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,...) _109 +#define __ARG110(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,...) _110 +#define __ARG111(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,...) _111 +#define __ARG112(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,...) _112 +#define __ARG113(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,...) _113 +#define __ARG114(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,...) _114 +#define __ARG115(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,...) _115 +#define __ARG116(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,...) _116 +#define __ARG117(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,...) _117 +#define __ARG118(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,...) _118 +#define __ARG119(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,_119,...) _119 +#define __ARG120(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,_119,_120,...) _120 +#define __ARG121(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,_119,_120,_121,...) _121 +#define __ARG122(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,_119,_120,_121,_122,...) _122 +#define __ARG123(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69,_70,_71,_72,_73,_74,_75,_76,_77,_78,_79,_80,_81,_82,_83,_84,_85,_86,_87,_88,_89,_90,_91,_92,_93,_94,_95,_96,_97,_98,_99,_100,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,_119,_120,_121,_122,_123,...) _123 + +#define __VA0(...) __ARGS(__ARG0(__VA_ARGS__,0)) +#define __VA1(...) __ARGS(__ARG1(__VA_ARGS__,0,0)) +#define __VA2(...) __ARGS(__ARG2(__VA_ARGS__,0,0,0)) +#define __VA3(...) __ARGS(__ARG3(__VA_ARGS__,0,0,0,0)) +#define __VA4(...) __ARGS(__ARG4(__VA_ARGS__,0,0,0,0,0)) +#define __VA5(...) __ARGS(__ARG5(__VA_ARGS__,0,0,0,0,0,0)) +#define __VA6(...) __ARGS(__ARG6(__VA_ARGS__,0,0,0,0,0,0,0)) +#define __VA7(...) __ARGS(__ARG7(__VA_ARGS__,0,0,0,0,0,0,0,0)) +#define __VA8(...) __ARGS(__ARG8(__VA_ARGS__,0,0,0,0,0,0,0,0,0)) +#define __VA9(...) __ARGS(__ARG9(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0)) +#define __VA10(...) __ARGS(__ARG10(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA11(...) __ARGS(__ARG11(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA12(...) __ARGS(__ARG12(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA13(...) __ARGS(__ARG13(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA14(...) __ARGS(__ARG14(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA15(...) __ARGS(__ARG15(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA16(...) __ARGS(__ARG16(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA17(...) __ARGS(__ARG17(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA18(...) __ARGS(__ARG18(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA19(...) __ARGS(__ARG19(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA20(...) __ARGS(__ARG20(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA21(...) __ARGS(__ARG21(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA22(...) __ARGS(__ARG22(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA23(...) __ARGS(__ARG23(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA24(...) __ARGS(__ARG24(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA25(...) __ARGS(__ARG25(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA26(...) __ARGS(__ARG26(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA27(...) __ARGS(__ARG27(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA28(...) __ARGS(__ARG28(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA29(...) __ARGS(__ARG29(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA30(...) __ARGS(__ARG30(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA31(...) __ARGS(__ARG31(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA32(...) __ARGS(__ARG32(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA33(...) __ARGS(__ARG33(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA34(...) __ARGS(__ARG34(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA35(...) __ARGS(__ARG35(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA36(...) __ARGS(__ARG36(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA37(...) __ARGS(__ARG37(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA38(...) __ARGS(__ARG38(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA39(...) __ARGS(__ARG39(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA40(...) __ARGS(__ARG40(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA41(...) __ARGS(__ARG41(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA42(...) __ARGS(__ARG42(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA43(...) __ARGS(__ARG43(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA44(...) __ARGS(__ARG44(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA45(...) __ARGS(__ARG45(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA46(...) __ARGS(__ARG46(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA47(...) __ARGS(__ARG47(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA48(...) __ARGS(__ARG48(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA49(...) __ARGS(__ARG49(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA50(...) __ARGS(__ARG50(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA51(...) __ARGS(__ARG51(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA52(...) __ARGS(__ARG52(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA53(...) __ARGS(__ARG53(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA54(...) __ARGS(__ARG54(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA55(...) __ARGS(__ARG55(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA56(...) __ARGS(__ARG56(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA57(...) __ARGS(__ARG57(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA58(...) __ARGS(__ARG58(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA59(...) __ARGS(__ARG59(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA60(...) __ARGS(__ARG60(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA61(...) __ARGS(__ARG61(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA62(...) __ARGS(__ARG62(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA63(...) __ARGS(__ARG63(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA64(...) __ARGS(__ARG64(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA65(...) __ARGS(__ARG65(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA66(...) __ARGS(__ARG66(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA67(...) __ARGS(__ARG67(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA68(...) __ARGS(__ARG68(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA69(...) __ARGS(__ARG69(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA70(...) __ARGS(__ARG70(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA71(...) __ARGS(__ARG71(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA72(...) __ARGS(__ARG72(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA73(...) __ARGS(__ARG73(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA74(...) __ARGS(__ARG74(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA75(...) __ARGS(__ARG75(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA76(...) __ARGS(__ARG76(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA77(...) __ARGS(__ARG77(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA78(...) __ARGS(__ARG78(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA79(...) __ARGS(__ARG79(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA80(...) __ARGS(__ARG80(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA81(...) __ARGS(__ARG81(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA82(...) __ARGS(__ARG82(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA83(...) __ARGS(__ARG83(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA84(...) __ARGS(__ARG84(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA85(...) __ARGS(__ARG85(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA86(...) __ARGS(__ARG86(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA87(...) __ARGS(__ARG87(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA88(...) __ARGS(__ARG88(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA89(...) __ARGS(__ARG89(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA90(...) __ARGS(__ARG90(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA91(...) __ARGS(__ARG91(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA92(...) __ARGS(__ARG92(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA93(...) __ARGS(__ARG93(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA94(...) __ARGS(__ARG94(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA95(...) __ARGS(__ARG95(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA96(...) __ARGS(__ARG96(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA97(...) __ARGS(__ARG97(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA98(...) __ARGS(__ARG98(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA99(...) __ARGS(__ARG99(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA100(...) __ARGS(__ARG100(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA101(...) __ARGS(__ARG101(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA102(...) __ARGS(__ARG102(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA103(...) __ARGS(__ARG103(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA104(...) __ARGS(__ARG104(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA105(...) __ARGS(__ARG105(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA106(...) __ARGS(__ARG106(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA107(...) __ARGS(__ARG107(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA108(...) __ARGS(__ARG108(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA109(...) __ARGS(__ARG109(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA110(...) __ARGS(__ARG110(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA111(...) __ARGS(__ARG111(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA112(...) __ARGS(__ARG112(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA113(...) __ARGS(__ARG113(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA114(...) __ARGS(__ARG114(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA115(...) __ARGS(__ARG115(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA116(...) __ARGS(__ARG116(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA117(...) __ARGS(__ARG117(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA118(...) __ARGS(__ARG118(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA119(...) __ARGS(__ARG119(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA120(...) __ARGS(__ARG120(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA121(...) __ARGS(__ARG121(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA122(...) __ARGS(__ARG122(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) +#define __VA123(...) __ARGS(__ARG123(__VA_ARGS__,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)) + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/01_general/cPatten.c b/source/01_general/cPatten.c new file mode 100644 index 0000000..a5f9afa --- /dev/null +++ b/source/01_general/cPatten.c @@ -0,0 +1,311 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file cPatten.c + * \unit cPatten + * \brief This is a C language artistic character patterns + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "cPatten.h" +#include +#include + +typedef struct +{ + char *line[5]; +} artCharType_simple; + +static artCharType_simple artCharType_simpleTable[] = { + /* A */ + { + " AAA ", + " A A ", + " AAAAA ", + " A A ", + " A A ", + }, + /* B */ + { + " BBB ", + " B B ", + " BBB ", + " B B ", + " BBB ", + }, + /* C */ + { + " CCC ", + " C C ", + " C ", + " C C ", + " CCC ", + }, + /* D */ + { + " DDD ", + " D D ", + " D D ", + " D D ", + " DDD ", + }, + /* E */ + { + " EEEE ", + " E ", + " EEE ", + " E ", + " EEEE ", + }, + /* F */ + { + " FFFFF ", + " F ", + " FFF ", + " F ", + " F ", + }, + /* G */ + { + " GGG ", + " G ", + " G GG ", + " G G ", + " GGG ", + }, + /* H */ + { + " H H ", + " H H ", + " HHHHH ", + " H H ", + " H H ", + }, + /* I */ + { + " III ", + " I ", + " I ", + " I ", + " III ", + }, + /* J */ + { + " JJJ ", + " J ", + " J ", + " J J ", + " JJJJ ", + }, + /* K */ + { + " K K ", + " K K ", + " KK ", + " K K ", + " K K ", + }, + /* L */ + { + " L ", + " L ", + " L ", + " L ", + " LLLLL ", + }, + /* M */ + { + " M M ", + " MM MM ", + " M M M M ", + " M M M ", + " M M ", + }, + /* N */ + { + " N N ", + " NN N ", + " N N N ", + " N N N ", + " N N N ", + }, + /* O */ + { + " OOO ", + " O O ", + " O O ", + " O O ", + " OOO ", + }, + /* P */ + { + " PPP ", + " P P ", + " PPP ", + " P ", + " P ", + }, + /* Q */ + { + " QQQ ", + " Q Q ", + " Q Q ", + " Q QQ ", + " QQQ Q ", + }, + /* R */ + { + " RRR ", + " R R ", + " RRR ", + " R R ", + " R R ", + }, + /* S */ + { + " SSS ", + " S ", + " SS ", + " S ", + " SSS ", + }, + /* T */ + { + " TTTTT ", + " T ", + " T ", + " T ", + " T ", + }, + /* U */ + { + " U U ", + " U U ", + " U U ", + " U U ", + " UUU ", + }, + /* V */ + { + " V V ", + " V V ", + " V V ", + " V V ", + " V ", + }, + /* W */ + { + " W W ", + " W W ", + " W W W ", + " W W W W ", + " W W W ", + }, + /* X */ + { + " X X ", + " X X ", + " X ", + " X X ", + " X X ", + }, + /* Y */ + { + " Y Y ", + " Y Y ", + " Y ", + " Y ", + " Y ", + }, + /* Z */ + { + " ZZZZZ ", + " Z ", + " Z ", + " Z ", + " ZZZZZ ", + } +}; + +static void cPatten_putChar(char c, char mask) +{ + if (mask && c != ' ') + { + putchar(mask); + } + else + { + putchar(c); + } +} + +static void cPatten_putString(char *s, char mask) +{ + while (*s) + { + cPatten_putChar(*s, mask); + s++; + } +} + +static char cMask = 0; + +int cPatten_setMask(char c) +{ + if ((c >= ' ' && c <= '~') || c == 0) + { + cMask = c; + } + else return 0; + + return 1; +} + +void cPatten_showChar(char c) +{ + int i; + int tableIndex = -1; + + if ('A' <= c && c <= 'Z') tableIndex = c - 'A'; + if ('a' <= c && c <= 'z') tableIndex = c - 'a'; + + if (tableIndex < 0) return; + + for (i = 0; i < 5; i++) + { + cPatten_putString(artCharType_simpleTable[tableIndex].line[i], cMask); + printf("\r\n"); + } +} + +void cPatten_showString(char *s) +{ + int len, i, j; + int tableIndex; + char c; + + if (!s) return; + + len = strlen(s); + + for (i = 0; i < 5; i++) + { + for (j = 0; j < len; j++) + { + c = s[j]; + + tableIndex = -1; + + if ('A' <= c && c <= 'Z') tableIndex = c - 'A'; + if ('a' <= c && c <= 'z') tableIndex = c - 'a'; + + if (tableIndex < 0) continue; + + cPatten_putString(artCharType_simpleTable[tableIndex].line[i], cMask); + } + printf("\r\n"); + } +} \ No newline at end of file diff --git a/source/01_general/cPatten.h b/source/01_general/cPatten.h new file mode 100644 index 0000000..f87a38f --- /dev/null +++ b/source/01_general/cPatten.h @@ -0,0 +1,29 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file cPatten.h + * \unit cPatten + * \brief This is a C language artistic character patterns + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __cPatten_H +#define __cPatten_H + +#include + +/* Version infomation */ + +#define CPATTEN_V_MAJOR 1 +#define CPATTEN_V_MINOR 0 +#define CPATTEN_V_PATCH 0 + +int cPatten_setMask(char c); +void cPatten_showChar(char c); +void cPatten_showString(char *s); + + +#endif diff --git a/source/01_general/cQueue.c b/source/01_general/cQueue.c new file mode 100644 index 0000000..1d27224 --- /dev/null +++ b/source/01_general/cQueue.c @@ -0,0 +1,43 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file cQueue.c + * \unit cQueue + * \brief This is a C language universal queue controller + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "cQueue.h" + +void cQueue_initCap(cQueue *q, int cap) +{ + q->head = 0; + q->tail = 0; + q->size = 0; + q->cap = cap; +} + +int cQueue_moveHead(cQueue *q) +{ + int index = q->head; + q->head = (q->head + 1) % q->cap; + q->size--; + return index; +} + +int cQueue_moveTail(cQueue *q) +{ + int index = q->tail; + q->tail = (q->tail + 1) % q->cap; + q->size++; + return index; +} + +int cQueue_index(cQueue *q, unsigned int index) +{ + /* Starting from the head, calculate the data index */ + return (q->head + index) % (q->cap); +} diff --git a/source/01_general/cQueue.h b/source/01_general/cQueue.h new file mode 100644 index 0000000..db76546 --- /dev/null +++ b/source/01_general/cQueue.h @@ -0,0 +1,119 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file cQueue.h + * \unit cQueue + * \brief This is a C language universal queue controller + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __cQueue_H +#define __cQueue_H + +#include + +/* Version infomation */ + +#define CQUEUE_V_MAJOR 1 +#define CQUEUE_V_MINOR 0 +#define CQUEUE_V_PATCH 0 + +typedef struct +{ + /**< Index of queue head */ + unsigned int head; + + /**< Index of queue tail */ + unsigned int tail; + + /**< Size of queue */ + unsigned int size; + + /**< Capacity of queue */ + unsigned int cap; +} cQueue; + +/* + * The control method of cQueue controller provides an interface for macro definition methods + * In general, these functions are not directly used, but rather macro definitions are used + * Using macro definition methods will be more convenient, direct, and simple + */ +void cQueue_initCap(cQueue *q, int cap); +int cQueue_moveHead(cQueue *q); +int cQueue_moveTail(cQueue *q); +int cQueue_index(cQueue *q, unsigned int index); + +/** + * \brief Initialize queue object structure. + * \param[in] qObject: queue object + * \return none + */ +#define cQueue_init(qObject) (cQueue_initCap(&((qObject).queue), sizeof((qObject).data)/sizeof((qObject).data[0]))) + +/** + * \brief check if empty. + * \param[in] qObject: queue object + * \return 1 empty or 0 not empty + */ +#define cQueue_empty(qObject) (((qObject).queue.size == 0) ? 1 : 0) + +/** + * \brief check if full. + * \param[in] qObject: queue object + * \return 1 full or 0 not full + */ +#define cQueue_full(qObject) (((qObject).queue.size == (qObject).queue.cap) ? 1 : 0) + +/** + * \brief push data into the queue. + * \param[in] qObject: queue object + * \param[in] d: pushed data + * \return 1 success or 0 fail + */ +#define cQueue_push(qObject, d) (((qObject).queue.size == (qObject).queue.cap) ? 0 : ((qObject).data[cQueue_moveTail(&((qObject).queue))] = (d), 1)) + +/** + * \brief pop data from the queue. + * \param[in] qObject: queue object + * \param[in] d: pushed data + * \return 1 success or 0 fail + */ +#define cQueue_pop(qObject, d) (((qObject).queue.size == 0) ? 0 : (((d) = ((qObject).data[cQueue_moveHead(&((qObject).queue))])), 1)) + +/** + * \brief Random access method for queue data. + * \param[in] qObject: queue object + * \param[in] i: index starting from queue header + * \return Reference to queue data + */ +#define cQueue_at(qObject, i) ((qObject).data[cQueue_index(&((qObject).queue))]) + +#if 0 /* cQueue demo */ +void test_int(void) +{ + typedef struct { /* 1. Define a new structure */ + cQueue queue; /* 2. The structure contains cQueue structure member and named `queue` */ + int data[10]; /* 3. Define a buffer array of any data type, and named data */ + } intQueueType; /* 4. New structure name */ + intQueueType intQueue; /* 5. Define a queue structure variable */ + + cQueue_init(intQueue); /* 6. Initialize the variable */ + /* 7. Next, you can manipulate the queue through this structural variable */ + for (int i = 0; i < intQueue.queue.cap; i++) /* 8. Queue capacity */ + { + cQueue_push(intQueue, i); /* 9. Push data */ + } + + while (intQueue.queue.size > 0) /* 10. Queue size */ + { + int data; + cQueue_pop(intQueue, data); /* 11. Pop data */ + printf("cQueue_pop %d\r\n", data); + } +} +#endif + +#endif diff --git a/source/01_general/calculate.c b/source/01_general/calculate.c new file mode 100644 index 0000000..e311b5a --- /dev/null +++ b/source/01_general/calculate.c @@ -0,0 +1,440 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file calculate.h + * \unit calculate + * \brief This is a simple math expression calculation module for C language + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "calculate.h" +#include +#include +#include +#include +#include + +/* Constant and macro definitions */ +#define PI 3.141592653589793238462643383279502884197169399375105820974944592308 +#define E 2.718281828459045235360287471352662497757247093699959574966967627724 +#define isint(n) (fabs(floor(n) - n) <= DBL_EPSILON && fabs(n) < 1.0e60) + +/* Minimum unit information of calculation expression */ +typedef struct +{ + char type; /**< Type of information, '+', '-', '*' ... */ + double value; /**< Unit value */ +} cinfo_t; + +/* Calculation function structure definition */ +typedef struct +{ + char *name; /**< Function name */ + int len; /**< Length of function name */ + double (*func)(); /**< C function that performs calculations */ + int argc; /**< Number of function arguments, Maximum support 8 */ +} function_t; + +/* The definition of the built-in function corresponding to the calculation function */ +static double cot(double v1) { return 1 / tan(v1); } +static double acot(double v1) { return atan(1 / v1); } +static double min(double v1, double v2) { return (v1v2)?v1:v2; } +static double log_r(double v1, double v2) { return log(v2) / log(v1); } + +/* Built-in function table */ +static function_t in_function_table[] = +{ /* name len func argc */ + {"abs", 3, fabs, 1}, + {"sqrt", 4, sqrt, 1}, + {"exp", 3, exp, 1}, + {"ln", 2, log, 1}, + {"log10", 5, log10, 1}, + {"sin", 3, sin, 1}, + {"cos", 3, cos, 1}, + {"tan", 3, tan, 1}, + {"cot", 3, cot, 1}, + {"asin", 4, asin, 1}, + {"acos", 4, acos, 1}, + {"atan", 4, atan, 1}, + {"acot", 4, acot, 1}, + {"ceil", 4, ceil, 1}, + {"floor", 5, floor, 1}, + {"round", 5, round, 1}, + {"min", 3, min, 2}, + {"max", 3, max, 2}, + {"pow", 3, pow, 2}, + {"log", 3, log_r, 2}, +}; + +/* External function table */ +static function_t ex_function_table[CALCULATE_EXFUNC_MAX]; + +/* The current number of external functions */ +static int ex_function_num = 0; + +/* Function declaration */ +static char *parse_value(char *p, double *n); + +static char* skip(char* in) +{ + while (*in && (unsigned char)*in <= ' ') in++; + return in; +} + +/** + * \brief convert numeric string to numeric value + * \param[in] *s: numeric string + * \param[in] len: the length of the string to be converted + * \return convert result or NAN fail + */ +static double v_atof(const char *s, int len) +{ + long double result = 0.0L; + int sign = 1; /* +/- sign */ + int fraction = 0; /* decimal part flag, 1 has a decimal part, but 0 does not. */ + long double div = 1.0L; /* decimal division factor */ + + /* Get the numerical prefix, positive or negative */ + if (*s == '-') { sign = -1; s++; } + else if (*s == '+') { s++; } + + /* Within the valid length and legal characters, convert bit by bit */ + while (len-- > 0 && (isdigit(*s) || *s == '.')) + { + /* Parsed to the decimal point, the value has a decimal part */ + if (*s == '.') + { + /* There is already a decimal point, no more decimal points are allowed. */ + if (fraction == 1) return NAN; + + /* Mark the decimal point and skip the decimal point */ + fraction = 1; + s++; + continue; + } + + /* Integer part */ + if (fraction == 0) + { + result = result * 10.0L + (long double) (*s - '0'); + } + /* Decimal part */ + else + { + div *= 10.0L; + result = result + (long double) (*s - '0') / div; + } + + s++; + } + + /* Skip extra spaces */ + while (len-- > 0) + { + if (*s++ > ' ') return NAN; + } + + return sign * result; +} + +static double get_number(char *p, int size) +{ + cinfo_t adata[size]; + cinfo_t *cal = NULL, *pre = NULL; + cinfo_t info; + int i = 0; + double n = 0; + + /* Skip invalid characters */ + p = skip(p); + + /* Get sign for unit information */ + info.type = '+'; + if (*p == '-') + { + info.type = '-'; + p = skip(p + 1); + } + + /* Parse out the first value and store it in the `adada` table */ + p = skip(parse_value(p, &info.value)); + adata[i++] = info; + + /* Consecutively parse each remaining subexpression */ + while (*p >= ' ' && *p != ')' && *p != ',') /* Encountering ')' or ',', ends the scope of the subexpression */ + { + info.type = *p; + p = skip(parse_value(skip(p + 1), &info.value)); + adata[i++] = info; + } + + /* Stepwise symbolic operations according to the precedence of the operation symbols + * 1. '^' right union + * 2. '*', '/', '%' left union + * 3. '+', '-' left union + */ + + /* '^' */ + for (i = size - 1; i > 0; i--) + { + cal = adata + i; + pre = adata + i - 1; + if (cal->type == '^') + { + cal->type = '='; + cal->value = pow(pre->value, cal->value); + pre->value = cal->value; + } + } + + /* '*', '/', '%' */ + for (i = 1; i < size; i++) + { + cal = adata + i; + pre = adata + i - 1; + + if (cal->type == '=') { cal->value = pre->value; } + else if (cal->type == '*') { cal->type = '='; cal->value = pre->value * cal->value; } + else if (cal->type == '/') { cal->type = '='; cal->value = pre->value / cal->value; } + else if (cal->type == '%') { cal->type = '='; cal->value = fmod(pre->value, cal->value); } + } + + for (i = size - 1; i > 0; i--) + { + cal = adata + i; + pre = adata + i - 1; + + if (cal->type == '=') { pre->value = cal->value; } + } + + /* '+', '-' */ + for (i = 0; i < size; i++) + { + cal = adata + i; + + if (cal->type == '+') n += cal->value; + else if (cal->type == '-') n -= cal->value; + } + + return n; +} + +static char* evaluate_expression(char *p, double *n) +{ + double t = NAN; + char *s = p; + int size = 0; + + *n = NAN; + + /* Skip invalid characters */ + p = skip(p); + + /* Skip sign for unit information */ + if (*p == '-') p = skip(p + 1); + + /* Preliminarily parse the calculation expression, that is, check the expression syntax */ + p = skip(parse_value(p, &t)); + if (isnan(t)) return p; + + size++; + + /* Divide the calculation expression into independent minimum operation units */ + while (*p >= ' ' && *p != ')' && *p != ',') + { + if (*p != '+' && *p != '-' && *p != '*' && *p != '/' && *p != '%' && *p != '^') return p; + p = skip(parse_value(skip(p + 1), &t)); + if (isnan(t)) return p; + size++; + } + + /* Get expression evaluation result */ + *n = get_number(s, size); + + return p; +} + +static char *parse_value(char *p, double *n) +{ + double value, v[8]; + char sign = '+', *s; + int i, count, argc; + function_t *function = NULL; + + *n = NAN; + + /* Get sign for unit information */ + p = skip(p); + if (*p == '-' || *p == '+') sign = *p++; + + /* Skip invalid characters and start parsing from valid characters */ + p = skip(p); + s = p; + + count = sizeof(in_function_table) / sizeof(in_function_table[0]); + + while (*p && *p != ')' && *p != ',' && *p != '+' && *p != '-' && *p != '*' && *p != '/' && *p != '%' && *p != '^') + { + /* When brackets '"()" are encountered, the expression inside the brackets is evaluated first. + * In the brackets, there may be a unit, or an expression, or several parameters of the function. + */ + if (*p == '(') + { + p = skip(p + 1); + + /* The brackets are immediately adjacent to the previous character. */ + /* Calculate the result and return it directly */ + if (p == skip(s + 1)) + { + p = evaluate_expression(p, &value); + if (*p == ')') *n = value; + return p + 1; + } + + /* built-in function */ + for (i = 0; i < count; i++) + { + if (!strncmp(s, in_function_table[i].name, in_function_table[i].len)) + { + /* Check each parameter, which is an expression */ + for (argc = 0; argc < in_function_table[i].argc; argc++) + { + p = evaluate_expression(p, &v[argc]); + if (isnan(v[argc])) return p; + if (argc == in_function_table[i].argc - 1) { if (*p != ')') return p; } + else { if (*p != ',') return p; } + p++; + } + + argc = in_function_table[i].argc; + function = &in_function_table[i]; + break; + + } + } + + /* extern function */ + if (!function) + { + for (i = 0; i < ex_function_num; i++) + { + if (!strncmp(s, ex_function_table[i].name, ex_function_table[i].len)) + { + /* Check each parameter, which is an expression */ + for (argc = 0; argc < ex_function_table[i].argc; argc++) + { + p = evaluate_expression(p, &v[argc]); + if (isnan(v[argc])) return p; + if (argc == ex_function_table[i].argc - 1) { if (*p != ')') return p; } + else { if (*p != ',') return p; } + p++; + } + + argc = ex_function_table[i].argc; + function = &ex_function_table[i]; + break; + + } + } + } + + /* Call the function based on the number of function arguments */ + if (function) + { + switch (argc) + { + case 1: *n = function->func(v[0]); break; + case 2: *n = function->func(v[0], v[1]); break; + case 3: *n = function->func(v[0], v[1], v[2]); break; + case 4: *n = function->func(v[0], v[1], v[2], v[3]); break; + case 5: *n = function->func(v[0], v[1], v[2], v[3], v[4]); break; + case 6: *n = function->func(v[0], v[1], v[2], v[3], v[4], v[5]); break; + case 7: *n = function->func(v[0], v[1], v[2], v[3], v[4], v[5], v[6]); break; + case 8: *n = function->func(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]); break; + } + } + + return p; + } + + p = skip(p + 1); + } + + /* constant */ + if ((s[0] == 'p' || s[0] == 'P') && (s[1] == 'i' || s[1] == 'I')) value = PI; + else if (s[0] == 'e' || s[0] == 'E') value = E; + else if (s[0] >= '0' && s[0] <= '9') value = v_atof(s, p - s); + else return p; + + if (isnan(value)) return p; + + *n = (sign == '+') ? value : -value; + + return p; +} + +int calculate_export(const char *name, double (*func)(), int argc) +{ + int i = 0, count, len; + + if (ex_function_num >= CALCULATE_EXFUNC_MAX) return 0; + + /* check validity */ + if (!name || !func) return 0; + + if (argc <= 0) return 0; + + len = strlen(name); + if (len <= 0) return 0; + + /* Traverse the function list and check if there are duplicate functions */ + count = sizeof(in_function_table) / sizeof(in_function_table[0]); + for (i = 0; i < count; i++) + { + if (!strncmp(name, in_function_table[i].name, in_function_table[i].len)) + { + return 0; + } + } + + for (i = 0; i < ex_function_num; i++) + { + if (!strncmp(name, ex_function_table[i].name, ex_function_table[i].len)) + { + return 0; + } + } + + /* Add a function to the extern function list */ + ex_function_table[ex_function_num].name = (char *)name; + ex_function_table[ex_function_num].len = len; + ex_function_table[ex_function_num].func = func; + ex_function_table[ex_function_num].argc = argc; + ex_function_num++; + + return 1; +} + +double calculate(const char *expression) +{ + double n; + char *p; + + /* Check the validity of a calculated expression */ + if (!expression) return NAN; + + /* Start evaluating a calculation expression */ + p = evaluate_expression((char *)expression, &n); + if (isnan(n) || *p) + { + printf("Calculate fail at column %d\r\n", (int)(p - expression)); + return NAN; + } + + return n; +} + diff --git a/source/01_general/calculate.h b/source/01_general/calculate.h new file mode 100644 index 0000000..aa8603c --- /dev/null +++ b/source/01_general/calculate.h @@ -0,0 +1,40 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file calculate.h + * \unit calculate + * \brief This is a simple math expression calculation module for C language + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __calculate_H +#define __calculate_H + +/* Version infomation */ +#define CALCULATE_V_MAJOR 1 +#define CALCULATE_V_MINOR 0 +#define CALCULATE_V_REVISE 0 + +/* Configuration information */ +#define CALCULATE_EXFUNC_MAX 32 /**< The maximum extern function count supported */ + +/** + * \brief enter a calculation expression to calculate the result, support 'in_function_table[]' table operation function + * \param[in] *expression: expression + * \return calculation result or NAN fail + */ +double calculate(const char *expression); + +/** + * \brief Export external functions + * \param[in] *name: function name + * \param[in] func: function handle + * \param[in] argc: count of arguments + * \return 1 success or 0 fail + */ +int calculate_export(const char *name, double (*func)(), int argc); + +#endif diff --git a/source/01_general/command.c b/source/01_general/command.c new file mode 100644 index 0000000..b3ba177 --- /dev/null +++ b/source/01_general/command.c @@ -0,0 +1,517 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file command.c + * \unit command + * \brief This is a simple string command parsing module for C language + * \author Lamdonn + * \version v1.4.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "command.h" +#include +#include +#include +#include + +struct COMMAND +{ + char *name; /**< The name of the command, the command name itself has no space, so the string pointer passed in needs to be a constant. */ + command_handle_t handle; /**< Command matching callback handler function */ +}; + +int command_optind = 1; /* index into parent argv vector */ +int command_optopt = '?'; /* character checked for validity */ +char *command_optarg = NULL; /* argument associated with option */ + +static int command_num = 1; /* count of commands */ +static int reset = 1; /* reset command_getopt */ +static char *place = ""; /* option letter processing */ +static int nstart = -1; /* first non option argument (for permute) */ +static int nend = -1; /* first option after non options (for permute) */ + +static int cmd(int argc, char *argv[]); + +/* The basis of the command array */ +static struct COMMAND base[COMMAND_COUNT_MAX] = +{ +/* name handle */ + {"cmd", cmd}, +}; + +static void usage(void) +{ + printf( +"Usage:\n" +"Enter the command line to execute the corresponding command\n" +"\n" +"OPTIONS\n" +"[-l] : Print currently supported commands\n" +"[-n] : Print the number of currently supported commands\n" +"[-h] : Print help\n" +"[-v] : Print version\n" +"[-c] : Print the configuration information of the current command module\n" +" `argc` : The maximum number of parameters supported for parsing in the input command\n" +" `line` : The maximum length supported for parsing in the input command\n" +" `count` : The maximum command count supported\n" + ); +} + +/** + * \brief callback handler function of `cmd` command, this command is a built-in function of the command module + * \param[in] argc: count of arguments + * \param[in] argv: value of arguments + * \return result + */ +static int cmd(int argc, char *argv[]) +{ + int opt; + int flag = 0; + int i; + + /* reset getopt */ + command_opt_init(); + while (1) + { + opt = command_getopt(argc, argv, "lnhvc:"); + if (opt == -1) break; + + switch (opt) + { + case 'l' : + printf("command list: \r\n"); + for (i = 0; i < command_num; i++) + { + printf("@ %s\r\n", base[i].name); + } + break; + case 'n': + printf("%d\r\n", command_num); + break; + case 'h' : + usage(); + break; + case 'v' : + printf("command version %d.%d.%d\r\n", COMAMND_V_MAJOR, COMAMND_V_MINOR, COMAMND_V_PATCH); + break; + case 'c' : + if (!strcmp(command_optarg, "argc")) + { + printf("COMMAND_ARGC_MAX: %d\r\n", COMMAND_ARGC_MAX); + } + else if (!strcmp(command_optarg, "line")) + { + printf("COMMAND_LINE_MAX: %d\r\n", COMMAND_LINE_MAX); + } + else if (!strcmp(command_optarg, "count")) + { + printf("COMMAND_COUNT_MAX: %d\r\n", COMMAND_COUNT_MAX); + } + else + { + printf("no such optarg: %s\r\n", command_optarg); + } + break; + case '?': + printf("Unknown option `%c`\r\n", command_optopt); + return -1; + default: + usage(); + break; + } + } + + return 0; +} + +/** + * \brief Skips leading whitespace characters in a string and returns a pointer to the first non-whitespace character. + * + * \param[in] The input string. + * \return A pointer to the first non-whitespace character in the string. + */ +static char* skip(char* in) +{ + while (*in && (unsigned char)*in <= ' ') in++; + return in; +} + +int command(const char *line) +{ + static char s[COMMAND_LINE_MAX]; /* Ensure that the input command is not modified and the parsed arguments will exist here. */ + static char *argv[COMMAND_ARGC_MAX]; /* The parsed arguments pointer will exist here */ + + int argc = 0; + char *in = (char *)line; + char *out = s; + int i = 0; + + if (strlen(line) >= COMMAND_LINE_MAX) return COMMAND_E_LENGTH; + + /* Check whether the entered command line is empty command + * When there are valid characters, whether the command line conforms to the specification on the input + */ + in = skip(in); /* skip spaces */ + if (!*in) return COMMAND_E_LINE; /* no valid characters */ + + *out = 0; + argv[argc++] = out; + if (argc > COMMAND_ARGC_MAX) return COMMAND_E_ARGC; + + while (*in) + { + /* Space separator + * When a space character is encountered, it indicates that the parsing of the previous parameter has ended. + * Stop parsing arguments and record the arguments. + */ + if (*in == ' ') + { + *out++ = 0; /* Add a terminator to the previous argument */ + + in = skip(in); /* skip spaces */ + if (!*in) break; + + argv[argc++] = out; /* Start new argument parsing */ + if (argc > COMMAND_ARGC_MAX) return COMMAND_E_ARGC; + } + /* Escape character + * The escape character is `\` + specific character + */ + else if (*in == '\\') + { + in++; /* skip '\' */ + if (*in == ' ') { *out++ = ' '; } + else if (*in == '\\') { *out++ = '\\'; } + else if (*in == '\"') { *out++ = '\"'; } + else { *out++ = '\\', *out++ = *in; } /* Copy `\` with normal character */ + in++; + } + /* Double quotes + * Characters enclosed in double quotes will not be parsed, except for escaped double quotes + */ + else if (*in == '\"') + { + in++; /* skip '"' */ + while (*in) + { + if (*in == '\"') break; /* Parsed to `"` on the right, double quotes form a closed range */ + + /* Within a double quote range, if an escaped double quote is encountered, + * it will not be treated as a right double quote, but as an ordinary character. + */ + if (*in == '\\' && *(in + 1) == '\"') in++; + + *out++ = *in++; + } + i++; + } + /* General character + */ + else + { + *out++ = *in++; + } + } + *out = 0; + + /* Match command + * Match the commands in the command list one by one + */ + for (i = 0; i < command_num; i++) + { + /* If successfully matched, execute the callback processing function directly and return */ + if (!strcmp(argv[0], base[i].name)) + { + return (base[i].handle)(argc, argv); + } + } + + printf("No '%s' such command!\r\n", argv[0]); + + return COMMAND_E_MATCH; +} + +int command_export(const char *name, command_handle_t handle) +{ + int i = 0; + + if (command_num >= COMMAND_COUNT_MAX) return COMMAND_E_COUNT; + + /* check validity */ + if (!name || !handle) return COMMAND_E_NULL; + + /* Traverse the command list and check if there are duplicate commands */ + for (i = 0; i < command_num; i++) + { + if (!strcmp(name, base[i].name)) + { + return COMMAND_E_REPEAT; + } + } + + /* Add a command to the command list */ + base[command_num].name = (char *)name; + base[command_num].handle = handle; + command_num++; + + return COMMAND_E_OK; +} + +void command_clear(void) +{ + command_num = 1; +} + +/** + * \brief Calculates the greatest common divisor (GCD) of two integers. + * + * \param[in] a: The first integer. + * \param[in] b: The second integer. + * \return The GCD of the two integers. + */ +static int gcd(int a, int b) +{ + int c = a % b; + + while (c != 0) + { + a = b; + b = c; + c = a % b; + } + + return b; +} + +/** + * \brief Rearranges a subset of the command-line arguments based on given indices. + * + * Args within the specified indices are treated as non-optional (panonopt), + * from panonopt_start (inclusive) to panonopt_end (exclusive), + * followed by optional arguments from panonopt_end (inclusive) to opt_end (exclusive). + * + * \param[in] panonopt_start: The start index for non-optional arguments (inclusive). + * \param[in] panonopt_end: The end index for non-optional arguments (exclusive). + * \param[in] opt_end: The end index for all optional arguments (exclusive). + * \param[in,out] argv: The command-line arguments array that needs to be rearranged. + * \return none. + */ +static void permute_args(int panonopt_start, int panonopt_end, int opt_end, char *argv[]) +{ + int i, j; + int cstart, pos; + char *swap; + + /* calculate the lengths of non-optional and optional argument blocks. */ + int nnonopts = panonopt_end - panonopt_start; + int nopts = opt_end - panonopt_end; + + /* determine the number of cycles needed and the length of each cycle. */ + int ncycle = gcd(nnonopts, nopts); /* custom or library function to find greatest common divisor. */ + int cyclelen = (opt_end - panonopt_start) / ncycle; + + /* iterate over each cycle. */ + for (i = 0; i < ncycle; i++) + { + cstart = panonopt_end + i; /* cycle start index. */ + pos = cstart; /* current position within the cycle. */ + + /* permute the cycle. */ + for (j = 0; j < cyclelen; j++) + { + /* determine the new position within the cycle, wrapping around if necessary. */ + if (pos >= panonopt_end) + { + pos -= nnonopts; + } + else + { + pos += nopts; + } + + /* swap the arguments at pos and cstart. */ + swap = argv[pos]; + argv[pos] = argv[cstart]; + argv[cstart] = swap; + } + } +} + +int command_getopt(int argc, char *argv[], const char *optstring) +{ + char *index; /* option letter list index */ + int optc; /* option char */ + char premute = 1; /* permute non-options to the end of argv */ + char allargs = 0; /* treat non-options as args to option "-1" */ + + if (!optstring) return -1; + + if (*optstring == '+') + { + premute = 0; + optstring++; + } + else if (*optstring == '-') + { + allargs = 1; + optstring++; + } + + command_optarg = NULL; + + /* reset getopt function */ + if (reset) + { + command_optind = 1; + command_optopt = '?'; + nstart = -1; + nend = -1; + place = ""; + } + + while (1) + { + if (reset || !*place) /* update scanning pointer */ + { + reset = 0; + + /* end of argument vector */ + if (command_optind >= argc) + { + place = ""; + if (nend != -1) + { + /* do permutation, if have to */ + permute_args(nstart, nend, command_optind, argv); + command_optind -= (nend - nstart); + } + else if (nstart != -1) + { + /* if skipped non-options, set command_optind to the first of them. */ + command_optind = nstart; + } + + nstart = -1; + nend = -1; + + return -1; + } + + place = argv[command_optind]; + + if (place[0] != '-' || (place[1] == '\0' && !strchr(optstring, '-'))) + { + place = ""; /* found non-option */ + if (allargs) + { + /* return non-option as argument to option 1 */ + command_optarg = argv[command_optind++]; + return 1; + } + + /* if no permutation wanted, stop parsing at first non-option. */ + if (!premute) return -1; + + /* do permutation */ + if (nstart == -1) + { + nstart = command_optind; + } + else if (nend != -1) + { + permute_args(nstart, nend, command_optind, argv); + nstart = command_optind - (nend - nstart); + nend = -1; + } + command_optind++; + /* process next argument */ + continue; + } + if (nstart != -1 && nend == -1) + { + nend = command_optind; + } + + /* if have "-" do nothing */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') + { + command_optind++; + place = ""; + + /* if skipped non-options, have to permute. */ + if (nend != -1) + { + permute_args(nstart, nend, command_optind, argv); + command_optind -= (nend - nstart); + } + + nstart = -1; + nend = -1; + return -1; + } + } + + optc = (int)*place++; + if ((optc == (int)':') || + (optc == (int)'-' && *place != '\0') || + (index = strchr(optstring, optc)) == NULL) + { + /* + * if the user specified "-" and '-' isn't listed in options, return -1 (non-option) as per POSIX + * otherwise, it is an unknown option character (or ':'). + */ + if (optc == (int)'-' && *place == '\0') return -1; + + if (!*place) ++command_optind; + + command_optopt = optc; + + return '?'; + } + if (*++index != ':') /* doesn't take argument */ + { + if (!*place) ++command_optind; + } + else /* takes (optional) argument */ + { + command_optarg = NULL; + if (*place) /* no white space */ + { + command_optarg = place; + } + else if (index[1] != ':') /* arg not optional */ + { + if (++command_optind >= argc) /* no arg */ + { + place = ""; + command_optopt = optc; + return (*optstring == ':') ? ':' : '?'; + } + else + { + command_optarg = argv[command_optind]; + } + } + else if (!premute) + { + /* if permutation is disabled, can accept an optional arg separated by whitespace so long as it does not start with a dash (-). */ + if (command_optind + 1 < argc && *argv[command_optind + 1] != '-') + { + command_optarg = argv[++command_optind]; + } + } + + place = ""; + ++command_optind; + } + /* dump back option letter */ + return optc; + } +} + +void command_opt_init(void) +{ + reset = 1; +} diff --git a/source/01_general/command.h b/source/01_general/command.h new file mode 100644 index 0000000..8777dbf --- /dev/null +++ b/source/01_general/command.h @@ -0,0 +1,117 @@ + +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file command.h + * \unit command + * \brief This is a simple string command parsing module for C language + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __command_H +#define __command_H + +/* Version infomation */ +#define COMAMND_V_MAJOR 1 +#define COMAMND_V_MINOR 0 +#define COMAMND_V_PATCH 0 + +/* Configuration information */ +#define COMMAND_ARGC_MAX 16 /**< The maximum number of parameters supported for parsing in the input command */ +#define COMMAND_LINE_MAX 256 /**< The maximum length supported for parsing in the input command */ +#define COMMAND_COUNT_MAX 32 /**< The maximum command count supported */ + +/* Return value of command module + * Negative return values are used internally by the module + * Positive return values are used by the command callback processing function. + */ +#define COMMAND_E_OK 0 /**< Return correctly */ +#define COMMAND_E_LINE -1 /**< Command line error, no valid characters */ +#define COMMAND_E_ARGC -2 /**< Argument count exceeds maximum limit */ +#define COMMAND_E_LENGTH -3 /**< Command length exceeds maximum limit */ +#define COMMAND_E_COUNT -4 /**< Command count exceeds maximum limit */ +#define COMMAND_E_MATCH -5 /**< No matching command in command list */ +#define COMMAND_E_NULL -6 /**< NULL pointer */ +#define COMMAND_E_REPEAT -7 /**< Repeated commands */ + +/** + * Defines a function pointer type `command_handle_t`. + * + * The `command_handle_t` type represents a pointer to a function that takes two arguments: + * - `int argc`: The number of command line arguments. + * - `char *argv[]`: An array of command line arguments. + * + * The function pointed to by `command_handle_t` returns an `int` value. + * + * This function pointer type is typically used to define callbacks or handlers for command processing or execution. + */ +typedef int (*command_handle_t)(int argc, char *argv[]); + +/** + * Stores communication from `command_getopt` to the caller. + * + * In `command_getopt`, when an option that requires an argument is found, the argument value is returned through this variable. + * + * Additionally, when the `ordering` parameter is set to `RETURN_IN_ORDER`, each non-option ARGV-element is also returned through this variable. + */ +extern char *command_optarg; + +/** + * Stores the index in ARGV of the next element to be scanned. + * + * This variable is used for communication between the caller and `command_getopt`, as well as between successive calls to `command_getopt`. + * + * - When `command_getopt` is called for the first time, a value of zero indicates that it is the first call and initialization is required. + * - When `command_getopt` returns -1, `command_optind` indicates the index of the first non-option element that the caller should scan. + * - Otherwise, `command_optind` is used to communicate how much of ARGV has been scanned so far from one `command_getopt` call to the next. + */ +extern int command_optind; + +/** + * Stores the unrecognized option character that was encountered. + */ +extern int command_optopt; + +/** + * \brief command line parsing function. + * \param[in] *line: command line + * \return COMMAND_E_XXX + */ +int command(const char *line); + +/** + * \brief export command. + * \param[in] *name: command name + * \param[in] handle: command handle + * \return COMMAND_E_XXX + */ +int command_export(const char *name, command_handle_t handle); + +/** + * \brief clear all exported commands. + * \return none + */ +void command_clear(void); + +/** + * \brief Parses the command line arguments for options using the short option format. + * + * \param[in] argc: The number of command line arguments. + * \param[in] argv: The array of command line arguments. + * \param[in] optstring: The string containing valid option characters. + * + * \return The next option character or a special value indicating the end or an error. + */ +int command_getopt(int argc, char *argv[], const char *optstring); + +/** + * \brief Initialize command options and execute this function before using `command_getopt` to traverse parameters. + * \return none + */ +void command_opt_init(void); + +#endif + diff --git a/source/01_general/dList.c b/source/01_general/dList.c new file mode 100644 index 0000000..ba6d3f2 --- /dev/null +++ b/source/01_general/dList.c @@ -0,0 +1,462 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file dList.c + * \unit dList + * \brief This is a C language doubly linked list + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "dList.h" +#include + +dList *dList_create(void) +{ + dList *list; + + /* Allocate memory for the dList structure */ + list = (dList *)malloc(sizeof(dList)); + if (!list) return NULL; + + /* Initialize structural parameters */ + list->next = list; + list->prev = list; + list->data = NULL; + list->size = 0; + + return list; +} + +void dList_delete(dList *list) +{ + dList *temp = NULL; + + if (!list) return; + + temp = list->prev; + temp->next = NULL; + + while (list) + { + /* Save the next pointer before freeing the current node */ + temp = list->next; + + /* Free the data associated with the current node */ + if (list->data) free(list->data); + + /* Free the memory allocated for the current node */ + free(list); + + /* Move to the next node in the list */ + list = temp; + } +} + +dList *dList_attach(dList **listRef, int index, dList *attach) +{ + dList *list, *temp; + + /* Input value validity check */ + if (!listRef) return NULL; + if (!(list = *listRef)) return NULL; + if (!attach) return NULL; + + if (list->next == list) + { + switch (index) + { + case 0: + *listRef = attach; + case 1: + case -1: + break; + default: + return NULL; + } + } + else + { + if (index > 0) + { + /* Iterative movement to obtain the prev node to be inserted at the location */ + while ((--index) && (list->next != *listRef)) + { + list = list->next; + } + + /* Return NULL if the index is out of bounds */ + if (index > 0) return NULL; + } + else if (index < 0) + { + /* Iterative movement to obtain the prev node to be inserted at the location */ + while ((++index) && (list->prev != *listRef)) + { + list = list->prev; + } + list = list->prev; + + /* Return NULL if the index is out of bounds */ + if (index < 0) return NULL; + } + else + { + list = list->prev; + *listRef = attach; + } + } + + temp = attach->prev; + + temp->next = list->next; + list->next->prev = temp; + + list->next = attach; + attach->prev = list; + + return attach; +} + +static int dList_locate2(dList *list, int begin, int end, dList **beginNodeRef, dList **endNodeRef) +{ + dList *beginNode = NULL, *endNode = NULL; + + if (begin >= 0) + { + /* Move to the position in the original list where the sublist should be attached */ + beginNode = dList_to(list, begin); + if (!beginNode) return 0; + + /* Forward positioning of end node */ + if (end > 0) + { + /* Ensure the correctness of the start and end positions */ + if (end < begin) return 0; + + /* Starting from the begin node, iteratively move to the specified position node */ + endNode = beginNode; + while ((begin < end) && (endNode->next != list)) + { + endNode = endNode->next; + begin++; + } + + /* Return NULL if the index is out of bounds */ + if (begin != end) return 0; + } + else + { + /* Iterative movement to obtain the prev node to be inserted at the location */ + endNode = list->prev; + while ((++end) && (endNode != beginNode)) + { + endNode = endNode->prev; + } + + /* Return NULL if the index is out of bounds */ + if (end < 0) return 0; + } + } + else + { + /* Starting from the beginning in reverse, it is not allowed to position the end from the forward direction */ + if (end > 0) return 0; + + /* Ensure the correctness of the start and end positions */ + if (end < begin) return 0; + + /* Move to the position in the original list where the sublist should be attached */ + endNode = dList_to(list, end); + if (!endNode) return 0; + + /* Starting from the end node, iteratively move to the specified position node */ + beginNode = endNode; + begin -= (end + 1); + while ((++begin) && (beginNode != list)) + { + beginNode = beginNode->prev; + } + + /* Return NULL if the index is out of bounds */ + if (begin < 0) return 0; + } + + *beginNodeRef = beginNode; + *endNodeRef = endNode; + + return 1; +} + +dList *dList_detach(dList **listRef, int begin, int end, dList **outPrev) +{ + dList *list, *detach = NULL, *prev = NULL, *beginNode = NULL, *endNode = NULL; + + /* Input value validity check */ + if (!listRef) return 0; + if (!(list = *listRef)) return 0; + + if (!dList_locate2(list, begin, end, &beginNode, &endNode)) return NULL; + + /* Update the head of the list to point to the node following the detached sublist */ + if (beginNode == *listRef) *listRef = endNode->next; + + prev = beginNode->prev; + + /* Adjusting the orientation of two circular doubly linked lists */ + prev->next = endNode->next; + endNode->next->prev = prev; + + beginNode->prev = endNode; + endNode->next = beginNode; + + detach = beginNode; + + /* Store the pointer to the previous node of the detached sublist */ + if (outPrev) *outPrev = prev; + + return detach; +} + +dList *dList_insert(dList **listRef, int index, void *data, int size) +{ + dList *node; + + /* Input value validity check */ + if (!listRef) return NULL; + + /* Create a new node */ + node = dList_create(); + if (!node) return NULL; + + /* Set the data for the new node */ + if (!dList_set(node, data, size)) goto FAIL; + + /* Insert the new node at the specified position */ + if (!*listRef) + { + if (index == 0 || index == -1) *listRef = node; + else goto FAIL; + } + else + { + /* Jump to the failure label if attaching the new node fails */ + if (!dList_attach(listRef, index, node)) goto FAIL; + } + + return node; + +FAIL: + /* Delete the new node in case of failure */ + dList_delete(node); + return NULL; +} + +int dList_erase(dList **listRef, int index, dList **outPrev) +{ + dList *node; + + /* Detach the node at the specified position */ + node = dList_detach(listRef, index, index, outPrev); + if (!node) return 0; + + /* Delete the detached node */ + dList_delete(node); + + return 1; +} + +int dList_pushFront(dList **listRef, void *data, int size) +{ + return dList_insert(listRef, 0, data, size) ? 1 : 0; +} + +int dList_pushBack(dList **listRef, void *data, int size) +{ + return dList_insert(listRef, -1, data, size) ? 1 : 0; +} + +int dList_popFront(dList **listRef) +{ + return dList_erase(listRef, 0, NULL); +} + +int dList_popBack(dList **listRef) +{ + return dList_erase(listRef, -1, NULL); +} + +int dList_append(dList *list, dList **append) +{ + dList *prev; + + /* Input value validity check */ + if (!list) return 0; + if (!append || !*append) return 0; + + /* Move to the last node of the list */ + prev = list->prev; + + /* Connect the last node of the list to the head of the sublist */ + (*append)->prev->next = list; + list->prev = (*append)->prev; + + prev->next = (*append); + (*append)->prev = prev; + + /* Update the sublist pointer to NULL */ + *append = NULL; + + return 1; +} + +dList *dList_copy(dList *list, int begin, int end) +{ + dList *copy = NULL, *node = NULL, *beginNode = NULL, *endNode = NULL; + + if (!dList_locate2(list, begin, end, &beginNode, &endNode)) return NULL; + + if (!dList_pushBack(©, beginNode->data, beginNode->size)) goto FAIL; + + for (node = beginNode->next; node != endNode->next; node = node->next) + { + /* Jump to the failure label if inserting a node fails */ + if (!dList_pushBack(©, node->data, node->size)) goto FAIL; + } + + return copy; + +FAIL: + /* Delete the copied sublist in case of failure */ + dList_delete(copy); + return NULL; +} + +int dList_reverse(dList *list, int begin, int end) +{ + dList *beginNode = NULL, *endNode = NULL; + void *data; + int s; + + /* Input value validity check */ + if (!list) return 0; + + if (begin == end) return 0; + + if (!dList_locate2(list, begin, end, &beginNode, &endNode)) return 0; + + /* Traverse the specified interval in the list for reversing */ + for ( ; (beginNode->next != endNode) && (beginNode->next != endNode->prev); beginNode = beginNode->next, endNode = endNode->prev) + { + /* Swap the data and size between the current node and the corresponding node at the end position */ + data = beginNode->data; + beginNode->data = endNode->data; + endNode->data = data; + + s = beginNode->size; + beginNode->size = endNode->size; + endNode->size = s; + } + + return 1; +} + +int dList_size(dList *list) +{ + dList* node; + int size = 0; + + /* Input value validity check */ + if (!list) return 0; + + for (node = list; node; node = ((node->next == list) ? NULL : node->next)) + { + /* Increment the size counter */ + size++; + } + + return size; +} + +dList *dList_to(dList *list, int index) +{ + dList *node = list; + + /* Input value validity check */ + if (!list) return NULL; + + if (index > 0) + { + /* Iterative movement to obtain the prev node to be inserted at the location */ + while ((index--) && (node->next != list)) + { + node = node->next; + } + + /* Return NULL if the index is out of bounds */ + if (index >= 0) return NULL; + } + else if (index < 0) + { + /* Iterative movement to obtain the prev node to be inserted at the location */ + node = node->prev; + while ((++index) && (node != list)) + { + node = node->prev; + } + + /* Return NULL if the index is out of bounds */ + if (index < 0) return NULL; + } + + return node; +} + +int dList_set(dList *list, void* data, int size) +{ + void* d = NULL; + + /* Input value validity check */ + if (!list) return 0; + if (size < 0) return 0; + + /* If the incoming data size is 0, set the air sensitive data directly */ + if (size == 0) + { + if (list->data) free(list->data); + list->data = NULL; + list->size = 0; + return 1; + } + + /* If the data size is inconsistent, update the data storage space */ + if (size != list->size) + { + d = realloc(list->data, size); + if (!d) return 0; + } + list->data = d; + + /* Data assignment */ + if (data) memcpy(list->data, data, size); + + /* Update data size */ + list->size = size; + + return 1; +} + +int dList_get(dList *list, void* data, int size) +{ + /* Input value validity check */ + if (!list) return 0; + if (!data) return 0; + if (size < list->size) return 0; + + /* Data assignment */ + memcpy(data, list->data, list->size); + + return 1; +} diff --git a/source/01_general/dList.h b/source/01_general/dList.h new file mode 100644 index 0000000..7d1a4a0 --- /dev/null +++ b/source/01_general/dList.h @@ -0,0 +1,227 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file dList.h + * \unit dList + * \brief This is a C language doubly linked list + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __dList_H +#define __dList_H + +#include + +/* Version infomation */ + +#define DLIST_V_MAJOR 1 +#define DLIST_V_MINOR 0 +#define DLIST_V_PATCH 0 + +/* Type of dList */ +typedef struct dList +{ + /**< Doubly linked list, pointing to the next and prev node in the linked list */ + struct dList *next; + struct dList *prev; + + /**< The base address for storing data, allocated in the API */ + void *data; + + /**< The size of node data */ + int size; +} dList; + + +/** + * \brief Creates a new doubly linked list. + * + * \return A pointer to the newly created dList structure, or NULL if memory allocation fails. + */ +dList *dList_create(void); + +/** + * \brief Deletes a doubly linked list and frees the associated memory. + * + * \param[in] list The dList structure to delete. + */ +void dList_delete(dList *list); + +/** + * \brief Attaches a sublist to a specified position in a doubly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the original list. + * \param[in] index The index at which to attach the sublist (0-based index, negative-Reverse starting from `-1`). + * \param[in] attach The sublist to attach. + * \return A pointer to the attach head of the modified list, or NULL if invalid parameters are provided. + */ +dList *dList_attach(dList **listRef, int index, dList *attach); + +/** + * \brief Detaches a sublist from a specified position in a doubly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] begin The index at begin position which to detach the sublist (0-based index, negative-Reverse starting from `-1`). + * \param[in] end The index at end position which to detach the sublist (0-based index, negative-Reverse starting from `-1`). + * \param[out] outPrev A pointer to store the pointer to the previous node of the detached sublist. + * \return A pointer to the head of the detached sublist, or NULL if invalid parameters are provided. + */ +dList *dList_detach(dList **listRef, int begin, int end, dList **outPrev); + +/** + * \brief Inserts a node with the specified data at a specified position in a doubly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] index The index at which to insert the node (0-based index, negative-Reverse starting from `-1`). + * \param[in] data A pointer to the data to be inserted. + * \param[in] size The size of the data to be inserted. + * \return A pointer to the newly inserted node, or NULL if memory allocation fails or invalid parameters are provided. + */ +dList *dList_insert(dList **listRef, int index, void *data, int size); + +/** + * \brief Erases a node at the specified position in a doubly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] index The index of the node to erase (0-based index, negative-Reverse starting from `-1`). + * \param[out] outPrev A pointer to store the pointer to the previous node of the erased node. + * \return 1 if the node was successfully erased, 0 if invalid parameters are provided or the index is out of bounds. + */ +int dList_erase(dList **listRef, int index, dList **outPrev); + +/** + * \brief Push a data from the front to the doubly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] data A pointer to the data to be inserted. + * \param[in] size The size of the data to be inserted. + * \return 1 success or 0 fail. + */ +int dList_pushFront(dList **listRef, void *data, int size); + +/** + * \brief Push a data from the back to the doubly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] data A pointer to the data to be inserted. + * \param[in] size The size of the data to be inserted. + * \return 1 success or 0 fail. + */ +int dList_pushBack(dList **listRef, void *data, int size); + +/** + * \brief Pop a data from the front in the doubly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \return 1 success or 0 fail. + */ +int dList_popFront(dList **listRef); + +/** + * \brief Pop a data from the back in the doubly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \return 1 success or 0 fail. + */ +int dList_popBack(dList **listRef); + +/** + * \brief Appends a sublist to the end of a doubly linked list. + * + * \param[in] list The head of the doubly linked list. + * \param[in,out] append A pointer to the pointer to the head of the sublist to append. + * \return 1 success or 0 fail. + */ +int dList_append(dList *list, dList **append); + +/** + * \brief Creates a copy of a sublist within a doubly linked list. + * + * \param[in] list The head of the doubly linked list. + * \param[in] begin The index at begin position which to copy the sublist (0-based index, negative-Reverse starting from `-1`). + * \param[in] end The index at end position which to copy the sublist (0-based index, negative-Reverse starting from `-1`). + * \return A pointer to the head of the copied sublist, or NULL if invalid parameters are provided or the copying process fails. + */ +dList *dList_copy(dList *list, int begin, int end); + +/** + * \brief Reverses a sublist within a doubly linked list. + * + * \param[in] list The head of the doubly linked list. + * \param[in] begin The index at begin position which to reverse the sublist (0-based index, negative-Reverse starting from `-1`). + * \param[in] end The index at end position which to reverse the sublist (0-based index, negative-Reverse starting from `-1`). + * \return 1 if the sublist was successfully reversed, 0 if invalid parameters are provided or the reversal process fails. + */ +int dList_reverse(dList *list, int begin, int end); + +/** + * \brief Calculates the size (number of nodes) of a doubly linked list. + * + * \param[in] list The head of the doubly linked list. + * \return The size (number of nodes) of the list. + */ +int dList_size(dList *list); + +/** + * \brief Moves to a specific position in a doubly linked list. + * + * \param[in] list The head of the doubly linked list. + * \param[in] index The index of the desired position (0-based index, negative-Reverse starting from `-1`). + * \return A pointer to the node at the specified position, or NULL if invalid parameters are provided or the index is out of bounds. + */ +dList *dList_to(dList *list, int index); + +/** + * \brief Sets the data and size of a node in a doubly linked list. + * + * \param[in] list The node in the doubly linked list. + * \param[in] data A pointer to the data to be set. + * \param[in] size The size of the data to be set. + * \return 1 if the data was successfully set, 0 if invalid parameters are provided. + */ +int dList_set(dList *list, void* data, int size); + +/** + * \brief Retrieves the data from a node in a doubly linked list. + * + * \param[in] list The node in the doubly linked list. + * \param[out] data A pointer to the memory location where the data will be retrieved. + * \param[in] size The size of the data to be retrieved. + * \return 1 if the data was successfully retrieved, 0 if invalid parameters are provided or the retrieval process fails. + */ +int dList_get(dList *list, void* data, int size); + +/** + * \brief Macro for forward iterating over each node in a doubly linked list. + * + * \param[in] list The head of the doubly linked list. + * \param[in] node The iterator variable to represent each node in the list. + */ +#define dList_forEachForward(list, node) \ + for (dList* (node) = (list); (node); (node) = (((node)->next==(list))?NULL:(node)->next)) + +/** + * \brief Macro for reverse iterating over each node in a doubly linked list. + * + * \param[in] list The head of the doubly linked list. + * \param[in] node The iterator variable to represent each node in the list. + */ +#define dList_forEachReverse(list, node) \ + for (dList* (node) = (list)->prev; (node); (node) = (((node)==(list))?NULL:(node)->prev)) + +/** + * \brief Macro for referencing the data of a node in a doubly linked list. + * + * \param[in] node The node in the doubly linked list. + * \param[in] type The type of the data stored in the node. + */ +#define dList_ref(node, type) (*(type *)((node)->data)) + +/* Standard front and back indexes */ +#define dList_front (0) +#define dList_back (-1) + +#endif diff --git a/source/01_general/fsm.c b/source/01_general/fsm.c new file mode 100644 index 0000000..d079e97 --- /dev/null +++ b/source/01_general/fsm.c @@ -0,0 +1,50 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file fsm.c + * \brief This is a C language universal finite state machine module. + * \author Lamdonn + * \version 1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "fsm.h" +#include "stdlib.h" + +int fsm_init(FSM* fsm, StateTransform* trans, int count, int state) +{ + if (!fsm) return 0; + if (!trans) return 0; + if (count < 1) return 0; + + fsm->trans = trans; + fsm->count = count; + fsm->state = state; + + return 1; +} + +int fsm_execute(FSM* fsm, int event) +{ + StateTransform* trans = NULL; + int i = 0; + + if (fsm == NULL) return 0; + + for (i = 0; i < fsm->count; i++) + { + if ((fsm->state == fsm->trans[i].from) && (event == fsm->trans[i].event)) + { + trans = &fsm->trans[i]; + break; + } + } + if (trans == NULL) return 0; + + fsm->state = trans->to; + + if (trans->action) trans->action(event); + + return 1; +} \ No newline at end of file diff --git a/source/01_general/fsm.h b/source/01_general/fsm.h new file mode 100644 index 0000000..fef3705 --- /dev/null +++ b/source/01_general/fsm.h @@ -0,0 +1,134 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file fsm.h + * \brief This is a C language universal finite state machine module. + * \author Lamdonn + * \version 1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __fsm_H +#define __fsm_H + +/* Version infomation */ +#define FSM_V_MAJOR 1 +#define FSM_V_MINOR 0 +#define FSM_V_REVISE 0 + +/* State transition type definition */ +typedef struct +{ + int from; /* from stste */ + int to; /* to state */ + int event; /* event id */ + void (*action)(int event); /* transform action */ +} StateTransform; + +/* Finite State Machine type define */ +typedef struct +{ + int state; /* current stste */ + StateTransform* trans; /* transform table */ + int count; /* size of trans */ +} FSM; + +/** + * \brief initialize the state machine. + * \param[in] fsm: state machine structure address + * \param[in] trans: state transition table + * \param[in] count: count of state transition table + * \param[in] state: initial state + * \return 1 success or 0 fail + */ +int fsm_init(FSM* fsm, StateTransform* trans, int count, int state); + +/** + * \brief execution state machine. + * \param[in] fsm: state machine structure address + * \param[in] event: state transition event + * \return 1 success or 0 fail + */ +int fsm_execute(FSM* fsm, int event); + +#if 0 /* for example */ +#include +#include "fsm.h" + +enum +{ + TestState_Init = 0, + TestState_Stanby, + TestState_Run, + TestState_Error, + TestState_Exit, +}; + +enum +{ + TestEvent_Init = 0, + TestEvent_ToRun, + TestEvent_ToStanby, + TestEvent_Error, + TestEvent_Exit, +}; + +/* State transition diagram ++-----------+ +-----------+ +-----------+ +| | | | -----------> | | +| init | -------> | standby | | run | +| | | | <---------- | | ++-----------+ +-----------+ +-----------+ + | \ / | + | \ / | + | \ / | + | \ / | + | \ / | + | \/ | + | /\ | + | / \ | + | / \ | + | / \ | + | / \ | + | / \ | + v v v v + +-----------+ +-----------+ + | | | | + | error | -----------> | exit | + | | | | + +-----------+ +-----------+ +*/ +StateTransform TestFsmTable[] = +{ /* from, to, event, action */ + {TestState_Init, TestState_Stanby, TestEvent_Init, NULL}, + {TestState_Stanby, TestState_Run, TestEvent_ToRun, NULL}, + {TestState_Stanby, TestState_Error, TestEvent_Error, NULL}, + {TestState_Stanby, TestState_Exit, TestEvent_Exit, NULL}, + {TestState_Run, TestState_Stanby, TestEvent_ToStanby, NULL}, + {TestState_Run, TestState_Error, TestEvent_Error, NULL}, + {TestState_Run, TestState_Exit, TestEvent_Exit, NULL}, + {TestState_Error, TestState_Exit, TestEvent_Exit, NULL}, +}; + +int main(int argc, char* argv[]) +{ + FSM TestFsm; + int event[] = { TestState_Init, TestEvent_ToRun, TestState_Exit }; + + fsm_init(&TestFsm, TestFsmTable, 8, TestState_Init); + + printf("%d\r\n", TestFsm.count); + + for (int i = 0; i < sizeof(event) / sizeof(event[0]); i++) + { + fsm_execute(&TestFsm, event[i]); + printf("state %d\r\n", TestFsm.state); + } + + printf("%c", getchar()); + return 0; +} +#endif + +#endif diff --git a/source/01_general/kern.c b/source/01_general/kern.c new file mode 100644 index 0000000..ee6f710 --- /dev/null +++ b/source/01_general/kern.c @@ -0,0 +1,157 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file kern.h + * \unit kern + * \brief This is a simple timing task scheduling kernel module for C language + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "kern.h" +#include + +/* task control block */ +typedef struct +{ + task_t task; /* task id, unique, natural number */ + unsigned short period; /* task run cycle */ + unsigned short ctime; /* task countdown */ + task_handler_t handler; /* task callback function */ +} TCB, *tcb_t; + +/* task list */ +typedef struct TASK_LIST +{ + struct TASK_LIST *next; /* next task */ + tcb_t tcb; /* task control block */ +} TASK_LIST, *task_list_t; + +/* kern manager */ +typedef struct +{ + task_list_t base; /* next task */ + kern_tick_t tick_func; /* task control block */ + unsigned int tick; + unsigned short time_slice; + task_t running; +} KERN_MANAGE; + +static KERN_MANAGE manager; + +int kern_init(kern_tick_t tick_func, unsigned short time_slice) +{ + if (!tick_func) return KE_TASK_INVALID; + manager.tick_func = tick_func; + manager.time_slice = time_slice; + manager.base = NULL; + manager.tick = 0; + manager.running = 0; + return KE_OK; +} + +task_t task_create(unsigned short period, task_handler_t handler) +{ + task_t task = 1; + task_list_t node = NULL, prev = NULL; + + if (!handler) return 0; + + /* alloc task id */ + node = manager.base; + while (node) + { + if (task < node->tcb->task) break; + else task = node->tcb->task + 1; + + prev = node; + node = node->next; + } + + /* allocate task control block space */ + node = (task_list_t)malloc(sizeof(TASK_LIST)); + if (!node) return 0; + node->tcb = (tcb_t)malloc(sizeof(TCB)); + if (!node->tcb) { free(node); return 0; } + node->next = NULL; + + if (!prev) manager.base = node; + else + { + node->next = prev->next; + prev->next = node; + } + + node->tcb->task = task; + node->tcb->period = period; + node->tcb->ctime = 0; + node->tcb->handler = handler; + + return task; +} + +int task_delete(task_t task) +{ + task_list_t node = NULL, prev = NULL; + + if (task == 0) return KE_TASK_ID_ERR; + + node = manager.base; + while (node) + { + if (node->tcb->task >= task) break; + prev = node; + node = node->next; + } + if (!node || task != node->tcb->task) return KE_TASK_ID_ERR; + + if (!prev) manager.base = node->next; + else prev->next = node->next; + + free(node->tcb); + free(node); + + return KE_OK; +} + +task_t task_running(void) +{ + return manager.running; +} + +void kern_schedule(void) +{ + task_list_t l; + unsigned int t = manager.tick_func(); + + while (1) + { + l = manager.base; + t = manager.tick_func(); + t = t < manager.tick ? ((int)manager.tick + t) : (t - manager.tick); + + if (t) + { + while (l) + { + l->tcb->ctime += t; + if (l->tcb->ctime > l->tcb->period) + { + if (l->tcb->handler) + { + manager.running = l->tcb->task; + l->tcb->handler(); + manager.running = 0; + } + l->tcb->ctime = 0; + } + + l = l->next; + } + + manager.tick = manager.tick_func(); + } + } +} diff --git a/source/01_general/kern.h b/source/01_general/kern.h new file mode 100644 index 0000000..cc00dd7 --- /dev/null +++ b/source/01_general/kern.h @@ -0,0 +1,75 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file kern.h + * \unit kern + * \brief This is a simple timing task scheduling kernel module for C language + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __kern_H +#define __kern_H + +/* Version infomation */ +#define KERN_V_MAJOR 1 +#define KERN_V_MINOR 0 +#define KERN_V_REVISE 0 + +/* task type */ +typedef unsigned int task_t; + +/* task callback function */ +typedef void (*task_handler_t)(void); + +/* the kernel gets the time callback function */ +typedef unsigned int (*kern_tick_t)(void); + +/* kern error return value */ +#define KE_OK 0 /* no error */ +#define KE_ALLOC_FAIL 1 /* memory space allocation failed */ +#define KE_TASK_LIST_NULL 2 /* task list is empty */ +#define KE_NO_THIS_TASK 3 /* no such task */ +#define KE_TASK_NUM_OVER 4 /* number of tasks exceeded */ +#define KE_TASK_ID_ERR 5 /* wrong task id */ +#define KE_TASK_REPEAT 6 /* repetitive tasks */ +#define KE_TASK_INVALID 7 /* invalid parameter */ + +/** + * \brief initialize the kernel. + * \param[in] tick_func: function to get the tick + * \param[in] time_slice: length of tick + * \return KE_OK success or other fail + */ +int kern_init(kern_tick_t tick_func, unsigned short time_slice); + +/** + * \brief create task. + * \param[in] period: task scheduling period, the unit is the time slice period passed in by the `kern_init` + * \param[in] handler: task handler + * \return task id or 0 fail + */ +task_t task_create(unsigned short period, task_handler_t handler); + +/** + * \brief delete task. + * \param[in] task: task handler + * \return KE_OK success or other fail + */ +int task_delete(task_t task); + +/** + * \brief get running task. + * \return task id or 0 no task is running + */ +task_t task_running(void); + +/** + * \brief task scheduling function. + * \return none + */ +void kern_schedule(void); + +#endif diff --git a/source/01_general/oscp.c b/source/01_general/oscp.c new file mode 100644 index 0000000..21f3953 --- /dev/null +++ b/source/01_general/oscp.c @@ -0,0 +1,105 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file oscp.c + * \unit oscp + * \brief This is a simple analog oscilloscope module for C language + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "oscp.h" +#include + +static unsigned int scale = 10; +static int* monitor = NULL; + +static void display(int level) +{ + static int prev = -1; + int i = 0; + int min, max; + + if (prev == -1) prev = level; + + if (prev < level) + { + min = prev; + max = level; + } + else + { + min = level; + max = prev; + } + + putchar('#'); + if (prev < 0) putchar('<'); + for (i = 0; i <= RESOLUTION; i++) + { + if (prev == i) + { + putchar('|'); + } + else if (i > min && i < max) + { + putchar('_'); + } + else + { + putchar(' '); + } + } + if (prev > RESOLUTION) putchar('>'); + printf("#\r\n"); + + printf("\r#"); + for (i = 0; i <= RESOLUTION; i++) + { + if (i % 10 == 0) putchar('+'); + else putchar('-'); + } + printf("#\r"); + + prev = level; +} + +void oscp_handle(void) +{ + static unsigned int count = 0; + + count++; + if (count >= 2520000) count = 0; + + if (count % scale == 0) + { + if (monitor) display(*monitor); + } +} + +int oscp_set_monitor(int *m) +{ + if (!m) return 0; + + monitor = m; + + return 1; +} + +int oscp_set_scale(int s) +{ + if (s == O_SCALE_5MS) scale = 1; + else if (s == O_SCALE_10MS) scale = 2; + else if (s == O_SCALE_20MS) scale = 4; + else if (s == O_SCALE_50MS) scale = 10; + else if (s == O_SCALE_100MS) scale = 20; + else if (s == O_SCALE_200MS) scale = 40; + else if (s == O_SCALE_500MS) scale = 100; + else if (s == O_SCALE_1S) scale = 200; + else if (s == O_SCALE_2S) scale = 400; + else return 0; + + return 1; +} diff --git a/source/01_general/oscp.h b/source/01_general/oscp.h new file mode 100644 index 0000000..37743af --- /dev/null +++ b/source/01_general/oscp.h @@ -0,0 +1,55 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file oscp.h + * \unit oscp + * \brief This is a simple analog oscilloscope module for C language + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __oscp_H +#define __oscp_H + +/* Version infomation */ +#define OSCP_V_MAJOR 1 +#define OSCP_V_MINOR 0 +#define OSCP_V_REVISE 0 + +/* Display resolution, which is the maximum horizontal value that a numerical value can display */ +#define RESOLUTION 100 + +/* Time scale, how long it takes to display a data point */ +#define O_SCALE_5MS 1 +#define O_SCALE_10MS 2 +#define O_SCALE_20MS 3 +#define O_SCALE_50MS 4 +#define O_SCALE_100MS 5 +#define O_SCALE_200MS 6 +#define O_SCALE_500MS 7 +#define O_SCALE_1S 8 +#define O_SCALE_2S 9 + +/** + * \brief the displayed processing task function needs to be called every 5ms.. + * \return none + */ +void oscp_handle(void); + +/** + * \brief set the address of the value to be monitored. + * \param[in] *m: the address of the value to be monitored + * \return 1 success or 0 fail + */ +int oscp_set_monitor(int *m); + +/** + * \brief set the time scale. + * \param[in] *s: time scale, O_SCALE_XXX + * \return 1 success or 0 fail + */ +int oscp_set_scale(int s); + +#endif diff --git a/source/01_general/sList.c b/source/01_general/sList.c new file mode 100644 index 0000000..9682d34 --- /dev/null +++ b/source/01_general/sList.c @@ -0,0 +1,368 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file sList.c + * \unit sList + * \brief This is a C language singly linked list + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "sList.h" +#include + +sList *sList_create(void) +{ + sList *list; + + /* Allocate memory for the sList structure */ + list = (sList *)malloc(sizeof(sList)); + if (!list) return NULL; + + /* Initialize structural parameters */ + memset(list, 0, sizeof(sList)); + + return list; +} + +void sList_delete(sList *list) +{ + sList *temp = NULL; + + while (list) + { + /* Save the next pointer before freeing the current node */ + temp = list->next; + + /* Free the data associated with the current node */ + if (list->data) free(list->data); + + /* Free the memory allocated for the current node */ + free(list); + + /* Move to the next node in the list */ + list = temp; + } +} + +sList *sList_attach(sList **listRef, int index, sList *attach) +{ + sList *list, *temp = attach; + + /* Input value validity check */ + if (!listRef) return NULL; + if (!(list = *listRef)) return NULL; + if (!attach) return NULL; + + /* Attach the sublist at the beginning of the original list */ + if (index == 0) + { + /* Move to the last node of the sublist */ + while (temp->next) temp = temp->next; + + /* Connect the last node of the sublist to the original list */ + temp->next = list; + + /* Update the head of the original list to point to the sublist */ + *listRef = attach; + + return attach; + } + + /* Move to the position in the original list where the sublist should be attached */ + while (list->next && (index < 0 || --index)) + { + list = list->next; + } + /* Return NULL if the index is out of bounds */ + if (index > 0) return NULL; + + /* Attach the sublist in the middle or at the end of the original list */ + while (temp->next) temp = temp->next; + /* Connect the last node of the sublist to the node following the attachment point */ + temp->next = list->next; + /* Connect the attachment point in the original list to the sublist */ + list->next = attach; + + return attach; +} + +sList *sList_detach(sList **listRef, int index, int count, sList **outPrev) +{ + sList *list, *detach = NULL, *prev = NULL; + + /* Input value validity check */ + if (!listRef) return 0; + if (!(list = *listRef)) return 0; + + /* Move to the position in the original list where the sublist should be attached */ + while (list->next && (index < 0 || index--)) + { + /* Keep track of the previous node */ + prev = list; + /* Move to the next node in the list */ + list = list->next; + } + /* Return NULL if the index is out of bounds */ + if (index > 0) return NULL; + + /* Set the detach pointer to the current node */ + detach = list; + + /* Move to the next node in the list */ + while (list->next && (count < 0 || --count)) list = list->next; + + /* Connect the previous node to the node following the detached sublist */ + /* Update the head of the list to point to the node following the detached sublist */ + prev ? (prev->next = list->next) : (*listRef = list->next); + /* Set the next pointer of the last node in the detached sublist to NULL */ + list->next = NULL; + + /* Store the pointer to the previous node of the detached sublist */ + if (outPrev) *outPrev = prev; + + return detach; +} + +sList *sList_insert(sList **listRef, int index, void *data, int size) +{ + sList *node; + + /* Input value validity check */ + if (!listRef) return NULL; + + /* Create a new node */ + node = sList_create(); + if (!node) return NULL; + + /* Set the data for the new node */ + if (!sList_set(node, data, size)) goto FAIL; + + /* Insert the new node at the specified position */ + if (!*listRef) + { + if (index <= 0) *listRef = node; + else goto FAIL; + } + else + { + /* Jump to the failure label if attaching the new node fails */ + if (!sList_attach(listRef, index, node)) goto FAIL; + } + + return node; + +FAIL: + /* Delete the new node in case of failure */ + sList_delete(node); + return NULL; +} + +int sList_erase(sList **listRef, int index, sList **outPrev) +{ + sList *node; + + /* Detach the node at the specified position */ + node = sList_detach(listRef, index, 1, outPrev); + if (!node) return 0; + + /* Delete the detached node */ + sList_delete(node); + + return 1; +} + +int sList_pushFront(sList **listRef, void *data, int size) +{ + return sList_insert(listRef, 0, data, size) ? 1 : 0; +} + +int sList_pushBack(sList **listRef, void *data, int size) +{ + return sList_insert(listRef, -1, data, size) ? 1 : 0; +} + +int sList_popFront(sList **listRef) +{ + return sList_erase(listRef, 0, NULL); +} + +int sList_popBack(sList **listRef) +{ + return sList_erase(listRef, -1, NULL); +} + +int sList_append(sList *list, sList **append) +{ + /* Input value validity check */ + if (!list) return 0; + if (!append || !*append) return 0; + + /* Move to the last node of the list */ + list = sList_to(list, -1); + + /* Connect the last node of the list to the head of the sublist */ + list->next = *append; + /* Update the sublist pointer to NULL */ + *append = NULL; + + return 1; +} + +sList *sList_copy(sList *list, int begin, int end) +{ + int size; + sList *copy = NULL, *node = NULL; + + /* Get the size(count of list node) of list */ + size = sList_size(list); + if (size == 0) return 0; + + /* Set the default begin and end index to the last node of the list */ + if (begin < 0) begin = size - 1; + if (end < 0) end = size - 1; + + /* Return NULL if the indices are out of bounds or invalid */ + if (begin >= end || end >= size) return 0; + + /* Traverse the specified interval in the list for copying */ + for (list = sList_to(list, begin), end -= begin; end >= 0; end--, list = list->next) + { + /* Jump to the failure label if inserting a node fails */ + node = sList_insert(&node, -1, list->data, list->size); + if (!node) goto FAIL; + + /* Set the copy pointer to the head of the copied sublist */ + if (!copy) copy = node; + } + + return copy; + +FAIL: + /* Delete the copied sublist in case of failure */ + sList_delete(copy); + return NULL; +} + +int sList_reverse(sList *list, int begin, int end) +{ + int size; + sList *node, *temp; + void *data; + int s; + + /* Input value validity check */ + if (!list) return 0; + + /* Get the size(count of list node) of list */ + size = sList_size(list); + if (size == 0) return 0; + + /* Set the default begin and end index to the last node of the list */ + if (begin < 0) begin = size - 1; + if (end < 0) end = size - 1; + + /* Return NULL if the indices are out of bounds or invalid */ + if (begin >= end || end >= size) return 0; + + /* Traverse the specified interval in the list for reversing */ + for (node = sList_to(list, begin), end -= begin; end > 0; end -= 2, node = node->next) + { + /* Move to the node at the end position of the sublist */ + temp = sList_to(node, end); + + /* Swap the data and size between the current node and the corresponding node at the end position */ + data = node->data; + node->data = temp->data; + temp->data = data; + + s = node->size; + node->size = temp->size; + temp->size = s; + } + + return 1; +} + +int sList_size(sList *list) +{ + int size = 0; + + /* Input value validity check */ + if (!list) return 0; + + while (list) + { + /* Increment the size counter */ + size++; + + list = list->next; + } + + return size; +} + +sList *sList_to(sList *list, int index) +{ + /* Input value validity check */ + if (!list) return NULL; + + /* Move to the next node in the list */ + while (list->next && (index < 0 || index--)) + { + list = list->next; + } + /* Return NULL if the index is out of bounds */ + if (index > 0) return NULL; + + return list; +} + +int sList_set(sList *list, void* data, int size) +{ + void* d = NULL; + + /* Input value validity check */ + if (!list) return 0; + if (size < 0) return 0; + + /* If the incoming data size is 0, set the air sensitive data directly */ + if (size == 0) + { + if (list->data) free(list->data); + list->data = NULL; + list->size = 0; + return 1; + } + + /* If the data size is inconsistent, update the data storage space */ + if (size != list->size) + { + d = realloc(list->data, size); + if (!d) return 0; + } + list->data = d; + + /* Data assignment */ + if (data) memcpy(list->data, data, size); + + /* Update data size */ + list->size = size; + + return 1; +} + +int sList_get(sList *list, void* data, int size) +{ + /* Input value validity check */ + if (!list) return 0; + if (!data) return 0; + if (size < list->size) return 0; + + /* Data assignment */ + memcpy(data, list->data, list->size); + + return 1; +} diff --git a/source/01_general/sList.h b/source/01_general/sList.h new file mode 100644 index 0000000..6567dbf --- /dev/null +++ b/source/01_general/sList.h @@ -0,0 +1,216 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file sList.h + * \unit sList + * \brief This is a C language singly linked list + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __sList_H +#define __sList_H + +#include + +/* Version infomation */ + +#define SLIST_V_MAJOR 1 +#define SLIST_V_MINOR 0 +#define SLIST_V_PATCH 0 + +/* Type of sList */ +typedef struct sList +{ + /**< Singly linked list, pointing to the next node in the linked list */ + struct sList *next; + + /**< The base address for storing data, allocated in the API */ + void *data; + + /**< The size of node data */ + int size; +} sList; + + +/** + * \brief Creates a new singly linked list. + * + * \return A pointer to the newly created sList structure, or NULL if memory allocation fails. + */ +sList *sList_create(void); + +/** + * \brief Deletes a singly linked list and frees the associated memory. + * + * \param[in] list The sList structure to delete. + */ +void sList_delete(sList *list); + +/** + * \brief Attaches a sublist to a specified position in a singly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the original list. + * \param[in] index The index at which to attach the sublist (0-based index, negative-end index). + * \param[in] attach The sublist to attach. + * \return A pointer to the attach head of the modified list, or NULL if invalid parameters are provided. + */ +sList *sList_attach(sList **listRef, int index, sList *attach); + +/** + * \brief Detaches a sublist from a specified position in a singly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] index The index at which to detach the sublist (0-based index, negative-end index). + * \param[in] count The number of nodes to detach from the specified position (-1 to detach all nodes until the end). + * \param[out] outPrev A pointer to store the pointer to the previous node of the detached sublist. + * \return A pointer to the head of the detached sublist, or NULL if invalid parameters are provided. + */ +sList *sList_detach(sList **listRef, int index, int count, sList **outPrev); + +/** + * \brief Inserts a node with the specified data at a specified position in a singly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] index The index at which to insert the node (0-based index, negative-end index). + * \param[in] data A pointer to the data to be inserted. + * \param[in] size The size of the data to be inserted. + * \return A pointer to the newly inserted node, or NULL if memory allocation fails or invalid parameters are provided. + */ +sList *sList_insert(sList **listRef, int index, void *data, int size); + +/** + * \brief Erases a node at the specified position in a singly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] index The index of the node to erase (0-based index, negative-end index). + * \param[out] outPrev A pointer to store the pointer to the previous node of the erased node. + * \return 1 if the node was successfully erased, 0 if invalid parameters are provided or the index is out of bounds. + */ +int sList_erase(sList **listRef, int index, sList **outPrev); + +/** + * \brief Push a data from the front to the singly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] data A pointer to the data to be inserted. + * \param[in] size The size of the data to be inserted. + * \return 1 success or 0 fail. + */ +int sList_pushFront(sList **listRef, void *data, int size); + +/** + * \brief Push a data from the back to the singly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \param[in] data A pointer to the data to be inserted. + * \param[in] size The size of the data to be inserted. + * \return 1 success or 0 fail. + */ +int sList_pushBack(sList **listRef, void *data, int size); + +/** + * \brief Pop a data from the front in the singly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \return 1 success or 0 fail. + */ +int sList_popFront(sList **listRef); + +/** + * \brief Pop a data from the back in the singly linked list. + * + * \param[in,out] listRef A pointer to the pointer to the head of the list. + * \return 1 success or 0 fail. + */ +int sList_popBack(sList **listRef); + +/** + * \brief Appends a sublist to the end of a singly linked list. + * + * \param[in] list The head of the singly linked list. + * \param[in,out] append A pointer to the pointer to the head of the sublist to append. + * \return 1 success or 0 fail. + */ +int sList_append(sList *list, sList **append); + +/** + * \brief Creates a copy of a sublist within a singly linked list. + * + * \param[in] list The head of the singly linked list. + * \param[in] begin The starting index of the sublist (0-based index, negative-end index). + * \param[in] end The ending index of the sublist (0-based index, negative-end index). + * \return A pointer to the head of the copied sublist, or NULL if invalid parameters are provided or the copying process fails. + */ +sList *sList_copy(sList *list, int begin, int end); + +/** + * \brief Reverses a sublist within a singly linked list. + * + * \param[in] list The head of the singly linked list. + * \param[in] begin The starting index of the sublist (0-based index, negative-end index). + * \param[in] end The ending index of the sublist (0-based index, negative-end index). + * \return 1 if the sublist was successfully reversed, 0 if invalid parameters are provided or the reversal process fails. + */ +int sList_reverse(sList *list, int begin, int end); + +/** + * \brief Calculates the size (number of nodes) of a singly linked list. + * + * \param[in] list The head of the singly linked list. + * \return The size (number of nodes) of the list. + */ +int sList_size(sList *list); + +/** + * \brief Moves to a specific position in a singly linked list. + * + * \param[in] list The head of the singly linked list. + * \param[in] index The index of the desired position (0-based index, negative-end index). + * \return A pointer to the node at the specified position, or NULL if invalid parameters are provided or the index is out of bounds. + */ +sList *sList_to(sList *list, int index); + +/** + * \brief Sets the data and size of a node in a singly linked list. + * + * \param[in] list The node in the singly linked list. + * \param[in] data A pointer to the data to be set. + * \param[in] size The size of the data to be set. + * \return 1 if the data was successfully set, 0 if invalid parameters are provided. + */ +int sList_set(sList *list, void* data, int size); + +/** + * \brief Retrieves the data from a node in a singly linked list. + * + * \param[in] list The node in the singly linked list. + * \param[out] data A pointer to the memory location where the data will be retrieved. + * \param[in] size The size of the data to be retrieved. + * \return 1 if the data was successfully retrieved, 0 if invalid parameters are provided or the retrieval process fails. + */ +int sList_get(sList *list, void* data, int size); + +/** + * \brief Macro for iterating over each node in a singly linked list. + * + * \param[in] list The head of the singly linked list. + * \param[in] node The iterator variable to represent each node in the list. + */ +#define sList_forEach(list, node) for (sList* (node) = (list); (node); (node) = (node)->next) + +/** + * \brief Macro for referencing the data of a node in a singly linked list. + * + * \param[in] node The node in the singly linked list. + * \param[in] type The type of the data stored in the node. + */ +#define sList_ref(node, type) (*(type *)((node)->data)) + +/* Standard front and back indexes */ +#define sList_front (0) +#define sList_back (-1) + +#endif diff --git a/source/01_general/tool.c b/source/01_general/tool.c new file mode 100644 index 0000000..40bd183 --- /dev/null +++ b/source/01_general/tool.c @@ -0,0 +1,138 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file tool.c + * \unit tool + * \brief This is a C language common tool functions and macro definitions + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "tool.h" + +// #define DEBUG +#ifdef DEBUG +#define LOG(format, ...) printf(format, ##__VA_ARGS__) +#else +#define LOG(format, ...) +#endif // DEBUG + +void showBits(void *data, unsigned int width) +{ + int i; + unsigned char *base = data; + while (width-- > 0) + { + for (i = 0; i < 8; i++) + { + printf("%d", getBit(*base, (7-i))?1:0); + } + printf("\r\n"); + base++; + } +} + +void showHex(void *data, int len) +{ + int i; + unsigned char *base = data; + for (i = 0; i < len; i++) + { + printf("%02X ", base[i]); + } + printf("\r\n"); +} + +int ToStringHex(char inArray[], unsigned int maxInSize, char outHexString[], unsigned int maxOutSize) +{ + int len = 0; + unsigned int i = 0, j = 0; + unsigned char c; + + // Check if the output buffer is large enough to hold the hexadecimal string + if (maxOutSize < maxInSize * 3) + { + return -1; // Return -1 if the output buffer is too small + } + + // Convert each character into its hexadecimal representation + for (i = 0; i < maxInSize; i++) + { + // Convert the upper 4 bits of the character into hexadecimal + c = (inArray[i] >> 4) & 0x0F; + outHexString[j++] = (c < 10) ? ('0' + c) : ('A' + c - 10); + + // Convert the lower 4 bits of the character into hexadecimal + c = inArray[i] & 0x0F; + outHexString[j++] = (c < 10) ? ('0' + c) : ('A' + c - 10); + + // Add a space between each pair of hexadecimal characters + outHexString[j++] = ' '; + + // Increment the length counter + len++; + } + + // Null-terminate the output string + outHexString[j - 1] = 0; + + return len; // Return the length of the output hexadecimal string +} + +int GetStringHex(char inHexString[], unsigned int maxInSize, char outArray[], unsigned int maxOutSize) +{ + int len = 0; // Counter for the length of the output array + unsigned int i = 0; // Counter for the input string index + unsigned int width = 0; // Counter for the width of the converted characters + char c = 0; // Temporary storage for the converted character + + // Skip any useless characters at the beginning of the input string + while (i < maxInSize - 1 && inHexString[i] != 0 && inHexString[i] <= ' ') + i++; + + // Convert each hexadecimal digit in the input string to its corresponding ASCII character + while (i < maxInSize && inHexString[i] != 0) + { + if ('0' <= inHexString[i] && inHexString[i] <= '9') + { + c = (c << 4) | (inHexString[i] - '0'); + width++; + } + else if ('a' <= inHexString[i] && inHexString[i] <= 'f') + { + c = (c << 4) | (inHexString[i] - 'a' + 10); + width++; + } + else if ('A' <= inHexString[i] && inHexString[i] <= 'F') + { + c = (c << 4) | (inHexString[i] - 'A' + 10); + width++; + } + else if (' ' == inHexString[i]) + { + outArray[len++] = c; + if (len >= maxOutSize) + break; + width = 0; + c = 0; + // Skip any useless characters between the hexadecimal digits + while (i < maxInSize - 1 && inHexString[i] != 0 && inHexString[i] <= ' ') + i++; + continue; + } + else + { + return -1; // Return -1 if an invalid hexadecimal digit is encountered + } + + i++; + } + + // Append the last converted character if not followed by any useless characters + if (width > 0) + outArray[len++] = c; + + return len; // Return the length of the converted array +} diff --git a/source/01_general/tool.h b/source/01_general/tool.h new file mode 100644 index 0000000..9bc483e --- /dev/null +++ b/source/01_general/tool.h @@ -0,0 +1,108 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file tool.h + * \unit tool + * \brief This is a C language common tool functions and macro definitions + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __tool_H +#define __tool_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +/* Version infomation */ + +#define TOOL_V_MAJOR 1 +#define TOOL_V_MINOR 0 +#define TOOL_V_PATCH 0 + +/** + * \brief Show data by bit + * \param[in] data To show the base address of data. + * \param[in] width Number of bytes to show. + * \return none + */ +void showBits(void *data, unsigned int width); + +/** + * \brief Show data by hex + * \param[in] data To show the base address of data. + * \param[in] len Number of bytes to show. + * \return none + */ +void showHex(void *data, int len); + +/** + * \brief Converts an array of characters into a hexadecimal string representation. + * + * \param[in] inArray The input array of characters. + * \param[in] maxInSize The maximum size of the input array. + * \param[out] outHexString The output hexadecimal string. + * \param[in] maxOutSize The maximum size of the output hexadecimal string. + * \return The length of the output hexadecimal string if successful, -1 if the output buffer is too small. + */ +int ToStringHex(char inArray[], unsigned int maxInSize, char outHexString[], unsigned int maxOutSize); + +/** +* \brief Converts a null-terminated hexadecimal string into an array of characters. +* +* This function takes a null-terminated hexadecimal string as input and converts +* each hexadecimal digit into its corresponding ASCII character. The converted +* characters are stored in the outArray[] parameter. The function returns the +* length of the converted array. +* +* \param[in] inHexString The null-terminated hexadecimal string to be converted. +* \param[in] maxInSize The maximum size of the input character array. +* \param[out] outArray The array to store the converted characters. +* \param[in] maxOutSize The maximum size of the output character array. +* +* \return The length of the converted array if successful, or -1 if an error occurs. +*/ +int GetStringHex(char inHexString[], unsigned int maxInSize, char outArray[], unsigned int maxOutSize); + +#define setBit(data, i) ((data)|=(1<<(i))) +#define clrBit(data, i) ((data)&=(~(1<<(i)))) +#define flpBit(data, i) ((data)^=(1<<(i))) +#define getBit(data, i) ((data)>>(i)&1) +#define chkBit(data, i) ((data)&(1<<(i))) +#define MAX(x, y) ((x)>(y)?(x):(y)) +#define MIN(x, y) ((x)<(y)?(x):(y)) +#define ABS(x) (((x)>0)?(x):(0-(x))) +#define CEIL(num, deno) (((num)+(deno)-1)/(deno)) +#define FLOOR(num, deno) ((num)/(deno)) +#define STR(x) #x +#define CONCAT(a, b) a##b +#define structOffset(s, m) ((size_t)(&((s*)0)->m)) +#define ASSERT(condition) do{if(!(condition)){fprintf(stderr,"Assertion fail: %s, file %s, line %d\r\n",#condition,__FILE__,__LINE__);exit(0);}}while(0) +#define isNegative(n) ((n)<0) +#define isEvenNum(n) ((n)%2==0) +#define isOddNum(n) ((n)%2!=0) +#define isSameSign(n1, n2) ((n1)*(n2)>0) +#define isLeapYear(y) ((y)%4==0&&(y)%100!=0||(y)%400==0) +#define CLAMP(value, min, max) (((value)<(min))?(min):(((value)>(max))?(max):(value))) +#define ArrayLength(a) (sizeof(a)/sizeof((a)[0])) +#define SQUARE(x) ((x)*(x)) +#define ALLOCATE(type, count) (type*)malloc((count)*sizeof(type)) +#define printChar(c) printf(#c ": %c\r\n", c) +#define printInt(i) printf(#i ": %d\r\n", i) +#define printFloat(f) printf(#f ": %f\r\n", f) +#define printString(s) printf(#s ": %s\r\n", s) +#define printPoint(p) printf(#p ": %p\r\n", p) +#define htol(x) ((uint32)(((x)&0xff000000)>>24)|((x)&0x00ff0000)<<8|((x)&0x0000ff00)<<24|((x)&0x000000ff)<<16) + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/source/01_general/valloc.c b/source/01_general/valloc.c new file mode 100644 index 0000000..54d6c03 --- /dev/null +++ b/source/01_general/valloc.c @@ -0,0 +1,524 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file valloc.c + * \unit valloc + * \brief Test how much space is allocated + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include +#include +#include + +/* memory alloc info type define */ +typedef struct +{ + char *file; + int line; + int size; +} minfo; + +/* rbtree node type define */ +typedef struct NODE +{ + struct NODE *parent; + struct NODE *left; + struct NODE *right; + int color; + void* pointer; + minfo info; +} NODE; + +/* rbtree type define */ +typedef struct +{ + NODE* root; + NODE* nil; + int size; +} RBTREE, *rbtree_t; + +/* rbtree node color */ +#define BLACK (0) +#define RED (1) + +static rbtree_t mrbtree = NULL; + +static rbtree_t rbtree_create(void) +{ + rbtree_t rbtree; + rbtree = (rbtree_t)malloc(sizeof(RBTREE)); + if (!rbtree) return NULL; + rbtree->nil = (NODE*)malloc(sizeof(NODE)); + if (!rbtree->nil) + { + free(rbtree); + return NULL; + } + rbtree->nil->color = BLACK; + rbtree->root = rbtree->nil; + rbtree->size = 0; + return rbtree; +} + +static void recursion_delete_node(rbtree_t rbtree, NODE* node) +{ + if (node == rbtree->nil) return; + recursion_delete_node(rbtree, node->left); + recursion_delete_node(rbtree, node->right); + free(node); +} + +static void rbtree_delete(rbtree_t rbtree) +{ + if (!rbtree) return; + recursion_delete_node(rbtree, rbtree->root);; + free(rbtree->nil); + free(rbtree); +} + +static NODE* rbtree_find_node(rbtree_t rbtree, void* pointer) +{ + NODE* node = rbtree->root; + while (node != rbtree->nil) + { + if (pointer < node->pointer) node = node->left; + else if (pointer > node->pointer) node = node->right; + else return node; + } + return rbtree->nil; +} + +static void left_rotate(rbtree_t rbtree, NODE* x) +{ + NODE* y = x->right; + + x->right = y->left; + if (y->left != rbtree->nil) y->left->parent = x; + + y->parent = x->parent; + if (x->parent == rbtree->nil) rbtree->root = y; + else if (x == x->parent->left) x->parent->left = y; + else x->parent->right = y; + + y->left = x; + x->parent = y; +} + +static void right_rotate(rbtree_t rbtree, NODE* y) +{ + NODE* x = y->left; + + y->left = x->right; + if (x->right != rbtree->nil) x->right->parent = y; + + x->parent = y->parent; + if (y->parent == rbtree->nil) rbtree->root = x; + else if (y == y->parent->right) y->parent->right = x; + else y->parent->left = x; + + x->right = y; + y->parent = x; +} + +static void rbtree_insert_fixup(rbtree_t rbtree, NODE* z) +{ + NODE* y = NULL; + + while (z->parent->color == RED) + { + if (z->parent == z->parent->parent->left) + { + y = z->parent->parent->right; + if (y->color == RED) + { + z->parent->color = BLACK; + y->color = BLACK; + z->parent->parent->color = RED; + z = z->parent->parent; + } + else + { + if (z == z->parent->right) + { + z = z->parent; + left_rotate(rbtree, z); + } + z->parent->color = BLACK; + z->parent->parent->color = RED; + right_rotate(rbtree, z->parent->parent); + } + } + else + { + y = z->parent->parent->left; + if (y->color == RED) + { + z->parent->color = BLACK; + y->color = BLACK; + z->parent->parent->color = RED; + z = z->parent->parent; + } + else + { + if (z == z->parent->left) + { + z = z->parent; + right_rotate(rbtree, z); + } + z->parent->color = BLACK; + z->parent->parent->color = RED; + left_rotate(rbtree, z->parent->parent); + } + } + } + rbtree->root->color = BLACK; +} + +static void rbtree_insert_node(rbtree_t rbtree, NODE* z) +{ + NODE* y = rbtree->nil; + NODE* x = rbtree->root; + + while (x != rbtree->nil) + { + y = x; + if (z->pointer < x->pointer) x = x->left; + else if (z->pointer > x->pointer) x = x->right; + else return; + } + + z->parent = y; + if (y == rbtree->nil) rbtree->root = z; + else if (z->pointer < y->pointer) y->left = z; + else y->right = z; + + z->left = rbtree->nil; + z->right = rbtree->nil; + z->color = RED; + + rbtree_insert_fixup(rbtree, z); +} + +static minfo* rbtree_insert(rbtree_t rbtree, void* pointer, minfo info) +{ + NODE* node; + int child = 0; + if (!rbtree) return NULL; + node = (NODE*)malloc(sizeof(NODE)); + if (!node) return NULL; + node->pointer = pointer; + node->info = info; + rbtree_insert_node(rbtree, node); + rbtree->size++; + return &(node->info); +} + +static void rbtree_erase_fixup(rbtree_t rbtree, NODE* x) +{ + NODE* w = NULL; + + while ((x != rbtree->root) && (x->color == BLACK)) + { + if (x == x->parent->left) + { + w = x->parent->right; + + if (w->color == RED) + { + w->color = BLACK; + x->parent->color = RED; + left_rotate(rbtree, x->parent); + w = x->parent->right; + } + + if ((w->left->color == BLACK) && (w->right->color == BLACK)) + { + w->color = RED; + x = x->parent; + } + else + { + if (w->right->color == BLACK) + { + w->left->color = BLACK; + w->color = RED; + right_rotate(rbtree, w); + w = x->parent->right; + } + w->color = x->parent->color; + x->parent->color = BLACK; + w->right->color = BLACK; + left_rotate(rbtree, x->parent); + x = rbtree->root; + } + + } + else + { + w = x->parent->left; + if (w->color == RED) + { + w->color = BLACK; + x->parent->color = RED; + right_rotate(rbtree, x->parent); + w = x->parent->left; + } + + if ((w->left->color == BLACK) && (w->right->color == BLACK)) + { + w->color = RED; + x = x->parent; + } + else + { + if (w->left->color == BLACK) + { + w->right->color = BLACK; + w->color = RED; + left_rotate(rbtree, w); + w = x->parent->left; + } + + w->color = x->parent->color; + x->parent->color = BLACK; + w->left->color = BLACK; + right_rotate(rbtree, x->parent); + x = rbtree->root; + } + } + } + x->color = BLACK; +} + +static NODE* node_min(rbtree_t rbtree, NODE* x) +{ + if (x == rbtree->nil) return x; + while (x->left != rbtree->nil) x = x->left; + return x; +} + +static NODE* node_max(rbtree_t rbtree, NODE* x) +{ + if (x == rbtree->nil) return x; + while (x->right != rbtree->nil) x = x->right; + return x; +} + +static NODE* rbtree_successor(rbtree_t rbtree, NODE* x) +{ + NODE* y = x->parent; + if (x->right != rbtree->nil) return node_min(rbtree, x->right); + while ((y != rbtree->nil) && (x == y->right)) + { + x = y; + y = y->parent; + } + return y; +} + +static NODE* rbtree_erase_node(rbtree_t rbtree, NODE* z) +{ + NODE* y = rbtree->nil; + NODE* x = rbtree->nil; + + if ((z->left == rbtree->nil) || (z->right == rbtree->nil)) y = z; + else y = rbtree_successor(rbtree, z); + + if (y->left != rbtree->nil) x = y->left; + else if (y->right != rbtree->nil) x = y->right; + + x->parent = y->parent; + if (y->parent == rbtree->nil) rbtree->root = x; + else if (y == y->parent->left) y->parent->left = x; + else y->parent->right = x; + + if (y != z) + { + z->pointer = y->pointer; + z->info = y->info; + } + + if (y->color == BLACK) rbtree_erase_fixup(rbtree, x); + + return y; +} + +static int rbtree_erase(rbtree_t rbtree, void* pointer) +{ + NODE* node = NULL; + NODE* cur = NULL; + if (!rbtree) return 0; + node = rbtree_find_node(rbtree, pointer); + if (node == rbtree->nil) return 0; + cur = rbtree_erase_node(rbtree, node); + free(cur); + rbtree->size--; + return 1; +} + +static int rbtree_size(rbtree_t rbtree) +{ + if (!rbtree) return 0; + return rbtree->size; +} + +static int rbtree_find(rbtree_t rbtree, void* pointer) +{ + if (!rbtree) return 0; + return rbtree_find_node(rbtree, pointer)==rbtree->nil?0:1; +} + +static minfo* rbtree_data(rbtree_t rbtree, void* pointer) +{ + if (!rbtree) return NULL; + return &(rbtree_find_node(rbtree, pointer)->info); +} + + +static NODE* node_next(rbtree_t rbtree, NODE* node) +{ + if (node->right != rbtree->nil) + { + node = node->right; + node = node_min(rbtree, node); + } + else + { + if (node == node->parent->left) node = node->parent; + else node = node->parent->parent; + } + return node; +} + +static NODE* node_prev(rbtree_t rbtree, NODE* node) +{ + if (node->left != rbtree->nil) + { + node = node->left; + node = node_max(rbtree, node); + } + else + { + if (node == node->parent->right) node = node->parent; + else node = node->parent->parent; + } + return node; +} + +void* vm_malloc(size_t size, char *file, int line) +{ + void* p; + minfo info; + if (!mrbtree) + { + mrbtree = rbtree_create(); + if (!mrbtree) return NULL; + } + p = malloc(size); + if (!p) return NULL; + info.size = size; + info.file = file; + info.line = line; + if (!rbtree_insert(mrbtree, p, info)) + { + free(p); + return NULL; + } + return p; +} + +void* vm_calloc(size_t num, size_t size, char *file, int line) +{ + void* p; + p = vm_malloc(num * size, file, line); + if (!p) return NULL; + memset(p, 0, num * size); + return p; +} + +void vm_free(void* block) +{ + if (!block) return; + if (rbtree_erase(mrbtree, block)) free(block); + if (rbtree_size(mrbtree) == 0) + { + rbtree_delete(mrbtree); + mrbtree = NULL; + } +} + +void* vm_realloc(void* block, size_t size, char *file, int line) +{ + void* p; + if (!block) + { + return vm_malloc(size, file, line); + } + if (size == 0) + { + vm_free(block); + return NULL; + } + if (!rbtree_find(mrbtree, block)) return NULL; + p = realloc(block, size); + if (!p) return NULL; + if (p == block) + { + minfo *info; + info = rbtree_data(mrbtree, block); + info->file = file; + info->line = line; + info->size = size; + } + else + { + minfo info; + rbtree_erase(mrbtree, block); + info.file = file; + info.line = line; + info.size = size; + rbtree_insert(mrbtree, p, info); + } + return p; +} + +void v_check_unfree(void) +{ + int size, i; + void *p; + NODE* node; + if (!mrbtree) return; + size = rbtree_size(mrbtree); + node = node_min(mrbtree, mrbtree->root); + for (i = 0; i < size; i++) + { + printf("address: %p, size: %d, file: %s, line: %d\r\n", node->pointer, node->info.size, node->info.file, node->info.line); + node = node_next(mrbtree, node); + } +} + +int v_check_count(void) +{ + if (!mrbtree) return 0; + return rbtree_size(mrbtree); +} + +int v_check_used(void) +{ + int size, i; + int used = 0; + NODE* node; + if (!mrbtree) return 0; + size = rbtree_size(mrbtree); + node = node_min(mrbtree, mrbtree->root); + for (i = 0; i < size; i++) + { + used += node->info.size; + node = node_next(mrbtree, node); + } + return used; +} \ No newline at end of file diff --git a/source/01_general/valloc.h b/source/01_general/valloc.h new file mode 100644 index 0000000..7edf796 --- /dev/null +++ b/source/01_general/valloc.h @@ -0,0 +1,74 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file valloc.h + * \unit valloc + * \brief Test how much space is allocated + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __valloc_H +#define __valloc_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/* Version infomation */ + +#define VALLOC_V_MAJOR 1 +#define VALLOC_V_MINOR 0 +#define VALLOC_V_PATCH 0 + +/* These methods are generally not directly called, but use versions that are reinstalled through macro definitions */ + +void* vm_malloc(size_t size, char *file, int line); +void* vm_calloc(size_t num, size_t size, char *file, int line); +void vm_free(void* block); +void* vm_realloc(void* block, size_t size, char *file, int line); + +/* Statistics on memory usage */ + +/** + * \brief List memory blocks that have been allocated but not yet free + * This method will provide a detailed list of memory block addresses and sizes, and indicate which file and line to allocate them in + * \return none + */ +void v_check_unfree(void); + +/** + * \brief Count how many blocks of memory are currently allocated + * \return count of memory + */ +int v_check_count(void); + +/** + * \brief Count how much memory is currently allocated and used in total + * \return total size of memory + */ +int v_check_used(void); + +/* These memory APIs, named the same as those provided by stdlib, + * are intended to replace the method of Valloc in stdlib to track memory usage. + * + * When using, simply include the `valloc.h` header file after `stdlib.h`, + * and the API of `stdlib.h` will be replaced by the API of `valloc.h` + * Without the need for large-scale function replacement. + */ + +#define malloc(s) vm_malloc(s, __FILE__, __LINE__) +#define calloc(n, s) vm_calloc(n, s, __FILE__, __LINE__) +#define free(b) vm_free(b) +#define realloc(b, s) vm_realloc(b, s, __FILE__, __LINE__) + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/source/01_general/vlog.c b/source/01_general/vlog.c new file mode 100644 index 0000000..d8e0a39 --- /dev/null +++ b/source/01_general/vlog.c @@ -0,0 +1,112 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file vlog.c + * \unit vlog + * \brief This is a simple log module for C language + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "vlog.h" +#include + +static char channel_table = VLOG_CHANNEL_0; /* Channel configuration, default only opens channel 0 */ +static char vlog_buffer[VLOG_BUFFER_SIZE]; /* vlog output buffer */ +static vlog_func_t vlog_func = 0; /* Additional vlog callback function */ +static FILE *vlog_file = 0; /* Offline recorded files */ + +/** + * \brief output log information + * \param[in] channel: VLOG_CHANNEL_XXX, from 0 to 7 + * \param[in] format: format string + * \param[in] ...: format parameters + * \return log string length + */ +int vlog(char channel, const char *format, ...) +{ + int len = 0; + va_list args; + + /* Check channel effectiveness */ + if (channel & channel_table) + { + va_start(args, format); + + len = vsnprintf(vlog_buffer, sizeof(vlog_buffer), format, args); + + /* Output to default console */ + printf(vlog_buffer); + + /* Output to offline file */ + if (vlog_file) fwrite(vlog_buffer, sizeof(char), len, vlog_file); + + /* Output to callback function */ + if (vlog_func) vlog_func(vlog_buffer, len); + + va_end(args); + } + + return len; +} + +/** + * \brief set vlog channel configuration + * \param[in] channel: VLOG_CHANNEL_XXX, from 0 to 7, multiple channels can be selected through `|` + * \return none + */ +void vlog_set_channel(char channel) +{ + channel_table = channel; +} + +/** + * \brief get vlog channel configuration + * \return channel configuration + */ +char vlog_get_channel(void) +{ + return channel_table; +} + +/** + * \brief start vlog offline save, need to be used in pairs with vlog_stop_offline() + * \param[in] *filename: offline save file name + * \return 1 success or 0 fail + */ +int vlog_start_offline(const char *filename) +{ + FILE *file; + + file = fopen(filename, "a+"); + if (!file) return 0; + + vlog_file = file; + + return 1; +} + +/** + * \brief stop vlog offline save, need to be used in pairs with vlog_start_offline() + * \return none + */ +void vlog_stop_offline(void) +{ + if (vlog_file) + { + fclose(vlog_file); + vlog_file = 0; + } +} + +/** + * \brief set additional vlog callback function + * \return none + */ +void vlog_set_func(vlog_func_t func) +{ + vlog_func = func; +} + diff --git a/source/01_general/vlog.h b/source/01_general/vlog.h new file mode 100644 index 0000000..9e3bd79 --- /dev/null +++ b/source/01_general/vlog.h @@ -0,0 +1,45 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file vlog.h + * \unit vlog + * \brief This is a simple log module for C language + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __vlog_H +#define __vlog_H + +#include + +/* vlog buffer size */ +#define VLOG_BUFFER_SIZE (1024) + +/* vlog channel */ +#define VLOG_CHANNEL_0 (0x01) +#define VLOG_CHANNEL_1 (0x02) +#define VLOG_CHANNEL_2 (0x04) +#define VLOG_CHANNEL_3 (0x08) +#define VLOG_CHANNEL_4 (0x10) +#define VLOG_CHANNEL_5 (0x20) +#define VLOG_CHANNEL_6 (0x40) +#define VLOG_CHANNEL_7 (0x80) +#define VLOG_CHANNEL_ALL (0xFF) + +#define VLOG_ENABALE(c) vlog_set_channel(vlog_get_channel() | (1 << (c))) +#define VLOG_DISABALE(c) vlog_set_channel(vlog_get_channel() & ~(1 << (c))) + +/* callback function define */ +typedef void (*vlog_func_t)(char *buf, int len); + +int vlog(char channel, const char *format, ...); +void vlog_set_channel(char channel); +char vlog_get_channel(void); +int vlog_start_offline(const char *filename); +void vlog_stop_offline(void); +void vlog_set_func(vlog_func_t func); + +#endif diff --git a/source/02_vstd/vctype.c b/source/02_vstd/vctype.c new file mode 100644 index 0000000..af50fba --- /dev/null +++ b/source/02_vstd/vctype.c @@ -0,0 +1,124 @@ +#include "vctype.h" + +int v_tolower(int c) +{ + if (c >= 'A' && c <= 'Z') + { + return c + ('a' - 'A'); + } + return c; +} + +int v_toupper(int c) +{ + if (c >= 'a' && c <= 'z') + { + return c - ('a' - 'A'); + } + return c; +} + +int v_isascii(int c) +{ + return (c >= 0 && c <= 127); +} + +int v_toascii(int c) +{ + return (c & 0x7f); +} + +int v_isalpha(int c) +{ + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +int v_isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +int v_isalnum(int c) +{ + return (v_isalpha(c) || v_isdigit(c)); +} + +int v_iscntrl(int c) +{ + return ((c >= 0 && c <= 31) || (c == 127)); +} + +int v_islower(int c) +{ + return (c >= 'a' && c <= 'z'); +} + +int v_isupper(int c) +{ + return (c >= 'A' && c <= 'Z'); +} + +int v_isgraph(int c) +{ + return (c >= '!' && c <= '~'); +} + +int v_isprint(int c) +{ + return (c >= ' ' && c <= '~'); +} + +int v_ispunct(int c) +{ + return ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~')); +} + +int v_isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'); +} + +int v_isxdigit(int c) +{ + return (v_isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); +} + +int v_isctype(int c, int mask) +{ + if (mask & (_V_ISalnum | _V_ISalpha | _V_ISdigit | _V_ISlower | _V_ISprint | _V_ISpunct | _V_ISspace | _V_ISupper)) + { + if (mask & _V_ISalnum) + { + return v_isalnum(c); + } + if (mask & _V_ISalpha) + { + return v_isalpha(c); + } + if (mask & _V_ISdigit) + { + return v_isdigit(c); + } + if (mask & _V_ISlower) + { + return v_islower(c); + } + if (mask & _V_ISprint) + { + return v_isprint(c); + } + if (mask & _V_ISpunct) + { + return v_ispunct(c); + } + if (mask & _V_ISspace) + { + return v_isspace(c); + } + if (mask & _V_ISupper) + { + return v_isupper(c); + } + } + return 0; +} \ No newline at end of file diff --git a/source/02_vstd/vctype.h b/source/02_vstd/vctype.h new file mode 100644 index 0000000..1e82b91 --- /dev/null +++ b/source/02_vstd/vctype.h @@ -0,0 +1,43 @@ +#ifndef __vctype_H +#define __vctype_H + +#if 0 /* BIG_ENDIAN */ +#define _V_ISbit(bit) (1 << (bit)) +#else /* LITTLE_ENDIAN */ +#define _V_ISbit(bit) ((bit) < 8 ? ((1 << (bit)) << 8) : ((1 << (bit)) >> 8)) +#endif + +enum +{ + _V_ISupper = _V_ISbit (0), /* UPPERCASE. */ + _V_ISlower = _V_ISbit (1), /* lowercase. */ + _V_ISalpha = _V_ISbit (2), /* Alphabetic. */ + _V_ISdigit = _V_ISbit (3), /* Numeric. */ + _V_ISxdigit = _V_ISbit (4), /* Hexadecimal numeric. */ + _V_ISspace = _V_ISbit (5), /* Whitespace. */ + _V_ISprint = _V_ISbit (6), /* Printing. */ + _V_ISgraph = _V_ISbit (7), /* Graphical. */ + _V_ISblank = _V_ISbit (8), /* Blank (usually SPC and TAB). */ + _V_IScntrl = _V_ISbit (9), /* Control character. */ + _V_ISpunct = _V_ISbit (10), /* Punctuation. */ + _V_ISalnum = _V_ISbit (11) /* Alphanumeric. */ +}; + +extern int v_tolower(int c); +extern int v_toupper(int c); +extern int v_isascii(int c); +extern int v_toascii(int c); +extern int v_isalpha(int c); +extern int v_isdigit(int c); +extern int v_isalnum(int c); +extern int v_iscntrl(int c); +extern int v_islower(int c); +extern int v_isupper(int c); +extern int v_isgraph(int c); +extern int v_isprint(int c); +extern int v_ispunct(int c); +extern int v_isspace(int c); +extern int v_isxdigit(int c); +extern int v_isctype(int c, int mask); + +#endif diff --git a/source/02_vstd/vmath.c b/source/02_vstd/vmath.c new file mode 100644 index 0000000..be23061 --- /dev/null +++ b/source/02_vstd/vmath.c @@ -0,0 +1,215 @@ +#include "vmath.h" + +static double _ln[100] = { 0.0,0.009950330853168,0.019802627296180,0.029558802241544,0.039220713153281,0.048790164169432,0.058268908123976,0.067658648473815,0.076961041136128,0.086177696241052,0.095310179804325,0.104360015324243,0.113328685307003,0.122217632724249,0.131028262406404,0.139761942375159,0.148420005118273,0.157003748809665,0.165514438477574,0.173953307123438,0.182321556793955,0.190620359608650,0.198850858745165,0.207014169384326,0.215111379616946,0.223143551314210,0.231111720963387,0.239016900470500,0.246860077931526,0.254642218373581,0.262364264467491,0.270027137213060,0.277631736598280,0.285178942233663,0.292669613962820,0.300104592450338,0.307484699747961,0.314810739840034,0.322083499169114,0.329303747142601,0.336472236621213,0.343589704390077,0.350656871613170,0.357674444271816,0.364643113587910,0.371563556432483,0.378436435720245,0.385262400790645,0.392042087776024,0.398776119957368,0.405465108108165,0.412109650826833,0.418710334858185,0.425267735404344,0.431782416425538,0.438254930931156,0.444685821261446,0.451075619360217,0.457424847038876,0.463734016232140,0.470003629245736,0.476234178996372,0.482426149244293,0.488580014818671,0.494696241836107,0.500775287912490,0.506817602368452,0.512823626428664,0.518793793415168,0.524728528934982,0.530628251062171,0.536493370514569,0.542324290825362,0.548121408509688,0.553885113226438,0.559615787935423,0.565313809050061,0.570979546585738,0.576613364303994,0.582215619852664,0.587786664902119,0.593326845277735,0.598836501088704,0.604315966853330,0.609765571620895,0.615185639090234,0.620576487725110,0.625938430866496,0.631271776841858,0.636576829071551,0.641853886172395,0.647103242058539,0.652325186039691,0.657520002916795,0.662687973075237,0.667829372575656,0.672944473242426,0.678033542749898,0.683096844706444,0.688134638736401 }; + +static double _exp1[71] = { 1.0,2.202646579480672e+004,4.851651954097903e+008,1.068647458152446e+013,2.353852668370200e+017,5.184705528587072e+021,1.142007389815684e+026,2.515438670919167e+030,5.540622384393510e+034,1.220403294317841e+039,2.688117141816136e+043,5.920972027664670e+047,1.304180878393632e+052,2.872649550817832e+056,6.327431707155585e+060,1.393709580666380e+065,3.069849640644242e+069,6.761793810485009e+073,1.489384200781838e+078,3.280587015384671e+082,7.225973768125749e+086,1.591626640377924e+091,3.505790975238748e+095,7.722018499983836e+099,1.700887763567586e+104,3.746454614502673e+108,8.252115441813891e+112,1.817649385139100e+117,4.003639200871785e+121,8.818602191274965e+125,1.942426395241256e+130,4.278478855371123e+134,9.423976816163585e+138,2.075769029922787e+143,4.572185553551339e+147,1.007090887028080e+152,2.218265297538556e+156,4.886054470003974e+160,1.076225116551050e+165,2.370543571722357e+169,5.221469689764144e+173,1.150105235202100e+178,2.533275362360718e+182,5.579910311786494e+186,1.229057036206545e+191,2.707178276786998e+195,5.962956971409261e+199,1.313428677666503e+204,2.893019184253945e+208,6.372298810568915e+212,1.403592217852838e+217,3.091617597639242e+221,6.809740926502327e+225,1.499945255890989e+230,3.303849287296548e+234,7.277212331783397e+238,1.602912685075726e+243,3.530650142988227e+247,7.776774460795963e+251,1.712948566546487e+256,3.773020300929940e+260,8.310630260154467e+264,1.830538131585780e+269,4.032028554146358e+273,8.881133903158874e+277,1.956199921370272e+282,4.308817065586588e+286,9.490801171122244e+290,2.090488073610356e+295,4.604606404782990e+299 }; + +static double _exp2[70] = { 1.0,4.539992976248485e-005,2.061153622438558e-009,9.357622968840175e-014,4.248354255291589e-018,1.928749847963918e-022,8.756510762696520e-027,3.975449735908647e-031,1.804851387845415e-035,8.194012623990515e-040,3.720075976020836e-044,1.688911880224532e-048,7.667648073722000e-053,3.481106839904311e-057,1.580420060273613e-061,7.175095973164411e-066,3.257488532207521e-070,1.478897505643213e-074,6.714184288211594e-079,3.048234950971857e-083,1.383896526736738e-087,6.282880511239462e-092,2.852423339163565e-096,1.294998192508984e-100,5.879282698245269e-105,2.669190215541276e-109,1.211810483082857e-113,5.501611081740457e-118,2.497727566915251e-122,1.133966561037746e-126,5.148200222412014e-131,2.337279285007143e-135,1.061123153746351e-139,4.817491664943076e-144,2.187137832197718e-148,9.929590396264980e-153,4.508027065606742e-157,2.046641121459268e-161,9.291736316326398e-166,4.218441761327482e-170,1.915169596714006e-174,8.694856517406230e-179,3.947458751851265e-183,1.792143500743535e-187,8.136318905805023e-192,3.693883068487256e-196,1.677020318601535e-200,7.613660467476964e-205,3.456596504588617e-209,1.569292385255739e-213,7.124576406741286e-218,3.234552684535111e-222,1.468484646909508e-226,6.666909982697905e-231,3.026772449472940e-235,1.374152566130957e-239,6.238642998528377e-244,2.832339539464062e-248,1.285880161551771e-252,5.837886901742308e-257,2.650396553004311e-261,1.203278173491277e-265,5.462874456123503e-270,2.480141166092796e-274,1.125982347416602e-278,5.111951948651156e-283,2.320822594179601e-287,1.053651827669418e-291,4.783571897030535e-296,2.171738281389827e-300 }; + +static double _exp3[101] = { 1.0,1.105170918075648,1.221402758160170,1.349858807576003,1.491824697641270,1.648721270700128,1.822118800390509,2.013752707470477,2.225540928492467,2.459603111156949,2.718281828459045,3.004166023946433,3.320116922736547,3.669296667619244,4.055199966844675,4.481689070338065,4.953032424395117,5.473947391727202,6.049647464412949,6.685894442279273,7.389056098930653,8.166169912567655,9.025013499434127,9.974182454814727,11.023176380641610,12.182493960703484,13.463738035001704,14.879731724872849,16.444646771097069,18.174145369443085,20.085536923187693,22.197951281441664,24.532530197109384,27.112638920657929,29.964100047397064,33.115451958692375,36.598234443678059,40.447304360067470,44.701184493300914,49.402449105530280,54.598150033144336,60.340287597362057,66.686331040925211,73.699793699595844,81.450868664968141,90.017131300521811,99.484315641933776,109.94717245212343,121.51041751873476,134.28977968493530,148.41315910257634,164.02190729990139,181.27224187515074,200.33680997479112,221.40641620418637,244.69193226421953,270.42640742615157,298.86740096705898,330.29955990964714,365.03746786532696,403.42879349273295,445.85777008251438,492.74904109325325,544.57191012592557,601.84503787207802,665.14163304435715,735.09518924196743,812.40582516753682,897.84729165041040,992.27471560501738,1096.6331584284490,1211.9670744925654,1339.4307643944051,1480.2999275845305,1635.9844299959097,1808.0424144560438,1998.1958951040961,2208.3479918871835,2440.6019776244702,2697.2823282684762,2980.9579870416910,3294.4680752837994,3640.9503073323067,4023.8723938222556,4447.0667476997942,4914.7688402990643,5431.6595913629008,6002.9122172609323,6634.2440062777841,7331.9735391558779,8103.0839275752542,8955.2927034823661,9897.1290587437506,10938.019208164997,12088.380730216773,13359.726829661635,14764.781565577005,16317.607198015130,18033.744927828171,19930.370438229907,22026.465794806718 }; + +static double _exp4[101] = { 1.0,1.001000500166708,1.002002001334000,1.003004504503377,1.004008010677342,1.005012520859401,1.006018036054065,1.007024557266849,1.008032085504274,1.009040621773868,1.010050167084168,1.011060722444720,1.012072288866078,1.013084867359809,1.014098458938492,1.015113064615719,1.016128685406095,1.017145322325241,1.018162976389794,1.019181648617408,1.020201340026756,1.021222051637529,1.022243784470438,1.023266539547218,1.024290317890622,1.025315120524429,1.026340948473442,1.027367802763489,1.028395684421425,1.029424594475131,1.030454533953517,1.031485503886523,1.032517505305119,1.033550539241306,1.034584606728118,1.035619708799623,1.036655846490924,1.037693020838157,1.038731232878498,1.039770483650158,1.040810774192388,1.041852105545480,1.042894478750763,1.043937894850613,1.044982354888444,1.046027859908717,1.047074410956937,1.048122009079656,1.049170655324471,1.050220350740028,1.051271096376024,1.052322893283204,1.053375742513365,1.054429645119356,1.055484602155080,1.056540614675494,1.057597683736611,1.058655810395500,1.059714995710288,1.060775240740159,1.061836546545360,1.062898914187195,1.063962344728034,1.065026839231306,1.066092398761505,1.067159024384193,1.068226717165993,1.069295478174600,1.070365308478774,1.071436209148346,1.072508181254217,1.073581225868358,1.074655344063814,1.075730536914703,1.076806805496220,1.077884150884632,1.078962574157284,1.080042076392600,1.081122658670083,1.082204322070315,1.083287067674959,1.084370896566760,1.085455809829549,1.086541808548238,1.087628893808826,1.088717066698399,1.089806328305129,1.090896679718278,1.091988122028198,1.093080656326330,1.094174283705210,1.095269005258466,1.096364822080817,1.097461735268082,1.098559745917174,1.099658855126103,1.100759063993979,1.101860373621011,1.102962785108508,1.104066299558882,1.105170918075648 }; + +double v_fmod(double x, double y) +{ + return x - ((int)(x / y)) * y; +} + +static int factorial(int n) +{ + int res = 1, i; + for (i = 1; i <= n; i++) + { + res *= i; + } + return res; +} + +static double sector(double x) +{ + int i; + double sum = 0, t; + for (i = 1; i <= 10; i += 2) + { + t = v_pow(x, i) / factorial(i); + if (i % 4 == 1) sum += t; + else if (i % 4 == 3) sum -= t; + } + return sum; +} + +double v_sin(double x) +{ + x = v_fmod(x, 2 * PI); + if (x < 0) return -v_sin(-x); // sin(x) = -sin(-x) + else if (x == 0) return 0; + else if (x < PI * 0.5) return sector(x); + else if (x == PI * 0.5) return 1; + else if (x <= PI) return v_sin(PI - x); // sin(x) = sin(pi - x) + else if (x <= PI * 2) return -v_sin(2 * PI - x); // f(x) = -sin(2pi - x) + return 0; +} + +double v_cos(double x) +{ + x = v_fmod(x, 2 * PI); + return v_sin(x + PI * 0.5); // cos(x) = sin(x + 0.5pi) +} + +double v_sec(double x) +{ + return 1.0 / v_cos(x); +} + +double v_csc(double x) +{ + return 1.0 / v_sin(x); +} + +double v_tan(double x) +{ + double s = v_sin(x); + double c = v_cos(x); + if (c == 0) return s > 0 ? INFINITY : -INFINITY; + return s / c; +} + +double v_cot(double x) +{ + return v_tan(0.5 * PI - x); +} + +static inline double i_ln(double x) +{ + long long* b = (long long*)(&x); + int c = ((*b) >> 52) - 1023; + double rst = ((double)c) * 0.69314718055994530941723212145818; + int d; + double e; + + *b = ((*b) & 0x000fffffffffffff) | 0x3ff0000000000000; + d = (int)(x * 100.0); + rst += _ln[d - 100]; + x = (x * 100.0 / (double)d) - 1.0; + e = x * x; + + return (rst + x - e / 2.0 + e * x / 3.0 - e * e / 4.0); +} + +double v_ln(double x) +{ + if (x <= 0.0) return -INFINITY; + return i_ln(x); +} + +#if 0 +#define N 100 +double m_ln(double x) { + double res = 0.0; + double h = (x - 1) / N; // N为分段数,可以根据需要调整 + for (int i = 1; i <= N; i++) { + double ti = 1 + i * h; + res += h * (1 / ti); + } + return res; +} +#endif + +double v_lg(double x) +{ + if (x <= 0.0) return -INFINITY; + return i_ln(x) / 2.3025850929940456840179914546844; +} + +double v_log2(double x) +{ + if (x <= 0.0) return -INFINITY; + return i_ln(x) / 0.69314718055994530941723212145818; +} + +double v_loga(double x, double y) +{ + if ((x == 0.0) || (y <= 0.0)) return -INFINITY; + return i_ln(y) / i_ln(x); +} + +double v_exp(double x) +{ + if ((x > 700.0) || (x < -700)) return -INFINITY; + double f = x; + int b = (int)(f * 1000.0); + f -= (double)(b) / 1000.0; + int c = b / 10000 - (x < 0); b -= c * 10000; + double rst = (x < 0) ? _exp2[0 - c] : _exp1[c]; + + int d = b / 100; int e = b % 100; + rst = rst * _exp3[d] * _exp4[e]; + + double g = f * f; + return (1 + f + g / 2.0 + g * f / 6.0 + g * g / 24.0) * rst; +} + +double v_pow(double x, double y) +{ + if (x <= 0.0) return -INFINITY; + return v_exp(y * i_ln(x)); +} + +double v_sigmoid(double x) +{ + return (1.0 / (1.0 + v_exp(0.0 - x))); +} + +double v_dsigmoid(double x) +{ + double s = (1.0 / (1.0 + v_exp(0.0 - x))); + return s * (1 - s); +} + +double v_sinh(double x) +{ + double a = v_exp(x); + return (a - 1.0 / a) / 2.0; +} + +double v_cosh(double x) +{ + double a = v_exp(x); + return (a + 1.0 / a) / 2.0; +} + +double v_tanh(double x) +{ + double a = v_exp(x); double b = 1.0 / a; + return (a - b) / (a + b); +} + +double v_coth(double x) +{ + double a = v_exp(x); double b = 1.0 / a; + return (a + b) / (a - b); +} + +double v_sech(double x) +{ + double a = v_exp(x); + return 2.0 / (a + 1.0 / a); +} + +double v_csch(double x) +{ + double a = v_exp(x); + return 2.0 / (a - 1.0 / a); +} + +double v_sqrt(double x) +{ + if (x < 0.0) return -INFINITY; + return v_pow(x, 0.5); +} + +double v_gaussian(double x, double mean, double variance) +{ + double b = (x - mean); b = 0 - b * b / (variance + variance); + return v_exp(b) / (v_sqrt(variance) * 2.506628274631000502415765284811); +} diff --git a/source/02_vstd/vmath.h b/source/02_vstd/vmath.h new file mode 100644 index 0000000..9aaaacf --- /dev/null +++ b/source/02_vstd/vmath.h @@ -0,0 +1,37 @@ +#ifndef __vmath_H +#define __vmath_H + +#ifndef PI +#define PI 3.141592653589793238462643383279502884197169399375105820974944592308 +#endif +#ifndef E +#define E 2.718281828459045235360287471352662497757247093699959574966967627724 +#endif + +#define INFINITY ((float)(1e+300 * 1e+300)) + +double v_fmod(double x, double y); +double v_sin(double x); +double v_cos(double x); +double v_sec(double x); +double v_csc(double x); +double v_tan(double x); +double v_cot(double x); +double v_ln(double x); +double v_lg(double x); +double v_log2(double x); +double v_loga(double x,double y); +double v_exp(double x); +double v_pow(double x,double y); +double v_sigmoid(double x); +double v_dsigmoid(double x); +double v_sinh(double x); +double v_cosh(double x); +double v_tanh(double x); +double v_coth(double x); +double v_sech(double x); +double v_csch(double x); +double v_sqrt(double x); +double v_gaussian(double x, double mean, double variance); + +#endif \ No newline at end of file diff --git a/source/02_vstd/vmem.c b/source/02_vstd/vmem.c new file mode 100644 index 0000000..bd50157 --- /dev/null +++ b/source/02_vstd/vmem.c @@ -0,0 +1,146 @@ +#include "vmem.h" +#include "vstring.h" + +/**< Memory pool */ +static uint8_t mem_pool_base[VMEM_POOL_SIZE] = {0}; +/**< Memory management table */ +static uint16_t mem_map_base[VMEM_MTABLE_SIZE] = {0}; +/**< Memory table size */ +static const uint32_t mem_table_size = VMEM_MTABLE_SIZE; +/**< Memory chunking size */ +static const uint32_t mem_block_size = VMEM_BLOCK_SIZE; +/**< Memory pool size */ +static const uint32_t mem_pool_size = VMEM_POOL_SIZE; + +uint8_t vmem_used(void) +{ + uint32_t used = 0; + uint32_t i; + for (i = 0; i < mem_table_size; i++) + { + if(mem_map_base[i])used++; + } + return (used*100)/(mem_table_size); +} + +/** + * \brief memory allocation function for internal calls + * \param[in] size: memory size to allocate (in bytes) + * \return memory offset address or 0xFFFFFFFF failed + */ +static uint32_t memory_alloc(uint32_t size) +{ + signed long offset=0; + uint32_t nmemb; /* Number of memory blocks required */ + uint32_t cmemb = 0; /* Number of contiguous empty memory blocks */ + uint32_t i; + + /* No allocation required */ + if (size == 0) return 0xFFFFFFFF; + + /* Obtain the number of contiguous memory blocks that need to be allocated */ + nmemb = size / mem_block_size; + + /* Round up to obtain enough memory block space */ + if (size % mem_block_size) nmemb++; + + /* Search the entire memory control area */ + for (offset = mem_table_size - 1; offset >= 0; offset--) + { + /* Continuously increasing the number of empty memory blocks */ + if (!mem_map_base[offset]) cmemb++; + /* Zeroing Continuous Memory Blocks */ + else cmemb = 0; + + /* Found consecutive nmemb empty memory blocks */ + if (cmemb == nmemb) + { + /* Mark memory block is not empty */ + for (i = 0; i < nmemb; i++) + { + mem_map_base[offset + i] = nmemb; + } + + /* Return offset address */ + return (offset * mem_block_size); + } + } + + return 0xFFFFFFFF; +} + +/** + * \brief memory free function for internal calls + * \param[in] offset: memory address offset + * \return 0 success or others fail + */ +static uint8_t memory_free(uint32_t offset) +{ + int index, nmemb, i; + + /* Offset within memory pool */ + if (offset < mem_pool_size) + { + index = offset / mem_block_size; /* Offset memory block number */ + nmemb = mem_map_base[index]; /* Number of memory blocks */ + + /* Memory Block Zeroing */ + for (i = 0; i < nmemb; i++) + { + mem_map_base[index + i] = 0; + } + + return 0; + } + /* Offset not within memory pool */ + else return 2; +} + +void v_free(void *ptr) +{ + uint32_t offset; + + if (ptr == NULL) return; + + offset = (uint32_t)ptr - (uint32_t)mem_pool_base; + + memory_free(offset); +} + +void *v_malloc(size_t size) +{ + uint32_t offset; + + offset = memory_alloc(size); + + if (offset == 0xFFFFFFFF) return NULL; + + return (void *)((uint32_t)mem_pool_base + offset); +} + +void *v_realloc(void *ptr, size_t size) +{ + uint32_t offset; + offset = memory_alloc(size); + + if (offset == 0xFFFFFFFF) return NULL; + + /* Copy old memory content to new memory */ + v_memcpy((void *)((uint32_t)mem_pool_base + offset), ptr, size); + + /* Release old memory */ + v_free(ptr); + + return (void *)((uint32_t)mem_pool_base + offset); +} + +void *v_calloc(size_t size) +{ + void *ptr; + + ptr = v_malloc(size); + + if (ptr) v_memset(ptr, 0, size); + + return ptr; +} diff --git a/source/02_vstd/vmem.h b/source/02_vstd/vmem.h new file mode 100644 index 0000000..aede4c7 --- /dev/null +++ b/source/02_vstd/vmem.h @@ -0,0 +1,20 @@ +#ifndef __vmem_H +#define __vmem_H + +#include "vstdint.h" +#include "vstdlib.h" + +/**< Memory block size is 32 bytes */ +#define VMEM_BLOCK_SIZE (32) +/**< Size of memory pool */ +#define VMEM_POOL_SIZE (40 * 1024) /* 40K */ +/**< Size of memory management table */ +#define VMEM_MTABLE_SIZE (VMEM_POOL_SIZE / VMEM_BLOCK_SIZE) + +/** + * \brief check dynamic memory usage + * \return usage rate (0 - 100) as (0% - 100%) + */ +uint8_t vmem_used(void); + +#endif diff --git a/source/02_vstd/vstddef.h b/source/02_vstd/vstddef.h new file mode 100644 index 0000000..0a4ce44 --- /dev/null +++ b/source/02_vstd/vstddef.h @@ -0,0 +1,33 @@ +#ifndef __vstddef_H +#define __vstddef_H + +/* define size_t */ +#ifndef size_t +typedef unsigned long size_t; +#endif + +/* define ptrdiff_t */ +#ifndef ptrdiff_t +typedef long ptrdiff_t; +#endif + +/* define wchar_t */ +#ifndef wchar_t +typedef int wchar_t; +#endif + +/* define NULL */ +#ifndef NULL +#define NULL ((void*)0) +#endif + +/* define offsetof */ +#ifndef offsetof +#ifdef _WIN64 +#define offsetof(s,m) (size_t)( (ptrdiff_t)&(((s *)0)->m) ) +#else +#define offsetof(s,m) (size_t)&(((s *)0)->m) +#endif +#endif + +#endif diff --git a/source/02_vstd/vstdint.h b/source/02_vstd/vstdint.h new file mode 100644 index 0000000..b4ed585 --- /dev/null +++ b/source/02_vstd/vstdint.h @@ -0,0 +1,119 @@ +#ifndef __vsdint_H +#define __vsdint_H + +#include "vstddef.h" + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +typedef signed char int_least8_t; +typedef unsigned char uint_least8_t; +typedef short int_least16_t; +typedef unsigned short uint_least16_t; +typedef int int_least32_t; +typedef unsigned uint_least32_t; +typedef long long int_least64_t; +typedef unsigned long long uint_least64_t; + +typedef signed char int_fast8_t; +typedef unsigned char uint_fast8_t; +typedef short int_fast16_t; +typedef unsigned short uint_fast16_t; +typedef int int_fast32_t; +typedef unsigned int uint_fast32_t; +typedef long long int_fast64_t; +typedef unsigned long long uint_fast64_t; + +#define INT8_MIN (-128) +#define INT16_MIN (-32768) +#define INT32_MIN (-2147483647 - 1) +#define INT64_MIN (-9223372036854775807LL - 1) + +#define INT8_MAX 127 +#define INT16_MAX 32767 +#define INT32_MAX 2147483647 +#define INT64_MAX 9223372036854775807LL + +#define UINT8_MAX 0xffU /* 255U */ +#define UINT16_MAX 0xffffU /* 65535U */ +#define UINT32_MAX 0xffffffffUL /* 4294967295U */ +#define UINT64_MAX 0xffffffffffffffffULL /* 18446744073709551615ULL */ + +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST64_MIN INT64_MIN + +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MAX INT64_MAX + +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST64_MIN INT64_MIN + +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MAX INT64_MAX + +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +#ifdef _WIN64 +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif + +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +#define PTRDIFF_MIN INTPTR_MIN +#define PTRDIFF_MAX INTPTR_MAX + +#define SIG_ATOMIC_MIN INTPTR_MIN +#define SIG_ATOMIC_MAX INTPTR_MAX + +#define SIZE_MAX UINTPTR_MAX + +#define WCHAR_MIN 0 +#define WCHAR_MAX 0xffff /* UINT16_MAX */ + +#define WINT_MIN 0 +#define WINT_MAX 0xffff /* UINT16_MAX */ + +#define INT8_C(val) val +#define UINT8_C(val) val +#define INT16_C(val) val +#define UINT16_C(val) val + +#define INT32_C(val) val +#define UINT32_C(val) val##U +#define INT64_C(val) val##LL +#define UINT64_C(val) val##ULL + +#define INTMAX_C(val) INT64_C(val) +#define UINTMAX_C(val) UINT64_C(val) + +#endif diff --git a/source/02_vstd/vstdlib.c b/source/02_vstd/vstdlib.c new file mode 100644 index 0000000..d537522 --- /dev/null +++ b/source/02_vstd/vstdlib.c @@ -0,0 +1,356 @@ +#include "vstdlib.h" +#include "vctype.h" +#include "vstddef.h" + +int v_atoi(const char *str) +{ + int result = v_atoll(str); + return result; +} + +long int v_atol(const char *str) +{ + long int result = v_atoll(str); + return result; +} + +long long int v_atoll(const char *str) +{ + long long int result = 0; + int sign = 1; + int i = 0; + if (str[i] == '-') + { + sign = -1; + i++; + } + else if (str[i] == '+') + { + i++; + } + while (str[i] != '\0' && v_isdigit(str[i])) + { + result = result * 10 + (str[i] - '0'); + i++; + } + return sign * result; +} + +double v_atof(const char *str) +{ + return v_strtod(str, NULL); +} + +float v_strtof(const char *str, char **endptr) +{ + float result = v_strtold(str, endptr); + return result; +} + +double v_strtod(const char *str, char **endptr) +{ + double result = v_strtold(str, endptr); + return result; +} + +long double v_strtold(const char *str, char **endptr) +{ + long double result = 0.0L; + int sign = 1; + int fraction = 0; + long double div = 1.0L; + int i = 0; + if (str[i] == '-') + { + sign = -1; + i++; + } + else if (str[i] == '+') + { + i++; + } + while (str[i] != '\0' && (v_isdigit(str[i]) || str[i] == '.')) + { + if (str[i] == '.') + { + fraction = 1; + } + else if (fraction == 0) + { + result = result * 10.0L + (long double) (str[i] - '0'); + } + else + { + div *= 10.0L; + result = result + (long double) (str[i] - '0') / div; + } + i++; + } + if (endptr != NULL) + { + *endptr = (char *) &str[i]; + } + return sign * result; +} + +static int hex_to_int(char c) +{ + if (v_isdigit(c)) + { + return c - '0'; + } + else if (v_islower(c)) + { + return c - 'a' + 10; + } + else if (v_isupper(c)) + { + return c - 'A' + 10; + } + else + { + return 0; + } +} + +float v_strtof16(const char *str, char **endptr) +{ + float result = 0.0f; + int i = 0; + if (str[i] == '-') + { + i++; + } + else if (str[i] == '+') + { + i++; + } + if (str[i] == '0' && (str[i+1] == 'x' || str[i+1] == 'X')) + { + i += 2; + } + while (v_isxdigit(str[i])) + { + result = result * 16.0f + (float) hex_to_int(str[i]); + i++; + } + if (endptr != NULL) + { + *endptr = (char *) &str[i]; + } + return result; +} + +float v_strtof32(const char *str, char **endptr) +{ + float result = v_strtof(str, endptr); + return result; +} + +double v_strtof64(const char *str, char **endptr) +{ + double result = v_strtod(str, endptr); + return result; +} + +long double v_strtof128(const char *str, char **endptr) +{ + long double result = v_strtold(str, endptr); + return result; +} + +float v_strtof32x(const char *str, char **endptr) +{ + float result = v_strtof128x(str, endptr); + return result; +} + +double v_strtof64x(const char *str, char **endptr) +{ + double result = v_strtof128x(str, endptr); + return result; +} + +long double v_strtof128x(const char *str, char **endptr) +{ + long double result = 0.0L; + int i = 0; + if (str[i] == '-') + { + i++; + } + else if (str[i] == '+') + { + i++; + } + if (str[i] == '0' && (str[i+1] == 'x' || str[i+1] == 'X')) + { + i += 2; + } + while (v_isxdigit(str[i])) + { + result = result * 16.0L + (long double) hex_to_int(str[i]); + i++; + } + if (endptr != NULL) + { + *endptr = (char *) &str[i]; + } + return result; +} + +long int v_strtol(const char *str, char **endptr, int base) +{ + long int result = v_strtoq(str, endptr, base); + return result; +} + +unsigned long int v_strtoul(const char *str, char **endptr, int base) +{ + unsigned long int result = v_strtouq(str, endptr, base); + return result; +} + +long long int v_strtoq(const char *str, char **endptr, int base) +{ + long long int result = 0; + int sign = 1; + int i = 0; + if (str[i] == '-') + { + sign = -1; + i++; + } + else if (str[i] == '+') + { + i++; + } + if (base == 0) + { + if (str[i] == '0' && (str[i + 1] == 'x' || str[i + 1] == 'X')) + { + base = 16; + i += 2; + } + else if (str[i] == '0') + { + base = 8; + i++; + } + else + { + base = 10; + } + } + while (str[i] != '\0') + { + int digit = 0; + if (str[i] >= '0' && str[i] <= '9') + { + digit = str[i] - '0'; + } + else if (str[i] >= 'a' && str[i] <= 'z') + { + digit = str[i] - 'a' + 10; + } + else if (str[i] >= 'A' && str[i] <= 'Z') + { + digit = str[i] - 'A' + 10; + } + else + { + break; + } + if (digit >= base) + { + break; + } + result = result * base + digit; + i++; + } + if (endptr != NULL) + { + *endptr = (char *) &str[i]; + } + return sign * result; +} + +unsigned long long int v_strtouq(const char *str, char **endptr, int base) +{ + unsigned long long int result = 0; + int i = 0; + if (base == 0) + { + if (str[i] == '0' && (str[i + 1] == 'x' || str[i + 1] == 'X')) + { + base = 16; + i += 2; + } + else if (str[i] == '0') + { + base = 8; + i++; + } + else + { + base = 10; + } + } + while (str[i] != '\0') + { + int digit = 0; + if (str[i] >= '0' && str[i] <= '9') + { + digit = str[i] - '0'; + } + else if (str[i] >= 'a' && str[i] <= 'z') + { + digit = str[i] - 'a' + 10; + } + else if (str[i] >= 'A' && str[i] <= 'Z') + { + digit = str[i] - 'A' + 10; + } + else + { + break; + } + if (digit >= base) + { + break; + } + result = result * base + digit; + i++; + } + if (endptr != NULL) + { + *endptr = (char *) &str[i]; + } + return result; +} + +long long int v_strtoll(const char *str, char **endptr, int base) +{ + return v_strtoq(str, endptr, base); +} + +unsigned long long int v_strtoull(const char *str, char **endptr, int base) +{ + return v_strtouq(str, endptr, base); +} + +#define _mul 1103515245 +#define _ins 1 + +static int _seed_value = 0; + +void v_srand(int seed) +{ + _seed_value = seed; +} + +int v_rand(void) +{ + _seed_value = (_mul * _seed_value) + _ins; + return _seed_value; +} \ No newline at end of file diff --git a/source/02_vstd/vstdlib.h b/source/02_vstd/vstdlib.h new file mode 100644 index 0000000..c6e9384 --- /dev/null +++ b/source/02_vstd/vstdlib.h @@ -0,0 +1,33 @@ +#ifndef __vstdlib_H +#define __vstdlib_H + +#include "vstdint.h" + +extern int v_atoi(const char *str); +extern long int v_atol(const char *str); +extern long long int v_atoll(const char *str); +extern double v_atof(const char *str); +extern float v_strtof(const char *str, char **endptr); +extern double v_strtod(const char *str, char **endptr); +extern long double v_strtold(const char *str, char **endptr); +extern float v_strtof16(const char *str, char **endptr); +extern float v_strtof32(const char *str, char **endptr); +extern double v_strtof64(const char *str, char **endptr); +extern long double v_strtof128(const char *str, char **endptr); +extern float v_strtof32x(const char *str, char **endptr); +extern double v_strtof64x(const char *str, char **endptr); +extern long double v_strtof128x(const char *str, char **endptr); +extern long int v_strtol(const char *str, char **endptr, int base); +extern unsigned long int v_strtoul(const char *str, char **endptr, int base); +extern long long int v_strtoq(const char *str, char **endptr, int base); +extern unsigned long long int v_strtouq(const char *str, char **endptr, int base); +extern long long int v_strtoll(const char *str, char **endptr, int base); +extern unsigned long long int v_strtoull(const char *str, char **endptr, int base); +extern void v_srand(int seed); +extern int v_rand(void); +extern void v_free(void *ptr); +extern void *v_malloc(size_t size); +extern void *v_calloc(size_t size); +extern void *v_realloc(void *ptr, size_t size); + +#endif diff --git a/source/02_vstd/vstring.c b/source/02_vstd/vstring.c new file mode 100644 index 0000000..56f4d93 --- /dev/null +++ b/source/02_vstd/vstring.c @@ -0,0 +1,619 @@ +#include "vstring.h" +#include "vctype.h" +#include "vstdlib.h" + +void *v_memcpy(void *dest, const void *src, size_t n) +{ + char *dst = dest; + const char *s = src; + while (n--) + { + *dst++ = *s++; + } + return dest; +} + +void *v_mempcpy(void *dest, const void *src, size_t n) +{ + char *p1 = (char *)dest; + const char *p2 = (const char *)src; + size_t i; + for (i = 0; i < n; i++) + { + p1[i] = p2[i]; + } + return (void *)(p1 + i); +} + +void *v_memmove(void *dest, const void *src, size_t n) +{ + char *dst = dest; + const char *s = src; + if (dst <= s) + { + while (n--) + { + *dst++ = *s++; + } + } + else + { + dst += n; + s += n; + while (n--) + { + *--dst = *--s; + } + } + return dest; +} + +void *v_memccpy(void *dest, const void *src, int c, size_t n) +{ + char *dst = dest; + const char *s = src; + while (n--) + { + *dst++ = *s; + if (*s++ == c) + { + return dst; + } + } + return NULL; +} + +void *v_memset(void *s, int c, size_t n) +{ + char *p = s; + while (n--) + { + *p++ = (char) c; + } + return s; +} + +int v_memcmp(const void *s1, const void *s2, size_t n) +{ + const unsigned char *p1 = s1, *p2 = s2; + for (size_t i = 0; i < n; ++i, ++p1, ++p2) + { + if (*p1 != *p2) + { + return (*p1 < *p2) ? -1 : 1; + } + } + return 0; +} + +void *v_memchr(const void *s, int c, size_t n) +{ + const unsigned char *p = s; + for (size_t i = 0; i < n; ++i, ++p) + { + if (*p == (unsigned char) c) + { + return (void *) p; + } + } + return NULL; +} + +void *v_memrchr(const void *s, int c, size_t n) +{ + const unsigned char *p = (const unsigned char *) s + n - 1; + for (; p >= (const unsigned char *) s; --p) + { + if (*p == (unsigned char) c) + { + return (void *) p; + } + } + return NULL; +} + +void *v_memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) +{ + const char *p1 = (const char *)haystack; + const char *p2 = (const char *)needle; + size_t i; + if (needlelen == 0) + { + return (void *)p1; + } + if (haystacklen < needlelen) + { + return NULL; + } + for (i = 0; i <= haystacklen - needlelen; i++) + { + if (p1[i] == *p2 && v_memcmp(p1 + i, p2, needlelen) == 0) + { + return (void *)(p1 + i); + } + } + return NULL; +} + +void *v_memfrob(void *s, size_t n) +{ + unsigned char *p = (unsigned char *)s; + for (size_t i = 0; i < n; i++) + { + p[i] ^= 42; + } + return s; +} + +char *v_strcpy(char *dest, const char *src) +{ + char *p = dest; + while (*src) + { + *p++ = *src++; + } + *p = '\0'; + return dest; +} + +char *v_strncpy(char *dest, const char *src, size_t n) +{ + char *p = dest; + size_t i = 0; + while (i < n && *src) + { + *p++ = *src++; + ++i; + } + while (i < n) + { + *p++ = '\0'; + ++i; + } + return dest; +} + +size_t v_strlen(const char *s) +{ + const char *p = s; + while (*p) + { + ++p; + } + return p - s; +} + +size_t v_strnlen(const char *s, size_t n) +{ + const char *p = s; + size_t i; + for (i = 0; i < n && *p != '\0'; i++, p++); + return i; +} + +char *v_strcat(char *dest, const char *src) +{ + char *p = dest + v_strlen(dest); + while (*src) + { + *p++ = *src++; + } + *p = '\0'; + return dest; +} + +int v_strcmp(const char *s1, const char *s2) +{ + while (*s1 && *s1 == *s2) + { + ++s1; + ++s2; + } + return *s1 - *s2; +} + +int v_strncmp(const char *s1, const char *s2, size_t n) +{ + size_t i = 0; + while (i < n && *s1 && *s1 == *s2) + { + ++s1; + ++s2; + ++i; + } + if (i == n) + { + return 0; + } + else + { + return *s1 - *s2; + } +} + +static int v_strcoll_helper(char c1, char c2) +{ + if (c1 < c2) + { + return -1; + } + else if (c1 > c2) + { + return 1; + } + else + { + return 0; + } +} + +int v_strcoll(const char *s1, const char *s2) +{ + while (*s1 && *s1 == *s2) + { + ++s1; + ++s2; + } + return v_strcoll_helper(*s1, *s2); +} + +size_t v_strxfrm(char *dest, const char *src, size_t n) +{ + size_t i = 0; + while (*src && i < n) + { + *dest = *src; + ++dest; + ++src; + ++i; + } + if (i == n) + { + *dest = '\0'; + return n; + } + else + { + *dest = '\0'; + return i; + } +} + +char *v_strdup(const char *s) +{ + size_t len = v_strlen(s) + 1; + char *p = v_malloc(len); + if (p == NULL) + { + return NULL; + } + return (char *) v_memcpy(p, s, len); +} + +char *v_strndup(const char *str, size_t n) +{ + size_t len = v_strlen(str); + if (n < len) + { + len = n; + } + char *new_str = (char *) v_malloc(len + 1); + if (new_str == NULL) { + return NULL; + } + v_memcpy(new_str, str, len); + new_str[len] = '\0'; + return new_str; +} + +char *v_strchr(const char *str, int ch) +{ + while (*str != '\0' && *str != ch) + { + str++; + } + if (*str == ch) + { + return (char *) str; + } + else + { + return NULL; + } +} + +char *v_strrchr(const char *str, int ch) +{ + const char *last = NULL; + while (*str != '\0') + { + if (*str == ch) + { + last = str; + } + str++; + } + if (*str == ch) + { + return (char *) str; + } + else + { + return (char *) last; + } +} + +char *v_strchrnul(const char *str, int ch) +{ + while (*str != '\0' && *str != ch) + { + str++; + } + return (char *) str; +} + +size_t v_strcspn(const char *str, const char *charset) +{ + size_t len = 0; + while (*str != '\0') + { + const char *p = charset; + while (*p != '\0') + { + if (*str == *p) + { + return len; + } + p++; + } + str++; + len++; + } + return len; +} + +char *v_strpbrk(const char *str, const char *charset) +{ + while (*str != '\0') + { + const char *p = charset; + while (*p != '\0') + { + if (*str == *p) + { + return (char *) str; + } + p++; + } + str++; + } + return NULL; +} + +char *v_strstr(const char *str, const char *substr) +{ + if (*substr == '\0') + { + return (char *) str; + } + while (*str != '\0') + { + const char *p = str; + const char *q = substr; + while (*p != '\0' && *q != '\0' && *p == *q) + { + p++; + q++; + } + if (*q == '\0') + { + return (char *) str; + } + str++; + } + return NULL; +} + +char *v_strrstr(const char *str, const char *substr) +{ + size_t len = v_strlen(substr); + if (len == 0) { + return (char *) (str + v_strlen(str)); + } + const char *end = str + v_strlen(str) - len; + for (const char *p = end; p >= str; p--) + { + if (v_strncmp(p, substr, len) == 0) + { + return (char *) p; + } + } + return NULL; +} + +char *v_strtok(char *str, const char *delim) +{ + static char *last_str = NULL; + static const char *last_delim = NULL; + char *ret = NULL; + if (str != NULL) + { + last_str = str; + } + else if (last_str == NULL) + { + return NULL; + } + if (delim != NULL) + { + last_delim = delim; + } + else if (last_delim == NULL) + { + return NULL; + } + ret = last_str; + while (*last_str != '\0') + { + const char *p = last_delim; + while (*p != '\0' && *p != *last_str) + { + p++; + } + if (*p != '\0') + { + *last_str = '\0'; + last_str++; + break; + } + last_str++; + } + return ret; +} + +char *v_strtok_r(char *str, const char *delim, char **saveptr) +{ + char *last_str = NULL; + const char *last_delim = NULL; + char *ret = NULL; + if (str != NULL) + { + last_str = str; + } + else if (*saveptr == NULL) + { + return NULL; + } + else + { + last_str = *saveptr; + } + if (delim != NULL) + { + last_delim = delim; + } + else if (*saveptr == NULL) + { + return NULL; + } + else + { + last_delim = *saveptr; + } + ret = last_str; + while (*last_str != '\0') + { + const char *p = last_delim; + while (*p != '\0' && *p != *last_str) + { + p++; + } + if (*p != '\0') + { + *last_str = '\0'; + last_str++; + break; + } + last_str++; + } + *saveptr = last_str; + return ret; +} + +char *v_strcasestr(const char *haystack, const char *needle) +{ + const char *p1 = haystack; + while (*p1 != '\0') + { + const char *p2 = needle; + const char *p3 = p1; + while (*p2 != '\0' && v_tolower(*p2) == v_tolower(*p3)) + { + p2++; + p3++; + } + if (*p2 == '\0') + { + return (char *)p1; + } + p1++; + } + return NULL; +} + +char *v_strsep(char **stringp, const char *delim) +{ + char *p, *start; + start = *stringp; + p = (start != NULL) ? v_strpbrk(start, delim) : NULL; + if (p == NULL) + { + *stringp = NULL; + } + else + { + *p = '\0'; + *stringp = p + 1; + } + return start; +} + +char *v_stpcpy(char *dest, const char *src) +{ + while ((*dest++ = *src++)); + return dest - 1; +} + +char *v_stpncpy(char *dest, const char *src, size_t n) +{ + char *p = dest; + while (n-- && *src) + { + *p++ = *src++; + } + while (n--) + { + *p++ = '\0'; + } + return p; +} + +int v_strverscmp(const char *s1, const char *s2) +{ + const unsigned char *p1 = (const unsigned char *)s1; + const unsigned char *p2 = (const unsigned char *)s2; + int num1 = 0, num2 = 0; + while (*p1 || *p2) + { + while (v_isdigit(*p1)) + { + num1 = num1 * 10 + (*p1 - '0'); + p1++; + } + while (v_isdigit(*p2)) + { + num2 = num2 * 10 + (*p2 - '0'); + p2++; + } + if (num1 != num2) + { + return num1 > num2 ? 1 : -1; + } + if (*p1 != *p2) + { + return *p1 > *p2 ? 1 : -1; + } + if (*p1) + { + p1++; + } + if (*p2) + { + p2++; + } + num1 = num2 = 0; + } + return 0; +} + + diff --git a/source/02_vstd/vstring.h b/source/02_vstd/vstring.h new file mode 100644 index 0000000..7dfc3e6 --- /dev/null +++ b/source/02_vstd/vstring.h @@ -0,0 +1,43 @@ +#ifndef __vstring_H +#define __vstring_H + +#include "vstddef.h" + +extern void *v_memcpy(void *dest, const void *src, size_t n); +extern void *v_mempcpy(void *dest, const void *src, size_t n); +extern void *v_memmove(void *dest, const void *src, size_t n); +extern void *v_memccpy(void *dest, const void *src, int c, size_t n); +extern void *v_memset(void *s, int c, size_t n); +extern int v_memcmp(const void *s1, const void *s2, size_t n); +extern void *v_memchr(const void *s, int c, size_t n); +extern void *v_memrchr(const void *s, int c, size_t n); +extern void *v_memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen); +extern void *v_memfrob(void *s, size_t n); +extern char *v_strcpy(char *dest, const char *src); +extern char *v_strncpy(char *dest, const char *src, size_t n); +extern char *v_strcat(char *dest, const char *src); +extern size_t v_strlen(const char *s); +extern size_t v_strnlen(const char *s, size_t n); +extern char *v_strcat(char *dest, const char *src); +extern int v_strcmp(const char *s1, const char *s2); +extern int v_strncmp(const char *s1, const char *s2, size_t n); +extern int v_strcoll(const char *s1, const char *s2); +extern size_t v_strxfrm(char *dest, const char *src, size_t n); +extern char *v_strdup(const char *s); +extern char *v_strndup(const char *str, size_t n); +extern char *v_strchr(const char *str, int ch); +extern char *v_strrchr(const char *str, int ch); +extern char *v_strchrnul(const char *str, int ch); +extern size_t v_strcspn(const char *str, const char *charset); +extern char *v_strpbrk(const char *str, const char *charset); +extern char *v_strstr(const char *str, const char *substr); +extern char *v_strrstr(const char *str, const char *substr); +extern char *v_strtok(char *str, const char *delim); +extern char *v_strtok_r(char *str, const char *delim, char **saveptr); +extern char *v_strcasestr(const char *haystack, const char *needle); +extern char *v_strsep(char **stringp, const char *delim); +extern char *v_stpcpy(char *dest, const char *src); +extern char *v_stpncpy(char *dest, const char *src, size_t n); +extern int v_strverscmp(const char *s1, const char *s2); + +#endif diff --git a/source/03_container/deque.c b/source/03_container/deque.c new file mode 100644 index 0000000..c6daf7c --- /dev/null +++ b/source/03_container/deque.c @@ -0,0 +1,230 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file deque.c + * \unit deque + * \brief This is a C language deque + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "deque.h" +#include + +/* type of deque */ +typedef struct DEQUE +{ + void* base; /**< base address of data */ + int cst; /**< base const */ + int dsize; /**< size of deque data */ + int capacity; /**< capacity of deque */ + int size; /**< size of deque */ + int head; /**< index of deque head */ + int tail; /**< index of deque tail */ +} DEQUE; + +/* Address of deque array */ +#define at(i) (((unsigned char *)(deque->base))+(i)*(deque->dsize)) + +deque_t deque_create(int dsize, int capacity, void *base) +{ + deque_t deque; + + /* Input value validity check */ + if (dsize <= 0) return NULL; + if (capacity <= 0) return NULL; + + /* Allocate memory for the DEQUE structure */ + deque = (deque_t)malloc(sizeof(DEQUE)); + if (!deque) return NULL; + + /* Initialize structural parameters */ + deque->base = base; + deque->cst = 1; + deque->capacity = capacity; + deque->dsize = dsize; + deque->tail = 0; + deque->head = 0; + deque->size = 0; + + /* Dynamically allocate an array without passing it in */ + if (!deque->base) + { + deque->base = malloc(dsize * capacity); + deque->cst = 0; + } + + /* Check if the array space is valid */ + if (!deque->base) + { + deque_delete(deque); + return NULL; + } + + return deque; +} + +void deque_delete(deque_t deque) +{ + /* Input value validity check */ + if (!deque) return; + + /* If it is not a constant array but a dynamic array, release the allocated space */ + if (!deque->cst && deque->base) free(deque->base); + + /* Free deque structure */ + free(deque); +} + +int deque_push_front(deque_t deque, void* data) +{ + /* Input value validity check */ + if (!deque) return 0; + + /* Check if the deque is full */ + if (deque_full(deque)) return 0; + + /* Update deque status */ + deque->head = (deque->head + deque->capacity - 1) % deque->capacity; + deque->size++; + + /* Assigning data to the deque */ + if (data) memcpy(at(deque->head), data, deque->dsize); + + return 1; +} + +int deque_push_back(deque_t deque, void* data) +{ + /* Input value validity check */ + if (!deque) return 0; + + /* Check if the deque is full */ + if (deque_full(deque)) return 0; + + /* Assigning data to the deque */ + if (data) memcpy(at(deque->tail), data, deque->dsize); + + /* Update deque status */ + deque->tail = (deque->tail + 1) % deque->capacity; + deque->size++; + + return 1; +} + +int deque_pop_front(deque_t deque, void* data) +{ + /* Input value validity check */ + if (!deque) return 0; + + /* Check if the deque is full */ + if (deque_empty(deque)) return 0; + + /* Assigning data from the deque */ + if (data) memcpy(data, at(deque->head), deque->dsize); + + /* Update deque status */ + deque->head = (deque->head + 1) % deque->capacity; + deque->size--; + + return 1; +} + +int deque_pop_back(deque_t deque, void* data) +{ + /* Input value validity check */ + if (!deque) return 0; + + /* Check if the deque is full */ + if (deque_empty(deque)) return 0; + + /* Update deque status */ + deque->tail = (deque->tail + deque->capacity - 1) % deque->capacity; + deque->size--; + + /* Assigning data from the deque */ + if (data) memcpy(data, at(deque->tail), deque->dsize); + + return 1; +} + +void deque_clear(deque_t deque) +{ + /* Input value validity check */ + if (!deque) return; + + /* Reset deque status */ + deque->tail = 0; + deque->head = 0; + deque->size = 0; +} + +int deque_index(deque_t deque, int index) +{ + /* Input value validity check */ + if (!deque) return -1; + if (index < 0 || index >= deque->size) return -1; + + /* Starting from the head, calculate the data index */ + return (deque->head + index) % (deque->capacity); +} + +void* deque_data(deque_t deque, int index) +{ + /* Input value validity check */ + if (!deque) return NULL; + + /* Get indexe for accessing data */ + index = deque_index(deque, index); + if (index < 0) return NULL; + + /* Return array address based on index */ + return (void*)at(index); +} + +int deque_size(deque_t deque) +{ + /* Input value validity check */ + if (!deque) return 0; + + /* Return deque size */ + return deque->size; +} + +int deque_capacity(deque_t deque) +{ + /* Input value validity check */ + if (!deque) return 0; + + /* Return deque capacity */ + return deque->capacity; +} + +int deque_dsize(deque_t deque) +{ + /* Input value validity check */ + if (!deque) return 0; + + /* Return deque data size */ + return deque->dsize; +} + +int deque_empty(deque_t deque) +{ + /* Input value validity check */ + if (!deque) return 1; + + /* Determine if size is 0 */ + return (deque->size == 0) ? 1 : 0; +} + +int deque_full(deque_t deque) +{ + /* Input value validity check */ + if (!deque) return 0; + + /* Determine if size is capacity */ + return (deque->size == deque->capacity) ? 1 : 0; +} diff --git a/source/03_container/deque.h b/source/03_container/deque.h new file mode 100644 index 0000000..c2850bf --- /dev/null +++ b/source/03_container/deque.h @@ -0,0 +1,158 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file deque.h + * \unit deque + * \brief This is a C language deque + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __deque_H +#define __deque_H + +#include + +/* version infomation */ + +#define DEQUE_V_MAJOR 1 +#define DEQUE_V_MINOR 0 +#define DEQUE_V_PATCH 0 + +/* deque type definition, hiding structural members, not for external use */ + +typedef struct DEQUE *deque_t; + +/** + * \brief create a deque. + * \param[in] dsize: size of deque data + * \param[in] capacity: capacity of deque + * \param[in] base: allocated array or pass in `NULL` to dynamically allocate space + * \return deque handler or NULL fail + */ +deque_t deque_create(int dsize, int capacity, void *base); + +/** + * \brief delete a deque. + * \param[in] deque: deque handler + * \return none + */ +void deque_delete(deque_t deque); + +/** + * \brief push data into the deque from the front. + * \param[in] deque: address of deque + * \param[in] data: the address of data + * \return 1 success or 0 fail + */ +int deque_push_front(deque_t deque, void* data); + +/** + * \brief push data into the deque from the back. + * \param[in] deque: address of deque + * \param[in] data: the address of data + * \return 1 success or 0 fail + */ +int deque_push_back(deque_t deque, void* data); + +/** + * \brief pop data from the deque from the front. + * \param[in] deque: address of deque + * \param[out] data: the address of data + * \return 1 success or 0 fail + */ +int deque_pop_front(deque_t deque, void* data); + +/** + * \brief pop data from the deque from the back. + * \param[in] deque: address of deque + * \param[out] data: the address of data + * \return 1 success or 0 fail + */ +int deque_pop_back(deque_t deque, void* data); + +/** + * \brief clear deque. + * \param[in] *deque: address of deque + * \return none + */ +void deque_clear(deque_t deque); + +/** + * \brief get deque index. + * \param[in] *deque: address of deque + * \param[in] index: index of deque + * \return index of deque buffer + */ +int deque_index(deque_t deque, int index); + +/** + * \brief get data address of deque. + * \param[in] deque: deque handler + * \param[in] index: index + * \return address of deque data or NULL fail + */ +void* deque_data(deque_t deque, int index); + +/** + * \brief get size of deque. + * \param[in] deque: deque handler + * \return size of deque + */ +int deque_size(deque_t deque); + +/** + * \brief get capacity of deque. + * \param[in] deque: deque handler + * \return capacity of deque + */ +int deque_capacity(deque_t deque); + +/** + * \brief get data size of deque. + * \param[in] deque: deque handler + * \return data size of deque + */ +int deque_dsize(deque_t deque); + +/** + * \brief check if empty. + * \param[in] deque: deque handler + * \return 1 empty or 0 not empty + */ +int deque_empty(deque_t deque); + +/** + * \brief check if full. + * \param[in] deque: deque handler + * \return 1 full or 0 not full + */ +int deque_full(deque_t deque); + +/** + * \brief A simple method for `deque_create`. + * \param[in] type: data type + * \param[in] capacity: capacity of deque + * \return deque handler or NULL fail + */ +#define deque(type, capacity) deque_create(sizeof(type), (capacity), NULL) + +/** + * \brief A simple method for `deque_delete`. + * \param[in] deque: deque handler + * \return none + */ +#define _deque(deque) do{deque_delete(deque);(deque)=NULL;}while(0) + +/** + * \brief Random access method for deque data. + * \param[in] deque: deque handler + * \param[in] type: data type + * \param[in] i: index starting from deque header + * \return Reference to deque data + */ +#define deque_at(deque, type, i) (*(type *)deque_data((deque), (i))) + +#endif diff --git a/source/03_container/dict.c b/source/03_container/dict.c new file mode 100644 index 0000000..4cb2110 --- /dev/null +++ b/source/03_container/dict.c @@ -0,0 +1,388 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file dict.c + * \unit dict + * \brief This is a general-purpose C language dict module, with common data structure, realized by hash table. + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "dict.h" +#include +#include +#include + +/**< Minimum capacity of hash table */ +#define MIN_CAPACITY 4 + +/**< Address method of dict hash table */ +#define TBALE ((groove_t *)(dict->base)) + +/* Hash table groove definition */ +typedef struct +{ + /**< Hash value */ + unsigned int hash; + + /**< Key address */ + char *key; + + /**< Value address */ + void *value; +} GROOVE, *groove_t; + +/* dict type define */ +typedef struct DICT +{ + /**< Base address for groove data, the data of the hash table exists in this address space */ + void *base; + + /**< Size of value, for example, sizeof(int), sizeof(char), ... */ + unsigned int vsize; + + /**< Size of dict, that is the count of dict members */ + unsigned int size; + + /**< Capacity of dict, actual hash table capacity */ + unsigned int capacity; + + /**< Iterator index, the static index required to record the iteration position during iteration traversal */ + unsigned int it; + + /**< Size of key, when it is a positive number, it is a fixed-length key, and 0 is a variable-length key. */ + unsigned int ksize; + + /**< Function to get indefinite key length */ + int (*klength)(void *key); + +#ifdef DICT_USE_ERROR + /**< Error space used due to misoperations in data reading and writing */ + void *error; +#endif +} DICT; + +static unsigned int hash_bkdr(void *data, unsigned int size) +{ + int i = 0; + unsigned char *str = (unsigned char *)data; + unsigned int seed = 131; // 31 131 1313 13131 131313 etc.. + unsigned int hash = 0; + for (i = 0; i < size; i++) + { + hash = hash * seed + (*str++); + } + return (hash & INT_MAX); +} + +static int default_klength(void *key) +{ + return strlen(key) + 1; +} + +/* Default memory space comparison function */ +static int kcompare(const void *s1, unsigned int n1, const void *s2, unsigned int n2) +{ + const unsigned char *p1 = s1, *p2 = s2; + unsigned int i = 0; + + for (i = 0; i < n1 && i < n2; i++) + { + if (p1[i] != p2[i]) + { + return (p1[i] < p2[i]) ? -1 : 1; + } + } + + if (n1 == n2) return 0; + + return (n1 < n2) ? -1 : 1; +} + +dict_t dict_create(unsigned int vsize) +{ + dict_t dict; + + /* Allocate space */ + dict = (dict_t)malloc(sizeof(DICT)); + if (!dict) return NULL; + +#ifdef DICT_USE_ERROR + dict->error = malloc(vsize); + if (!dict->error) { free(dict); return NULL; } +#endif + + /* Initialize structure members */ + dict->base = NULL; + dict->ksize = 0; + dict->klength = default_klength; + dict->vsize = vsize; + dict->size = 0; + dict->capacity = 0; + dict->it = 0; + + return dict; +} + +void dict_delete(dict_t dict) +{ + if (!dict) return; + dict_clear(dict); +#ifdef DICT_USE_ERROR + if (dict->error) free(dict->error); +#endif + free(dict); +} + +/* But when the hash table stores a certain coefficient, the hash table needs to be readjusted to obtain more space. */ +static int dict_resize(dict_t dict, unsigned int capacity) +{ + unsigned int i = 0, index = 0, hash = 0; + int len = 0; + groove_t *base; + + /* Newly allocate and initialize a space */ + base = malloc(capacity * sizeof(groove_t)); + if (!base) return 0; + memset(base, 0, capacity * sizeof(groove_t)); + + /* Copy old data to new hash table */ + for (i = 0; i < dict->capacity; i++) + { + /* Data is stored in the groove */ + if (TBALE[i]) + { + /* The hash value of the current groove is marked as -1, + * indicating that this groove is still occupied, + * but no data is actually stored. + * The data originally stored will not be recorded in the new hash table, but will be released. */ + if (TBALE[i]->hash == (unsigned int)-1) + { + if (TBALE[i]->key) free(TBALE[i]->key); + if (TBALE[i]->value) free(TBALE[i]->value); + free(TBALE[i]); + } + /* Add the new valid hash table data to the new hash table one by one. */ + else + { + len = (dict->ksize > 0) ? dict->ksize : dict->klength(TBALE[i]->key); + hash = hash_bkdr((void *)TBALE[i]->key, len) % capacity; + index = hash; + while (base[index]) index = (index + 1) % capacity; + base[index] = TBALE[i]; + base[index]->hash = hash; + } + } + } + + /* Update hash table */ + if (dict->base) free(dict->base); + dict->base = base; + dict->capacity = capacity; + + return 1; +} + +int dict_set_klength(dict_t dict, unsigned int ksize, int (*klength)(void *key)) +{ + if (!dict) return 0; + + if (ksize == 0) + { + if (!klength) return 0; /* Functions that do not obtain indefinite length keys are not allowed */ + dict->ksize = 0; + dict->klength = klength; + } + else + { + dict->ksize = ksize; + dict->klength = NULL; + } + + return 1; +} + +void* dict_insert(dict_t dict, void *key, void *value) +{ + groove_t groove = NULL; + unsigned int hash = 0, index; + int len = 0; + + if (!dict) return NULL; + if (!key) return NULL; + + /* The current capacity affects the search rate and needs to be expanded */ + if (dict->size >= ((dict->capacity >> 2) + (dict->capacity >> 1))) /* size exceeds 3/4 of capacity */ + { + /* Allocate new hash table space */ + if (!dict_resize(dict, dict->capacity < MIN_CAPACITY ? MIN_CAPACITY : dict->capacity << 1)) return NULL; + } + + /* find a free groove */ + len = (dict->ksize > 0) ? dict->ksize : dict->klength(key); + hash = hash_bkdr(key, len) % dict->capacity; + index = hash; + while (TBALE[index] && TBALE[index]->hash != -1) + { + index = (index + 1) % dict->capacity; + if (index == hash) return NULL; + } + + /* space allocation */ + groove = TBALE[index]; + if (!groove) groove = (groove_t)malloc(sizeof(GROOVE)); + if (!groove) return NULL; + groove->key = malloc(len); + if (!groove->key) { if (!TBALE[index]) free(groove); return NULL; } + groove->value = malloc(dict->vsize); + if (!groove->value) { free(groove->key), groove->key = NULL; if (!TBALE[index]) free(groove); return NULL; } + + /* assign */ + groove->hash = hash; + memcpy(groove->key, key, len); + if (value) memcpy(groove->value, value, dict->vsize); + + /* insert */ + TBALE[index] = groove; + dict->size++; + + return groove->value; +} + +static unsigned int find_index(dict_t dict, void *key) +{ + unsigned int hash = 0, index; + int len = 0; + + if (dict->capacity == 0) return -1; + + len = (dict->ksize > 0) ? dict->ksize : dict->klength(key); + hash = hash_bkdr(key, len) % dict->capacity; + index = hash; + + /* Traverse the entire hash table and find the groove where both hash value and key match */ + while (TBALE[index]) + { + if (TBALE[index]->hash == hash && !kcompare(key, len, TBALE[index]->key, (dict->ksize > 0) ? dict->ksize : dict->klength(TBALE[index]->key))) break; + index = (index + 1) % dict->capacity; /* linear search */ + } + if (!TBALE[index]) return -1; + + return index; +} + +int dict_erase(dict_t dict, void *key) +{ + groove_t groove; + unsigned int index, next; + + if (!dict) return 0; + if (!key) return 0; + + index = find_index(dict, key); + if (index == -1) return 0; + groove = TBALE[index]; + + /* Release the data stored in the groove and mark it as occupied */ + if (groove->key) { free(groove->key); groove->key = NULL; } + if (groove->value) { free(groove->value); groove->value = NULL; } + groove->hash = -1; + dict->size--; + + /* Size less than 1/4 of capacity */ + if (dict->capacity > MIN_CAPACITY && dict->size <= (dict->capacity >> 2)) + { + dict_resize(dict, dict->capacity >> 1); + } + + return 1; +} + +void dict_clear(dict_t dict) +{ + int i = 0; + if (!dict) return; + for (i = 0; i < dict->capacity; i++) + { + if (TBALE[i]) + { + if (TBALE[i]->key) free(TBALE[i]->key); + if (TBALE[i]->value) free(TBALE[i]->value); + free(TBALE[i]); + TBALE[i] = NULL; + } + } + if (dict->base) free(dict->base); + dict->base = NULL; + dict->size = 0; + dict->capacity = 0; +} + +int dict_size(dict_t dict) +{ + if (!dict) return 0; + return dict->size; +} + +int dict_vsize(dict_t dict) +{ + if (!dict) return 0; + return dict->vsize; +} + +int dict_find(dict_t dict, void *key) +{ + if (!dict) return 0; + if (!key) return 0; + return (find_index(dict, key) == -1) ? 0 : 1; +} + +void* dict_value(dict_t dict, void *key) +{ + unsigned int index; + groove_t groove; + + if (!dict) return NULL; + if (!key) return dict_error(dict); + + index = find_index(dict, key); + if (index == -1) return dict_error(dict); + + return TBALE[index]->value; +} + +#ifdef DICT_USE_ERROR +void* dict_error(dict_t dict) +{ + if (!dict) return NULL; + return dict->error; +} +#endif + +void dict_it_init(dict_t dict) +{ + if (!dict) return; + dict->it = 0; +} + +void* dict_it_get(dict_t dict, char **key) +{ + groove_t groove = NULL; + if (!dict) return NULL; + if (dict->it >= dict->capacity) return dict_error(dict); + while (dict->it < dict->capacity) + { + if (TBALE[dict->it] && TBALE[dict->it]->hash != -1) + { + groove = TBALE[dict->it]; + dict->it++; + break; + } + dict->it++; + } + if (!groove) return dict_error(dict); + if (key) *key = groove->key; + return groove->value; +} diff --git a/source/03_container/dict.h b/source/03_container/dict.h new file mode 100644 index 0000000..4e63845 --- /dev/null +++ b/source/03_container/dict.h @@ -0,0 +1,169 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file dict.h + * \unit dict + * \brief This is a general-purpose C language dict module, with common data structure, realized by hash table. + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __dict_H +#define __dict_H + +#include + +/* Version infomation */ +#define DICT_V_MAJOR 1 +#define DICT_V_MINOR 0 +#define DICT_V_REVISE 0 + +/** Using dict error area + * when there is an error in the read or write operation, + * the operation space will point to the error space to ensure that the program runs normally. */ +#define DICT_USE_ERROR + +/* dict type definition, hiding structural members, not for external use */ + +typedef struct DICT *dict_t; + +/** + * \brief create dict + * \param[in] vsize: size of dict data + * \return dict handler or NULL: fail + */ +dict_t dict_create(unsigned int dsize); + +/** + * \brief delete dict + * \param[in] dict: dict handler + * \return none + */ +void dict_delete(dict_t dict); + +/** + * \brief insert data to dict. + * \param[in] dict: dict handler + * \param[in] key: address of key + * \param[in] value: address of value + * \return address of dict data or NULL fail + */ +void* dict_insert(dict_t dict, void *key, void *value); + +/** + * \brief erase data from dict. + * \param[in] dict: dict handler + * \param[in] key: address of key + * \return 1 success or 0 fail + */ +int dict_erase(dict_t dict, void *key); + +/** + * \brief clear all nodes under the dict + * \param[in] dict: dict handler + * \return none + */ +void dict_clear(dict_t dict); + +/** + * \brief get the size of dict + * \param[in] dict: dict handler + * \return size of dict + */ +int dict_size(dict_t dict); + +/** + * \brief get the size of value + * \param[in] dict: dict handler + * \return size of value + */ +int dict_vsize(dict_t dict); + +/** + * \brief find key from dict + * \param[in] dict: dict handler + * \param[in] key: address of key + * \return 1 success or 0 fail + */ +int dict_find(dict_t dict, void *key); + +/** + * \brief get the address of item data from dict + * \param[in] dict: dict handler + * \param[in] key: address of key + * \return address of dict data or dict_error(): fail + */ +void* dict_value(dict_t dict, void *key); + +/** + * \brief error return value + * \param[in] dict: dict handler + * \return error pointer + */ +#ifdef DICT_USE_ERROR +void* dict_error(dict_t dict); +#else +#define dict_error(dict) NULL +#endif + +/** + * \brief Set key length + * \param[in] dict: dict handler + * \param[in] ksize: 0 - indefinite length, need to specify the `klength` function to get the length of the key, + other - definite length, `klength` will have no effect. + * \param[in] klength: function to get indefinite key length + * \return none + */ +int dict_set_klength(dict_t dict, unsigned int ksize, int (*klength)(void *key)); + +/** + * \brief iterate init + * \param[in] dict: dict handler + * \return none + */ +void dict_it_init(dict_t dict); + +/** + * \brief iterate get + * \param[in] dict: dict handler + * \param[out] key: out key + * \return address of dict iterator data + */ +void* dict_it_get(dict_t dict, char **key); + +/** + * \brief simplified creation method + * \param[in] type: the type of value can be any entity type, for example, `int`, `char`, ... + * \return dict handler or NULL: fail + */ +#define dict(type) dict_create(sizeof(type)) + +/** + * \brief simplified delete method + * \param[in] dict: dict handler + * \return none + */ +#define _dict(dict) do{dict_delete(dict);(dict)=NULL;}while(0) + +/** + * \brief access method + * \param[in] dict: dict handler + * \param[in] type: the type of value, it needs to be the same as when it was created. + * \param[in] key: address of key + * \return none + */ +#define dict_at(dict, type, key) (*(type *)dict_value((dict), (key))) + +/** + * \brief convert literals to addresses + * \param[in] type: type, such as int/char/float etc. + * \param[in] value: literal value + * \return address of literal + */ +#ifndef literal +#define literal(type, value) ((type[1]){value}) +#endif + +#endif diff --git a/source/03_container/heap.c b/source/03_container/heap.c new file mode 100644 index 0000000..06288bf --- /dev/null +++ b/source/03_container/heap.c @@ -0,0 +1,243 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file heap.c + * \unit heap + * \brief This is a general C language heap container module + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "heap.h" +#include + +/* type of heap */ +struct HEAP +{ + void* base; /**< base address of heap */ + int dsize; /**< data size */ + int capacity; /**< capacity of base address */ + int size; /**< size of heap */ + heap_root_t root; /**< root type of heap, big or small */ +} HEAP; + +/* Address of heap array */ +#define at(i) (((unsigned char *)(heap->base))+(i)*(heap->dsize)) + +/* Assign source data to target data */ +#define assign(t, s) memcpy((t), (s), heap->dsize) + +heap_t heap_create(int dsize, int capacity, heap_root_t root) +{ + heap_t heap; + + /* Input value validity check */ + if (dsize <= 0) return NULL; + if (capacity <= 0) return NULL; + if (!root) return NULL; + + /* Allocate memory for the HEAP structure */ + heap = (heap_t)malloc(sizeof(HEAP)); + if (!heap) return NULL; + + /* Dynamically allocate an array */ + heap->base = malloc(dsize * capacity); + if (!heap->base) + { + free(heap); + return NULL; + } + + /* Initialize structural parameters */ + heap->size = 0; + heap->dsize = dsize; + heap->capacity = capacity; + heap->root = root; + + return heap; +} + +void heap_delete(heap_t heap) +{ + /* Input value validity check */ + if (!heap) return; + + /* Release the allocated space */ + if (heap->base) free(heap->base); + + /* Free heap structure */ + free(heap); +} + +static void swap(void *data0, void *data1, int size) +{ + char temp; + int i; + for (i = 0; i < size; i++) + { + temp = ((char *)data0)[i]; + ((char *)data0)[i] = ((char *)data1)[i]; + ((char *)data1)[i] = temp; + } +} + +int heap_push(heap_t heap, void *data) +{ + int i, parent; + + /* Input value validity check */ + if (!heap) return 0; + if (heap->size == heap->capacity) return 0; + + /* Get the current size of the heap */ + i = heap->size; + + /* Calculate the index of the parent node */ + parent = HEAP_PARENT(i); + + /* While i is not 0 and the current data is greater than its parent, + * assign the value of the parent to the current node and update i to the index of the parent node + */ + while (i != 0 && heap->root(data, at(parent))) + { + assign(at(i), at(parent)); + i = parent; + parent = HEAP_PARENT(i); + } + + /* Assign the data to the correct position */ + assign(at(i), data); + + /* Update the size of the heap */ + heap->size++; + + return 1; +} + +int heap_pop(heap_t heap, void *data) +{ + void *last; + int i, child; + + /* Input value validity check */ + if (!heap) return 0; + if (heap->size == 0) return 0; + + /* If data is provided, assign the top element of the heap to data */ + if (data) assign(data, at(0)); + + /* If data is provided, assign the top element of the heap to data */ + last = at(heap->size - 1); + + /* Initialize variables for parent and child nodes */ + i = 0, child = 1; + + while (child < heap->size) + { + /* If the right child is smaller, move to the right child */ + if (child < heap->size - 1 && heap->root(at(child + 1), at(child))) child++; + + /* If the parent node follow the rule with both nodes */ + if (heap->root(at(child), last)) + { + /* Swap the parent and child nodes */ + assign(at(i), at(child)); + + i = child; + + /* Move to the left child */ + child = HEAP_LEFT(child); + } + else break; + } + + /* Assign the last element to the parent node */ + assign(at(i), last); + + /* Decrease the size of the heap */ + heap->size--; + + return 1; +} + +int heap_modify(heap_t heap, int index, void *data) +{ + int parent; + + /* Input value validity check */ + if (!heap) return 0; + if (!data) return 0; + if (index < 0 || index >= heap->size) return 0; + + if (heap->root(data, at(index))) + { + /* Assign the data to the specified index position */ + assign(at(index), data); + + /* Get the parent node of the specified index position */ + parent = HEAP_PARENT(index); + while (index > 0 && heap->root(at(index), at(parent))) + { + /* Swap the parent and child nodes */ + swap(at(index), at(parent), heap->dsize); + + index = parent; + + /* Get the parent node of the current index position */ + parent = HEAP_PARENT(index); + } + } + else if (heap->root(at(index), data)) + { + int i = index, child = HEAP_LEFT(index); /* nitialize variables for parent and child nodes */ + + /* Assign the data to the specified index position */ + assign(at(index), data); + while (child < heap->size) + { + /* If the right child is smaller, move to the right child */ + if (child < heap->size - 1 && heap->root(at(child + 1), at(child))) child++; + + if (heap->root(at(child), data)) + { + /* Swap the parent and child nodes */ + assign(at(i), at(child)); + + i = child; + + /* Move to the left child */ + child = HEAP_LEFT(child); + } + else break; + } + + /* Assign the data to the last parent node */ + assign(at(i), data); + } + + return 1; +} + +int heap_top(heap_t heap, void *data) +{ + /* Input value validity check */ + if (!heap) return 0; + if (!data) return 0; + if (heap->size == 0) return 0; + + /* Copy data out */ + assign(data, at(0)); + + return 1; +} + +int heap_size(heap_t heap) +{ + /* Input value validity check */ + if (!heap) return 0; + + /* Return heap size */ + return heap->size; +} diff --git a/source/03_container/heap.h b/source/03_container/heap.h new file mode 100644 index 0000000..811aba4 --- /dev/null +++ b/source/03_container/heap.h @@ -0,0 +1,110 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file heap.h + * \unit heap + * \brief This is a general C language heap container module + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __heap_H +#define __heap_H + +#include + +/* version infomation */ + +#define HEAP_V_MAJOR 1 +#define HEAP_V_MINOR 0 +#define HEAP_V_PATCH 0 + +/* heap type definition, hiding structural members, not for external use */ + +typedef struct HEAP *heap_t; + +/* big root: parent > child return 1, small root: parent < child return 1. + * Follow the rules to return 1, otherwise return 0. + */ +typedef int (*heap_root_t)(void *parent, void *child); + +/** + * \brief create heap + * \param[in] dsize: data size of heap item + * \param[in] capacity: capacity of heap base + * \param[in] root: root type of heap, big or small + * \return handler of new heap + */ +heap_t heap_create(int dsize, int capacity, heap_root_t root); + +/** + * \brief delete heap + * \param[in] heap: heap handle + * \return none + */ +void heap_delete(heap_t heap); + +/** + * \brief push data into heap + * \param[in] heap: heap handle + * \param[in] data: address of data + * \return 1 success or 0 fail + */ +int heap_push(heap_t heap, void *data); + +/** + * \brief pop data from heap + * \param[in] heap: heap handle + * \param[in] data: address of data + * \return 1 success or 0 fail + */ +int heap_pop(heap_t heap, void *data); + +/** + * \brief modify data of heap + * \param[in] heap: heap handle + * \param[in] index: index of heap, start from 0 + * \param[in] data: address of data + * \return 1 success or 0 fail + */ +int heap_modify(heap_t heap, int index, void *data); + +/** + * \brief get data of heap + * \param[in] heap: heap handle + * \param[in] data: address of data + * \return 1 success or 0 fail + */ +int heap_top(heap_t heap, void *data); + +/** + * \brief get the size of heap + * \param[in] heap: heap handle + * \return size of heap + */ +int heap_size(heap_t heap); + +/** + * \brief get the index of the parent node + * \param[in] i: current node index + * \return parent index + */ +#define HEAP_PARENT(i) (((i)-1)>>1) + +/** + * \brief get the index of the left child node + * \param[in] i: current node index + * \return left child index + */ +#define HEAP_LEFT(i) (((i)<<1)+1) + +/** + * \brief get the index of the right child node + * \param[in] i: current node index + * \return right child index + */ +#define HEAP_RIGHT(i) (((i)<<1)+2) + +#endif diff --git a/source/03_container/list.c b/source/03_container/list.c new file mode 100644 index 0000000..c8afd39 --- /dev/null +++ b/source/03_container/list.c @@ -0,0 +1,212 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file list.c + * \unit list + * \brief This is a C language singly linked list with built-in iterators, simple, reliable, fast, small space + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "list.h" +#include + +/* type of list node */ +typedef struct _NODE_ +{ + struct _NODE_ *next; /**< next node */ +} NODE; +#define data(node) ((node)+1) /**< data of node */ + +/* type of list */ +typedef struct LIST +{ + NODE* base; /**< address of base node */ + NODE* iterator; /**< iterator of list */ + int size; /**< size of list */ + int dsize; /**< data size */ + int index; /**< index of iterator */ +} LIST; + +list_t list_create(int dsize) +{ + list_t list; + + /* Input value validity check */ + if (dsize <= 0) return NULL; + + /* Allocate memory for the LIST structure */ + list = (list_t)malloc(sizeof(LIST)); + if (!list) return NULL; + + /* Initialize structural parameters */ + list->base = NULL; + list->iterator = NULL; + list->dsize = dsize; + list->size = 0; + list->index = 0; + + return list; +} + +void list_delete(list_t list) +{ + NODE *node, *next; + + /* Input value validity check */ + if (!list) return; + + /* Iteratively free each node */ + node = list->base; + while (node) + { + next = node->next; + free(node); + node = next; + } + + /* Free list structure */ + free(list); +} + +/** + * \brief iterator iterates to the specified node. + * \param[in] list: list handler + * \param[in] index: index + * \return address of node + */ +static NODE* list_node(list_t list, int index) +{ + if (!list) return NULL; + if (index < 0 || index >= list->size) return NULL; + if (index < list->index || !list->iterator || index == 0) + { + list->index = 0; + list->iterator = list->base; + } + while (list->iterator && list->index < index) + { + list->iterator = list->iterator->next; + list->index++; + } + return list->iterator; +} + +void* list_insert(list_t list, int index, void* data) +{ + NODE *node, *prev = NULL; + + /* Input value validity check */ + if (!list) return NULL; + if (index < 0 || index > list->size) return NULL; + + /* Allocate memory for the NODE structure */ + node = (NODE*)malloc(sizeof(NODE) + list->dsize); + if (!node) return NULL; + + /* Assigning data to the list */ + if (data) memcpy(data(node), data, list->dsize); + + /* Adjusting the linked list structure */ + if (index) + { + prev = list_node(list, index - 1); + node->next = prev->next; + prev->next = node; + } + else + { + node->next = list->base; + list->base = node; + list->iterator = list->base, list->index = 0; + } + + /* Update list status */ + list->size++; + + return data(node); +} + +int list_erase(list_t list, int index, int num) +{ + NODE *node, *prev = NULL; + int count = 0; + + /* Input value validity check */ + if (!list) return 0; + if (index < 0 || index >= list->size) return 0; + if (num <= 0) return 0; + + /* Correct the number of erases to be made */ + if (num > list->size - index) num = list->size - index; + + + /* Not starting from the list header to erase */ + if (index) + { + prev = list_node(list, index - 1); + for (count = 0; count < num; count++) + { + if (!prev) break; + node = prev->next; + prev->next = node->next; + free(node); + } + } + /* Starting from the list header to erase */ + else + { + prev = list->base; + for (count = 0; count < num; count++) + { + if (!prev) break; + node = prev->next; + free(prev); + prev = node; + } + list->base = prev; + list->iterator = list->base, list->index = 0; + } + + /* Update list status */ + list->size -= count; + + return count; +} + +void* list_data(list_t list, int index) +{ + NODE *node; + + /* Input value validity check */ + if (!list) return NULL; + if (index < 0 || index >= list->size) return NULL; + + /* Get node for accessing data */ + node = list_node(list, index); + if (!node) return NULL; + + /* Return address of node data */ + return data(node); +} + +int list_size(list_t list) +{ + /* Input value validity check */ + if (!list) return 0; + + /* Return list size */ + return list->size; +} + + +int list_dsize(list_t list) +{ + /* Input value validity check */ + if (!list) return 0; + + /* Return list data size */ + return list->dsize; +} diff --git a/source/03_container/list.h b/source/03_container/list.h new file mode 100644 index 0000000..4b144d7 --- /dev/null +++ b/source/03_container/list.h @@ -0,0 +1,144 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file list.h + * \unit list + * \brief This is a C language singly linked list with built-in iterators, simple, reliable, fast, small space + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __list_H +#define __list_H + +#include + +/* version infomation */ + +#define LIST_V_MAJOR 1 +#define LIST_V_MINOR 0 +#define LIST_V_PATCH 0 + +/* list type definition, hiding structural members, not for external use */ + +typedef struct LIST *list_t; + +/** + * \brief create a list. + * \param[in] dsize: size of list data + * \return list handler or NULL fail + */ +list_t list_create(int dsize); + +/** + * \brief delete a list. + * \param[in] list: list handler + * \return none + */ +void list_delete(list_t list); + +/** + * \brief insert data to list. + * \param[in] list: list handler + * \param[in] index: index + * \param[in] data: address of data + * \return address of list data or NULL fail + */ +void* list_insert(list_t list, int index, void* data); + +/** + * \brief erase data from list. + * \param[in] list: list handler + * \param[in] index: index + * \param[in] num: number of erase + * \return the number of actual erasures + */ +int list_erase(list_t list, int index, int num); + +/** + * \brief get data address of list. + * \param[in] list: list handler + * \param[in] index: index + * \return address of list data or NULL fail + */ +void* list_data(list_t list, int index); + +/** + * \brief get the size of list. + * \param[in] list: list handler + * \return size of list + */ +int list_size(list_t list); + +/** + * \brief get the data size of list. + * \param[in] list: list handler + * \return data size of list + */ +int list_dsize(list_t list); + +/** + * \brief A simple method for `list_create`. + * \param[in] type: data type + * \return list handler or NULL fail + */ +#define list(type) list_create(sizeof(type)) + +/** + * \brief A simple method for `list_delete`. + * \param[in] list: list handler + * \return none + */ +#define _list(list) do{list_delete(list);(list)=NULL;}while(0) + +/** + * \brief push data into the list from the front. + * \param[in] list: address of list + * \param[in] data: the address of data + * \return address of list data or NULL fail + */ +#define list_push_front(list, data) list_insert((list), 0, (data)) + +/** + * \brief push data into the list from the back. + * \param[in] list: address of list + * \param[in] data: the address of data + * \return address of list data or NULL fail + */ +#define list_push_back(list, data) list_insert((list), list_size(list), (data)) + +/** + * \brief pop data from the list from the front. + * \param[in] list: address of list + * \param[out] data: the address of data + * \return 1 success or 0 fail + */ +#define list_pop_front(list) list_erase((list), 0, 1) + +/** + * \brief pop data from the list from the back. + * \param[in] list: address of list + * \param[out] data: the address of data + * \return 1 success or 0 fail + */ +#define list_pop_back(list) list_erase((list), list_size(list), 1) + +/** + * \brief clear list. + * \param[in] list: address of list + * \return list size + */ +#define list_clear(list) list_erase((list), 0, list_size(list)) + +/** + * \brief Random access method for list data. + * \param[in] list: list handler + * \param[in] type: data type + * \param[in] i: index starting from list header + * \return Reference to list data + */ +#define list_at(list, type, i) (*(type *)list_data((list), (i))) + +#endif diff --git a/source/03_container/map.c b/source/03_container/map.c new file mode 100644 index 0000000..ae44cfe --- /dev/null +++ b/source/03_container/map.c @@ -0,0 +1,748 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file map.c + * \unit map + * \brief This is a general-purpose C language map module, with common data structure + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "map.h" +#include +#include +#include + +/* key transfer function type define */ +typedef void* (*ktrans_t)(map_t map, va_list args); + +/* map key tpye define */ +typedef struct +{ + void* address; /**< address of key */ + int size; /**< size of key */ +} MKEY; + +/* map node type define */ +typedef struct NODE +{ + struct NODE *parent; /**< parent node */ + struct NODE *left; /**< left child node */ + struct NODE *right; /**< right child node */ + int color; /**< node color */ + MKEY key; /**< node key */ +} NODE; +#define data(node) ((node)+1) /**< data of node */ + +/* map type define */ +typedef struct MAP +{ + NODE* root; /**< root node */ + NODE* nil; /**< nil node */ + NODE* iterator; /**< iterator of map */ + int orgin; /**< iterator orgin */ + int size; /**< map size */ + int vsize; /**< value size */ + int ksize; /**< key size */ + ktrans_t trans; /**< key transfer function */ +} MAP; + +/* map node color */ +#define BLACK (0) +#define RED (1) + +map_t map_create(int vsize, int ksize, void *trans) +{ + map_t map; + + /* Input value validity check */ + if (vsize <= 0) return NULL; + if (ksize < 0) return NULL; + + /* Allocate memory for the MAP structure */ + map = (map_t)malloc(sizeof(MAP)); + if (!map) return NULL; + + /* Allocate memory for the nil node */ + map->nil = (NODE*)malloc(sizeof(NODE) + vsize); + if (!map->nil) + { + free(map); + return NULL; + } + + /* Fixed length key */ + if (ksize != 0) + { + map->nil->key.address = malloc(ksize); + if (!map->nil->key.address) + { + free(map->nil); + free(map); + return NULL; + } + } + + /* Initialize structural parameters */ + map->nil->key.size = ksize; + map->nil->color = BLACK; + map->nil->parent = map->nil; + map->nil->left = map->nil; + map->nil->right = map->nil; + map->iterator = map->nil; + map->root = map->nil; + map->vsize = vsize; + map->size = 0; + map->ksize = ksize; + map->trans = trans; + + return map; +} + +static void recursion_delete_node(map_t map, NODE* node) +{ + if (node == map->nil) return; + recursion_delete_node(map, node->left); + recursion_delete_node(map, node->right); + free(node->key.address); + free(node); +} + +void map_delete(map_t map) +{ + /* Input value validity check */ + if (!map) return; + + /* Clear map */ + map_clear(map); + + /* Free allocated space */ + if (map->ksize) free(map->nil->key.address); + free(map->nil); + free(map); +} + +void* map_trans_key(void* map, void *address, int size) +{ + if (((map_t)map)->ksize == 0) + { + ((map_t)map)->nil->key.address = address; + ((map_t)map)->nil->key.size = size; + } + else + { + if (((map_t)map)->ksize == size) + { + memcpy(((map_t)map)->nil->key.address, address, ((map_t)map)->ksize); + ((map_t)map)->nil->key.size = size; + } + else + { + ((map_t)map)->nil->key.size = 0; + } + } + return &(((map_t)map)->nil->key); +} + +static int key_compare(MKEY key0, MKEY key1) +{ + unsigned char *add0 = (unsigned char *)key0.address; + unsigned char *add1 = (unsigned char *)key1.address; + while (key0.size && key1.size) + { + key0.size--; + key1.size--; + if (*add0 < *add1) return -1; + else if (*add0 > *add1) return 1; + add0++; + add1++; + } + if (key0.size < key1.size) return -1; + else if (key0.size > key1.size) return 1; + return 0; +} + +static NODE* map_find_node(map_t map, MKEY key) +{ + NODE* node = map->root; + int cmp = 0; + + /* Loop through the left and right branches until they match the key */ + while (node != map->nil) + { + cmp = key_compare(key, node->key); + if (cmp < 0) node = node->left; + else if (cmp > 0) node = node->right; + else return node; + } + return map->nil; +} + +static void left_rotate(map_t map, NODE* x) +{ + NODE* y = x->right; /* Get the right child of x */ + + /* Set x's right child to y's left child */ + x->right = y->left; + /* Update the parent of y's left child if it is not nil */ + if (y->left != map->nil) y->left->parent = x; + + /* Set y's parent to x's parent */ + y->parent = x->parent; + + /* If x is the root, update the root to y */ + if (x->parent == map->nil) map->root = y; + /* If x is the left child of its parent, update the left child of its parent to y */ + else if (x == x->parent->left) x->parent->left = y; + /* update the right child of its parent to y */ + else x->parent->right = y; + + y->left = x; + x->parent = y; +} + +static void right_rotate(map_t map, NODE* y) +{ + NODE* x = y->left; /* Get the left child of y */ + + /* Set y's left child to x's right child */ + y->left = x->right; + /* Update the parent of x's right child if it is not nil */ + if (x->right != map->nil) x->right->parent = y; + + /* Set x's parent to y's parent */ + x->parent = y->parent; + + /* If y is the root, update the root to x */ + if (y->parent == map->nil) map->root = x; + /* If y is the right child of its parent, update the right child of its parent to x */ + else if (y == y->parent->right) y->parent->right = x; + /* update the left child of its parent to x */ + else y->parent->left = x; + + x->right = y; + y->parent = x; +} + +/** + * \brief Performs fix-up operations after inserting a node into a red-black tree. + * + * \param[in] map The map structure containing the red-black tree. + * \param[in] z The newly inserted node in the red-black tree. + */ +static void map_insert_fixup(map_t map, NODE* z) +{ + NODE* y = NULL; + + while (z->parent->color == RED) + { + if (z->parent == z->parent->parent->left) + { + y = z->parent->parent->right; + if (y->color == RED) + { + // Case 1: z's uncle y is RED + z->parent->color = BLACK; + y->color = BLACK; + z->parent->parent->color = RED; + z = z->parent->parent; + } + else + { + if (z == z->parent->right) + { + // Case 2: z's uncle y is BLACK and z is a right child + z = z->parent; + left_rotate(map, z); + } + // Case 3: z's uncle y is BLACK and z is a left child + z->parent->color = BLACK; + z->parent->parent->color = RED; + right_rotate(map, z->parent->parent); + } + } + else + { + y = z->parent->parent->left; + if (y->color == RED) + { + // Case 1: z's uncle y is RED + z->parent->color = BLACK; + y->color = BLACK; + z->parent->parent->color = RED; + z = z->parent->parent; + } + else + { + if (z == z->parent->left) + { + // Case 2: z's uncle y is BLACK and z is a left child + z = z->parent; + right_rotate(map, z); + } + // Case 3: z's uncle y is BLACK and z is a right child + z->parent->color = BLACK; + z->parent->parent->color = RED; + left_rotate(map, z->parent->parent); + } + } + } + map->root->color = BLACK; +} + +/** + * \brief Inserts a node into a red-black tree and performs fix-up operations to maintain the red-black tree properties. + * + * \param[in] map The map structure containing the red-black tree. + * \param[in] z The node to be inserted into the red-black tree. + * \return 1 if the node was successfully inserted, 0 if a node with the same index already exists. + */ +static int map_insert_node(map_t map, NODE* z) +{ + NODE* y = map->nil; // Initialize y as the sentinel node + NODE* x = map->root; // Start at the root of the red-black tree + + while (x != map->nil) + { + y = x; + // Move to the left child if z's index is less than the current node's index + if (key_compare(z->key, x->key) < 0) x = x->left; + // Move to the right child if z's index is greater than the current node's index + else if (key_compare(z->key, x->key) > 0) x = x->right; + // Return 0 if a node with the same index already exists + else return 0; + } + + z->parent = y; // Set z's parent to y + + // Insert z as a child of y based on the comparison of their indices + // If y is the sentinel node, set z as the root of the red-black tree + if (y == map->nil) map->root = z; + // Set z as the left child of y + else if (key_compare(z->key, y->key) < 0) y->left = z; + // Set z as the right child of y + else y->right = z; + + z->left = map->nil; // Set z's left child to the sentinel node + z->right = map->nil; // Set z's right child to the sentinel node + z->color = RED; // Set z's color to red (since it is inserted as a leaf node) + + map_insert_fixup(map, z); // Perform fix-up operations to maintain the red-black tree properties + + return 1; // Return 1 to indicate successful insertion +} + +void* map_insert(map_t map, ...) +{ + NODE* node; + int child = 0; + MKEY *key; + void* value; + va_list args; + + va_start(args, value); + value = va_arg(args, void*); + key = (MKEY*)(map->trans(map, args)); + va_end(args); + + /* Input value validity check */ + if (key->size == 0) return NULL; + if (!map) return NULL; + + /* Allocate memory for the node */ + node = (NODE*)malloc(sizeof(NODE) + map->vsize); + if (!node) return NULL; + + /* Allocate memory for the key */ + node->key.address = malloc(key->size); + if (!node->key.address) { free(node); return NULL; } + + /* Assign key */ + node->key.size = key->size; + memcpy(node->key.address, key->address, key->size); + + /* Assign value */ + if (value) memcpy(data(node), value, map->vsize); + + /* Insert node into tree */ + if (!map_insert_node(map, node)) + { + free(node->key.address); + free(node); + return NULL; + } + + /* Update map status */ + map->size++; + + /* Return node data */ + return data(node); +} + +/** + * \brief Performs fix-up operations after deleting a node from a red-black tree. + * + * \param[in] map The map structure containing the red-black tree. + * \param[in] x The replacement node in the red-black tree. + */ +static void map_erase_fixup(map_t map, NODE* x) +{ + NODE* w = NULL; + + while ((x != map->root) && (x->color == BLACK)) + { + if (x == x->parent->left) + { + w = x->parent->right; + + if (w->color == RED) + { + // Case 1: x's sibling w is RED + w->color = BLACK; + x->parent->color = RED; + left_rotate(map, x->parent); + w = x->parent->right; + } + + if ((w->left->color == BLACK) && (w->right->color == BLACK)) + { + // Case 2: x's sibling w is BLACK and both w's children are BLACK + w->color = RED; + x = x->parent; + } + else + { + if (w->right->color == BLACK) + { + // Case 3: x's sibling w is BLACK, w's left child is RED, and w's right child is BLACK + w->left->color = BLACK; + w->color = RED; + right_rotate(map, w); + w = x->parent->right; + } + // Case 4: x's sibling w is BLACK and w's right child is RED + w->color = x->parent->color; + x->parent->color = BLACK; + w->right->color = BLACK; + left_rotate(map, x->parent); + x = map->root; + } + + } + else + { + w = x->parent->left; + if (w->color == RED) + { + // Case 1: x's sibling w is RED + w->color = BLACK; + x->parent->color = RED; + right_rotate(map, x->parent); + w = x->parent->left; + } + + if ((w->left->color == BLACK) && (w->right->color == BLACK)) + { + // Case 2: x's sibling w is BLACK and both w's children are BLACK + w->color = RED; + x = x->parent; + } + else + { + if (w->left->color == BLACK) + { + // Case 3: x's sibling w is BLACK, w's right child is RED, and w's left child is BLACK + w->right->color = BLACK; + w->color = RED; + left_rotate(map, w); + w = x->parent->left; + } + // Case 4: x's sibling w is BLACK and w's left child is RED + w->color = x->parent->color; + x->parent->color = BLACK; + w->left->color = BLACK; + right_rotate(map, x->parent); + x = map->root; + } + } + } + x->color = BLACK; +} + +static NODE* node_min(map_t map, NODE* x) +{ + if (x == map->nil) return x; + while (x->left != map->nil) x = x->left; + return x; +} + +static NODE* node_max(map_t map, NODE* x) +{ + if (x == map->nil) return x; + while (x->right != map->nil) x = x->right; + return x; +} + +/** + * \brief Finds the successor of a node in a red-black tree. + * + * \param[in] map The map structure containing the red-black tree. + * \param[in] x The node for which to find the successor. + * \return A pointer to the successor node, or the sentinel node if the successor does not exist. + */ +static NODE* map_successor(map_t map, NODE* x) +{ + NODE* y = x->parent; // Initialize y as x's parent + + // If x has a right child, the successor is the minimum node in x's right subtree + if (x->right != map->nil) return node_min(map, x->right); + + // If x does not have a right child, find the closest ancestor y such that x is in y's left subtree + while ((y != map->nil) && (x == y->right)) + { + x = y; + y = y->parent; + } + + return y; // Return the successor node (or the sentinel node if the successor does not exist) +} + +/** + * \brief Removes a node from a red-black tree and performs fix-up operations to maintain the red-black tree properties. + * + * \param[in] map The map structure containing the red-black tree. + * \param[in] z The node to be erased from the red-black tree. + * \return The removed node. + */ +static NODE* map_erase_node(map_t map, NODE* z) +{ + NODE* y = map->nil; // Initialize y as the sentinel node + NODE* x = map->nil; // Initialize x as the sentinel node + + // Determine the node to be removed (y) based on the number of children of z + if ((z->left == map->nil) || (z->right == map->nil)) + { + y = z; + } + else + { + y = map_successor(map, z); + } + + // Determine the child of y (x) based on whether y has a left child or a right child + if (y->left != map->nil) + { + x = y->left; + } + else if (y->right != map->nil) + { + x = y->right; + } + + x->parent = y->parent; // Set x's parent to y's parent + // Update y's parent's child pointer to x + if (y->parent == map->nil) + { + map->root = x; // If y is the root of the red-black tree, set x as the new root + } + else if (y == y->parent->left) + { + y->parent->left = x; // If y is the left child of its parent, set x as the new left child + } + else + { + y->parent->right = x; // If y is the right child of its parent, set x as the new right child + } + + // Replace z's data with y's data if the removed node is not z + if (y != z) + { + MKEY temp; + temp = z->key; + z->key = y->key; + y->key = temp; + memcpy(data(z), data(y), map->vsize); + } + + if (y->color == BLACK) map_erase_fixup(map, x); // Perform fix-up operations to maintain the red-black tree properties + + return y; // Return the removed node +} + +int map_erase(map_t map, ...) +{ + NODE* node = NULL; + NODE* cur = NULL; + va_list args; + MKEY key; + + va_start(args, map); + key = *(MKEY *)(map->trans(map, args)); + va_end(args); + + /* Input value validity check */ + if (!map) return 0; + + /* Find the specified node */ + node = map_find_node(map, key); + if (node == map->nil) return 0; + + /* Detach the node from the tree */ + cur = map_erase_node(map, node); + + /* Free the current node */ + free(cur->key.address); + free(cur); + + /* Update queue status */ + map->size--; + + return 1; +} + +void map_clear(map_t map) +{ + /* Input value validity check */ + if (!map) return; + + /* Recursively delete each node */ + recursion_delete_node(map, map->root); + + /* Map root node to nil */ + map->root = map->nil; + + /* Update queue status */ + map->size = 0; +} + +int map_size(map_t map) +{ + /* Input value validity check */ + if (!map) return 0; + + /* Return size */ + return map->size; +} + +int map_ksize(map_t map) +{ + /* Input value validity check */ + if (!map) return 0; + + /* Return key size */ + return map->ksize; +} + +int map_vsize(map_t map) +{ + /* Input value validity check */ + if (!map) return 0; + + /* Return value size */ + return map->vsize; +} + +int map_find(map_t map, ...) +{ + va_list args; + MKEY key; + + /* Input value validity check */ + if (!map) return 0; + + va_start(args, map); + key = *(MKEY *)(map->trans(map, args)); + va_end(args); + + /* Determine if the index can be found */ + return map_find_node(map, key) == map->nil ? 0 : 1; +} + +void* map_data(map_t map, ...) +{ + va_list args; + MKEY key; + + /* Input value validity check */ + if (!map) return NULL; + + va_start(args, map); + key = *(MKEY *)(map->trans(map, args)); + va_end(args); + + /* Return data for node */ + return data(map_find_node(map, key)); +} + +void* map_error(map_t map) +{ + /* Input value validity check */ + if (!map) return NULL; + + /* Return data for nil node */ + return data(map->nil); +} + +static NODE* node_next(map_t map, NODE* node) +{ + if (node->right != map->nil) + { + node = node->right; + node = node_min(map, node); + } + else + { + if (node == node->parent->left) node = node->parent; + else node = node->parent->parent; + } + return node; +} + +static NODE* node_prev(map_t map, NODE* node) +{ + if (node->left != map->nil) + { + node = node->left; + node = node_max(map, node); + } + else + { + if (node == node->parent->right) node = node->parent; + else node = node->parent->parent; + } + return node; +} + +void map_it_init(map_t map, int orgin) +{ + /* Input value validity check */ + if (!map) return; + + /* Update origin */ + map->orgin = (orgin == MAP_HEAD) ? MAP_HEAD : MAP_TAIL; + + /* Locate the maximum or minimum node based on the origin */ + map->iterator = (map->orgin == MAP_HEAD) ? (NODE*)node_min(map, map->root) : (NODE*)node_max(map, map->root); +} + +void* map_it_get(map_t map, void **kaddress, int *ksize) +{ + NODE *node; + + /* Input value validity check */ + if (!map) return NULL; + + /* Starting from the current iterator */ + node = map->iterator; + + /* Move iterator */ + map->iterator = (map->orgin == MAP_HEAD) ? node_next(map, map->iterator) : node_prev(map, map->iterator); + + /* Output the current iterator key */ + if (kaddress) *kaddress = node->key.address; + if (ksize) *ksize = node->key.size; + + return data(node); +} diff --git a/source/03_container/map.h b/source/03_container/map.h new file mode 100644 index 0000000..8a98cda --- /dev/null +++ b/source/03_container/map.h @@ -0,0 +1,163 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file map.h + * \unit map + * \brief This is a general-purpose C language map module, with common data structure + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __map_H +#define __map_H + +#include "map_cfg.h" +#include + +/* version infomation */ + +#define MAP_V_MAJOR 1 +#define MAP_V_MINOR 0 +#define MAP_V_PATCH 0 + +/* map type definition, hiding structural members, not for external use */ + +typedef struct MAP *map_t; + +/** + * \brief create map + * \param[in] vsize: size of map data + * \return map handler or NULL: fail + */ +map_t map_create(int vsize, int ksize, void *trans); + +/** + * \brief delete map + * \param[in] map: map handler + * \return none + */ +void map_delete(map_t map); + +/** + * \brief insert data to map. + * \param[in] map: map handler + * \param[in] value value, address of value + * \param[in] ...: mpair(key, value), key and address of value + * \return address of map data or NULL fail + */ +void* map_insert(map_t map, ...); + +/** + * \brief erase data from map. + * \param[in] map: map handler + * \param[in] ...: key + * \return 1 success or 0 fail + */ +int map_erase(map_t map, ...); + +/** + * \brief find index from map + * \param[in] map: map handler + * \param[in] ...: key + * \return 1 success or 0 fail + */ +int map_find(map_t map, ...); + +/** + * \brief get the address of item data from map + * \param[in] map: map handler + * \param[in] ...: key + * \return address of map data or map_error(): fail + */ +void* map_data(map_t map, ...); + +/** + * \brief clear all nodes under the map + * \param[in] map: map handler + * \return none + */ +void map_clear(map_t map); + +/** + * \brief get the size of map + * \param[in] map: map handler + * \return size of map + */ +int map_size(map_t map); + +/** + * \brief get the size of map key + * \param[in] map: map handler + * \return size of map key + */ +int map_ksize(map_t map); + +/** + * \brief get the size of map value + * \param[in] map: map handler + * \return size of map value + */ +int map_vsize(map_t map); + +/** + * \brief error return value + * \param[in] map: map handler + * \return error pointer + */ +void* map_error(map_t map); + +/** + * \brief iterate init at tail + * \param[in] map: map handler + * \param[in] orgin: MAP_HEAD or MAP_TAIL + * \return none + */ +void map_it_init(map_t map, int orgin); + +/** + * \brief iterate get + * \param[in] map: map handler + * \param[out] kaddress: address of key + * \param[out] ksize: size of key + * \return address of map iterator data + */ +void* map_it_get(map_t map, void **kaddress, int *ksize); + +/** + * \brief A simple method for `map_create` + * \param[in] ktype: key type, default support: char, int, string, float, double + * \param[in] vtype: value type + * \return map handler or NULL: fail + */ +#define map(ktype, vtype) map_create(sizeof(vtype), (MK_TYPE(ktype)==MAP_KEY_TYPE_POINTER)?0:sizeof(ktype), MK_TRANS(ktype)) + +/** + * \brief A simple method for `map_delete`. + * \param[in] map: map handler + * \return none + */ +#define _map(map) do{map_delete(map);(map)=0;}while(0) + +/** + * \brief Random access method for map data. + * \param[in] map: map handler + * \param[in] type: value type + * \param[in] key: key + * \return Reference to map data + */ +#define map_at(map, type, key) (*(type *)map_data((map), (key))) + +/** + * \brief Key value pairs. + * \param[in] key: key + * \param[in] value: address of value + * \return pair + */ +#define mpair(key, value) (value),(key) + +#define MAP_HEAD 0 +#define MAP_TAIL 1 + +#endif diff --git a/source/03_container/map_cfg.c b/source/03_container/map_cfg.c new file mode 100644 index 0000000..9daf84b --- /dev/null +++ b/source/03_container/map_cfg.c @@ -0,0 +1,89 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file map_cfg.c + * \unit map + * \brief This is a general-purpose C language map module,key type configuration. + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "map_cfg.h" +#include +#include +#include +#include "map.h" + +extern void* map_trans_key(void* map, void *address, int size); + +/** + * \brief key transform function of type int + * \param[in] map: map handler + * \param[in] key: int key + * \return address of key + */ +void* map_key_trans__int(void* map, va_list args) +{ + int key; + key = va_arg(args, int); + return map_trans_key(map, &key, sizeof(int)); +} + +/** + * \brief key transform function of type char + * \param[in] map: map handler + * \param[in] key: char key + * \return address of key + */ +void* map_key_trans__char(void* map, va_list args) +{ + char key; + key = va_arg(args, int); + return map_trans_key(map, &key, sizeof(char)); +} + +/** + * \brief key transform function of type char* string + * \param[in] map: map handler + * \param[in] key: string key + * \return address of key + */ +void* map_key_trans__string(void* map, va_list args) +{ + char* key; + key = va_arg(args, char*); + return map_trans_key(map, key, strlen(key) + 1); +} + +/** + * \brief key transform function of type float + * \param[in] map: map handler + * \param[in] key: string key + * \return address of key + */ +void* map_key_trans__float(void* map, va_list args) +{ + float key; + key = va_arg(args, double); + return map_trans_key(map, &key, sizeof(float)); +} + +/** + * \brief key transform function of type double + * \param[in] map: map handler + * \param[in] key: string key + * \return address of key + */ +void* map_key_trans__double(void* map, va_list args) +{ + double key; + key = va_arg(args, double); + return map_trans_key(map, &key, sizeof(double)); +} + +/********************************************************************************************************* + * add other key type transform function here + ********************************************************************************************************/ + diff --git a/source/03_container/map_cfg.h b/source/03_container/map_cfg.h new file mode 100644 index 0000000..f7b4553 --- /dev/null +++ b/source/03_container/map_cfg.h @@ -0,0 +1,46 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file map_cfg.h + * \unit map + * \brief This is a general-purpose C language map module, key type configuration. + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __map_cfg_H +#define __map_cfg_H + +#include + +/* string type define */ +typedef char* string; + +#define MAP_KEY_TYPE_POINTER 0 +#define MAP_KEY_TYPE_ENTITY 1 + +/* macro type */ +#define MK_TYPE(type) MAP_KEY_TYPE__##type +#define MK_TRANS(type) map_key_trans__##type + +/* key type */ +#define MAP_KEY_TYPE__int MAP_KEY_TYPE_ENTITY +#define MAP_KEY_TYPE__char MAP_KEY_TYPE_ENTITY +#define MAP_KEY_TYPE__string MAP_KEY_TYPE_POINTER +#define MAP_KEY_TYPE__float MAP_KEY_TYPE_ENTITY +#define MAP_KEY_TYPE__double MAP_KEY_TYPE_ENTITY + +/* key transform function declare */ +void* map_key_trans__int(void* map, va_list args); +void* map_key_trans__char(void* map, va_list args); +void* map_key_trans__string(void* map, va_list args); +void* map_key_trans__float(void* map, va_list args); +void* map_key_trans__double(void* map, va_list args); + +/********************************************************************************************************* + * add other key type transform function declare here + ********************************************************************************************************/ + +#endif diff --git a/source/03_container/queue.c b/source/03_container/queue.c new file mode 100644 index 0000000..944bed1 --- /dev/null +++ b/source/03_container/queue.c @@ -0,0 +1,193 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file queue.c + * \unit queue + * \brief This is a C language queue + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "queue.h" +#include + +typedef struct QUEUE +{ + void* base; /**< base address of data */ + int cst; /**< base const */ + int dsize; /**< size of queue data */ + int capacity; /**< capacity of queue */ + int size; /**< size of queue */ + int head; /**< index of queue head */ + int tail; /**< index of queue tail */ +} QUEUE; + +/* Address of queue array */ +#define at(i) (((unsigned char *)(queue->base))+(i)*(queue->dsize)) + +queue_t queue_create(int dsize, int capacity, void *base) +{ + queue_t queue; + + /* Input value validity check */ + if (dsize <= 0) return NULL; + if (capacity <= 0) return NULL; + + /* Allocate memory for the QUEUE structure */ + queue = (queue_t)malloc(sizeof(QUEUE)); + if (!queue) return NULL; + + /* Initialize structural parameters */ + queue->base = base; + queue->cst = 1; + queue->capacity = capacity; + queue->dsize = dsize; + queue->tail = 0; + queue->head = 0; + queue->size = 0; + + /* Dynamically allocate an array without passing it in */ + if (!queue->base) + { + queue->base = malloc(dsize * capacity); + queue->cst = 0; + } + + /* Check if the array space is valid */ + if (!queue->base) + { + queue_delete(queue); + return NULL; + } + + return queue; +} + +void queue_delete(queue_t queue) +{ + /* Input value validity check */ + if (!queue) return; + + /* If it is not a constant array but a dynamic array, release the allocated space */ + if (!queue->cst && queue->base) free(queue->base); + + /* Free queue structure */ + free(queue); +} + +int queue_push(queue_t queue, void* data) +{ + /* Input value validity check */ + if (!queue) return 0; + + /* Check if the queue is full */ + if (queue_full(queue)) return 0; + + /* Assigning data to the queue */ + if (data) memcpy(at(queue->tail), data, queue->dsize); + + /* Update queue status */ + queue->tail = (queue->tail + 1) % queue->capacity; + queue->size++; + + return 1; +} + +int queue_pop(queue_t queue, void* data) +{ + /* Input value validity check */ + if (!queue) return 0; + + /* Check if the queue is full */ + if (queue_empty(queue)) return 0; + + /* Assigning data from the queue */ + if (data) memcpy(data, at(queue->head), queue->dsize); + + /* Update queue status */ + queue->head = (queue->head + 1) % queue->capacity; + queue->size--; + + return 1; +} + +void queue_clear(queue_t queue) +{ + /* Input value validity check */ + if (!queue) return; + + /* Reset queue status */ + queue->tail = 0; + queue->head = 0; + queue->size = 0; +} + +int queue_index(queue_t queue, int index) +{ + /* Input value validity check */ + if (!queue) return -1; + if (index < 0 || index >= queue->size) return -1; + + /* Starting from the head, calculate the data index */ + return (queue->head + index) % (queue->capacity); +} + +void* queue_data(queue_t queue, int index) +{ + /* Input value validity check */ + if (!queue) return NULL; + + /* Get indexe for accessing data */ + index = queue_index(queue, index); + if (index < 0) return NULL; + + /* Return array address based on index */ + return (void*)at(index); +} + +int queue_size(queue_t queue) +{ + /* Input value validity check */ + if (!queue) return 0; + + /* Return queue size */ + return queue->size; +} + +int queue_capacity(queue_t queue) +{ + /* Input value validity check */ + if (!queue) return 0; + + /* Return queue capacity */ + return queue->capacity; +} + +int queue_dsize(queue_t queue) +{ + /* Input value validity check */ + if (!queue) return 0; + + /* Return queue data size */ + return queue->dsize; +} + +int queue_empty(queue_t queue) +{ + /* Input value validity check */ + if (!queue) return 1; + + /* Determine if size is 0 */ + return (queue->size == 0) ? 1 : 0; +} + +int queue_full(queue_t queue) +{ + /* Input value validity check */ + if (!queue) return 0; + + /* Determine if size is capacity */ + return (queue->size == queue->capacity) ? 1 : 0; +} diff --git a/source/03_container/queue.h b/source/03_container/queue.h new file mode 100644 index 0000000..1cc5c1d --- /dev/null +++ b/source/03_container/queue.h @@ -0,0 +1,142 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file queue.h + * \unit queue + * \brief This is a C language queue + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __queue_H +#define __queue_H + +#include + +/* version infomation */ + +#define QUEUE_V_MAJOR 1 +#define QUEUE_V_MINOR 0 +#define QUEUE_V_PATCH 0 + +/* queue type definition, hiding structural members, not for external use */ + +typedef struct QUEUE *queue_t; + +/** + * \brief create a queue. + * \param[in] dsize: size of queue data + * \param[in] capacity: capacity of queue + * \param[in] base: allocated array or pass in `NULL` to dynamically allocate space + * \return queue handler or NULL fail + */ +queue_t queue_create(int dsize, int capacity, void *base); + +/** + * \brief delete a queue. + * \param[in] queue: queue handler + * \return none + */ +void queue_delete(queue_t queue); + +/** + * \brief push data into the queue. + * \param[in] queue: address of queue + * \param[in] data: the address of data + * \return 1 success or 0 fail + */ +int queue_push(queue_t queue, void* data); + +/** + * \brief pop data from the queue. + * \param[in] queue: address of queue + * \param[out] data: the address of data + * \return 1 success or 0 fail + */ +int queue_pop(queue_t queue, void* data); + +/** + * \brief clear queue. + * \param[in] queue: address of queue + * \return none + */ +void queue_clear(queue_t queue); + +/** + * \brief get queue index. + * \param[in] queue: address of queue + * \param[in] index: index of queue + * \return index of queue buffer or negative fail + */ +int queue_index(queue_t queue, int index); + +/** + * \brief get data address of queue. + * \param[in] queue: queue handler + * \param[in] index: index + * \return address of queue data or NULL fail + */ +void* queue_data(queue_t queue, int index); + +/** + * \brief get size of queue. + * \param[in] queue: queue handler + * \return size of queue + */ +int queue_size(queue_t queue); + +/** + * \brief get capacity of queue. + * \param[in] queue: queue handler + * \return capacity of queue + */ +int queue_capacity(queue_t queue); + +/** + * \brief get data size of queue. + * \param[in] queue: queue handler + * \return data size of queue + */ +int queue_dsize(queue_t queue); + +/** + * \brief check if empty. + * \param[in] queue: queue handler + * \return 1 empty or 0 not empty + */ +int queue_empty(queue_t queue); + +/** + * \brief check if full. + * \param[in] queue: queue handler + * \return 1 full or 0 not full + */ +int queue_full(queue_t queue); + +/** + * \brief A simple method for `queue_create`. + * \param[in] type: data type + * \param[in] capacity: capacity of queue + * \return queue handler or NULL fail + */ +#define queue(type, capacity) queue_create(sizeof(type), (capacity), NULL) + +/** + * \brief A simple method for `queue_delete`. + * \param[in] queue: queue handler + * \return none + */ +#define _queue(queue) do{queue_delete(queue);(queue)=NULL;}while(0) + +/** + * \brief Random access method for queue data. + * \param[in] queue: queue handler + * \param[in] type: data type + * \param[in] i: index starting from queue header + * \return Reference to queue data + */ +#define queue_at(queue, type, i) (*(type *)queue_data((queue), (i))) + +#endif diff --git a/source/03_container/set.c b/source/03_container/set.c new file mode 100644 index 0000000..80808ee --- /dev/null +++ b/source/03_container/set.c @@ -0,0 +1,645 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file set.c + * \unit set + * \brief This is a general-purpose C language set module, with common data structure + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "set.h" +#include +#include + +/* set node type define */ +typedef struct NODE +{ + struct NODE *parent; /**< parent node */ + struct NODE *left; /**< left child node */ + struct NODE *right; /**< right child node */ + int color; /**< node color */ + int index; /**< node index */ +} NODE; +#define data(node) ((node)+1) /**< data of node */ + +/* set type define */ +typedef struct SET +{ + NODE* root; /**< root node */ + NODE* nil; /**< nil node */ + NODE* iterator; /**< iterator of set */ + int orgin; /**< iterator orgin */ + int size; /**< set size */ + int dsize; /**< data size */ +} SET; + +/* set node color */ +#define BLACK (0) +#define RED (1) + +set_t set_create(int dsize) +{ + set_t set; + + /* Input value validity check */ + if (dsize <= 0) return NULL; + + /* Allocate memory for the SET structure */ + set = (set_t)malloc(sizeof(SET)); + if (!set) return NULL; + + /* Allocate memory for the nil node */ + set->nil = (NODE*)malloc(sizeof(NODE) + dsize); + if (!set->nil) + { + free(set); + return NULL; + } + + /* Initialize structural parameters */ + set->nil->color = BLACK; + set->nil->parent = set->nil; + set->nil->left = set->nil; + set->nil->right = set->nil; + set->iterator = set->nil; + set->root = set->nil; + set->dsize = dsize; + set->size = 0; + + return set; +} + +static void recursion_delete_node(set_t set, NODE* node) +{ + if (node == set->nil) return; + recursion_delete_node(set, node->left); + recursion_delete_node(set, node->right); + free(node); +} + +void set_delete(set_t set) +{ + /* Input value validity check */ + if (!set) return; + + /* Clear set */ + set_clear(set); + + /* Free allocated space */ + free(set->nil); + free(set); +} + +static NODE* set_find_node(set_t set, int index) +{ + NODE* node = set->root; + + /* Loop through the left and right branches until they match the index */ + while (node != set->nil) + { + if (index < node->index) node = node->left; + else if (index > node->index) node = node->right; + else return node; + } + + return set->nil; +} + +static void left_rotate(set_t set, NODE* x) +{ + NODE* y = x->right; /* Get the right child of x */ + + /* Set x's right child to y's left child */ + x->right = y->left; + /* Update the parent of y's left child if it is not nil */ + if (y->left != set->nil) y->left->parent = x; + + /* Set y's parent to x's parent */ + y->parent = x->parent; + + /* If x is the root, update the root to y */ + if (x->parent == set->nil) set->root = y; + /* If x is the left child of its parent, update the left child of its parent to y */ + else if (x == x->parent->left) x->parent->left = y; + /* update the right child of its parent to y */ + else x->parent->right = y; + + y->left = x; + x->parent = y; +} + + +static void right_rotate(set_t set, NODE* y) +{ + NODE* x = y->left; /* Get the left child of y */ + + /* Set y's left child to x's right child */ + y->left = x->right; + /* Update the parent of x's right child if it is not nil */ + if (x->right != set->nil) x->right->parent = y; + + /* Set x's parent to y's parent */ + x->parent = y->parent; + + /* If y is the root, update the root to x */ + if (y->parent == set->nil) set->root = x; + /* If y is the right child of its parent, update the right child of its parent to x */ + else if (y == y->parent->right) y->parent->right = x; + /* update the left child of its parent to x */ + else y->parent->left = x; + + x->right = y; + y->parent = x; +} + +/** + * \brief Performs fix-up operations after inserting a node into a red-black tree. + * + * \param[in] set The set structure containing the red-black tree. + * \param[in] z The newly inserted node in the red-black tree. + */ +static void set_insert_fixup(set_t set, NODE* z) +{ + NODE* y = NULL; + + while (z->parent->color == RED) + { + if (z->parent == z->parent->parent->left) + { + y = z->parent->parent->right; + if (y->color == RED) + { + // Case 1: z's uncle y is RED + z->parent->color = BLACK; + y->color = BLACK; + z->parent->parent->color = RED; + z = z->parent->parent; + } + else + { + if (z == z->parent->right) + { + // Case 2: z's uncle y is BLACK and z is a right child + z = z->parent; + left_rotate(set, z); + } + // Case 3: z's uncle y is BLACK and z is a left child + z->parent->color = BLACK; + z->parent->parent->color = RED; + right_rotate(set, z->parent->parent); + } + } + else + { + y = z->parent->parent->left; + if (y->color == RED) + { + // Case 1: z's uncle y is RED + z->parent->color = BLACK; + y->color = BLACK; + z->parent->parent->color = RED; + z = z->parent->parent; + } + else + { + if (z == z->parent->left) + { + // Case 2: z's uncle y is BLACK and z is a left child + z = z->parent; + right_rotate(set, z); + } + // Case 3: z's uncle y is BLACK and z is a right child + z->parent->color = BLACK; + z->parent->parent->color = RED; + left_rotate(set, z->parent->parent); + } + } + } + set->root->color = BLACK; +} + +/** + * \brief Inserts a node into a red-black tree and performs fix-up operations to maintain the red-black tree properties. + * + * \param[in] set The set structure containing the red-black tree. + * \param[in] z The node to be inserted into the red-black tree. + * \return 1 if the node was successfully inserted, 0 if a node with the same index already exists. + */ +static int set_insert_node(set_t set, NODE* z) +{ + NODE* y = set->nil; // Initialize y as the sentinel node + NODE* x = set->root; // Start at the root of the red-black tree + + while (x != set->nil) + { + y = x; + if (z->index < x->index) + { + x = x->left; // Move to the left child if z's index is less than the current node's index + } + else if (z->index > x->index) + { + x = x->right; // Move to the right child if z's index is greater than the current node's index + } + else + { + return 0; // Return 0 if a node with the same index already exists + } + } + + z->parent = y; // Set z's parent to y + + // Insert z as a child of y based on the comparison of their indices + if (y == set->nil) + { + set->root = z; // If y is the sentinel node, set z as the root of the red-black tree + } + else if (z->index < y->index) + { + y->left = z; // Set z as the left child of y + } + else + { + y->right = z; // Set z as the right child of y + } + + z->left = set->nil; // Set z's left child to the sentinel node + z->right = set->nil; // Set z's right child to the sentinel node + z->color = RED; // Set z's color to red (since it is inserted as a leaf node) + + set_insert_fixup(set, z); // Perform fix-up operations to maintain the red-black tree properties + + return 1; // Return 1 to indicate successful insertion +} + +void* set_insert(set_t set, int index, void* data) +{ + NODE* node; + + /* Input value validity check */ + if (!set) return NULL; + + /* Allocate memory for the node */ + node = (NODE*)malloc(sizeof(NODE) + set->dsize); + if (!node) return NULL; + + /* Record index */ + node->index = index; + + /* Assign values to data */ + if (data) memcpy(data(node), data, set->dsize); + + /* Insert node into tree */ + if (!set_insert_node(set, node)) + { + free(node); + return NULL; + } + + /* Update set status */ + set->size++; + + /* Return node data */ + return data(node); +} + +/** + * \brief Performs fix-up operations after deleting a node from a red-black tree. + * + * \param[in] set The set structure containing the red-black tree. + * \param[in] x The replacement node in the red-black tree. + */ +static void set_erase_fixup(set_t set, NODE* x) +{ + NODE* w = NULL; + + while ((x != set->root) && (x->color == BLACK)) + { + if (x == x->parent->left) + { + w = x->parent->right; + + if (w->color == RED) + { + // Case 1: x's sibling w is RED + w->color = BLACK; + x->parent->color = RED; + left_rotate(set, x->parent); + w = x->parent->right; + } + + if ((w->left->color == BLACK) && (w->right->color == BLACK)) + { + // Case 2: x's sibling w is BLACK and both w's children are BLACK + w->color = RED; + x = x->parent; + } + else + { + if (w->right->color == BLACK) + { + // Case 3: x's sibling w is BLACK, w's left child is RED, and w's right child is BLACK + w->left->color = BLACK; + w->color = RED; + right_rotate(set, w); + w = x->parent->right; + } + // Case 4: x's sibling w is BLACK and w's right child is RED + w->color = x->parent->color; + x->parent->color = BLACK; + w->right->color = BLACK; + left_rotate(set, x->parent); + x = set->root; + } + } + else + { + w = x->parent->left; + if (w->color == RED) + { + // Case 1: x's sibling w is RED + w->color = BLACK; + x->parent->color = RED; + right_rotate(set, x->parent); + w = x->parent->left; + } + + if ((w->left->color == BLACK) && (w->right->color == BLACK)) + { + // Case 2: x's sibling w is BLACK and both w's children are BLACK + w->color = RED; + x = x->parent; + } + else + { + if (w->left->color == BLACK) + { + // Case 3: x's sibling w is BLACK, w's right child is RED, and w's left child is BLACK + w->right->color = BLACK; + w->color = RED; + left_rotate(set, w); + w = x->parent->left; + } + // Case 4: x's sibling w is BLACK and w's left child is RED + w->color = x->parent->color; + x->parent->color = BLACK; + w->left->color = BLACK; + right_rotate(set, x->parent); + x = set->root; + } + } + } + x->color = BLACK; +} + +static NODE* node_min(set_t set, NODE* x) +{ + if (x == set->nil) return x; + while (x->left != set->nil) x = x->left; + return x; +} + +static NODE* node_max(set_t set, NODE* x) +{ + if (x == set->nil) return x; + while (x->right != set->nil) x = x->right; + return x; +} + +/** + * \brief Finds the successor of a node in a red-black tree. + * + * \param[in] set The set structure containing the red-black tree. + * \param[in] x The node for which to find the successor. + * \return A pointer to the successor node, or the sentinel node if the successor does not exist. + */ +static NODE* set_successor(set_t set, NODE* x) +{ + NODE* y = x->parent; // Initialize y as x's parent + + // If x has a right child, the successor is the minimum node in x's right subtree + if (x->right != set->nil) + { + return node_min(set, x->right); + } + + // If x does not have a right child, find the closest ancestor y such that x is in y's left subtree + while ((y != set->nil) && (x == y->right)) + { + x = y; + y = y->parent; + } + + return y; // Return the successor node (or the sentinel node if the successor does not exist) +} + +/** + * \brief Removes a node from a red-black tree and performs fix-up operations to maintain the red-black tree properties. + * + * \param[in] set The set structure containing the red-black tree. + * \param[in] z The node to be erased from the red-black tree. + * \return The removed node. + */ +static NODE* set_erase_node(set_t set, NODE* z) +{ + NODE* y = set->nil; // Initialize y as the sentinel node + NODE* x = set->nil; // Initialize x as the sentinel node + + // Determine the node to be removed (y) based on the number of children of z + if ((z->left == set->nil) || (z->right == set->nil)) + { + y = z; + } + else + { + y = set_successor(set, z); + } + + // Determine the child of y (x) based on whether y has a left child or a right child + if (y->left != set->nil) + { + x = y->left; + } + else if (y->right != set->nil) + { + x = y->right; + } + + x->parent = y->parent; // Set x's parent to y's parent + + // Update y's parent's child pointer to x + if (y->parent == set->nil) + { + set->root = x; // If y is the root of the red-black tree, set x as the new root + } + else if (y == y->parent->left) + { + y->parent->left = x; // If y is the left child of its parent, set x as the new left child + } + else + { + y->parent->right = x; // If y is the right child of its parent, set x as the new right child + } + + // Replace z's data with y's data if the removed node is not z + if (y != z) + { + z->index = y->index; + memcpy(data(z), data(y), set->dsize); + } + + if (y->color == BLACK) + { + set_erase_fixup(set, x); // Perform fix-up operations to maintain the red-black tree properties + } + + return y; // Return the removed node +} + +int set_erase(set_t set, int index) +{ + NODE* node = NULL; + NODE* cur = NULL; + + /* Input value validity check */ + if (!set) return 0; + + /* Find the specified node */ + node = set_find_node(set, index); + if (node == set->nil) return 0; + + /* Detach the node from the tree */ + cur = set_erase_node(set, node); + + /* Free the current node */ + free(cur); + + /* Update queue status */ + set->size--; + + return 1; +} + +void set_clear(set_t set) +{ + /* Input value validity check */ + if (!set) return; + + /* Recursively delete each node */ + recursion_delete_node(set, set->root); + + /* Set root node to nil */ + set->root = set->nil; + + /* Update queue status */ + set->size = 0; +} + +int set_size(set_t set) +{ + /* Input value validity check */ + if (!set) return 0; + + /* Return size */ + return set->size; +} + +int set_dsize(set_t set) +{ + /* Input value validity check */ + if (!set) return 0; + + /* Return data size */ + return set->dsize; +} + +int set_find(set_t set, int index) +{ + /* Input value validity check */ + if (!set) return 0; + + /* Determine if the index can be found */ + return set_find_node(set, index) == set->nil ? 0 : 1; +} + +void* set_data(set_t set, int index) +{ + /* Input value validity check */ + if (!set) return NULL; + + /* Return data for node */ + return data(set_find_node(set, index)); +} + +void* set_error(set_t set) +{ + /* Input value validity check */ + if (!set) return NULL; + + /* Return data for nil node */ + return data(set->nil); +} + +static NODE* node_next(set_t set, NODE* node) +{ + if (node->right != set->nil) + { + node = node->right; + node = node_min(set, node); + } + else + { + if (node == node->parent->left) node = node->parent; + else node = node->parent->parent; + } + return node; +} + +static NODE* node_prev(set_t set, NODE* node) +{ + if (node->left != set->nil) + { + node = node->left; + node = node_max(set, node); + } + else + { + if (node == node->parent->right) node = node->parent; + else node = node->parent->parent; + } + return node; +} + +void set_it_init(set_t set, int orgin) +{ + /* Input value validity check */ + if (!set) return; + + /* Update origin */ + set->orgin = (orgin == SET_HEAD) ? SET_HEAD : SET_TAIL; + + /* Locate the maximum or minimum node based on the origin */ + set->iterator = (set->orgin == SET_HEAD) ? (NODE*)node_min(set, set->root) : (NODE*)node_max(set, set->root); +} + +void* set_it_get(set_t set, int *out_index) +{ + NODE *node; + + /* Input value validity check */ + if (!set) return NULL; + + /* Starting from the current iterator */ + node = set->iterator; + + /* Move iterator */ + set->iterator = (set->orgin == SET_HEAD) ? node_next(set, set->iterator) : node_prev(set, set->iterator); + + /* Output the current iterator index */ + if (out_index) *out_index = node->index; + + return data(node); +} diff --git a/source/03_container/set.h b/source/03_container/set.h new file mode 100644 index 0000000..f0bb620 --- /dev/null +++ b/source/03_container/set.h @@ -0,0 +1,145 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file set.h + * \unit set + * \brief This is a general-purpose C language set module, with common data structure + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __set_H +#define __set_H + +#include + +/* version infomation */ + +#define SET_V_MAJOR 1 +#define SET_V_MINOR 0 +#define SET_V_PATCH 0 + +/* set type definition, hiding structural members, not for external use */ + +typedef struct SET *set_t; + +/** + * \brief create set + * \param[in] dsize: size of set data + * \return set handler or NULL: fail + */ +set_t set_create(int dsize); + +/** + * \brief delete set + * \param[in] set: set handler + * \return none + */ +void set_delete(set_t set); + +/** + * \brief insert data to set. + * \param[in] set: set handler + * \param[in] index: index + * \param[in] data: address of data + * \return address of set data or NULL fail + */ +void* set_insert(set_t set, int index, void* data); + +/** + * \brief erase data from set. + * \param[in] set: set handler + * \param[in] index: index + * \return 1 success or 0 fail + */ +int set_erase(set_t set, int index); + +/** + * \brief clear all nodes under the set + * \param[in] set: set handler + * \return none + */ +void set_clear(set_t set); + +/** + * \brief get the size of set + * \param[in] set: set handler + * \return size of set + */ +int set_size(set_t set); + +/** + * \brief get the data size of set. + * \param[in] set: set handler + * \return data size of set + */ +int set_dsize(set_t set); + +/** + * \brief find index from set + * \param[in] set: set handler + * \param[in] index: index + * \return 1 success or 0 fail + */ +int set_find(set_t set, int index); + +/** + * \brief get the address of item data from set + * \param[in] set: set handler + * \param[in] index: index + * \return address of set data or set_error(): fail + */ +void* set_data(set_t set, int index); + +/** + * \brief error return value + * \param[in] set: set handler + * \return error pointer + */ +void* set_error(set_t set); + +/** + * \brief iterate init at tail + * \param[in] set: set handler + * \param[in] orgin: SET_HEAD or SET_TAIL + * \return none + */ +void set_it_init(set_t set, int orgin); + +/** + * \brief iterate get + * \param[in] set: set handler + * \param[out] out_index: out index + * \return address of set iterator data + */ +void* set_it_get(set_t set, int *out_index); + +/** + * \brief A simple method for `set_create` + * \param[in] type: data type + * \return set handler or NULL: fail + */ +#define set(type) set_create(sizeof(type)) + +/** + * \brief A simple method for `set_delete`. + * \param[in] set: set handler + * \return none + */ +#define _set(set) do{set_delete(set);(set)=NULL;}while(0) + +/** + * \brief Random access method for set data. + * \param[in] set: set handler + * \param[in] type: data type + * \param[in] i: index starting from set header + * \return Reference to set data + */ +#define set_at(set, type, i) (*(type *)set_data((set), (i))) + +#define SET_HEAD 0 +#define SET_TAIL 1 + +#endif diff --git a/source/03_container/stack.c b/source/03_container/stack.c new file mode 100644 index 0000000..6a6b044 --- /dev/null +++ b/source/03_container/stack.c @@ -0,0 +1,182 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file stack.c + * \unit stack + * \brief This is a C language stack + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "stack.h" +#include + +typedef struct STACK +{ + void* base; /**< base address of data */ + int cst; /**< base const */ + int dsize; /**< size of stack data */ + int capacity; /**< capacity of stack */ + int top; /**< index of stack top */ +} STACK; + +/* Address of stack array */ +#define at(i) (((unsigned char *)(stack->base))+(i)*(stack->dsize)) + +stack_t stack_create(int dsize, int capacity, void *base) +{ + stack_t stack; + + /* Input value validity check */ + if (dsize <= 0) return NULL; + if (capacity <= 0) return NULL; + + /* Allocate memory for the STACK structure */ + stack = (stack_t)malloc(sizeof(STACK)); + if (!stack) return NULL; + + /* Initialize structural parameters */ + stack->base = base; + stack->cst = 1; + stack->capacity = capacity; + stack->dsize = dsize; + stack->top = 0; + + /* Dynamically allocate an array without passing it in */ + if (!stack->base) + { + stack->base = malloc(dsize * capacity); + stack->cst = 0; + } + + /* Check if the array space is valid */ + if (!stack->base) + { + stack_delete(stack); + return NULL; + } + + return stack; +} + +void stack_delete(stack_t stack) +{ + /* Input value validity check */ + if (!stack) return; + + /* If it is not a constant array but a dynamic array, release the allocated space */ + if (!stack->cst && stack->base) free(stack->base); + + /* Free stack structure */ + free(stack); +} + +int stack_push(stack_t stack, void* data) +{ + /* Input value validity check */ + if (!stack) return 0; + + /* Check if the stack is full */ + if (stack_full(stack)) return 0; + + /* Assigning data to the stack */ + if (data) memcpy(at(stack->top), data, stack->dsize); + + /* Update stack status */ + stack->top++; + + return 1; +} + +int stack_pop(stack_t stack, void* data) +{ + /* Input value validity check */ + if (!stack) return 0; + + /* Check if the stack is full */ + if (stack_empty(stack)) return 0; + + /* Update stack status */ + stack->top--; + + /* Assigning data from the stack */ + if (data) memcpy(data, at(stack->top), stack->dsize); + + return 1; +} + +void stack_clear(stack_t stack) +{ + /* Input value validity check */ + if (!stack) return; + + /* Reset stack status */ + stack->top = 0; +} + +int stack_index(stack_t stack, int index) +{ + /* Input value validity check */ + if (!stack) return -1; + if (index < 0 || index >= stack->top) return -1; + + /* Starting from the bottom, calculate the data index */ + return index; +} + +void* stack_data(stack_t stack, int index) +{ + /* Input value validity check */ + if (!stack) return NULL; + if (index < 0 || index >= stack->top) return NULL; + + /* Return array address based on index */ + return (void*)at(index); +} + +int stack_size(stack_t stack) +{ + /* Input value validity check */ + if (!stack) return 0; + + /* Return stack size */ + return stack->top; +} + +int stack_capacity(stack_t stack) +{ + /* Input value validity check */ + if (!stack) return 0; + + /* Return stack capacity */ + return stack->capacity; +} + +int stack_dsize(stack_t stack) +{ + /* Input value validity check */ + if (!stack) return 0; + + /* Return stack data size */ + return stack->dsize; +} + +int stack_empty(stack_t stack) +{ + /* Input value validity check */ + if (!stack) return 1; + + /* Determine if size is 0 */ + return (stack->top == 0) ? 1 : 0; +} + +int stack_full(stack_t stack) +{ + /* Input value validity check */ + if (!stack) return 0; + + /* Determine if size is capacity */ + return (stack->top == stack->capacity) ? 1 : 0; +} \ No newline at end of file diff --git a/source/03_container/stack.h b/source/03_container/stack.h new file mode 100644 index 0000000..a28059c --- /dev/null +++ b/source/03_container/stack.h @@ -0,0 +1,142 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file stack.h + * \unit stack + * \brief This is a C language stack + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __stack_H +#define __stack_H + +#include + +/* version infomation */ + +#define STACK_V_MAJOR 1 +#define STACK_V_MINOR 0 +#define STACK_V_PATCH 0 + +/* stack type definition, hiding structural members, not for external use */ + +typedef struct STACK *stack_t; + +/** + * \brief create a stack. + * \param[in] dsize: size of stack data + * \param[in] capacity: capacity of stack + * \param[in] base: allocated array or pass in `NULL` to dynamically allocate space + * \return stack handler or NULL fail + */ +stack_t stack_create(int dsize, int capacity, void *base); + +/** + * \brief delete a stack. + * \param[in] stack: stack handler + * \return none + */ +void stack_delete(stack_t stack); + +/** + * \brief push data into the stack. + * \param[in] stack: address of stack + * \param[in] data: the address of data + * \return 1 success or 0 fail + */ +int stack_push(stack_t stack, void* data); + +/** + * \brief pop data from the stack. + * \param[in] stack: address of stack + * \param[out] data: the address of data + * \return 1 success or 0 fail + */ +int stack_pop(stack_t stack, void* data); + +/** + * \brief clear stack. + * \param[in] stack: address of stack + * \return none + */ +void stack_clear(stack_t stack); + +/** + * \brief get stack index. + * \param[in] stack: address of stack + * \param[in] index: index of stack + * \return index of stack buffer or negative fail + */ +int stack_index(stack_t stack, int index); + +/** + * \brief get data address of stack. + * \param[in] stack: stack handler + * \param[in] index: index + * \return address of stack data or NULL fail + */ +void* stack_data(stack_t stack, int index); + +/** + * \brief get size of stack. + * \param[in] stack: stack handler + * \return size of stack + */ +int stack_size(stack_t stack); + +/** + * \brief get capacity of stack. + * \param[in] stack: stack handler + * \return capacity of stack + */ +int stack_capacity(stack_t stack); + +/** + * \brief get data size of stack. + * \param[in] stack: stack handler + * \return data size of stack + */ +int stack_dsize(stack_t stack); + +/** + * \brief check if empty. + * \param[in] stack: stack handler + * \return 1 empty or 0 not empty + */ +int stack_empty(stack_t stack); + +/** + * \brief check if full. + * \param[in] stack: stack handler + * \return 1 full or 0 not full + */ +int stack_full(stack_t stack); + +/** + * \brief A simple method for `stack_create`. + * \param[in] type: data type + * \param[in] capacity: capacity of stack + * \return stack handler or NULL fail + */ +#define stack(type, capacity) stack_create(sizeof(type), (capacity), NULL) + +/** + * \brief A simple method for `stack_delete`. + * \param[in] stack: stack handler + * \return none + */ +#define _stack(stack) do{stack_delete(stack);(stack)=NULL;}while(0) + +/** + * \brief Random access method for stack data. + * \param[in] stack: stack handler + * \param[in] type: data type + * \param[in] i: index starting from stack header + * \return Reference to stack data + */ +#define stack_at(stack, type, i) (*(type *)stack_data((stack), (i))) + +#endif diff --git a/source/03_container/str.c b/source/03_container/str.c new file mode 100644 index 0000000..ef6fc3c --- /dev/null +++ b/source/03_container/str.c @@ -0,0 +1,840 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file str.c + * \unit str + * \brief This is a general C language string container module + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "str.h" +#include +#include +#include + +/* str define */ +typedef struct STR +{ + char ident; /**< ident of str */ + char* base; /**< base address of string */ + int length; /**< length of str */ + int capacity; /**< capacity of str */ +} STR; + +/* str identification mark */ +#define ident() (-1) + +/* tool */ +#define up_multiple(x, mul) ((x)+((mul)-((x)-1)%(mul))-1) +#define is_digit(c) ((c)>='0'&&(c)<='9') +#define MAX(x, y) ((x)>(y)?(x):(y)) + +/* error space */ +static char error = 0; + +/* str info define */ +typedef struct +{ + char *base; /**< base address of string */ + int length; /**< length of str */ +} str_info_t; + +/** + * \brief calculate capacity according to gradient + * \param[in] size: size + * \return capacity + */ +static int gradient_capacity(int size) +{ + int capacity = 1; + if (size <= 1) return 1; + while (capacity < size) capacity <<= 1; + capacity >>= 1; + if (capacity < 4) capacity = capacity << 1; + else if (capacity < 16) capacity = up_multiple(size, capacity >> 1); + else if (capacity < 256) capacity = up_multiple(size, capacity >> 2); + else capacity = up_multiple(size, 64); + return capacity; +} + +/** + * \brief get string information + * \param[in] string: string, type can be array string or str + * \return information of string + */ +static str_info_t string_info(void *string) +{ + str_info_t info; + + if (((char *)string)[0] == 0) /* empty array string */ + { + info.base = (char *)string; + info.length = 0; + } + else if (((str_t)string)->ident == ident()) /* str type */ + { + info.base = ((str_t)string)->base; + info.length = ((str_t)string)->length; + } + else /* array string type */ + { + info.base = (char *)string; + info.length = strlen((char *)string); + } + + return info; +} + +/** + * \brief alter the capacity of the str + * \param[in] str: str handler + * \param[in] length: length of str + * \return new capacity of str + */ +static int str_alter_capacity(str_t str, int length) +{ + int capacity = 0; + char *base = NULL; + + /* check whether the capacity changes and need to adjust the capacity space */ + capacity = gradient_capacity(length); + if (str->capacity != 0 && str->capacity == capacity) return 1; + if (!str->base) /* allocate new space */ + { + str->base = (char *)malloc(capacity + 1); + if (!str->base) return 0; + } + else /* reallocate space */ + { + base = (char *)realloc(str->base, capacity + 1); + if (!base) return 0; + str->base = base; + } + str->capacity = capacity; + return 1; +} + +str_t str_create(void *string) +{ + str_t str; + + /* Input value validity check */ + if (!string) return NULL; + + /* Allocate memory for the STR structure */ + str = (str_t)malloc(sizeof(STR)); + if (!str) return NULL; + + /* Initialize structural parameters */ + str->ident = ident(); + str->base = NULL; + str->length = 0; + str->capacity = 0; + + /* Assign initial value */ + if (!str_assign(str, string)) + { + free(str); + return NULL; + } + + return str; +} + +void str_delete(str_t str) +{ + /* Input value validity check */ + if (!str) return; + + /* If it is not a constant array, release the allocated space */ + if (str->base) free(str->base); + + /* Free str structure */ + free(str); +} + +str_t str_assign(str_t str, void *string) +{ + str_info_t info; + + /* Input value validity check */ + if (!str) return NULL; + if (!string) return NULL; + + /* Get basic information about the incoming string */ + info = string_info(string); + + /* Allocate space to the string */ + if (!str_alter_capacity(str, info.length)) return NULL; + + /* Assignment string */ + strcpy(str->base, info.base); + + /* Update str status */ + str->length = info.length; + + return str; +} + +char* str_data(str_t str, int pos) +{ + /* Input value validity check */ + if (!str || pos < 0 || pos >= str->length) + { + error = 0; /* reset error area */ + return &error; + } + + /* Return the base address of the string storage */ + return &str->base[pos]; +} + +const char* str_c_str(str_t str) +{ + return (const char *)str_data(str, 0); +} + +int str_length(str_t str) +{ + /* Input value validity check */ + if (!str) return 0; + + /* Return the length of the string */ + return str->length; +} + +int str_capacity(str_t str) +{ + /* Input value validity check */ + if (!str) return 0; + + /* Return the capacity of the string */ + return str->capacity; +} + +int str_empty(str_t str) +{ + /* Input value validity check */ + if (!str) return 1; + + /* Judging whether it is empty based on its length */ + return str->length == 0 ? 1 : 0; +} + +void str_clear(str_t str) +{ + /* Directly assign a value to an empty string */ + str_assign(str, ""); +} + +str_t str_insert(str_t str, int pos, void *string) +{ + return str_replace(str, pos, 0, string); +} + +str_t str_erase(str_t str, int pos, int len) +{ + return str_replace(str, pos, len, ""); +} + +str_t str_append_series(str_t str, ...) +{ + va_list args; + void* s = NULL; + + /* Input value validity check */ + if (!str) return NULL; + + va_start(args, str); + + /* Loop to obtain indefinite parameters */ + s = va_arg(args, void*); + while (s) + { + /* insert at the end */ + if (!str_insert(str, str->length, s)) + { + va_end(args); + return NULL; + } + s = va_arg(args, void*); + } + + va_end(args); + + return str; +} + +int str_push_back(str_t str, char c) +{ + char temp[2] = {0}; + + /* Input value validity check */ + if (!str) return 0; + + /* Combine characters into a short string */ + temp[0] = c; + + /* Append characters to the end */ + if (!str_append_series(str, temp, NULL)) return 0; + + return 1; +} + +char str_pop_back(str_t str) +{ + char c = 0; + + /* Input value validity check */ + if (!str) return 0; + + /* Check if the string is not empty */ + if (str->length <= 0) return 0; + + /* Record Tail */ + c = str->base[str->length - 1]; + + /* Remove the tail */ + if (!str_erase(str, str->length - 1, 1)) return 0; + + return c; +} + +int str_compare(str_t str, void *string) +{ + str_info_t info; + int ret = 0; + + /* Input value validity check */ + if (!str) return -1; + if (!string) return 1; + + /* Get basic information about the incoming string */ + info = string_info(string); + + /* Compare two strings */ + ret = strcmp(str->base, info.base); + + /* Update return value */ + if (ret > 0) ret = 1; + else if (ret < 0) ret = -1; + + return ret; +} + +str_t str_substr(str_t str, int pos, int len) +{ + str_t copy = NULL; + + /* Input value validity check */ + if (!str) return NULL; + if (pos < 0 || pos >= str->length) return NULL; + if (len <= 0) return NULL; + + /* Create an empty string */ + copy = str_create(""); + if (!copy) return NULL; + + /* Update the copied length */ + if (len > str->length - pos) len = str->length - pos; + + /* Allocate space to the string */ + if (!str_alter_capacity(copy, len)) + { + str_delete(copy); + return NULL; + } + + /* Assignment string */ + memcpy(copy->base, &str->base[pos], len); + copy->base[len] = 0; + copy->length = len; + + return copy; +} + +int str_find(str_t str, void *string, int pos) +{ + str_info_t info; + char *find = NULL; + int ret = 0; + + /* Input value validity check */ + if (!str) return str_npos; + if (!string) return str_npos; + if (pos < 0 || pos >= str->length) return str_npos; + + /* Get basic information about the incoming string */ + info = string_info(string); + + /* The length of the substring exceeds the limit, return directly */ + if (info.length > str->length) return str_npos; + + /* Find substrings */ + find = strstr(&str->base[pos], info.base); + if (!find) return str_npos; + + /* Calculate find location */ + ret = find - str->base; + + return ret; +} + +/** + * \brief match substrings from the tail + * \param[in] *haystack: parent string + * \param[in] tail_index: specifies the end index of the parent string character array + * \param[in] *needle: child string + * \return matching segment in the parent string or NULL: fail + */ +static char* strrstr(char *haystack, int end_index, char *needle) +{ + char *find = NULL; + int index = 0; + int i = 0; + int hay_len = end_index; + int need_len = strlen(needle); + for (index = hay_len; index >= 0; index--) + { + if (haystack[index] == needle[0]) + { + find = &haystack[index]; + for (i = 0; i < need_len; i++) + { + if (find[i] != needle[i]) break; + } + if (i == need_len) return find; + } + } + + return NULL; +} + +int str_rfind(str_t str, void *string, int pos) +{ + str_info_t info; + char *find = NULL; + int ret = 0; + + /* Input value validity check */ + if (!str) return str_npos; + if (!string) return str_npos; + if (pos < 0 || pos >= str->length) return str_npos; + + /* Get basic information about the incoming string */ + info = string_info(string); + + /* The length of the substring exceeds the limit, return directly */ + if (info.length > str->length) return str_npos; + + /* Find substrings */ + find = strrstr(str->base, pos, info.base); + if (!find) return str_npos; + + /* Calculate find location */ + ret = find - str->base; + + return ret; +} + +/** str_find_of + * \brief find function of "of" type + * \param[in] str: str handler + * \param[in] *string: string, type can be array string or str + * \param[in] pos: the position to start find + * \param[in] from: 0: first, 1: last + * \param[in] of: 0: not of, 1: of + * \return position of the specified character or str_npos: fail + */ +static int str_find_of(str_t str, void *string, int pos, int from, int of) +{ + str_info_t info; + int i = 0; + if (!str) return str_npos; + if (!string) return str_npos; + if (pos < 0 || pos >= str->length) return str_npos; + info = string_info(string); + if (info.length <= 0) return str_npos; + + /* matching */ + if (from == 0) /* first */ + { + for (i = pos; i < str->length; i++) + { + if ((of == 1 && strchr(info.base, str->base[i])) || + (of == 0 && !strchr(info.base, str->base[i]))) + { + return i; + } + } + } + else /* last */ + { + for (i = pos; i >= 0; i--) + { + if ((of == 1 && strchr(info.base, str->base[i])) || + (of == 0 && !strchr(info.base, str->base[i]))) + { + return i; + } + } + } + + return str_npos; +} + +int str_find_first_of(str_t str, void *string, int pos) +{ + return str_find_of(str, string, pos, 0, 1); +} + +int str_find_first_not_of(str_t str, void *string, int pos) +{ + return str_find_of(str, string, pos, 0, 0); +} + +int str_find_last_of(str_t str, void *string, int pos) +{ + return str_find_of(str, string, pos, 1, 1); +} + +int str_find_last_not_of(str_t str, void *string, int pos) +{ + return str_find_of(str, string, pos, 1, 0); +} + +str_t str_reverse(str_t str, int begin, int end) +{ + int i = 0; + char c = 0; + + /* Input value validity check */ + if (!str) return NULL; + if (begin < 0) begin = 0; + if (end >= str->length) end = str->length - 1; + if (begin >= end) return NULL; + + /* Reverse characters with symmetrical positions in a loop */ + for (i = 0; i <= (end - begin) / 2; i++) + { + c = str->base[begin + i]; + str->base[begin + i] = str->base[end - i]; + str->base[end - i] = c; + } + + return str; +} + +str_t str_replace(str_t str, int pos, int len, void *string) +{ + str_info_t info; + char *overlap = NULL; + + /* Input value validity check */ + if (!str) return NULL; + if (!string) return NULL; + if (pos < 0 || pos > str->length) return NULL; + + /* Update the copied length */ + if (len > str->length - pos) len = str->length - pos; + + /* Get basic information about the incoming string */ + info = string_info(string); + + /* Check if addresses overlap */ + if (str->base <= info.base && info.base <= str->base + str->length && pos < str->length) + { + /* Allocate temporary space */ + overlap = (char *)malloc(info.length + 1); + if (!overlap) return NULL; + strcpy(overlap, info.base); + info.base = overlap; + } + + if (info.length > len) /* lengthen */ + { + if (str_alter_capacity(str, str->length + (info.length - len)) == 0) + { + if (overlap) free(overlap); + return NULL; + } + memmove(&str->base[pos + info.length], &str->base[pos + len], str->length - (pos + len)); + memcpy(&str->base[pos], info.base, info.length); + } + else if (info.length < len) /* shorten */ + { + memmove(&str->base[pos + info.length], &str->base[pos + len], str->length - (pos + len)); + memcpy(&str->base[pos], info.base, info.length); + str_alter_capacity(str, str->length + (info.length - len)); + } + else + { + memcpy(&str->base[pos], info.base, info.length); + } + + str->length += (info.length - len); + str->base[str->length] = 0; + + /* Free temporary space */ + if (overlap) free(overlap); + + return str; +} + +void str_swap(str_t str, str_t swap) +{ + STR temp; + + /* Input value validity check */ + if (!str) return; + if (!swap) return; + + /* Swap string structures */ + temp = *str; + *str = *swap; + *swap = temp; +} + +int str_copy(str_t str, int pos, int len, char *buf) +{ + /* Input value validity check */ + if (!str) return 0; + if (pos < 0 || pos > str->length) return 0; + if (len > str->length - pos) len = str->length - pos; + + /* Copy out a string */ + memcpy(buf, &str->base[pos], len); + + return len; +} + +/** + * \brief get digit from string + * \param[in] s: char string + * \param[out] out_digit: out digit + * \return length of digit + */ +static int get_digit(char *s, int *out_digit) +{ + int digit = 0; + int len = 0; + while (is_digit(*s)) + { + digit = digit * 10 + *(s++) - '0'; + len++; + } + *out_digit = digit; + return len; +} + +str_t str_format(str_t str, char *format, ...) +{ + va_list args; + int len = 0; /* length of sub string */ + char *s = NULL; /* temp string */ + str_info_t info; /* str info */ + double dbl = 0.0; /* double precision floating point */ + char *begin = NULL; /* begin of format */ + char qualifier = 0; /* conversion qualifier for integer as 'h','l','L' */ + int width = 0; /* transition field width */ + int precision = 0; /* minimum digits of integers and maximum digits of strings */ + char tfmt[16] = "%"; /* temporary format */ + + /* Input value validity check */ + if (!str) return NULL; + + /* Reset str */ + str_assign(str, ""); + + va_start(args, format); + + /* Traverse format string */ + for (; *format; format++) + { + if (*format != '%') + { + if (!begin) begin = format; + continue; + } + if (begin) + { + len = format - begin; + if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY; + while (len--) str->base[str->length++] = *begin++; + begin = NULL; + } + begin = format; + while (1) + { + /* skips the first '%' also */ + format++; + if ((*format != '0') && + (*format != ' ') && + (*format != '+') && + (*format != '-') && + (*format != '#')) break; + } + + /* get field width */ + width = -1; + if (is_digit(*format)) + { + format += get_digit(format, &width); + } + else if (*format == '*') + { + format++; + width = va_arg(args, int); + if (width < 0) width = -width; + } + + /* get the precision */ + precision = -1; + if (*format == '.') + { + format++; + if (is_digit(*format)) format += get_digit(format, &precision); + else if (*format == '*') + { + format++; + precision = va_arg(args, int); + } + if (precision < 0) precision = 0; + } + + /* get the conversion qualifier */ + qualifier = 0; + if (*format == 'h' || *format == 'l' || *format == 'L') + { + qualifier = *format; + format++; + if (qualifier == 'l' && *format == 'l') + { + qualifier = 'L'; + format++; + } + } + + /* format distribution */ + switch (*format) + { + case 'c': + len = MAX(width, 1); + if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY; + memcpy(&tfmt[1], begin + 1, format - begin); + tfmt[format - begin + 1] = 0; + begin = NULL; + len = sprintf(&str->base[str->length], tfmt, va_arg(args, int)); + if (len > 0) str->length += len; + break; + case 's': + info = string_info(va_arg(args, void*)); + s = info.base; + len = info.length; + if (precision > 0 && len > precision) len = precision; + len = MAX(width, len); + if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY; + memcpy(&tfmt[1], begin + 1, format - begin); + tfmt[format - begin + 1] = 0; + begin = NULL; + len = sprintf(&str->base[str->length], tfmt, s); + if (len > 0) str->length += len; + break; + case 'p': + if (width == -1) width = sizeof(void*) * 2; /* if no width is specified, the default width will be used */ + len = width; + if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY; + memcpy(&tfmt[1], begin + 1, format - begin); + tfmt[format - begin + 1] = 0; + begin = NULL; + len = sprintf(&str->base[str->length], tfmt, (long)va_arg(args, void*)); + if (len > 0) str->length += len; + break; + /* float number formats */ + case 'a': + case 'A': + case 'e': + case 'E': + case 'g': + case 'G': + case 'f': + dbl = va_arg(args, double); + if (*format == 'f') + { + /* get the gradient length of the integer part of the floating point number */ + if (fabs(dbl) < powl(10, 8)) len = 8; + else if (fabs(dbl) < powl(10, 16)) len = 16; + else if (fabs(dbl) < powl(10, 32)) len = 32; + else if (fabs(dbl) < powl(10, 64)) len = 64; + else if (fabs(dbl) < powl(10, 128)) len = 128; + else if (fabs(dbl) < powl(10, 256)) len = 256; + else len = 309; + } + else len = 24; + + len += ((precision==-1?6:precision) + 1); + len = MAX(width, len); + if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY; + memcpy(&tfmt[1], begin + 1, format - begin); + tfmt[format - begin + 1] = 0; + begin = NULL; + len = sprintf(&str->base[str->length], tfmt, dbl); + if (len > 0) str->length += len; + break; + /* integer number formats */ + case 'o': + case 'X': + case 'x': + case 'd': + case 'i': + case 'u': + len = qualifier=='L'?32:16; + len = MAX(width, len); + if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY; + memcpy(&tfmt[1], begin + 1, format - begin); + tfmt[format - begin + 1] = 0; + begin = NULL; + /* format conversion */ + if (qualifier == 'L') len = sprintf(&str->base[str->length], tfmt, va_arg(args, long long)); + else if (qualifier == 'l') len = sprintf(&str->base[str->length], tfmt, va_arg(args, int)); + else if (qualifier == 'h') len = sprintf(&str->base[str->length], tfmt, (short)va_arg(args, int)); + else len = sprintf(&str->base[str->length], tfmt, (int)va_arg(args, int)); + if (len > 0) str->length += len; + break; + case '%': + str_push_back(str, '%'); /* push % */ + begin = NULL; + break; + default: + str_push_back(str, '%'); /* push % */ + begin = NULL; + if (*format) str_push_back(str, *format); + else format--; + break; + } + } + + /* copy tail string to str */ + if (begin) + { + len = format - begin; + if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY; + while (len--) str->base[str->length++] = *begin++; + } + + if (!str_alter_capacity(str, str->length)) goto FAIL_CAPACITY; + + str->base[str->length] = '\0'; + + va_end(args); + + return str; + +FAIL_CAPACITY: + str->base[str->length] = '\0'; + va_end(args); + return NULL; +} diff --git a/source/03_container/str.h b/source/03_container/str.h new file mode 100644 index 0000000..8da3205 --- /dev/null +++ b/source/03_container/str.h @@ -0,0 +1,292 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file str.h + * \unit str + * \brief This is a general C language string container module + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __str_H +#define __str_H + +#include +#include +#include + +/* version infomation */ + +#define STR_V_MAJOR 1 +#define STR_V_MINOR 0 +#define STR_V_PATCH 0 + +/* str type definition, hiding structural members, not for external use */ + +typedef struct STR *str_t; + +/** + * \brief create str + * \param[in] string: string, type can be array string or str + * \return handler of new str + */ +str_t str_create(void *string); + +/** + * \brief delete str + * \param[in] str: str handler + * \return none + */ +void str_delete(str_t str); + +/** + * \brief assign value to str + * \param[in] str: str handler + * \param[in] string: string, type can be array string or str + * \return str itself + */ +str_t str_assign(str_t str, void *string); + +/** + * \brief get the data actually stored in str + * \param[in] str: str handler + * \param[in] pos: position of str + * \return address of string data + */ +char* str_data(str_t str, int pos); + +/** + * \brief get the string of str + * \param[in] str: str handler + * \return address of str string + */ +const char* str_c_str(str_t str); + +/** + * \brief get the length of str + * \param[in] str: str handler + * \return length of str + */ +int str_length(str_t str); + +/** + * \brief get the capacity of the space for storing str data + * \param[in] str: str handler + * \return capacity of str + */ +int str_capacity(str_t str); + +/** + * \brief judge whether the str is empty + * \param[in] str: str handler + * \return 1: empty or 0: not empty + */ +int str_empty(str_t str); + +/** + * \brief clear str + * \param[in] str: str handler + * \return none + */ +void str_clear(str_t str); + +/** + * \brief Insert string to str + * \param[in] str: str handler + * \param[in] pos: insertion position + * \param[in] string: string, type can be array string or str + * \return str itself + */ +str_t str_insert(str_t str, int pos, void *string); + +/** + * \brief Erase string in str + * \param[in] str: str handler + * \param[in] pos: start position of erasure + * \param[in] len: erased length + * \return str itself + */ +str_t str_erase(str_t str, int pos, int len); + +/** + * \brief insert a character at the end of str + * \param[in] str: str handler + * \param[in] c: char + * \return 1: success or 0: fail + */ +int str_push_back(str_t str, char c); + +/** + * \brief erase a character at the end of str + * \param[in] str: str handler + * \return char of erase + */ +char str_pop_back(str_t str); + +/** + * \brief append series string to the end of str, it is recommended to directly use the macro definition method of 'str_append()' + * \param[in] str: str handler + * \param[in] ...: strings, at least one, type can be array string or str + * \return str itself + */ +str_t str_append_series(str_t str, ...); + +/** + * \brief compare two strings + * \param[in] str: str handler + * \param[in] string: string, type can be array string or str + * \return 0: equal, -1: str < string, 1: str > string + */ +int str_compare(str_t str, void *string); + +/** + * \brief create substring + * \param[in] str: str handler + * \param[in] string: string, type can be array string or str + * \return new substring + */ +str_t str_substr(str_t str, int pos, int len); + +/** + * \brief find the specified string in str + * \param[in] str: str handler + * \param[in] string: string, type can be array string or str + * \param[in] pos: the position to start find + * \return find the first occurrence position of the specified string or str_npos: fail + */ +int str_find(str_t str, void *string, int pos); + +/** + * \brief find the specified string in str from tail + * \param[in] str: str handler + * \param[in] string: string, type can be array string or str + * \param[in] pos: the position to start find + * \return find the first occurrence position of the specified string or str_npos: fail + */ +int str_rfind(str_t str, void *string, int pos); + +/** + * \brief the first find contains the characters in the specified string + * \param[in] str: str handler + * \param[in] string: string, type can be array string or str + * \param[in] pos: the position to start find + * \return position of the specified character or str_npos: fail + */ +int str_find_first_of(str_t str, void *string, int pos); + +/** + * \brief the first find contains the characters not in the specified string + * \param[in] str: str handler + * \param[in] string: string, type can be array string or str + * \param[in] pos: the position to start find + * \return position of the specified character or str_npos: fail + */ +int str_find_first_not_of(str_t str, void *string, int pos); + +/** + * \brief the last find contains the characters in the specified string + * \param[in] str: str handler + * \param[in] string: string, type can be array string or str + * \param[in] pos: the position to start find + * \return position of the specified character or str_npos: fail + */ +int str_find_last_of(str_t str, void *string, int pos); + +/** + * \brief the last find contains the characters not in the specified string + * \param[in] str: str handler + * \param[in] string: string, type can be array string or str + * \param[in] pos: the position to start find + * \return position of the specified character or str_npos: fail + */ +int str_find_last_not_of(str_t str, void *string, int pos); + +/** + * \brief format str + * \param[in] str: str handler + * \param[in] begin: begin position + * \param[in] end: end position + * \return str itself + */ +str_t str_reverse(str_t str, int begin, int end); + +/** + * \brief str replace + * \param[in] str: str handler + * \param[in] pos: start position of be replace + * \param[in] len: erased length + * \param[in] string: string, type can be array string or str + * \return str itself + */ +str_t str_replace(str_t str, int pos, int len, void *string); + +/** + * \brief swap two str + * \param[in] str: str handler + * \param[in] swap: str handler of swap + * \return none + */ +void str_swap(str_t str, str_t swap); + +/** + * \brief swap two str + * \param[in] str: str handler + * \param[in] pos: start position of be copy + * \param[in] len: copy length + * \param[out] buf: array copied + * \return number of characters actually copied + */ +int str_copy(str_t str, int pos, int len, char *buf); + +/** + * \brief format str + * \param[in] str: str handler + * \param[in] format: format + * \param[in] ...: indefinite parameter + * \return str itself + */ +str_t str_format(str_t str, char *format, ...); + +/** + * \brief A simple method for `str_create`. + * \param[in] str: string, type can be array string or str + * \return handler of new str + */ +#define str(str) str_create(str) + +/** + * \brief A simple method for `str_delete`. + * \param[in] str: str handler + * \return none + */ +#define _str(str) do{str_delete(str);(str)=NULL;}while(0) + +/** + * \brief append series string to the end of str. + * \param[in] str: str handler + * \param[in] ...: strings, at least one, type can be array string or str + * \return str itself + */ +#define str_append(str, ...) str_append_series((str), ##__VA_ARGS__, NULL) + +/** + * \brief Random access method for str char. + * \param[in] str: str handler + * \param[in] i: index of str + * \return Reference to str char + */ +#define str_at(str, i) (*str_data(str, i)) + +/** + * \brief multiplexing `str_c_str()`, get the string of str + * \param[in] str: str handler + * \return reference to the back char + */ +#define _S(str) ((const char*)str_c_str(str)) + +/* non-existent position */ +#define str_npos INT_MAX + +#endif diff --git a/source/03_container/tree.c b/source/03_container/tree.c new file mode 100644 index 0000000..cd1a942 --- /dev/null +++ b/source/03_container/tree.c @@ -0,0 +1,424 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file tree.h + * \unit tree + * \brief This is a C language tree, general data structure + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "tree.h" +#include +#include + +/* type of tree */ +typedef struct TREE +{ + tree_t parent; /**< parent node */ + tree_t *child; /**< child tree */ + int csize; /**< size of child */ + void *data; /**< address of data */ + int dsize; /**< data size */ + void *attribute; /**< address of attribute */ + int asize; /**< attribute size */ +} TREE; + +tree_t tree_create(void) +{ + tree_t tree; + + /* Allocate memory for the TREE structure */ + tree = (tree_t)malloc(sizeof(TREE)); + if (!tree) return NULL; + + /* Initialize structural parameters */ + memset(tree, 0, sizeof(TREE)); + + return tree; +} + +void tree_delete(tree_t tree, void (*func)(tree_t tree)) +{ + int i; + + /* Input value validity check */ + if (!tree) return; + + /* Recursively delete each child tree */ + for (i = 0; i < tree->csize; i++) tree_delete(tree->child[i], func); + + /* Additional execution function */ + if (func) func(tree); + + /* Free childs */ + if (tree->child) free(tree->child); + + /* Free data */ + if (tree->data) free(tree->data); + + /* Free attribute */ + if (tree->attribute) free(tree->attribute); + + /* Free tree structure */ + free(tree); +} + +int tree_insert(tree_t tree, int index) +{ + tree_t *array; + + /* Input value validity check */ + if (!tree) return 0; + if (index < 0 || index > tree->csize) return 0; + + /* Readjust the number of child */ + array = realloc(tree->child, (tree->csize + 1) * sizeof(tree_t)); + if (!array) return 0; + + /* Update child array */ + tree->child = array; + + /* Move the childs after the index back */ + if (index < tree->csize) memmove(&array[index + 1], &array[index], sizeof(tree_t) * (tree->csize - index)); + + /* Reset child */ + array[index] = NULL; + + /* Update queue status */ + tree->csize++; + + return 1; +} + +int tree_erase(tree_t tree, int index) +{ + tree_t *array, temp; + + /* Input value validity check */ + if (!tree) return 0; + if (index < 0 || index >= tree->csize) return 0; + if (tree->child[index]) return 0; + + /* Only one child, free it directly */ + if (tree->csize == 1) + { + free(tree->child); + tree->child = NULL; + tree->csize = 0; + return 1; + } + + /* Record the last child first, then reduce the space to prevent crossing the boundary */ + temp = tree->child[tree->csize - 1]; + array = realloc(tree->child, tree->csize - 1); + if (!array) return 0; + + /* Update child array */ + tree->child = array; + + /* Move the childs after the index front */ + memmove(&array[index], &array[index + 1], sizeof(tree_t) * (tree->csize - index - 2)); + + /* Make up for the last child as well */ + array[tree->csize - 2] = temp; + + /* Update queue status */ + tree->csize--; + + return 1; +} + +int tree_attach(tree_t tree, int index, tree_t attach) +{ + /* Input value validity check */ + if (!tree) return 0; + if (index < 0 || index >= tree->csize) return 0; + if (!attach) return 0; + if (attach->parent) return 0; + + /* Linking parent and child */ + tree->child[index] = attach; + attach->parent = tree; + + return 1; +} + +tree_t tree_detach(tree_t tree, int index) +{ + tree_t detach; + + /* Input value validity check */ + if (!tree) return NULL; + if (index < 0 || index >= tree->csize) return NULL; + if (!tree->child[index]) return NULL; + + /* Record the child that needs to be detached */ + detach = tree->child[index]; + + /* Leave the position blank */ + tree->child[index] = NULL; + + /* The child parent of the datach needs to be left blank */ + detach->parent = NULL; + + return detach; +} + +tree_t tree_parent(tree_t tree) +{ + /* Input value validity check */ + if (!tree) return NULL; + + /* Return parent */ + return tree->parent; +} + +tree_t tree_child(tree_t tree, int index) +{ + /* Input value validity check */ + if (!tree) return NULL; + if (index < 0 || index >= tree->csize) return NULL; + + /* Return child */ + return tree->child[index]; +} + +int tree_csize(tree_t tree) +{ + /* Input value validity check */ + if (!tree) return 0; + + /* Return the child size */ + return tree->csize; +} + +const void* tree_data(tree_t tree) +{ + /* Input value validity check */ + if (!tree) return NULL; + + /* Return tree data */ + return tree->data; +} + +int tree_dsize(tree_t tree) +{ + /* Input value validity check */ + if (!tree) return 0; + + /* Return tree data size */ + return tree->dsize; +} + +const void* tree_attribute(tree_t tree) +{ + /* Input value validity check */ + if (!tree) return NULL; + + /* Return tree attribute */ + return tree->attribute; +} + +int tree_asize(tree_t tree) +{ + /* Input value validity check */ + if (!tree) return 0; + + /* Return tree attribute size */ + return tree->asize; +} + +int tree_size(tree_t tree) +{ + int i, size = 0; + + /* Input value validity check */ + if (!tree) return 0; + + /* Traverse each child and recursively obtain its number of child */ + for (i = 0; i < tree->csize; i++) size += tree_size(tree->child[i]); + + /* Update size */ + return size + tree->csize; +} + +int tree_depth(tree_t tree) +{ + int i, depth = 0, temp = 0; + + /* Input value validity check */ + if (!tree) return 0; + + /* Traverse each child and recursively obtain its depth of child */ + for (i = 0; i < tree->csize; i++) + { + temp = tree_depth(tree->child[i]); + + /* Record maximum depth */ + if (temp > depth) depth = temp; + } + + /* Update depth */ + return depth + 1; +} + +tree_t tree_to_valist(tree_t tree, int index, ...) +{ + tree_t node = tree; + int i = index; + va_list args; + + /* Input value validity check */ + if (!tree) return NULL; + + va_start(args, index); + + /* Loop to obtain the children of each level */ + while (node && i >= 0) + { + if (i >= node->csize) return NULL; + node = node->child[i]; + i = va_arg(args, int); + } + + va_end(args); + + return node; +} + +int tree_set_data(tree_t tree, void* data, int size) +{ + void* d = NULL; + + /* Input value validity check */ + if (!tree) return 0; + if (size < 0) return 0; + + /* If the incoming data size is 0, set the air sensitive data directly */ + if (size == 0) + { + if (tree->data) free(tree->data); + tree->data = NULL; + tree->dsize = 0; + return 1; + } + + /* If the data size is inconsistent, update the data storage space */ + if (size != tree->dsize) + { + d = realloc(tree->data, size); + if (!d) return 0; + } + tree->data = d; + + /* Data assignment */ + if (data) memcpy(tree->data, data, size); + + /* Update data size */ + tree->dsize = size; + + return 1; +} + +int tree_get_data(tree_t tree, void* data, int size) +{ + /* Input value validity check */ + if (!tree) return 0; + if (!data) return 0; + if (size < tree->dsize) return 0; + + /* Data assignment */ + memcpy(data, tree->data, tree->dsize); + + return 1; +} + +int tree_set_attribute(tree_t tree, void* attribute, int size) +{ + void* d = NULL; + + /* Input value validity check */ + if (!tree) return 0; + if (size < 0) return 0; + + /* If the incoming attribute size is 0, set the air sensitive attribute directly */ + if (size == 0) + { + if (tree->attribute) free(tree->attribute); + tree->attribute = NULL; + tree->asize = 0; + return 1; + } + + /* If the attribute size is inconsistent, update the attribute storage space */ + if (size != tree->asize) + { + d = realloc(tree->attribute, size); + if (!d) return 0; + } + tree->attribute = d; + + /* Attribute assignment */ + if (attribute) memcpy(tree->attribute, attribute, size); + + /* Update attribute size */ + tree->asize = size; + + return 1; +} + +int tree_get_attribute(tree_t tree, void* attribute, int size) +{ + /* Input value validity check */ + if (!tree) return 0; + if (!attribute) return 0; + if (size < tree->asize) return 0; + + /* Attribute assignment */ + memcpy(attribute, tree->attribute, tree->asize); + + return 1; +} + +static void expand_tree(tree_t tree, int depth, int limit, char* scope, void (*print)(tree_t tree)) +{ + int i = 0; + if (limit && depth >= limit) return; /* exceeded layer depth limit */ + if (depth > 0) /* skip indent for layer 0 */ + { + for (i = 0; i < depth - 1; i++) { putchar(scope[i]?'|':' '); putchar(' '); putchar(' '); putchar(' '); } + putchar(scope[i]?'|':'\''); + putchar('-'); + if (!tree) { printf("O\r\n"); return; } /* empty child */ + putchar('-'); + putchar('-'); + } + printf("> "); + if (print) print(tree); + printf("\r\n"); + for (i = 0; i < tree->csize; i++) + { + scope[depth] = (i < tree->csize - 1) ? 1 : 0; + expand_tree(tree->child[i], depth + 1, limit, scope, print); + } +} + +static void tree_print_to_depth(tree_t tree, int depth, int limit, void (*print)(tree_t tree)) +{ + char scope[depth]; + memset(scope, 0, depth); + expand_tree(tree, 0, limit, scope, print); +} + +void tree_print(tree_t tree, int depth, void (*print)(tree_t tree)) +{ + /* Input value validity check */ + if (!tree) return; + if (depth < 0) return; + + /* Expand the tree and print it out */ + tree_print_to_depth(tree, tree_depth(tree), depth, print); +} diff --git a/source/03_container/tree.h b/source/03_container/tree.h new file mode 100644 index 0000000..3d7b996 --- /dev/null +++ b/source/03_container/tree.h @@ -0,0 +1,203 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file tree.h + * \unit tree + * \brief This is a C language tree, general data structure + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __tree_H +#define __tree_H + +#include +#include + +/* version infomation */ + +#define TREE_V_MAJOR 1 +#define TREE_V_MINOR 0 +#define TREE_V_PATCH 0 + +/* tree type definition, hiding structural members, not for external use */ + +typedef struct TREE* tree_t; + +/** + * \brief create a null tree. + * \return tree handler or NULL fail + */ +tree_t tree_create(void); + +/** + * \brief delete a tree. + * \param[in] tree: tree handler + * \param[in] func: additional execution function + * \return none + */ +void tree_delete(tree_t tree, void (*func)(tree_t tree)); + +/** + * \brief insert an empty child space at the specified index position. + * \param[in] tree: tree handler + * \param[in] index: index + * \return 1 success or 0 fail + */ +int tree_insert(tree_t tree, int index); + +/** + * \brief Erase the empty child space of the specified index. + * \param[in] tree: tree handler + * \param[in] index: index + * \return 1 success or 0 fail + */ +int tree_erase(tree_t tree, int index); + +/** + * \brief attach an independent tree to a specified index of another tree. + * \param[in] tree: tree handler + * \param[in] index: index + * \param[in] attach: independent tree + * \return 1 success or 0 fail + */ +int tree_attach(tree_t tree, int index, tree_t attach); + +/** + * \brief detach the tree with the specified index. + * \param[in] tree: tree handler + * \param[in] index: index + * \return the detach tree or NULL fail + */ +tree_t tree_detach(tree_t tree, int index); + +/** + * \brief get the parent tree. + * \param[in] tree: tree handler + * \return parent tree or NULL fail + */ +tree_t tree_parent(tree_t tree); + +/** + * \brief get the child tree. + * \param[in] tree: tree handler + * \param[in] index: index + * \return child tree or NULL fail + */ +tree_t tree_child(tree_t tree, int index); + +/** + * \brief get the child size(count of children). + * \param[in] tree: tree handler + * \return child size + */ +int tree_csize(tree_t tree); + +/** + * \brief get the data of the tree. + * \param[in] tree: tree handler + * \return data address or NULL fail + */ +const void* tree_data(tree_t tree); + +/** + * \brief get the data size. + * \param[in] tree: tree handler + * \return data size + */ +int tree_dsize(tree_t tree); + +/** + * \brief get the attribute of the tree. + * \param[in] tree: tree handler + * \return attribute address or NULL fail + */ +const void* tree_attribute(tree_t tree); + +/** + * \brief get the attribute size. + * \param[in] tree: tree handler + * \return attribute size + */ +int tree_asize(tree_t tree); + +/** + * \brief get the size of tree(count of all node). + * \param[in] tree: tree handler + * \return tree size + */ +int tree_size(tree_t tree); + +/** + * \brief get the depth of tree(include itself). + * \param[in] tree: tree handler + * \return tree depth + */ +int tree_depth(tree_t tree); + +/** + * \brief successive indexes get a child tree. + * \param[in] tree: tree handler + * \param[in] index: index + * \param[in] ...: other indexs, continuous index stops until a negative number + * \return child tree or NULL fail + */ +tree_t tree_to_valist(tree_t tree, int index, ...); + +/** + * \brief set data to tree. + * \param[in] tree: tree handler + * \param[in] data: address of data, will overwrite the original data + * \param[in] size: size of data, the original data will be deleted if it's 0 + * \return 1 success or 0 fail + */ +int tree_set_data(tree_t tree, void* data, int size); + +/** + * \brief get the data of tree. + * \param[in] tree: tree handler + * \param[in] data: address of data + * \param[in] size: size of data, need to fit the tree data + * \return 1 success or 0 fail + */ +int tree_get_data(tree_t tree, void* data, int size); + +/** + * \brief set attribute to tree. + * \param[in] tree: tree handler + * \param[in] attribute: address of attribute, will overwrite the original attribute + * \param[in] size: size of attribute, the original attribute will be deleted if it's 0 + * \return 1 success or 0 fail + */ +int tree_set_attribute(tree_t tree, void* attribute, int size); + +/** + * \brief get the attribute of tree. + * \param[in] tree: tree handler + * \param[in] attribute: address of attribute + * \param[in] size: size of attribute, need to fit the tree attribute + * \return 1 success or 0 fail + */ +int tree_get_attribute(tree_t tree, void* attribute, int size); + +/** + * \brief tree expansion print. + * \param[in] tree: tree handler + * \param[in] depth: depth to print, 0 is print all + * \param[in] print: print function + * \return none + */ +void tree_print(tree_t tree, int depth, void (*print)(tree_t tree)); + +/** + * \brief successive indexes get a child tree, a simple method for `tree_to_valist`. + * \param[in] tree: tree handler + * \param[in] i: index + * \param[in] ...: other indexs, continuous index stops until a negative number + * \return child tree or NULL fail + */ +#define tree_to(tree, i, ...) (tree_to_valist(tree,(i),##__VA_ARGS__,-1)) + +#endif diff --git a/source/03_container/vector.c b/source/03_container/vector.c new file mode 100644 index 0000000..8a32607 --- /dev/null +++ b/source/03_container/vector.c @@ -0,0 +1,192 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file vector.c + * \unit vector + * \brief This is a C language vector + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "vector.h" +#include + +/* type of vector */ +typedef struct VECTOR +{ + void* base; /**< base address for storing data */ + int dsize; /**< size of item */ + int size; /**< size of vector */ + int capacity; /**< capacity of vector */ +} VECTOR; + +/* get the smallest 'mul' multiple larger than 'x' */ +#define up_multiple(x, mul) ((x)+((mul)-((x)-1)%(mul))-1) + +/* address of void array */ +#define at(i) (((unsigned char *)(vector->base))+(i)*(vector->dsize)) + +/** + * \brief calculate capacity according to gradient + * \param[in] size: size + * \return capacity + */ +static int gradient_capacity(int size) +{ + int capacity = 1; + if (size <= 1) return 1; + while (capacity < size) capacity <<= 1; + capacity >>= 1; + if (capacity < 4) capacity = capacity << 1; + else if (capacity < 16) capacity = up_multiple(size, capacity >> 1); + else if (capacity < 256) capacity = up_multiple(size, capacity >> 2); + else capacity = up_multiple(size, 64); + return capacity; +} + +vector_t vector_create(int dsize, int size) +{ + vector_t vector; + int capacity; + + /* Input value validity check */ + if (dsize <= 0 || size < 0) return NULL; + + /* Calculate the capacity required for the vector to meet size requirements */ + capacity = gradient_capacity(size); + + /* Allocate memory for the LIST structure */ + vector = (vector_t)malloc(sizeof(VECTOR)); + if (!vector) return NULL; + + /* Allocate memory for the array */ + vector->base = malloc(dsize * capacity); + if (!vector->base) + { + free(vector); + return NULL; + } + + /* Initialize structural parameters */ + vector->dsize = dsize; + vector->size = size; + vector->capacity = capacity; + + return vector; +} + +void vector_delete(vector_t vector) +{ + /* Input value validity check */ + if (!vector) return; + + /* Free array */ + free(vector->base); + + /* Free list structure */ + free(vector); +} + +void* vector_data(vector_t vector, int index) +{ + /* Input value validity check */ + if (!vector) return NULL; + if (index < 0 || index >= vector->size) return NULL; + + /* Return address of index data */ + return (void *)at(index); +} + +int vector_size(vector_t vector) +{ + /* Input value validity check */ + if (!vector) return 0; + + /* Return list size */ + return vector->size; +} + +int vector_capacity(vector_t vector) +{ + /* Input value validity check */ + if (!vector) return 0; + + /* Return list capacity */ + return vector->capacity; +} + +int vector_resize(vector_t vector, int size) +{ + void* base = NULL; + int capacity; + + /* Input value validity check */ + if (!vector) return 0; + if (size < 0) return 0; + + /* Calculate the capacity required for the vector to meet size requirements */ + capacity = gradient_capacity(size); + + /* If the capacity changes, reallocate the capacity */ + if (capacity != vector->capacity) + { + base = realloc(vector->base, capacity * vector->dsize); + if (!base) return 0; + vector->base = base; + vector->capacity = capacity; + } + + /* Update list status */ + vector->size = size; + + return 1; +} + +int vector_insert(vector_t vector, int index, void* data, int num) +{ + int i = 0; + int size; + + /* Input value validity check */ + if (!vector) return 0; + if (index < 0 || index > vector->size) return 0; + if (num <= 0) return 0; + + /* Record the original size */ + size = vector->size; + + /* Reassign space and expand capacity */ + if (!vector_resize(vector, vector->size + num)) return 0; + + /* Move the data at the insertion position back */ + if (index < size) memmove(at(index + num), at(index), vector->dsize * (size - index)); + + /* Assigning data to the list */ + if (data) memcpy(at(index + i), data, vector->dsize * num); + + return 1; +} + +int vector_erase(vector_t vector, int index, int num) +{ + unsigned char *op_ptr = NULL; + + /* Input value validity check */ + if (!vector) return 0; + if (vector->size == 0) return 0; + if (index < 0 || index >= vector->size) return 0; + if (num <= 0) return 0; + + /* Correct the number of erases to be made */ + if (num > vector->size - index) num = vector->size - index; + + /* Move the data at the insertion position front */ + memmove(at(index), at(index + num), vector->dsize * (vector->size - (index + num))); + + /* Reassign space and reduce capacity */ + vector_resize(vector, vector->size - num); + + return 1; +} diff --git a/source/03_container/vector.h b/source/03_container/vector.h new file mode 100644 index 0000000..ca017fc --- /dev/null +++ b/source/03_container/vector.h @@ -0,0 +1,162 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file vector.h + * \unit vector + * \brief This is a C language vector + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __vector_H +#define __vector_H + +#include + +/* version infomation */ + +#define VECTOR_V_MAJOR 1 +#define VECTOR_V_MINOR 0 +#define VECTOR_V_PATCH 0 + +/* vector type definition, hiding structural members, not for external use */ + +typedef struct VECTOR *vector_t; + +/** + * \brief create a vector. + * \param[in] dsize: size of vector data + * \param[in] size: capacity of vector + * \return vector handler or NULL fail + */ +vector_t vector_create(int dsize, int size); + +/** + * \brief delete a vector. + * \param[in] vector: vector handler + * \return none + */ +void vector_delete(vector_t vector); + +/** + * \brief get data address of vector. + * \param[in] vector: vector handler + * \param[in] index: index + * \return address of vector data or NULL fail + */ +void* vector_data(vector_t vector, int index); + +/** + * \brief get the size of vector. + * \param[in] vector: vector handler + * \return the size of vector + */ +int vector_size(vector_t vector); + +/** + * \brief resize vector. + * \param[in] vector: vector handler + * \param[in] size: new size + * \return 1 success or 0 fail + */ +int vector_resize(vector_t vector, int size); + +/** + * \brief get the capacity of vector. + * \param[in] vector: vector handler + * \return the capacity of vector + */ +int vector_capacity(vector_t vector); + +/** + * \brief insert data to vector. + * \param[in] vector: vector handler + * \param[in] index: index + * \param[in] data: array of data, if data is NULL, it is to insert a new space without assigning a value + * \param[in] num: number of data + * \return 1 success or 0 fail + */ +int vector_insert(vector_t vector, int index, void* data, int num); + +/** + * \brief erase data from vector. + * \param[in] vector: vector handler + * \param[in] index: index + * \param[in] num: number of erase, max count + * \return the number of actual erasures + */ +int vector_erase(vector_t vector, int index, int num); + +/** + * \brief A simple method for `vector_create`. + * \param[in] type: data type + * \return vector handler or NULL fail + */ +#define vector(type, size) vector_create(sizeof(type), size) + +/** + * \brief A simple method for `vector_delete`. + * \param[in] vector: vector handler + * \return none + */ +#define _vector(vector) do{vector_delete(vector);(vector)=NULL;}while(0) + +/** + * \brief push data into the vector from the front. + * \param[in] vector: address of vector + * \param[in] data: the address of data + * \return address of vector data or NULL fail + */ +#define vector_push_front(vector, data) vector_insert((vector), 0, (data), 1) + +/** + * \brief push data into the vector from the back. + * \param[in] vector: address of vector + * \param[in] data: the address of data + * \return address of vector data or NULL fail + */ +#define vector_push_back(vector, data) vector_insert((vector), vector_size(vector), (data), 1) + +/** + * \brief pop data from the vector from the front. + * \param[in] vector: address of vector + * \param[out] data: the address of data + * \return 1 success or 0 fail + */ +#define vector_pop_front(vector) vector_erase((vector), 0, 1) + +/** + * \brief pop data from the vector from the back. + * \param[in] vector: address of vector + * \param[out] data: the address of data + * \return 1 success or 0 fail + */ +#define vector_pop_back(vector) vector_erase((vector), vector_size(vector)-1, 1) + +/** + * \brief clear vector. + * \param[in] vector: address of vector + * \return vector size + */ +#define vector_clear(vector) vector_resize((vector), 0) + +/** + * \brief Random access method for vector data. + * \param[in] vector: vector handler + * \param[in] type: data type + * \param[in] i: index starting from vector header + * \return Reference to vector data + */ +#define vector_at(vector, type, i) (*(type *)vector_data((vector), (i))) + +/** + * \brief Using vector as array. + * \param[in] vector: vector handler + * \param[in] type: data type + * \return The array address where the vector actually stores data + */ +#define v2a(vector, type) ((type *)vector_data((vector), 0)) + +#endif diff --git a/source/04_algorithm/check.c b/source/04_algorithm/check.c new file mode 100644 index 0000000..7d248b7 --- /dev/null +++ b/source/04_algorithm/check.c @@ -0,0 +1,84 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file check.c + * \unit check + * \brief This is a C language version of commonly used check code algorithms + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "check.h" + +/** + * \brief check_sum. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t check_sum(uint8_t* data, uint32_t len) +{ + uint32_t i; + uint8_t check = 0; + for (i = 0; i < len; i++) + { + check += data[i]; + } + return check; +} + +/** + * \brief check_parity. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t check_parity(uint8_t* data, uint32_t len) +{ + uint32_t i, b; + uint8_t check = 0; + for (i = 0; i < len; i++) + { + for (b = 0; b < 8; b++) + { + if ((data[i] >> b) & 1) check++; + } + } + return check % 2; +} + +/** + * \brief check_lrc. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t check_lrc(uint8_t* data, uint32_t len) +{ + uint32_t i; + uint8_t check = 0; + for (i = 0; i < len; i++) + { + check += data[i]; + } + return 0xFF - check + 1; +} + +/** + * \brief check_xor. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t check_xor(uint8_t* data, uint32_t len) +{ + uint32_t i; + uint8_t check = 0; + for (i = 0; i < len; i++) + { + check ^= data[i]; + } + return check; +} diff --git a/source/04_algorithm/check.h b/source/04_algorithm/check.h new file mode 100644 index 0000000..efb0640 --- /dev/null +++ b/source/04_algorithm/check.h @@ -0,0 +1,29 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file check.h + * \unit check + * \brief This is a C language version of commonly used check code algorithms + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __check_H +#define __check_H + +#include + +/* Version infomation */ + +#define CHECK_V_MAJOR 1 +#define CHECK_V_MINOR 0 +#define CHECK_V_PATCH 0 + +uint8_t check_sum(uint8_t* data, uint32_t len); +uint8_t check_parity(uint8_t* data, uint32_t len); +uint8_t check_lrc(uint8_t* data, uint32_t len); +uint8_t check_xor(uint8_t* data, uint32_t len); + +#endif diff --git a/source/04_algorithm/crc.c b/source/04_algorithm/crc.c new file mode 100644 index 0000000..1d1f521 --- /dev/null +++ b/source/04_algorithm/crc.c @@ -0,0 +1,596 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file crc.c + * \unit crc + * \brief This is a C language version of commonly used crc algorithms + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "crc.h" + +static uint8_t rf8(uint8_t data) +{ + uint8_t xor = 0; + uint8_t i = 4; + while (i--) + { + xor |= ((data >> ((i << 1) + 1)) ^ data) & (1 << (3 - i)); + xor |= ((xor << ((i << 1) + 1)) & (1 << (4 + i))); + } + return xor ^ data; +} + +static uint16_t rf16(uint16_t data) +{ + uint16_t xor = 0; + uint16_t i = 8; + while (i--) + { + xor |= ((data >> ((i << 1) + 1)) ^ data) & (1 << (7 - i)); + xor |= ((xor << ((i << 1) + 1)) & (1 << (8 + i))); + } + return xor ^ data; +} + +static uint32_t rf32(uint32_t data) +{ + uint32_t xor = 0; + uint32_t i = 16; + while (i--) + { + xor |= ((data >> ((i << 1) + 1)) ^ data) & (1 << (15 - i)); + xor |= ((xor << ((i << 1) + 1)) & (1 << (16 + i))); + } + return xor ^ data; +} + +crcOptType crcParaModelTable[21] = { +/* width refin refout poly init xorout name */ +/*-------------------------------------------------------------------------------------------------------*/ + { 4, 1, 1, 0x03, 0x00, 0x00 }, /*| CRC-4/ITU |*/ + { 5, 0, 0, 0x09, 0x09, 0x00 }, /*| CRC-5/EPC |*/ + { 5, 1, 1, 0x15, 0x00, 0x00 }, /*| CRC-5/ITU |*/ + { 5, 1, 1, 0x05, 0x1F, 0x1F }, /*| CRC-5/USB |*/ + { 6, 1, 1, 0x03, 0x00, 0x00 }, /*| CRC-6/ITU |*/ + { 7, 0, 0, 0x09, 0x00, 0x00 }, /*| CRC-7/MMC |*/ + { 8, 0, 0, 0x07, 0x00, 0x00 }, /*| CRC-8 |*/ + { 8, 0, 0, 0x07, 0x00, 0x55 }, /*| CRC-8/ITU |*/ + { 8, 1, 1, 0x07, 0xFF, 0x00 }, /*| CRC-8/ROHC |*/ + { 8, 1, 1, 0x31, 0x00, 0x00 }, /*| CRC-8/MAXIM |*/ + { 16, 1, 1, 0x8005, 0x0000, 0x0000 }, /*| CRC-16/IBM |*/ + { 16, 1, 1, 0x8005, 0x0000, 0xFFFF }, /*| CRC-16/MAXIM |*/ + { 16, 1, 1, 0x8005, 0xFFFF, 0xFFFF }, /*| CRC-16/USB |*/ + { 16, 1, 1, 0x8005, 0xFFFF, 0x0000 }, /*| CRC-16/MODBUS |*/ + { 16, 1, 1, 0x1021, 0x0000, 0x0000 }, /*| CRC-16/CCITT |*/ + { 16, 0, 0, 0x1021, 0xFFFF, 0x0000 }, /*| CRC-16/CCITT-FALSE |*/ + { 16, 1, 1, 0x1021, 0xFFFF, 0xFFFF }, /*| CRC-16/X25 |*/ + { 16, 0, 0, 0x1021, 0x0000, 0x0000 }, /*| CRC-16/XMODEM |*/ + { 16, 1, 1, 0x3D65, 0x0000, 0xFFFF }, /*| CRC-16/DNP |*/ + { 32, 1, 1, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF }, /*| CRC-32 |*/ + { 32, 0, 0, 0x04C11DB7, 0xFFFFFFFF, 0x00000000 }, /*|CRC-32/MPEG-2 |*/ +}; + +/** + * \brief the general crc algorithm + * \param[in] data: data address + * \param[in] len: length of data + * \param[in] opt: crc algorithm customization options + * \return crc code + */ +uint32_t crc(uint8_t* data, uint32_t len, const crcOptType * const opt) +{ + uint8_t i; + uint32_t crc; + uint32_t poly; + + if (!data) return 0; + if (!opt) return 0; + if (opt->width == 0 || opt->width > 32) return 0; + + crc = opt->init; + poly = opt->poly; + + if (opt->refin) + { + poly = rf32(poly) >> (32 - opt->width); + crc = rf32(crc) >> (32 - opt->width); + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 0x1) crc = (crc >> 1) ^ poly; + else crc >>= 1; + } + } + if (!opt->refout) + { + crc = rf32(crc); + crc >>= (32 - opt->width); + } + } + else + { + crc <<= (32 - opt->width); + poly <<= (32 - opt->width); + while (len--) + { + crc ^= (uint32_t)(*data++) << 24; + for (i = 0; i < 8; i++) + { + if (crc & 0x80000000) crc = (crc << 1) ^ poly; + else crc <<= 1; + } + } + if (opt->refout) crc = rf32(crc); + else crc >>= (32 - opt->width); + } + crc ^= opt->xorout; + return crc; +} + +/** + * \brief crc4_itu. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc4_itu(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0x0C; /* 0x0C = (reflect 0x03) >> (8 - 4) */ + else crc = (crc >> 1); + } + } + return crc; +} + +/** + * \brief crc5_epc. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc5_epc(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0x48; /* 0x48 = 0x09 << (8 - 5) */ + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 0x80) crc = (crc << 1) ^ 0x48; /* 0x48 = 0x09 << (8 - 5) */ + else crc <<= 1; + } + } + return crc >> 3; +} + +/** + * \brief crc5_itu. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc5_itu(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0x15; /* 0x15 = (reflect 0x15) >> (8 - 5) */ + else crc = (crc >> 1); + } + } + return crc; +} + +/** + * \brief crc5_usb. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc5_usb(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0x1F; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0x14; /* 0x14 = (reflect 0x05) >> (8 - 5) */ + else crc = (crc >> 1); + } + } + return crc ^ 0x1F; +} + +/** + * \brief crc6_itu. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc6_itu(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0x30;/* 0x30 = (reflect 0x03) >> (8 - 6) */ + else crc = (crc >> 1); + } + } + return crc; +} + +/** + * \brief crc7_mmc. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc7_mmc(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 0x80) crc = (crc << 1) ^ 0x12; /* 0x12 = 0x09 << (8 - 7) */ + else crc <<= 1; + } + } + return crc >> 1; +} + +/** + * \brief crc8. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc8(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 0x80) crc = (crc << 1) ^ 0x07; + else crc <<= 1; + } + } + return crc; +} + +/** + * \brief crc8_itu. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc8_itu(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 0x80) crc = (crc << 1) ^ 0x07; + else crc <<= 1; + } + } + return crc ^ 0x55; +} + +/** + * \brief crc8_rohc. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc8_rohc(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0xFF; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0xE0; /* 0xE0 = reflect 0x07 */ + else crc = (crc >> 1); + } + } + return crc; +} + +/** + * \brief crc8_maxim. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint8_t crc8_maxim(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint8_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0x8C; /* 0x8C = reflect 0x31 */ + else crc >>= 1; + } + } + return crc; +} + +/** + * \brief crc16_ibm. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint16_t crc16_ibm(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint16_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0xA001; /* 0xA001 = reflect 0x8005 */ + else crc = (crc >> 1); + } + } + return crc; +} + +/** + * \brief crc16_maxim. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint16_t crc16_maxim(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint16_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0xA001; /* 0xA001 = reflect 0x8005 */ + else crc = (crc >> 1); + } + } + return crc ^ 0xFFFF; +} + +/** + * \brief crc16_usb. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint16_t crc16_usb(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint16_t crc = 0xFFFF; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0xA001; /* 0xA001 = reflect 0x8005 */ + else crc = (crc >> 1); + } + } + return crc ^ 0xFFFF; +} + +/** + * \brief crc16_modbus. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint16_t crc16_modbus(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint16_t crc = 0xFFFF; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0xA001; /* 0xA001 = reflect 0x8005 */ + else crc = (crc >> 1); + } + } + return crc; +} + +/** + * \brief crc16_ccitt. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint16_t crc16_ccitt(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint16_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0x8408; /* 0x8408 = reflect 0x1021 */ + else crc = (crc >> 1); + } + } + return crc; +} + +/** + * \brief crc16_ccitt_false. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint16_t crc16_ccitt_false(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint16_t crc = 0xFFFF; + while (len--) + { + crc ^= (uint16_t)(*data++) << 8; + for (i = 0; i < 8; i++) + { + if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; + else crc <<= 1; + } + } + return crc; +} + +/** + * \brief crc16_x25. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint16_t crc16_x25(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint16_t crc = 0xFFFF; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0x8408; /* 0x8408 = reflect 0x1021 */ + else crc = (crc >> 1); + } + } + return crc ^ 0xFFFF; +} + +/** + * \brief crc16_xmodem. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint16_t crc16_xmodem(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint16_t crc = 0; + while (len--) + { + crc ^= (uint16_t)(*data++) << 8; + for (i = 0; i < 8; i++) + { + if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; + else crc <<= 1; + } + } + return crc; +} + +/** + * \brief crc16_dnp. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint16_t crc16_dnp(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint16_t crc = 0; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0xA6BC; /* 0xA6BC = reflect 0x3D65 */ + else crc = (crc >> 1); + } + } + return crc ^ 0xFFFF; +} + +/** + * \brief crc32. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint32_t crc32(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint32_t crc = 0xFFFFFFFF; + while (len--) + { + crc ^= *data++; + for (i = 0; i < 8; i++) + { + if (crc & 1) crc = (crc >> 1) ^ 0xEDB88320; /* 0xEDB88320 = reflect 0x04C11DB7 */ + else crc = (crc >> 1); + } + } + return crc ^ 0xFFFFFFFF; +} + +/** + * \brief crc32_mpeg_2. + * \param[in] data: address of data + * \param[in] len: length of data + * \return check value + */ +uint32_t crc32_mpeg_2(uint8_t* data, uint32_t len) +{ + uint8_t i; + uint32_t crc = 0xFFFFFFFF; + while (len--) + { + crc ^= (uint32_t)(*data++) << 24; + for (i = 0; i < 8; i++) + { + if (crc & 0x80000000) crc = (crc << 1) ^ 0x04C11DB7; + else crc <<= 1; + } + } + return crc; +} diff --git a/source/04_algorithm/crc.h b/source/04_algorithm/crc.h new file mode 100644 index 0000000..98c3cd3 --- /dev/null +++ b/source/04_algorithm/crc.h @@ -0,0 +1,103 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file crc.h + * \unit crc + * \brief This is a C language version of commonly used crc algorithms + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __crc_H +#define __crc_H + +#include + +/* Version infomation */ + +#define CRC_V_MAJOR 1 +#define CRC_V_MINOR 0 +#define CRC_V_PATCH 0 + +/* CRC algorithm customization options define */ +typedef struct +{ + /**< The width of data validation can range from 1 to 32 */ + uint8_t width; + + /**< Flag for reversing input data */ + uint8_t refin; + + /**< Flag for reversing output data */ + uint8_t refout; + + /**< Calculating polynomials, the effective width must be consistent with the width, and any excess parts will be invalid */ + uint32_t poly; + + /**< Calculated initial value */ + uint32_t init; + + /**< Result XOR output value */ + uint32_t xorout; +} crcOptType; + +/* +|-------------------------------------------------------------------------------------------------------| +| index |CRC name | width | refin | refout | poly | init | xorout | +|-------------------------------------------------------------------------------------------------------| +| 0 |CRC-4/ITU | 4 | true | true | 03 | 00 | 00 | +| 1 |CRC-5/EPC | 5 | false | false | 9 | 09 | 00 | +| 2 |CRC-5/ITU | 5 | true | true | 15 | 00 | 00 | +| 3 |CRC-5/USB | 5 | true | true | 5 | 1F | 1F | +| 4 |CRC-6/ITU | 6 | true | true | 3 | 00 | 00 | +| 5 |CRC-7/MMC | 7 | false | false | 9 | 00 | 00 | +| 6 |CRC-8 | 8 | false | false | 7 | 00 | 00 | +| 7 |CRC-8/ITU | 8 | false | false | 7 | 00 | 55 | +| 8 |CRC-8/ROHC | 8 | true | true | 7 | FF | 00 | +| 9 |CRC-8/MAXIM | 8 | true | true | 1 | 00 | 00 | +| 10 |CRC-16/IBM | 16 | true | true | 005 | 0000 | 0000 | +| 11 |CRC-16/MAXIM | 16 | true | true | 005 | 0000 | FFFF | +| 12 |CRC-16/USB | 16 | true | true | 005 | FFFF | FFFF | +| 13 |CRC-16/MODBUS | 16 | true | true | 005 | FFFF | 0000 | +| 14 |CRC-16/CCITT | 16 | true | true | 021 | 0000 | 0000 | +| 15 |CRC-16/CCITT-FALSE | 16 | false | false | 021 | FFFF | 0000 | +| 16 |CRC-16/X25 | 16 | true | true | 021 | FFFF | FFFF | +| 17 |CRC-16/XMODEM | 16 | false | false | 021 | 0000 | 0000 | +| 18 |CRC-16/DNP | 16 | true | true | D65 | 0000 | FFFF | +| 19 |CRC-32 | 32 | true | true | 4C11DB7 | FFFFFFFF | FFFFFFFF | +| 20 |CRC-32/MPEG-2 | 32 | false | false | 4C11DB7 | FFFFFFFF | 00000000 | +|-------------------------------------------------------------------------------------------------------| + */ +extern crcOptType crcParaModelTable[21]; + +/* Custom universal model, within 32 bits, any customization is possible */ + +uint32_t crc(uint8_t* data, uint32_t len, const crcOptType * const opt); + +/* Standard reference model */ + +uint8_t crc4_itu(uint8_t* data, uint32_t len); +uint8_t crc5_epc(uint8_t* data, uint32_t len); +uint8_t crc5_itu(uint8_t* data, uint32_t len); +uint8_t crc5_usb(uint8_t* data, uint32_t len); +uint8_t crc6_itu(uint8_t* data, uint32_t len); +uint8_t crc7_mmc(uint8_t* data, uint32_t len); +uint8_t crc8(uint8_t* data, uint32_t len); +uint8_t crc8_itu(uint8_t* data, uint32_t len); +uint8_t crc8_rohc(uint8_t* data, uint32_t len); +uint8_t crc8_maxim(uint8_t* data, uint32_t len); +uint16_t crc16_ibm(uint8_t* data, uint32_t len); +uint16_t crc16_maxim(uint8_t* data, uint32_t len); +uint16_t crc16_usb(uint8_t* data, uint32_t len); +uint16_t crc16_modbus(uint8_t* data, uint32_t len); +uint16_t crc16_ccitt(uint8_t* data, uint32_t len); +uint16_t crc16_ccitt_false(uint8_t* data, uint32_t len); +uint16_t crc16_x25(uint8_t* data, uint32_t len); +uint16_t crc16_xmodem(uint8_t* data, uint32_t len); +uint16_t crc16_dnp(uint8_t* data, uint32_t len); +uint32_t crc32(uint8_t* data, uint32_t len); +uint32_t crc32_mpeg_2(uint8_t* data, uint32_t len); + +#endif diff --git a/source/04_algorithm/encrypt.c b/source/04_algorithm/encrypt.c new file mode 100644 index 0000000..a6e2799 --- /dev/null +++ b/source/04_algorithm/encrypt.c @@ -0,0 +1,375 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file encrypt.c + * \unit encrypt + * \brief This is a C language version of common encryption and decryption algorithms + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "encrypt.h" +#include +#include +#include +#include + +/* des key */ +static uint32_t des_sk[32] = {0}; +static uint32_t des3_esk[96], des3_dsk[96]; + +/* expanded des s-boxes */ +static const uint32_t sb[8][64] = { + { 0x01010400, 0x00000000, 0x00010000, 0x01010404, 0x01010004, 0x00010404, 0x00000004, 0x00010000, 0x00000400, 0x01010400, 0x01010404, 0x00000400, 0x01000404, 0x01010004, 0x01000000, 0x00000004, 0x00000404, 0x01000400, 0x01000400, 0x00010400, 0x00010400, 0x01010000, 0x01010000, 0x01000404, 0x00010004, 0x01000004, 0x01000004, 0x00010004, 0x00000000, 0x00000404, 0x00010404, 0x01000000, 0x00010000, 0x01010404, 0x00000004, 0x01010000, 0x01010400, 0x01000000, 0x01000000, 0x00000400, 0x01010004, 0x00010000, 0x00010400, 0x01000004, 0x00000400, 0x00000004, 0x01000404, 0x00010404, 0x01010404, 0x00010004, 0x01010000, 0x01000404, 0x01000004, 0x00000404, 0x00010404, 0x01010400, 0x00000404, 0x01000400, 0x01000400, 0x00000000, 0x00010004, 0x00010400, 0x00000000, 0x01010004 }, + { 0x80108020, 0x80008000, 0x00008000, 0x00108020, 0x00100000, 0x00000020, 0x80100020, 0x80008020, 0x80000020, 0x80108020, 0x80108000, 0x80000000, 0x80008000, 0x00100000, 0x00000020, 0x80100020, 0x00108000, 0x00100020, 0x80008020, 0x00000000, 0x80000000, 0x00008000, 0x00108020, 0x80100000, 0x00100020, 0x80000020, 0x00000000, 0x00108000, 0x00008020, 0x80108000, 0x80100000, 0x00008020, 0x00000000, 0x00108020, 0x80100020, 0x00100000, 0x80008020, 0x80100000, 0x80108000, 0x00008000, 0x80100000, 0x80008000, 0x00000020, 0x80108020, 0x00108020, 0x00000020, 0x00008000, 0x80000000, 0x00008020, 0x80108000, 0x00100000, 0x80000020, 0x00100020, 0x80008020, 0x80000020, 0x00100020, 0x00108000, 0x00000000, 0x80008000, 0x00008020, 0x80000000, 0x80100020, 0x80108020, 0x00108000 }, + { 0x00000208, 0x08020200, 0x00000000, 0x08020008, 0x08000200, 0x00000000, 0x00020208, 0x08000200, 0x00020008, 0x08000008, 0x08000008, 0x00020000, 0x08020208, 0x00020008, 0x08020000, 0x00000208, 0x08000000, 0x00000008, 0x08020200, 0x00000200, 0x00020200, 0x08020000, 0x08020008, 0x00020208, 0x08000208, 0x00020200, 0x00020000, 0x08000208, 0x00000008, 0x08020208, 0x00000200, 0x08000000, 0x08020200, 0x08000000, 0x00020008, 0x00000208, 0x00020000, 0x08020200, 0x08000200, 0x00000000, 0x00000200, 0x00020008, 0x08020208, 0x08000200, 0x08000008, 0x00000200, 0x00000000, 0x08020008, 0x08000208, 0x00020000, 0x08000000, 0x08020208, 0x00000008, 0x00020208, 0x00020200, 0x08000008, 0x08020000, 0x08000208, 0x00000208, 0x08020000, 0x00020208, 0x00000008, 0x08020008, 0x00020200 }, + { 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802080, 0x00800081, 0x00800001, 0x00002001, 0x00000000, 0x00802000, 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00800080, 0x00800001, 0x00000001, 0x00002000, 0x00800000, 0x00802001, 0x00000080, 0x00800000, 0x00002001, 0x00002080, 0x00800081, 0x00000001, 0x00002080, 0x00800080, 0x00002000, 0x00802080, 0x00802081, 0x00000081, 0x00800080, 0x00800001, 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00000000, 0x00802000, 0x00002080, 0x00800080, 0x00800081, 0x00000001, 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802081, 0x00000081, 0x00000001, 0x00002000, 0x00800001, 0x00002001, 0x00802080, 0x00800081, 0x00002001, 0x00002080, 0x00800000, 0x00802001, 0x00000080, 0x00800000, 0x00002000, 0x00802080 }, + { 0x00000100, 0x02080100, 0x02080000, 0x42000100, 0x00080000, 0x00000100, 0x40000000, 0x02080000, 0x40080100, 0x00080000, 0x02000100, 0x40080100, 0x42000100, 0x42080000, 0x00080100, 0x40000000, 0x02000000, 0x40080000, 0x40080000, 0x00000000, 0x40000100, 0x42080100, 0x42080100, 0x02000100, 0x42080000, 0x40000100, 0x00000000, 0x42000000, 0x02080100, 0x02000000, 0x42000000, 0x00080100, 0x00080000, 0x42000100, 0x00000100, 0x02000000, 0x40000000, 0x02080000, 0x42000100, 0x40080100, 0x02000100, 0x40000000, 0x42080000, 0x02080100, 0x40080100, 0x00000100, 0x02000000, 0x42080000, 0x42080100, 0x00080100, 0x42000000, 0x42080100, 0x02080000, 0x00000000, 0x40080000, 0x42000000, 0x00080100, 0x02000100, 0x40000100, 0x00080000, 0x00000000, 0x40080000, 0x02080100, 0x40000100 }, + { 0x20000010, 0x20400000, 0x00004000, 0x20404010, 0x20400000, 0x00000010, 0x20404010, 0x00400000, 0x20004000, 0x00404010, 0x00400000, 0x20000010, 0x00400010, 0x20004000, 0x20000000, 0x00004010, 0x00000000, 0x00400010, 0x20004010, 0x00004000, 0x00404000, 0x20004010, 0x00000010, 0x20400010, 0x20400010, 0x00000000, 0x00404010, 0x20404000, 0x00004010, 0x00404000, 0x20404000, 0x20000000, 0x20004000, 0x00000010, 0x20400010, 0x00404000, 0x20404010, 0x00400000, 0x00004010, 0x20000010, 0x00400000, 0x20004000, 0x20000000, 0x00004010, 0x20000010, 0x20404010, 0x00404000, 0x20400000, 0x00404010, 0x20404000, 0x00000000, 0x20400010, 0x00000010, 0x00004000, 0x20400000, 0x00404010, 0x00004000, 0x00400010, 0x20004010, 0x00000000, 0x20404000, 0x20000000, 0x00400010, 0x20004010 }, + { 0x00200000, 0x04200002, 0x04000802, 0x00000000, 0x00000800, 0x04000802, 0x00200802, 0x04200800, 0x04200802, 0x00200000, 0x00000000, 0x04000002, 0x00000002, 0x04000000, 0x04200002, 0x00000802, 0x04000800, 0x00200802, 0x00200002, 0x04000800, 0x04000002, 0x04200000, 0x04200800, 0x00200002, 0x04200000, 0x00000800, 0x00000802, 0x04200802, 0x00200800, 0x00000002, 0x04000000, 0x00200800, 0x04000000, 0x00200800, 0x00200000, 0x04000802, 0x04000802, 0x04200002, 0x04200002, 0x00000002, 0x00200002, 0x04000000, 0x04000800, 0x00200000, 0x04200800, 0x00000802, 0x00200802, 0x04200800, 0x00000802, 0x04000002, 0x04200802, 0x04200000, 0x00200800, 0x00000000, 0x00000002, 0x04200802, 0x00000000, 0x00200802, 0x04200000, 0x00000800, 0x04000002, 0x04000800, 0x00000800, 0x00200002 }, + { 0x10001040, 0x00001000, 0x00040000, 0x10041040, 0x10000000, 0x10001040, 0x00000040, 0x10000000, 0x00040040, 0x10040000, 0x10041040, 0x00041000, 0x10041000, 0x00041040, 0x00001000, 0x00000040, 0x10040000, 0x10000040, 0x10001000, 0x00001040, 0x00041000, 0x00040040, 0x10040040, 0x10041000, 0x00001040, 0x00000000, 0x00000000, 0x10040040, 0x10000040, 0x10001000, 0x00041040, 0x00040000, 0x00041040, 0x00040000, 0x10041000, 0x00001000, 0x00000040, 0x10040040, 0x00001000, 0x00041040, 0x10001000, 0x00000040, 0x10000040, 0x10040000, 0x10040040, 0x10000000, 0x00040000, 0x10001040, 0x00000000, 0x10041040, 0x00040040, 0x10000040, 0x10040000, 0x10001000, 0x10001040, 0x00000000, 0x10041040, 0x00041000, 0x00041000, 0x00001040, 0x00001040, 0x00040040, 0x10000000, 0x10041000 }, +}; + +/* pc1: left and right halves bit-swap */ +static const uint32_t lhs[16] = { 0x00000000, 0x00000001, 0x00000100, 0x00000101, 0x00010000, 0x00010001, 0x00010100, 0x00010101, 0x01000000, 0x01000001, 0x01000100, 0x01000101, 0x01010000, 0x01010001, 0x01010100, 0x01010101 }; +static const uint32_t rhs[16] = { 0x00000000, 0x01000000, 0x00010000, 0x01010000, 0x00000100, 0x01000100, 0x00010100, 0x01010100, 0x00000001, 0x01000001, 0x00010001, 0x01010001, 0x00000101, 0x01000101, 0x00010101, 0x01010101 }; + +/* 32-bit integer manipulation macros (big endian) */ +#define get_uint32_be(n, b, i) do{(n)=((uint32_t)(b)[(i)]<<24)|((uint32_t)(b)[(i)+1]<<16)|((uint32_t)(b)[(i)+2]<<8)|((uint32_t)(b)[(i)+3]);}while(0) +#define put_uint32_be(n, b, i) do{(b)[(i)]=(uint8_t)((n)>>24);(b)[(i)+1]=(uint8_t)((n)>>16);(b)[(i)+2]=(uint8_t)((n)>>8);(b)[(i)+3]=(uint8_t)(n);}while(0) + +/* initial permutation macro */ +#define des_ip(x, y) do \ +{ \ + t = (((x) >> 4) ^ (y)) & 0x0F0F0F0F; (y) ^= t; (x) ^= (t << 4); \ + t = (((x) >> 16) ^ (y)) & 0x0000FFFF; (y) ^= t; (x) ^= (t << 16); \ + t = (((y) >> 2) ^ (x)) & 0x33333333; (x) ^= t; (y) ^= (t << 2); \ + t = (((y) >> 8) ^ (x)) & 0x00FF00FF; (x) ^= t; (y) ^= (t << 8); \ + (y) = (((y) << 1) | ((y) >> 31)) & 0xFFFFFFFF; \ + t = ((x) ^ (y)) & 0xAAAAAAAA; (y) ^= t; (x) ^= t; \ + (x) = (((x) << 1) | ((x) >> 31)) & 0xFFFFFFFF; \ +} while (0) + +/* final permutation macro */ +#define des_fp(x, y) do \ +{ \ + (x) = (((x) << 31) | ((x) >> 1)) & 0xFFFFFFFF; \ + t = ((x) ^ (y)) & 0xAAAAAAAA; (x) ^= t; (y) ^= t; \ + (y) = (((y) << 31) | ((y) >> 1)) & 0xFFFFFFFF; \ + t = (((y) >> 8) ^ (x)) & 0x00FF00FF; (x) ^= t; (y) ^= (t << 8); \ + t = (((y) >> 2) ^ (x)) & 0x33333333; (x) ^= t; (y) ^= (t << 2); \ + t = (((x) >> 16) ^ (y)) & 0x0000FFFF; (y) ^= t; (x) ^= (t << 16); \ + t = (((x) >> 4) ^ (y)) & 0x0F0F0F0F; (y) ^= t; (x) ^= (t << 4); \ +} while (0) + +/* des round macro */ +#define des_round(k,x,y) do { \ + t = (k) ^ (x); \ + (y) ^= sb[7][(t) & 0x3F] ^ sb[5][((t)>>8) & 0x3F] ^ sb[3][((t)>>16) & 0x3F] ^ sb[1][((t)>>24) & 0x3F]; \ + t = (k) ^ (((x)<<28) | ((x)>>4)); \ + (y) ^= sb[6][(t) & 0x3F] ^ sb[4][((t)>>8) & 0x3F] ^ sb[2][((t)>>16) & 0x3F] ^ sb[0][((t)>>24) & 0x3F]; \ +} while (0) + +static void set_keys(uint32_t SK[32], const uint8_t key[8]) +{ + int32_t i; + uint32_t x, y, t; + + get_uint32_be(x, key, 0); + get_uint32_be(y, key, 4); + + /* permuted choice 1 */ + t = ((y >> 4) ^ x) & 0x0F0F0F0F; x ^= t; y ^= (t << 4); + t = ((y ) ^ x) & 0x10101010; x ^= t; y ^= (t ); + + x = (lhs[ (x ) & 0xF] << 3) | (lhs[ (x >> 8) & 0xF ] << 2) + | (lhs[ (x >> 16) & 0xF] << 1) | (lhs[ (x >> 24) & 0xF ] ) + | (lhs[ (x >> 5) & 0xF] << 7) | (lhs[ (x >> 13) & 0xF ] << 6) + | (lhs[ (x >> 21) & 0xF] << 5) | (lhs[ (x >> 29) & 0xF ] << 4); + + y = (rhs[ (y >> 1) & 0xF] << 3) | (rhs[ (y >> 9) & 0xF ] << 2) + | (rhs[ (y >> 17) & 0xF] << 1) | (rhs[ (y >> 25) & 0xF ] ) + | (rhs[ (y >> 4) & 0xF] << 7) | (rhs[ (y >> 12) & 0xF ] << 6) + | (rhs[ (y >> 20) & 0xF] << 5) | (rhs[ (y >> 28) & 0xF ] << 4); + + x &= 0x0FFFFFFF; + y &= 0x0FFFFFFF; + + /* calculate subkeys */ + for (i = 0; i < 16; i++) + { + if (i < 2 || i == 8 || i == 15) + { + x = ((x << 1) | (x >> 27)) & 0x0FFFFFFF; + y = ((y << 1) | (y >> 27)) & 0x0FFFFFFF; + } + else + { + x = ((x << 2) | (x >> 26)) & 0x0FFFFFFF; + y = ((y << 2) | (y >> 26)) & 0x0FFFFFFF; + } + + *SK++ = ((x << 4) & 0x24000000) | ((x << 28) & 0x10000000) + | ((x << 14) & 0x08000000) | ((x << 18) & 0x02080000) + | ((x << 6) & 0x01000000) | ((x << 9) & 0x00200000) + | ((x >> 1) & 0x00100000) | ((x << 10) & 0x00040000) + | ((x << 2) & 0x00020000) | ((x >> 10) & 0x00010000) + | ((y >> 13) & 0x00002000) | ((y >> 4) & 0x00001000) + | ((y << 6) & 0x00000800) | ((y >> 1) & 0x00000400) + | ((y >> 14) & 0x00000200) | ((y ) & 0x00000100) + | ((y >> 5) & 0x00000020) | ((y >> 10) & 0x00000010) + | ((y >> 3) & 0x00000008) | ((y >> 18) & 0x00000004) + | ((y >> 26) & 0x00000002) | ((y >> 24) & 0x00000001); + + *SK++ = ((x << 15) & 0x20000000) | ((x << 17) & 0x10000000) + | ((x << 10) & 0x08000000) | ((x << 22) & 0x04000000) + | ((x >> 2) & 0x02000000) | ((x << 1) & 0x01000000) + | ((x << 16) & 0x00200000) | ((x << 11) & 0x00100000) + | ((x << 3) & 0x00080000) | ((x >> 6) & 0x00040000) + | ((x << 15) & 0x00020000) | ((x >> 4) & 0x00010000) + | ((y >> 2) & 0x00002000) | ((y << 8) & 0x00001000) + | ((y >> 14) & 0x00000808) | ((y >> 9) & 0x00000400) + | ((y ) & 0x00000200) | ((y << 7) & 0x00000100) + | ((y >> 7) & 0x00000020) | ((y >> 3) & 0x00000011) + | ((y << 2) & 0x00000004) | ((y >> 21) & 0x00000002); + } +} + +/** + * \brief set the key for des encryption and decryption. + * \param[in] key: 8 byte key + * \return 1 success + */ +int des_set_key(const uint8_t key[8]) +{ + set_keys(des_sk, key); + return 1; +} + +/** + * \brief des ecb encryption and decryption. + * \param[in] input: 8 byte input + * \param[out] output: 8 byte output + * \param[in] mode: DES_ENCRYPT or DES_DECRYPT + * \return 1 success + */ +int des_crypt_ecb(const uint8_t input[8], uint8_t output[8], uint8_t mode) +{ + int32_t i; + uint32_t x, y, t, k; + + get_uint32_be(x, input, 0); + get_uint32_be(y, input, 4); + + des_ip(x, y); + + for (i = 0; i < 8; i++) + { + k = des_sk[mode==DES_ENCRYPT?i:7-i]; + des_round(k, y, x); + des_round(k, x, y); + } + + des_fp(y, x); + + put_uint32_be(y, output, 0); + put_uint32_be(x, output, 4); + + return 1; +} + +/** + * \brief des cbc encryption and decryption. + * \param[in] input: input + * \param[out] output: output + * \param[in] length: the length of input and output needs to be a multiple of 8 + * \param[in] mode: DES_ENCRYPT or DES_DECRYPT + * \return 1 success + */ +int des_crypt_cbc(const uint8_t *input, uint8_t *output, uint32_t length, uint8_t mode) +{ + int32_t i; + uint8_t temp[8]; + uint8_t iv[8] = {0}; + + if (length % 8) return 0; + + if (mode == DES_ENCRYPT) + { + while (length > 0) + { + for (i = 0; i < 8; i++) output[i] = (uint8_t)(input[i] ^ iv[i]); + des_crypt_ecb(output, output, mode); + memcpy(iv, output, 8); + input += 8; + output += 8; + length -= 8; + } + } + else + { + while (length > 0) + { + memcpy(temp, input, 8); + des_crypt_ecb(input, output, mode); + for (i = 0; i < 8; i++) output[i] = (uint8_t)(output[i] ^ iv[i]); + memcpy(iv, temp, 8); + input += 8; + output += 8; + length -= 8; + } + } + + return 1; +} + +/** + * \brief set the double key for des3 encryption and decryption. + * \param[in] key: 16 byte key + * \return 1 success + */ +int des3_set_key2(const uint8_t key[16]) +{ + int32_t i; + set_keys(des3_esk, key); + set_keys(des3_dsk + 32, key + 8); + for (i = 0; i < 32; i += 2) + { + des3_dsk[i ] = des3_esk[30 - i]; + des3_dsk[i + 1] = des3_esk[31 - i]; + + des3_esk[i + 32] = des3_dsk[62 - i]; + des3_esk[i + 33] = des3_dsk[63 - i]; + + des3_esk[i + 64] = des3_esk[i ]; + des3_esk[i + 65] = des3_esk[i + 1]; + + des3_dsk[i + 64] = des3_dsk[i ]; + des3_dsk[i + 65] = des3_dsk[i + 1]; + } + return 1; +} + +/** + * \brief set the triple key for des3 encryption and decryption. + * \param[in] key: 24 byte key + * \return 1 success + */ +int des3_set_key3(const uint8_t key[24]) +{ + int32_t i; + set_keys(des3_esk, key); + set_keys(des3_dsk + 32, key + 8); + set_keys(des3_esk + 64, key + 16); + for (i = 0; i < 32; i += 2) + { + des3_dsk[i ] = des3_esk[94 - i]; + des3_dsk[i + 1] = des3_esk[95 - i]; + + des3_esk[i + 32] = des3_dsk[62 - i]; + des3_esk[i + 33] = des3_dsk[63 - i]; + + des3_dsk[i + 64] = des3_esk[30 - i]; + des3_dsk[i + 65] = des3_esk[31 - i]; + } + return 1; +} + +/** + * \brief des3 ecb encryption and decryption. + * \param[in] input: 8 byte input + * \param[out] output: 8 byte output + * \param[in] mode: DES_ENCRYPT or DES_DECRYPT + * \return 1 success + */ +int des3_crypt_ecb(const uint8_t input[8], uint8_t output[8], uint8_t mode) +{ + int32_t i, r = 3; + uint32_t x, y, t, *sk; + + get_uint32_be(x, input, 0); + get_uint32_be(y, input, 4); + + des_ip(x, y); + + sk = ((mode==DES_ENCRYPT)?des3_esk:des3_dsk); + + for (i = 0; i < 8; i++) + { + des_round(*sk++, y, x); + des_round(*sk++, x, y); + } + for (i = 0; i < 8; i++) + { + des_round(*sk++, x, y); + des_round(*sk++, y, x); + } + for (i = 0; i < 8; i++) + { + des_round(*sk++, y, x); + des_round(*sk++, x, y); + } + + des_fp(y, x); + + put_uint32_be(y, output, 0); + put_uint32_be(x, output, 4); + + return 1; +} + +/** + * \brief des3 cbc encryption and decryption. + * \param[in] input: input + * \param[out] output: output + * \param[in] length: the length of input and output needs to be a multiple of 8 + * \param[in] mode: DES_ENCRYPT or DES_DECRYPT + * \return 1 success + */ +int des3_crypt_cbc(const uint8_t *input, uint8_t *output, uint32_t length, uint8_t mode) +{ + int32_t i; + uint8_t temp[8]; + uint8_t iv[8] = {0}; + + if (length % 8) return 0; + + if (mode == DES_ENCRYPT) + { + while (length > 0) + { + for (i = 0; i < 8; i++) output[i] = (uint8_t)(input[i] ^ iv[i]); + des3_crypt_ecb(output, output, mode); + memcpy(iv, output, 8); + input += 8; + output += 8; + length -= 8; + } + } + else + { + while (length > 0) + { + memcpy(temp, input, 8); + des3_crypt_ecb(input, output, mode); + for (i = 0; i < 8; i++) output[i] = (uint8_t)(output[i] ^ iv[i]); + memcpy(iv, temp, 8); + input += 8; + output += 8; + length -= 8; + } + } + + return 1; +} + +/* TODO: mbedtls + AES + SHA1 + MD5 + HMAC + blowfish + RSA +*/ \ No newline at end of file diff --git a/source/04_algorithm/encrypt.h b/source/04_algorithm/encrypt.h new file mode 100644 index 0000000..6310033 --- /dev/null +++ b/source/04_algorithm/encrypt.h @@ -0,0 +1,47 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file encrypt.h + * \unit encrypt + * \brief This is a C language version of common encryption and decryption algorithms + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ + /* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef __encrypt_H +#define __encrypt_H + +#include +#include + +#define DES_ENCRYPT 0 +#define DES_DECRYPT 1 + +int des_set_key(const uint8_t key[8]); +int des_crypt_ecb(const uint8_t input[8], uint8_t output[8], uint8_t mode); +int des_crypt_cbc(const uint8_t *input, uint8_t *output, uint32_t length, uint8_t mode); + +int des3_set_key2(const uint8_t key[16]); +int des3_set_key3(const uint8_t key[24]); +int des3_crypt_ecb(const uint8_t input[8], uint8_t output[8], uint8_t mode); +int des3_crypt_cbc(const uint8_t *input, uint8_t *output, uint32_t length, uint8_t mode); + +#endif diff --git a/source/04_algorithm/hash.c b/source/04_algorithm/hash.c new file mode 100644 index 0000000..b94057f --- /dev/null +++ b/source/04_algorithm/hash.c @@ -0,0 +1,283 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file hash.h + * \unit hash + * \brief This is a C language version of commonly used hash algorithms + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "hash.h" + +/** + * \brief hash_bkdr. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_bkdr(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t seed = 131; // 31 131 1313 13131 131313 etc.. + uint32_t hash = 0; + + for (i = 0; i < size; i++) + { + hash = hash * seed + (*str++); + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_ap. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_ap(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t hash = 0; + + for (i = 0; i < size; i++) + { + if ((i & 1) == 0) + { + hash ^= ((hash << 7) ^ (*str++) ^ (hash >> 3)); + } + else + { + hash ^= (~((hash << 11) ^ (*str++) ^ (hash >> 5))); + } + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_djb. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_djb(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t hash = 5381; + + for (i = 0; i < size; i++) + { + hash += (hash << 5) + (*str++); + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_js. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_js(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t hash = 1315423911; + + for (i = 0; i < size; i++) + { + hash ^= ((hash << 5) + (*str++) + (hash >> 2)); + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_rs. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_rs(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t b = 378551; + uint32_t a = 63689; + uint32_t hash = 0; + + for (i = 0; i < size; i++) + { + hash = hash * a + (*str++); + a *= b; + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_sdbm. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_sdbm(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t hash = 0; + + for (i = 0; i < size; i++) + { + // equivalent to: hash = 65599*hash + (*str++); + hash = (*str++) + (hash << 6) + (hash << 16) - hash; + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_pjw. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_pjw(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t bits_in_unigned_int = (uint32_t)(sizeof(uint32_t) * 8); + uint32_t three_quarters = (uint32_t)((bits_in_unigned_int * 3) / 4); + uint32_t one_eighth = (uint32_t)(bits_in_unigned_int / 8); + uint32_t high_bits = (uint32_t)(0xFFFFFFFF) << (bits_in_unigned_int - one_eighth); + uint32_t hash = 0; + uint32_t test = 0; + + for (i = 0; i < size; i++) + { + hash = (hash << one_eighth) + (*str++); + if ((test = hash & high_bits) != 0) + { + hash = ((hash ^ (test >> three_quarters)) & (~high_bits)); + } + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_elf. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_elf(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t hash = 0; + uint32_t x = 0; + + for (i = 0; i < size; i++) + { + hash = (hash << 4) + (*str++); + if ((x = hash & 0xF0000000L) != 0) + { + hash ^= (x >> 24); + hash &= ~x; + } + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_dek. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_dek(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t hash = size; + + for (i = 0; i < size; i++) + { + hash = (hash << 5) ^ (hash >> 27) ^ (*str++); + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_bp. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_bp(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t hash = size; + + for (i = 0; i < size; i++) + { + hash = (hash << 7) ^ (*str++); + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_fnv. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_fnv(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t fnv_prime = 0x811C9DC5; + uint32_t hash = 0; + + for (i = 0; i < size; i++) + { + hash *= fnv_prime; + hash ^= (*str++); + } + + return (hash & 0x7FFFFFFF); +} + +/** + * \brief hash_jdk6. + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ +uint32_t hash_jdk6(void *data, uint32_t size) +{ + int i = 0; + uint8_t *str = (uint8_t *)data; + uint32_t hash = 0; + + for (i = 0; i < size; i++) + { + hash = hash * 31 + (*str++); + } + + return (hash & 0x7FFFFFFF); +} + diff --git a/source/04_algorithm/hash.h b/source/04_algorithm/hash.h new file mode 100644 index 0000000..f3bff01 --- /dev/null +++ b/source/04_algorithm/hash.h @@ -0,0 +1,42 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file hash.h + * \unit hash + * \brief This is a C language version of commonly used hash algorithms + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __hash_H +#define __hash_H + +#include + +/* Hash algorithm declare + * + * All method interface types are consistent, and the external use methods are also consistent. + * + * Choose a specific hash algorithm based on specific usage scenarios. + * + * \param[in] data: address of data + * \param[in] size: size of data + * \return hash value + */ + +uint32_t hash_bkdr(void *data, uint32_t size); +uint32_t hash_ap(void *data, uint32_t size); +uint32_t hash_djb(void *data, uint32_t size); +uint32_t hash_js(void *data, uint32_t size); +uint32_t hash_rs(void *data, uint32_t size); +uint32_t hash_sdbm(void *data, uint32_t size); +uint32_t hash_pjw(void *data, uint32_t size); +uint32_t hash_elf(void *data, uint32_t size); +uint32_t hash_dek(void *data, uint32_t size); +uint32_t hash_bp(void *data, uint32_t size); +uint32_t hash_fnv(void *data, uint32_t size); +uint32_t hash_jdk6(void *data, uint32_t size); + +#endif diff --git a/source/04_algorithm/sort.c b/source/04_algorithm/sort.c new file mode 100644 index 0000000..61fc563 --- /dev/null +++ b/source/04_algorithm/sort.c @@ -0,0 +1,442 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file sort.c + * \unit sort + * \brief This is a general-purpose C language sort module + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "sort.h" +#include +#include + +/* simplify the operation function for obtaining `ops` */ +#define ptr(i) ((ops)->addr(array, (i))) +#define order(f, b) ((ops)->order(ptr(f), ptr(b))) +#define swap(i, j) ((ops)->swap(array, (i), (j))) + +/** + * \brief Bubble sort algorithm + * \param[in] array: Data handle + * \param[in] begin: Begin index + * \param[in] end: End index + * \param[in] ops: Function operation set + * \return 1: success or 0: fail + */ +int sort_bubble(void *array, int begin, int end, SOPS* ops) +{ + int i = 0, j = 0; + int tail = end + 1; + + /* Check the validity of input parameters. */ + if (!array) return 0; + if (begin >= end || begin < 0) return 0; + if (!ops || !ops->order || !ops->addr || !ops->swap) return 0; + + /* Traverse data */ + for (i = begin; i < tail; i++) + { + for (j = i; j < tail; j++) + { + /* Violating the sorting rules, adjust the position to meet the rules */ + if (order(i, j) < 0) swap(i, j); + } + } + + return 1; +} + +/** + * \brief Selection sort algorithm + * \param[in] array: Data handle + * \param[in] begin: Begin index + * \param[in] end: End index + * \param[in] ops: Function operation set + * \return 1: success or 0: fail + */ +int sort_select(void *array, int begin, int end, SOPS* ops) +{ + int i = 0, j = 0; + int tail = end + 1; + int temp = 0; + + /* Check the validity of input parameters. */ + if (!array) return 0; + if (begin >= end || begin < 0) return 0; + if (!ops || !ops->order || !ops->addr || !ops->swap) return 0; + + /* Traverse data */ + for (i = begin; i < tail; i++) + { + temp = i; + for (j = i + 1; j < tail; j++) + { + if (order(temp, j) < 0) temp = j; + } + if (temp != i) swap(temp, i); + } + + return 1; +} + +/** + * \brief Insert sort algorithm + * \param[in] array: Data handle + * \param[in] begin: Begin index + * \param[in] end: End index + * \param[in] ops: Function operation set + * \return 1: success or 0: fail + */ +int sort_insert(void *array, int begin, int end, SOPS* ops) +{ + int i = 0, j = 0; + int tail = end + 1; + + /* Check the validity of input parameters. */ + if (!array) return 0; + if (begin >= end || begin < 0) return 0; + if (!ops || !ops->order || !ops->addr || !ops->swap) return 0; + + /* Traverse data */ + for (i = 1; i < tail; i++) + { + if (order(i - 1, i) < 0) + { + for (j = i - 1; j >= begin; j--) + { + if (order(j, j + 1) < 0) swap(j, j + 1); + else break; + } + } + } + + return 1; +} + +/** + * \brief Shell sort algorithm + * \param[in] array: Data handle + * \param[in] begin: Begin index + * \param[in] end: End index + * \param[in] ops: Function operation set + * \return 1: success or 0: fail + */ +int sort_shell(void *array, int begin, int end, SOPS* ops) +{ + int i = 0, j = 0, k = 0; + int tail = end + 1; + int increasement = tail - begin; + + /* Check the validity of input parameters. */ + if (!array) return 0; + if (begin >= end || begin < 0) return 0; + if (!ops || !ops->order || !ops->addr || !ops->swap) return 0; + + /* Traverse data */ + do + { + /* Determine the increment of the grouping */ + increasement = increasement / 3 + 1; + for (i = begin; i < increasement + begin; i++) + { + for (j = i + increasement; j < tail; j += increasement) + { + if (order(j - increasement, j) < 0) + { + for (k = j - increasement; k >= (int)begin; k -= increasement) + { + if (order(k, k + increasement) < 0) swap(k + increasement, k); + else break; + } + } + } + } + } while (increasement > 1); + + return 1; +} + +/** + * \brief Quick sort algorithm + * \param[in] array: Data handle + * \param[in] begin: Begin index + * \param[in] end: End index + * \param[in] ops: Function operation set + * \return 1: success or 0: fail + */ +int sort_quick(void *array, int begin, int end, SOPS* ops) +{ + int temp = begin; /* reference */ + int i = begin; + int j = end; + + /* Check the validity of input parameters. */ + if (!array) return 0; + if (begin >= end || begin < 0) return 0; + if (!ops || !ops->order || !ops->addr || !ops->swap) return 0; + + /* Traverse data */ + while (i != j) + { + /* Find numbers that do not conform to the sorting rules from right to left */ + while (order(j, temp) <= 0 && j > i) j--; + /* Find numbers that do not violate the ordering rules from left to right */ + while (order(i, temp) >= 0 && j > i) i++; + + if (j > i) swap(i, j); + } + swap(i, temp); + + /* Recursively sort subsequences */ + sort_quick(array, begin, i, ops); + sort_quick(array, i + 1, end, ops); + + return 1; +} + +/** + * \brief Adjust the binary tree structure in the heap sort algorithm + * \param[in] array: Data handle + * \param[in] i: Current node index + * \param[in] tail: Array len + * \param[in] ops: Function operation set + * \return 0: ok or other: fail + */ +static int heap_adjust(void *array, int base, int i, int tail, SOPS* ops) +{ + int parent = i; + int lchild = (i - base) * 2 + 1 + base; + int rchild = (i - base) * 2 + 2 + base; + + if (lchild < tail && order(lchild, parent) < 0) parent = lchild; + + if (rchild < tail && order(rchild, parent) < 0) parent = rchild; + + /* If the value at i is smaller than the value of its left and right child nodes, swap it with the parent value */ + if (parent != i) + { + swap(i, parent); + + /* Recursion */ + heap_adjust(array, base, parent, tail, ops); + } + + return 1; +} + +/** + * \brief Heap sort algorithm + * \param[in] array: Data handle + * \param[in] begin: Begin index + * \param[in] end: End index + * \param[in] ops: Function operation set + * \return 1: success or 0: fail + */ +int sort_heap(void *array, int begin, int end, SOPS* ops) +{ + int i = 0; + int length = end - begin + 1; + + /* Check the validity of input parameters. */ + if (!array) return 0; + if (begin >= end || begin < 0) return 0; + if (!ops || !ops->order || !ops->addr || !ops->swap) return 0; + + /* Init heap, (length / 2 - 1 + begin) is the sequence number of the last non-leaf node in the binary tree*/ + for (i = length / 2 - 1 + begin; i >= (int)begin; i--) + { + heap_adjust(array, begin, i, end + 1, ops); + } + /* Swap the top and last elements of the heap */ + for (i = end; i >= (int)begin; i--) + { + swap(begin, i); + heap_adjust(array, begin, begin, i, ops); + } + + return 1; +} + +/* int */ +static int int_ascend(void *front, void *back) +{ + if (*(int *)front < *(int *)back) return 1; + else if (*(int *)front > *(int *)back) return -1; + else return 0; +} + +static int int_descend(void *front, void *back) +{ + return -int_ascend(front, back); +} + +static int uint_ascend(void *front, void *back) +{ + if (*(unsigned int *)front < *(unsigned int *)back) return 1; + else if (*(unsigned int *)front > *(unsigned int *)back) return -1; + else return 0; +} + +static int uint_descend(void *front, void *back) +{ + return -uint_ascend(front, back); +} + +static void* int_addr(void *array, int index) +{ + return ((int *)array) + index; +} + +static void int_swap(void *array, int index0, int index1) +{ + int temp = ((int *)array)[index0]; + ((int *)array)[index0] = ((int *)array)[index1]; + ((int *)array)[index1] = temp; +} + +/* char */ +static int char_ascend(void *front, void *back) +{ + if (*(char *)front < *(char *)back) return 1; + else if (*(char *)front > *(char *)back) return -1; + else return 0; +} + +static int char_descend(void *front, void *back) +{ + return -char_ascend(front, back); +} + +static int uchar_ascend(void *front, void *back) +{ + if (*(unsigned char *)front < *(unsigned char *)back) return 1; + else if (*(unsigned char *)front > *(unsigned char *)back) return -1; + else return 0; +} + +static int uchar_descend(void *front, void *back) +{ + return -uchar_ascend(front, back); +} + +static void* char_addr(void *array, int index) +{ + return ((char *)array) + index; +} + +static void char_swap(void *array, int index0, int index1) +{ + char temp = ((char *)array)[index0]; + ((char *)array)[index0] = ((char *)array)[index1]; + ((char *)array)[index1] = temp; +} + +/* short */ +static int short_ascend(void *front, void *back) +{ + if (*(short *)front < *(short *)back) return 1; + else if (*(short *)front > *(short *)back) return -1; + else return 0; +} + +static int short_descend(void *front, void *back) +{ + return -short_ascend(front, back); +} + +static int ushort_ascend(void *front, void *back) +{ + if (*(unsigned short *)front < *(unsigned short *)back) return 1; + else if (*(unsigned short *)front > *(unsigned short *)back) return -1; + else return 0; +} + +static int ushort_descend(void *front, void *back) +{ + return -ushort_ascend(front, back); +} + +static void* short_addr(void *array, int index) +{ + return ((short *)array) + index; +} + +static void short_swap(void *array, int index0, int index1) +{ + short temp = ((short *)array)[index0]; + ((short *)array)[index0] = ((short *)array)[index1]; + ((short *)array)[index1] = temp; +} + +/* float */ +static int float_ascend(void *front, void *back) +{ + if (*(float *)front < *(float *)back) return 1; + else if (*(float *)front > *(float *)back) return -1; + else return 0; +} + +static int float_descend(void *front, void *back) +{ + return -float_ascend(front, back); +} + +static void* float_addr(void *array, int index) +{ + return ((float *)array) + index; +} + +static void float_swap(void *array, int index0, int index1) +{ + float temp = ((float *)array)[index0]; + ((float *)array)[index0] = ((float *)array)[index1]; + ((float *)array)[index1] = temp; +} + +/* double */ +static int double_ascend(void *front, void *back) +{ + if (*(double *)front < *(double *)back) return 1; + else if (*(double *)front > *(double *)back) return -1; + else return 0; +} + +static int double_descend(void *front, void *back) +{ + return -double_ascend(front, back); +} + +static void* double_addr(void *array, int index) +{ + return ((double *)array) + index; +} + +static void double_swap(void *array, int index0, int index1) +{ + double temp = ((double *)array)[index0]; + ((double *)array)[index0] = ((double *)array)[index1]; + ((double *)array)[index1] = temp; +} + +/* Basic `SOPS` define */ + +SOPS sops_int_ascend = {int_ascend, int_addr, int_swap }; +SOPS sops_int_descend = {int_descend, int_addr, int_swap }; +SOPS sops_uint_ascend = {uint_ascend, int_addr, int_swap }; +SOPS sops_uint_descend = {uint_descend, int_addr, int_swap }; +SOPS sops_char_ascend = {char_ascend, char_addr, char_swap }; +SOPS sops_char_descend = {char_descend, char_addr, char_swap }; +SOPS sops_uchar_ascend = {uchar_ascend, char_addr, char_swap }; +SOPS sops_uchar_descend = {uchar_descend, char_addr, char_swap }; +SOPS sops_short_ascend = {short_ascend, short_addr, short_swap }; +SOPS sops_short_descend = {short_descend, short_addr, short_swap }; +SOPS sops_ushort_ascend = {ushort_ascend, short_addr, short_swap }; +SOPS sops_ushort_descend = {ushort_descend, short_addr, short_swap }; +SOPS sops_float_ascend = {float_ascend, float_addr, float_swap }; +SOPS sops_float_descend = {float_descend, float_addr, float_swap }; +SOPS sops_double_ascend = {double_ascend, double_addr, double_swap }; +SOPS sops_double_descend = {double_descend, double_addr, double_swap }; diff --git a/source/04_algorithm/sort.h b/source/04_algorithm/sort.h new file mode 100644 index 0000000..5ceff1e --- /dev/null +++ b/source/04_algorithm/sort.h @@ -0,0 +1,102 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file sort.h + * \unit sort + * \brief This is a general-purpose C language sort module + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __sort_H +#define __sort_H + +#define SORT_V_MAJOR 1 +#define SORT_V_MINOR 0 +#define SORT_V_PATCH 0 + +/* Sorting algorithm operation function set structure definition */ +typedef struct +{ + /** + * \brief ordering rules, e.g front < back ascending and front > back descending + * \param[in] front: address of front data + * \param[in] back: address of back data + * \return positive: following sorting rules + * negative: violating sorting rules + * 0: does't affect sorting rules + */ + int (*order)(void *front, void *back); + + /** + * \brief get the address of the space where the element is located + * \param[in] array: data handle + * \param[in] index: item data index + * \return address of item data + */ + void* (*addr)(void *array, int index); + + /** + * \brief Swap data between two spaces + * \param[in] array: data handle + * \param[in] index0: item data index0 + * \param[in] index1: item data index1 + * \return none + */ + void (*swap)(void *array, int index0, int index1); + +} SOPS; + + +/* Sorting algorithm declare + * + * All method interface types are consistent, and the external use methods are also consistent. + * + * Choose a specific sorting algorithm based on specific usage scenarios. + * + * Choose `SOPS` based on different data structures and sorting rules. + * + * \param[in] array: data handle + * \param[in] begin: begin index + * \param[in] end: end index + * \param[in] ops: operation function set + * \return 1: success or 0: fail + */ + +int sort_bubble(void *array, int begin, int end, SOPS* ops); +int sort_select(void *array, int begin, int end, SOPS* ops); +int sort_insert(void *array, int begin, int end, SOPS* ops); +int sort_shell(void *array, int begin, int end, SOPS* ops); +int sort_quick(void *array, int begin, int end, SOPS* ops); +int sort_heap(void *array, int begin, int end, SOPS* ops); + +/* Basic `SOPS` declare + * + * Contains a set of ascending and descending order operation functions for basic data types. + * + * These `SOPS` can be used directly for array type data sets. + * + * For other types of data sets (such as list, queue, ...), you can reuse some functions of `SOPS` and + * add a small number of change functions to form a new `SOPS`. + */ + +extern SOPS sops_char_ascend; +extern SOPS sops_char_descend; +extern SOPS sops_uchar_ascend; +extern SOPS sops_uchar_descend; +extern SOPS sops_short_ascend; +extern SOPS sops_short_descend; +extern SOPS sops_ushort_ascend; +extern SOPS sops_ushort_descend; +extern SOPS sops_int_ascend; +extern SOPS sops_int_descend; +extern SOPS sops_uint_ascend; +extern SOPS sops_uint_descend; +extern SOPS sops_float_ascend; +extern SOPS sops_float_descend; +extern SOPS sops_double_ascend; +extern SOPS sops_double_descend; + +#endif diff --git a/source/05_parser/csv.c b/source/05_parser/csv.c new file mode 100644 index 0000000..e0dcf77 --- /dev/null +++ b/source/05_parser/csv.c @@ -0,0 +1,2208 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file csv.c + * \unit csv + * \brief This is a C language version of csv excel parser + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "csv.h" +#include +#include + +/* dump buffer define */ +typedef struct +{ + char* address; /**< buffer base address */ + unsigned int size; /**< size of buffer */ + unsigned int end; /**< end of buffer used */ +} BUFFER; + +/* iterator define */ +typedef struct +{ + void *p; /**< iteration pointer */ + unsigned int i; /**< iteration index */ +} ITERATOR; + +/* smallest memory cell */ +typedef struct CELL +{ + struct CELL *next; /**< next cell */ + char* address; /**< address of cell content */ +} CELL; + +/* row storage */ +typedef struct ROW +{ + struct ROW *next; /**< next column */ + CELL *cells; /**< the cell base address of this column */ + unsigned int size; /**< columns of current row */ + ITERATOR iterator; /**< cell list iterator */ +} ROW; + +/* type of csv */ +typedef struct CSV +{ + ROW *rows; /**< rows base */ + unsigned int size; /**< rows of csv */ + ITERATOR iterator; /**< rows list iterator */ +} CSV; + +/* parsing error message storage */ +static int etype = 0; +static int eline = 0; +static int ecolumn = 0; +static const char* lbegin = NULL; + +#define Tarray(a, r, c, i, j) (*(char**)(((char *)a)+((i)*(c)+(j))*sizeof(void*))) +#define E(type) etype=(type) + +/** + * \brief Duplicate a given string. + * + * \param[in] str String to be duplicated. + * \param[in] len Length of the string. + * \return Pointer to the duplicated string if successful, NULL otherwise. + */ +static char* csv_strdup(const char* str, int len) +{ + char* s; + + /* Allocate memory for the new string */ + s = (char*)malloc(len + 1); + if (!s) return NULL; + + /* Copy the given string into the allocated memory */ + memcpy(s, str, len); + s[len] = '\0'; + + return s; +} + +static ROW *it_row(csv_t csv, int index, int size) +{ + if (index >= size) return NULL; + if (index < csv->iterator.i || !csv->iterator.p || index == 0) + { + csv->iterator.i = 0; + csv->iterator.p = csv->rows; + } + while (csv->iterator.p && csv->iterator.i < index) + { + csv->iterator.p = (void *)(((ROW *)(csv->iterator.p))->next); + csv->iterator.i++; + } + return csv->iterator.p; +} + +static CELL *it_cell(ROW *row, int index, int size) +{ + if (index >= size) return NULL; + if (index < row->iterator.i || !row->iterator.p || index == 0) + { + row->iterator.i = 0; + row->iterator.p = row->cells; + } + while (row->iterator.p && row->iterator.i < index) + { + row->iterator.p = (void *)(((CELL *)(row->iterator.p))->next); + row->iterator.i++; + } + return row->iterator.p; +} + +/** + * \brief Free the memory occupied by a linked list of CELL structures. + * + * \param[in] cell Pointer to the first CELL in the linked list. + * + * This function frees the memory occupied by a linked list of CELL structures. + * It iterates through the linked list, freeing each CELL structure and its associated memory. + * + * \note This function does not return a value. + */ +static void cell_free(CELL* cell) +{ + CELL* t; + + /* Iterate through the linked list, freeing each CELL structure */ + while (cell) + { + t = cell->next; + + /* Free the address string if it exists */ + if (cell->address) + { + free(cell->address); + } + + /* Free the CELL structure */ + free(cell); + + cell = t; + } +} + +/** + * \brief Create a linked list of CELL structures with the specified number of elements. + * + * \param[in] number Number of CELL structures to create in the linked list. + * \return Pointer to the first CELL in the linked list if successful, NULL otherwise. + * + * This function creates a linked list of CELL structures with the specified number of elements. + * Each CELL structure is allocated dynamically, with an empty address string and a NULL next pointer. + * The function returns a pointer to the first CELL in the linked list if successful, and NULL otherwise. + * + * \note The caller is responsible for freeing the allocated memory. + */ +static CELL* cell_new(unsigned int number) +{ + CELL* cell = NULL, *t, *p = NULL; + unsigned int i = 0; + + /* Create the specified number of CELL structures in the linked list */ + for (i = 0; i < number; i++) + { + t = (CELL*)malloc(sizeof(CELL)); + if (!t) + { + cell_free(cell); + return NULL; + } + + t->address = malloc(sizeof(char)); + if (!t->address) + { + cell_free(cell); + return NULL; + } + + t->next = NULL; + t->address[0] = '\0'; + + if (p) + { + p->next = t; + } + p = t; + + if (cell == NULL) + { + cell = t; + } + } + + return cell; +} + +/** + * \brief Free the memory occupied by a linked list of ROW structures. + * + * \param[in] row Pointer to the first ROW in the linked list. + * + * This function frees the memory occupied by a linked list of ROW structures. + * It iterates through the linked list, freeing each ROW structure and its associated memory. + * Additionally, it calls the `cell_free` function to free the memory occupied by the cells in each ROW structure. + * + * \note This function does not return a value. + */ +static void row_free(ROW *row) +{ + ROW* t; + + /* Iterate through the linked list, freeing each ROW structure */ + while (row) + { + t = row->next; + + /* Free the memory occupied by the cells in the ROW structure */ + cell_free(row->cells); + + /* Free the ROW structure */ + free(row); + + row = t; + } +} + +/** + * \brief Create a linked list of ROW structures with the specified number of elements. + * + * \param[in] number Number of ROW structures to create in the linked list. + * \return Pointer to the first ROW in the linked list if successful, NULL otherwise. + * + * This function creates a linked list of ROW structures with the specified number of elements. + * Each ROW structure is allocated dynamically, with the cells set to NULL, size set to 0, next pointer set to NULL, and iterator fields set to initial values. + * The function returns a pointer to the first ROW in the linked list if successful, and NULL otherwise. + * + * \note The caller is responsible for freeing the allocated memory. + */ +static ROW *row_new(unsigned int number) +{ + ROW *row = NULL, *t, *p = NULL; + unsigned int i = 0; + + /* Create the specified number of ROW structures in the linked list */ + for (i = 0; i < number; i++) + { + t = (ROW *)malloc(sizeof(ROW)); + if (!t) + { + row_free(row); + return NULL; + } + + t->cells = NULL; + t->size = 0; + t->next = NULL; + t->iterator.p = NULL; + t->iterator.i = 0; + + if (p) + { + p->next = t; + } + p = t; + + if (row == NULL) + { + row = t; + } + } + + return row; +} + +/** + * \brief Minify the given row by removing empty cells at the end. + * + * \param[in] row The row object to be minified + */ +static void row_minify(ROW *row) +{ + CELL *cell, *t = NULL; + unsigned int i = 0; + + if (!row) return; + if (!row->cells) return; + + cell = row->cells; + while (cell) + { + /* Check if the cell contains a null string */ + if (!cell->address || cell->address[0] == '\0') + { + i++; /* Count the number of consecutive null cells */ + } + else /* Not a null string */ + { + t = cell; /* Keep track of the last non-null cell */ + i = 0; /* Reset the consecutive null cell count */ + } + + cell = cell->next; /* Move to the next cell */ + } + + /* If there are consecutive null cells at the end, remove them */ + if (i > 0 && t) + { + cell_free(t->next); /* Free the memory of the consecutive null cells */ + t->next = NULL; /* Set the next pointer of the last non-null cell to NULL */ + row->size -= i; /* Subtract the number of consecutive null cells from the row size */ + } +} + +/** + * \brief Minify the given CSV table by removing empty rows at the end. + * + * \param[in] csv handle + */ +void csv_minify(csv_t csv) +{ + ROW *row, *t = NULL; + unsigned int i = 0; + + if (!csv) return; + if (!csv->rows) return; + + row = csv->rows; + while (row) + { + /* Minify the current row by removing empty cells */ + row_minify(row); + + /* Check if the row is empty (null row) */ + if (!row->cells || row->size == 0) + { + i++; /* Count the number of consecutive null rows */ + } + else /* Not a null row */ + { + t = row; /* Keep track of the last non-null row */ + i = 0; /* Reset the consecutive null row count */ + } + + row = row->next; /* Move to the next row */ + } + + /* If there are consecutive null rows at the end, remove them */ + if (i > 0 && t) + { + row_free(t->next); /* Free the memory of the consecutive null rows */ + t->next = NULL; /* Set the next pointer of the last non-null row to NULL */ + csv->size -= i; /* Subtract the number of consecutive null rows from the total size */ + } +} + +/** + * \brief create a csv object + * \param[in] col: number of columns + * \param[in] row: number of rows + * \param[in] array: initialize CSV array, defined as `char *array[row][col] = {...}`, set null when passed in `NULL` + * \return csv handle or NULL FAIL + */ +csv_t csv_create(unsigned int row, unsigned int col, const void *array) +{ + csv_t csv = NULL; + unsigned int i, j; + char *text = NULL; + + /* create null csv */ + csv = (csv_t)malloc(sizeof(CSV)); + if (!csv) return NULL; + csv->rows = NULL; + csv->size = 0; + csv->iterator.p = NULL; + csv->iterator.i = 0; + + for (i = 0; i < row; i++) + { + if (array) + { + for (j = 0; j < col; j++) + { + text = Tarray(array, row, col, i, j); + if (!csv_set_text(csv, i + 1, j + 1, text ? text : "")) goto FAIL; + } + } + else + { + if (!csv_set_text(csv, i + 1, col, "")) goto FAIL; + } + } + + return csv; +FAIL: + csv_delete(csv); + return NULL; +} + +/** + * \brief delete the csv entity and its sub-entities. + * \param[in] csv: csv handle + * \return none + */ +void csv_delete(csv_t csv) +{ + if (csv) + { + row_free(csv->rows); + free(csv); + } +} + +/** + * \brief Attach a row to a CSV table at the specified position. + * + * \param[in] csv csv handle + * \param[in] pos The position where the row should be attached + * \param[in] row The row to be attached + * + * \return Returns the number of rows attached to the table + */ +static int attach_row(csv_t csv, unsigned int pos, ROW *row) +{ + ROW *prev = NULL, *next = NULL; + int count = 1; + + if (pos == 1) + { + next = csv->rows; + csv->rows = row; + } + else + { + prev = it_row(csv, pos - 2, csv->size); + next = prev->next; + prev->next = row; + } + + while (row->next) + { + row = row->next; + count++; + } + row->next = next; + + return count; +} + +/** + * \brief Detach a row from a CSV table at the specified position. + * + * \param[in] csv csv handle + * \param[in] pos The position of the row to be detached + * + * \return Returns the detached row + */ +static ROW *detach_row(csv_t csv, unsigned int pos) +{ + ROW *row = NULL, *prev = NULL; + + /* Delete the row at the specified position */ + if (pos == 1) + { + /* Delete the first row */ + row = it_row(csv, 0, csv->size); + csv->rows = row->next; + } + else + { + /* Delete a row other than the first row */ + prev = it_row(csv, pos - 2, csv->size); + row = prev->next; + prev->next = row->next; + } + + /* Clear the next pointer of the detached row */ + row->next = NULL; + + return row; +} + +/** + * \brief Insert a new row at the specified position in the CSV data. + * + * \param[in] csv csv handle + * \param[in] pos Position to insert the new row (counting from 1) + * \param[in] array initialize row array, defined as `char *array[size] = {...}`, set null when passed in `NULL` + * \param[in] count count of array + * \return 1 if the row is successfully inserted, 0 otherwise + */ +int csv_insert_row(csv_t csv, unsigned int pos, const char **array, unsigned int count) +{ + ROW *row = NULL; + unsigned int i = 1, ins; + unsigned int old = 0; + + /* Check if input parameter is valid */ + if (!csv) return 0; + + /* Adjust the position if it is 0 (insert at the end) */ + if (pos == 0) + { + pos = csv->size + 1; + } + + old = csv->size; + ins = pos; + + if (pos > csv->size + 1) + { + i = pos - csv->size; + ins = csv->size + 1; + } + + row = row_new(i); + if (!row) return 0; + + csv->size += attach_row(csv, ins, row); + + /* Initialize insertion value */ + if (array && count) + { + for (i = 0; i < count; i++) + { + if (!csv_set_text(csv, pos, i + 1, array[i])) + { + csv_delete_row(csv, pos); + for (; csv->size > old; ) + { + csv_delete_row(csv, old + 1); + } + return 0; + } + } + } + + return 1; +} + +/** + * \brief Delete the row at the specified position in the CSV data. + * + * \param[in] csv csv handle. + * \param[in] pos Position of the row to delete (counting from 1). If pos is 0, the last row will be deleted. + * \return 1 if the row is successfully deleted, 0 otherwise. + */ +int csv_delete_row(csv_t csv, unsigned int pos) +{ + ROW *row = NULL; + + /* Check if input parameter is valid */ + if (!csv) return 0; + + if (csv->size == 0) return 0; + + /* Adjust the position if it is 0 (delete the last row) */ + if (pos == 0) pos = csv->size; + + if (pos > csv->size) return 0; + + row = detach_row(csv, pos); + + row_free(row); + + csv->size--; + + return 1; +} + +/** + * \brief Attach a cell to a row at the specified position. + * + * \param[in] row The row object to attach the cell to + * \param[in] pos The position where the cell should be attached + * \param[in] cell The cell to be attached + * + * \return Returns the number of cells attached to the row + */ +static int attach_cell(ROW *row, unsigned int pos, CELL *cell) +{ + CELL *prev = NULL, *next = NULL; + int count = 1; + + if (!row) return 0; + if (!cell) return 0; + + if (pos == 1) + { + next = row->cells; + row->cells = cell; + } + else + { + prev = it_cell(row, pos - 2, row->size); + next = prev->next; + prev->next = cell; + } + + while (cell->next) + { + cell = cell->next; + count++; + } + cell->next = next; + + return count; +} + +/** + * \brief Detach a cell from a row at the specified position. + * + * \param[in] row The row object to detach the cell from + * \param[in] pos The position of the cell to be detached + * + * \return Returns the detached cell + */ +static CELL *detach_cell(ROW *row, unsigned int pos) +{ + CELL *cell = NULL, *prev = NULL; + + if (!row) return NULL; + + if (pos == 1) + { + cell = it_cell(row, 0, row->size); + row->cells = cell->next; + } + else + { + prev = it_cell(row, pos - 2, row->size); + cell = prev->next; + prev->next = cell->next; + } + + cell->next = NULL; + + return cell; +} + +/** + * \brief Insert a new cell at the specified position in a row. + * + * \param[in] row Pointer to the row where the cell will be inserted. + * \param[in] pos Position to insert the new cell (counting from 1). If pos is 0, the new cell will be inserted at the end of the row. + * \return 1 if the cell is successfully inserted, 0 otherwise. + * + * This function inserts a new cell at the specified position in a row. If pos is 0, the new cell will be inserted at the end of the row. + * The function returns 1 if the cell is successfully inserted at the specified position, and 0 otherwise. + * + * \note The position number is counted from 1. + * \note It is the responsibility of the caller to ensure the validity of the row structure. + * \note The memory for the new cell is allocated dynamically using the `cell_new` function. + */ +static int row_insert_cell(ROW *row, unsigned int pos) +{ + CELL* cell = NULL; + unsigned int i = 1, ins; + + /* Check if input parameter is valid */ + if (!row) return 0; + + /* Adjust the position if it is 0 (insert at the end) */ + if (pos == 0) + { + pos = row->size + 1; + } + + ins = pos; + + if (pos > row->size + 1) + { + i = pos - row->size; + ins = row->size + 1; + } + + cell = cell_new(i); + if (!cell) return 0; + + row->size += attach_cell(row, ins, cell); + + return 1; +} + +/** + * \brief Delete the cell at the specified position in a row. + * + * \param[in] row Pointer to the row from which the cell will be deleted. + * \param[in] pos Position of the cell to delete (counting from 1). If pos is 0, the last cell will be deleted. + * \return 1 if the cell is successfully deleted, 0 otherwise. + * + * This function deletes the cell at the specified position in a row. If pos is 0, the last cell in the row will be deleted. + * The function returns 1 if the cell is successfully deleted, and 0 otherwise. + * + * \note The position number is counted from 1. + * \note It is the responsibility of the caller to ensure the validity of the row structure. + * \note The memory occupied by the deleted cell will be freed using the `cell_free` function. + */ +static int row_delete_cell(ROW* row, unsigned int pos) +{ + CELL* cell = NULL; + + /* Check if input parameter is valid */ + if (!row) return 0; + + /* Adjust the position if it is 0 (delete the last cell) */ + if (pos == 0) pos = row->size; + + if (pos > row->size) return 0; + + cell = detach_cell(row, pos); + + /* Free the memory occupied by the deleted cell */ + cell_free(cell); + + row->size--; + + return 1; +} + +/** + * \brief Insert a new column at the specified position in the CSV data. + * + * \param[in] csv csv handle. + * \param[in] pos Position to insert the new column (counting from 1). If pos is 0, the new column will be inserted at the end of the CSV data. + * \param[in] array initialize column array, defined as `char *array[size] = {...}`, set null when passed in `NULL` + * \param[in] count count of array + * \return 1 if the column is successfully inserted, 0 otherwise. + */ +int csv_insert_col(csv_t csv, unsigned int pos, const char **array, unsigned int count) +{ + ROW *row = NULL; + unsigned int i = 0, j = 0; + unsigned int old; /* original number of rows */ + + /* Check if input parameter is valid */ + if (!csv) return 0; + + /* Adjust the position if it is 0 (insert at the end) */ + if (pos == 0) + { + pos = csv_col(csv) + 1; + } + + old = csv->size; + + /* Insert the new column in each row of the CSV data */ + for (i = 1; i <= csv->size; i++) + { + row = it_row(csv, i - 1, csv->size); + if (!row_insert_cell(row, pos)) + { + goto FAIL; + } + } + + /* Initialize insertion value */ + if (array && count) + { + for (j = 0; j < count; j++) + { + if (!csv_set_text(csv, j + 1, pos, array[j])) + { + goto FAIL; + } + } + } + + return 1; + +FAIL: + /* Rollback the column insertion if it fails */ + for (i--; i >= 1; i--) + { + row = it_row(csv, i - 1, csv->size); + row_delete_cell(row, pos); + } + for (; csv->size > old; ) + { + csv_delete_row(csv, old + 1); + } + return 0; +} + +/** + * \brief Delete the column at the specified position in the CSV data. + * + * \param[in] csv csv handle. + * \param[in] pos Position of the column to delete (counting from 1). If pos is 0, the last column will be deleted. + * \return 1 if the column is successfully deleted, 0 otherwise. + */ +int csv_delete_col(csv_t csv, unsigned int pos) +{ + ROW *row = NULL; + unsigned int i = 0; + + /* Check if input parameter is valid */ + if (!csv) return 0; + + /* Adjust the position if it is 0 (delete the last column) */ + if (pos == 0) + { + pos = csv_col(csv); + } + + /* Delete the column from each row of the CSV data */ + for (i = 1; i <= csv->size; i++) + { + row = it_row(csv, i - 1, csv->size); + row_delete_cell(row, pos); + } + + return 1; +} + +/** + * \brief Move a row to a new position within a CSV data structure. + * + * \param[in] csv Pointer to the CSV data structure. + * \param[in] pos Current position of the row to be moved (counting from 1). + * \param[in] dest New position to move the row to (counting from 1). + * \return 1 if the row is successfully moved, 0 otherwise. + * + * This function moves a row within a CSV data structure from its current position to a new position. + * If the current position (pos) is greater than the current size of the CSV data structure, the function checks if the new position (dest) is also greater than the current size. If so, it returns 1 indicating that nothing needs to be done. + * If the new position is greater than the current size, an empty row is inserted at the new position using the `csv_insert_row` function. + * The function then detaches the row from its current position using the `detach_row` function, updates the size of the CSV data structure, and attaches the row at the new position using the `attach_row` function. + * The function returns 1 if the row is successfully moved, and 0 otherwise. + * + * \note The caller is responsible for ensuring the validity of the CSV data structure and the indices of the current and new positions. + */ +int csv_move_row_to(csv_t csv, unsigned int pos, unsigned int dest) +{ + ROW *row = NULL; + + /* Check if the CSV data structure exists and is not empty */ + if (!csv) return 0; + if (csv->size == 0) return 0; + + /* If the current position is greater than the size, check if the new position is also greater than the size */ + if (pos > csv->size) + { + if (dest > csv->size) return 1; + else return csv_insert_row(csv, dest, NULL, 0); + } + + /* If the new position is greater than the size, insert an empty row at the new position */ + if (dest > csv->size) + { + if (!csv_set_text(csv, dest, 0, "")) return 0; + } + + /* Detach the row from the current position */ + row = detach_row(csv, pos); + csv->size--; + + /* Attach the row at the new position */ + attach_row(csv, dest, row); + csv->size++; + + return 1; +} + +/** + * \brief Move a column to a new position within a CSV data structure. + * + * \param[in] csv Pointer to the CSV data structure. + * \param[in] pos Current position of the column to be moved (counting from 1). + * \param[in] dest New position to move the column to (counting from 1). + * \return 1 if the column is successfully moved, 0 otherwise. + * + * This function moves a column within a CSV data structure from its current position to a new position. + * If the current position (pos) is greater than the maximum column index in the CSV data structure, the function checks if the new position (dest) is also greater than the maximum column index. If so, it returns 1 indicating that nothing needs to be done. + * If the new position is greater than the maximum column index, an empty column is inserted at the new position using the `csv_insert_col` function. + * The function then iterates through each row in the CSV data structure, detaches the cell from the current position using the `detach_cell` function, updates the size of the row, and attaches the cell at the new position using the `attach_cell` function. + * Finally, the function returns 1 if the column is successfully moved, and 0 otherwise. + * + * \note The caller is responsible for ensuring the validity of the CSV data structure and the indices of the current and new positions. + */ +int csv_move_col_to(csv_t csv, unsigned int pos, unsigned int dest) +{ + ROW *row; + CELL *cell; + unsigned int max_col = 0; + unsigned int i; + + /* Check if the CSV data structure exists and is not empty */ + if (!csv) return 0; + if (csv->size == 0) return 0; + + /* Determine the maximum column index in the CSV data structure */ + max_col = csv_col(csv); + + /* If the current position is greater than the maximum column index, check if the new position is also greater than the maximum column index */ + if (pos > max_col) + { + if (dest > max_col) return 1; + else return csv_insert_col(csv, dest, NULL, 0); + } + + /* If the new position is greater than the maximum column index, insert an empty column at the new position */ + if (dest > max_col) + { + for (i = 1; i <= csv->size; i++) + { + if (!csv_set_text(csv, i, dest, "")) goto FAIL; + } + } + + /* Move the column to the new position */ + for (i = 1; i <= csv->size; i++) + { + row = it_row(csv, i - 1, csv->size); + cell = detach_cell(row, pos); + row->size--; + attach_cell(row, dest, cell); + row->size++; + } + + return 1; + +FAIL: + /* Rollback the column insertion if an error occurs */ + if (i > 1) + { + for (; dest > max_col; dest--) + { + csv_delete_col(csv, max_col + 1); + } + } + return 0; +} + + +/** + * \brief Copy a row from a CSV table to another position in the table. + * + * \param[in] csv handle + * \param[in] pos The index of the row to be copied + * \param[in] dest The index where the row should be copied to + * + * \return Returns 1 if the row is successfully copied, 0 otherwise + */ +int csv_copy_row_to(csv_t csv, unsigned int pos, unsigned int dest) +{ + unsigned int i; + unsigned int size; + + if (!csv) return 0; + if (csv->size == 0) return 0; + + if (pos == 0) + { + pos = csv->size; + } + + /* Insert a new row at the destination position */ + csv_insert_row(csv, dest, NULL, 0); + + /* If the destination is before the source position, increment the source position by 1 */ + if (dest < pos) pos += 1; + + /* Get the size of the source row */ + size = it_row(csv, pos - 1, csv->size)->size; + + /* Copy each cell from the source row to the destination row */ + for (i = 1; i <= size; i++) + { + /* Set the text of the destination cell to the text of the source cell */ + if (!csv_set_text(csv, dest, i, csv_get_text(csv, pos, i))) + { + /* If setting text fails, delete the destination row and return 0 */ + csv_delete_row(csv, dest); + return 0; + } + } + + return 1; +} + +/** + * \brief Copy a column from a CSV table to another position in the table. + * + * \param[in] csv handle + * \param[in] pos The index of the column to be copied + * \param[in] dest The index where the column should be copied to + * + * \return Returns 1 if the column is successfully copied, 0 otherwise + */ +int csv_copy_col_to(csv_t csv, unsigned int pos, unsigned int dest) +{ + unsigned int i; + unsigned int max_col = 0; + const char *text; + + if (!csv) return 0; + if (csv->size == 0) return 0; + + max_col = csv_col(csv); + if (pos == 0) + { + pos = max_col; + } + + /* Insert a new column at the destination position */ + csv_insert_col(csv, dest, NULL, 0); + + /* If the destination is before the source position, increment the source position by 1 */ + if (dest < pos) pos += 1; + + /* Copy each cell from the source column to the destination column */ + for (i = 1; i <= max_col; i++) + { + /* Get the text of the source cell */ + text = csv_get_text(csv, i, pos); + + /* If the text is available, set it to the destination cell */ + if (text && !csv_set_text(csv, i, dest, text)) + { + /* If setting text fails, delete the destination column and return 0 */ + csv_delete_col(csv, dest); + return 0; + } + } + + return 1; +} + +/** + * \brief get the number of CSV rows + * \param[in] csv: csv handle + * \return number of rows + */ +unsigned int csv_row(csv_t csv) +{ + if (!csv) return 0; + + return csv->size; +} + +/** + * \brief get the number of CSV columns + * \param[in] csv: csv handle + * \return number of columns + */ +unsigned int csv_col(csv_t csv) +{ + unsigned int col = 0; + ROW *row; + + if (!csv) return 0; + row = csv->rows; + + while (row) + { + if (row->size > col) col = row->size; + + row = row->next; + } + + return col; +} + +/** + * \brief Get the total number of non-empty cells in a CSV table. + * + * \param[in] csv csv handle + * + * \return Returns the number of non-empty cells in the table + */ +unsigned int csv_cell(csv_t csv) +{ + unsigned int count = 0; + ROW *row; + CELL *cell; + + if (!csv) return 0; + + row = csv->rows; + while (row) + { + cell = row->cells; + while (cell) + { + if (cell->address && cell->address[0]) + { + count++; /* Increment the count for each non-empty cell */ + } + + cell = cell->next; + } + + row = row->next; + } + + return count; +} + +/** + * \brief get the number of CSV columns + * \param[in] csv: csv handle + * \param[in] row: row index, counting from 1 + * \param[in] col: column index, counting from 1 + * \param[in] text: cell text + * \return 1 success or 0 fail + */ +int csv_set_text(csv_t csv, unsigned int row, unsigned int col, const char* text) +{ + ROW *srow; /* Pointer to the row */ + CELL *cell; /* Pointer to the cell */ + char* s; /* Buffer for copied text content */ + int len = 0; /* Counter for the length of the text content */ + int i; + + /* Check if input parameters are valid */ + if (!csv) return 0; + if (!text) return 0; + if (col < 1) return 0; + if (row < 1) return 0; + + /* If the specified row exceeds the size of the CSV data, insert a new row */ + if (row > csv->size) + { + if (!csv_insert_row(csv, row, NULL, 0)) return 0; + } + + /* Get the specified row */ + srow = it_row(csv, row - 1, csv->size); + if (!srow) return 0; + + /* If the specified column exceeds the size of the row, insert a new cell */ + if (col > srow->size) + { + if (!row_insert_cell(srow, col)) return 0; + } + + /* Get the specified cell */ + cell = it_cell(srow, col - 1, srow->size); + if (!cell) return 0; + + /* Calculate the length of the text content and check if it contains printable characters */ + for (i = 0;;i++) + { + if (text[i] == 0) break; +#if 0 + /* Not a printable character */ + if (!(text[i] >= ' ' && text[i] <= '~') && text[i] != '\n') return 0; +#endif + len++; + } + + /* Copy the text content to a newly allocated memory space */ + s = csv_strdup(text, len); + if (!s) return 0; + + /* Free the old address pointer and update it with the new address */ + if (cell->address) free(cell->address); + cell->address = s; + + return 1; +} + +/** + * \brief Get the text content at the specified row and column position in the CSV data. + * + * \param[in] csv csv handle. + * \param[in] row Row index, counting from 1. + * \param[in] col Column index, counting from 1. + * \return Pointer to the text content if found, NULL otherwise. + */ +const char* csv_get_text(csv_t csv, unsigned int row, unsigned int col) +{ + ROW* srow; /* Pointer to the row */ + CELL* cell; /* Pointer to the cell */ + + /* Check if input parameter is valid */ + if (!csv) return NULL; + + /* Get the specified row */ + srow = it_row(csv, row - 1, csv->size); + if (!srow) return NULL; + + /* Get the specified cell */ + cell = it_cell(srow, col - 1, srow->size); + if (!cell) return NULL; + + return cell->address; +} + +/** + * \brief Clean the text content at the specified position in a CSV data structure by setting it to an empty string. + * + * \param[in] csv Pointer to the CSV data structure. + * \param[in] row Row index (counting from 1). + * \param[in] col Column index (counting from 1). + * + * This function cleans the text content at the specified position in a CSV data structure by setting it to an empty string. + * If the specified position is invalid or if the text content is already empty, the function does nothing. + * + * \note The caller is responsible for ensuring the validity of the CSV data structure and the indices of the row and column. + */ +void csv_clean_text(csv_t csv, unsigned int row, unsigned int col) +{ + if (!csv) return; + if (col < 1) return; + if (row < 1) return; + + /* Check if the text content at the specified position is not empty */ + if (!csv_get_text(csv, row, col)) return; + + /* Set the text content at the specified position to an empty string */ + csv_set_text(csv, row, col, ""); +} + +/** + * \brief Calculate the smallest power of 2 that is greater than or equal to a given number. + * + * \param[in] x The given number. + * \return The smallest power of 2 greater than or equal to x. + */ +static unsigned int pow2gt(unsigned int x) +{ + int b = sizeof(int) * 8; + int i = 1; + + --x; + while (i < b) + { + x |= (x >> i); + i <<= 1; + } + + return x + 1; +} + +/** + * \brief confirm whether buf still has the required capacity, otherwise add capacity. + * \param[in] buf: buf handle + * \param[in] needed: required capacity + * \return 1 success or 0 fail + */ +static int expansion(BUFFER* buf, unsigned int needed) +{ + char* address; + unsigned int size; + if (!buf || !buf->address) return 0; + needed += buf->end; + if (needed <= buf->size) return 1; /* there is still enough space in the current buf */ + size = pow2gt(needed); + address = (char*)realloc(buf->address, size); + if (!address) return 0; + buf->size = size; + buf->address = address; + return 1; +} +#define buf_append(n) expansion(buf, (n)) /* append n size space for buf */ +#define buf_putc(c) (buf->address[buf->end++]=(c)) /* put a non zero character into buf */ +#define buf_end() (buf->address[buf->end]) /* obtain the tail of buf */ + +/** + * \brief Print the content of a CELL structure to a BUFFER structure. + * + * \param[in] cell Pointer to the CELL structure containing the content to be printed. + * \param[in] buf Pointer to the BUFFER structure where the content will be stored. + * \return 1 if the content is successfully printed, 0 otherwise. + * + * This function prints the content of a CELL structure to a BUFFER structure. + * It iterates through the characters in the address string of the CELL structure and handles special characters, such as double quotes and newlines, by properly escaping them. + * The function returns 1 if the content is successfully printed, and 0 otherwise. + * + * \note The caller is responsible for managing the BUFFER structure and ensuring its validity. + */ +static int print_cel(CELL* cell, BUFFER* buf) +{ + char* addr; + char escape = 0; + unsigned int size = 0; + + /* Check if the address string in the CELL structure is valid */ + if (!cell->address) return 1; + + /* Calculate the size of the content and determine if any special characters need to be escaped */ + for (addr = cell->address; *addr; addr++) + { + switch (*addr) + { + case '"': + size++; + case '\n': + case ',': + escape = 1; + break; + default: + break; + } + + size++; + } + if (escape) size += 2; + + /* Ensure that the BUFFER structure has enough space to store the content */ + if (!buf_append(size)) return 0; + + /* Print the content to the BUFFER structure, properly escaping special characters */ + if (escape) buf_putc('"'); + for (addr = cell->address; *addr; addr++) + { + switch (*addr) + { + case '"': + buf_putc('"'); + break; + default: + break; + } + buf_putc(*addr); + } + if (escape) buf_putc('"'); + + return 1; +} + +/** + * \brief Print the contents of a CSV data structure to a BUFFER structure. + * + * \param[in] csv csv handle. + * \param[in] buf Pointer to the BUFFER structure where the contents will be stored. + * \return 1 if the contents are successfully printed, 0 otherwise. + * + * This function prints the contents of a CSV data structure to a BUFFER structure. + * It iterates through the rows and cells in the CSV data structure and uses the `print_cel` function to print each cell's content to the BUFFER structure. + * The function returns 1 if the contents are successfully printed, and 0 otherwise. + * + * \note The caller is responsible for managing the CSV data structure and ensuring its validity. + * \note The caller is also responsible for managing the BUFFER structure and ensuring its validity. + */ +static int print_csv(csv_t csv, BUFFER* buf) +{ + ROW* row, *rnext; + CELL* cell, *cnext; + + /* Check if the CSV data structure is valid */ + if (csv) + { + row = csv->rows; + + /* Iterate through the rows in the CSV data structure */ + while (row) + { + rnext = row->next; + + cell = row->cells; + + /* Iterate through the cells in each row */ + if (cell) + { + while (cell) + { + cnext = cell->next; + + /* Print the cell's content to the BUFFER structure */ + print_cel(cell, buf); + + cell = cnext; + + /* Append a comma if there are more cells in the row */ + if (cell) + { + if (!buf_append(1)) return 0; + buf_putc(','); + } + } + } + + row = rnext; + + /* Append a newline if there are more rows in the CSV data structure */ + if (row) + { + if (!buf_append(1)) return 0; + buf_putc('\n'); + } + } + + /* Null-terminate the BUFFER structure */ + buf_end() = '\0'; + } + + return 1; +} + +/** + * \brief convert csv to text, using a buffered strategy. + * \param[in] csv: csv handle + * \param[out] *len: address that receives the length of printed characters + * \return address of converted text, free the char* when finished + * + * This function generates a CSV string representation of a CSV data structure. + * It uses the `print_csv` function to print the contents of the CSV data structure to a BUFFER structure, and then allocates memory to store the generated CSV string. + * The function returns a pointer to the generated CSV string if successful, and NULL otherwise. + * The length of the CSV string is stored in the `len` parameter if it is not NULL. + * + * \note The caller is responsible for freeing the allocated memory. + */ +char* csv_dumps(csv_t csv, int* len) +{ + BUFFER p; + int preset = 1; + + /* Check if the CSV data structure is valid and not empty */ + if (!csv) return NULL; + if (csv->size == 0) return NULL; + + /* Allocate memory for the initial BUFFER structure */ + p.address = (char*)malloc(preset); + if (!p.address) return NULL; + p.size = preset; + p.end = 0; + + /* Print the CSV data to the BUFFER structure */ + if (!print_csv(csv, &p)) + { + free(p.address); + return NULL; + } + + /* Store the length of the generated CSV string if the len parameter is provided */ + if (len) *len = p.end; + + return p.address; +} + +/** + * \brief according to the csv, generate a file. + * \param[in] csv: csv handle + * \param[in] *filename: file name + * \return The length of the dumped CSV string if successful, or an error code if an error occurs. + * -1: csv handle null + * -2: filename null + * -3: dump string fail + * -4: create file fail + * + * This function dumps the contents of a CSV data structure to a file. + * It calls the `csv_dumps` function to generate the CSV string representation of the CSV data structure. + * The generated CSV string is then written to the specified file. + * The function returns the length of the dumped CSV string if successful, or an error code if an error occurs. + * + * \note The caller is responsible for managing the CSV data structure and ensuring its validity. + * \note If an error occurs during file operations, the function may leave the file partially written. + */ +int csv_file_dump(csv_t csv, const char* filename) +{ + FILE* f; + char* out; + int len; + + /* Check if the CSV data structure and filename are valid */ + if (!csv) return -1; + if (!filename) return -2; + + /* Generate the CSV string representation of the CSV data structure */ + out = csv_dumps(csv, &len); + if (!out) return -3; + + /* Open the file for writing */ + f = fopen(filename, "w"); + if (!f) + { + free(out); + return -4; + } + + /* Write the CSV string to the file */ + fwrite(out, 1, len, f); + fclose(f); + + /* Free the memory allocated for the CSV string */ + free(out); + + return len; +} + +/** + * \brief load csv text and generate csv. + * \param[in] *text: address of text + * \return csv handler + */ +csv_t csv_loads(const char* text) +{ + csv_t csv; + ROW* row = NULL; + CELL* cell = NULL; + const char* s = text; + const char* start = s; + const char* end = NULL; /* [start, end) is a cell text interval */ + const char* quotation = NULL; /* (start, quotation) is a cell text escape interval */ + const char* t; + int len = 0; + + if (!text) return NULL; + + /* Initialize static parameters for parsing */ + etype = CSV_E_OK; + eline = 1; + ecolumn = 0; + lbegin = s; + + /* create a null csv */ + csv = csv_create(0, 0, NULL); + if (!csv) + { + E(CSV_E_MEMORY); + return NULL; + } + + while (1) + { + switch (*s) + { + case '\0': + end = s; + if (*start == '"') quotation = s; /* At the end of the text, use the end as an escape closed interval */ + case '\n': + eline++; + lbegin = s + 1; + case ',': + if (*start == '"') + { + if (quotation) end = s; /* Leaving the escape interval, it can be concluded now */ + } + else + { + end = s; /* Not within the escape interval, ending directly */ + } + + /* Parsing string has reached the end */ + if (end) + { +#if defined(_WIN32) || defined(_WIN64) + if (*end == '\n') end--; /* skip '\r' */ +#endif + /* If there is currently no selected row, create a new row */ + if (!row) + { + if (!csv_insert_row(csv, 0, NULL, 0)) + { + E(CSV_E_MEMORY); + goto FAIL; + } + row = it_row(csv, csv->size - 1, csv->size); + } + + /* Add a cell to store parsed text */ + if (!row_insert_cell(row, 0)) + { + E(CSV_E_MEMORY); + goto FAIL; + } + cell = it_cell(row, row->size - 1, row->size); + + /* The length of the string is end minus start, and then subtract the excess in the escape `"` */ + len = end - start - len; + + cell->address = malloc(len + 1); + if (!cell->address) + { + E(CSV_E_MEMORY); + goto FAIL; + } + + /* Convert text to cells */ + len = 0; + for (t = start; t < end; t++) + { + /* Double quotation marks including escape */ + if (*start == '"') + { + if (*t == '"') + { + if (t == start || t == quotation) continue; /* Skip both ends of quotation marks directly */ + if (t < quotation) t++; /* Escaped quotation marks skip the first one */ + } + } + cell->address[len++] = *t; + } + cell->address[len] = '\0'; + + /* Reset to resolve the next one */ + if (*s == '\n') row = NULL; + cell = NULL; + start = s + 1; + end = NULL; + quotation = NULL; + len = 0; + } + break; + case '"': + /* Starting with quotation marks, there is an escaped interval */ + if (*start == '"') + { + /* Currently at the starting position */ + if (s == start) + { + len++; + } + else + { + /* The quotation marks have not yet formed a closed interval, they are still within the scope of escape */ + if (!quotation) + { + if (s[1] == '"') s++; /* it is an escaped quotation mark */ + else quotation = s; /* A single quotation mark indicates the formation of a closed interval */ + len++; + } + } + } + break; + default: + break; + } + + /* Exit of loop parsing */ + if (!*s) break; + + s++; + } + + return csv; + +FAIL: + ecolumn = s - lbegin + 1; + csv_delete(csv); + return NULL; +} + +/** + * \brief load csv file and generate csv. + * \param[in] *filename: filename + * \return csv handler + */ +csv_t csv_file_load(const char* filename) +{ + csv_t csv; + FILE* f; + long len; + char* text = NULL; + + if (!filename) return NULL; + + eline = 0; + + /* open file and get the length of file */ + f = fopen(filename, "rb"); + if (!f) + { + E(CSV_E_OPEN); + goto FAIL; + } + + /* get file length */ + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + + /* read file */ + text = (char*)malloc(len + 1); + if (text) + { + fread(text, 1, len, f); + fclose(f); + f = NULL; + } + else + { + E(CSV_E_MEMORY); + goto FAIL; + } + text[len] = 0; + + csv = csv_loads(text); /* load text */ + if (!csv) + { + goto FAIL; + } + + free(text); + + return csv; + +FAIL: + if (f) fclose(f); + if (text) free(text); + return NULL; +} + +/** + * \brief obtain parsing error information. + * \param[out] *line: line number where the error occurred + * \param[out] *column: column number where the error occurred + * \return error type + */ +int csv_error_info(int* line, int* column) +{ + if (etype == CSV_E_OK) return 0; + if (line) *line = eline; + if (column) *column = ecolumn; + return etype; +} + +/** + * \brief Create a duplicate of a given csv handle. + * + * \param[in] csv csv handle. + * \return Pointer to the duplicated CSV data structure if successful, NULL otherwise. + * + * This function creates a duplicate of a given CSV data structure. It creates a new CSV data structure, iterates through each row and cell in the original CSV data structure, and sets the corresponding text content in the duplicated CSV data structure using the `csv_set_text` and `csv_get_text` functions. + * The function returns a pointer to the duplicated CSV data structure if successful, and NULL otherwise. + * + * \note The caller is responsible for freeing the allocated memory by calling the `csv_delete` function for the duplicated CSV data structure. + * \note If an error occurs during the duplication process, the function may leave the duplicated CSV data structure in an inconsistent state. + */ +csv_t csv_duplicate(csv_t csv) +{ + csv_t dp = NULL; + ROW *row = NULL; + unsigned int i, j; + + /* Check if the original CSV data structure is valid */ + if (!csv) return NULL; + + /* Create a new CSV data structure for the duplicate */ + dp = csv_create(0, 0, NULL); + if (!dp) return NULL; + + /* Iterate through each row and cell in the original CSV data structure */ + for (i = 1; i <= csv->size; i++) + { + row = it_row(csv, i - 1, csv->size); + + for (j = 1; j <= row->size; j++) + { + /* Set the text content in the duplicated CSV data structure using the original CSV data structure */ + if (!csv_set_text(dp, i, j, csv_get_text(csv, i, j))) + { + csv_delete(dp); + return NULL; + } + } + } + + return dp; +} + +/** + * \brief Copy the text content from a source cell to a destination cell in a CSV data structure. + * + * \param[in] csv csv handle. + * \param[in] s_row Source row index (counting from 1). + * \param[in] s_col Source column index (counting from 1). + * \param[in] d_row Destination row index (counting from 1). + * \param[in] d_col Destination column index (counting from 1). + * \return 1 if the text content is successfully copied, 0 otherwise. + * + * This function copies the text content from a source cell in a CSV data structure to a destination cell. + * It retrieves the text content from the source cell using the `csv_get_text` function and sets the same text content in the destination cell using the `csv_set_text` function. + * The function returns 1 if the text content is successfully copied, and 0 otherwise. + * + * \note The caller is responsible for ensuring the validity of the CSV data structure and the indices of the source and destination cells. + */ +int csv_copy_cell_to(csv_t csv, unsigned int s_row, unsigned int s_col, unsigned int d_row, unsigned int d_col) +{ + const char* text = NULL; + + if (!csv) return 0; + if (!s_row || !s_col || !d_row || !d_col) return 0; + + text = csv_get_text(csv, s_row, s_col); + + return csv_set_text(csv, d_row, d_col, text ? text : ""); +} + +/** + * \brief Cut the text content from a source cell and paste it into a destination cell in a CSV data structure. + * + * \param[in] csv csv handle. + * \param[in] s_row Source row index (counting from 1). + * \param[in] s_col Source column index (counting from 1). + * \param[in] d_row Destination row index (counting from 1). + * \param[in] d_col Destination column index (counting from 1). + * \return 1 if the text content is successfully cut and pasted, 0 otherwise. + * + * This function cuts the text content from a source cell in a CSV data structure and pastes it into a destination cell. + * It retrieves the text content from the source cell and sets it in the destination cell. Additionally, it swaps the addresses of the source and destination cells to complete the cut and paste operation. + * The function returns 1 if the text content is successfully cut and pasted, and 0 otherwise. + */ +int csv_cut_cell_to(csv_t csv, unsigned int s_row, unsigned int s_col, unsigned int d_row, unsigned int d_col) +{ + ROW *row = NULL; + CELL *cell = NULL, *d_cell = NULL; + char *swap; + + if (!csv) return 0; + if (!s_row || !s_col || !d_row || !d_col) return 0; + + if (!csv_set_text(csv, d_row, d_col, "")) return 0; + row = it_row(csv, d_row - 1, csv->size); + d_cell = it_cell(row, d_col - 1, row->size); + + row = it_row(csv, s_row - 1, csv->size); + if (!row) return 1; + + cell = it_cell(row, s_col - 1, row->size); + if (!cell) return 1; + + swap = cell->address; + cell->address = d_cell->address; + d_cell->address = swap; + + return 1; +} + +/** + * \brief Insert a cell in the given CSV table. + * + * \param[in] csv handle + * \param[in] row The index of the row where the cell is inserted + * \param[in] col The index of the column where the cell is inserted + * \param[in] move_down Whether to move other cells down + * - 0: Do not move other cells down + * - Non-zero: Move other cells right + * + * \return Returns 1 if the cell is successfully inserted, 0 otherwise + */ +int csv_insert_cell(csv_t csv, unsigned int row, unsigned int col, int move_down) +{ + CELL *c1, *c2; + ROW *r1, *r2; + unsigned int i, size; + char *text; + + if (!csv) return 0; + if (csv->size == 0) return 0; + if (!row || !col) return 0; + if (row > csv->size) return 0; + if (col > csv_col(csv)) return 0; + + if (move_down) + { + size = csv->size; + /* Move other cells down from the given row to the end of the table */ + for (i = row; i <= size + 1; i++) + { + if (!csv_get_text(csv, i, col)) + { + /* If the current cell is empty, set it to an empty string */ + if (!csv_set_text(csv, i, col, "")) return 0; + } + } + + /* Starting from the end, swap cell contents row by row and column by column to complete the move down operation */ + for (i = size; i >= row; i--) + { + r1 = it_row(csv, i - 1, csv->size); + c1 = it_cell(r1, col - 1, r1->size); + r2 = it_row(csv, i, csv->size); + c2 = it_cell(r2, col - 1, r2->size); + + text = c1->address; + c1->address = c2->address; + c2->address = text; + } + } + else + { + /* If not moving other cells down, simply insert a new cell at the specified column in the given row */ + r1 = it_row(csv, row - 1, csv->size); + if (col > r1->size) return 1; + + c1 = cell_new(1); + if (!c1) return 0; + attach_cell(r1, col, c1); + } + + return 1; +} + +/** + * \brief Delete a cell in the given CSV table. + * + * \param[in] csv handle + * \param[in] row The index of the row where the cell is deleted + * \param[in] col The index of the column where the cell is deleted + * \param[in] move_up Whether to move other cells up + * - 0: Do not move other cells up + * - Non-zero: Move other cells left + * + * \return Returns 1 if the cell is successfully deleted, 0 otherwise + */ +int csv_delete_cell(csv_t csv, unsigned int row, unsigned int col, int move_up) +{ + CELL *c1, *c2; + ROW *r1, *r2; + unsigned int i; + char *text, *old; + + if (!csv) return 0; + if (csv->size == 0) return 0; + if (!row || !col) return 0; + if (row > csv->size) return 0; + if (col > csv_col(csv)) return 0; + + if (move_up) + { + /* Delete the cell and move cells up from the given row to the end of the table */ + for (i = row; i <= csv->size; i++) + { + if (!csv_get_text(csv, i, col)) + { + /* If the current cell is empty, set it to an empty string */ + if (!csv_set_text(csv, i, col, "")) return 0; + } + } + + /* Set the cell at the specified row and column to an empty string */ + if (!csv_set_text(csv, row, col, "")) return 0; + + /* Starting from the given row, swap cell contents row by row and column by column to complete the move up operation */ + for (i = row; i < csv->size; i++) + { + r1 = it_row(csv, i - 1, csv->size); + c1 = it_cell(r1, col - 1, r1->size); + r2 = it_row(csv, i, csv->size); + c2 = it_cell(r2, col - 1, r2->size); + + text = c1->address; + c1->address = c2->address; + c2->address = text; + } + } + else + { + /* If not moving other cells up, simply detach and free the cell at the specified column in the given row */ + r1 = it_row(csv, row - 1, csv->size); + if (col > r1->size) return 1; + + c1 = detach_cell(r1, col); + cell_free(c1); + } + + return 1; +} + +/** + * \brief Copy the text content from a CSV data structure to a 2D array. + * + * \param[in] csv Pointer to the CSV data structure. + * \param[in] o_row Starting row index in the CSV data structure (counting from 1). + * \param[in] o_col Starting column index in the CSV data structure (counting from 1). + * \param[out] array Pointer to the 2D array where the text content will be copied, defined as `char *array[row][col]` + * \param[in] row_size Number of rows in the 2D array. + * \param[in] col_size Number of columns in the 2D array. + * \return 1 if the text content is successfully copied, 0 otherwise. + * + * This function copies the text content from a CSV data structure to a 2D array. It iterates through the specified range of rows and columns in the CSV data structure, retrieves the text content from each cell using the `csv_get_text` function, and copies it to the corresponding location in the 2D array. + * The function returns 1 if the text content is successfully copied, and 0 otherwise. + * + * \note The caller is responsible for ensuring the validity of the CSV data structure and the indices of the starting row and column. + * \note The caller is also responsible for managing the memory of the 2D array. + */ +int csv_to_array(csv_t csv, unsigned int o_row, unsigned int o_col, void *array, unsigned int row_size, unsigned int col_size) +{ + const char *text = NULL; + unsigned int i, j; + + /* Check if the CSV data structure exists */ + if (!csv) return 0; + if (!array) return 0; + if (!row_size || !col_size) return 0; + + /* Iterate through the specified range of rows and columns */ + for (i = 0; i < row_size; i++) + { + for (j = 0; j < col_size; j++) + { + /* Retrieve the text content from the CSV data structure */ + text = csv_get_text(csv, o_row + i, o_col + j); + + /* Copy the text content to the corresponding location in the 2D array */ + Tarray(array, row_size, col_size, i, j) = (char *)text; + } + } + + return 1; +} + +/** + * \brief Compare two strings case-insensitively. + * + * \param[in] s1 The first string to compare + * \param[in] s2 The second string to compare + * + * \return Returns an integer less than, equal to, or greater than zero if s1 is found, respectively, + * to be less than, to match, or be greater than s2. + */ +static int strccmp(const char* s1, const char* s2) +{ + if (!s1) return (s1 == s2) ? 0 : 1; + if (!s2) return 1; + + for (; tolower(*s1) == tolower(*s2); ++s1, ++s2) + { + if (*s1 == 0) return 0; + } + + return tolower(*(const unsigned char*)s1) - tolower(*(const unsigned char*)s2); +} + +/** + * \brief Find the first occurrence of a substring within a string, case-insensitively. + * + * \param[in] str The string to search within + * \param[in] substr The substring to search for + * + * \return Returns a pointer to the first occurrence of the substring within the string, + * or NULL if the substring is not found. + */ +static char *strcstr(const char *str, const char *substr) +{ + const char *p; + const char *q; + + if (*substr == '\0') + { + return (char *) str; + } + + while (*str != '\0') + { + p = str; + q = substr; + + while (*p != '\0' && *q != '\0' && tolower(*p) == tolower(*q)) + { + p++; + q++; + } + + if (*q == '\0') + { + return (char *) str; + } + + str++; + } + + return NULL; +} + +/** + * \brief Find a substring within a string, with matching options. + * + * \param[in] s The string to search within + * \param[in] d The substring to search for + * \param[in] flag The matching options + * + * \return Returns 1 if the substring is found, 0 otherwise + */ +static int strfind(const char *s, const char *d, int flag) +{ + if (flag & CSV_F_FLAG_MatchEntire) + { + if (flag & CSV_F_FLAG_MatchCase) + { + /* Compare the strings case-sensitively */ + if (!strcmp(s, d)) + { + return 1; /* Substring found */ + } + } + else + { + /* Compare the strings case-insensitively */ + if (!strccmp(s, d)) + { + return 1; /* Substring found */ + } + } + } + else + { + if (flag & CSV_F_FLAG_MatchCase) + { + /* Check if the substring is present in the string case-sensitively */ + if (strstr(s, d)) + { + return 1; /* Substring found */ + } + } + else + { + /* Check if the substring is present in the string case-insensitively */ + if (strcstr(s, d)) + { + return 1; /* Substring found */ + } + } + } + return 0; /* Substring not found */ +} + +/** + * \brief Find the first occurrence of a text within a CSV table. + * + * \param[in] csv csv handle + * \param[in] text The text to search for + * \param[in] flag The matching options + * \param[out] row The index of the row where the text is found + * \param[out] col The index of the column where the text is found + * + * \return Returns 1 if the text is found, 0 if the text is not found, or -1 if the search ends + */ +int csv_find(csv_t csv, const char* text, int flag, unsigned int* row, unsigned int* col) +{ + static csv_t s = NULL; + static char *cmp = NULL; + static unsigned int match_row = 0, match_col = 0, *x, *y; + static unsigned int size_row = 0, size_col = 0, *size_x, *size_y; + static int move_x, move_y; + char *t; + + /* Check if the CSV data structure exists */ + if (!csv) return 0; + if (!text) return 0; + if (text[0] == '\0') return 0; + if (!row || !col) return 0; + + /* Update lookup items */ + if (s != csv || cmp != text) + { + s = csv; + cmp = text; + + size_row = csv_row(csv); + size_col = csv_col(csv); + + move_x = 1; + move_y = 1; + + if (flag & CSV_F_FLAG_MatchByCol) + { + x = &match_col; + y = &match_row; + size_x = &size_col; + size_y = &size_row; + } + else + { + x = &match_row; + y = &match_col; + size_x = &size_row; + size_y = &size_col; + } + + if (flag & CSV_F_FLAG_MatchForward) + { + move_x = -move_x; + move_y = -move_y; + *x = *size_x; + *y = *size_y; + } + else + { + *x = 1; + *y = 1; + } + } + + for (; 1 <= *x && *x <= *size_x; *x += move_x) + { + for (; 1 <= *y && *y <= *size_y; *y += move_y) + { + t = csv_get_text(csv, match_row, match_col); + if (t) + { + if (strfind(t, cmp, flag)) + { + *row = match_row; + *col = match_col; + + *y += move_y; + + return 1; /* Text found */ + } + } + } + + /* Update iteration position */ + *y = (flag & CSV_F_FLAG_MatchForward) ? *size_y : 1; + } + + return -1; /* Found end */ +} + diff --git a/source/05_parser/csv.h b/source/05_parser/csv.h new file mode 100644 index 0000000..57e257e --- /dev/null +++ b/source/05_parser/csv.h @@ -0,0 +1,123 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file csv.h + * \unit csv + * \brief This is a C language version of csv excel parser + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __csv_H +#define __csv_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/* version infomation */ + +#define CSV_V_MAJOR 1 +#define CSV_V_MINOR 0 +#define CSV_V_PATCH 0 + +/* csv type definition, hiding structural members, not for external use */ + +typedef struct CSV* csv_t; + +/* error type */ + +#define CSV_E_OK (0) /* no error */ +#define CSV_E_MEMORY (1) /* memory allocation failed */ +#define CSV_E_OPEN (2) /* fail to open file */ + +/* find flag */ + +#define CSV_F_FLAG_MatchCase (0x01) /* match case sensitive */ +#define CSV_F_FLAG_MatchEntire (0x02) /* match the entire cell content */ +#define CSV_F_FLAG_MatchByCol (0x04) /* match by column */ +#define CSV_F_FLAG_MatchForward (0x08) /* match from back to front */ + +/* load csv */ + +csv_t csv_loads(const char* text); +csv_t csv_file_load(const char* filename); + +/* when loading fails, use this method to locate the error */ + +int csv_error_info(int* line, int* column); + +/* dump csv */ + +char* csv_dumps(csv_t csv, int* len); +int csv_file_dump(csv_t csv, const char* filename); + +/* create and delete csv */ + +csv_t csv_create(unsigned int row, unsigned int col, const void *array); +void csv_delete(csv_t csv); + +/* conversion operation function */ + +csv_t csv_duplicate(csv_t csv); +int csv_to_array(csv_t csv, unsigned int o_row, unsigned int o_col, void *array, unsigned int row_size, unsigned int col_size); +void csv_minify(csv_t csv); + +/* get the number of CSV rows, columns, cells */ + +unsigned int csv_row(csv_t csv); +unsigned int csv_col(csv_t csv); +unsigned int csv_cell(csv_t csv); + +/* set and get CSV cell content */ + +int csv_set_text(csv_t csv, unsigned int row, unsigned int col, const char* text); +const char* csv_get_text(csv_t csv, unsigned int row, unsigned int col); +void csv_clean_text(csv_t csv, unsigned int row, unsigned int col); + +/* row and column operations */ + +int csv_insert_row(csv_t csv, unsigned int pos, const char **array, unsigned int count); +int csv_insert_col(csv_t csv, unsigned int pos, const char **array, unsigned int count); +int csv_delete_row(csv_t csv, unsigned int pos); +int csv_delete_col(csv_t csv, unsigned int pos); +int csv_move_row_to(csv_t csv, unsigned int pos, unsigned int dest); +int csv_move_col_to(csv_t csv, unsigned int pos, unsigned int dest); +int csv_copy_row_to(csv_t csv, unsigned int pos, unsigned int dest); +int csv_copy_col_to(csv_t csv, unsigned int pos, unsigned int dest); + +/* cell operations */ + +int csv_insert_cell(csv_t csv, unsigned int row, unsigned int col, int move_down); +int csv_delete_cell(csv_t csv, unsigned int row, unsigned int col, int move_up); +int csv_copy_cell_to(csv_t csv, unsigned int s_row, unsigned int s_col, unsigned int d_row, unsigned int d_col); +int csv_cut_cell_to(csv_t csv, unsigned int s_row, unsigned int s_col, unsigned int d_row, unsigned int d_col); + +/* Find operation */ + +int csv_find(csv_t csv, const char* text, int flag, unsigned int* row, unsigned int* col); + +/* Universal traversal method. */ +#define csv_for_each(csv, row, col, text) \ +for ( \ + (row) = 1, (col) = 1; \ + (csv) && ( \ + (((text) = csv_get_text((csv), (row), (col))) == NULL) ? \ + ((row) < csv_row(csv) ? 1 : 0) : \ + 1 \ + ); \ + (text) ? \ + ((col)++) : \ + ((row)++, (col) = 1) \ +) if (text) \ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/05_parser/ini.c b/source/05_parser/ini.c new file mode 100644 index 0000000..3e341c1 --- /dev/null +++ b/source/05_parser/ini.c @@ -0,0 +1,1400 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file ini.h + * \unit ini + * \brief This is a C language version of ini parser + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "ini.h" +#include +#include +#include + +/* dump buffer define */ +typedef struct +{ + char* address; /**< buffer base address */ + unsigned int size; /**< size of buffer */ + unsigned int end; /**< end of buffer used */ +} BUFFER; + +/* iterator define */ +typedef struct +{ + void *p; /**< iteration pointer */ + unsigned int i; /**< iteration index */ +} ITERATOR; + +/* key-value define */ +typedef struct PAIR +{ + struct PAIR *next; /**< next pair */ + char* key; /**< key */ + char* value; /**< value */ +} PAIR; + +/* section define */ +typedef struct SECTION +{ + struct SECTION *next; /**< next section */ + char* name; /**< section name */ + PAIR* pairs; /**< pairs base */ + ITERATOR iterator; /**< pair iterator */ + int count; /**< pair count */ +} SECTION; + +/* ini structure define */ +typedef struct INI +{ + SECTION* sections; /**< sections base */ + ITERATOR iterator; /**< section iterator */ + int count; /**< section count */ +} INI; + +static int etype = 0; /**< error type */ +static int eline = 0; /**< error line */ + +#define E(type) etype=(type) +#define iscomment(c) ((c) == '#' || (c) == ';') /* ini supports `#` and `;` style annotations */ + +/** + * \brief Calculate the smallest power of 2 that is greater than or equal to a given number. + * + * \param[in] x The given number. + * \return The smallest power of 2 greater than or equal to x. + */ +static unsigned int pow2gt(unsigned int x) +{ + int b = sizeof(int) * 8; + int i = 1; + + --x; + while (i < b) + { + x |= (x >> i); + i <<= 1; + } + + return x + 1; +} + +/** + * \brief Duplicate a given string. + * + * \param[in] str String to be duplicated. + * \param[in] len Length of the string. + * \return Pointer to the duplicated string if successful, NULL otherwise. + */ +static char* ini_strdup(const char* str, int len) +{ + char* s; + + /* Allocate memory for the new string */ + s = (char*)malloc(len + 1); + if (!s) return NULL; + + /* Copy the given string into the allocated memory */ + memcpy(s, str, len); + s[len] = '\0'; + + return s; +} + +/** + * \brief Compare the first n1 characters of two strings. + * + * \param[in] s1 The first string to compare + * \param[in] n1 The number of characters to compare in s1 + * \param[in] s2 The second string to compare + * \param[in] n2 The number of characters to compare in s2 + * + * \return Returns an integer less than, equal to, or greater than zero if the first n1 characters of s1 is found, + * respectively, to be less than, to match, or be greater than the first n2 characters of s2. + */ +static int ini_strnncmp(const char* s1, int n1, const char* s2, int n2) +{ + int i = 0; + if (s1 == s2) return 0; + if (!s1) return -1; + if (!s2) return 1; + if (n1 != n2) return n1 - n2; + + for (i = 0; i < n1; i++) + { + if (s1[i] != s2[i]) return (s1[i] - s2[i]); + } + + return 0; +} + +/** + * \brief Compare the first n1 characters of two strings case-insensitively. + * + * \param[in] s1 The first string to compare + * \param[in] n1 The number of characters to compare in s1 + * \param[in] s2 The second string to compare + * \param[in] n2 The number of characters to compare in s2 + * + * \return Returns an integer less than, equal to, or greater than zero if the first n1 characters of s1 is found, + * respectively, to be less than, to match, or be greater than the first n2 characters of s2. + */ +static int ini_strcsnncmp(const char* s1, int n1, const char* s2, int n2) +{ + int i = 0; + if (s1 == s2) return 0; + if (!s1) return -1; + if (!s2) return 1; + if (n1 != n2) return n1 - n2; + + for (i = 0; i < n1; i++) + { + if (tolower(s1[i]) != tolower(s2[i])) return (tolower(s1[i]) - tolower(s2[i])); + } + + return 0; +} + +/** + * \brief confirm whether buf still has the required capacity, otherwise add capacity. + * \param[in] buf: buf handle + * \param[in] needed: required capacity + * \return 1 success or 0 fail + */ +static int expansion(BUFFER* buf, unsigned int needed) +{ + char* address; + unsigned int size; + if (!buf || !buf->address) return 0; + needed += buf->end; + if (needed <= buf->size) return 1; /* there is still enough space in the current buf */ + size = pow2gt(needed); + address = (char*)realloc(buf->address, size); + if (!address) return 0; + buf->size = size; + buf->address = address; + return 1; +} +#define buf_append(n) expansion(buf, (n)) /* append n size space for buf */ +#define buf_putc(c) (buf->address[buf->end++]=(c)) /* put a non zero character into buf */ +#define buf_end() (buf->address[buf->end]) /* obtain the tail of buf */ + +/** + * \brief Get the section at the specified index in an INI file. + * + * \param[in] ini The INI file object + * \param[in] index The index of the section to retrieve + * + * \return Returns a pointer to the section at the specified index, or NULL if the index is out of range + */ +static SECTION* ini_section(ini_t ini, int index) +{ + if (index >= ini->count) return NULL; + if (index < ini->iterator.i || !ini->iterator.p || index == 0) + { + ini->iterator.i = 0; + ini->iterator.p = ini->sections; + } + while (ini->iterator.p && ini->iterator.i < index) + { + ini->iterator.p = ((SECTION *)(ini->iterator.p))->next; + ini->iterator.i++; + } + return ini->iterator.p; +} + +/** + * \brief Get the pair at the specified index in a section of an INI file. + * + * \param[in] sect The section object + * \param[in] index The index of the pair to retrieve + * + * \return Returns a pointer to the pair at the specified index, or NULL if the index is out of range + */ +static PAIR* section_pair(SECTION *sect, int index) +{ + if (index >= sect->count) return NULL; + if (index < sect->iterator.i || !sect->iterator.p || index == 0) + { + sect->iterator.i = 0; + sect->iterator.p = sect->pairs; + } + while (sect->iterator.p && sect->iterator.i < index) + { + sect->iterator.p = ((SECTION *)(sect->iterator.p))->next; + sect->iterator.i++; + } + return sect->iterator.p; +} + +/** + * \brief create an ini object. + * \param[in] none + * \return ini object + */ +ini_t ini_create(void) +{ + ini_t ini = NULL; + + /* Allocate ini structure space */ + ini = (ini_t)malloc(sizeof(INI)); + if (!ini) return NULL; + + /* Initialize ini structure member variables */ + ini->sections = NULL; + ini->count = 0; + ini->iterator.p = NULL; + ini->iterator.i = 0; + + return ini; +} + +/** + * \brief Free the memory allocated for a section in an INI file. + * + * \param[in] sect The section object to be freed + */ +static void section_free(SECTION *sect) +{ + PAIR *pair = NULL, *next = NULL; + + if (!sect) return; + + /* Traverse and free each pair */ + pair = sect->pairs; + while (pair) + { + next = pair->next; + if (pair->key) free(pair->key); + if (pair->value) free(pair->value); + free(pair); + pair = next; + } + if (sect->name) free(sect->name); + free(sect); +} + +/** + * \brief delete an ini object. + * \param[in] ini object + * \return none + */ +void ini_delete(ini_t ini) +{ + SECTION *sect = NULL, *next = NULL; + + if (!ini) return; + + /* Traverse and free each section */ + sect = ini->sections; + while (sect) + { + next = sect->next; + + section_free(sect); + + sect = next; + } + + free(ini); +} + +/** + * \brief Find the index of a section in an INI file with a specified name. + * + * \param[in] ini The INI file object + * \param[in] section The name of the section to find + * \param[in] len The length of the section name + * + * \return Returns the index of the section if found, or -1 if the section is not found + */ +static int find_section(ini_t ini, const char* section, int len) +{ + int i; + SECTION* sect = NULL; + + if (!ini) return -1; + + /* Find if a section with the same name already exists */ + for (i = 0; i < ini->count; i++) + { + sect = ini_section(ini, i); + if (ini_strnncmp(sect->name, strlen(sect->name), section, len) == 0) + { + return i; + } + } + + return -1; +} + +/** + * \brief find the index where the section is located. + * \param[in] ini: ini handle + * \param[in] *section: section name + * \return index or negative number not found + */ +int ini_section_index(ini_t ini, const char* section) +{ + int i; + SECTION* sect = NULL; + + if (!ini) return -1; + if (!section) return -1; + + if (ini->iterator.p) + { + /* at the current iteration position */ + if (strcmp(((SECTION*)(ini->iterator.p))->name, section) == 0) + { + return ini->iterator.i; + } + } + + /* Find if a section with the same name already exists */ + for (i = 0; i < ini->count; i++) + { + sect = ini_section(ini, i); + if (strcmp(sect->name, section) == 0) + { + return i; + } + } + + return -1; +} + +/** + * \brief get the section name of the specified index. + * \param[in] ini: ini handle + * \param[in] index: index + * \return section name or NULL fail + */ +const char* ini_section_name(ini_t ini, int index) +{ + SECTION* sect = NULL; + + if (!ini) return NULL; + if (index < 0 || index >= ini->count) return NULL; + + sect = ini_section(ini, index); + if (!sect) return NULL; + + return sect->name; +} + +/** + * \brief Add a new section to an INI file with the specified name. + * + * \param[in] ini The INI file object + * \param[in] section The name of the section to add + * \param[in] len The length of the section name + * + * \return Returns a pointer to the newly added section, or NULL if the section cannot be added + */ +static SECTION* add_section(ini_t ini, const char* section, int len) +{ + SECTION* sect = NULL; + + /* Create a new section and add it to the INI file structure */ + sect = (SECTION*)malloc(sizeof(SECTION)); + if (!sect) return NULL; + + /* Allocate space for section name */ + sect->name = ini_strdup(section, len); + if (!sect->name) + { + free(sect); + return NULL; + } + + /* Initialize section structure variables */ + sect->pairs = NULL; + sect->count = 0; + sect->iterator.p = NULL; + sect->iterator.i = 0; + sect->next = NULL; + + /* Link the new section to ini */ + if (ini->count == 0) ini->sections = sect; + else ini_section(ini, ini->count - 1)->next = sect; + + ini->count++; + + return sect; +} + +/** + * \brief Find the index of a pair in a section of an INI file with a specified key. + * + * \param[in] sect The section object + * \param[in] key The key of the pair to find + * \param[in] len The length of the key + * + * \return Returns the index of the pair if found, or -1 if the pair is not found + */ +static int find_pair(SECTION *sect, const char *key, int len) +{ + int i = 0; + PAIR *pair = NULL; + + if (sect->iterator.p) + { + /* at the current iteration position */ + if (ini_strcsnncmp(((PAIR*)(sect->iterator.p))->key, strlen(((PAIR*)(sect->iterator.p))->key), key, len) == 0) + { + return sect->iterator.i; + } + } + + /* Traverse the pairs in the section and match the same key */ + for (i = 0; i < sect->count; i++) + { + pair = section_pair(sect, i); + if (ini_strcsnncmp(pair->key, strlen(pair->key), key, len) == 0) + { + return i; + } + } + + return -1; /* No match found */ +} + +/** + * \brief Add a new key-value pair to a section in an INI file. + * + * \param[in] sect The section object + * \param[in] key The key to add + * \param[in] key_len The length of the key + * \param[in] value The value to add + * \param[in] value_len The length of the value + * + * \return Returns a pointer to the newly added pair, or NULL if the pair cannot be added + */ +static PAIR* add_pair(SECTION* sect, const char* key, int key_len, const char* value, int value_len) +{ + PAIR* pair; + + if (!sect) return NULL; + + /* Allocate pair space and initialize */ + pair = (PAIR *)malloc(sizeof(PAIR)); + if (!pair) return NULL; + pair->next = NULL; + pair->key = NULL; + pair->value = NULL; + + /* duplicate the key */ + if (key) + { + pair->key = ini_strdup(key, key_len); + if (!pair->key) goto FAIL; + } + + /* Duplicate the value */ + if (value) + { + pair->value = ini_strdup(value, value_len); + if (!pair->value) goto FAIL; + } + + /* Link the new pair to section */ + if (sect->count > 0) section_pair(sect, sect->count - 1)->next = pair; + else sect->pairs = pair; + + sect->count++; + + return pair; + +FAIL: + /* Free the allocated space before exiting the function */ + if (pair) + { + if (pair->key) free(pair->key); + if (pair->value) free(pair->value); + free(pair); + } + return NULL; +} + +/** + * \brief add section to ini. + * \param[in] ini: ini handle + * \param[in] *section: section name + * \return 1 success or 0 fail + */ +int ini_add_section(ini_t ini, const char* section) +{ + int i; + + if (!ini) return 0; + if (!section) return 0; + + /* Find if a section with the same name already exists */ + if (ini_section_index(ini, section) >= 0) return 0; + + /* Calculate the length of section name */ + for (i = 0;;i++) + { + if (section[i] == '\0') break; + // if (!(section[i] >= ' ' && section[i] <= '~')) return 0; /* Not a printable character */ + } + + return add_section(ini, section, i) == NULL ? 0 : 1; +} + +/** + * \brief remove section from ini. + * \param[in] ini: ini handle + * \param[in] *section: section name + * \return 1 success or 0 fail + */ +int ini_remove_section(ini_t ini, const char* section) +{ + int i; + SECTION* sect = NULL; + SECTION* prev = NULL; + + if (!ini) return 0; + if (!section) return 0; + + /* Find if a section with the same name already exists */ + for (i = 0; i < ini->count; i++) + { + sect = ini_section(ini, i); + if (strcmp(sect->name, section) == 0) break; + prev = sect; + } + if (i == ini->count || sect == NULL) return 0; + + /* Adjusting linked list */ + if (prev) prev->next = sect->next; + else ini->sections = sect->next; + + section_free(sect); + + ini->count--; + + /* reset iterator */ + ini->iterator.p = NULL; + + return 1; +} + +/** + * \brief set the specified value, it will be created if not exist. + * \param[in] ini: ini handle + * \param[in] *section: section name + * \param[in] *key: key + * \param[in] *value: value + * \return 1 success or 0 fail + */ +int ini_set_value(ini_t ini, const char* section, const char* key, const char* value) +{ + int i; + char *tvalue = NULL; + SECTION *sect = NULL; + PAIR *pair = NULL; + + if (!ini) return 0; + if (!section) return 0; + if (!key) return 0; + if (!value) return 0; + + i = ini_section_index(ini, section); + if (i < 0) /* Add this section without it */ + { + /* Calculate the length of section name */ + for (i = 0;;i++) + { + if (section[i] == '\0') break; + // if (!(section[i] >= ' ' && section[i] <= '~')) return 0; /* Not a printable character */ + } + if (add_section(ini, section, i) == NULL) return 0; + sect = ini_section(ini, ini->count - 1); + } + else + { + sect = ini_section(ini, i); + } + + i = find_pair(sect, key, strlen(key)); + if (i < 0) /* Add this pair without it */ + { + /* Calculate the length of section name */ + for (i = 0;;i++) + { + if (key[i] == '\0') break; + // if (!(key[i] >= ' ' && key[i] <= '~')) return 0; /* Not a printable character */ + } + if (add_pair(sect, key, i, value, strlen(value)) == NULL) return 0; + } + else + { + pair = section_pair(sect, i); + + /* Duplicate the value to be set first */ + tvalue = ini_strdup(value, strlen(value)); + if (!tvalue) return 0; + + /* The original value needs to be free */ + if (pair->value) free(pair->value); + + /* Update value */ + pair->value = tvalue; + } + + return 1; +} + +/** + * \brief get the specified value. + * \param[in] ini: ini handle + * \param[in] *section: section name + * \param[in] *key: key + * \return address of value or NULL fail + */ +const char* ini_get_value(ini_t ini, const char* section, const char* key) +{ + int i = 0; + SECTION *sect = NULL; + PAIR *pair = NULL; + + if (!ini) return NULL; + if (!section) return NULL; + if (!key) return NULL; + + /* Obtain the corresponding index based on the section name */ + i = ini_section_index(ini, section); + if (i < 0) return NULL; + + /* Obtain the section handle based on the index */ + sect = ini_section(ini, i); + if (!sect) return NULL; + + /* Obtain the corresponding index based on the key name */ + i = find_pair(sect, key, strlen(key)); + if (i < 0) return NULL; + + /* Obtain the pair handle based on the index */ + pair = section_pair(sect, i); + if (!pair) return NULL; + + return pair->value; +} + +/** + * \brief get the index of the specified key under the section. + * \param[in] ini: ini handle + * \param[in] *section: section name + * \param[in] *key: key + * \return index or negative number not found + */ +int ini_key_index(ini_t ini, const char* section, const char* key) +{ + int i = 0; + SECTION *sect = NULL; + PAIR *pair = NULL; + + if (!ini) return -1; + if (!section) return -1; + if (!key) return -1; + + /* Obtain the corresponding index based on the section name */ + i = ini_section_index(ini, section); + if (i < 0) return -1; + + /* Obtain the section handle based on the index */ + sect = ini_section(ini, i); + if (!sect) return -1; + + /* Matching key */ + i = find_pair(sect, key, strlen(key)); + if (i < 0) return -1; + + return i; +} + +/** + * \brief get the key name of the specified index under the section. + * \param[in] ini: ini handle + * \param[in] *section: section name + * \param[in] index: index + * \return key name or NULL fail + */ +const char* ini_key_name(ini_t ini, const char* section, int index) +{ + int i = 0; + SECTION *sect = NULL; + PAIR *pair = NULL; + + if (!ini) return NULL; + if (!section) return NULL; + if (index < 0) return NULL; + + /* Obtain the corresponding index based on the section name */ + i = ini_section_index(ini, section); + if (i < 0) return NULL; + + /* Obtain the section handle based on the index */ + sect = ini_section(ini, i); + if (!sect) return NULL; + + /* Matching index */ + pair = section_pair(sect, index); + if (!pair) return NULL; + + return pair->key; +} + +/** + * \brief get the section count of ini. + * \param[in] ini: ini handle + * \return section count of ini + */ +int ini_section_count(ini_t ini) +{ + if (!ini) return 0; + return ini->count; +} + +/** + * \brief get the pair count of ini section. + * \param[in] ini: ini handle + * \param[in] *section: section name + * \return pair count of ini section + */ +int ini_pair_count(ini_t ini, const char* section) +{ + SECTION *sect = NULL; + int index = 0; + + if (!ini) return 0; + + /* Obtain the corresponding index based on the section name */ + index = ini_section_index(ini, section); + if (index < 0) return 0; + + /* Obtain the section handle based on the index */ + sect = ini_section(ini, index); + if (!sect) return 0; + + return sect->count; +} + +/** + * \brief remove key from ini section. + * \param[in] ini: ini handle + * \param[in] *section: section name + * \param[in] *key: key + * \return 1 success or 0 fail + */ +int ini_remove_key(ini_t ini, const char* section, const char* key) +{ + int i = 0; + SECTION *sect = NULL; + PAIR *pair = NULL, *prev = NULL; + + if (!ini) return 0; + if (!section) return 0; + if (!key) return 0; + + /* Obtain the corresponding index based on the section name */ + i = ini_section_index(ini, section); + if (i < 0) return 0; + + /* Obtain the section handle based on the index */ + sect = ini_section(ini, i); + if (!sect) return 0; + + /* Traverse section matching key */ + for (i = 0; i < sect->count; i++) + { + pair = section_pair(sect, i); + if (ini_strcsnncmp(pair->key, strlen(pair->key), key, strlen(key)) == 0) break; + prev = pair; + } + if (i == sect->count) return 0; /* Not matched */ + + /* Adjusting linked list */ + if (prev) prev->next = pair->next; + else sect->pairs = pair->next; + + /* Free pair space */ + if (pair->key) free(pair->key); + if (pair->value) free(pair->value); + free(pair); + + sect->count--; + + /* Reset iterator */ + sect->iterator.p = NULL; + + return 1; +} + +/** + * \brief Print the contents of an INI file to a buffer. + * + * \param[in] ini The INI file object + * \param[in] buf The buffer to print the INI contents to + * + * \return Returns 1 if the printing is successful, 0 otherwise + */ +static int print_ini(ini_t ini, BUFFER* buf) +{ + int i, j, k; + int olen = 0; + SECTION* sect = NULL; + PAIR* pair = NULL; + + /* Traverse each section */ + for (i = 0; i < ini->count; i++) + { + /* Get section handle */ + sect = ini_section(ini, i); + if (!sect) return 0; + + /* Print section name */ + olen = strlen(sect->name); + if (!buf_append(olen + 3)) return 0; + buf_putc('['); + for (k = 0; k < olen; k++) + { + buf_putc(sect->name[k]); + } + buf_putc(']'); + buf_putc('\n'); + + /* Traverse each pair */ + for (j = 0; j < sect->count; j++) + { + /* Get pair handle */ + pair = section_pair(sect, j); + + /* Print key */ + olen = strlen(pair->key); + if (!buf_append(olen + 3)) return 0; + for (k = 0; k < olen; k++) + { + buf_putc(tolower(pair->key[k])); + } + buf_putc(' '); + buf_putc('='); + buf_putc(' '); + + /* Get length of value */ + olen = 0; + k = 0; + while (1) + { + if (!pair->value[olen]) break; + if (pair->value[olen] == '\n') k++; /* Print `\t` before line breaks */ + olen++; + } + if (!buf_append(olen + k + 1)) return 0; + + /* Print value */ + for (k = 0; k < olen; k++) + { + buf_putc(pair->value[k]); + if (pair->value[k] == '\n') buf_putc('\t'); + } + buf_putc('\n'); + } + + /* Add a blank line to distinguish each section */ + if (!buf_append(1)) return 0; + buf_putc('\n'); + } + + /* End of text */ + if (!buf_append(1)) return 0; + buf_end() = '\0'; + + return 1; +} + +/** + * \brief dump the ini object into a string. + * \param[in] ini: ini handle + * \param[in] preset: preset string length + * \param[out] *len: the length of the string actually dumped + * \return dumped string, please release this string space after use + */ +char* ini_dumps(ini_t ini, int preset, int *len) +{ + BUFFER p; + + if (!ini) return NULL; + + /* initialize buffer */ + if (preset < 1) preset = 1; + p.address = (char*)malloc(preset); + if (!p.address) return NULL; + p.size = preset; + p.end = 0; + + /* Print the ini data to the BUFFER structure */ + if (!print_ini(ini, &p)) + { + free(p.address); + return NULL; + } + + /* Store the length of the generated ini string if the len parameter is provided */ + if (len) *len = p.end; + + return p.address; +} + +/** + * \brief dump the ini object into a file. + * \param[in] ini: ini handle + * \param[in] *filename: file name + * \return length of the dump or negative fail + */ +int ini_file_dump(ini_t ini, char* filename) +{ + FILE* f; + char* out; + int len; + + if (!ini) return -1; + + out = (char *)ini_dumps(ini, 0, &len); + if (!out) return -1; + + f = fopen(filename, "w"); + if (!f) + { + free(out); + return -1; + } + + fwrite(out, 1, len - 1, f); + fclose(f); + + free(out); + + return len; +} + +/** + * \brief Skip leading spaces, tabs, and carriage returns in a string. + * + * \param[in] text The input string to be skipped + * + * \return Returns a pointer to the first non-space, non-tab, non-carriage return character in the string + */ +static const char* skip(const char* text) +{ + while (*text && (*text == ' ' || *text == '\r' || *text == '\t')) + { + text++; // Skip spaces, tabs, and carriage returns + } + return text; +} + +/** + * \brief Skip trailing spaces, tabs, and carriage returns in a string, starting from the end and moving backwards. + * + * \param[in] text The input string to be skipped + * \param[in] base The base of the string to limit the backward movement + * + * \return Returns a pointer to the last non-space, non-tab, non-carriage return character in the string + */ +static const char* rskip(const char* text, const char* base) +{ + while ((text >= base) && (*text == ' ' || *text == '\r' || *text == '\t')) + { + text--; + } + return text; +} + +/** + * \brief Find the end of the current line in a string. + * + * \param[in] text The input string + * + * \return Returns a pointer to the end of the current line in the string + */ +static const char* lend(const char* text) +{ + while (*text && *text != '\n') + { + text++; // Move to the next character until the end of the line or the end of the string is reached + } + return text; // Return the pointer to the end of the line +} + +/** + * \brief Find the scope of a value in a string, considering the given depth. + * + * \param[in] text The input string + * \param[in] depth The depth of the scope to consider + * + * \return Returns a pointer to the end of the scope of the value in the string + */ +static const char* value_scope(const char* text, int depth) +{ + const char *s = text; + const char *sentinel = NULL; + + s = lend(s); + while (*s) + { + /* Sentinel locate non empty characters on a new line */ + sentinel = skip(s + 1); + + /* skip comments */ + if (iscomment(*sentinel)) + { + s = lend(sentinel); + continue; + } + + /* The depth of the current row is greater than the depth of the section, indicating that it still belongs to that section */ + if (sentinel - s - 1 > depth) + { + s = sentinel; + s = lend(s); + } + /* Exceeding the depth, determine whether to stop detection based on the current sentinel character */ + else + { + /* If it is not at the end of the text or line, it indicates that this is a valid character. + * If it exceeds the scope of the section, it will not belong to the scope of this value and can exit the detection. + */ + if (*sentinel != 0 && *sentinel != '\n') break; + /* As an empty line, it is still within the scope of the value */ + else s = sentinel; + } + } + + /* Starting from the position of the sentry, probe back and remove empty lines (as there are no actual characters) */ + while (*s == 0 || *s == '\n') + { + /* Starting from the line break position and detecting backwards */ + sentinel = rskip(s - 1, text); + if (*sentinel != 0 && *sentinel != '\n') break; + + /* Fallback by an empty line */ + s = sentinel; + } + + return s; +} + +/** + * \brief load ini from string. + * \param[in] *text: string text + * \return ini handle or NULL fail + */ +ini_t ini_loads(const char* text) +{ + ini_t ini; + const char *s = NULL, *tail = NULL; + const char *scope = NULL; + char *value = NULL; + int len = 0; + int depth = 0; /* current line depth */ + SECTION *sect = NULL; + PAIR *pair = NULL; + + if (!text) return NULL; + ini = ini_create(); + if (!ini) return NULL; + + eline = 1; + etype = INI_E_OK; + + while (*text) + { + /* skip useless characters */ + s = skip(text); + depth = s - text; + text = s; + + /* end of text, exit parsing */ + if (*text == 0) break; + + /* skip comments */ + if (iscomment(*text)) + { + text = lend(text); + if (*text == '\n') + { + text++; + eline++; + continue; + } + } + + /* Exceeded the range of the previous value and does not belong to the previous value. Starting a new parsing */ + if (text >= scope) + { + /* Blank line, skip */ + if (*text == '\n') + { + text++; + eline++; + continue; + } + + /* Parsing section */ + if (*text == '[') + { + /* Locate the end of the line to determine the range of section names */ + s = lend(text); + tail = rskip(s - 1, text); + + /* The square brackets `[]` do not appear in pairs, and the section name does not hold */ + if (*tail != ']') + { + E(INI_E_BRACKETS); + goto FAIL; + } + + /* Only one pair of empty square brackets `[]` without a section name */ + if (tail - text - 1 <= 0) + { + E(INI_E_SECTION); + goto FAIL; + } + + /* Check for duplicates and determine if the section already exists */ + if (find_section(ini, text + 1, tail - text - 1) >= 0) + { + E(INI_E_RESECTION); + goto FAIL; + } + + /* Add section to ini */ + sect = add_section(ini, text + 1, tail - text - 1); + if (!sect) + { + E(INI_E_MEMORY); + goto FAIL; + } + + /* Update parsing location */ + text = s; + pair = NULL; + } + /* Parsing key-value pair */ + else + { + s = text; + tail = NULL; + + /* Parse the key and determine its scope */ + while (*s) + { + /* The key value pair is incomplete as it breaks a line before parsing the delimiter */ + if (*s == '\n') + { + E(INI_E_DELIM); + goto FAIL; + } + + /* Parsed key value delimiter, the scope of the key has been determined */ + if (*s == '=' || *s == ':') + { + tail = s - 1; + tail = rskip(tail, text); + break; + } + + s++; + } + + /* Confirmed the scope of the key and determined whether the key is empty */ + if (!tail || tail < text) + { + E(INI_E_KEY); + goto FAIL; + } + + /* When there is no record of the current section, even if the key is parsed, it is invalid */ + if (!sect) + { + E(INI_E_SECTION); + goto FAIL; + } + + /* Check for duplicates and determine if the key already exists */ + if (find_pair(sect, text, tail - text + 1) >= 0) + { + E(INI_E_REKEY); + goto FAIL; + } + + /* Add pair to section */ + pair = add_pair(sect, text, tail - text + 1, "", 0); + if (!pair) + { + E(INI_E_MEMORY); + goto FAIL; + } + + /* Update parsing location */ + len = 0; + text = s + 1; /* skip delimiter '=' and ':' */ + + /* After determining the key, we need to explore the scope of the value. + * + * The indentation depth of the incoming section is used to determine + * whether the depth of the value is within the depth range of the section. + */ + scope = value_scope(text, depth); + } + } + /* This line belongs to the previous line, still within the range of the previous value */ + else + { + /* There is no record of pair, which means there is no key, and the key value pair is not valid */ + if (!pair) + { + E(INI_E_MEMORY); + goto FAIL; + } + + /* Value can contain multiple lines. + * If the first line value after the key is empty, record the `\n` first + */ + if (*text == '\n') + { + value = realloc(pair->value, len + 1); + if (!value) + { + E(INI_E_MEMORY); + goto FAIL; + } + pair->value = value; + pair->value[len++] = '\n'; + pair->value[len] = 0; + } + + text = skip(text); + + /* Empty line, skip first */ + if (*text == '\n') + { + text++; + eline++; + continue; + } + + /* Go to the end of the current line and temporarily do not record blank line endings. + * + * If the next line is still within the scope of value, space will be reallocated next time, + * and these blank characters will be added. + */ + tail = lend(text); + s = rskip(tail - 1, text); + + /* Reassign space and append the content of the current line */ + value = realloc(pair->value, len + (s - text + 1)); + if (!value) + { + E(INI_E_MEMORY); + goto FAIL; + } + pair->value = value; + + /* Assign and update length */ + memcpy(&pair->value[len], text, (s - text + 1)); + len += (s - text + 1); + pair->value[len] = 0; + + /* Update parsing location */ + text = tail; + } + } + + return ini; + +FAIL: + ini_delete(ini); + return NULL; +} + +/** + * \brief load ini from file. + * \param[in] *filename: file name + * \return ini handle or NULL fail + */ +ini_t ini_file_load(const char* filename) +{ + FILE* f = NULL; + ini_t ini = NULL; + long len; + char* text = NULL; + + if (!filename) return NULL; + + eline = 0; + + /* open file and get the length of file */ + f = fopen(filename, "rb"); + if (!f) + { + E(INI_E_OPEN); + goto FAIL; + } + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + + /* read file */ + text = (char*)malloc(len + 1); + if (text) + { + fread(text, 1, len, f); + fclose(f); + f = NULL; + } + else + { + E(INI_E_MEMORY); + goto FAIL; + } + text[len] = 0; + + /* load text */ + ini = ini_loads(text); + if (!ini) + { + goto FAIL; + } + free(text); + + return ini; + +FAIL: + // printf("ini parse error! line %d, error %d.\r\n", eline, etype); + if (f) fclose(f); + if (text) free(text); + return NULL; +} + +/** + * \brief obtain parsing error information. + * \param[out] *line: line number where the error occurred + * \param[out] *type: error type, ref INI_E_XXX + * \return 1 has an error or 0 does not exist + */ +int ini_error_info(int* line, int* type) +{ + if (etype == INI_E_OK) return 0; + if (line) *line = eline; + if (type) *type = etype; + return 1; +} + diff --git a/source/05_parser/ini.h b/source/05_parser/ini.h new file mode 100644 index 0000000..ce7dac3 --- /dev/null +++ b/source/05_parser/ini.h @@ -0,0 +1,96 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file ini.h + * \unit ini + * \brief This is a C language version of ini parser + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __ini_H +#define __ini_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* version infomation */ + +#define INI_V_MAJOR 1 +#define INI_V_MINOR 0 +#define INI_V_PATCH 0 + +/* ini type definition, hiding structural members, not for external use */ + +typedef struct INI* ini_t; + +/* error type define */ + +#define INI_E_OK (0) /* ok */ +#define INI_E_BRACKETS (1) /* missing brackets ']' */ +#define INI_E_DELIM (2) /* missing delimiter */ +#define INI_E_KEY (3) /* missing key */ +#define INI_E_SECTION (4) /* missing section */ +#define INI_E_REKEY (5) /* key repeat */ +#define INI_E_RESECTION (6) /* section repeat */ +#define INI_E_MEMORY (7) /* memory allocation failed */ +#define INI_E_OPEN (8) /* fail to open file */ +#define INI_E_MAX (9) /* */ + +/* load ini */ + +ini_t ini_loads(const char* text); +ini_t ini_file_load(const char* filename); + +/* when loading fails, use this method to locate the error */ + +int ini_error_info(int* line, int* type); + +/* dump ini */ + +char* ini_dumps(ini_t ini, int preset, int *len); +int ini_file_dump(ini_t ini, char* filename); + +/* create and delete ini */ + +ini_t ini_create(void); +void ini_delete(ini_t ini); + +/* section name and index are mutually located */ + +int ini_section_index(ini_t ini, const char* section); +const char* ini_section_name(ini_t ini, int index); + +/* add and remove section */ + +int ini_add_section(ini_t ini, const char* section); +int ini_remove_section(ini_t ini, const char* section); + +/* key name and index are mutually located */ + +int ini_key_index(ini_t ini, const char* section, const char* key); +const char* ini_key_name(ini_t ini, const char* section, int index); + +/* set and get the value of the corresponding key in the specified section */ + +int ini_set_value(ini_t ini, const char* section, const char* key, const char* value); +const char* ini_get_value(ini_t ini, const char* section, const char* key); + +/* remove key value pairs, there is no specific method to add key value pairs, but instead `ini_set_value()` */ + +int ini_remove_key(ini_t ini, const char* section, const char* key); + +/* statistics section count and key value pair count under specified section */ + +int ini_section_count(ini_t ini); +int ini_pair_count(ini_t ini, const char* section); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/05_parser/json.c b/source/05_parser/json.c new file mode 100644 index 0000000..72025cc --- /dev/null +++ b/source/05_parser/json.c @@ -0,0 +1,2013 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file json.c + * \unit json + * \brief This is a C language version of json streamlined parser + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "json.h" +#include +#include +#include +#include +#include + +/* dump buffer define */ +typedef struct +{ + char* address; /**< buffer base address */ + unsigned int size; /**< size of buffer */ + unsigned int end; /**< end of buffer used */ +} BUFFER; + +/* json define */ +typedef struct JSON { + struct JSON* next; /**< next json */ + char* key; /**< the key of json is empty when the type is array */ + int type; /**< json base type, @ref JSON_TYPE_xxx */ + union + { + int bool_; /**< bool type */ + double float_; /**< float number type */ + int int_; /**< int number type */ + char* string_; /**< string type */ + struct JSON* child_; /**< array or object type */ + } value; +} JSON; + +static const char* lbegin = 0; /**< beginning of line */ +static int eline = 0; /**< line of error message */ +static int ecolumn = 0; /**< column of error message */ +static int etype = 0; /**< type of error message */ + +/* predeclare these prototypes. */ +static const char* parse_text(json_t json, const char* text); +static int print_json(json_t json, BUFFER* buf, int depth, int format); + +/* set error message and type */ +#define E(type) (etype=(type),ecolumn=text-lbegin) + +/** + * \brief for analysing failed parses + * \param[out] line: error line + * \param[out] column: error column + * \return error type + */ +int json_error_info(int* line, int* column) +{ + /* No error occurred, return directly */ + if (etype == JSON_E_OK) return JSON_E_OK; + + /* Output the line and column where the error is located */ + if (line) *line = eline; + if (column) *column = ecolumn; + + /* Return error type */ + return etype; +} + +/** + * \brief Compare two strings case-insensitively. + * + * \param[in] s1 The first string to compare + * \param[in] s2 The second string to compare + * + * \return Returns an integer less than, equal to, or greater than zero if s1 is found, respectively, + * to be less than, to match, or be greater than s2. + */ +static int json_strccmp(const char* s1, const char* s2) +{ + if (!s1) return (s1 == s2) ? 0 : 1; + if (!s2) return 1; + + /* Compare the similarities and differences of characters one by one */ + for (; tolower(*s1) == tolower(*s2); ++s1, ++s2) + { + if (*s1 == 0) return 0; + } + + return tolower(*(const unsigned char*)s1) - tolower(*(const unsigned char*)s2); +} + +/** + * \brief Duplicate a given string. + * + * \param[in] str String to be duplicated. + * \param[in] len Length of the string. + * \return Pointer to the duplicated string if successful, NULL otherwise. + */ +static char* json_strdup(const char* str, int len) +{ + char* s; + + /* Allocate memory for the new string */ + s = (char*)malloc(len + 1); + if (!s) return NULL; + + /* Copy the given string into the allocated memory */ + memcpy(s, str, len); + s[len] = '\0'; + + return s; +} + +/** + * \brief get the smallest power of 2 not greater than x. + * \param[in] x: positive integer + * \return the smallest power of 2 not greater than x + */ +static int pow2gt(int x) +{ + int b = sizeof(int) * 8; + int i = 1; + --x; + while (i < b) { x |= (x >> i); i <<= 1; } + return x + 1; +} + +/** + * \brief confirm whether buf still has the required capacity, otherwise add capacity. + * \param[in] buf: buf handle + * \param[in] needed: required capacity + * \return 1 success or 0 fail + */ +static int expansion(BUFFER *buf, unsigned int needed) +{ + char* address; + int size; + if (!buf || !buf->address) return 0; + needed += buf->end; + if (needed <= buf->size) return 1; /* there is still enough space in the current buf */ + size = pow2gt(needed); + address = (char*)realloc(buf->address, size); + if (!address) return 0; + buf->size = size; + buf->address = address; + return 1; +} +#define buf_append(n) expansion(buf, (n)) /* append n size space for buf */ +#define buf_putc(c) (buf->address[buf->end++]=(c)) /* put a non zero character into buf */ +#define buf_end() (buf->address[buf->end]) /* obtain the tail of buf */ + +static unsigned int parse_hex4(const char* str) +{ + unsigned int h = 0; + if (*str >= '0' && *str <= '9') h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; + else return 0; + h = h << 4; str++; + if (*str >= '0' && *str <= '9') h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; + else return 0; + h = h << 4; str++; + if (*str >= '0' && *str <= '9') h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; + else return 0; + h = h << 4; str++; + if (*str >= '0' && *str <= '9') h += (*str) - '0'; + else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A'; + else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a'; + else return 0; + return h; +} + +/** + * \brief Convert and write a UTF-8 character sequence to an output buffer. + * + * \param[in] in The input string containing the UTF-8 sequence + * \param[in,out] out The output buffer to write the converted UTF-8 sequence + */ +static void json_utf(const char **in, char **out) +{ + const char* ptr = *in; + char* ptr2 = *out; + int len = 0; + unsigned int uc, uc2; + unsigned char mask_first_byte[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + uc = parse_hex4(ptr + 1); ptr += 4; /* get the unicode char. */ + if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) return; /* check for invalid. */ + if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ + { + if (ptr[1] != '\\' || ptr[2] != 'u') return; /* missing second-half of surrogate */ + uc2 = parse_hex4(ptr + 3); + ptr += 6; + if (uc2 < 0xDC00 || uc2>0xDFFF) return; /* invalid second-half of surrogate */ + uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); + } + len = 4; + if (uc < 0x80) len = 1; + else if (uc < 0x800) len = 2; + else if (uc < 0x10000) len = 3; + ptr2 += len; + switch (len) + { + case 4: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6; + case 3: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6; + case 2: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6; + case 1: *--ptr2 = (uc | mask_first_byte[len]); + } + ptr2 += len; +} + +/** + * \brief Skip leading whitespace characters in a string, including newline characters. + * + * \param[in] in The input string to be skipped + * + * \return Returns a pointer to the first non-whitespace character in the string + */ +static const char* skip(const char* in) +{ + while (in && *in && (unsigned char)*in <= ' ') + { + /* when a newline character is encountered, record the current parsing line */ + if (*in == '\n') + { + eline++; + lbegin = in; /* Record line start position */ + } + + in++; + } + + return in; +} + +/** + * \brief create a null json object. + * \return json handle or NULL fail + */ +json_t json_create(void) +{ + json_t json; + + /* Allocate json structure space and initialize */ + json = (json_t)malloc(sizeof(JSON)); + if (json) memset(json, 0, sizeof(JSON)); + + return json; +} + +/** + * \brief delete the json entity and its sub-entities. + * \param[in] json: json handle + * \return none + */ +void json_delete(json_t json) +{ + json_t next; + while (json) + { + next = json->next; + + /* For arrays or object types, recursively delete child json */ + if (json->type == JSON_TYPE_ARRAY || json->type == JSON_TYPE_OBJECT) json_delete(json->value.child_); + /* String type, then free the string */ + else if (json->type == JSON_TYPE_STRING) free(json->value.string_); + + /* Free the key of json */ + if (json->key) free(json->key); + + /* Delete self */ + free(json); + + json = next; + } +} + +/** + * \brief get the number of children of a json array or object. + * \param[in] json: json handle + * \return json size + */ +int json_size(json_t json) +{ + json_t c; + int i = 0; + + if (!json) return 0; + + /* Only array and object have child objects */ + if (json->type != JSON_TYPE_ARRAY && json->type != JSON_TYPE_OBJECT) return 0; + + /* Traverse for statistics */ + c = json->value.child_; + while (c) + { + i++; + c = c->next; + } + + return i; +} + +/** + * \brief get the type of json. + * \param[in] json: json handle + * \return json type + */ +int json_type(json_t json) +{ + if (!json) return JSON_TYPE_UNKNOW; + return json->type; +} + +/** + * \brief get the key of json. + * \param[in] json: json handle + * \return json key + */ +const char* json_key(json_t json) +{ + if (!json) return NULL; + return json->key; +} + +/** + * \brief get the bool of json. + * \param[in] json: json handle + * \return json bool + */ +int json_value_bool(json_t json) +{ + if (!json) return 0; + if (json->type != JSON_TYPE_BOOL) return 0; + return json->value.bool_; +} + +/** + * \brief get the int of json. + * \param[in] json: json handle + * \return json int + */ +int json_value_int(json_t json) +{ + if (!json) return 0; + if (json->type != JSON_TYPE_INT) return 0; + return json->value.int_; +} + +/** + * \brief get the float of json. + * \param[in] json: json handle + * \return json float + */ +double json_value_float(json_t json) +{ + if (!json) return 0.0; + if (json->type != JSON_TYPE_FLOAT) return 0.0; + return json->value.float_; +} + +/** + * \brief get the string of json. + * \param[in] json: json handle + * \return json string + */ +const char* json_value_string(json_t json) +{ + if (!json) return NULL; + if (json->type != JSON_TYPE_STRING) return NULL; + return json->value.string_; +} + +/** + * \brief get the array of json. + * \param[in] json: json handle + * \return json array + */ +json_t json_value_array(json_t json) +{ + if (!json) return NULL; + if (json->type != JSON_TYPE_ARRAY) return NULL; + return json->value.child_; +} + +/** + * \brief get the object of json. + * \param[in] json: json handle + * \return json object + */ +json_t json_value_object(json_t json) +{ + if (!json) return NULL; + if (json->type != JSON_TYPE_OBJECT) return NULL; + return json->value.child_; +} + +/** + * \brief set the key of json. + * \param[in] json: json handle + * \param[in] key: new key + * \return json itself success or NULL fail + */ +json_t json_set_key(json_t json, const char* key) +{ + char* k; + + if (!json) return NULL; + + /* The current key and the one to be set can be the same, and can be set successfully directly */ + if (json->key && (json->key == key || !strcmp(json->key, key))) return json; + + /* If the passed in key is not empty, duplicate a backup */ + if (key) + { + k = json_strdup(key, strlen(key)); + if (!k) return NULL; + } + /* Otherwise, clear the json key */ + else k = NULL; + + /* Release the old key to update the new one */ + if (json->key) free(json->key); + json->key = k; + + return json; +} + +/** + * \brief set the value of json to null. + * \param[in] json: json handle + * \return json itself success or NULL fail + */ +json_t json_set_null(json_t json) +{ + if (!json) return NULL; + + /* delete string value */ + if (json->type == JSON_TYPE_STRING) free(json->value.string_); + /* delete child objects */ + else if (json->type == JSON_TYPE_ARRAY || json->type == JSON_TYPE_OBJECT) json_delete(json->value.child_); + + /* Change the type to null and reset the value */ + json->type = JSON_TYPE_NULL; + memset(&json->value, 0, sizeof(json->value)); + + return json; +} + +/** + * \brief set the value of json to bool. + * \param[in] json: json handle + * \param[in] b: new bool + * \return json itself success or NULL fail + */ +json_t json_set_bool(json_t json, int b) +{ + if (!json) return NULL; + + /* If the current type does not match, set the type to null first */ + if (json->type != JSON_TYPE_BOOL) json_set_null(json); + + /* Change the type to bool and set the bool value */ + json->type = JSON_TYPE_BOOL; + json->value.bool_ = (b == JSON_FALSE ? JSON_FALSE : JSON_TRUE); + + return json; +} + +/** + * \brief set the value of json to int. + * \param[in] json: json handle + * \param[in] num: new number + * \return json itself success or NULL fail + */ +json_t json_set_int(json_t json, int num) +{ + if (!json) return NULL; + + /* If the current type does not match, set the type to null first */ + if (json->type != JSON_TYPE_INT || json->type != JSON_TYPE_FLOAT) json_set_null(json); + + /* Change the type to int and set the int value */ + json->type = JSON_TYPE_INT; + json->value.int_ = num; + + return json; +} + +/** + * \brief set the value of json to float. + * \param[in] json: json handle + * \param[in] num: new number + * \return json itself success or NULL fail + */ +json_t json_set_float(json_t json, double num) +{ + if (!json) return NULL; + + /* If the current type does not match, set the type to null first */ + if (json->type != JSON_TYPE_INT || json->type != JSON_TYPE_FLOAT) json_set_null(json); + + /* Change the type to float and set the float value */ + json->type = JSON_TYPE_FLOAT; + json->value.float_ = num; + + return json; +} + +/** + * \brief set the value of json to string. + * \param[in] json: json handle + * \param[in] string: new string + * \return json itself success or NULL fail + */ +json_t json_set_string(json_t json, const char* string) +{ + char* s; + + if (!json) return NULL; + + /* If the current type does not match, set the type to null first */ + if (json->type != JSON_TYPE_STRING) json_set_null(json); + + /* Change the type to string */ + json->type = JSON_TYPE_STRING; + + /* The current string and the one to be set can be the same, and can be set successfully directly */ + if (json->value.string_ && (json->value.string_ == string || !strcmp(json->value.string_, string))) return json; + + /* If the passed in string is not empty, duplicate a backup */ + if (string) + { + s = json_strdup(string, strlen(string)); + if (!s) return NULL; + } + /* Otherwise, clear the json string */ + else s = NULL; + + /* Release the old string to update the new one */ + if (json->value.string_) free(json->value.string_); + json->value.string_ = s; + + return json; +} + +/** + * \brief set the value of json to array. + * \param[in] json: json handle + * \param[in] object: object handle + * \return json itself success or NULL fail + */ +json_t json_set_object(json_t json, json_t object) +{ + if (!json) return NULL; + + /* If the current type does not match, set the type to null first */ + if (json->type != JSON_TYPE_OBJECT) json_set_null(json); + + /* Change the type to object */ + json->type = JSON_TYPE_OBJECT; + + /* The current object and the one to be set can be the same, and can be set successfully directly */ + if (json->value.child_ && json->value.child_ == object) return json; + + /* Release the old object to update the new one */ + if (json->value.child_) json_delete(json->value.child_); + json->value.child_ = object; + + return json; +} + +/** + * \brief set the value of json to array. + * \param[in] json: json handle + * \param[in] array: array handle + * \return json itself success or NULL fail + */ +json_t json_set_array(json_t json, json_t array) +{ + if (!json) return NULL; + + /* If the current type does not match, set the type to null first */ + if (json->type != JSON_TYPE_ARRAY) json_set_null(json); + + /* Change the type to array */ + json->type = JSON_TYPE_ARRAY; + + /* The current array and the one to be set can be the same, and can be set successfully directly */ + if (json->value.child_ && json->value.child_ == array) return json; + + /* Release the old array to update the new one */ + if (json->value.child_) json_delete(json->value.child_); + json->value.child_ = array; + + return json; +} + +/** + * \brief set the int values of json to array. + * \param[in] json: json handle + * \param[in] numbers: array + * \param[in] count: size of array + * \return json itself success or NULL fail + */ +json_t json_set_array_int(json_t json, const int* numbers, int count) +{ + json_t array = NULL, n = NULL, p = NULL; + int i; + + /* Input parameter validity check */ + if (!json) return NULL; + if (!numbers) return NULL; + if (count <= 0) return NULL; + + /* Add data one by one to the array */ + for (i = 0; i < count; i++) + { + /* Create a single json data item */ + n = json_create(); + if (!n) + { + json_delete(array); + return NULL; + } + + /* Set array items as input data */ + json_set_int(n, numbers[i]); + + /* When i is 0, which is the first item of the array, the json array points to it */ + if (!i) array = n; + /* When it is not the first item, link it directly */ + else p->next = n; + + p = n; + } + + /* Set the current json to generate an array */ + json_set_array(json, array); + + return json; +} + +/** + * \brief set the float values of json to array. + * \param[in] json: json handle + * \param[in] numbers: array + * \param[in] count: size of array + * \return json itself success or NULL fail + */ +json_t json_set_array_float(json_t json, const float* numbers, int count) +{ + json_t array = NULL, n = NULL, p = NULL; + int i; + + /* Input parameter validity check */ + if (!json) return NULL; + if (!numbers) return NULL; + if (count <= 0) return NULL; + + /* Add data one by one to the array */ + for (i = 0; i < count; i++) + { + /* Create a single json data item */ + n = json_create(); + if (!n) + { + json_delete(array); + return NULL; + } + + /* Set array items as input data */ + json_set_float(n, numbers[i]); + + /* When i is 0, which is the first item of the array, the json array points to it */ + if (!i) array = n; + /* When it is not the first item, link it directly */ + else p->next = n; + + p = n; + } + + /* Set the current json to generate an array */ + json_set_array(json, array); + + return json; +} + +/** + * \brief set the double values of json to array. + * \param[in] json: json handle + * \param[in] numbers: array + * \param[in] count: size of array + * \return json itself success or NULL fail + */ +json_t json_set_array_double(json_t json, const double* numbers, int count) +{ + json_t array = NULL, n = NULL, p = NULL; + int i; + + /* Input parameter validity check */ + if (!json) return NULL; + if (!numbers) return NULL; + if (count <= 0) return NULL; + + /* Add data one by one to the array */ + for (i = 0; i < count; i++) + { + /* Create a single json data item */ + n = json_create(); + if (!n) + { + json_delete(array); + return NULL; + } + + /* Set array items as input data */ + json_set_float(n, numbers[i]); + + /* When i is 0, which is the first item of the array, the json array points to it */ + if (!i) array = n; + /* When it is not the first item, link it directly */ + else p->next = n; + + p = n; + } + + /* Set the current json to generate an array */ + json_set_array(json, array); + + return json; +} + +/** + * \brief set the string values of json to array. + * \param[in] json: json handle + * \param[in] strings: array + * \param[in] count: size of array + * \return json itself success or NULL fail + */ +json_t json_set_array_string(json_t json, const char** strings, int count) +{ + json_t array = NULL, n = NULL, p = NULL; + int i; + + /* Input parameter validity check */ + if (!json) return NULL; + if (!strings) return NULL; + if (count <= 0) return NULL; + + /* Add data one by one to the array */ + for (i = 0; i < count; i++) + { + /* Create a single json data item */ + n = json_create(); + if (!n) + { + json_delete(array); + return NULL; + } + + /* Set array items as input data. + * When designing new space allocation using `json_set_string()`, there may be allocation failures. + * Space allocation failed, we need to delete all the previously created json objects. + */ + if (!json_set_string(n, strings[i])) + { + json_delete(n); + json_delete(array); + return NULL; + } + + /* When i is 0, which is the first item of the array, the json array points to it */ + if (!i) array = n; + /* When it is not the first item, link it directly */ + else p->next = n; + + p = n; + } + + /* Set the current json to generate an array */ + json_set_array(json, array); + + return json; +} + +/** + * \brief Get the child element of a json object or array based on the specified key or index. + * + * \param[in] json The json object or array + * \param[in] key The key to search for (ignored for json arrays) + * \param[in] index The index of the child element to retrieve + * \param[out] out_prev Optional pointer to store the previous child element + * + * \return Returns the child element at the specified key or index, or NULL if not found or invalid input + */ +static json_t json_get_child(json_t json, const char* key, int index, json_t *out_prev) +{ + json_t c, prev = NULL; + + if (!json) return NULL; + + /* json of value type cannot have a key */ + if (key && json->type == JSON_TYPE_ARRAY) return NULL; + + /* Only array and object have child objects */ + if (json->type != JSON_TYPE_ARRAY && json->type != JSON_TYPE_OBJECT) return NULL; + + /* Traversing and matching json that match */ + c = json->value.child_; + while (c) + { + /* When no key is specified, only update the next match based on the index. + * When specifying the key, it is necessary to update the next match only when the key is the same. + */ + if (!key || !json_strccmp(c->key, key)) + { + index--; + if (index < 0) break; /* Out of valid indexes, exit matching */ + } + + /* Update traversal link */ + prev = c; + c = c->next; + } + /* The index is still used up, which means it doesn't match the specified one */ + if (index >= 0) return NULL; + + /* The matching json object is invalid, exit directly */ + if (!c) return NULL; + + /* Output, matching the previous object of json */ + if (out_prev) *out_prev = prev; + + return c; +} + +/** + * \brief Get the child element of a json object or array based on the specified key or index. + * When no key is specified, only update the next match based on the index. + * When specifying the key, it is necessary to update the next match only when the key is the same. + * + * \param[in] json The json object or array + * \param[in] key The key to search for (ignored for json arrays) + * \param[in] index The index of the child element to retrieve + * + * \return Returns the child element at the specified key or index, or NULL if not found or invalid input + */ +json_t json_get(json_t json, const char* key, int index) +{ + return json_get_child(json, key, index, NULL); +} + +/** + * \brief get the child of json object by index continuously. + * \param[in] json: json handle + * \param[in] index: index + * \param[in] ...: unexposed parameters, continuously enter index until the INT_MIN ends + * \return the specify child json object, or NULL fail + */ +json_t json_to_index_valist(json_t json, int index, ...) +{ + json_t c = json; + va_list args; + int i = index; + + if (!json) return NULL; + + /* First time get */ + c = json_get(c, NULL, i); + if (!c) return NULL; + + va_start(args, index); + + /* Continuously obtain the index of indefinite parameter inputs to recursively obtain json sub objects */ + i = va_arg(args, int); + while (c && i != INT_MIN) /* Stop getting when the index is `INT_MIN` */ + { + c = json_get(c, NULL, i); + i = va_arg(args, int); + } + + va_end(args); + + return c; +} + +/** + * \brief get the child of json object by key continuously. + * \param[in] json: json handle + * \param[in] key: address of key + * \param[in] ...: unexposed parameters, continuously enter key until the NULL ends + * \return the specify child json object, or NULL fail + */ +json_t json_to_key_valist(json_t json, char* key, ...) +{ + json_t c = json; + va_list args; + char* s = key; + + if (!json) return NULL; + + /* First time get */ + c = json_get(c, s, 0); + if (!c) return c; + + va_start(args, key); + + /* Continuously obtain the index of indefinite parameter inputs to recursively obtain json sub objects */ + s = va_arg(args, char*); + while (c && s != NULL) /* Stop getting when the key is `NULL` */ + { + c = json_get(c, s, 0); + s = va_arg(args, char*); + } + + va_end(args); + + return c; +} + +/** + * \brief insert a json object inito json by index. + * \param[in] json: json handle + * \param[in] index: index + * \param[in] item: another json handle + * \return ins itself success or NULL fail + */ +json_t json_attach(json_t json, int index, json_t ins) +{ + json_t c; + json_t prev = NULL; + + /* Input parameter validity check */ + if (!json) return NULL; + if (!ins) return NULL; + + /* Check if the `ins` has a key and determine if it matches the type of `json` */ + if (!(json->type == JSON_TYPE_ARRAY && !ins->key) && !(json->type == JSON_TYPE_OBJECT && ins->key)) return NULL; + + /* Traverse and iterate to the specified index */ + c = json->value.child_; + while (c && index > 0) + { + prev = c; + c = c->next; + index--; + } + + /* When the json child object is still empty, ins is taken as the first item */ + if (!c && !prev) + { + json->value.child_ = ins; + return ins; + } + + /* Link ins to its child */ + if (prev) prev->next = ins; + ins->next = c; + + /* Head insertion */ + if (c == json->value.child_) json->value.child_ = ins; + + return ins; +} + +/** + * \brief detach json object by key. + * \param[in] json: json handle + * \param[in] key: address of key + * \return detached object success or NULL fail + */ +json_t json_detach(json_t json, const char* key, int index) +{ + json_t c, prev = NULL; + + /* Getting for json object that require detach */ + c = json_get_child(json, key, index, &prev); + if (!c) return NULL; + + /* Detach `c` from the child linked list */ + if (prev) prev->next = c->next; + if (c == json->value.child_) json->value.child_ = c->next; + + /* Rest `c` link */ + c->next = NULL; + + return c; +} + +/** + * \brief copy json with options. + * \param[in] json: json handle + * \param[in] recurse: whether to copy the sub-object, 0-no, other-yes + * \return new json object + */ +json_t json_copy(json_t json) +{ + json_t copy, cptr, nptr = NULL, child; + + if (!json) return NULL; + + /* Create a backup empty json object */ + copy = json_create(); + if (!copy) return NULL; + + copy->type = json->type; + copy->value = json->value; + + /* If it is a string type, copy the string */ + if (json->type == JSON_TYPE_STRING && json->value.string_) + { + copy->value.string_ = json_strdup(json->value.string_, strlen(json->value.string_)); + if (!copy->value.string_) + { + json_delete(copy); + return NULL; + } + } + + /* If there is a key, copy the key */ + if (json->key) + { + copy->key = json_strdup(json->key, strlen(json->key)); + if (!copy->key) + { + json_delete(copy); + return NULL; + } + } + + /* walk the ->next chain for the child. */ + if (json->type == JSON_TYPE_OBJECT || json->type == JSON_TYPE_ARRAY) + { + cptr = json->value.child_; + while (cptr) + { + /* copy (with recurse) each json in the ->next chain */ + child = json_copy(cptr); + if (!child) + { + json_delete(copy); + return NULL; + } + + /* if newitem->child already set, then crosswire ->prev and ->next and move on */ + if (nptr) + { + nptr->next = child; + nptr = child; + } + /* set newitem->child and move to it */ + else + { + copy->value.child_ = child; + nptr = child; + } + + cptr = cptr->next; + } + } + + return copy; +} + +/** + * \brief Parse and convert a number in a json string. + * + * \param[in,out] json The json object to store the parsed number + * \param[in] text The input string containing the number + * + * \return Returns a pointer to the next character after the parsed number in the string, or NULL if there was an error in parsing the number + */ +static const char* parse_number(json_t json, const char* text) +{ + double number = 0; /* Converted number */ + int sign = 1, scale = 0; /* Sign and scale of integer part */ + int e_sign = 1, e_scale = 0; /* Sign and scale of exponent part */ + int isint = 1; /* Flag for whether there is only an integer part within a string */ + + /* First, check if there is a negative number with `-` before it */ + if (*text == '-') + { + sign = -1; + text++; + + /* Check if the first character is a valid number */ + if (!(*text >= '0' && *text <= '9')) + { + E(JSON_E_VALUE); + return NULL; + } + } + + /* Skip invalid `0` in the previous section */ + while (*text == '0') text++; + + /* Integer part */ + if (*text >= '1' && *text <= '9') + { + do + { + number = (number * 10.0) + (*text++ - '0'); /* carry addition */ + } while (*text >= '0' && *text <= '9'); + } + + /* Decimal part */ + if (*text == '.') + { + text++; + + /* Check if the first character is a valid number */ + if (!(*text >= '0' && *text <= '9')) + { + E(JSON_E_VALUE); + return NULL; + } + + /* The number of decimal parts is also increased by 10 times first, and then reduced according to the scale later on */ + do + { + number = (number * 10.0) + (*text++ - '0'); + scale--; + } while (*text >= '0' && *text <= '9'); + + /* Decimal part present, marked as non integer */ + isint = 0; + } + + /* Exponent part */ + if (*text == 'e' || *text == 'E') + { + text++; + + /* Symbol `+` skip */ + if (*text == '+') text++; + /* Symbol `-` with sign */ + else if (*text == '-') + { + e_sign = -1; + text++; + } + + /* Check if the first character is a valid number */ + if (!(*text >= '0' && *text <= '9')) + { + E(JSON_E_VALUE); + return NULL; + } + + /* Conversion exponent part */ + while (*text >= '0' && *text <= '9') + { + e_scale = (e_scale * 10) + (*text++ - '0'); /* number */ + } + + /* Exponent part present, marked as non integer */ + isint = 0; + } + + /* Calculated conversion result */ + number = (double)sign * number * pow(10.0, (scale + e_scale * e_sign)); + + /* As an integer and within the scope of an integer */ + if (isint && INT_MIN <= number && number <= INT_MAX) + { + json->type = JSON_TYPE_INT; + json->value.int_ = (int)number; + } + /* As a floating-point number */ + else + { + json->type = JSON_TYPE_FLOAT; + json->value.float_ = number; + } + + return text; +} + +/** + * \brief parse the input text to buffer, and fill the results into buf. + * \param[in] text: number text + * \param[out] buf: the address used to receive the parsed string pointer + * \return the new address of the transformed text + */ +static const char* parse_string_buffer(char** buf, const char* text) +{ + const char* ptr = text + 1; + char* ptr2; + char* out; + int len = 0; + + *buf = NULL; + + /* Not a string! */ + if (*text != '\"') + { + E(JSON_E_INVALID); + return NULL; + } + + /* Get the length of the string */ + while (*ptr && *ptr != '\"') + { + if (*ptr++ == '\\') ptr++; /* skip escaped quotes. */ + len++; + } + + /* Allocate storage space based on the calculated string length */ + out = (char*)malloc(len + 1); + if (!out) + { + E(JSON_E_MEMORY); + return NULL; + } + + /* Copy text to new space */ + ptr = text + 1; + ptr2 = out; + while (*ptr && *ptr != '\"') + { + /* Normal character */ + if (*ptr != '\\') { *ptr2++ = *ptr++; } + /* Escape character */ + else + { + ptr++; + if (*ptr == 'b') { *ptr2++ = '\b'; } + else if (*ptr == 'f') { *ptr2++ = '\f'; } + else if (*ptr == 'n') { *ptr2++ = '\n'; } + else if (*ptr == 'r') { *ptr2++ = '\r'; } + else if (*ptr == 't') { *ptr2++ = '\t'; } + else if (*ptr == 'u') { json_utf(&ptr, &ptr2); } + else { *ptr2++ = *ptr; } + ptr++; + } + } + + *ptr2 = 0; + if (*ptr == '\"') ptr++; + + *buf = out; + + return ptr; +} + +/** + * \brief parse the input text and fill the result into json. + * \param[in,out] json: json handle + * \param[in] text: string text + * \return the new address of the transformed text + */ +static const char* parse_string(json_t json, const char* text) +{ + json->value.string_ = NULL; + json->type = JSON_TYPE_STRING; + return parse_string_buffer(&(json->value.string_), text); +} + +/** + * \brief Parse an array in a json string and create a corresponding json object. + * + * \param[in,out] json The json object to store the parsed array + * \param[in] text The input string containing the array + * + * \return Returns a pointer to the next character after the parsed array in the string, or NULL if there was an error in parsing the array + */ +static const char* parse_array(json_t json, const char* text) +{ + json_t child, prev = NULL; + + /* Not an array! */ + if (*text != '[') + { + E(JSON_E_INVALID); + return NULL; + } + + /* First, record the json type as array */ + json->type = JSON_TYPE_ARRAY; + + /* Skip useless characters */ + text = skip(text + 1); + + /* Encountered `]`, indicating that it is an empty array, return directly */ + if (*text == ']') return text + 1; + + /* Loop parsing each member of an array */ + do + { + /* skip ',' */ + if (prev) text++; + + /* Create json objects as array member */ + child = json_create(); + if (!child) + { + E(JSON_E_MEMORY); + return NULL; + } + + /* skip meaningless character parsing. */ + text = skip(parse_text(child, skip(text))); + /* parse_text has already logged the error message */ + if (!text) + { + if (child) json_delete(child); + return NULL; + } + + /* Linking array members to an array linked list */ + if (prev) prev->next = child; + else json->value.child_ = child; + + /* Update the previous member's pointing */ + prev = child; + + } while (*text == ','); /* Array members are separated by the symbol `,` */ + + /* Not a complete array, not formed as a closed `[]` */ + if (*text != ']') + { + E(JSON_E_SQUARE); + return NULL; + } + + return text + 1; /* end of array */ +} + +/** + * \brief Parse an object in a json string and create a corresponding json object. + * + * \param[out] json The json object to store the parsed object + * \param[in] text The input string containing the object + * + * \return Returns a pointer to the next character after the parsed object in the string, or NULL if there was an error in parsing the object + */ +static const char* parse_object(json_t json, const char* text) +{ + char* key = NULL; + json_t child, prev = NULL; + + /* Not an object! */ + if (*text != '{') + { + E(JSON_E_INVALID); + return NULL; + } + + /* First, record the json type as object */ + json->type = JSON_TYPE_OBJECT; + + /* Skip useless characters */ + text = skip(text + 1); + + /* Encountered `}`, indicating that it is an empty object, return directly */ + if (*text == '}') return text + 1; + + /* Loop parsing each member of an object */ + do + { + /* skip ',' */ + if (prev) text++; + + /* First, parse the key section */ + text = skip(parse_string_buffer(&key, skip(text))); + if (!text) + { + if (key) free(key); + E(JSON_E_VALUE); + return NULL; + } + + /* Not the correct key-value delimiter */ + if (*text != ':') /* fail! */ + { + if (key) free(key); + E(JSON_E_KEY); + return NULL; + } + + /* Create json objects as object member */ + child = json_create(); + if (!child) + { + E(JSON_E_MEMORY); + return NULL; + } + + /* skip any spacing, get the text. */ + text = skip(parse_text(child, skip(text + 1))); + /* parse_text has already logged the error message */ + if (!text) + { + if (key) free(key); + if (child) json_delete(child); + return NULL; + } + + /* Point to key */ + child->key = key; + + /* Linking array members to an object linked list */ + if (prev) prev->next = child; + else json->value.child_ = child; + + /* Update the previous member's pointing */ + prev = child; + + } while (*text == ','); /* Object members are separated by the symbol `,` */ + + /* Not a complete object, not formed as a closed `{}` */ + if (*text != '}') { E(JSON_E_CURLY); return NULL; } + + return text + 1; /* end of object */ +} + +/** + * \brief Parse the text part of a json value in a json string. + * + * \param[in,out] json The json object to store the parsed value + * \param[in] text The input string containing the text part of the value + * + * \return Returns a pointer to the next character after the parsed value in the string, or NULL if there was an error in parsing the value + */ +static const char* parse_text(json_t json, const char* text) +{ + if (!strncmp(text, "null", 4)) + { + json->type = JSON_TYPE_NULL; + return text + 4; + } + if (!strncmp(text, "false", 5)) + { + json->type = JSON_TYPE_BOOL; + json->value.bool_ = JSON_FALSE; + return text + 5; + } + if (!strncmp(text, "true", 4)) + { + json->type = JSON_TYPE_BOOL; + json->value.bool_ = JSON_TRUE; + return text + 4; + } + if (*text == '-' || (*text >= '0' && *text <= '9')) return parse_number(json, text); + if (*text == '\"') return parse_string(json, text); + if (*text == '[') return parse_array(json, text); + if (*text == '{') return parse_object(json, text); + E(JSON_E_INVALID); + return NULL; +} + +/** + * \brief convert numbers in json to text and append to buf. + * \param[in] json: json handle + * \param[in] buf: buf handle + * \return 1 success or 0 fail + */ +static int print_number(json_t json, BUFFER* buf) +{ + double num = json->value.float_; + int len = 0; + + /* Number is 0 */ + if (num == 0) + { + if (!buf_append(1)) return 0; + buf_putc('0'); + } + /* The number type is an integer type */ + else if (json->type == JSON_TYPE_INT) + { + if (!buf_append(20)) return 0; + len = sprintf(&buf_end(), "%d", (int)json->value.int_); + buf->end += len; + } + /* The number type is a floating point type */ + else if (json->type == JSON_TYPE_FLOAT) + { + if (!buf_append(64)) return 0; + /* use full transformation within bounded space */ + if (fabs(floor(num) - num) <= DBL_EPSILON && fabs(num) < 1.0e60) len = sprintf(&buf_end(), "%.1lf", num); + /* use exponential form conversion beyond the limited range */ + else if (fabs(num) < 1.0e-6 || fabs(num) > 1.0e9) len = sprintf(&buf_end(), "%e", num); + /* default conversion */ + else + { + len = sprintf(&buf_end(), "%lf", num); + while (len > 0 && (&buf_end())[len-1] == '0' && (&buf_end())[len-2] != '.') len--; /* remove the invalid 0 in the decimal part */ + } + buf->end += len; + } + /* Not of number type */ + else return 0; + + return 1; +} + +/** + * \brief store c string conversion to buf. + * \param[in] str: address of string + * \param[in] buf: buf handle + * \return 1 success or 0 fail + */ +static int print_string_buffer(const char* str, BUFFER* buf) +{ + const char* p; + int len = 0, escape = 0; + + /* Empty string */ + if (!str) + { + if (!buf_append(2)) return 0; + buf_putc('\"'); + buf_putc('\"'); + return 1; + } + + /* Get the length of string */ + p = str; + while (*p) + { + len++; + if (*p == '\"' || *p == '\\' || *p == '\b' || *p == '\f' || *p == '\n' || *p == '\r' || *p == '\t') /* escape character */ + { + escape = 1; + len++; + } + else if ((unsigned char)(*p) < ' ') /* control character */ + { + escape = 1; + len += 5; // utf + } + p++; + } + + if (!buf_append(len + 2)) return 0; // \" \" + buf_putc('\"'); + + /* Without escape characters */ + if (!escape) + { + while (len--) buf_putc(*str++); + buf_putc('\"'); + return 1; + } + + p = str; + while (*p) + { + if ((unsigned char)(*p) >= ' ' && *p != '\"' && *p != '\\') + { + buf_putc(*p++); + } + else + { + /* escape and print */ + buf_putc('\\'); + if (*p == '\\') buf_putc('\\'); + else if (*p == '\"') buf_putc('\"'); + else if (*p == '\b') buf_putc('b'); + else if (*p == '\f') buf_putc('f'); + else if (*p == '\n') buf_putc('n'); + else if (*p == '\r') buf_putc('r'); + else if (*p == '\t') buf_putc('t'); + else + { + sprintf(&buf_end(), "u%04x", (unsigned char)(*p)); + buf->end += 5; + } + p++; + } + } + + buf_putc('\"'); + + return 1; +} + +/** + * \brief store json string conversion to buf. + * \param[in] json: json handle + * \param[in] buf: buf handle + * \return 1 success or 0 fail + */ +static int print_string(json_t json, BUFFER* buf) +{ + return print_string_buffer(json->value.string_, buf); +} + +/** + * \brief render a array to text. + * \param[in] json: json handle + * \param[in] buf: buf handle + * \param[in] depth: print depth, indentation + * \param[in] format: 0 gives unformatted, otherwise gives formatted + * \return 1 success or 0 fail + */ +static int print_array(json_t json, BUFFER* buf, int depth, int format) +{ + int i = 0, count = 0; + json_t child = json->value.child_; + + /* Empty array */ + if (!child) + { + if (!buf_append(2)) return 0; + buf_putc('['); + buf_putc(']'); + return 1; + } + + if (format) + { + while (child) + { + /* check if there are arrays or objects in the children */ + if ((child->type == JSON_TYPE_ARRAY || child->type == JSON_TYPE_OBJECT) && child->value.child_) + { + count++; + break; + } + + child = child->next; + } + } + + if (!buf_append((format && count) ? 2 : 1)) return 0; + buf_putc('['); + if (format && count) buf_putc('\n'); + + /* print children */ + child = json->value.child_; + while (child) + { + /* print starting indent */ + if (format && count) + { + if (!buf_append(depth + 1)) return 0; + for (i = 0; i < depth + 1; i++) buf_putc('\t'); + } + + /* print value */ + if (!print_json(child, buf, depth + 1, format)) return 0; + + /* print member separator ',' */ + if (child->next) + { + if (!buf_append(format ? 2 : 1)) return 0; + buf_putc(','); + if (format) + { + if (count) buf_putc('\n'); + else buf_putc(' '); + } + } + + child = child->next; + } + + /* print ending indent */ + if (!buf_append((format && count) ? depth + 2 : 1)) return 0; + if (format && count) + { + buf_putc('\n'); + for (i = 0; i < depth; i++) buf_putc('\t'); + } + buf_putc(']'); + + return 1; +} + +/** + * \brief render a object to text. + * \param[in] json: json handle + * \param[in] buf: buf handle + * \param[in] depth: print depth, indentation + * \param[in] format: 0 gives unformatted, otherwise gives formatted + * \return 1 success or 0 fail + */ +static int print_object(json_t json, BUFFER* buf, int depth, int format) +{ + int i; + json_t child = json->value.child_; + + /* empty object */ + if (!child) + { + if (!buf_append(2)) return 0; + buf_putc('{'); + buf_putc('}'); + return 1; + } + + if (!buf_append(format ? 2 : 1)) return 0; + buf_putc('{'); + if (format) buf_putc('\n'); + + /* print children */ + while (child) + { + /* print starting indent */ + if (format) + { + if (!buf_append(depth + 1)) return 0; + for (i = 0; i < depth + 1; i++) buf_putc('\t'); + } + + /* print key */ + if (!print_string_buffer(child->key, buf)) return 0; + + /* print indicator ':' */ + if (!buf_append(format ? 2 : 1)) return 0; + buf_putc(':'); + if (format) buf_putc('\t'); + + /* print value */ + if (!print_json(child, buf, depth + 1, format)) return 0; + + /* print separator ',' */ + if (!buf_append((child->next ? 1 : 0) + (format ? 1 : 0))) return 0; + if (child->next) buf_putc(','); + if (format) buf_putc('\n'); + + child = child->next; + } + + /* print ending indent */ + if (!buf_append(format ? depth + 1 : 1)) return 0; + if (format) + { + for (i = 0; i < depth; i++) buf_putc('\t'); + } + buf_putc('}'); + + return 1; +} + +/** + * \brief Print a json value to a buffer. + * + * \param[in] json The json value to be printed + * \param[in] buf The buffer to print the json value to + * \param[in] depth The current depth of the json value (used for pretty printing) + * \param[in] format The formatting option (compact or indented) + * + * \return Returns 1 if the printing is successful, 0 otherwise + */ +static int print_json(json_t json, BUFFER* buf, int depth, int format) +{ + switch (json->type) + { + case JSON_TYPE_NULL: + { + /* Print "null" */ + if (!buf_append(4)) return 0; + buf_putc('n'); + buf_putc('u'); + buf_putc('l'); + buf_putc('l'); + break; + } + case JSON_TYPE_BOOL: + { + if (json->value.bool_ == JSON_FALSE) + { + /* Print "false" */ + if (!buf_append(5)) return 0; + buf_putc('f'); + buf_putc('a'); + buf_putc('l'); + buf_putc('s'); + buf_putc('e'); + } + else + { + /* Print "true" */ + if (!buf_append(4)) return 0; + buf_putc('t'); + buf_putc('r'); + buf_putc('u'); + buf_putc('e'); + } + break; + } + case JSON_TYPE_INT: + case JSON_TYPE_FLOAT: return print_number(json, buf); + case JSON_TYPE_STRING: return print_string(json, buf); + case JSON_TYPE_ARRAY: return print_array(json, buf, depth, format); + case JSON_TYPE_OBJECT: return print_object(json, buf, depth, format); + } + return 1; +} + +/** + * \brief convert json to text, using a buffered strategy. + * \param[in] json: json handle + * \param[in] preset: preset is a guess at the final size, guessing well reduces reallocation + * \param[in] unformat: unformat=0 gives formatted, otherwise gives unformatted + * \param[out] len: address that receives the length of printed characters + * \return address of converted text, free the char* when finished + */ +char* json_dumps(json_t json, int preset, int unformat, int* len) +{ + BUFFER p, *buf = &p; + + if (!json) return NULL; + + /* Allocate buffer and initialize */ + if (preset < 1) preset = 1; + p.address = (char*)malloc(preset); + if (!p.address) return NULL; + p.size = preset; + p.end = 0; + + /* Start printing json */ + if (!print_json(json, &p, 0, !unformat)) + { + free(p.address); + return NULL; + } + + /* At the end of the text */ + if (!buf_append(1)) + { + free(p.address); + return NULL; + } + buf_end() = '\0'; + + /* Output conversion length */ + if (len) *len = p.end; + + return p.address; +} + +/** + * \brief according to the json object, generate a file. + * \param[in] json: json handle + * \param[in] filename: file name + * \return file length or negative fail + */ +int json_file_dump(json_t json, char* filename) +{ + FILE* f; + char* out; + int len; + + if (!json) return -1; + + /* Convert characters */ + out = json_dumps(json, 0, 0, &len); + if (!out) return -1; + + /* Open file */ + f = fopen(filename, "w"); + if (!f) + { + free(out); + return -1; + } + + /* Write the string to the file */ + fwrite(out, 1, len, f); + + /* Close file */ + fclose(f); + + /* free the sapce of string */ + free(out); + + return len; +} + +/** + * \brief json text parser. + * \param[in] text: address of text + * \return the address of the next meaningful character + */ +json_t json_loads(const char* text) +{ + json_t json = NULL; + + /* reset error info */ + lbegin = text; + eline = 1; + etype = JSON_E_OK; + + /* create json object and parse */ + json = json_create(); + if (!json) + { + E(JSON_E_MEMORY); + return NULL; + } + + text = parse_text(json, skip(text)); + /* parse failure. error is set. */ + if (!text) + { + json_delete(json); + return NULL; + } + + /* check whether there are meaningless characters after the text after parsing */ + text = skip(text); + if (*text) + { + json_delete(json); + E(JSON_E_END); + return NULL; + } + + return json; +} + +/** + * \brief load a json file, parse and generate json objects. + * \param[in] filename: file name + * \return json handle or NULL fail + */ +json_t json_file_load(char* filename) +{ + FILE* f; + json_t json = NULL; + long len; + char* data; + + /* Open file */ + f = fopen(filename, "rb"); + if (!f) return NULL; + + /* Get the length of file */ + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + + /* read file */ + data = (char*)malloc(len + 1); + if (data) + { + /* Read the file content into `data` */ + fread(data, 1, len, f); + fclose(f); + } + else + { + fclose(f); + return NULL; + } + + /* Add string terminator */ + data[len] = 0; + + /* Load json string */ + json = json_loads(data); + + /* Free up space */ + free(data); + + return json; +} + +/** + * \brief minify json text, remove the character that does not affect the analysis. + * \param[in] text: the address of the source text + * \return none + */ +void json_minify(char* text) +{ + char* into = text; + while (*text) + { + /* whitespace characters. */ + if (*text == ' ' || *text == '\t' || *text == '\r' || *text == '\n') + { + text++; + } + /* double-slash comments, to end of line. */ + else if (*text == '/' && text[1] == '/') + { + while (*text && *text != '\n') text++; + } + /* multiline comments. */ + else if (*text == '/' && text[1] == '*') + { + while (*text && !(*text == '*' && text[1] == '/')) text++; + text += 2; + } + /* string literals, which are \" sensitive. */ + else if (*text == '\"') + { + *into++ = *text++; + while (*text && *text != '\"') + { + if (*text == '\\') *into++ = *text++; + *into++ = *text++; + } + *into++ = *text++; + } + /* all other characters. */ + else *into++ = *text++; + } + *into = 0; /* and null-terminate. */ +} diff --git a/source/05_parser/json.h b/source/05_parser/json.h new file mode 100644 index 0000000..67d2b61 --- /dev/null +++ b/source/05_parser/json.h @@ -0,0 +1,209 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file json.h + * \unit json + * \brief This is a C language version of json streamlined parser + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __json_H +#define __json_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include + +/* version infomation */ + +#define JSON_V_MAJOR 1 +#define JSON_V_MINOR 0 +#define JSON_V_PATCH 0 + +/* json type definition, hiding structural members, not for external use */ + +typedef struct JSON* json_t; + +/* json normal types define */ + +#define JSON_TYPE_UNKNOW (0) /* unknown type */ +#define JSON_TYPE_NULL (1) /* null type */ +#define JSON_TYPE_BOOL (2) /* bool type */ +#define JSON_TYPE_INT (3) /* number int type */ +#define JSON_TYPE_FLOAT (4) /* number float type */ +#define JSON_TYPE_STRING (5) /* string type */ +#define JSON_TYPE_ARRAY (6) /* array type */ +#define JSON_TYPE_OBJECT (7) /* object type */ + +/* bool define */ +#define JSON_FALSE (0) /* bool false */ +#define JSON_TRUE (1) /* bool true */ + +/* error type define */ +#define JSON_E_OK (0) /* ok, no error */ +#define JSON_E_INVALID (1) /* invalid, not a valid expected value */ +#define JSON_E_END (2) /* many invalid characters appear at the end */ +#define JSON_E_KEY (3) /* parsing key, invalid key content found */ +#define JSON_E_VALUE (4) /* parsing value, invalid value content found */ +#define JSON_E_MEMORY (5) /* memory allocation failed */ +#define JSON_E_SQUARE (6) /* mising ']' */ +#define JSON_E_CURLY (7) /* mising '}' */ + +/* Load json */ + +json_t json_loads(const char* text); +json_t json_file_load(char* filename); + +/* When loading fails, use this method to locate the error */ + +int json_error_info(int* line, int* column); + +/* Dump json */ + +char* json_dumps(json_t json, int preset, int unformat, int* len); +int json_file_dump(json_t json, char* filename); + +/* Create and delete json */ + +json_t json_create(void); +void json_delete(json_t json); + +/* Get information of array/object json */ + +int json_size(json_t json); +int json_type(json_t json); + +/* Get json key and value */ +/* Before getting the value, it is recommended to judge whether it is the target type before getting it */ + +const char* json_key(json_t json); +int json_value_bool(json_t json); +int json_value_int(json_t json); +double json_value_float(json_t json); +const char* json_value_string(json_t json); +json_t json_value_array(json_t json); +json_t json_value_object(json_t json); + +/* Modify json basic type value */ +/* These methods have coverage and will overwrite the original data to change it to the specified data */ + +json_t json_set_key(json_t json, const char* key); +json_t json_set_null(json_t json); +json_t json_set_bool(json_t json, int b); +json_t json_set_int(json_t json, int num); +json_t json_set_float(json_t json, double num); +json_t json_set_string(json_t json, const char* string); +json_t json_set_object(json_t json, json_t object); +json_t json_set_array(json_t json, json_t array); + +/* These functions set json as an array of basic types in C language */ + +json_t json_set_array_int(json_t json, const int* numbers, int count); +json_t json_set_array_float(json_t json, const float* numbers, int count); +json_t json_set_array_double(json_t json, const double* numbers, int count); +json_t json_set_array_string(json_t json, const char** strings, int count); + +/* These methods obtain child json */ + +json_t json_get(json_t json, const char* key, int index); +json_t json_to_index_valist(json_t json, int index, ...); +json_t json_to_key_valist(json_t json, char* key, ...); + +/* json storage structure adjustment method */ +/* Usually used in conjunction with other methods */ + +json_t json_attach(json_t json, int index, json_t ins); +json_t json_detach(json_t json, const char* key, int index); + +/* json format text minify */ + +void json_minify(char* text); + +/* json deep copy */ + +json_t json_copy(json_t json); + +/* These creation methods are composed of functions, */ +/* that can create json basic types, which can be added to json arrays and child json objects */ + +#define json_create_null_for_object(key) (json_set_key(json_set_null(json_create()),(key))) +#define json_create_true_for_object(key) (json_set_key(json_set_bool(json_create(),JSON_TRUE),(key))) +#define json_create_false_for_object(key) (json_set_key(json_set_bool(json_create(),JSON_FALSE),(key))) +#define json_create_bool_for_object(key, b) (json_set_key(json_set_bool(json_create(),(b)),(key))) +#define json_create_int_for_object(key, n) (json_set_key(json_set_int(json_create(),(n)),(key))) +#define json_create_float_for_object(key, n) (json_set_key(json_set_float(json_create(),(n)),(key))) +#define json_create_string_for_object(key, s) (json_set_key(json_set_string(json_create(),(s)),(key))) +#define json_create_array_for_object(key) (json_set_key(json_set_array(json_create(),NULL),(key))) +#define json_create_object_for_object(key) (json_set_key(json_set_object(json_create(),NULL),(key))) +#define json_create_null_for_array() (json_set_null(json_create())) +#define json_create_true_for_array() (json_set_bool(json_create(),JSON_TRUE)) +#define json_create_false_for_array() (json_set_bool(json_create(),JSON_FALSE)) +#define json_create_bool_for_array(b) (json_set_bool(json_create(),(b))) +#define json_create_int_for_array(n) (json_set_int(json_create(),(n))) +#define json_create_float_for_array(n) (json_set_float(json_create(),(n))) +#define json_create_string_for_array(s) (json_set_string(json_create(),(s))) +#define json_create_array_for_array() (json_set_array(json_create(),NULL)) +#define json_create_object_for_array() (json_set_object(json_create(),NULL)) + +/* Add json item to array and object */ +/* The default location for adding is at the tail. +/* Because json objects use a unidirectional linked list storage structure, which can be time-consuming for continuous tail insertion. */ +/* Head insertion can be carried out according to the actual situation */ + +#define json_add_null_to_object(json, key) (json_isobject(json)?json_attach(json,json_size(json),json_create_null_for_object(key)):0) +#define json_add_true_to_object(json, key) (json_isobject(json)?json_attach(json,json_size(json),json_create_true_for_object(key)):0) +#define json_add_false_to_object(json, key) (json_isobject(json)?json_attach(json,json_size(json),json_create_false_for_object(key)):0) +#define json_add_bool_to_object(json, key, b) (json_isobject(json)?json_attach(json,json_size(json),json_create_bool_for_object(key,b)):0) +#define json_add_int_to_object(json, key, n) (json_isobject(json)?json_attach(json,json_size(json),json_create_int_for_object(key,n)):0) +#define json_add_float_to_object(json, key, n) (json_isobject(json)?json_attach(json,json_size(json),json_create_float_for_object(key,n)):0) +#define json_add_string_to_object(json, key, s) (json_isobject(json)?json_attach(json,json_size(json),json_create_string_for_object(key,s)):0) +#define json_add_array_to_object(json, key) (json_isobject(json)?json_attach(json,json_size(json),json_create_array_for_object(key)):0) +#define json_add_object_to_object(json, key) (json_isobject(json)?json_attach(json,json_size(json),json_create_object_for_object(key)):0) +#define json_add_null_to_array(json) (json_isarray(json)?json_attach(json,json_size(json),json_create_null_for_array()):0) +#define json_add_true_to_array(json) (json_isarray(json)?json_attach(json,json_size(json),json_create_true_for_array()):0) +#define json_add_false_to_array(json) (json_isarray(json)?json_attach(json,json_size(json),json_create_false_for_array()):0) +#define json_add_bool_to_array(json, b) (json_isarray(json)?json_attach(json,json_size(json),json_create_bool_for_array(b)):0) +#define json_add_int_to_array(json, n) (json_isarray(json)?json_attach(json,json_size(json),json_create_int_for_array(n)):0) +#define json_add_float_to_array(json, n) (json_isarray(json)?json_attach(json,json_size(json),json_create_float_for_array(n)):0) +#define json_add_string_to_array(json, s) (json_isarray(json)?json_attach(json,json_size(json),json_create_string_for_array(s)):0) +#define json_add_array_to_array(json) (json_isarray(json)?json_attach(json,json_size(json),json_create_array_for_array()):0) +#define json_add_object_to_array(json) (json_isarray(json)?json_attach(json,json_size(json),json_create_object_for_array()):0) + +/* These methods are used to detach and erase child json from its parent json */ +/* Divided into the form of indexe or key */ + +#define json_detach_by_index(json, index) json_detach(json,NULL,(index)) +#define json_detach_by_key(json, key) json_detach(json,(key),0) +#define json_erase(json, key, index) json_delete(json_detach((json),(key),(index))) +#define json_erase_by_index(json, index) json_erase(json,NULL,index) +#define json_erase_by_key(json, key) json_erase(json,key,0) + +/* Determine whether json is the specified basic type */ + +#define json_isnull(json) (json_type(json)==JSON_TYPE_NULL) +#define json_isbool(json) (json_type(json)==JSON_TYPE_BOOL) +#define json_isint(json) (json_type(json)==JSON_TYPE_INT) +#define json_isfloat(json) (json_type(json)==JSON_TYPE_FLOAT) +#define json_isnumber(json) (json_isint(json)||json_isfloat(json)) +#define json_isstring(json) (json_type(json)==JSON_TYPE_STRING) +#define json_isarray(json) (json_type(json)==JSON_TYPE_ARRAY) +#define json_isobject(json) (json_type(json)==JSON_TYPE_OBJECT) + +/* Continuously input index or key to recursively obtain child json */ + +#define json_to_index(json, i, ...) (json_to_index_valist(json,(i),##__VA_ARGS__,INT_MIN)) +#define json_to_key(json, key, ...) (json_to_key_valist(json,(key),##__VA_ARGS__,NULL)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/05_parser/txls.c b/source/05_parser/txls.c new file mode 100644 index 0000000..49de6da --- /dev/null +++ b/source/05_parser/txls.c @@ -0,0 +1,1396 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file txls.c + * \unit txls + * \brief This is a C language version of text excel parser + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "txls.h" +#include +#include +#include +#include + +/* dump buffer define */ +typedef struct +{ + char* address; /**< buffer base address */ + unsigned int size; /**< size of buffer */ + unsigned int end; /**< end of buffer used */ +} BUFFER; + +/* iterator define */ +typedef struct +{ + void *p; /**< iteration pointer */ + unsigned int i; /**< iteration index */ +} ITERATOR; + +/* smallest memory cell */ +typedef struct CELL +{ + struct CELL *next; /**< next cell */ + char* address; /**< address of cell content */ +} CELL; + +/* column storage */ +typedef struct COLUMN +{ + struct COLUMN *next; /**< next column */ + CELL *cells; /**< the cell base address of this column */ + ITERATOR iterator; /**< cell list iterator */ + int align; /**< alignment */ + unsigned int width; /**< the output width of this column when neatly outputting */ +} COLUMN; + +/* type of txls */ +typedef struct TXLS +{ + COLUMN *columns; /**< columns base */ + ITERATOR iterator; /**< column iterator */ + unsigned int col; /**< column count */ + unsigned int row; /**< row count */ +} TXLS; + +/* parsing error message storage */ +static int etype = 0; +static int eline = 0; + +#define E(type) etype=(type) + +static COLUMN *txls_column(txls_t txls, int index, int size) +{ + if (index >= size) return NULL; + if (index < txls->iterator.i || !txls->iterator.p || index == 0) + { + txls->iterator.i = 0; + txls->iterator.p = txls->columns; + } + while (txls->iterator.p && txls->iterator.i < index) + { + txls->iterator.p = ((COLUMN *)(txls->iterator.p))->next; + txls->iterator.i++; + } + return txls->iterator.p; +} + +static CELL *column_cell(COLUMN *column, int index, int size) +{ + if (index >= size) return NULL; + if (index < column->iterator.i || !column->iterator.p || index == 0) + { + column->iterator.i = 0; + column->iterator.p = column->cells; + } + while (column->iterator.p && column->iterator.i < index) + { + column->iterator.p = ((CELL *)(column->iterator.p))->next; + column->iterator.i++; + } + return column->iterator.p; +} + +/** + * \brief create a txls object + * \param[in] col: number of columns + * \param[in] row: number of rows + * \return txls handle or NULL FAIL + */ +txls_t txls_create(unsigned int col, unsigned int row) +{ + txls_t txls = NULL; + unsigned int i; + + /* create null txls and initialize */ + txls = (txls_t)malloc(sizeof(TXLS)); + if (!txls) return NULL; + txls->columns = NULL; + txls->col = 0; + txls->row = 0; + + /* Insert Column */ + for (i = 0; i < col; i++) + { + if (!txls_insert_column(txls, 1)) goto FAIL; + } + + /* Insert Row */ + for (i = 0; i < row; i++) + { + if (!txls_insert_row(txls, 1)) goto FAIL; + } + + txls->col = col; + txls->row = row; + + return txls; + +FAIL: + txls_delete(txls); + return NULL; +} + +/** + * \brief Create a new CELL structure and allocate memory for it + * + * \param[in] len Length of the address string to be allocated + * \return A pointer to the newly created CELL structure, or NULL if the allocation fails + */ +static CELL* new_cell(int len) +{ + CELL *cell; + + /* If the length is negative, return NULL */ + if (len < 0) return NULL; + + /* Allocate memory for the CELL structure */ + cell = (CELL *)malloc(sizeof(CELL)); + if (!cell) return NULL; + + /* Allocate memory for the address string */ + cell->address = malloc(len + 1); + if (!cell->address) + { + free(cell); + return NULL; + } + + /* Add a null terminator to the end of the address string */ + cell->address[len] = 0; + cell->next = NULL; + + return cell; +} + +/** + * \brief Free the memory allocated for a CELL structure + * + * \param[in] cell Pointer to the CELL structure to be freed + * \return void + */ +static void free_cell(CELL* cell) +{ + if (!cell) return; + + /* If the address string is allocated, free the memory */ + if (cell->address) free(cell->address); + + /* Free the memory allocated for the CELL structure */ + free(cell); +} + +/** + * \brief Free the memory allocated for a COLUMN structure and its associated CELL structures + * + * \param[in] column Pointer to the COLUMN structure to be freed + * \return void + */ +static void free_column(COLUMN *column) +{ + CELL *cell = NULL; + + /* Check if the column pointer is not NULL */ + if (column) + { + /* Loop through the cells in the column */ + while (column->cells) + { + /* Store the next cell pointer */ + cell = column->cells->next; + + /* Free the memory allocated for the current cell */ + free_cell(column->cells); + + /* Move to the next cell */ + column->cells = cell; + } + + /* Free the memory allocated for the COLUMN structure */ + free(column); + } +} + +/** + * \brief delete txls object + * \param[in] txls: txls handle + * \return none + */ +void txls_delete(txls_t txls) +{ + if (!txls) return; + + /* Loop through all columns in the txls_t structure */ + while (txls->col) + { + /* Delete the first column of the txls_t structure */ + txls_delete_column(txls, 1); + } + + /* Free the memory allocated for the txls_t structure */ + free(txls); +} + +/** + * \brief get the number of txls columns + * \param[in] txls: txls handle + * \return number of txls columns + */ +unsigned int txls_col(txls_t txls) +{ + if (!txls) return 0; + return txls->col; +} + +/** + * \brief get the number of txls rows + * \param[in] txls: txls handle + * \return number of txls rows + */ +unsigned int txls_row(txls_t txls) +{ + if (!txls) return 0; + return txls->row; +} + +/** + * \brief Duplicate a given string. + * + * \param[in] str String to be duplicated. + * \param[in] len Length of the string. + * \return Pointer to the duplicated string if successful, NULL otherwise. + */ +static char* txls_strdup(const char* str, int len) +{ + char* s; + + /* Allocate memory for the new string */ + s = (char*)malloc(len + 1); + if (!s) return NULL; + + /* Copy the given string into the allocated memory */ + memcpy(s, str, len); + s[len] = '\0'; + + return s; +} + +/** + * \brief Calculate the size of a string considering special characters + * + * \param[in] s Pointer to the input string + * \return The calculated size of the string taking special characters into account + */ +static unsigned int tsize(char *s) +{ + unsigned int size = 0; /* Initialize the size counter */ + + /* Loop through the string until the end */ + while (s && *s++) + { + /* If the current character is a newline, increase the size by 3 */ + if (*s == '\n') size += 3; + /* If the current character is a '|', increase the size by 1 */ + else if (*s == '|') size++; + + /* Increase the size for every character */ + size++; + } + + return size; +} + +/** + * \brief Update the width of a column based on the content of its cells + * + * \param[in] column Pointer to the COLUMN structure to be updated + * \param[in] cell Pointer to the CELL structure to be excluded from width calculation + * \param[in] row The row index for which the width is being updated + * \return void + */ +static void update_width(COLUMN *column, CELL *cell, unsigned int row) +{ + CELL *c = NULL; + unsigned int i; + unsigned int size = 0; + + /* Reset the column width to 0 */ + column->width = 0; + + /* Iterate through the rows up to the specified row index */ + for (i = 0; i <= row; i++) + { + /* Get the cell at the specified row index */ + c = column_cell(column, i, row + 1); + + /* Calculate the size of the cell's address */ + size = tsize(c->address); + + /* Update the column width if the size is greater */ + if (c != cell && size >= column->width) column->width = size; + } +} + +/** + * \brief set the alignment of the column + * \param[in] txls: txls handle + * \param[in] col: the column, starting from 1 + * \param[in] align: alignment, TXLS_ALIGN_LEFT, TXLS_ALIGN_RIGHT, TXLS_ALIGN_CENTER + * \return 1 success or 0 fail + */ +int txls_set_align(txls_t txls, unsigned int col, int align) +{ + COLUMN *column; + + if (!txls) return 0; + + /* Get the specified column from the txls_t structure */ + column = txls_column(txls, col - 1, txls->col); + if (!column) return 0; + + /* Check the alignment value */ + switch (align) + { + case TXLS_ALIGN_RIGHT: + case TXLS_ALIGN_CENTER: + case TXLS_ALIGN_LEFT: + column->align = align; /* Set the column's alignment */ + break; + default: + column->align = TXLS_ALIGN_UNKNOW; /* Set the column's alignment to unknown if the value is not recognized */ + break; + } + + return 1; +} + +/** + * \brief get the content of the specified cell + * \param[in] txls: txls handle + * \param[in] col: the column, starting from 1 + * \param[in] row: the row, starting from 1 + * \return conten address or NULL fail + */ +const char* txls_get_text(txls_t txls, unsigned int col, unsigned int row) +{ + COLUMN *column; + CELL *cell; + + if (!txls) return NULL; + + /**< If the column and row index is out of range, return NULL */ + if (col < 1 || col > txls->col) return NULL; + if (row > txls->row) return NULL; + + /* Get the specified column from the txls_t structure */ + column = txls_column(txls, col - 1, txls->col); + + /* Get the cell at the specified row index */ + cell = column_cell(column, row, txls->row + 1); + + return cell ? cell->address : NULL; +} + +/** + * \brief set the content of the specified cell + passing in other invisible characters will cause the setting to fail + * \param[in] txls: txls handle + * \param[in] col: the column, starting from 1 + * \param[in] row: the row, starting from 1 + * \param[in] *text: specified content + * \return 1 success or 0 fail + */ +int txls_set_text(txls_t txls, unsigned int col, unsigned int row, const char* text) +{ + COLUMN *column; + CELL *cell; + char* s; + int len = 0; + int i; + + if (!txls) return 0; + if (!text) return 0; + + /**< If the column and row index is out of range, return 0 */ + if (col < 1 || col > txls->col) return 0; + if (row > txls->row) return 0; + + /* Get the specified column from the txls_t structure */ + column = txls_column(txls, col - 1, txls->col); + + /* Get the cell at the specified row index */ + cell = column_cell(column, row, txls->row + 1); + + /* Loop through the text to calculate its length */ + for (i = 0;;i++) + { + if (text[i] == 0) break; + // if (!(text[i] >= ' ' && text[i] <= '~') && text[i] != '\n') return 0; /* Not a printable character */ + len++; + } + + /* Copy the text content to a new memory location */ + s = txls_strdup(text, len); + if (!s) return 0; + + /* Free the memory occupied by the original cell content */ + if (cell->address) free(cell->address); + + /* Set the cell's address to the copied text */ + cell->address = s; + + return 1; +} + +/** + * \brief insert a column into txls + * \param[in] txls: txls handle + * \param[in] col: the column, starting from 1 + * \param[in] *head: header + * \return 1 success or 0 fail + */ +int txls_insert_column(txls_t txls, unsigned int col) +{ + COLUMN *column = NULL, *pcolumn = NULL; + CELL *cell = NULL, *pcell = NULL; + unsigned int i; + + if (!txls) return 0; + + /* If the column index is out of range, return 0 */ + if (col > txls->col + 1 || col < 1) return 0; + + /* Allocate memory for the new column */ + column = (COLUMN *)malloc(sizeof(COLUMN)); + if (!column) goto FAIL; + + /**< Initialize the new column */ + column->align = TXLS_ALIGN_UNKNOW; + column->cells = NULL; + + /* Loop through the rows to create new cells for the new column */ + for (i = 0; i <= txls->row; i++) + { + /* Create a new cell */ + cell = new_cell(0); + if (!cell) goto FAIL; + + /* Set the first cell of the new column */ + if (!column->cells) column->cells = cell; + + /* Set the next pointer of the previous cell to the new cell */ + if (pcell) pcell->next = cell; + + /* Update the previous cell pointer */ + pcell = cell; + } + + /* If the new column is to be inserted at the beginning */ + if (col == 1) + { + /* Set the next pointer of the new column to the current first column */ + column->next = txls->columns; + /* Update the txls_t structure to point to the new first column */ + txls->columns = column; + } + /* If the new column is to be inserted in the middle or at the end */ + else + { + /* Get the column before the specified position */ + pcolumn = txls_column(txls, col - 2, txls->col); + + /* Set the next pointer of the new column to the next column of the previous column */ + column->next = pcolumn->next; + /* Update the next pointer of the previous column to the new column */ + pcolumn->next = column; + } + + /* Increment the total column count in the txls_t structure */ + txls->col++; + + return 1; + +FAIL: + /* Free the memory allocated for the new column in case of failure */ + if (column) free_column(column); + return 0; +} + +/** + * \brief delete a column from txls + * \param[in] txls: txls handle + * \param[in] col: the column, starting from 1 + * \return 1 success or 0 fail + */ +int txls_delete_column(txls_t txls, unsigned int col) +{ + COLUMN *column = NULL, *pcolumn = NULL; + + if (!txls) return 0; + + /* If the column index is out of range, return 0 */ + if (col > txls->col || col < 1) return 0; + + /* If the first column is to be deleted */ + if (col == 1) + { + /* Get the first column from the txls_t structure */ + column = txls_column(txls, 0, txls->col); + + /* Update the txls_t structure to point to the next column as the new first column */ + txls->columns = column->next; + } + /* If a column in the middle or at the end is to be deleted */ + else + { + /* Get the column before the specified position */ + pcolumn = txls_column(txls, col - 2, txls->col); + + /* Get the column to be deleted */ + column = pcolumn->next; + /* Update the next pointer of the previous column to skip the column to be deleted */ + pcolumn->next = column->next; + } + + /* Free the memory allocated for the deleted column */ + free_column(column); + + /* Decrement the total column count in the txls_t structure */ + txls->col--; + + return 1; +} + +/** + * \brief insert a row to txls + * \param[in] txls: txls handle + * \param[in] row: the row, starting from 1 + * \return 1 success or 0 fail + */ +int txls_insert_row(txls_t txls, unsigned int row) +{ + COLUMN *column = NULL; + CELL *cell = NULL, *pcell = NULL; + unsigned int i; + + if (!txls) return 0; + + /* If the row index is out of range, return 0 */ + if (row > txls->row + 1 || row < 1) return 0; + + /* Loop through the columns to create new cells for the new row */ + for (i = 0; i < txls->col; i++) + { + /* Create a new cell */ + cell = new_cell(0); + if (!cell) goto FAIL; + + /* Get the current column */ + column = txls_column(txls, i, txls->col); + + /* Get the cell before the specified position */ + pcell = column_cell(column, row - 1, txls->row + 1); + + /* Set the next pointer of the new cell to the cell at the specified position */ + cell->next = pcell->next; + /* Update the next pointer of the previous cell to the new cell */ + pcell->next = cell; + } + + /* Increment the total row count in the txls_t structure */ + txls->row++; + + return 1; + +FAIL: + /* In case of failure, revert the changes and free the memory */ + while (i--) + { + /* Get the current column */ + column = txls_column(txls, i, txls->col); + + /* Get the cell before the specified position */ + pcell = column_cell(column, row - 1, txls->row + 1); + + /* Get the cell to be deleted */ + cell = pcell->next; + /* Update the next pointer of the previous cell to skip the cell to be deleted */ + pcell->next = cell->next; + + /* Free the memory occupied by the deleted cell */ + free(cell); + } + + return 0; +} + +/** + * \brief delete a row from txls + * \param[in] txls: txls handle + * \param[in] row: the row, starting from 1 + * \return 1 success or 0 fail + */ +int txls_delete_row(txls_t txls, unsigned int row) +{ + COLUMN *column = NULL; + CELL *cell = NULL, *pcell = NULL; + unsigned int i; + + if (!txls) return 0; + + /* If the row index is out of range, return 0 */ + if (row > txls->row || row < 1) return 0; + + /* Loop through the columns to delete the cells of the specified row */ + for (i = 0; i < txls->col; i++) + { + /* Get the current column */ + column = txls_column(txls, i, txls->col); + + /* Get the cell before the specified position */ + pcell = column_cell(column, row - 1, txls->row + 1); + + /* Get the cell to be deleted */ + cell = pcell->next; + /* Update the next pointer of the previous cell to skip the cell to be deleted */ + pcell->next = cell->next; + + /* Free the memory occupied by the deleted cell */ + free_cell(cell); + } + + /* Decrement the total row count in the txls_t structure */ + txls->row--; + + return 1; +} + +/** + * \brief Calculate the smallest power of 2 that is greater than or equal to a given number. + * + * \param[in] x The given number. + * \return The smallest power of 2 greater than or equal to x. + */ +static unsigned int pow2gt(unsigned int x) +{ + int b = sizeof(int) * 8; + int i = 1; + + --x; + while (i < b) + { + x |= (x >> i); + i <<= 1; + } + + return x + 1; +} + +/** + * \brief confirm whether buf still has the required capacity, otherwise add capacity. + * \param[in] buf: buf handle + * \param[in] needed: required capacity + * \return 1 success or 0 fail + */ +static int expansion(BUFFER* buf, unsigned int needed) +{ + char* address; + unsigned int size; + if (!buf || !buf->address) return 0; + needed += buf->end; + if (needed <= buf->size) return 1; /* there is still enough space in the current buf */ + size = pow2gt(needed); + address = (char*)realloc(buf->address, size); + if (!address) return 0; + buf->size = size; + buf->address = address; + return 1; +} +#define buf_append(n) expansion(buf, (n)) /* append n size space for buf */ +#define buf_putc(c) (buf->address[buf->end++]=(c)) /* put a non zero character into buf */ +#define buf_end() (buf->address[buf->end]) /* obtain the tail of buf */ + +/** + * \brief Print the content of a cell with specified width and alignment into a buffer + * + * \param[in] cell Pointer to the CELL structure containing the cell content + * \param[in] width Width of the cell to be printed + * \param[in] align Alignment of the cell content (TXLS_ALIGN_CENTER, TXLS_ALIGN_RIGHT, or TXLS_ALIGN_LEFT) + * \param[in] buf Pointer to the BUFFER structure for storing the printed content + * \return 1 if the cell content is successfully printed into the buffer, 0 if the operation fails or the cell pointer is NULL + */ +static int print_cel(CELL *cell, unsigned int width, int align, BUFFER* buf) +{ + int i = 0; + char* addr = NULL; + unsigned int size = 0; + + if (!cell) return 0; + + /* Calculate the size of the cell's address */ + size = tsize(cell->address); + + /* Calculate the width to be filled with spaces */ + if (width < 1) width = 1; + if (width <= size) width = 0; + else width = width - size; + + /* Append the required space to the buffer */ + if (!buf_append(width + size + 3)) return 0; + buf_putc('|'); + buf_putc(' '); + + /* Calculate the padding for center and right alignment */ + if (align == TXLS_ALIGN_CENTER) i = width / 2; + else if (align == TXLS_ALIGN_RIGHT) i = width; + /* Fill the buffer with padding spaces */ + while (--i >= 0) buf_putc(' '); + + /* Set the address pointer to the cell's address */ + addr = cell->address; + /* Loop through the cell's address and process each character */ + while (addr && *addr) + { + /* If the character is a newline */ + if (*addr == '\n') + { + buf_putc('<'); /* Add '<' to the buffer */ + buf_putc('b'); /* Add 'b' to the buffer */ + buf_putc('r'); /* Add 'r' to the buffer */ + buf_putc('>'); /* Add '>' to the buffer */ + } + /* If the character is a '|' */ + else if (*addr == '|') + { + buf_putc('\\'); /* Add '\' to the buffer */ + buf_putc('|'); /* Add '|' to the buffer */ + } + /* For other characters */ + else + { + buf_putc(*addr); + } + + /* Move to the next character */ + addr++; + } + + /* Calculate the padding for center and left alignment */ + if (align == TXLS_ALIGN_CENTER) i = width - width / 2; + else if (align == TXLS_ALIGN_LEFT || align == TXLS_ALIGN_UNKNOW) i = width; + /* Fill the buffer with padding spaces */ + while (--i >= 0) buf_putc(' '); + + /* Add a space to the buffer */ + buf_putc(' '); + + return 1; +} + +/** + * \brief Prints a dividing line in the buffer with a specific width and alignment + * + * \param[in] width The width of the dividing line + * \param[in] align The alignment of the dividing line (TXLS_ALIGN_LEFT, TXLS_ALIGN_CENTER, or TXLS_ALIGN_RIGHT) + * \param[in] buf The pointer to the BUFFER structure where the dividing line will be printed + * \return Returns 1 if the dividing line is successfully printed, 0 otherwise + */ +static int print_div(int width, int align, BUFFER* buf) +{ + /* Ensure the width is at least 1 */ + if (width < 1) width = 1; + + /* Attempt to append enough space in the buffer for the dividing line */ + if (!buf_append(width + 3)) return 0; + + /* Print the left edge of the dividing line */ + buf_putc('|'); + + /* Print the appropriate alignment character (':' for left or center alignment, '-' for right alignment) */ + buf_putc((align == TXLS_ALIGN_LEFT || align == TXLS_ALIGN_CENTER) ? ':' : '-'); + + /* Print the actual dividing line based on the specified width */ + while (width--) buf_putc('-'); + + /* Print the appropriate alignment character (':' for right or center alignment, '-' for left alignment) */ + buf_putc((align == TXLS_ALIGN_RIGHT || align == TXLS_ALIGN_CENTER) ? ':' : '-'); + + return 1; +} + +/** + * \brief Prints the contents of a txls_t structure in a formatted manner to the buffer + * + * \param[in] txls The txls_t structure containing the data to be printed + * \param[in] buf The pointer to the BUFFER structure where the formatted contents will be printed + * \param[in] neat Flag indicating whether to print the contents in neat format + * \return Returns 1 if the contents are successfully printed, 0 otherwise + */ +static int print_txls(txls_t txls, BUFFER* buf, int neat) +{ + /* Pointer to a COLUMN structure */ + COLUMN *column; + /* Pointers to CELL structures */ + CELL *cell; + /* Loop counters */ + unsigned int i, j; + + if (!txls) return 0; + + if (txls->col == 0) + { + /* Set the end of the buffer to 0 */ + buf_end() = 0; + + return 1; + } + + /* Loop through each row */ + for (i = 0; i <= txls->row; i++) + { + /* Loop through each column */ + for (j = 0; j < txls->col; j++) + { + /* Get the column at index j */ + column = txls_column(txls, j, txls->col); + + /* Get the cell at (i, j) */ + cell = column_cell(column, i, txls->row + 1); + + /* Print the cell contents */ + if (!print_cel(cell, neat ? column->width : 0, column->align, buf)) return 0; + } + + /* Append 2 characters to the buffer */ + if (!buf_append(2)) return 0; + buf_putc('|'); + buf_putc('\n'); + + /* If it's the first row */ + if (i == 0) + { + /* Loop through each column */ + for (j = 0; j < txls->col; j++) + { + /* Get the column at index j */ + column = txls_column(txls, j, txls->col); + + /* Print the dividing line */ + if (!print_div(neat ? column->width : 0, column->align, buf)) return 0; + } + + /* Append (txls->col + 2) characters to the buffer */ + if (!buf_append(txls->col + 2)) return 0; + buf_putc('|'); + buf_putc('\n'); + } + } + + /* Append 1 character to the buffer */ + if (!buf_append(1)) return 0; + buf_end() = 0; + + return 1; +} + +/** + * \brief dump the txls object into a string. + * \param[in] txls: txls handle + * \param[in] neat: output neatly, with each column aligned + * \param[out] *len: the length of the string actually dumped + * \return dumped string, please release this string space after use + */ +/** + * \brief Dumps the contents of a txls_t structure into a character array and returns the pointer to the array + * + * \param[in] txls The txls_t structure containing the data to be dumped + * \param[in] neat Flag indicating whether to dump the contents in neat format + * \param[out] len Pointer to an integer where the length of the dumped content will be stored + * \return Returns a pointer to the character array containing the dumped contents, or NULL if an error occurs + */ +char* txls_dumps(txls_t txls, int neat, int* len) +{ + BUFFER p; + COLUMN *column; + unsigned int i; + int preset = 1; + + if (!txls) return NULL; + + /* Calculate the initial preset value */ + preset = (4 * txls->col + 2) * (txls->row + 2) + 1; + + /* If neat format is requested, calculate the additional space required */ + if (neat) + { + for (i = 0; i < txls->col; i++) + { + column = txls_column(txls, i, txls->col); + update_width(column, NULL, txls->row); + preset += (column->width - 1) * (txls->row + 2); + } + } + + /* Allocate memory for the buffer */ + p.address = (char*)malloc(preset); + if (!p.address) return NULL; + + /* Set the buffer size */ + p.size = preset; + /* Set the end of the buffer to 0 */ + p.end = 0; + + /* Print the contents of txls into the buffer */ + if (!print_txls(txls, &p, neat)) + { + /* Free the allocated memory */ + free(p.address); + + return NULL; + } + + /* Set the length of the dumped content if len pointer is provided */ + if (len) *len = p.end; + + /* Return the pointer to the character array containing the dumped contents */ + return p.address; +} + +/** + * \brief according to the txls, generate a file. + * \param[in] txls: txls handle + * \param[in] *filename: file name + * \return length + */ +int txls_file_dump(txls_t txls, const char* filename) +{ + FILE* f; + char* out; + int len; + + if (!txls) return -1; + if (!filename) return -1; + + out = txls_dumps(txls, 1, &len); + if (!out) return -1; + + f = fopen(filename, "w"); + if (!f) + { + free(out); + return -1; + } + + fwrite(out, 1, len, f); + fclose(f); + + free(out); + + return len; +} + +/** +* \brief Skip leading whitespace characters in a text. +* +* \param[in] in The text to process. +* \return A pointer to the first non-whitespace character in the text. +*/ +static const char* skip(const char* in) +{ + while (*in && (unsigned char)(*in) <= ' ') + { + /* when a newline character is encountered */ + if (*in == '\n') break; + + in++; + } + return in; +} + +/** + * \brief Parses a string in the given text and assigns it to the CELL structure. + * + * \param[in] text The input text to parse. + * \param[out] cell Pointer to the CELL structure to store the parsed string. + * \return A pointer to the remaining text after parsing the string. + */ +static const char* parse_string(const char* text, CELL *cell) +{ + const char *s, *e, *h; + int len = 0, i= 0; + char *address = NULL; + + text = skip(text); + if (*text == '|' || *text == '\n') /* null */ + { + return text; + } + + s = text; + h = s; + while (*s) + { + /* To the end of the line, but there is no '|' separator at the end */ + if (*s == '\n') + { + E(TXLS_E_END); + return s; + } + + /* The '|' delimiter is positioned to complete the range of this cell */ + if (*s == '|') + { + e = s - 1; + while (len > 0 && (unsigned char)(*e) <= ' ') e--, len--; + break; + } + + if (*s == '\\') /* escape character '|' delimiter */ + { + if (*(s+1) == '|') s++; + } + else if (*s == '<') /* escape character newline \n */ + { + if (strncmp(s, "
", 4) == 0) s += 3; + } + len++; + s++; + } + if (*s == 0) /* At the end of the text, the '|' separator is not positioned */ + { + E(TXLS_E_END); + return s; + } + + /* Allocate space and assign cell contents */ + if (len) + { + address = (char*)malloc(len + 1); + if (!address) + { + E(TXLS_E_MEMORY); + return s; + } + + i = 0; + while (h <= e) + { + if (*h == '\\') + { + if (*(h+1) == '|') + { + address[i++] = '|'; + h++; + } + else + { + address[i++] = '\\'; + } + } + else if (*h == '<') + { + if (strncmp(h, "
", 4) == 0) + { + address[i++] = '\n'; + h += 3; + } + else + { + address[i++] = '<'; + } + } + else + { + address[i++] = *h; + } + h++; + } + address[i] = 0; + if (cell->address) free(cell->address); + cell->address = address; + } + + return s; +} + +/** + * \brief Parses the header of a table in the given text and updates the txls_t structure. + * + * \param[in] text The input text to parse. + * \param[in,out] txls The txls_t structure to update with the parsed header information. + * \return A pointer to the remaining text after parsing the header. + */ +static const char* parse_head(const char* text, txls_t txls) +{ + const char* s = text; + COLUMN *column; + CELL *cell; + int i = 0; + int align = 0; + + // s = skip(s); + if (*s != '|') + { + E(TXLS_E_BEGIN); + return s; + } + + /* parse and get header */ + while (*s) + { + if (*s == '|') + { + s = skip(s + 1); + if (*s == '\n') continue; + if (!txls_insert_column(txls, txls->col + 1)) + { + E(TXLS_E_MEMORY); + return s; + } + column = txls_column(txls, txls->col - 1, txls->col); + cell = column_cell(column, 0, txls->row + 1); + s = parse_string(s, cell); + if (etype) return s; + } + else if (*s == '\n') + { + s++; + eline++; + break; + } + } + + // s = skip(s); + if (*s != '|') + { + E(TXLS_E_BEGIN); + return s; + } + + i = txls->col; + while (*s) + { + if (*s == '|') + { + column = txls_column(txls, txls->col - i, txls->col); + s = skip(s + 1); + if (*s == '\n') continue; + if (i < 0) + { + E(TXLS_E_HEAD); + return s; + } + align = 0; + if (*s == ':') + { + align |= 1; + s++; + } + if (*s == '-') while (*s == '-') s++; + else + { + E(TXLS_E_IDENT); + return s; + } + if (*s == ':') + { + align |= 2; + s++; + } + s = skip(s); + if (*s != '|') + { + E(TXLS_E_END); + return s; + } + i--; + column->align = align; + } + else if (*s == '\n') + { + if (i != 0) + { + E(TXLS_E_HEAD); + return s; + } + eline++; + s++; + break; + } + } + + return s; +} + +/** + * \brief Parses a line of a table in the given text and updates the txls_t structure. + * + * \param[in] text The input text to parse. + * \param[in,out] txls The txls_t structure to update with the parsed line information. + * \return A pointer to the remaining text after parsing the line. + */ +static const char* parse_line(const char* text, txls_t txls) +{ + const char* s = text; + COLUMN *column; + CELL *cell; + int i = 0; + + s = skip(s); + if (!*s) return s; + if (*s == '\n') + { + E(TXLS_E_BRANK); + return s; + } + if (*s != '|') + { + E(TXLS_E_BEGIN); + return s; + } + if (!txls_insert_row(txls, txls->row + 1)) + { + E(TXLS_E_MEMORY); + return s; + } + + while (*s) + { + if (*s == '|') + { + if (i >= txls->col) + { + while (*s && *s != '\n') s++; + continue; + } + column = txls_column(txls, i++, txls->col); + cell = column_cell(column, txls->row, txls->row + 1); + s = parse_string(s + 1, cell); + if (etype) return s; + } + else if (*s == '\n') + { + s++; + eline++; + break; + } + } + + return s; +} + +/** + * \brief load txls text and generate txls. + * \param[in] *text: address of text + * \return txls handler + */ +txls_t txls_loads(const char* text) +{ + txls_t txls; + CELL *ll = NULL, *base = NULL, *up; + int count = 0; + const char* s; + + if (!text) return NULL; + txls = txls_create(0, 0); + if (!txls) + { + E(TXLS_E_MEMORY); + return NULL; + } + + etype = TXLS_E_OK; + eline = 1; + + s = parse_head(text, txls); + if (etype) goto FAIL; + while (1) + { + s = parse_line(s, txls); + if (etype) goto FAIL; + if (!*s) break; + } + return txls; + +FAIL: + txls_delete(txls); + return NULL; +} + +/** + * \brief load txls file and generate txls. + * \param[in] *filename: filename + * \return txls handler + */ +txls_t txls_file_load(const char* filename) +{ + txls_t txls; + FILE* f; + long len; + char* text; + + if (!filename) return NULL; + + eline = 0; + + /* open file and get the length of file */ + f = fopen(filename, "rb"); + if (!f) + { + E(TXLS_E_OPEN); + goto FAIL; + } + + /* get file length */ + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + + /* read file */ + text = (char*)malloc(len + 1); + if (text) + { + fread(text, 1, len, f); + fclose(f); + f = NULL; + } + else + { + E(TXLS_E_MEMORY); + goto FAIL; + } + text[len] = 0; + + txls = txls_loads(text); /* load text */ + if (!txls) + { + goto FAIL; + } + + free(text); + + return txls; + +FAIL: + // printf("error line %d code %d\r\n", eline, etype); /* output error information */ + if (f) fclose(f); + if (text) free(text); + return NULL; +} + +/** + * \brief obtain parsing error information. + * \param[out] *line: line number where the error occurred + * \param[out] *type: error type, ref TXLS_E_XXX + * \return 1 has an error or 0 does not exist + */ +int txls_error_info(int* line) +{ + if (etype == TXLS_E_OK) return TXLS_E_OK; + if (line) *line = eline; + return etype; +} diff --git a/source/05_parser/txls.h b/source/05_parser/txls.h new file mode 100644 index 0000000..ccb481e --- /dev/null +++ b/source/05_parser/txls.h @@ -0,0 +1,101 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file txls.h + * \unit txls + * \brief This is a C language version of text excel parser + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#ifndef __txls_H +#define __txls_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/* version infomation */ + +#define TXLS_V_MAJOR 1 +#define TXLS_V_MINOR 0 +#define TXLS_V_PATCH 0 + +/* txls type definition, hiding structural members, not for external use */ + +typedef struct TXLS *txls_t; + +/* error type define */ + +#define TXLS_E_OK (0) /* no error */ +#define TXLS_E_HEAD (1) /* irregular header format */ +#define TXLS_E_ALLOC (2) /* failed to allocate space */ +#define TXLS_E_BEGIN (3) /* missing "|" separator at the begin */ +#define TXLS_E_END (4) /* missing "|" separator at the end */ +#define TXLS_E_IDENT (5) /* missing "-" separator at split row */ +#define TXLS_E_BRANK (6) /* there are extra blank lines */ +#define TXLS_E_MEMORY (7) /* memory allocation failed */ +#define TXLS_E_OPEN (8) /* fail to open file */ + +/* cell alignment */ + +#define TXLS_ALIGN_UNKNOW (0) +#define TXLS_ALIGN_LEFT (1) +#define TXLS_ALIGN_RIGHT (2) +#define TXLS_ALIGN_CENTER (3) + +/* Load txls */ + +txls_t txls_loads(const char* text); +txls_t txls_file_load(const char* filename); + +/* When loading fails, use this method to locate the error */ + +int txls_error_info(int* line); + +/* Dump txls */ + +char* txls_dumps(txls_t txls, int neat, int* len); +int txls_file_dump(txls_t txls, const char* filename); + +/* Create and delete txls */ + +txls_t txls_create(unsigned int col, unsigned int row); +void txls_delete(txls_t txls); + +/* Get the number of txls rows and columns*/ + +unsigned int txls_row(txls_t txls); +unsigned int txls_col(txls_t txls); + +/* set and get txls cell text */ + +const char* txls_get_text(txls_t txls, unsigned int col, unsigned int row); +int txls_set_text(txls_t txls, unsigned int col, unsigned int row, const char* text); + +/* row and column operations */ + +int txls_insert_column(txls_t txls, unsigned int col); +int txls_delete_column(txls_t txls, unsigned int col); +int txls_insert_row(txls_t txls, unsigned int row); +int txls_delete_row(txls_t txls, unsigned int row); + +/* set column alignment */ + +int txls_set_align(txls_t txls, unsigned int col, int align); + +/* Set and get header content */ + +#define txls_set_head(txls, col, head) txls_set_text((txls), (col), 0, (head)) +#define txls_get_head(txls, col) txls_get_text((txls), (col), 0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/05_parser/xml.c b/source/05_parser/xml.c new file mode 100644 index 0000000..2729705 --- /dev/null +++ b/source/05_parser/xml.c @@ -0,0 +1,1621 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file xml.h + * \unit xml + * \brief This is a C language version of xml parser + * \author Lamdonn + * \version v1.0.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2023 Lamdonn. + ********************************************************************************************************/ +#include "xml.h" +#include +#include +#include +#include + +/* dump buffer define */ +typedef struct +{ + char* address; /**< buffer base address */ + unsigned int size; /**< size of buffer */ + unsigned int end; /**< end of buffer used */ +} BUFFER; + +/* attribute define */ +typedef struct ATTR +{ + struct ATTR *next; /**< next attribute */ + char* name; /**< name of attribute */ + char* value; /**< value of attribute */ +} ATTR; + +/* xml define */ +typedef struct XML +{ + struct XML *next; /**< next xml */ + char* name; /**< name of xml */ + ATTR* attrs; /**< attributes */ + char* text; /**< text of xml */ + struct XML *child; /**< child of xml */ +} XML; + +static const char* lbegin = NULL; /**< beginning of line */ +static int etype = XML_E_OK; /**< type of error message */ +static int eline = 0; /**< line of error message */ +static int ecolumn = 0; /**< column of error message */ + +/* set error message and type */ +#define E(type) (etype=(type),ecolumn=text-lbegin) + +/** + * \brief for analysing failed parses + * \param[out] line: error line + * \param[out] column: error column + * \return error type + */ +int xml_error_info(int* line, int* column) +{ + /* No error occurred, return directly */ + if (etype == XML_E_OK) return XML_E_OK; + + /* Output the line and column where the error is located */ + if (line) *line = eline; + if (column) *column = ecolumn; + + /* Return error type */ + return etype; +} + +/** + * \brief get the smallest power of 2 not greater than x. + * \param[in] x: positive integer + * \return the smallest power of 2 not greater than x + */ +static int pow2gt(int x) +{ + int b = sizeof(int) * 8; + int i = 1; + + --x; + while (i < b) + { + x |= (x >> i); + i <<= 1; + } + + return x + 1; +} + +/** + * \brief Duplicate a given string. + * + * \param[in] str String to be duplicated. + * \return Pointer to the duplicated string if successful, NULL otherwise. + */ +static char* xml_strdup(const char* str) +{ + int size = (int)strlen(str) + 1; + char* s; + + /* Allocate memory for the new string */ + s = (char*)malloc(size); + if (!s) return NULL; + + /* Copy the given string into the allocated memory */ + memcpy(s, str, size); + + return s; +} + +/** +* \brief Compare two strings. +* +* \param[in] s1 The first string to compare. +* \param[in] s2 The second string to compare. +* \return 0 if the strings are equal, a negative value if s1 is less than s2, or a positive value if s1 is greater than s2. +*/ +static int string_compare(const char* s1, const char* s2) +{ + if (s1 == s2) return 0; /* If the memory addresses are the same, the strings are equal */ + if (!s1) return -1; /* If s1 is NULL, it is considered less than s2 */ + if (!s2) return 1; /* If s2 is NULL, it is considered greater than s1 */ + + /* Compare the strings character by character */ + while (*s1 == *s2) + { + if (*s1 == 0) return 0; /* If both characters are null terminators, the strings are equal */ + s1++, s2++; /* Move to the next characters */ + } + + /* Return the difference between the ASCII values of the characters at the current positions */ + return *(const unsigned char*)s1 - *(const unsigned char*)s2; +} + +/** + * \brief confirm whether buf still has the required capacity, otherwise add capacity. + * \param[in] buf: buf handle + * \param[in] needed: required capacity + * \return 1 success or 0 fail + */ +static int expansion(BUFFER *buf, unsigned int needed) +{ + char* address; + int size; + if (!buf) return 0; + needed += buf->end; + if (needed <= buf->size) return 1; /* there is still enough space in the current buf */ + size = pow2gt(needed); + address = (char*)realloc(buf->address, size); + if (!address) return 0; + buf->size = size; + buf->address = address; + return 1; +} +#define buf_append(n) expansion(buf, (n)) /* append n size space for buf */ +#define buf_putc(c) (buf->address[buf->end++]=(c)) /* put a non zero character into buf */ +#define buf_puts(s, len) do{for(int i=0;iaddress[buf->end]) /* obtain the address at the tail of buf */ + +/** +* \brief Skip leading whitespace characters in a text. +* +* \param[in] text The text to process. +* \return A pointer to the first non-whitespace character in the text. +*/ +static const char* skip(const char* text) +{ + while (text && *text && (unsigned char)(*text) <= ' ') + { + /* when a newline character is encountered, record the current parsing line */ + if (*text == '\n') + { + /* Increase eline and set lbegin to the current position */ + eline++; + lbegin = text; + } + + /* Move to the next character */ + text++; + } + + /* Return a pointer to the first non-whitespace character */ + return text; +} + +/** +* \brief Create a new ATTR object. +* +* \return A pointer to a newly allocated ATTR object with its memory initialized to zero. +*/ +static ATTR* new_attr() +{ + /* Allocate memory for a new ATTR object */ + ATTR* attr = (ATTR*)malloc(sizeof(ATTR)); + + /* Initialize the memory of the ATTR object to zero */ + memset(attr, 0, sizeof(ATTR)); + + return attr; +} + +/** +* \brief Delete a linked list of ATTR objects and free the memory. +* +* \param[in] attrs The head of the linked list of ATTR objects to delete. +*/ +static void attribute_delete(ATTR* attrs) +{ + ATTR* next; + + while (attrs) + { + /* Store the next ATTR object */ + next = attrs->next; + + /* Free the memory allocated for the name */ + if (attrs->name) free(attrs->name); + + /* Free the memory allocated for the value */ + if (attrs->value) free(attrs->value); + + /* Free the memory allocated for the current ATTR object */ + free(attrs); + + /* Move to the next ATTR object in the linked list */ + attrs = next; + } +} + +/** +* \brief Create a new XML node. +* +* \return A pointer to a newly allocated XML node object with its memory initialized to zero, or NULL if memory allocation fails. +*/ +static xml_t new_node(void) +{ + /* Allocate memory for a new XML node object */ + xml_t node = (xml_t)malloc(sizeof(XML)); + + /* Check if memory allocation fails, return NULL */ + if (!node) return NULL; + + /* Initialize the memory of the XML node object to zero */ + memset(node, 0, sizeof(XML)); + + /* Return a pointer to the newly created XML node object */ + return node; +} + +/** + * \brief delete a xml object. + * \param[in] xml: xml handle + * \return none + */ +void xml_delete(xml_t xml) +{ + xml_t next; + + if (xml) + { + /* Free the memory allocated for the name */ + if (xml->name) free(xml->name); + + /* Free the memory allocated for the text */ + if (xml->text) free(xml->text); + + /* Delete the attributes of the XML node */ + if (xml->attrs) attribute_delete(xml->attrs); + + /* Recursively delete the child node */ + while (xml->child) + { + /* Store the next child node */ + next = xml->child->next; + + /* Delete the child node */ + xml_delete(xml->child); + + /* Move to the next child node */ + xml->child = next; + } + + /* Free the memory allocated for the XML node itself */ + free(xml); + } +} + +/** +* \brief Check the validity of an XML node name. +* +* \param[in] name The name of the XML node to check. +* \param[out] len The length of the XML node name. +* \return 0:illegal, 1:end<0>, 2:delim. +*/ +static int check_name(const char* name, int *len) +{ + int ret = 1; + char *s = (char *)name; + int l = 0; + + /* Check the validity of the beginning */ + if (*s > 0 && !isalpha(*s)) return 0; + + /* Check the name specification, and calculate the length */ + while (*s) + { + if (*s == '>' || *s == ' ' || *s == '\t') + { + ret = 2; + break; + } + + /* illegal character */ + if (*s > 0 && !(isalnum(*s) || *s=='_' || *s=='-' || *s=='.' || *s==':')) + { + ret = 0; + break; + } + + s++, l++; + } + + /* Output length */ + if (len) *len = l; + + return ret; +} + +/** +* \brief Duplicate an XML node name with validity check. +* +* \param[in] name The name of the XML node to duplicate. +* \return A newly allocated string containing the duplicated name, or NULL if the name is invalid or memory allocation fails. +*/ +static char *name_strdup(const char* name) +{ + char *s; + int len = 0, ret = 0; + + /* Check the validity of name and return its length */ + ret = check_name(name, &len); + if (ret == 0 || ret == 2) return NULL; + + /* Allocating space */ + s = malloc(len + 1); + if (!s) return NULL; + + /* Copy string */ + memcpy(s, name, len); + s[len] = 0; + + return s; +} + +/** +* \brief Duplicate an XML node value with validity check. +* +* \param[in] value The value of the XML node to duplicate. +* \return A newly allocated string containing the duplicated value, or NULL if the value contains non-printable characters or memory allocation fails. +*/ +static char *value_strdup(const char* value) +{ + char *s = (char *)value; + int len = 0; + + while (*s) + { + if (*s > 0 && !isprint(*s)) return NULL; /* Check the value specification, and calculate the length */ + + s++, len++; + } + + /* Allocating space and assigning */ + s = malloc(len + 1); + if (!s) return NULL; + memcpy(s, value, len); + s[len] = 0; + + return s; +} + +/** + * \brief create a xml object. + * \param[in] name: name of xml node + * \return new xml object + */ +xml_t xml_create(const char* name) +{ + xml_t xml = NULL; + + if (!name) return NULL; + + /* Create a new XML node */ + xml = new_node(); + if (!xml) return NULL; + + /* Assign to the name of XML */ + xml->name = name_strdup(name); + if (!xml->name) + { + free(xml); + return NULL; + } + + return xml; +} + +/** + * \brief add an attribute to xml. + * \param[in] xml: xml handle + * \param[in] name: attribute name + * \param[in] value: attribute value + * \return 1 success or 0 fail + */ +int xml_add_attribute(xml_t xml, const char *name, const char *value) +{ + ATTR* attr; + + /* Input value validity check */ + if (!xml) return 0; + if (!name) return 0; + if (!value) return 0; + + /* Create a new XML attribute */ + attr = new_attr(); + if (!attr) return 0; + + /* Assign to the attribute name */ + attr->name = name_strdup(name); + if (!attr->name) goto FAIL; + + /* Assign to the attribute value */ + attr->value = value_strdup(value); + if (!attr->value) goto FAIL; + + /* Attach attribute */ + attr->next = xml->attrs; + xml->attrs = attr; + + return 1; + +FAIL: + if (attr->name) free(attr->name); + if (attr->value) free(attr->value); + free(attr); + return 0; +} + +/** + * \brief remove an attribute from xml. + * \param[in] xml: xml handle + * \param[in] name: attribute name or NULL matches all attributes + * \param[in] index: index + * \return 1 success or 0 fail + */ +int xml_remove_attribute(xml_t xml, const char *name, int index) +{ + ATTR *attr, *prev; + + if (!xml) return 0; + if (index < 0) return 0; + + attr = xml->attrs; + while (attr) + { + /* Check if the attribute name matches */ + if (!name || !string_compare(name, attr->name)) + { + index--; + + /* Break if the correct attribute is found */ + if (index < 0) break; + } + + prev = attr; + attr = attr->next; + } + /* If the index is not found */ + if (index >= 0) return 0; + /* If the attribute is not found */ + if (!attr) return 0; + + /* Adjust the pointers to remove the attribute */ + if (prev) prev->next = attr->next; + /* Update the head of the attribute list if necessary */ + else xml->attrs = attr->next; + + /* Free the memory for the attribute name and value */ + if (attr->name) free(attr->name); + if (attr->value) free(attr->value); + + /* Free the memory for the attribute object */ + free(attr); + + return 1; +} + +/** + * \brief get attribute of xml. + * \param[in] xml: xml handle + * \param[in] name: attribute name or NULL matches all attributes + * \param[in] index: index + * \return value of attribute + */ +const char* xml_get_attribute(xml_t xml, const char *name, int index) +{ + ATTR *attr, *prev; + + if (!xml) return NULL; + if (index < 0) return NULL; + + attr = xml->attrs; + while (attr) + { + /* Check if the attribute name matches */ + if (!name || !string_compare(name, attr->name)) + { + index--; + + /* Break if the correct attribute is found */ + if (index < 0) break; + } + + prev = attr; + attr = attr->next; + } + /* If the index is not found */ + if (index >= 0) return NULL; + /* If the attribute is not found */ + if (!attr) return NULL; + + return attr->value; +} + +/** + * \brief inserts the specified xml object into another xml object. + * \param[in] xml: xml handle + * \param[in] index: index + * \param[in] ins: insert xml object + * \return 1 success or 0 fail + */ +int xml_insert(xml_t xml, int index, xml_t ins) +{ + xml_t prev; + + /* Input value validity check */ + if (!xml) return 0; + if (!ins) return 0; + if (index < 0) return 0; + + /* Insert the node at the beginning */ + if (index == 0) + { + ins->next = xml->child; + xml->child = ins; + } + + /* Move to the desired index */ + prev = xml->child; + while (prev && --index) + { + prev = prev->next; + } + /* If the index is not valid */ + if (!prev) return 0; + + /* Attach xml object */ + ins->next = prev->next; + prev->next = ins; + + return 1; +} + +/** + * \brief remove an child xml from xml. + * \param[in] xml: xml handle + * \param[in] name: child xml name or NULL matches all child + * \param[in] index: index + * \return 1 success or 0 fail + */ +int xml_remove(xml_t xml, const char *name, int index) +{ + xml_t prev = NULL, node; + + /* Input value validity check */ + if (!xml) return 0; + if (xml->text) return 0; + if (index < 0) return 0; + + node = xml->child; + while (node) + { + /* Check if the node name matches */ + if (!name || !string_compare(name, node->name)) + { + index--; + + /* Break if the correct node is found */ + if (index < 0) break; + } + + prev = node; + node = node->next; + } + /* If the index is not found */ + if (index >= 0) return 0; + /* If the node is not found */ + if (!node) return 0; + + /* Adjust the pointers to remove the node */ + if (prev) prev->next = node->next; + /* Update the head of the child list if necessary */ + else xml->child = node->next; + + /* Delete the node and free memory */ + xml_delete(node); + + return 1; +} + +/** + * \brief goes to the specified child object. + * \param[in] xml: xml handle + * \param[in] name: children xml name or NULL matches all children + * \param[in] index: index + * \return specified child object + */ +xml_t xml_to(xml_t xml, const char *name, int index) +{ + xml_t prev = NULL, node; + + /* Input value validity check */ + if (!xml) return NULL; + if (index < 0) return NULL; + + node = xml->child; + while (node) + { + /* Check if the node name matches */ + if (!name || !string_compare(name, node->name)) + { + index--; + + /* Break if the correct node is found */ + if (index < 0) break; + } + + prev = node; + node = node->next; + } + /* If the index is not found */ + if (index >= 0) return NULL; + /* If the node is not found */ + if (!node) return NULL; + + return node; +} + +/** + * \brief set text into xml. + * \param[in] xml: xml handle + * \param[in] text: text + * \return 1 success or 0 fail + */ +int xml_set_text(xml_t xml, const char *text) +{ + char *s; + if (!xml) return 0; + if (!text) return 0; + + /* Duplicate the text content */ + s = xml_strdup(text); + if (!s) return 0; + + /* Free the previous text content if it exists */ + if (xml->text) free(xml->text); + + /* Set the text content of the XML node */ + xml->text = s; + + return 1; +} + +/** + * \brief get text of xml. + * \param[in] xml: xml handle + * \return text of xml + */ +const char* xml_get_text(xml_t xml) +{ + if (!xml) return NULL; + return xml->text; +} + +/** + * \brief Print text content with special characters converted to XML entities. + * + * \param[in] text The text content to print. + * \param[in] buf The buffer for output. + * \return 1 if the text content is successfully printed, 0 otherwise. + */ +static int print_text(char *text, BUFFER* buf) +{ + int len = 0, ap = 0; + char *s = text; + + /* Calculate the length of the text and the length of the escape character that needs to be appended */ + while (*s) + { + if (*s == '<') ap += 3; + else if (*s == '>') ap += 3; + else if (*s == '&') ap += 4; + else if (*s == '\'') ap += 5; + else if (*s == '\"') ap += 5; + len++; + s++; + } + + /* Append buffer space */ + if (!buf_append(len + ap)) return 0; + + /* Print text into buffer */ + s = text; + while (len--) + { + if (*s == '<') buf_puts("<", 4); + else if (*s == '>') buf_puts(">", 4); + else if (*s == '&') buf_puts("&", 5); + else if (*s == '\'') buf_puts("'", 6); + else if (*s == '\"') buf_puts(""", 6); + else buf_putc(*s); + s++; + } + + return 1; +} + +/** + * \brief Print attribute content with special characters converted to XML entities. + * + * \param[in] attrs The attribute content to print. + * \param[in] buf The buffer for output. + * \return 1 if the attribute content is successfully printed, 0 otherwise. + */ +static int print_attrs(ATTR* attrs, BUFFER* buf) +{ + int len; + + /* Traverse and print each attribute */ + while (attrs) + { + /* Get the length of attrubute name */ + len = strlen(attrs->name); + + if (!buf_append(len + 3)) return 0; + buf_putc(' '); + buf_puts(attrs->name, len); /* Print attrubute name */ + buf_putc('='); + buf_putc('"'); + + /* Print attrubute value */ + print_text(attrs->value, buf); + + if (!buf_append(1)) return 0; + buf_putc('"'); + + attrs = attrs->next; + } + return 1; +} + +/** + * \brief convert xml to text, using a buffered strategy. + * \param[in] xml: xml handle + * \param[in] buf: The buffer for output. + * \param[in] depth: The depth at which the current node is located + * \param[in] format: Neat formatted printing + * \return 1 if the xml content is successfully printed, 0 otherwise. + */ +static int print_node(xml_t xml, BUFFER* buf, int depth, int format) +{ + int len; + int i; + if (!xml) return 0; + + /* Traverse and print each xml */ + while (xml) + { + /* print indent */ + if (format) + { + if (format == 1) + { + if (!buf_append(depth)) return 0; + for (i = 0; i < depth; i++) buf_putc('\t'); + } + else format = 1; + } + + /* print name */ + len = strlen(xml->name); + if (!buf_append(len + 1)) return 0; + buf_putc('<'); + buf_puts(xml->name, len); + + /* print attributes */ + if (xml->attrs) print_attrs(xml->attrs, buf); + + /* tag header closing */ + if (!buf_append(1)) return 0; + buf_putc('>'); + + /* print text */ + if (xml->text) + { + print_text(xml->text, buf); + if (format) format = 2; // After printing the text, it must follow the label + } + + /* print child */ + if (xml->child) + { + if (format == 1) + { + if (!buf_append(1)) return 0; + buf_putc('\n'); + } + print_node(xml->child, buf, depth + 1, format); + } + + /* print end label */ + if (format) + { + if (format == 1) + { + if (!buf_append(depth)) return 0; + for (i = 0; i < depth; i++) buf_putc('\t'); + } + else format = 1; + } + if (!buf_append(len + (format?4:3))) return 0; + buf_putc('<'); + buf_putc('/'); + buf_puts(xml->name, len); + buf_putc('>'); + if (format) buf_putc('\n'); + + xml = xml->next; + } + + return 1; +} + +/** + * \brief convert xml to text, using a buffered strategy. + * \param[in] xml: xml handle + * \param[in] preset: preset is a guess at the final size, guessing well reduces reallocation + * \param[in] unformat: unformat=0 gives formatted, otherwise gives unformatted + * \param[out] len: address that receives the length of printed characters + * \return address of converted text, free the char* when finished + */ +char* xml_dumps(xml_t xml, int preset, int unformat, int* len) +{ + BUFFER p = {NULL, 0, 0}; + BUFFER *buf = &p; + + /* Initialize buf */ + if (preset < 1) preset = 1; + if (!buf_append(preset)) return NULL; + + /* Print root node */ + if (!print_node(xml, buf, 0, unformat?0:1) || !buf_append(1)) + { + free(buf->address); + return NULL; + } + + /* Add string terminator */ + buf_end() = '\0'; + + /* Output string length */ + if (len) *len = buf->end; + + return buf->address; +} + +/** + * \brief according to the xml object, generate a file. + * \param[in] xml: xml handle + * \param[in] filename: file name + * \return file len or negative fail + */ +int xml_file_dump(xml_t xml, char* filename) +{ + FILE* f; + char* out; + int len; + + if (!xml) return -1; + + out = xml_dumps(xml, 0, 0, &len); + if (!out) return -1; + + f = fopen(filename, "w"); + if (!f) + { + free(out); + return -1; + } + + fwrite(out, 1, len, f); + fclose(f); + + free(out); + + return len; +} + +/** + * \brief skip XML comments. + * \param[in] text: The text content to skip. + * \return The character address after skip + */ +static const char* skip_comment(const char *text) +{ + if (*text == '<') + { + /* Matched to the front of the comment label */ + if (!strncmp(text, "", skip characters that are not '-' first */ + while (*text && *text != '-') + { + /* Record line breaks */ + if (*text == '\n') + { + eline++, lbegin = text; + } + text++; + } + + /* It ends at the end of the string, and the annotation does not form a closed interval */ + if (!*text) + { + E(XML_E_COMMENT); + return text; + } + + /* Matched to the end of the annotation label, the annotation has formed a complete closed interval */ + if (!strncmp(text, "-->", 3)) break; + + text++; + } + + /* skip "-->" */ + text += 3; + } + } + return text; +} + +/** + * \brief Parsing name. + * \param[in] text: The text content to parse. + * \param[out] name: Output the name generated by parsing + * \return The character address after parse + */ +static const char* parse_name(const char *text, char **name) +{ + int len = 0, ret = 0; + char *s; + + /* Check the validity of the name and obtain the length */ + ret = check_name(text, &len); + if (ret == 0 || ret == 1) + { + E(XML_E_ILLEGAL); + return text + len; + } + + /* Allocate space for name */ + s = (char*)malloc(len + 1); + if (!s) + { + E(XML_E_MEMORY); + return text + len; + } + + /* Assign a value to name and output it */ + memcpy(s, text, len); + s[len] = 0; + *name = s; + + return text + len; +} + +/** + * \brief Parsing one attribute. + * \param[in] text: The text content to parse. + * \param[out] attr: Output the attributes generated by parsing + * \return The character address after parse + */ +static const char* parse_one_attribute(const char *text, ATTR* attr) +{ + char* s; + int len = 0, ret = 0; + char *name, *value; + char q = 0; /* quote: single or double */ + + text = skip(text); + if (*text == '>') return text; /* null attributes */ + + /* Check the validity of the name and obtain the length */ + ret = check_name(text, &len); + /* Encountered illegal characters */ + if (ret == 0) + { + if (text[len] != '=') + { + E(XML_E_ILLEGAL); + return text + len; + } + } + /* Parsed to the end of the text */ + else if (ret == 1) + { + E(XML_E_END); + return text + len; + } + + /* Allocate space for name */ + name = (char*)malloc(len + 1); + if (!name) + { + E(XML_E_MEMORY); + return text + len; + } + + /* Assign a value to name */ + strncpy(name, text, len); + name[len] = 0; + + /* Recorde name */ + attr->name = name; + + /* Skip */ + text += len; + text = skip(text); + + /* No separator for name-value encountered */ + if (*text != '=') + { + E(XML_E_VALUE); + return text; + } + + /* Record whether the attribute value is currently included in `'` or in `"` */ + text = skip(++text); + if (*text == '\'') q = '\''; + else if (*text == '\"') q = '\"'; + else + { + E(XML_E_QUOTE); + return text; + } + + /* Initialize the value used to parse the value variable */ + text++; + s = (char *)text; + len = 0; + + /* How much space is needed for pre exploration and how many escape characters need to be parsed */ + while (*s != q) + { + if (*s == '&') + { + if (!strncmp(s, "<", 4)) s += 3; + else if (!strncmp(s, ">", 4)) s += 3; + else if (!strncmp(s, "&", 5)) s += 4; + else if (!strncmp(s, "'", 6)) s += 5; + else if (!strncmp(s, """, 6)) s += 5; + } + len++, s++; + } + + /* Allocate space to store parsed content */ + value = (char*)malloc(len + 1); + if (!value) + { + E(XML_E_MEMORY); + return text; + } + + /* Store character by character */ + s = value; + while (*text != q) + { + /* Record line breaks */ + if (*text == '\n') + { + eline++, lbegin = text; + } + + /* Record escape characters */ + if (*text == '&') + { + if (!strncmp(text, "<", 4)) {text += 4; *s++ = '<'; continue;} + else if (!strncmp(text, ">", 4)) {text += 4; *s++ = '>'; continue;} + else if (!strncmp(text, "&", 5)) {text += 5; *s++ = '&'; continue;} + else if (!strncmp(text, "'", 6)) {text += 6; *s++ = '\''; continue;} + else if (!strncmp(text, """, 6)) {text += 6; *s++ = '\"'; continue;} + } + + /* Normal character */ + *s++ = *text++; + } + + /* Add the end of the string and record it */ + value[len] = 0; + attr->value = value; + + return text + 1; /* skip quote */ +} + +/** + * \brief Parse all attributes of the entire label. + * \param[in] text: The text content to parse. + * \param[out] out: Output the attributes generated by parsing + * \return The character address after parse + */ +static const char* parse_attributes(const char *text, ATTR **out) +{ + ATTR *first = NULL, *prev = NULL, *attr = NULL; + + *out = NULL; /* Reset output */ + + /* Create new attributes to record the results of parsing */ + attr = new_attr(); + if (!attr) + { + E(XML_E_MEMORY); + return text; + } + + text = parse_one_attribute(text, attr); + /* parse error, or no attribute, return directly */ + if (etype != XML_E_OK || !attr->name) + { + attribute_delete(attr); + return text; + } + + /* Continue parsing other attributes */ + first = attr, prev = first, *out = first; + while (*text) + { + /* Create new attributes to record the results of parsing */ + attr = new_attr(); + if (!attr) + { + E(XML_E_MEMORY); + return text; + } + + /* Parse other attributes */ + text = parse_one_attribute(text, attr); + /* parse error */ + if (etype != XML_E_OK) + { + attribute_delete(attr); + return text; + } + /* end attribute, all attributes have been parsed */ + if (!attr->name) + { + attribute_delete(attr); + break; + } + + /* Link */ + prev->next = attr; + } + + return text; +} + +/** + * \brief Parse text. + * \param[in] text: The text content to parse. + * \param[out] out: Output the attributes generated by parsing + * \return The character address after parse + */ +static const char* parse_text(const char *text, char **out) +{ + char *s, *t; + int len = 0; + + s = (char *)skip(text); + /* Not an escape, but a separate '<' character is not allowed */ + if (*s == '<' && s[1] != '!') + { + *out = NULL; + return s; + } + + /* Predict the length of text to allocate space */ + while (*s) + { + /* Text contains special escape */ + if (*s == '<') + { + /* Skip comments */ + if (!strncmp(s, "", 3)) break; + s++; + } + s += 3; + continue; + } + /* Get CDATA */ + else if (!strncmp(s, "", 3)) break; + s++, len++; + } + s += 3; + continue; + } + else break; + } + + /* Normal escape characters */ + if (*s == '&') + { + if (!strncmp(s, "<", 4)) s += 3; + else if (!strncmp(s, ">", 4)) s += 3; + else if (!strncmp(s, "&", 5)) s += 4; + else if (!strncmp(s, "'", 6)) s += 5; + else if (!strncmp(s, """, 6)) s += 5; + } + s++, len++; + } + + /* Allocate space */ + t = (char *)malloc(len + 1); + if (!t) + { + E(XML_E_MEMORY); + return text; + } + + /* Similar work when repeatedly predicting length to store characters in allocated space */ + *out = t; + while (text < s) + { + /* Skip comments */ + text = skip_comment(text); + if (etype != XML_E_OK) return text; + + if (*text == '<') + { + if (!strncmp(text, "", 3)) break; + *t++ = *text++; + } + text += 3; + continue; + } + } + if (*text == '\n') {eline++, lbegin = text;} + if (*text == '&') + { + if (!strncmp(text, "<", 4)) {text += 4; *t++ = '<'; continue;} + else if (!strncmp(text, ">", 4)) {text += 4; *t++ = '>'; continue;} + else if (!strncmp(text, "&", 5)) {text += 5; *t++ = '&'; continue;} + else if (!strncmp(text, "'", 6)) {text += 6; *t++ = '\''; continue;} + else if (!strncmp(text, """, 6)) {text += 6; *t++ = '\"'; continue;} + } + *t++ = *text++; + } + + /* Add string terminator */ + *t = 0; + + return text; +} + +/** + * \brief Parse xml node. + * \param[in] text: The text content to parse. + * \param[out] node: Output the node information + * \return The character address after parse + */ +static const char* parse_node(const char* text, xml_t node) +{ + xml_t tail = NULL, child; + ATTR* attrs = NULL; + int len = 0; + char *out = NULL; + const char *s = NULL; + + /* Get '<' */ + while (*text) + { + /* Exploring Character `<` */ + text = skip(text); + if (*text != '<') + { + E(XML_E_LABEL); + return text; + } + + /* Check if this is a comment, if it is, skip it */ + s = skip_comment(text); + if (etype != XML_E_OK) return text; + + /* Indicates that the middle comment has been skipped */ + if (s > text) + { + text = s; + continue; + } + /* Normal `<` */ + else + { + text = s; + break; + } + } + /* Skip `<` */ + text++; + + /* parse name */ + s = parse_name(text, &node->name); + if (etype != XML_E_OK) return s; + len = s - text; + text = s; + + /* parsing the header tag is not over yet, and then parsing the attribute */ + if (*text != '>') + { + text = parse_attributes(text, &attrs); + node->attrs = attrs; + if (etype != XML_E_OK) return text; + } + + /* parse text */ + text = parse_text(text + 1, &out); + node->text = out; + + /* parse children and end tag */ + while (*text) + { + /* Exploring Character `<` */ + text = skip(text); + if (*text != '<') + { + E(XML_E_ILLEGAL); + return text; + } + + /* label tail */ + if (text[1] == '/') + { + /* Skip `name, text, len)) + { + E(XML_E_LABEL); + return text; + } + + /* Skip name */ + text += len; + + /* Determine if aend tag has been formed */ + if (*text != '>') + { + E(XML_E_LABEL); + return text; + } + + /* Reset `len` */ + len = 0; + + /* Skip `>` */ + text++; + + return text; + } + /* parsing child elements */ + else + { + /* Check if this is a comment, if it is, skip it */ + s = skip_comment(text); + if (etype != XML_E_OK) return text; + + /* Indicates that the middle comment has been skipped */ + if (s > text) + { + text = s; + continue; + } + /* Normal `<` */ + else text = s; + + /* Create a new node to store new parsing content */ + child = new_node(); + if (!child) + { + E(XML_E_MEMORY); + return text; + } + + /* Add to child node linked list */ + if (!node->child) node->child = child; + else tail->next = child; + tail = child; + + /* Parse child node */ + text = parse_node(text, child); + if (etype != XML_E_OK) return text; + + /* Skip the extra characters */ + while (*text && *text != '<') + { + if (*text == '\n') + { + eline++, lbegin = text; + } + text++; + } + } + } + + /* No closing tag */ + if (len > 0) E(XML_E_LABEL); + + return text; +} + +/** + * \brief Parse xml file head. + * \param[in] text: The text content to parse. + * \return The character address after parse + */ +static const char* parse_head(const char* text) +{ + char *name = NULL, *version = NULL, *encoding = NULL; + ATTR *attr_version = NULL, *attr_encoding = NULL; + + /* Skip `name, "version")) + { + E(XML_E_VERSION); + goto END; + } + + /* Create new attributes to store encoding information */ + attr_encoding = new_attr(); + if (!attr_encoding) + { + E(XML_E_MEMORY); + goto END; + } + + /* Parse attributes to determine if it is the expected `encoding` */ + text = parse_one_attribute(text, attr_encoding); + if (etype != XML_E_OK) goto END; /* parse error */ + if (strcmp(attr_encoding->name, "encoding")) + { + E(XML_E_ENCODING); + goto END; + } + } + + /* Determine whether the label forms a complete closed interval */ + text = skip(text); + if (strncmp(text, "?>", 2)) + { + E(XML_E_NOTES); + goto END; + } + + /* Skip `?>` */ + text += 2; + +END: + if (attr_version) attribute_delete(attr_version); + if (attr_encoding) attribute_delete(attr_encoding); + if (name) free(name); + return text; +} + +/** + * \brief load xml from text string. + * \param[in] text: text + * \return xml object + */ +xml_t xml_loads(const char* text) +{ + xml_t root; + + etype = XML_E_OK; + eline = 1; + lbegin = text; + ecolumn = 0; + + /* parse head notes */ + if (strncmp(text, " */ +#define XML_E_NOTES (11) /* head notes error */ +#define XML_E_CDATA (12) /* missing comment tail ]]> */ + +/* Load xml */ + +xml_t xml_loads(const char* text); +xml_t xml_file_load(const char* filename); + +/* When loading fails, use this method to locate the error */ + +int xml_error_info(int* line, int* column); + +/* Dump xml */ + +char* xml_dumps(xml_t xml, int preset, int unformat, int* len); +int xml_file_dump(xml_t xml, char* filename); + +/* Create and delete xml */ + +xml_t xml_create(const char* name); +void xml_delete(xml_t xml); + +/* Add and remove XML attribute */ + +int xml_add_attribute(xml_t xml, const char *name, const char *value); +int xml_remove_attribute(xml_t xml, const char *name, int index); + +/* Get XML attribute */ + +const char* xml_get_attribute(xml_t xml, const char *name, int index); + +/* Insert and remove XML object */ + +int xml_insert(xml_t xml, int index, xml_t ins); +int xml_remove(xml_t xml, const char *name, int index); + +/* Get child xml object */ + +xml_t xml_to(xml_t xml, const char *name, int index); + +/* Set and get xml value text */ + +int xml_set_text(xml_t xml, const char *text); +const char* xml_get_text(xml_t xml); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/test/file/read.csv b/test/file/read.csv new file mode 100644 index 0000000..d5d8da0 --- /dev/null +++ b/test/file/read.csv @@ -0,0 +1,5 @@ + DID ,Function,Comment +$D131 ,Sample ID, "S""Yes + 1" +,,中国 +" 123 \ No newline at end of file diff --git a/test/file/read.ini b/test/file/read.ini new file mode 100644 index 0000000..0bfb58e --- /dev/null +++ b/test/file/read.ini @@ -0,0 +1,28 @@ +# 这是读取的配置文件 + +[张三] +年级 = 2 +班级 = 1 +类别 = 文科 +成绩1 = 数学(90) +成绩2 = 英语(70) +成绩3 = 物理(95) +成绩4 = 化学(80) + +[李四] +年级 = 3 +班级 = 4 +类别 = 文科 +成绩1 = 数学(60) +成绩2 = 语文(90) +成绩3 = 英语(70) +成绩4 = 历史(95) +成绩5 = 政治(85) + +[王二麻] +年级 = 1 +班级 = 2 +类别 = 退学 + +[aa[]] + diff --git a/test/file/read.json b/test/file/read.json new file mode 100644 index 0000000..0e6bed6 --- /dev/null +++ b/test/file/read.json @@ -0,0 +1,15 @@ +{ + "name": "varch json", + "version": "1.0.0", + "description": "This is a C language version of json streamlined parser.", + "repository": "https://gitee.com/Lamdonn/varch", + "keywords": [ + "json", + "varch", + "streamlined", + "parser" + ], + "history": [1.0, 1.1, 1.2, 1.21, 1.3, 1.31, 1.32, 1.42], + "open": true, + "license": "GPL3.0" +} \ No newline at end of file diff --git a/test/file/read.md b/test/file/read.md new file mode 100644 index 0000000..a7d7a24 --- /dev/null +++ b/test/file/read.md @@ -0,0 +1,3 @@ +| col 1 | col 2 | col 3 | col 4 | col 5 | +|:------|-------|:------------------:|-------|------:| +| 11 | | 31 | 41 | 51 | \ No newline at end of file diff --git a/test/file/read.xml b/test/file/read.xml new file mode 100644 index 0000000..16356c9 --- /dev/null +++ b/test/file/read.xml @@ -0,0 +1,15 @@ + + + + Harry Potter + J K.Rowling + 2005 + 29.99 + + + Learning XML + Erik T.Ray + 2004 + 39.95 + + \ No newline at end of file diff --git a/test/file/write.csv b/test/file/write.csv new file mode 100644 index 0000000..87f1d8c --- /dev/null +++ b/test/file/write.csv @@ -0,0 +1,4 @@ +ID,Name,Gender,Age,Height,Weight +20240107001,ZhangSan,Man,18,178,56 +20240107002,LiSi,Woman,24,162,62 +20240107005,Wangwu,Woman,21,160 \ No newline at end of file diff --git a/test/file/write.ini b/test/file/write.ini new file mode 100644 index 0000000..3d0cfa5 --- /dev/null +++ b/test/file/write.ini @@ -0,0 +1,25 @@ +[张三] +年级 = 2 +班级 = 1 +类别 = 文科 +成绩1 = 数学(90) +成绩2 = 英语(70) +成绩3 = 物理(95) +成绩4 = 化学(80) + +[李四] +年级 = 3 +班级 = 4 +类别 = 文科 +成绩1 = 数学(60) +成绩2 = 语文(90) +成绩3 = 英语(70) +成绩4 = 历史(95) +成绩5 = 政治(85) + +[王二麻] +年级 = 1 +班级 = 2 +类别 = 退学 + +[aa[]] diff --git a/test/file/write.json b/test/file/write.json new file mode 100644 index 0000000..37d663f --- /dev/null +++ b/test/file/write.json @@ -0,0 +1,17 @@ +{ + "name": "Lisi", + "table": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "null": null, + "pi": 3.14159, + "true": true, + "false": false, + "int": 178, + "float": 12.3333, + "string": "18", + "array": [null, true, false, []], + "object": { + "1": false, + "2": 15 + }, + "数组": [] +} \ No newline at end of file diff --git a/test/file/write.md b/test/file/write.md new file mode 100644 index 0000000..518902f --- /dev/null +++ b/test/file/write.md @@ -0,0 +1,7 @@ +| | Zhang San | Li Si | Wang Wu | +|--------|:--------------|:------------:|--------------------:| +| age | 18 | 24 | 20 | +| gender | man | woman | man | +| height | 178.5 | 165 | 175 | +| weight | 65 | 48 | 75 | +| email | 123321@qq.com | lisi@163.com | ww1234567890@qq.com | diff --git a/test/file/write.xml b/test/file/write.xml new file mode 100644 index 0000000..10aa67e --- /dev/null +++ b/test/file/write.xml @@ -0,0 +1,5 @@ + + xml parser + This is a C language version of xml parser. + GPL3.0 + diff --git a/test/test_cQueue.c b/test/test_cQueue.c new file mode 100644 index 0000000..4b297d3 --- /dev/null +++ b/test/test_cQueue.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "cQueue.h" + +void test_int(void) +{ + typedef struct + { + cQueue queue; + int data[10]; + } intQueueType; + intQueueType intQueue; + + cQueue_init(intQueue); + + for (int i = 0; i < intQueue.queue.cap; i++) + { + cQueue_push(intQueue, i); + } + + while (intQueue.queue.size > 0) + { + int data; + cQueue_pop(intQueue, data); + printf("cQueue_pop %d\r\n", data); + } +} + +void test_struct(void) +{ + typedef struct + { + char *name; + int age; + } Stu; + typedef struct + { + cQueue queue; + Stu data[10]; + } StuQueueType; + StuQueueType StuQueue; + + Stu s = {"Zhang", 18}; + + cQueue_init(StuQueue); + + for (int i = 0; i < StuQueue.queue.cap; i++) + { + s.age = 18 + i; + cQueue_push(StuQueue, s); + } + + while (StuQueue.queue.size > 0) + { + cQueue_pop(StuQueue, s); + printf("cQueue_pop name: %s age %d\r\n", s.name, s.age); + } +} + +static void test(void) +{ + printf("cQueue test!\r\n"); + + // test_int(); + test_struct(); + + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_calculate.c b/test/test_calculate.c new file mode 100644 index 0000000..0871012 --- /dev/null +++ b/test/test_calculate.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "calculate.h" +#include +#include +#include "command.h" +#include +#include + +static int command_calculate(int argc, char *argv[]) +{ + double r = NAN; + if (argc < 2) return 0; + r = calculate(argv[1]); + if (fabs(floor(r) - r) <= DBL_EPSILON && fabs(r) < 1.0e60) printf("%.0lf\r\n", r); + else if (fabs(r) < 1.0e-6 || fabs(r) > 1.0e9) printf("%e\r\n", r); + else + { + char p[64]; + int len = 0; + len = sprintf(p, "%lf", r); + while (len > 0 && p[len-1] == '0' && p[len-2] != '.') {p[--len] = 0;} + printf("%s\r\n", p); + } + return 1; +} + +static double factorial(double n) +{ + if (n < 1) return 1; + return n * factorial(n - 1); +} + +static double K() +{ + return 1024.0; +} + +static void test(void) +{ + printf("mul %lf\r\n", calculate(" ( 99 * 3 ) ")); + printf("min %lf\r\n", calculate(" min (12, 3)")); + printf("sin %lf\r\n", calculate("sin ( 11 / 2 * pi ) + 100 ")); + + calculate_export("fac", factorial, 1); + calculate_export("K", K, 0); + + command_export("cal", command_calculate); +} +init_export_app(test); diff --git a/test/test_check.c b/test/test_check.c new file mode 100644 index 0000000..f479524 --- /dev/null +++ b/test/test_check.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "check.h" + +typedef uint8_t (*checkType)(uint8_t* data, uint32_t len); + +static checkType checkFunTable[] = { + check_sum, + check_parity, + check_lrc, + check_xor, +}; + +static void test(void) +{ + char *testSample[] = { + "Hello", + "check algorithms", + "123456789", + }; + uint8_t check = 0; + for (int i = 0; i < sizeof(testSample) / sizeof(testSample[0]); i++) + { + printf("testSample %d\r\n", i); + for (int j = 0; j < sizeof(checkFunTable) / sizeof(checkFunTable[0]); j++) + { + check = (checkFunTable[j])(testSample[i], strlen(testSample[i])); + printf("Func %d, check %02X\r\n", j, check); + } + } +} +init_export_app(test); diff --git a/test/test_command.c b/test/test_command.c new file mode 100644 index 0000000..a594b92 --- /dev/null +++ b/test/test_command.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "command.h" + +int func1(int argc, char *argv[]) +{ + printf("I am func1!\r\n"); + printf("argc = %d\r\n", argc); + for (int i = 0; i < argc; i++) + { + printf("argv[%d] = %s\r\n", i, argv[i]); + } + return 1; +} + +int func2(int argc, char *argv[]) +{ + printf("I am func2!\r\n"); + return 1; +} + +int func3(int argc, char *argv[]) +{ + printf("I am func3!\r\n"); + return 1; +} + +static void test(void) +{ + command_export("func1", func1); + command_export("func2", func2); + command_export("func3", func3); + + command("cmd -nl"); + printf("--------------------------\r\n"); + command("cmd -ln"); + printf("--------------------------\r\n"); + // command("func2"); + // printf("--------------------------\r\n"); + // command("func3"); + // printf("--------------------------\r\n"); + // command("func1 1 2 3"); + // printf("--------------------------\r\n"); + // command("func1 1\\ 2 3"); + // printf("--------------------------\r\n"); + // command("func1 \"1 2 3\""); +} +init_export_app(test); diff --git a/test/test_crc.c b/test/test_crc.c new file mode 100644 index 0000000..e3774ad --- /dev/null +++ b/test/test_crc.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "crc.h" + +uint32_t std_crc(uint8_t* data, uint32_t len, uint32_t index) +{ + uint32_t crc = 0; + + switch (index) + { + case 0: crc = crc4_itu(data, len); break; + case 1: crc = crc5_epc(data, len); break; + case 2: crc = crc5_itu(data, len); break; + case 3: crc = crc5_usb(data, len); break; + case 4: crc = crc6_itu(data, len); break; + case 5: crc = crc7_mmc(data, len); break; + case 6: crc = crc8(data, len); break; + case 7: crc = crc8_itu(data, len); break; + case 8: crc = crc8_rohc(data, len); break; + case 9: crc = crc8_maxim(data, len); break; + case 10: crc = crc16_ibm(data, len); break; + case 11: crc = crc16_maxim(data, len); break; + case 12: crc = crc16_usb(data, len); break; + case 13: crc = crc16_modbus(data, len); break; + case 14: crc = crc16_ccitt(data, len); break; + case 15: crc = crc16_ccitt_false(data, len); break; + case 16: crc = crc16_x25(data, len); break; + case 17: crc = crc16_xmodem(data, len); break; + case 18: crc = crc16_dnp(data, len); break; + case 19: crc = crc32(data, len); break; + case 20: crc = crc32_mpeg_2(data, len); break; + default: + break; + } + + return crc; +} + +static void test(void) +{ + char *testSample[] = { + "Hello", + "crc algorithms", + "123456789", + }; + uint32_t crc0 = 0, crc1 = 0; + + for (int i = 0; i < sizeof(testSample) / sizeof(testSample[0]); i++) + { + printf("testSample %d\r\n", i); + for (int j = 0; j < sizeof(crcParaModelTable) / sizeof(crcParaModelTable[0]); j++) + { + crc0 = std_crc(testSample[i], strlen(testSample[i]), j); + crc1 = crc(testSample[i], strlen(testSample[i]), &crcParaModelTable[j]); + printf("crc %02d, crc0 %08X, crc1 %08X, same %d\r\n", j, crc0, crc1, (crc0 == crc1) ? 1 : 0); + } + } +} +init_export_app(test); diff --git a/test/test_csv.c b/test/test_csv.c new file mode 100644 index 0000000..4199213 --- /dev/null +++ b/test/test_csv.c @@ -0,0 +1,188 @@ +#include +#include +#include "init.h" +#include "csv.h" +#include "valloc.h" + +#define READ_FILE "source/application/test/file/read.csv" +#define WRITE_FILE "source/application/test/file/write.csv" + +static void test_dump(void) +{ + csv_t csv; + const char *array[3][5] = { + {"ID", "Name", "Gender", "Age", "Height"}, + {"20240107001", "ZhangSan", "Man", "18", "178"}, + {"20240107002", "LiSi", "Woman", "24", "162"}, + }; + char *out[2][3]; + + csv = csv_create(3, 5, array); + if (!csv) + { + printf("create csv fail!\r\n"); + return; + } + + int row = csv_row(csv); + int col = csv_col(csv); + printf("row %d, col %d\r\n", row, col); + + csv_to_array(csv, 2, 4, out, 2, 3); + + printf("out: %s\r\n", out[1][2]); + +#if 0 + csv_set_text(csv, 1, 1, "DID"); + csv_set_text(csv, 1, 2, "Function"); + csv_set_text(csv, 1, 3, "Comment"); + + csv_set_text(csv, 2, 1, "$D131"); + csv_set_text(csv, 2, 2, "Sample ID"); + csv_set_text(csv, 2, 3, "Yes/No"); + + csv_set_text(csv, 3, 3, "中国"); + + csv_copy_cell_to(csv, 3,3, 3, 1); + + csv_cut_cell_to(csv, 3, 1, 7, 10); +#endif + // csv_insert_col(csv, 2); + char *rows[5] = {"20240107005", "Wangwu", "Woman", "21", "160"}; + csv_insert_row(csv, 0, rows, 5); + char *cols[3] = {"Weight", "56", "62"}; + csv_insert_col(csv, 0, cols, 3); + + // csv_move_row_to(csv, 6, 1); + // csv_move_col_to(csv, 6, 3); + // csv_copy_row_to(csv, 4, 2); + // csv_copy_col_to(csv, 4, 2); + // csv_insert_cell(csv, 2, 4, 1); + // csv_delete_cell(csv, 2, 4, 1); + + // csv_insert_col(csv, 0, NULL, 0); + // csv_insert_col(csv, 0, NULL, 0); + + csv_minify(csv); + +#if 0 + const char *text = NULL; + csv_for_each(csv, row, col, text) + { + printf("[%d, %d]: %s\r\n", row, col, text); + } +#endif + + // printf("cell count %d.\r\n", csv_cell(csv)); + + while (csv_find(csv, "6", CSV_F_FLAG_MatchForward | CSV_F_FLAG_MatchByCol, &row, &col) == 1) + { + printf("[%d, %d]: %s\r\n", row, col, csv_get_text(csv, row, col)); + } + + printf("File length %d.\r\n", csv_file_dump(csv, WRITE_FILE)); + + csv_delete(csv); +} + +static void test_load(void) +{ + csv_t csv; + unsigned int row, col; + int i, j; + const char *text; + int error; + int line; + int ecol; + + csv = csv_file_load(READ_FILE); + if (!csv) + { + error = csv_error_info(&line, &ecol); + printf("Load csv file fail! Error line %d column %d code %d.\r\n", line, ecol, error); + return; + } + + row = csv_row(csv); + col = csv_col(csv); + text = csv_get_text(csv, 1, 3); + + printf("len %d, %s\r\n", strlen(text), text); + printf("row %d, col %d\r\n", row, col); + + for (i = 1; i <= row; i++) + { + for (j = 1; j <= col; j++) + { + text = csv_get_text(csv, i, j); + if (text) + { + if (j != 1) printf(","); + printf("%s", text); + } + } + printf("\r\n"); + } + + csv_delete(csv); +} + +static void dump_demo(void) +{ + csv_t csv; + const char *array[3][5] = { + {"ID", "Name", "Gender", "Age", "Height"}, + {"20240107001", "ZhangSan", "Man", "18", "178"}, + {"20240107002", "LiSi", "Woman", "24", "162"}, + }; + + csv = csv_create(3, 5, array); + if (!csv) + { + printf("create csv fail!\r\n"); + return; + } + + if (csv_file_dump(csv, "info.csv") < 0) + { + printf("csv dump fail!\r\n"); + } + else + { + printf("csv dump success!\r\n"); + } + + csv_delete(csv); +} + +static void load_demo(void) +{ + csv_t csv; + + csv = csv_file_load("info.csv"); + if (!csv) + { + printf("csv load fail!\r\n"); + return; + } + + unsigned int row, col; + const char *text = NULL; + csv_for_each(csv, row, col, text) + { + printf("[%u, %u]: %s\r\n", row, col, text); + } + + csv_delete(csv); +} + +static void test(void) +{ + // test_dump(); + // test_load(); + + // dump_demo(); + load_demo(); + printf("use %d\r\n", v_check_used()); +} +init_export_app(test); diff --git a/test/test_dList.c b/test/test_dList.c new file mode 100644 index 0000000..2dff1ba --- /dev/null +++ b/test/test_dList.c @@ -0,0 +1,337 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "dList.h" + +static void test_create(void) +{ + dList *list = NULL; + + list = dList_create(); + if (!list) + { + printf("dList_create Fail!\r\n"); + return; + } + + printf("dList_create Success!\r\n"); + + dList_delete(list); +} + +static void test_set(void) +{ + dList *list = NULL; + int dataInt = 3; + char *dataString = "Hello dList"; + + list = dList_create(); + if (!list) + { + printf("dList_create Fail!\r\n"); + return; + } + + printf("dList_create Success!\r\n"); + + dList_set(list, &dataInt, sizeof(dataInt)); + printf("list->data %d\r\n", dList_ref(list, int)); + + dList_set(list, dataString, strlen(dataString) + 1); + printf("list->data %s\r\n", ((char *)(list->data))); + + dList_delete(list); +} + +static void test_insert(void) +{ + dList *list = NULL; + + for (int i = 0; i < 2; i++) + { + if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL; + } + + int i = 100; + if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL; + + dList_forEachForward(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + + printf("------------\r\n"); + + dList_forEachReverse(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); +} + +static void test_erase(void) +{ + dList *list = NULL; + + for (int i = 0; i < 5; i++) + { + if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL; + } + + dList_erase(&list, 0, NULL); + + dList_forEachForward(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); +} + +static void test_attach(void) +{ + dList *list = NULL, *a = NULL; + + for (int i = 0; i < 5; i++) + { + if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + for (int i = 0; i < 3; i++) + { + if (!dList_pushBack(&a, &i, sizeof(i))) goto FAIL; + } + + dList_attach(&list, -1, a); + + dList_forEachForward(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); +} + +static void test_detach(void) +{ + dList *list = NULL, *node = NULL; + + for (int i = 0; i < 10; i++) + { + if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL; + } + +#if 1 + node = dList_detach(&list, 0, 3, NULL); + if (!node) + { + printf("dList_detach fail\r\n"); + } +#endif + + dList_forEachForward(node, n) + { + printf("node data %d\r\n", dList_ref(n, int)); + } + + dList_delete(node); + + dList_forEachForward(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); +} + +static void test_push(void) +{ + dList *list = NULL; + + for (int i = 0; i < 5; i++) + { + if (!dList_pushFront(&list, &i, sizeof(i))) goto FAIL; + } + for (int i = 0; i < 5; i++) + { + if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + dList_forEachForward(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); +} + +static void test_pop(void) +{ + dList *list = NULL; + + for (int i = 0; i < 5; i++) + { + if (!dList_pushFront(&list, &i, sizeof(i))) goto FAIL; + } + for (int i = 0; i < 5; i++) + { + if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + dList_popBack(&list); + dList_popBack(&list); + dList_popFront(&list); + + dList_forEachForward(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); +} + +static void test_to(void) +{ + dList *list = NULL, *node; + + for (int i = 0; i < 10; i++) + { + if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + node = dList_to(list, -11); + if (!node) + { + printf("dList_to fail\r\n"); + goto FAIL; + } + + printf("dList_to data %d\r\n", dList_ref(node, int)); + + dList_forEachForward(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); +} + +static void test_size(void) +{ + dList *list = NULL; + + for (int i = 0; i < 10; i++) + { + if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + printf("size %d\r\n", dList_size(list)); + printf("size %d\r\n", dList_size(dList_to(list, 3))); + +FAIL: + dList_delete(list); +} + +static void test_append(void) +{ + dList *list = NULL, *ap = NULL; + + for (int i = 0; i < 10; i++) + { + if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; + if (!dList_pushBack(&ap, &i, sizeof(i))) goto FAIL; + } + + if (!dList_append(list, &ap)) goto FAIL; + + printPoint(ap); + + dList_forEachForward(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); + dList_delete(ap); +} + +static void test_copy(void) +{ + dList *list = NULL, *copy = NULL; + + for (int i = 0; i < 10; i++) + { + if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + copy = dList_copy(list, -5, -1); + if (!copy) + { + printf("dList_copy fail\r\n"); + } + + dList_forEachForward(copy, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); + dList_delete(copy); +} + +static void test_reverse(void) +{ + dList *list = NULL; + + for (int i = 0; i < 10; i++) + { + if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + if (!dList_reverse(list, 1, 5)) + { + printf("dList_reverse fail\r\n"); + } + + dList_forEachForward(list, n) + { + printf("data %d\r\n", dList_ref(n, int)); + } + +FAIL: + dList_delete(list); +} + +static void test(void) +{ + printf("dList test!\r\n"); + + // test_create(); + // test_set(); + // test_insert(); + // test_erase(); + // test_attach(); + // test_detach(); + // test_push(); + // test_pop(); + // test_to(); + // test_size(); + test_append(); + // test_copy(); + // test_reverse(); + + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_deque.c b/test/test_deque.c new file mode 100644 index 0000000..2e135c2 --- /dev/null +++ b/test/test_deque.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "deque.h" + +static void test_deque(void) +{ + deque_t deque = deque(int, 10); + int i = 0; + + for (i = 0; i < deque_capacity(deque); i++) + { + deque_push_back(deque, &i); + } + deque_pop_front(deque, NULL); + deque_pop_back(deque, NULL); + for (i = 0; i < deque_size(deque); i++) + { + printf("deque[%d] = %d\r\n", i, deque_at(deque, int, i)); + } + + _deque(deque); +} + +static void test(void) +{ + test_deque(); + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_dict.c b/test/test_dict.c new file mode 100644 index 0000000..6a3f873 --- /dev/null +++ b/test/test_dict.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +// #include "valloc.h" +#include "dict.h" + +#if 0 +static void dict_print(dict_t dict) +{ + char *key; + void *value; + void *error; + if (!dict) return; + dict_it_init(dict); + error = dict_error(dict); + while (1) + { + value = dict_it_get(dict, &key); + if (value == error) break; + printf("key: %s,\tvalue: %d\r\n", key, *(int *)value); + } +} + +#define TEST_COUNT 1000 +static void test_performance(void) +{ +#if 0 + char name[10]; + int len; + int i, j; + printf("char *name[%d] = {", TEST_COUNT); + for (i = 0; i < TEST_COUNT; i++) + { + len = rand() % 9 + 1; + for (j = 0; j < len; j++) + { + name[j] = rand() % ('\\' - '0') + '0'; + } + name[j] = 0; + + printf("\"%s\", ", name); + } + printf("};\r\n"); +#else + // char *name[100] = {":A", "1?FLI1N7", "37JD:H", "[8", "QJ6F[3[", "B:>MOM", "S:MEUG0E", "D76Y6S", "XA6V@MUU4", "L9:=U10", "Z", "F", "RHWZ7<4", "TDS[>7L8D", "O", "VZ", "OC>N", "KM", "BM8@", "8LR<0E", "U", "8", "B7R7", "9H", "=?RVMN:", "Y3WH<[>0[", "R<=L0V24", "JT8@T7K", "D?", "71F9R4?", "US<;I1XJ", "Z640Z<@R", "C=TSR[<", "LGA0J", ";VY=", "=?OO", "T", "CH", "MXCZPN88B", "TY>0", "860IUB[", "C=5TR", "8", ";XMQP", "NMT0", "K>0XZ6AG[", "FLOTQ[23K", ">GDGO0", "ATM", "W[", "TZGMY[X", "K3PU", "R@", "16LEN@Y", "9", "1JMD", "YBZJ", "YF", "1J0", "4:N:<", ">9Z", "7>", "@SAE;TV", ";DZ8CNM", "6", "1@5", "4", "2R@8Y60Q", "UOMNI;Y29", "YW;B1N", "U", "K", "NMPF=5", "=>", "717=KBN5", }; + char *name[1000] = {":A", "1?FLI1N7", "37JD:H", "[8", "QJ6F[3[", "B:>MOM", "S:MEUG0E", "D76Y6S", "XA6V@MUU4", "L9:=U10", "Z", "F", "RHWZ7<4", "TDS[>7L8D", "O", "VZ", "OC>N", "KM", "BM8@", "8LR<0E", "U", "8", "B7R7", "9H", "=?RVMN:", "Y3WH<[>0[", "R<=L0V24", "JT8@T7K", "D?", "71F9R4?", "US<;I1XJ", "Z640Z<@R", "C=TSR[<", "LGA0J", ";VY=", "=?OO", "T", "CH", "MXCZPN88B", "TY>0", "860IUB[", "C=5TR", "8", ";XMQP", "NMT0", "K>0XZ6AG[", "FLOTQ[23K", ">GDGO0", "ATM", "W[", "TZGMY[X", "K3PU", "R@", "16LEN@Y", "9", "1JMD", "YBZJ", "YF", "1J0", "4:N:<", ">9Z", "7>", "@SAE;TV", ";DZ8CNM", "6", "1@5", "4", "2R@8Y60Q", "UOMNI;Y29", "YW;B1N", "U", "K", "NMPF=5", "=>", "717=KBN5", ";2BZX", "5;QAV7G[", "1", "X62:9M4", "I=PX", ";", ";Q47WKV4P", "A", "YLOUVG;", "R18", "2Y>Z", "1>", "RV=NE7:", "9KA", "O", "CJ4", "5", "7QC9OQL5S", ":1<", "7RJ", "0", "EUAMAT", "EZTS5", ";?=D[VOAH", "5", "XWJR", "B", "L;A244", "2X;LZZ", ";BB", "6;", "?", "NOPPF3", ":PV3:Z", "MDGX", "FV", "9QAS", "4Y:E3Y9S", "PBIX<4J", "O<", ";L79[6B", "0M34JQ1M", "NEFC?", "?K8ZQ?N?", "RF;QM6YMS", "9B9;G@", "0V@?", "8", "1J7A", "GCFE7<6=M", "H6EB?8Z", "5", "EZRWJL@8", "9XSMB<<", "WIT", "IZG0QOZ", "=@B;", "QU;JEGSU", "X;28VA960", "HGS=OQN0", "I;8>HC@E", "8>PHI:QW", "Z149I", "0?R91RSJ", "CAEVQZ<", "6", "9R5O>SPBS", "8E", "CP7X7H", "1RTQ7595", "SP4J>;@50", "@GCU4VIP:", "P49", "AZNZK;UC", "A", ">M", "W54;L[59U", "YGW", "4HLE[", "W21PY", "5V;R86?", "V0", "[M", "IJGXO", "E?H", "5C", "KKX4", "RT[ZB?N;V", "B1:M4", "KUI7L4=VE", "[=", "F9KT=PR:R", "4X", "XF37C;5", "NPC>0T", "?JQ=STJ", "H", ";>E?O2", "95V4@D538", "ULBJD", ">76I;BB", "=P", "AV9@?", "4B0KMP==<", "7W[", "9J", ":VG58T", "[", "781", "E5@E", "EMY;B0B=", "AZ2F8@1", "0=", "[C", "L", "7", "202NE7L", "IL", "VH0<2Z", "VCNVPQV7", "Z46GJ6I<3", "XTR", "W41?A3=", "YPD;X9I4", "GL>2", "D3Z", "P?SZT62", "EW", "RM=F60C?", "[G53", "LQ9779?", "0", ">KTK4O[?", "<=[=8R?", "RXN", "HW=83FG", "Z=T15C", "4HH91S", "BM", "9", "1LXN", ">S", "2QYZ3EZ3N", "?I>D[[?0L", "N40", "7OFY2=J42", "FJ", "Y58ADQ7X", "6<9:D", ">D7Y8", ">NBYR", "EQ[ZN46", "1@N", "HTM93I1U", "8", "N>5@8", "JI[?HBE", "H06@6", "A[M", "F:3SP<2", "2LR:", "AT1", "H", "EJ8P<>50", "=UX[NY6", "RCMD", "=", "A30", "6HB4JJT?Y", "?=", "L:", "?KE?9N", "GDV", "SER8", ";HI", "DCNN", "RK4", "P?YF", "P7ZKOBE", "NBX", "LKV9D>5", "E", "A4", "F<=WR", "4Y[", "4B", ":H", "JBO", "1D@", "827199[", "=>5@LR<", ";", ">?XY3", "B0RCE6>59", "JVNITC?Z", "[8HNCYD", ">A>R;S", "NP6D=?YW", "3DF[;X", "O4?U0P=;F", "1PY<", "30CG=", "XLXCHV", "TRMGVD@V", "MI7S3X8", "L;TG:IR", ":6I:0D2", "514", "U", ";Z", "VQGRY?", "LKB", "8LDVR25:Z", "03MI5Z0T", "DO", "0YH?T8Z", "GJ", "[E", "E2", "C", "Q[R>49>", "6R4PR1<", ">Y[1DJ", "WGV", "17<6X;A:", "JHQ8RUXI>", "JLJJ5FL", "A7<:LCG", "SR7", "J", "1SAT4", "UC[W31E", "GIK@8W", "T?", "3", "T:L", "X5N=", "S3", ":FJQ", "GBF51", "QAZJ8", "MRT?", "7;0V", "U", "HHS", "JHF5C1", "DTDUR", "1:7TFZNNN", "HO0", "O0X:JXQ7", ":><2:ILKHNHQU", "C", "KYU9MIPK@", "4", "4:18ALT", "ZX", "Z2P8X", "KG7IATN", "H>;KB", "74", "@EFL2X", "RW", "F3C", "6CL", "", ":B5B", "IJXT", "RNF", "K4JOC", "MXC", "RXC?39A>4", "P4H>EA", "O", "K=6", "WUJB", "FU9=Q44=", "FKV", "?B", "5WB:KBU", "01TVKNT", "GFWQJC2", "AM:VDM@1", ":", "KL50M", "T", ":F;TE", "7:", "2", "P", "UCR=H6S", "A381L", "6;X5", "VA9?JT", "ZVR7>", "J5U6OL", "4O", "J@ZP3V>", "P7MHQYJP", "TWJXE7R3", "BQ[", "H1", "FQEUGY", "N2ID", "7L:D[T@D", "J[>", "P=K", "R1O[ZHG?I", "U75", "RQF:P", "3LRMK", "NTUQHK:O", "L;JC=SD", "4IK", "PCZEC2", "75TVOP7", "0G?1N", "H3UV[", "BHWGJ@0H", "GA:Z", "4GI", ";?6E16", ">", "FFK@=", "OKW34CC3<", "2B:", "[>", "RXRT2", "6U3LXX", "9KE57", "0", "LQ8", "7VO6L0", "H:8JS?C?C", ";", "D>R1E?2H", "S8JZ2QHNQ", ":C5<;DO2?", "=LB3BDI9Z", "59QO7;D", "AN", "K5PWTL2V", "DKH", "O[", "PL8=", "AFD0Q", "76G;94CH", "H2;", "Z?H<", "[", "NL7[A1", ">1NOM", ":XYR?EO", "[C?Y:@", "8S8VCTU", "URK>XR[", "D?", ":7", "BFEUV", "GNGOE8IA", "VNU4=7D3", "4GZM", "5KKLHXZBC", "AJII7Z", "ELVIAF1X", "IWGNM", "=PQ=MO", "4SIO@", ">", "LTCFU=DW9", "X5", "K:", "@Z[NK", "U8YBW", "1", "H", "ED", "?Q4NZ5J", "NMQ@28?", "SEI[V0>LE", "8JYL9ELB", "@", "?J2JJQ>", "E9N4P8", "36WY", "W84E;", "YRV", "D", ">F", "A;3;9WLMY5I@", "0:248A", "MGX", "?PLAJ", "?35T", "=GXS", "36Q", "V6[Q85R", "OK", "O@7IZ", "?1", "79", "OJ>QMYF@;", "S0=YST4", "=;:Y<82", ":", "3O1W", "WM2I2>R", "YD@8?K5", "L", "DKZCUP93", "YI7PX<", "F>IP", "[8:7G=PT", "1P1[K>7UU", ":B", "BSWM=:OE4", "MS=", "HE", "ZFM", "YQ9I", "3HP", "S", "[", "RC7[4P", "7QQ5@@=", "LSH3", ">L=>ZEW9", "W", "@RK@", "?U2H3KYC", "P37", "JAK:6YLKU", "98=", "ZXUT", "O>P:", "5M>UPE6R", "90D", "4GD", "P4N:223", "::MZD2GI", "@Q>GGL", "[I?34;E=", "K[U628", "Q=:M", "Q;850F", "HJ=G743P", "HE7K", "1QF91O", ";A=04YJ4", "N4FC", "5H7=", "7", "GR:", "09Q1XO", "QE=UB", "ZR2FE[", ";", "NJ:DLQ8J", "E354Y1", "R8JO?C>HL", "E", "@8XF", "J", "W?JY7HEV4FN", "J<3R[R", "2594PC5:", "L59", "ZMB8?P3CO", "Q=1TG", "2H", "793WLL", "5<7>[5", "AQS6TN=1", "2F7IZ95Y", "?08CL:", "XAVWZR", "OI", "0OK13MH:", "ZX", "W9", "WUF[3G", "XU:", "KC2LQ2@", "G", "FG5MF1", "UN09HZ8K", "H0?:>Z", "@KCVC:", "WWCA<=3", "ZK", "6OLN", "CAEB2VEY8", "9J6XH1", "CBH", "F<6IC", "<4WCGPWNBFT>", ">IA", "5JN", "8<", "Y0AUX", "5=RX055WS", "U4U?BB9J", "S=V[I7AI", ":EZS", "P8:==JI<", "M5;G[D5C", "B=", "CE[129ZD", "ML5WZVY", "6ULO47N", "P677WQJ", "Q3VBF>", "7", "DFA>DXX", "HKU", ">=WX", "FSM", "LB4ZUJ", "898M8IC", "Y@AYCN4Q", "C5?ZA0G", "W3CP68", "?TEGUYKRQ", "O86;BIO", "@5TA4=<79", "=U", "5", ">4GO", ">XLEH;9Z<", "BME:FI", ":8QNF", "Z3P", "7", "OQAS>", "6", "E1MH2", "KYGN", "KL:S:", "1N", "LOL5R5N", "L?", "X", "TE@K", "=K:8NW", "EI1FOGJB", "7", "Q", "6A8LQFX", ">G?3J1;<", "5", "Y7OLN=2", "EOF>U?[", "5YA?", "A6?[83", "H8EZS", "J", "Z5OW", "9NMIM", "N", "DE>=", "NQPQE", "V", "<7", "R1;", "O", "7GNTA", "3PW", "UGTN", "P1JF:YYF", "CQJNA=@", "TF@:RW", "50", "L36AS", "FCV<@3", "XO", "MMDJ", "@", "H", "=XQTTEZ", "97W?KZ67E", "885OF;LH1", "I27", "Z@", "S", "IQF84=S3", "C0J", "87", "7BC7X?", "ZF3XWN7", ";@7WED", "0@J", "ZML", "X;0", "CX7L>M7", "0UVMH;<", "88K", "IML[>P1;", "22WB9", "Q", "6DSDRX", "E8XMU[UB4", "UWNWJ6A4", ";>0D56@@", "7=3LEC>", "WKL[I", ">;U", "A:1BYSV", "XD", "<3", "IJ2GETQ@", "BT0@6O934", "HZT?T:;QE", "COOB>?", "BRGZHZ", "ZIM", "PILP", "J47N", "G", "5BATU8LI2", "H1BFQTGB4", "CP", "FSHMY", "8KXKD", "X4=Y77Z", "IXU", "DOY8J5", "4X4QU", "BX;E", "DUE", ":4@8PY5IR", "PO", "?=", "C9UMG8", "CC0<", "=RIGVZP3?", "4JRUQC8", "L", "PY<;5S6", ";", "@H9", "HG@S", ";", "H@", "7TGJ9G", "73", "GSU", "T", "030GQL", "GWBR", "BJOIC", "74", "V;=5:4", "@AVTD>", "NZ", "M=RD", "8FXRAJA", "SP84=V", "O;213Z", "W4Q", "R2IN;9", "K[", "9", "311P", ">J:1U1OYV3", "PPVH?KM9<", "K[?H", "O;?B", "ONML", "NSA", ">5", "3CPB2U3", "I6", "A7", "4BZZJ", "L;Q4Z1YZ", "F7B", "W", "DQT;RZE", "I4O0FMJK", "=T=;U;N9Q", "378@KA", ">LJ;S", "?GL", "LN", "B[POOJ", "U;L6>S>", "V8>8TA", "31:JM", "O?", "5R7TE9O", "D?X", "6;Y", "W", "LO5", "R6QU", "A", "04", "DCE;", "8ME3A5:", "LD4A", "?5QA +#include +#include +#include "init.h" +#include "tool.h" +#include "encrypt.h" +#include + +static void test_des(void) +{ + uint8_t key[8] = "hello"; + uint8_t data_block[8] = {1, 2, 3, 4, 5, 6, 7, 9}; + uint8_t processed_block[8]; + int i; + + des_set_key(key); + + printf("des ecb encrypt: "); + des_crypt_ecb(data_block, processed_block, DES_ENCRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", processed_block[i]); + } + printf("\r\n"); + + printf("des ecb decrypt: "); + des_crypt_ecb(processed_block, data_block, DES_DECRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", data_block[i]); + } + printf("\r\n"); + + /////////////////////////////////////// + printf("des cbc encrypt: "); + des_crypt_cbc(data_block, processed_block, 8, DES_ENCRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", processed_block[i]); + } + printf("\r\n"); + + printf("des cbc decrypt: "); + des_crypt_cbc(processed_block, data_block, 8, DES_DECRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", data_block[i]); + } + printf("\r\n"); +} + +static void test_des3(void) +{ + uint8_t key[24] = "hello world"; + uint8_t data_block[8] = {1, 2, 3, 4, 5, 6, 7, 9}; + uint8_t processed_block[8]; + int i; + + des3_set_key2(key); + + printf("des3 ecb encrypt: "); + des3_crypt_ecb(data_block, processed_block, DES_ENCRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", processed_block[i]); + } + printf("\r\n"); + + printf("des3 ecb decrypt: "); + des3_crypt_ecb(processed_block, data_block, DES_DECRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", data_block[i]); + } + printf("\r\n"); + + /////////////////////////////////////// + printf("des3 cbc encrypt: "); + des3_crypt_cbc(data_block, processed_block, 8, DES_ENCRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", processed_block[i]); + } + printf("\r\n"); + + printf("des3 cbc decrypt: "); + des3_crypt_cbc(processed_block, data_block, 8, DES_DECRYPT); + for (i = 0; i < 8; i++) + { + printf("%d ", data_block[i]); + } + printf("\r\n"); +} + +static void test(void) +{ + // test_des(); + test_des3(); +} +init_export_app(test); diff --git a/test/test_hash.c b/test/test_hash.c new file mode 100644 index 0000000..f05509d --- /dev/null +++ b/test/test_hash.c @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "hash.h" + +static void test(void) +{ + printf("hash_bkdr 0x%X\r\n", hash_bkdr("Hello", 5)); + printf("hash_ap 0x%X\r\n", hash_ap("Hello", 5)); + printf("hash_djb 0x%X\r\n", hash_djb("Hello", 5)); + printf("hash_js 0x%X\r\n", hash_js("Hello", 5)); + printf("hash_rs 0x%X\r\n", hash_rs("Hello", 5)); + printf("hash_sdbm 0x%X\r\n", hash_sdbm("Hello", 5)); + printf("hash_pjw 0x%X\r\n", hash_pjw("Hello", 5)); + printf("hash_elf 0x%X\r\n", hash_elf("Hello", 5)); + printf("hash_dek 0x%X\r\n", hash_dek("Hello", 5)); + printf("hash_bp 0x%X\r\n", hash_bp("Hello", 5)); + printf("hash_fnv 0x%X\r\n", hash_fnv("Hello", 5)); + printf("hash_jdk6 0x%X\r\n", hash_jdk6("Hello", 5)); +} +init_export_app(test); diff --git a/test/test_heap.c b/test/test_heap.c new file mode 100644 index 0000000..34fac30 --- /dev/null +++ b/test/test_heap.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "heap.h" + +static int heap_root_min(void *parent, void *child) +{ + if (*(int *)parent < *(int *)child) return 1; + return 0; +} +static int heap_root_max(void *parent, void *child) +{ + if (*(int *)parent > *(int *)child) return 1; + return 0; +} +static void test_heap(void) +{ + heap_t h = heap_create(sizeof(int), 11, heap_root_max); + int i = 0; + + for (i = 0; i < 11; i++) + { + heap_push(h, &i); + } + printf("size %d\r\n", heap_size(h)); + + heap_pop(h, NULL); + i = -9;heap_modify(h, 6, &i); + i = -100;heap_modify(h, 3, &i); + i = 1000;heap_push(h, &i); + + while (heap_size(h)) + { + if (heap_pop(h, &i)) printf("pop %d\r\n", i); + else printf("pop fail!\r\n"); + } + + heap_delete(h); +} + +static void test(void) +{ + test_heap(); + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_ini.c b/test/test_ini.c new file mode 100644 index 0000000..eaecac1 --- /dev/null +++ b/test/test_ini.c @@ -0,0 +1,86 @@ +#include +#include "init.h" +#include "ini.h" +#include "valloc.h" + +#define READ_FILE "test/file/read.ini" +#define WRITE_FILE "test/file/write.ini" + +static void test_dump(void) +{ + ini_t ini = NULL; // 定义ini对象,习惯初始化为NULL + + ini = ini_create(); // 创建空ini对象 + if (ini == NULL) + { + printf("ini create fail!\r\n"); + return; + } + + /* 添加section */ + ini_add_section(ini, "Zhang San"); + ini_add_section(ini, "Li Si"); + ini_add_section(ini, "Wang Wu"); + + /* 添加键值 */ + ini_set_value(ini, "Zhang San", "age", "18"); + ini_set_value(ini, "Zhang San", "height", "178"); + ini_set_value(ini, "Zhang San", "email", "123456@qq.com"); + + ini_set_value(ini, "Li Si", "age", "20"); + ini_set_value(ini, "Li Si", "gender", "man"); + ini_set_value(ini, "Li Si", "weight", "65"); + + ini_set_value(ini, "Wang Wu", "age", "22"); + + /* 转储ini到文件 */ + ini_file_dump(ini, WRITE_FILE); + + ini_delete(ini); // 用完之后需要删除 +} + +static void test_load(void) +{ + ini_t ini; + char *p = NULL; + int len = 0; + + ini = ini_file_load(READ_FILE); + if (!ini) + { + int line, type; + ini_error_info(&line, &type); + printf("ini parse error! line %d, error %d.\r\n", line, type); + // printf("load fail!\r\n"); + return; + } + printf("load success!\r\n"); + // ini_set_value(ini, "王五", "年级", "2"); + // ini_set_value(ini, "王五", "班级", "1"); + // ini_set_value(ini, "王五", "类别", "理科"); + // printf("%s\r\n", ini_key_name(ini, "王五", 2)); + // + #if 1 + for (int i = 0; i < ini_section_count(ini); i++) + { + char *s = (const char*)ini_section_name(ini, i); + printf("section: [%s]\r\n", s); + int count = ini_pair_count(ini, s); + for (int j = 0; j < count; j++) + { + char *k = (const char*)ini_key_name(ini, s, j); + printf("%s : %s\r\n", k, ini_get_value(ini, s, k)); + } + } + #endif + ini_file_dump(ini, WRITE_FILE); + ini_delete(ini); + v_check_unfree(); +} + +static void test(void) +{ + // test_dump(); + test_load(); +} +init_export_app(test); diff --git a/test/test_init.c b/test/test_init.c new file mode 100644 index 0000000..a996e15 --- /dev/null +++ b/test/test_init.c @@ -0,0 +1,38 @@ +#include +#include "init.h" + +void test_init_hardware(void) +{ + printf("hardware init!\r\n"); +} +init_export_hardware(test_init_hardware); + +void test_init_driver(void) +{ + printf("driver init!\r\n"); +} +init_export_driver(test_init_driver); + +void test_init_system(void) +{ + printf("system init!\r\n"); +} +init_export_system(test_init_system); + +void test_init_module(void) +{ + printf("module init!\r\n"); +} +init_export_module(test_init_module); + +void test_init_app(void) +{ + printf("app init!\r\n"); +} +init_export_app(test_init_app); + +void test_init_system1(void) +{ + printf("system1 init!\r\n"); +} +init_export_system(test_init_system1); diff --git a/test/test_json.c b/test/test_json.c new file mode 100644 index 0000000..7ffe667 --- /dev/null +++ b/test/test_json.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include "init.h" +#include "json.h" +#include "valloc.h" + +#define READ_FILE "test/file/read.json" +#define WRITE_FILE "test/file/write.json" + +static void test_read(void) +{ + json_t root = NULL, x = NULL; + char *s = NULL; + + root = json_file_load(WRITE_FILE); + if (!root) + { + int type = 0, line = 0, column = 0; + type = json_error_info(&line, &column); + printf("error at line %d column %d type %d.\r\n", line, column, type); + return; + } + printf("load success!\r\n"); + + x = json_to_key(root, "version"); + if (x) + { + if (json_isstring(x)) printf("%s\r\n", json_value_string(x)); + } + + x = json_to_index(root, 5, 0); + printf("x type %d, %d\r\n", json_type(x), json_value_int(x)); + + s = json_dumps(root, 0, 0, NULL); + printf("%s\r\n", s); + if (s) free(s); + + /* dump json file */ + // json_file_dump(root, WRITE_FILE); + + json_delete(root); +} + +static void test_write(void) +{ + json_t json, t, j; + int aa[10] = {0,1,2,3,4,5,6,7,8,9}; + char *s; + + /* 创建根结点 */ + json = json_create(); + json_set_object(json, NULL); + + /* 向根节点尾部插入string和int型的两个键值对 */ + json_add_string_to_object(json, "name", "Lisi"); + json_set_array_int(json_add_null_to_object(json, "table"), aa, 10); + + json_add_null_to_object(json, "null"); + json_add_true_to_object(json, "true"); + json_add_false_to_object(json, "false"); + json_add_bool_to_object(json, "bool", JSON_TRUE); + json_add_int_to_object(json, "int", 178); + json_add_float_to_object(json, "float", 12.3333); + json_add_string_to_object(json, "string", "18"); + json_add_null_to_object(json, "array"); + json_add_null_to_object(json, "object"); + json_add_array_to_object(json, "数组"); + json_attach(json, 3, json_create_float_for_object("pi", 3.14159)); + + json_add_null_to_array(t); + json_add_true_to_array(t); + json_add_false_to_array(t); + json_add_bool_to_array(t, JSON_TRUE); + json_add_int_to_array(t, 178); + json_add_float_to_array(t, 12.340000); + json_add_string_to_array(t, "18"); + json_attach(t, 3, json_create_string_for_array("pi")); + + json_erase_by_index(t, 8); + json_erase_by_key(json, "bool"); + + /* get */ + t = json_to_key(json, "int"); + if (json_isint(t)) printf("%d\r\n", json_value_int(t)); + + /* array */ + t = json_to_key(json, "array"); + json_set_array(t, NULL); + json_add_null_to_array(t); + json_add_true_to_array(t); + json_add_false_to_array(t); + json_attach(t, json_size(t), json_set_array(json_create(), NULL)); + + /* object */ + t = json_to_key(json, "object"); + json_set_object(t, NULL); + json_add_false_to_object(t, "1"); + json_add_int_to_object(t, "2", 15); + + /* preview json */ + s = json_dumps(json, 0, 0, NULL); + printf("%s\r\n", s); + if (s) free(s); + + /* dump json file */ + json_file_dump(json, WRITE_FILE); + + json_delete(json); +} + +static void test(void) +{ + printf("json test!\r\n"); + test_write(); + // test_read(); + + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_kern.c b/test/test_kern.c new file mode 100644 index 0000000..daedadd --- /dev/null +++ b/test/test_kern.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "kern.h" +#include + +static unsigned int get_msec(void) +{ + struct timeval mstime; + unsigned int ms = 0; + gettimeofday(&mstime, NULL); + ms = mstime.tv_sec * 1000 + mstime.tv_usec / 1000; + return ms; +} + +void task1(void) +{ + static int count = 0; + printf("task1 running! %d\r\n", ++count); +} + +void task2(void) +{ + static int count = 0; + printf("task2 running! %d\r\n", ++count); +} + +static void test(void) +{ + if (kern_init(get_msec, 1) == KE_OK) + { + printf("kern init success!\r\n"); + } + else + { + printf("*** kern init fail!\r\n"); + return; + } + + // printf("create task %d\r\n", task_create(1000, task1)); + // printf("create task %d\r\n", task_create(500, task2)); + + // kern_schedule(); +} +init_export_system(test); diff --git a/test/test_list.c b/test/test_list.c new file mode 100644 index 0000000..43adad3 --- /dev/null +++ b/test/test_list.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "list.h" +#include "vector.h" + +static void test(void) +{ + list_t list = list(int); // 定义并创建int型list + int i = 0; + unsigned long long start = 0; + unsigned long long stop = 0; + + for (i = 0; i < 10000; i++) list_push_back(list, NULL); // 依次插入 0 1 2 + + // 正向遍历用时 + start = reckon_usec(); + for (i = 0; i < list_size(list); i++) + { + list_at(list, int, i); + } + stop = reckon_usec(); + printf("use %llu us!\r\n", (stop - start)); + + // 反向遍历用时 + start = reckon_usec(); + for (i = list_size(list) - 1; i >= 0; i--) + { + list_at(list, int, i); + } + stop = reckon_usec(); + printf("use %llu us!\r\n", (stop - start)); + + // 随机访问用时 + start = reckon_usec(); + for (i = 0; i < list_size(list); i++) + { + list_at(list, int, rand()%list_size(list)); + } + stop = reckon_usec(); + printf("use %llu us!\r\n", (stop - start)); + + + printf("size %d\r\n", list_size(list)); + + // 结束使用该list后就删除 + _list(list); + + vector_t vt = vector(int, 10000); + + // 正向遍历用时 + start = reckon_usec(); + for (i = 0; i < vector_size(vt); i++) + { + vector_at(vt, int, i); + } + stop = reckon_usec(); + printf("use %llu us!\r\n", (stop - start)); + + _vector(vt); + + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_map.c b/test/test_map.c new file mode 100644 index 0000000..ed8ec4a --- /dev/null +++ b/test/test_map.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "map.h" + +static void test_it(void) +{ + map_t map = map(string, int); + int value; + char *key; + void *data; + int i; + + value = 100; map_insert(map, mpair("hello", &value)); + value = 1; map_insert(map, mpair("ZhangSan", &value)); + value = 2; map_insert(map, mpair("LiSi", &value)); + value = 3; map_insert(map, mpair("WangWu", &value)); + value = 4; map_insert(map, mpair("SunLiu", &value)); + value = 5; map_insert(map, mpair("QianQi", &value)); + + map_it_init(map, MAP_HEAD); + i = map_size(map); + while (i--) + { + data = map_it_get(map, &key, NULL); + printf("map[%s] = %d\r\n", key, *(int *)data); + } + + _map(map); +} + +static void test_map(void) +{ + int value; + map_t map = map(string, int); + if (!map) return; + + value = 100; map_insert(map, mpair("hello", &value)); + value = 110; map_insert(map, mpair("Zhang", &value)); + + printf("size = %d, key size = %d, value size = %d\r\n", map_size(map), map_ksize(map), map_vsize(map)); + + printf("map[hello] = %d\r\n", map_at(map, int, "hello")); + printf("map[Zhang] = %d\r\n", map_at(map, int, "Zhang")); + map_erase(map, "hello"); + + _map(map); +} + +static void test_float_map(void) +{ + int value; + map_t map = map(double, int); + if (!map) return; + + value = 111; map_insert(map, mpair(12.3, &value)); + value = 100; map_insert(map, mpair(16.9, &value)); + printf("%d\r\n", map_at(map, int, 16.9000000000000000000001)); + map_erase(map, 16.9); + _map(map); +} + +static void test(void) +{ + test_it(); + // test_map(); + // test_float_map(); + v_check_unfree(); +} +init_export_app(test); \ No newline at end of file diff --git a/test/test_oscp.c b/test/test_oscp.c new file mode 100644 index 0000000..f8823d6 --- /dev/null +++ b/test/test_oscp.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include "init.h" +#include "kern.h" +#include "oscp.h" +#include + +int level = 0; + +static unsigned int get_msec(void) +{ + struct timeval mstime; + unsigned int ms = 0; + gettimeofday(&mstime, NULL); + ms = mstime.tv_sec * 1000 + mstime.tv_usec / 1000; + return ms; +} + +void task1(void) +{ + oscp_handle(); +} + +void task2(void) +{ + static int count = 0; + + count++; + if (count > 628) count = 0; + + level = RESOLUTION / 2 * sin((double)count / 10) + RESOLUTION / 2; +} + +static void test(void) +{ + if (kern_init(get_msec, 1) == KE_OK) + { + printf("kern init success!\r\n"); + } + else + { + printf("*** kern init fail!\r\n"); + return; + } + + printf("create task %d\r\n", task_create(5, task1)); + printf("create task %d\r\n", task_create(50, task2)); + + oscp_set_scale(O_SCALE_50MS); + oscp_set_monitor(&level); + + kern_schedule(); +} +init_export_app(test); diff --git a/test/test_pthread.c b/test/test_pthread.c new file mode 100644 index 0000000..0a42913 --- /dev/null +++ b/test/test_pthread.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include +#include + +sem_t s; + +static void *thread_entry(void *param) +{ + long thread_id = (long)param; + + for (;;) + { + sem_wait(&s); + printf("in\r\n"); + } + return NULL; +}; + + +static void test(void) +{ + pthread_t t; + pthread_create(&t, NULL, thread_entry, NULL); + printf("pthread\r\n"); + sem_post(&s); +} +init_export_app(test); diff --git a/test/test_queue.c b/test/test_queue.c new file mode 100644 index 0000000..5ba7d4c --- /dev/null +++ b/test/test_queue.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "queue.h" + +static void test_queue(void) +{ + queue_t queue = queue(int, 10); + int i = 0; + + for (i = 0; i < queue_capacity(queue); i++) + { + queue_push(queue, &i); + } + queue_pop(queue, NULL); + queue_pop(queue, NULL); + printf("queue capacity=%d, size=%d, dsize=%d\r\n", queue_capacity(queue), queue_size(queue), queue_dsize(queue)); + for (i = 0; i < queue_size(queue); i++) + { + printf("queue[%d] = %d\r\n", i, queue_at(queue, int, i)); + } + + _queue(queue); +} + +static void test(void) +{ + test_queue(); + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_rbtree.c b/test/test_rbtree.c new file mode 100644 index 0000000..20f2790 --- /dev/null +++ b/test/test_rbtree.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "rbtree.h" + +static void print_int(tree_t tree) +{ + if (tree_data(tree)) printf("%d", *(int *)tree_data(tree)); +} + + + +static void test(void) +{ + printf("rbtree test\r\n"); + + + + v_check_unfree(); + // printf("used %d\r\n", v_check_used()); +} +init_export_app(test); diff --git a/test/test_sList.c b/test/test_sList.c new file mode 100644 index 0000000..b53cbf9 --- /dev/null +++ b/test/test_sList.c @@ -0,0 +1,334 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "sList.h" + +static void test_create(void); +static void test_set(void); +static void test_insert(void); +static void test_erase(void); +static void test_attach(void); +static void test_detach(void); +static void test_push(void); +static void test_pop(void); +static void test_to(void); +static void test_size(void); +static void test_append(void); +static void test_copy(void); +static void test_reverse(void); + +static void test_create(void) +{ + sList *list = NULL; + + list = sList_create(); + if (!list) + { + printf("sList_create Fail!\r\n"); + return; + } + + printf("sList_create Success!\r\n"); + + sList_delete(list); +} + +static void test_set(void) +{ + sList *list = NULL; + int dataInt = 3; + char *dataString = "Hello sList"; + + list = sList_create(); + if (!list) + { + printf("sList_create Fail!\r\n"); + return; + } + + printf("sList_create Success!\r\n"); + + sList_set(list, &dataInt, sizeof(dataInt)); + printf("list->data %d\r\n", sList_ref(list, int)); + + sList_set(list, dataString, strlen(dataString) + 1); + printf("list->data %s\r\n", ((char *)(list->data))); + + sList_delete(list); +} + +static void test_insert(void) +{ + sList *list = NULL; + + for (int i = 0; i < 5; i++) + { + if (!sList_insert(&list, -1, &i, sizeof(i))) goto FAIL; + } + + sList_forEach(list, n) + { + printf("data %d\r\n", sList_ref(n, int)); + } + +FAIL: + sList_delete(list); +} + +static void test_erase(void) +{ + sList *list = NULL; + + for (int i = 0; i < 5; i++) + { + if (!sList_insert(&list, -1, &i, sizeof(i))) goto FAIL; + } + + sList_erase(&list, 0, NULL); + + sList_forEach(list, n) + { + printf("data %d\r\n", sList_ref(n, int)); + } + +FAIL: + sList_delete(list); +} + +static void test_attach(void) +{ + sList *list = NULL, *a = NULL; + + for (int i = 0; i < 5; i++) + { + if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + for (int i = 0; i < 3; i++) + { + if (!sList_pushBack(&a, &i, sizeof(i))) goto FAIL; + } + + sList_attach(&list, -1, a); + + sList_forEach(list, n) + { + printf("data %d\r\n", sList_ref(n, int)); + } + +FAIL: + sList_delete(list); +} + +static void test_detach(void) +{ + sList *list = NULL, *node; + + for (int i = 0; i < 10; i++) + { + if (!sList_insert(&list, -1, &i, sizeof(i))) goto FAIL; + } + + node = sList_detach(&list, 3, -1, NULL); + if (!node) + { + printf("sList_detach fail\r\n"); + } + + sList_forEach(node, n) + { + printf("node data %d\r\n", sList_ref(n, int)); + } + + sList_delete(node); + + sList_forEach(list, n) + { + printf("data %d\r\n", sList_ref(n, int)); + } + +FAIL: + sList_delete(list); +} + +static void test_push(void) +{ + sList *list = NULL; + + for (int i = 0; i < 5; i++) + { + if (!sList_pushFront(&list, &i, sizeof(i))) goto FAIL; + } + for (int i = 0; i < 5; i++) + { + if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + sList_forEach(list, n) + { + printf("data %d\r\n", sList_ref(n, int)); + } + +FAIL: + sList_delete(list); +} + +static void test_pop(void) +{ + sList *list = NULL; + + for (int i = 0; i < 5; i++) + { + if (!sList_pushFront(&list, &i, sizeof(i))) goto FAIL; + } + for (int i = 0; i < 5; i++) + { + if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + sList_popBack(&list); + sList_popBack(&list); + sList_popFront(&list); + + sList_forEach(list, n) + { + printf("data %d\r\n", sList_ref(n, int)); + } + +FAIL: + sList_delete(list); +} + +static void test_to(void) +{ + sList *list = NULL, *node; + + for (int i = 0; i < 10; i++) + { + if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + node = sList_to(list, 10); + if (!node) + { + printf("sList_to fail\r\n"); + goto FAIL; + } + + printf("data %d\r\n", sList_ref(node, int)); + +FAIL: + sList_delete(list); +} + +static void test_size(void) +{ + sList *list = NULL; + + for (int i = 0; i < 10; i++) + { + if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + printf("size %d\r\n", sList_size(list)); + printf("size %d\r\n", sList_size(sList_to(list, 3))); + +FAIL: + sList_delete(list); +} + +static void test_append(void) +{ + sList *list = NULL, *ap = NULL; + + for (int i = 0; i < 10; i++) + { + if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; + if (!sList_pushBack(&ap, &i, sizeof(i))) goto FAIL; + } + + if (!sList_append(list, &ap)) goto FAIL; + + printPoint(ap); + + sList_forEach(list, n) + { + printf("data %d\r\n", sList_ref(n, int)); + } + +FAIL: + sList_delete(list); + sList_delete(ap); +} + +static void test_copy(void) +{ + sList *list = NULL, *copy = NULL; + + for (int i = 0; i < 10; i++) + { + if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + copy = sList_copy(list, 2, -1); + if (!copy) + { + printf("sList_copy fail\r\n"); + } + + sList_forEach(copy, n) + { + printf("data %d\r\n", sList_ref(n, int)); + } + +FAIL: + sList_delete(list); + sList_delete(copy); +} + +static void test_reverse(void) +{ + sList *list = NULL; + + for (int i = 0; i < 10; i++) + { + if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; + } + + if (!sList_reverse(list, sList_front, sList_back)) + { + printf("sList_reverse fail\r\n"); + } + + sList_forEach(list, n) + { + printf("data %d\r\n", sList_ref(n, int)); + } + +FAIL: + sList_delete(list); +} + +static void test(void) +{ + printf("sList test!\r\n"); + + // test_create(); + // test_set(); + // test_insert(); + // test_erase(); + // test_attach(); + test_detach(); + // test_push(); + // test_pop(); + // test_to(); + // test_size(); + // test_append(); + // test_copy(); + // test_reverse(); + + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_set.c b/test/test_set.c new file mode 100644 index 0000000..7cbcac4 --- /dev/null +++ b/test/test_set.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "set.h" + +static void test_it(void) +{ + set_t set = set(int); + int i, index; + void *data; + + i = -100; set_insert(set, i, &i); + i = 1024; set_insert(set, i, &i); + i = 0; set_insert(set, i, &i); + i = 7; set_insert(set, i, &i); + i = -2; set_insert(set, i, &i); + i = -2; set_insert(set, i, &i); + + set_at(set, int, 3) = 100; + + set_it_init(set, SET_HEAD); + i = set_size(set); + while (i--) + { + data = set_it_get(set, &index); + printf("set[%d] = %d\r\n", index, *(int *)data); + } + + _set(set); +} + +static void test_set(void) +{ + set_t set = set(int); + int i; + + for (i = 0; i < 100; i++) + { + set_insert(set, i, &i); + } + printf("size = %d, data size = %d\r\n", set_size(set), set_dsize(set)); + + i = -100; set_insert(set, i, &i); + i = 1024; set_insert(set, i, &i); + + printf("set[6] = %d\r\n", set_at(set, int, 6)); + printf("set[-100] = %d\r\n", set_at(set, int, -100)); + printf("set[1024] = %d\r\n", set_at(set, int, 1024)); + + set_at(set, int, 6) = 11111; + printf("set[6] = %d\r\n", set_at(set, int, 6)); + + _set(set); +} + +static void test(void) +{ + // test_set(); + test_it(); + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_sort.c b/test/test_sort.c new file mode 100644 index 0000000..c207d8f --- /dev/null +++ b/test/test_sort.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "sort.h" +#include "list.h" + +static void test_basic_usage(void) +{ + /* 针对int型数组,进行升序 + */ + int array_int[] = {1, 3, 6, 5, 0, 2, 9, 8, 7, -4}; // 定义时候任意乱序 + + // 传入数组基地址,排序的区间,指定int型的升序ops + sort_bubble(array_int, 0, sizeof(array_int)/sizeof(array_int[0]) - 1, &sops_int_ascend); + + // 输出排序后的数组 + for (int i = 0; i < sizeof(array_int)/sizeof(array_int[0]); i++) + { + printf("%d, ", array_int[i]); + } + printf("\r\n"); + + /* ---------------------------------------- 代码分割线 ---------------------------------------- */ + + /* 针对float型数组,进行降序 + */ + float array_float[] = {-1.1, 2.1, 6.6, 5.0, 0.1, 2.8, 9.9, 9.8, 7.0, -4.5}; // 定义时候任意乱序 + + // 传入数组基地址,排序的区间,指定float型的降序ops + sort_bubble(array_float, 0, sizeof(array_float)/sizeof(array_float[0]) - 1, &sops_float_descend); + + // 输出排序后的数组 + for (int i = 0; i < sizeof(array_float)/sizeof(array_float[0]); i++) + { + printf("%.2f, ", array_float[i]); + } + printf("\r\n"); +} + +static void test_interval(void) +{ + int array_int[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 先定义升序的数组 + + // 传入数组基地址,指定在[3, 7]区间内进行排序,指定int型的降序ops + sort_bubble(array_int, 3, 7, &sops_int_descend); + + // 输出排序后的数组 + for (int i = 0; i < sizeof(array_int)/sizeof(array_int[0]); i++) + { + printf("%d, ", array_int[i]); + } + printf("\r\n"); +} + +// 获取list数据项地址 +static void* list_int_addr(void *array, int index) +{ + return list_data((list_t)array, index); +} + +// 交换list两个成员数据 +static void list_int_swap(void *array, int index0, int index1) +{ + int temp = list_at((list_t)array, int, index0); + list_at((list_t)array, int, index0) = list_at((list_t)array, int, index1); + list_at((list_t)array, int, index1) = temp; +} + +// 显示list存储的数据 +static void list_int_show(list_t list) +{ + for (int i = 0; i < list_size(list); i++) + { + printf("%d, ", list_at(list, int, i)); + } + printf("\r\n"); +} + +static void test_list_sort(void) +{ + list_t list = list(int); // 定义一个int型的list + SOPS ops; // 定义一个新的ops + int array[] = {1, 3, 6, 5, 0, 2, 9, 8, 7, -4}; // 定义要给list装载的数据 + + // 将数据装载到list + for (int i = 0; i < sizeof(array)/sizeof(array[0]); i++) + { + list_push_back(list, &array[i]); + } + + ops.order = sops_int_ascend.order; // 复用int升序规则 + ops.addr = list_int_addr; // 使用list获取元素的地址 + ops.swap = list_int_swap; // list交换数据 + + // 输出排序前的list + list_int_show(list); + + // 进行排序 + sort_bubble(list, 0, list_size(list) - 1, &ops); + + // 输出排序后的list + list_int_show(list); + + // 释放list + _list(list); +} + +typedef struct +{ + char *name; + int age; + char gender; /* 1 表示男,0 表示女 */ + float weight; +} STUDENT; + +static void student_print(STUDENT *s, int size) +{ + for (int i = 0; i < size; i++) + { + printf("name: %s, \t\t age = %d, \t\t gender = %d, \t\t weight = %.2f\r\n", s[i].name, s[i].age, s[i].gender, s[i].weight); + } +} + +static void* student_addr(void *array, int index) +{ + return ((STUDENT *)array) + index; +} + +static void student_swap(void *array, int index0, int index1) +{ + STUDENT temp = ((STUDENT *)array)[index0]; + ((STUDENT *)array)[index0] = ((STUDENT *)array)[index1]; + ((STUDENT *)array)[index1] = temp; +} + +static int student_name_ascend(void *front, void *back) +{ + return -strcmp(((STUDENT *)front)->name, ((STUDENT *)back)->name); +} + +static int student_gender_descend(void *front, void *back) +{ + if (((STUDENT *)front)->gender > ((STUDENT *)back)->gender) return 1; + else if (((STUDENT *)front)->gender < ((STUDENT *)back)->gender) return -1; + else return 0; +} + +static int student_age_descend(void *front, void *back) +{ + if (((STUDENT *)front)->age > ((STUDENT *)back)->age) return 1; + else if (((STUDENT *)front)->age < ((STUDENT *)back)->age) return -1; + else return 0; +} + +static int student_weight_ascend(void *front, void *back) +{ + if (((STUDENT *)front)->weight < ((STUDENT *)back)->weight) return 1; + else if (((STUDENT *)front)->weight > ((STUDENT *)back)->weight) return -1; + else return 0; +} + +static void test_struct(void) +{ + SOPS ops; + STUDENT table[] = { + {"ZhanSan", 18, 1, 56.3}, + {"LiSi", 17, 1, 62.2}, + {"WangWu", 21, 0, 58.9}, + {"ZhaoLiu", 22, 1, 65.5}, + {"SunQi", 18, 1, 71.7}, + {"ZhouBa", 30, 0, 48.8}, + {"WuJiu", 16, 1, 56.3}, + {"ZhenShi", 19, 0, 52.1}, + }; + + ops.addr = student_addr; + ops.swap = student_swap; + + // 指定为按照名字升序排序 + ops.order = student_name_ascend; + sort_bubble(table, 0, sizeof(table)/sizeof(table[0])-1, &ops); + printf("sorting by name in ascending order\r\n"); + student_print(table, sizeof(table)/sizeof(table[0])); + printf("---------------------------------\r\n"); + + // 指定为按照年龄降序排序 + ops.order = student_age_descend; + sort_bubble(table, 0, sizeof(table)/sizeof(table[0])-1, &ops); + printf("sorting by age in descending order\r\n"); + student_print(table, sizeof(table)/sizeof(table[0])); + printf("---------------------------------\r\n"); + + // 指定为按照性别降序排序 + ops.order = student_gender_descend; + sort_bubble(table, 0, sizeof(table)/sizeof(table[0])-1, &ops); + printf("sorting by gender in descending order\r\n"); + student_print(table, sizeof(table)/sizeof(table[0])); + printf("---------------------------------\r\n"); + + // 指定为按照体重升序排序 + ops.order = student_weight_ascend; + sort_bubble(table, 0, sizeof(table)/sizeof(table[0])-1, &ops); + printf("sorting by weight in ascending order\r\n"); + student_print(table, sizeof(table)/sizeof(table[0])); + printf("---------------------------------\r\n"); +} + +static void test(void) +{ + // test_basic_usage(); + // test_interval(); + // test_list_sort(); + test_struct(); +} + +init_export_app(test); diff --git a/test/test_stack.c b/test/test_stack.c new file mode 100644 index 0000000..355b6a2 --- /dev/null +++ b/test/test_stack.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "stack.h" + +static void test_stack(void) +{ + stack_t stack = stack(int, 10); + int i = 0; + + for (i = 0; i < stack_capacity(stack); i++) + { + stack_push(stack, &i); + } + stack_pop(stack, NULL); + stack_pop(stack, NULL); + for (i = 0; i < stack_size(stack); i++) + { + printf("stack[%d] = %d\r\n", i, stack_at(stack, int, i)); + } + + _stack(stack); +} + +static void test(void) +{ + test_stack(); + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_str.c b/test/test_str.c new file mode 100644 index 0000000..c84a314 --- /dev/null +++ b/test/test_str.c @@ -0,0 +1,17 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "str.h" + +static void test(void) +{ + str_t s = str("0123456789"); + str_replace(s, 9, 10, "AAAAAAAAAA"); + printf("len %d, %d\r\n", str_length(s), str_capacity(s)); + printf("%s\r\n", _S(s)); + _str(s); +} +init_export_app(test); diff --git a/test/test_tool.c b/test/test_tool.c new file mode 100644 index 0000000..8581bc6 --- /dev/null +++ b/test/test_tool.c @@ -0,0 +1,57 @@ +#include +#include +#include "init.h" +#include "tool.h" + +static void test_GetStringHex(void) +{ + char out[12]; + for (int i = 0; i < 3; i++) + { + int len = GetStringHex("12 34 56 78 89 ", -1, out, 12); + if (len >= 0) + { + printf("len %d\r\n", len); + for (int i = 0; i < len; i++) + { + printf("%02X ", (char)(out[i]) & 0xFF); + } + printf("\r\n"); + } + else + { + printf("error %d\r\n", len); + } + } + + char a[4] = {0x10, 0xFF, 0x45, 0x1F}; + char buffer[1024]; + ToStringHex(a, 4, buffer, 1024); + printf("%s\r\n", buffer); +} + +static void test_ASSERT(void) +{ + ASSERT(0 == 1); +} + +static void test_print(void) +{ + char c = 'c'; + int i = 123; + float f = 3.14; + char *s = "string"; + + printChar(c); + printInt(i); + printFloat(f); + printString(s); +} + +static void test(void) +{ + // test_GetStringHex(); + // test_ASSERT(); + test_print(); +} +init_export_app(test); diff --git a/test/test_tree.c b/test/test_tree.c new file mode 100644 index 0000000..cd2e0b5 --- /dev/null +++ b/test/test_tree.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include "init.h" +#include "tool.h" +#include "valloc.h" +#include "tree.h" + +static void print_int(tree_t tree) +{ + if (tree_data(tree)) printf("%d", *(int *)tree_data(tree)); +} + +static void print_dir(tree_t tree) +{ + if (tree_data(tree)) printf("%s", (char *)tree_data(tree)); +} + +static void print_ini(tree_t tree) +{ + if (!tree_asize(tree)) return; + printf("%s", (char *)tree_attribute(tree)); + if (tree_dsize(tree)) printf(" = %s", (char *)tree_data(tree)); +} + +static void print_bitree(tree_t tree) +{ + if (tree_dsize(tree)) printf("%s", (char *)tree_data(tree)); +} + +#if 0 +#include "ini.h" +static tree_t tree_from_ini(const char *filename) +{ + tree_t tree = NULL, section, pair; + ini_t ini; + char *section_name, *key, *value; + int i, j; + ini = ini_file_load(filename); + if (!ini) goto FAIL; + tree = tree_create(); + if (!tree) goto FAIL; + for (i = 0; i < ini_section_count(ini); i++) + { + section = tree_create(); + if (!section) goto FAIL; + tree_insert(tree, tree_csize(tree)); + tree_attach(tree, tree_csize(tree) - 1, section); + section_name = (char*)ini_section_name(ini, i); + tree_set_attribute(section, section_name, strlen(section_name) + 1); + for (j = 0; j < ini_pair_count(ini, section_name); j++) + { + pair = tree_create(); + if (!pair) goto FAIL; + tree_insert(section, tree_csize(section)); + tree_attach(section, tree_csize(section) - 1, pair); + key = (char *)ini_key_name(ini, section_name, j); + tree_set_attribute(pair, key, strlen(key) + 1); + value = (char *)ini_get_value(ini, section_name, key); + tree_set_data(pair, value, strlen(value) + 1); + } + } + ini_delete(ini); + return tree; +FAIL: + if (ini) ini_delete(ini); + if (tree) tree_delete(tree, NULL); + return NULL; +} + +static void test_ini(void) +{ + tree_t tree, n; + tree = tree_from_ini("../source/application/test/file/read.ini"); + if (!tree) + { + printf("transfer fail!\r\n"); + } + tree_print(tree_child(tree, 0), 0, print_ini); + tree_delete(tree, NULL); +} +#endif + +#include +#include +static tree_t tree_directory(const char *directory, int depth) +{ + tree_t tree = NULL, n; + DIR *dirptr; + struct dirent* dircontent; + if (depth < 0) return NULL; + tree = tree_create(); + if (!tree) return NULL; + dirptr = opendir(directory); + if (!dirptr) goto FAIL; + while (1) + { + dircontent = readdir(dirptr); + if (dircontent == NULL) break; + if (strcmp(dircontent->d_name,".") == 0 || strcmp(dircontent->d_name,"..") == 0) continue; + struct stat statinfo; + char fullname[1024]; + sprintf(fullname, "%s/%s", directory, dircontent->d_name); + if (stat(fullname, &statinfo)) continue; + if (S_ISDIR(statinfo.st_mode) && depth != 1) n = tree_directory(fullname, depth ? depth - 1 : 0); + else if (S_ISREG(statinfo.st_mode)) n = tree_create(); + else continue; + if (n) + { + if (!tree_insert(tree, tree_csize(tree))) goto FAIL; + tree_attach(tree, tree_csize(tree) - 1, n); + } + tree_set_data(n, dircontent->d_name, strlen(dircontent->d_name) + 1); + } + if (!tree_data(tree)) tree_set_data(tree, directory, strlen(directory) + 1); + return tree; +FAIL: + if (tree) tree_delete(tree, NULL); + return NULL; +} + +static void test_dir(void) +{ + tree_t tree; + tree = tree_directory("./", 0); + if (!tree) + { + printf("load fail!\r\n"); + } + // tree_insert(tree, 1); + // printf("name %s, %d\r\n", tree_data(tree), tree_size(tree)); + tree_print(tree, 0, print_dir); + tree_delete(tree, NULL); +} + +static void test_create(void) +{ + tree_t root, n, t; + int i, data; + + root = tree_create(); + data = 1024; tree_set_data(root, &data, sizeof(data)); + + for (i = 0; i < 12; i++) + { + n = tree_create(); + if (!n) break; + tree_insert(root, tree_csize(root)); + tree_attach(root, tree_csize(root) - 1, n); + data = i; tree_set_data(n, &data, sizeof(data)); + } + + t = tree_to(root, 3); + for (i = 0; i < 8; i++) + { + n = tree_create(); + if (!n) break; + tree_insert(t, tree_csize(t)); + tree_attach(t, tree_csize(t) - 1, n); + data = i + 30; tree_set_data(n, &data, sizeof(data)); + } + + t = tree_to(root, 3, 6); + for (i = 0; i < 4; i++) + { + n = tree_create(); + if (!n) break; + tree_insert(t, tree_csize(t)); + tree_attach(t, tree_csize(t) - 1, n); + data = i + 360; tree_set_data(n, &data, sizeof(data)); + } + + printf("tree size %d\r\n", tree_size(root)); + printf("tree depth %d\r\n", tree_depth(root)); + /* show */ + tree_print(root, 0, print_int); + tree_delete(root, NULL); +} + +static tree_t bitree_gen(int depth) +{ + tree_t tree; + if (depth <= 0) return NULL; + tree = tree_create(); + tree_insert(tree, 0); + tree_insert(tree, 0); + tree_attach(tree, 0, bitree_gen(depth - 1)); + tree_attach(tree, 1, bitree_gen(depth - 1)); + return tree; +} + + + +static void test_bitree(void) +{ + tree_t tree = bitree_gen(5); + tree_set_data(tree_to(tree, 0, 0, 0), "123", strlen("123") + 1); + tree_print(tree, 0, print_bitree); + tree_delete(tree, NULL); +} + +static void test(void) +{ + printf("tree test\r\n"); + + // test_create(); + test_dir(); + // test_ini(); + // test_bitree(); + + v_check_unfree(); + // printf("used %d\r\n", v_check_used()); +} +init_export_app(test); diff --git a/test/test_txls.c b/test/test_txls.c new file mode 100644 index 0000000..cb6ceba --- /dev/null +++ b/test/test_txls.c @@ -0,0 +1,122 @@ +#include +#include +#include "init.h" +#include "txls.h" +#include "valloc.h" + +#define READ_FILE "test/file/read.md" +#define WRITE_FILE "test/file/write.md" + +void txls_preview(txls_t txls) +{ + char* s; + int len = 0; + if (!txls) return; + s = txls_dumps(txls, 1, &len); + printf("s %p\r\n", s); + if (!s) return; + printf("len = %d, <%d, %d>\r\n%s\r\n", len, txls_col(txls), txls_row(txls), s); + free(s); +} + +static void test_read(void) +{ + txls_t x = NULL; // 定义txls对象,习惯初始化为NULL + + /* 加载txls文件 */ + x = txls_file_load(READ_FILE); + if (!x) // 加载失败,定位错误 + { + int line, type; + type = txls_error_info(&line); + printf("txls parse error! line %d, error %d.\r\n", line, type); + return; + } + + /* 遍历表头,定位所在列 */ + int col = 0; + for (col = 1; col <= txls_col(x); col++) + { + if (strcmp("Li Si", txls_get_head(x, col)) == 0) + { + break; + } + } + if (col > txls_col(x)) // 没有查找到 + { + printf("Lookup failed\r\n"); + return; + } + + /* 打印信息 */ + printf("name: %s, age=%s, gender: %s, height=%s, weight=%s, email:%s\r\n", + txls_get_text(x, col, 0), + txls_get_text(x, col, 1), + txls_get_text(x, col, 2), + txls_get_text(x, col, 3), + txls_get_text(x, col, 4), + txls_get_text(x, col, 5)); + + txls_delete(x); +} + +static void test_write(void) +{ + txls_t x = NULL; // 定义txls对象,习惯初始化为NULL + + x = txls_create(4, 5); // 创建4x5的表格 + if (!x) + { + return; + } + + /* 设置表头,第一列留空 */ + txls_set_head(x, 2, "Zhang San"); + txls_set_head(x, 3, "Li Si"); + txls_set_head(x, 4, "Wang Wu"); + + /* 设置对齐方式 */ + txls_set_align(x, 2, TXLS_ALIGN_LEFT); + txls_set_align(x, 3, TXLS_ALIGN_CENTER); + txls_set_align(x, 4, TXLS_ALIGN_RIGHT); + + /* 第一列作为信息类别 */ + txls_set_text(x, 1, 1, "age"); + txls_set_text(x, 1, 2, "gender"); + txls_set_text(x, 1, 3, "height"); + txls_set_text(x, 1, 4, "weight"); + txls_set_text(x, 1, 5, "email"); + + /* 写入每个人信息 */ + // Zhang San + txls_set_text(x, 2, 1, "18"); + txls_set_text(x, 2, 2, "man"); + txls_set_text(x, 2, 3, "178.5"); + txls_set_text(x, 2, 4, "65"); + txls_set_text(x, 2, 5, "123321@qq.com"); + // Li Si + txls_set_text(x, 3, 1, "24"); + txls_set_text(x, 3, 2, "woman"); + txls_set_text(x, 3, 3, "165"); + txls_set_text(x, 3, 4, "48"); + txls_set_text(x, 3, 5, "lisi@163.com"); + // Wang Wu + txls_set_text(x, 4, 1, "20"); + txls_set_text(x, 4, 2, "man"); + txls_set_text(x, 4, 3, "175"); + txls_set_text(x, 4, 4, "75"); + txls_set_text(x, 4, 5, "ww1234567890@qq.com"); + + txls_file_dump(x, WRITE_FILE); + + txls_delete(x); +} + +static void test(void) +{ + // test_write(); + test_read(); + + // v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_valloc.c b/test/test_valloc.c new file mode 100644 index 0000000..a81c95b --- /dev/null +++ b/test/test_valloc.c @@ -0,0 +1,28 @@ +#include "test.h" +#include +#include +#include +#include "valloc.h" + +void test_main(void) +{ + void* p = NULL; + printf("malloc %p\r\n", malloc(0)); + p = realloc(NULL, 100); + p = malloc(64); + p = malloc(50); + printf("realloc %p\r\n", realloc(NULL, 0)); + printf("%d\r\n", *(int *)p); + free(p); + + v_check_unfree(); + printf("count = %d\r\n", v_check_count()); + printf("used = %d\r\n", v_check_used()); +} + +std_return test_init(void) +{ + test_main(); + + return E_OK; +} \ No newline at end of file diff --git a/test/test_vector.c b/test/test_vector.c new file mode 100644 index 0000000..5551e8e --- /dev/null +++ b/test/test_vector.c @@ -0,0 +1,33 @@ +#include +#include +#include "init.h" +#include "valloc.h" +#include "vector.h" + +#define up_multiple(x, mul) ((x)+((mul)-((x)-1)%(mul))-1) /* get the smallest 'mul' multiple larger than 'x' */ +static int gradient_capacity(int size) +{ + int capacity = 1; + if (size <= 1) return 1; + while (capacity < size) capacity <<= 1; + capacity >>= 1; + if (capacity < 4) capacity = capacity << 1; + else if (capacity < 16) capacity = up_multiple(size, capacity >> 1); + else if (capacity < 256) capacity = up_multiple(size, capacity >> 2); + else capacity = up_multiple(size, 64); + return capacity; +} + +static void test(void) +{ + int size = 0, capacity = 0; + for (size = 1; size < 1024; ) + { + capacity = gradient_capacity(size); + printf("+%d\t%d\r\n", capacity - size + 1, capacity); + size = capacity + 1; + } + + v_check_unfree(); +} +init_export_app(test); diff --git a/test/test_vlog.c b/test/test_vlog.c new file mode 100644 index 0000000..c4700e0 --- /dev/null +++ b/test/test_vlog.c @@ -0,0 +1,20 @@ +#include "vlog.h" +#include "init.h" +#include + +static void vlog_callback(char *buf, int len) +{ + printf("vlog_callback[%d]: %s", len, buf); +} + +static void test(void) +{ + // vlog_start_offline("log_20231227.txt"); + vlog_set_func(vlog_callback); + vlog(VLOG_CHANNEL_0, "Hello vlog!\r\n"); + vlog(VLOG_CHANNEL_0, "Hello vlog!\r\n"); + vlog(VLOG_CHANNEL_0, "Hello vlog!\r\n"); + vlog(VLOG_CHANNEL_0, "Hello vlog!\r\n"); + // vlog_stop_offline(); +} +init_export_app(test); \ No newline at end of file diff --git a/test/test_vstd.c b/test/test_vstd.c new file mode 100644 index 0000000..f2ea145 --- /dev/null +++ b/test/test_vstd.c @@ -0,0 +1,30 @@ +#include "init.h" +#include "vstdlib.h" +#include "vmem.h" + +int printf (const char *__format, ...); + +static void test(void) +{ + uint8_t *p; + + printf("used %d\r\n", vmem_used()); + + p = v_malloc(2048); + if (!p) + { + printf("malloc fail!\r\n"); + return; + } + else + { + printf("malloc success!\r\n"); + } + + printf("used %d\r\n", vmem_used()); + + v_free(p); + + printf("used %d\r\n", vmem_used()); +} +init_export_app(test); diff --git a/test/test_xml.c b/test/test_xml.c new file mode 100644 index 0000000..05513a6 --- /dev/null +++ b/test/test_xml.c @@ -0,0 +1,66 @@ +#include +#include +#include "init.h" +#include "xml.h" + +#define READ_FILE "test/file/read.xml" +#define WRITE_FILE "test/file/write.xml" + +static void test_read(void) +{ + xml_t root, x; + + root = xml_file_load(READ_FILE); + if (!root) return; + + printf("load success!\r\n"); + + x = xml_to(root, "book", 1); + printf("x attr: %s\r\n", xml_get_attribute(x, NULL, 0)); + + x = xml_to(x, "author", 0); + printf("author: %s\r\n", xml_get_text(x)); + + xml_delete(root); +} + +static void test_write(void) +{ + xml_t root, x; + + root = xml_create("root"); + if (!root) return; + + x = xml_create("name"); + xml_set_text(x, "xml parser"); + xml_insert(root, 0, x); + + x = xml_create("description"); + xml_set_text(x, "This is a C language version of xml parser."); + xml_insert(root, 1, x); + + x = xml_create("license"); + xml_set_text(x, "GPL3.0"); + xml_insert(root, 2, x); + + char *s = xml_dumps(root, 1, 0, NULL); + if (s) + { + printf(s); + free(s); + } + + xml_file_dump(root, WRITE_FILE); + + xml_delete(root); +} + +static void test(void) +{ + printf("xml test!\r\n"); + test_write(); + // test_read(); + + v_check_unfree(); +} +init_export_app(test);