feat(argparse): 重构命令行参数解析器以支持更灵活的参数处理

将optparse库的API进行了重大改进,包括:
- 修改结构体字段为const类型以提高安全性
- 添加了宏定义SCC_OPTPARSE_OPT和SCC_OPTPARSE_OPT_END用于简化选项定义
- 重构了解析逻辑,引入greedy_mode和current状态跟踪
- 改进了短选项和长选项的解析机制
- 添加了参数计数验证功能(min_args/max_args检查)
- 调整了函数返回值,parse函数现在返回int类型表示是否还有更多参数
- 完善了错误处理和边界条件检查
This commit is contained in:
zzy
2026-02-06 17:33:43 +08:00
parent d1b215861c
commit 34d7eb3c42
3 changed files with 242 additions and 203 deletions

View File

@@ -3,11 +3,23 @@
void scc_optparse_init(scc_optparse_t *parser, int argc, const char **argv) {
parser->argc = argc;
parser->argv = argv;
parser->handle_positional = 0;
parser->opts = 0;
parser->prev = 0;
parser->current_arg_pos = 1;
parser->short_opt_pos = 0;
scc_optparse_reset(parser);
}
void scc_optparse_drop(scc_optparse_t *parser) { (void)parser; }
void scc_optparse_set(scc_optparse_t *parser, const scc_optparse_opt_t *opts) {
parser->opts = opts;
scc_optparse_reset(parser);
}
void scc_optparse_reset(scc_optparse_t *parser) {
parser->handle_positional = 0;
parser->current.opt = 0;
parser->current.count = 0;
parser->current.arg_pos = 1;
parser->current.opt_pos = 0;
}
static inline const char *str_chr(const char *s, char c) {
@@ -16,12 +28,12 @@ static inline const char *str_chr(const char *s, char c) {
return *s ? s : 0;
}
scc_optparse_opt_t *scc_optparse_get_long_name(scc_optparse_opt_t *opts,
const char *name,
const char end) {
const scc_optparse_opt_t *
scc_optparse_get_long_name(const scc_optparse_opt_t *opts, const char *name,
const char end) {
if (*name == '\0')
return 0;
for (scc_optparse_opt_t *opt = opts; opt->prefix; ++opt) {
for (const scc_optparse_opt_t *opt = opts; opt->prefix; ++opt) {
if (!opt->long_name) {
continue;
}
@@ -37,9 +49,10 @@ scc_optparse_opt_t *scc_optparse_get_long_name(scc_optparse_opt_t *opts,
return 0;
}
scc_optparse_opt_t *scc_optparse_get_short_name(scc_optparse_opt_t *opts,
const char short_name) {
for (scc_optparse_opt_t *opt = opts; opt->prefix; ++opt) {
const scc_optparse_opt_t *
scc_optparse_get_short_name(const scc_optparse_opt_t *opts,
const char short_name) {
for (const scc_optparse_opt_t *opt = opts; opt->prefix; ++opt) {
if (opt->short_name == short_name) {
return opt;
}
@@ -47,11 +60,11 @@ scc_optparse_opt_t *scc_optparse_get_short_name(scc_optparse_opt_t *opts,
return 0;
}
static inline void scc_optparse_parse_long_name(scc_optparse_opt_t *opts,
static inline void scc_optparse_parse_long_name(const scc_optparse_opt_t *opts,
scc_optparse_result_t *res,
const char *arg) {
// caller MUST use `--` start arg
scc_optparse_opt_t *ret = 0;
const scc_optparse_opt_t *ret = 0;
const char *chr = str_chr(arg, '=');
if (chr) {
// --file=a.txt
@@ -77,86 +90,80 @@ static inline void scc_optparse_parse_long_name(scc_optparse_opt_t *opts,
}
}
static inline int scc_optparse_parse_short_name(scc_optparse_opt_t *opts,
scc_optparse_result_t *res,
const char *arg,
int arg_start) {
for (int i = arg_start; arg[i]; ++i) {
scc_optparse_opt_t *ret = scc_optparse_get_short_name(opts, arg[i]);
if (ret == 0) {
res->error = SCC_OPT_ERROR_NOT_FOUND_SHORT_ARG;
return 0;
}
static inline void scc_optparse_parse_short_name(const scc_optparse_opt_t *opts,
scc_optparse_result_t *res,
const char *arg,
int arg_start) {
// const char *chr =
// str_chr(arg + arg_start, '='); // TODO maybe short can have -I=/usr
const scc_optparse_opt_t *ret =
scc_optparse_get_short_name(opts, arg[arg_start]);
if (ret == 0) {
res->error = SCC_OPT_ERROR_NOT_FOUND_SHORT_ARG;
} else {
res->opt = ret;
if (res->opt->parsed_args < res->opt->max_args && arg[i + 1] != '\0') {
res->value = arg + i + 1;
res->opt->parsed_args++;
return 0;
}
return arg[i + 1] ? i + 1 : 0;
}
return 0;
}
static inline void scc_optparse_parse_position_arg(scc_optparse_t *parser,
const char *arg,
scc_optparse_result_t *res) {
parser->current_arg_pos++;
parser->current.arg_pos++;
res->value = arg;
}
void scc_optparse_parse(scc_optparse_t *parser, scc_optparse_result_t *res) {
scc_optparse_opt_t *opts = parser->opts;
static inline int scc_optparse_check(scc_optparse_t *parser,
scc_optparse_result_t *res) {
if (!res->opt || res->error)
return 0;
if (res->value)
parser->current.count += 1;
if (parser->current.count < res->opt->max_args && res->opt->max_args > 0)
parser->current.opt = res->opt;
if (parser->current.count < res->opt->min_args && !res->value)
return 1;
return 0;
}
int scc_optparse_parse(scc_optparse_t *parser, scc_optparse_result_t *res) {
const scc_optparse_opt_t *opts = parser->opts;
const char *arg = 0;
res->opt = 0;
res->value = 0;
res->error = SCC_OPT_ERROR_NONE;
if (parser->short_opt_pos != 0) {
parser->short_opt_pos = scc_optparse_parse_short_name(
opts, res, parser->argv[parser->current_arg_pos],
parser->short_opt_pos);
if (parser->short_opt_pos == 0) {
parser->short_opt_pos = 0;
parser->current_arg_pos += 1;
}
goto RETURN;
if (parser->current.opt_pos != 0) {
arg = parser->argv[parser->current.arg_pos];
goto CONTINUE_SHORT_OPTION;
}
while (parser->current_arg_pos < parser->argc) {
const char *arg = parser->argv[parser->current_arg_pos];
while (parser->current.arg_pos < parser->argc) {
arg = parser->argv[parser->current.arg_pos];
if (arg[0] == '\0' || parser->handle_positional) {
return scc_optparse_parse_position_arg(parser, arg, res);
scc_optparse_parse_position_arg(parser, arg, res);
goto RETURN;
}
for (const scc_optparse_opt_t *opt = opts; opt->prefix; ++opt) {
if (arg[0] != opt->prefix) {
continue;
}
parser->current.opt = 0;
parser->current.count = 0;
if (arg[1] == opt->prefix) {
// long option like --
if (arg[2] == '\0') {
// the `--`
res->value = arg;
parser->handle_positional = 1;
parser->current_arg_pos++;
parser->current.arg_pos++;
goto RETURN;
}
scc_optparse_parse_long_name(opts, res, arg);
if (!res->opt || res->error) {
goto RETURN;
}
parser->prev = 0;
if (res->value)
res->opt->parsed_args++;
parser->current_arg_pos++;
if (res->opt->parsed_args < res->opt->max_args &&
res->opt->max_args > 0) {
parser->prev = res->opt;
}
if (res->opt->parsed_args < res->opt->min_args && !res->value) {
parser->current.arg_pos += 1;
if (scc_optparse_check(parser, res)) {
goto NEXT;
}
goto RETURN;
@@ -165,37 +172,37 @@ void scc_optparse_parse(scc_optparse_t *parser, scc_optparse_result_t *res) {
if (arg[1] == '\0') {
// the `-`
res->value = arg;
parser->handle_positional = 1;
parser->current_arg_pos++;
parser->current.arg_pos++;
goto RETURN;
}
parser->short_opt_pos =
scc_optparse_parse_short_name(opts, res, arg, 1);
if (!res->opt || res->error) {
goto RETURN;
}
if (parser->short_opt_pos == 0) {
parser->current_arg_pos++;
}
parser->prev = 0;
parser->current.opt_pos = 1;
CONTINUE_SHORT_OPTION:
scc_optparse_parse_short_name(opts, res, arg,
parser->current.opt_pos++);
int have_next = arg[parser->current.opt_pos];
if (res->opt->parsed_args < res->opt->max_args &&
res->opt->max_args > 0) {
parser->prev = res->opt;
}
if (res->opt->parsed_args < res->opt->min_args) {
parser->current.opt_pos =
have_next ? parser->current.opt_pos : 0;
parser->current.arg_pos += have_next ? 0 : 1;
if (scc_optparse_check(parser, res)) {
if (have_next) {
res->value = arg + parser->current.opt_pos;
parser->current.opt_pos = 0;
parser->current.arg_pos += 1;
goto RETURN;
}
goto NEXT;
}
goto RETURN;
}
}
// position arg
if (parser->prev &&
parser->prev->parsed_args < parser->prev->max_args) {
res->opt = parser->prev;
if (parser->current.opt &&
parser->current.count < parser->current.opt->max_args) {
res->opt = parser->current.opt;
res->value = arg;
res->opt->parsed_args++;
parser->current_arg_pos++;
parser->current.count++;
parser->current.arg_pos++;
goto RETURN;
} else {
scc_optparse_parse_position_arg(parser, arg, res);
@@ -206,8 +213,8 @@ void scc_optparse_parse(scc_optparse_t *parser, scc_optparse_result_t *res) {
}
RETURN:
if (res->opt && res->opt->max_args > 0 &&
res->opt->parsed_args > res->opt->max_args) {
parser->current.count > res->opt->max_args) {
res->error = SCC_OPT_ERROR_TOO_MANY_ARGS;
}
return;
return res->opt != 0 || res->value != 0;
}