From 07c5287760ca7a80c06dd80defe007ad009695fc Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 31 Aug 2025 12:51:47 -0400 Subject: Add argument aliases so programs can use `func main(verbose|v=no)` to do single-letter argument flags. --- src/compile/cli.c | 47 ++++++++++++++++++++++++++++++++--------------- src/compile/functions.c | 46 ++++++++++++++++++++++++---------------------- 2 files changed, 56 insertions(+), 37 deletions(-) (limited to 'src/compile') diff --git a/src/compile/cli.c b/src/compile/cli.c index 4a3be333..b082239d 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -2,6 +2,7 @@ #include "../environment.h" #include "../stdlib/datatypes.h" +#include "../stdlib/optionals.h" #include "../stdlib/text.h" #include "../stdlib/util.h" #include "../typecheck.h" @@ -25,6 +26,14 @@ static Text_t get_flag_options(type_t *t, const char *separator) { } } +static OptionalText_t flagify(const char *name, bool prefix) { + if (!name) return NONE_TEXT; + Text_t flag = Text$from_str(name); + flag = Text$replace(flag, Text("_"), Text("-")); + if (prefix) flag = flag.length == 1 ? Texts("-", flag) : Texts("--", flag); + return flag; +} + public Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version) { DeclareMatch(fn_info, fn_type, FunctionType); @@ -49,20 +58,22 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c for (arg_t *arg = fn_info->args; arg; arg = arg->next) { usage = Texts(usage, " "); type_t *t = get_arg_type(main_env, arg); - Text_t flag = Text$replace(Text$from_str(arg->name), Text("_"), Text("-")); if (arg->default_val || arg->type->tag == OptionalType) { - if (strlen(arg->name) == 1) { - if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - usage = Texts(usage, "[-", flag, "]"); - else usage = Texts(usage, "[-", flag, " ", get_flag_options(t, "|"), "]"); - } else { - if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) - usage = Texts(usage, "[--", flag, "]"); - else if (t->tag == ListType) usage = Texts(usage, "[--", flag, " ", get_flag_options(t, "|"), "]"); - else usage = Texts(usage, "[--", flag, "=", get_flag_options(t, "|"), "]"); - } + OptionalText_t flag = flagify(arg->name, true); + assert(flag.length >= 0); + OptionalText_t alias_flag = flagify(arg->alias, true); + Text_t flags = alias_flag.length >= 0 ? Texts(flag, "|", alias_flag) : flag; + if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType)) + usage = Texts(usage, "[", flags, "]"); + else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, "|"), "]"); + else usage = Texts(usage, "[", flags, "=", get_flag_options(t, "|"), "]"); } else { - if (t->tag == BoolType) usage = Texts(usage, "<--", flag, "|--no-", flag, ">"); + OptionalText_t flag = flagify(arg->name, false); + assert(flag.length >= 0); + OptionalText_t alias_flag = flagify(arg->alias, true); + if (t->tag == BoolType) + usage = Texts(usage, "<--", flag, alias_flag.length >= 0 ? Texts("|", alias_flag) : EMPTY_TEXT, + "|--no-", flag, ">"); else if (t->tag == EnumType) usage = Texts(usage, get_flag_options(t, "|")); else if (t->tag == ListType) usage = Texts(usage, "[", flag, "...]"); else usage = Texts(usage, "<", flag, ">"); @@ -76,9 +87,10 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c for (arg_t *arg = fn_info->args; arg; arg = arg->next) { type_t *opt_type = arg->type->tag == OptionalType ? arg->type : Type(OptionalType, .type = arg->type); - code = Texts(code, compile_declaration(opt_type, Texts("_$", arg->name))); + code = Texts(code, compile_declaration(opt_type, Texts("_$", Text$from_str(arg->name)))); if (arg->default_val) { - Text_t default_val = compile(env, arg->default_val); + Text_t default_val = + arg->type ? compile_to_type(env, arg->default_val, arg->type) : compile(env, arg->default_val); if (arg->type->tag != OptionalType) default_val = promote_to_optional(arg->type, default_val); code = Texts(code, " = ", default_val); } else { @@ -92,7 +104,12 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c for (arg_t *arg = fn_info->args; arg; arg = arg->next) { code = Texts(code, ",\n{", quoted_text(Text$replace(Text$from_str(arg->name), Text("_"), Text("-"))), ", ", (arg->default_val || arg->type->tag == OptionalType) ? "false" : "true", ", ", - compile_type_info(arg->type), ", &", Texts("_$", arg->name), "}"); + compile_type_info(arg->type), ", &", Texts("_$", Text$from_str(arg->name)), "}"); + if (arg->alias) { + code = Texts(code, ",\n{", quoted_text(Text$replace(Text$from_str(arg->alias), Text("_"), Text("-"))), ", ", + (arg->default_val || arg->type->tag == OptionalType) ? "false" : "true", ", ", + compile_type_info(arg->type), ", &", Texts("_$", Text$from_str(arg->name)), "}"); + } } code = Texts(code, ");\n"); diff --git a/src/compile/functions.c b/src/compile/functions.c index 665c7379..01de26e3 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -71,29 +71,31 @@ Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_ for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) { int64_t i = 1; // Find keyword: - if (spec_arg->name) { - for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { - if (call_arg->name && streq(call_arg->name, spec_arg->name)) { - Text_t value; - if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) { - value = compile_int_to_type(env, call_arg->value, spec_arg->type); - } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) { - OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str); - if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer"); - if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64) - value = Text$from_str(String(hex_double(Num$from_int(int_val, false)))); - else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f")); - } else { - env_t *arg_env = with_enum_scope(env, spec_arg->type); - value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type); - } - Table$str_set(&used_args, call_arg->name, call_arg); - if (code.length > 0) code = Texts(code, ", "); - code = Texts(code, value); - goto found_it; - } + assert(spec_arg->name); + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (!call_arg->name) continue; + if (!(streq(call_arg->name, spec_arg->name) || (spec_arg->alias && streq(call_arg->name, spec_arg->alias)))) + continue; + + Text_t value; + if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) { + value = compile_int_to_type(env, call_arg->value, spec_arg->type); + } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) { + OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str); + if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer"); + if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64) + value = Text$from_str(String(hex_double(Num$from_int(int_val, false)))); + else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f")); + } else { + env_t *arg_env = with_enum_scope(env, spec_arg->type); + value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type); } + Table$str_set(&used_args, call_arg->name, call_arg); + if (code.length > 0) code = Texts(code, ", "); + code = Texts(code, value); + goto found_it; } + // Find positional: for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { if (call_arg->name) continue; @@ -161,7 +163,7 @@ Text_t compile_function_call(env_t *env, ast_t *ast) { args = new (arg_t, .name = a->name, .type = get_type(env, a->value), .next = args); REVERSE_LIST(args); code_err(ast, - "This function's public signature doesn't match this call site.\n" + "This function's signature doesn't match this call site.\n" "The signature is: ", type_to_text(fn_t), "\n" -- cgit v1.2.3