基于 x-macros 的命令行参数解析库实现
Contents
在之前文档讲述了一下 C X-Macros 的特性和一些应用场景,也提到在编译期可以确定的范式基本都可以通过 X-Macros 来辅助实现,比如命令行参数解析,这里就具体讲述如何实现。
具体代码在 github:macros_args
目标
因为使用过 argtable 这样的库,所以充分考虑了这种基于描述的命令行参数解析,但是也觉得还有一些繁琐,明明可以用一张表来表示所有参数,使用时却需要一个一个的初始化,而且库还有一些过于复杂的东西,比如特殊单位的处理支持。
设想中,这样的参数解析应该这样:
- 纯 header 文件实现,无 c 文件依赖;
- 表驱动配置解析参数,明确各个参数类型和特性;
- 支持参数重复,也就是同个参数传递多个值;
- 支持短参数和长参数,支持 literal/int/float/string 这样简单的参数类型;
- 尽量少依赖,不依赖动态内存分配,只适用少量 std 库和 getopt;
- 无全局状态,支持多实例;
考虑到实现复杂度,不依赖 std 库和 getopt 则会代码量巨大且不可维护,基本上就是需要利用标准库少量函数和getopt,来实现一个类似 argtable 这样的命令行参数解析。
思路
有了目标,就可以考虑如何实现了,本质上,就是如何将一个参数描述表转换为最终的 getopt 调用。
首先,需要考虑参数原型设计,基于以上,考虑设计4种基本参数类型:
- LIT, 也就是 literial,比如
-h-v这样的开关参数,在内部应该是个整数表示是否出现及出现次数; - INT,整数参数,内部用
int保存即可。 - NUM,浮点参数,内部用
double保存。 - STR,字符串参数,内部需要用
char *保存指针即可。 对于同参多值,可以将1个参数的情况作为多参数特例,所以表设计应该如下:
| 项目 | 描述 |
|---|---|
| 类型 | 参数类型,宏 |
| 最大数量 | 允许的最大数量,0 表示无限制(可以内部预设最大限制) |
| 名称 | 参数在代码中的引用名称,需要符合 token 央视 |
| 短参名 | 类似 -t 的短参,用 'c' 这样表示,实际是整数,没有则写0 |
| 长参名 | 类似--long的长参,用"long"这样的字面量表示,没有则写 "" 空字符串 |
| 参数提示 | 类似 "<count>" 这样的字面量,用于帮助中提示带参的类型 |
| 默认值 | 参数默认值,对于多参数,只有第一个参数会被设置 |
| 描述 | 描述这个参数用来做什么 |
使用时,需要用户提供一个 X Macros 列表,我们再基于这个列表,来实现我们的必要拼装:
- 利用表生成一个结构体定义,用来存储我们的参数;
- 利用表来生成解析函数,用来解析命令行并存储结果;
- 利用表明来实现实例唯一,可以保证无全局状态多实例;
- 利用表进行约束检查,避免错误使用;
实现
具体实现可以参考代码,其中最重要的就是 _ARG_IF_ELSE 这个编译期条件宏,可以基于条件生成编译期代码,而非运行期:
|
|
宏的实现就是简单的宏拼接,所以其应用很受限,只能基于宏展开时字面量数字来进行条件选择,参数必须是字面量0或1,如果直接使用,会很受限,需要配合_ARG_BOOL宏:
|
|
这个宏更绕一些,是根据宏字面量的0或非0来条件生成字面量0或1,这里用到了__VA_ARGS__ 展开参数数量,具体可以仔细揣摩。
有了这些宏,在结构体定义、静态检查和函数实现中,可以基于参数最大数量和参数类型来生成不同的编译期代码,避免编译器告警。
使用时,程序需要提供一个类似如下的参数列表宏,并利用库的宏来生成必要定义:
|
|
然后在主函数中,可以进行解析:
|
|
后续就可以利用arg成员,来判断参数,或者使用宏来打印参数帮助信息:
|
|
因为实现利用了大量宏,所以阅读起来会比较费劲,尤其是琐碎的条件处理。目前仍需要改进的部分就是利用短参字符字面量拼接getopt_long字符串参数时,通过手动去除单引号实现,这里之前考虑使用字面量而不是字符字面量,但是问题在于:
#宏可以方便实现字符串字面量,但是很难基于宏实现字符字面量;getopt_long中即要字符字面量,也要字符串字面量;
手动去除虽然拙劣但十分有效。