/** * @file sh_vset.h * @author LokLiang * @brief shell 模块专用,可用于通过命令行中的参数设置变量的基本接口 * @version 0.1 * @date 2023-09-22 * * @copyright Copyright (c) 2023 * * 本模块实现解析字符,并根据解析的结果对常见类型的的变量赋值的功能。 * * 特性: * - 自动分析设置变量的具体类型 * - 对取值范围限制 * - 可通过命令行的形式进行设置 * - 每增加一项写入接口最低消耗 38 个字节的代码空间 * * 使用: * 1. 使用 vset_init() 指定内部的 sh 模块对象 * 2. 使用对应的宏作为设置函数,其中: * - @b SET_VAR 用于设置无符号整形、带符号整型、单精度的浮点数和字符串的变量 * - @b SET_ENUM 用于设置枚举型的变量,支持 SET_VAR 所支持的全部类型 * - @b SET_CP_ENUM 对应 SET_ENUM 所设置的变量的可用自动补全函数 * 3. 如果输入的参数为 ? 可用于打印当前的设置范围。 * 4. 通过选项设置参数,其中: * - @b PSET_FN 指定一个已定义的 const sh_vset_param_t* 的结构作为选项 * - @b PSET_CP 根据一个已定义的 const sh_vset_param_t* 执行自动补选项 * * 实例1: 以下这些函数可被 SH_SETUP_CMD 中定义的 FUNC 执行,或者作为 sh_vset_param_t::set_func 的成员 * static int _value_set_u8(const char *argv[]) { return SET_VAR(&var_u8, 1, 200); } * static int _value_set_s16(const char *argv[]) { return SET_VAR(&var_s16, -1000, 1000); } * static int _value_set_float(const char *argv[]) { return SET_VAR(&var_float, -1000, var_u8); } * static int _value_set_str(const char *argv[]) { return SET_VAR(&var_str, 0, 0); } * static int _value_set_enum(const char *argv[]) { return SET_ENUM(&var_s32, "enable=1,disable=0"); } * * 实例2: 实现选项+参数的格式 * static sh_vset_param_t const s_param_template[] = { * {"--u8", "该选项的帮助信息", value_set_u8, NULL}, * {"--s16", "该选项的帮助信息", value_set_s16, NULL}, * {"--float", "该选项的帮助信息", value_set_float, NULL}, * {"--str", "该选项的帮助信息", value_set_str, NULL}, * {"--enum", "该选项的帮助信息", value_set_enum, "enable=1,disable=0"}, * static int _value_set(sh_t *sh_hdl, int argc, const char *argv[]) { PSET_FN(s_param_template); } // 这个函数可作为 SH_SETUP_CMD 中定义的 FUNC * static void _value_cp(sh_t *sh_hdl, int argc, const char *argv[], bool flag) { PSET_CP(s_param_template); } // 这个函数可作为 SH_SETUP_CMD 中定义的 SUB * */ #ifndef __SH_VSET_H__ #define __SH_VSET_H__ #include "sys_types.h" #include "sh.h" /* 描述支待的待设置变量的具体类型 */ typedef enum { __TYPE_ATTR_CHR, __TYPE_ATTR_BOOL, __TYPE_ATTR_U8, __TYPE_ATTR_S8, __TYPE_ATTR_U16, __TYPE_ATTR_S16, __TYPE_ATTR_U32, __TYPE_ATTR_S32, __TYPE_ATTR_U64, __TYPE_ATTR_S64, __TYPE_ATTR_FLOAT, __TYPE_ATTR_DOUBLE, __TYPE_ATTR_STR, __TYPE_ATTR_OTHER, } __type_attr_t; /** * @brief vset_cb * 当一个参数的设置值合法,即将被执行设置前回调的函数。当回调退出后才会更新到目标变量中。 * @param new_value 指向 sh_vset 内部的新值的栈内存地址。提示:可配合 __typeof() 强制转换为确定的类型。 */ typedef void (*vset_cb)(sh_t *sh_hdl, void *new_value); #define __GENERIC_ATTR(VAR) (__builtin_types_compatible_p(__typeof(VAR), char) ? __TYPE_ATTR_CHR \ : __builtin_types_compatible_p(__typeof(VAR), volatile char) ? __TYPE_ATTR_CHR \ : __builtin_types_compatible_p(__typeof(VAR), bool) ? __TYPE_ATTR_BOOL \ : __builtin_types_compatible_p(__typeof(VAR), volatile bool) ? __TYPE_ATTR_BOOL \ : __builtin_types_compatible_p(__typeof(VAR), uint8_t) ? __TYPE_ATTR_U8 \ : __builtin_types_compatible_p(__typeof(VAR), volatile uint8_t) ? __TYPE_ATTR_U8 \ : __builtin_types_compatible_p(__typeof(VAR), int8_t) ? __TYPE_ATTR_S8 \ : __builtin_types_compatible_p(__typeof(VAR), volatile int8_t) ? __TYPE_ATTR_S8 \ : __builtin_types_compatible_p(__typeof(VAR), uint16_t) ? __TYPE_ATTR_U16 \ : __builtin_types_compatible_p(__typeof(VAR), volatile uint16_t) ? __TYPE_ATTR_U16 \ : __builtin_types_compatible_p(__typeof(VAR), int16_t) ? __TYPE_ATTR_S16 \ : __builtin_types_compatible_p(__typeof(VAR), volatile int16_t) ? __TYPE_ATTR_S16 \ : __builtin_types_compatible_p(__typeof(VAR), uint32_t) ? __TYPE_ATTR_U32 \ : __builtin_types_compatible_p(__typeof(VAR), volatile uint32_t) ? __TYPE_ATTR_U32 \ : __builtin_types_compatible_p(__typeof(VAR), int32_t) ? __TYPE_ATTR_S32 \ : __builtin_types_compatible_p(__typeof(VAR), volatile int32_t) ? __TYPE_ATTR_S32 \ : __builtin_types_compatible_p(__typeof(VAR), uint64_t) ? __TYPE_ATTR_U64 \ : __builtin_types_compatible_p(__typeof(VAR), volatile uint64_t) ? __TYPE_ATTR_U64 \ : __builtin_types_compatible_p(__typeof(VAR), int64_t) ? __TYPE_ATTR_S64 \ : __builtin_types_compatible_p(__typeof(VAR), volatile int64_t) ? __TYPE_ATTR_S64 \ : __builtin_types_compatible_p(__typeof(VAR), float) ? __TYPE_ATTR_FLOAT \ : __builtin_types_compatible_p(__typeof(VAR), volatile float) ? __TYPE_ATTR_FLOAT \ : __builtin_types_compatible_p(__typeof(VAR), double) ? __TYPE_ATTR_DOUBLE \ : __builtin_types_compatible_p(__typeof(VAR), volatile double) ? __TYPE_ATTR_DOUBLE \ : __builtin_types_compatible_p(__typeof(VAR), char[]) ? __TYPE_ATTR_STR \ : __TYPE_ATTR_OTHER) typedef int (*vset_var_fn)(const char *argv[]); typedef struct // 用于长选项设置的描述结构 { const char *option; // 选项,如 "--value" const char *help; // 对该选项的描述 vset_var_fn set_func; // 与 option 对应的,使用宏 SET_VAR() 或 SET_ENUM() 设置变量的函数。如果值为 NULL 表示该选项无参数,同时对应的输入参数被保留 const char *enum_str; // 仅在类型为 vset_enum_fn 时有效,为对应的选项提供可选的补全参数选项,值为 NULL 或 "" 时默认候选参数为 '?' } sh_vset_param_t; int vset_unsigned(void *dest, __type_attr_t attr, vset_cb cb, const char *argv[], unsigned int low, unsigned int high); int vset_integer(void *dest, __type_attr_t attr, vset_cb cb, const char *argv[], signed int low, signed int high); int vset_float(void *dest, __type_attr_t attr, vset_cb cb, const char *argv[], float low, float high); int vset_str(void *dest, __type_attr_t attr, vset_cb cb, const char *argv[], unsigned bufsize); int vset_enum(void *dest, __type_attr_t attr, vset_cb cb, const char *argv[], const char *enum_str); void vset_cp_enum(int argc, bool flag, const char *enum_str); int vset_option_set(sh_t *sh_hdl, int *argc, const char *argv[], const sh_vset_param_t *p, unsigned size); bool vset_option_cp(sh_t *sh_hdl, int argc, const char *argv[], bool flag, const sh_vset_param_t *p, unsigned size); /* 自动分析并设置变量的值,带设置回调 */ #define SET_VAR_CB(NAME, LOW, HIGH, CB) \ ((__GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_U8 || \ __GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_U16 || \ __GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_U32 || \ __GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_U64) \ ? vset_unsigned(NAME, __GENERIC_ATTR(*(NAME)), CB, argv, LOW, HIGH) \ : ((__GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_CHR || \ __GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_BOOL || \ __GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_S8 || \ __GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_S16 || \ __GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_S32 || \ __GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_S64) \ ? vset_integer(NAME, __GENERIC_ATTR(*(NAME)), CB, argv, LOW, HIGH) \ : ((__GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_FLOAT || \ __GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_DOUBLE) \ ? vset_float(NAME, __GENERIC_ATTR(*(NAME)), CB, argv, LOW, HIGH) \ : ((__GENERIC_ATTR(*(NAME)) == __TYPE_ATTR_STR) \ ? vset_str(NAME, __GENERIC_ATTR(*(NAME)), CB, argv, sizeof(*(NAME))) \ : -1)))) /* 设置数据类型为 枚举型 的变量,带设置回调 */ #define SET_ENUM_CB(NAME, ENUM_STR, CB) \ (vset_enum(NAME, __GENERIC_ATTR(*(NAME)), CB, \ argv, ENUM_STR)) /* 自动分析并设置变量的值 */ #define SET_VAR(NAME, LOW, HIGH) SET_VAR_CB(NAME, LOW, HIGH, NULL) /* 设置数据类型为 枚举型 的变量 */ #define SET_ENUM(NAME, ENUM_STR) SET_ENUM_CB(NAME, ENUM_STR, NULL) /* 对应 SET_ENUM 所设置的变量的可用自动补全函数 */ #define SET_CP_ENUM(ENUM_STR) \ do \ { \ vset_cp_enum(argc, flag, ENUM_STR); \ } while (0) #define PSET_FN(OPT) vset_option_set(sh_hdl, &argc, argv, OPT, sizeof(OPT)) /* 作为 SH_CMD_FN() 的实际执行函数 */ #define PSET_CP(OPT) vset_option_cp(sh_hdl, argc, argv, flag, OPT, sizeof(OPT)) /* 作为 SH_CMD_CP_FN() 的实际执行函数 */ typedef void (*vset_global_cb)(sh_t *sh_hdl); void vset_init(sh_t *sh_hdl, vset_global_cb global_cb); void vset_force_cb(void); /** 定义设置变量的简化形式 ***************************************************************************************/ /* 对应 SET_VAR 所设置的变量的可用自动补全函数,默认补全一个 '?' 号 */ void vset_cp_hint(sh_t *sh_hdl, int argc, const char *argv[], bool flag); /** * @brief 实例化一个变量的设置函数。 * @param NAME 函数名称 * @param VAR 静态变量地址,支持的类型有:无符号整形、带符号整型、单精度的浮点数 * @param LOW 变量的最小值。变量值可用 * @param HIGH 变量的最大值。变量值可用 * @param CB 设置变量的回调函数。注意:只有在设置变量的值合法并且新的值与原值不相等时才会被调用。也可取值为 NULL */ #define VSET_VAR(NAME, VAR, LOW, HIGH, CB) \ SH_CMD_FN(NAME) { return SET_VAR_CB(VAR, LOW, HIGH, CB); } \ SH_CMD_CP_FN(_SH_CONCAT(NAME, _tab)) { vset_cp_hint(sh_hdl, argc, argv, flag); } /** * @brief 实例化一个字符串的设置函数。 * @param NAME 函数名称 * @param VAR 静态字符串数组的地址 * @param CB 设置变量的回调函数。注意:只有在设置变量的值合法并且新的值与原值不相等时才会被调用。也可取值为 NULL */ #define VSET_STR(NAME, VAR, CB) \ SH_CMD_FN(NAME) { return SET_VAR_CB(VAR, 0, 0, CB); } \ SH_CMD_CP_FN(_SH_CONCAT(NAME, _tab)) { vset_cp_hint(sh_hdl, argc, argv, flag); } /** * @brief 实例化一个枚举型变量的设置函数。 * @param NAME 函数名称 * @param VAR 静态变量地址,支持的类型有:无符号整形、带符号整型,不支持浮点数 * @param ENUM_STR 枚举型变量的可选值 * @param CB 设置变量的回调函数。注意:只有在设置变量的值合法并且新的值与原值不相等时才会被调用。也可取值为 NULL */ #define VSET_ENUM(NAME, VAR, ENUM, CB) \ SH_CMD_FN(NAME) { return SET_ENUM_CB(VAR, ENUM, CB); } \ SH_CMD_CP_FN(_SH_CONCAT(NAME, _tab)) { SET_CP_ENUM(ENUM); } /** * @brief 实例化一个选项的设置函数。 * @param NAME 函数名称 * @param ... 可变参数,选项描述结构体的数组。成员格式: {"选项", "选项的帮助信息", 设置函数, 枚举型变量的可选值 } * @example * @code * static int _vset_int(const char *argv[]) { return SET_VAR_CB(&g_int32, -10, 10, _vset_cb_int32); } * static int _vset_str(const char *argv[]) { return SET_VAR_CB(&g_str, 0, 0, _vset_cb_char); } * static int _vset_enum(const char *argv[]) { return SET_ENUM_CB(&g_int32, s_enum_str, _vset_cb_int32); } * VSET_OPTION( * _example, * {"--int32", "int32_t 类型的变量", _vset_int, NULL}, * {"--str", "字符串 类型的变量", _vset_str, NULL}, * {"--enum", "枚举类型的变量", _vset_enum, s_enum_str}) * @endcode */ #define VSET_OPTION(NAME, ...) \ static sh_vset_param_t const _SH_CONCAT(_opts_, NAME)[] = { \ __VA_ARGS__}; \ SH_CMD_FN(NAME) { return PSET_FN(_SH_CONCAT(_opts_, NAME)); } \ SH_CMD_CP_FN(_SH_CONCAT(NAME, _tab)) { PSET_CP(_SH_CONCAT(_opts_, NAME)); } /** * @brief 可替代 SH_SETUP_CMD 定义命令列表 SH_REGISTER_CMD 或 SH_DEF_SUB_CMD 中的成员 * @param CMD 命令(字符串,不要有空格) * @param HELP 命令的帮助信息(字符串) * @param FUNC 由 VSET_VAR()、VSET_STR()、VSET_ENUM()、VSET_OPTION() 定义的函数 * @example * @code * SH_DEF_SUB_CMD(_register_sample, * VSET_SETUP_CMD("ex1", "设置单个 数值 变量", _ex1), // ex1 <数值> * VSET_SETUP_CMD("ex2", "设置单个 字符串 变量", _ex2), // ex2 <字符串> * VSET_SETUP_CMD("ex3", "设置单个 枚举 变量", _ex3), // ex3 <枚举> * VSET_SETUP_CMD("ex4", "设置有多个选项的变量", _ex4), // ex4 [选项<值>] [选项<值>] ... * ); * @endcode */ #define VSET_SETUP_CMD(CMD, HELP, FUNC) SH_SETUP_CMD(CMD, HELP, FUNC, FUNC##_tab) /** * @note 对本函数的进一步说明。 * * 在 sh 模块设计完成后,发现除了用于执行某些定义好的的操作外,更多情况下是用于设置变量。 * 为了更好的支持这种需求,本模块应运而生。 * 本模块的设计目标是通过命令行的形式设置变量,同时支持对变量的取值范围限制。 * 本模块的设计思路是通过宏定义,将变量的类型和取值范围传递给 SET_VAR() 或 SET_ENUM() 进行具体的设置。 * 本模块主要使用到的宏定义有: * - @b SET_VAR() * - @b SET_ENUM() * - @b SET_CP_ENUM() * - @b PSET_FN() * - @b PSET_CP() * * 这些都是根据实际的使用场景定义的。 * 根据 sh 模块的主体功能,分为两个部分(可在使用 SH_SETUP_CMD() 定义命令时的第三和第四个参数来体现): * - 一个是用于执行某些操作的函数。 * - 另一个是用于像 bash 一样,按 Tab 键自动补全的函数。 * 对于前者,可在实际执行函数中直接使用 SET_VAR() 或 SET_ENUM() 来设置变量。 * 对于后者,可在实际补全函数中直接使用 SET_CP_ENUM() 来提示可选项和可选值。 * * 为什么设置宏要分两种? * SET_VAR() 和 SET_ENUM() 的本质区别是, SET_VAR() 用于设置无符号整形、带符号整型、单精度的浮点数和字符串的变量,它们的取值范围是连续的。 * 值得注意的是: SET_VAR() 也支持字符串的设置,但是字符串的取值范围是无法限制的。 * 而 SET_ENUM() 用于设置枚举型的变量,它的取值范围是允许离散的,并且可用一些字符串来表示具体的取值。 * * 为什么补全函数只有 SET_CP_ENUM() ? * 因为 SET_ENUM() 的取值范围是离散的,所以需要一个额外的函数来提供可选的补全参数选项。对于连续的取值范围,不需要这个函数。 * * 设置宏较为独立,虽然它是为了配合本模块而设计的,但是它的功能是独立的,在传输的参数中的 sh_hdl 主要是为了回显一些错误信息。 * 补全宏则是为了配合 sh 模块的补全功能而设计的,它的功能是为了提供可选的补全参数选项。 * * 小结: * - SET_VAR() 和 SET_ENUM() 用于设置变量的值。 * - SET_CP_ENUM() 用于提供可选的补全参数选项。 * - 它们可能在命令执行函数和补全函数中直接使用。 * * 接着新问题来了,使用 SET_VAR() 或 SET_ENUM() 时,只能设置一个变量,如果这个命令要包含多种设置,该怎么办? * 为了解决这个问题,本模块引入了 sh_vset_param_t 结构体,它是一个数组,用于描述支待的待设置变量的具体类型。 * 它统一描述了该命令的所有选项,包括选项的名称、选项的描述、选项的设置函数和选项的可选值。 * 在命令执行函数和补全函数中,通过调用 PSET_FN() 和 PSET_CP() 来执行具体的设置。 * 这两个宏的参数就是 sh_vset_param_t 结构体的数组。 * 这个结构体的定义是固定的,它的成员包括: * - 选项,如 "--value" * - 对该选项的描述 * - 与 option 对应的,使用宏 SET_VAR() 或 SET_ENUM() 设置变量的函数。如果值为 NULL 表示该选项无参数,同时对应的输入参数被保留 * - 仅在类型为 vset_enum_fn 时有效,为对应的选项提供可选的补全参数选项,值为 NULL 或 "" 时默认候选参数为 '?' * 通过设置这个结构,便可得到一个完整的命令格式: <命令> [选项 <参数>] [选项 <参数>] ... * * 其他一些细节: * - 这些宏都是以最简约的形式定义的,以便在有大量数据的情况下降低阅读难度。在一些特殊情况下,可根据这些提及到的宏的定义展开理解再灵活使用。 * * 总结: * SET_VAR(), SET_ENUM() 和 SET_CP_ENUM() 是用于单个变量的设置和补全,它们可以直接在命令执行函数和补全函数中使用,也可以在 sh_vset_param_t 结构体中对应的函数使用。 * PSET_FN() 和 PSET_CP() 是用于多个变量的设置和补全,它们可以直接在命令执行函数和补全函数中被调用。 * sh_vset_param_t 结构体是用于描述支待的待设置变量的具体类型,它可以直接在命令执行函数和补全函数中被调用。 * */ #endif