aboutsummaryrefslogtreecommitdiff
path: root/src/stdlib
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-10-18 19:02:07 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-10-18 19:02:07 -0400
commit5bdf96234a388cbd3854747b0667620ebb60ccdf (patch)
treee4ab4055e0190f4a080b69fcd933cd8ca6146260 /src/stdlib
parent82e3c05d547b2372ca4033c68469479260092b5a (diff)
Improved CLI parsing and add CString.join()
Diffstat (limited to 'src/stdlib')
-rw-r--r--src/stdlib/c_strings.c11
-rw-r--r--src/stdlib/c_strings.h1
-rw-r--r--src/stdlib/cli.c112
3 files changed, 68 insertions, 56 deletions
diff --git a/src/stdlib/c_strings.c b/src/stdlib/c_strings.c
index 54ea1e40..57960577 100644
--- a/src/stdlib/c_strings.c
+++ b/src/stdlib/c_strings.c
@@ -66,6 +66,17 @@ static void CString$deserialize(FILE *in, void *out, List_t *pointers, const Typ
}
public
+const char *CString$join(const char *glue, List_t strings) {
+ Text_t ret = EMPTY_TEXT;
+ Text_t glue_text = Text$from_str(glue);
+ for (int64_t i = 0; i < (int64_t)strings.length; i++) {
+ if (i > 0) ret = Texts(ret, glue_text);
+ ret = Texts(ret, Text$from_str(*(const char **)(strings.data + i * strings.stride)));
+ }
+ return Text$as_c_string(ret);
+}
+
+public
const TypeInfo_t CString$info = {
.size = sizeof(const char *),
.align = __alignof__(const char *),
diff --git a/src/stdlib/c_strings.h b/src/stdlib/c_strings.h
index f85fa0d6..eba3a3df 100644
--- a/src/stdlib/c_strings.h
+++ b/src/stdlib/c_strings.h
@@ -12,5 +12,6 @@ Text_t CString$as_text(const char **str, bool colorize, const TypeInfo_t *info);
PUREFUNC int CString$compare(const void *x, const void *y, const TypeInfo_t *type);
PUREFUNC bool CString$equal(const void *x, const void *y, const TypeInfo_t *type);
PUREFUNC uint64_t CString$hash(const void *str, const TypeInfo_t *type);
+const char *CString$join(const char *glue, List_t strings);
extern const TypeInfo_t CString$info;
diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c
index bf461105..c3def8c0 100644
--- a/src/stdlib/cli.c
+++ b/src/stdlib/cli.c
@@ -122,8 +122,6 @@ void tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const ch
before_double_dash = List$slice(args, I(1), I(i));
after_double_dash = List$slice(args, I(i + 2), I(-1));
break;
- } else if (arg[0] == '-') {
- print_err("Unrecognized argument: ", arg);
}
}
@@ -133,6 +131,7 @@ void tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const ch
pop_cli_positional(&before_double_dash, spec[i].name, spec[i].dest, spec[i].type, false);
}
}
+
for (int i = 0; i < spec_len && after_double_dash.length > 0; i++) {
if (!spec[i].populated) {
spec[i].populated = pop_cli_positional(&after_double_dash, spec[i].name, spec[i].dest, spec[i].type, true);
@@ -145,63 +144,68 @@ void tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, const ch
List_t remaining_args = List$concat(before_double_dash, after_double_dash, sizeof(const char *));
if (remaining_args.length > 0) {
- print_err("Unknown flag values: ", generic_as_text(&remaining_args, true, List$info(&CString$info)));
+ print_err("Unknown flag values: ", CString$join(" ", remaining_args));
}
}
-static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const TypeInfo_t *type, bool allow_dashes) {
+static List_t parse_arg_list(List_t args, const char *flag, void *dest, const TypeInfo_t *type, bool allow_dashes) {
if (type->tag == ListInfo) {
- void *item = GC_MALLOC((size_t)type->ListInfo.item->size);
- int64_t n = 0;
- while (n < (int64_t)args.length) {
- const char *arg = *(const char **)(args.data + n * args.stride);
+ void *item = type->ListInfo.item->size ? GC_MALLOC((size_t)type->ListInfo.item->size) : NULL;
+ while (args.length > 0) {
+ const char *arg = *(const char **)args.data;
if (arg[0] == '-' && !allow_dashes) break;
- n += parse_arg_list(List$slice(args, I(n + 1), I(-1)), flag, item, type->ListInfo.item, allow_dashes);
+ args = parse_arg_list(args, flag, item, type->ListInfo.item, allow_dashes);
List$insert(dest, item, I(0), type->ListInfo.item->size);
}
- return n;
+ return args;
} else if (type->tag == TableInfo) {
- // Arguments take the form key=value, with a guarantee that there is an '='
- void *key = GC_MALLOC((size_t)type->TableInfo.key->size);
- void *value = GC_MALLOC((size_t)type->TableInfo.value->size);
- int64_t n = 0;
- while (n < (int64_t)args.length) {
- const char *arg = *(const char **)(args.data + n * args.stride);
+ // Arguments take the form key:value
+ void *key = type->TableInfo.key->size ? GC_MALLOC((size_t)type->TableInfo.key->size) : NULL;
+ void *value = type->TableInfo.value->size ? GC_MALLOC((size_t)type->TableInfo.value->size) : NULL;
+ while (args.length > 0) {
+ const char *arg = *(const char **)args.data;
if (arg[0] == '-' && !allow_dashes) break;
- const char *colon = strchr(arg, ':');
- if (!colon) break;
- const char *key_arg = String(string_slice(arg, (size_t)(colon - arg)));
- n += parse_arg_list(List(key_arg), flag, key, type->TableInfo.key, allow_dashes);
- const char *value_arg = colon + 1;
- n += parse_arg_list(List(value_arg), flag, value, type->TableInfo.value, allow_dashes);
- Table$set(dest, key, value, type);
+ if (type->TableInfo.value->size == 0) {
+ List_t key_arg = List(arg);
+ (void)parse_arg_list(key_arg, flag, key, type->TableInfo.key, allow_dashes);
+ Table$set(dest, key, NULL, type);
+ args = List$from(args, I(2));
+ } else {
+ const char *colon = strchr(arg, ':');
+ if (!colon) break;
+ List_t key_arg = List(String(string_slice(arg, (size_t)(colon - arg))));
+ (void)parse_arg_list(key_arg, flag, key, type->TableInfo.key, allow_dashes);
+ List_t value_arg = List(colon + 1);
+ (void)parse_arg_list(value_arg, flag, value, type->TableInfo.value, allow_dashes);
+ Table$set(dest, key, value, type);
+ args = List$from(args, I(2));
+ }
}
- return n;
+ return args;
} else if (type->tag == StructInfo) {
- int64_t n = 0;
for (int i = 0; i < type->StructInfo.num_fields; i++) {
const TypeInfo_t *field_type = type->StructInfo.fields[i].type;
if (field_type->align > 0 && (size_t)dest % (size_t)field_type->align > 0)
dest += (size_t)field_type->align - ((size_t)dest % (size_t)field_type->align);
- n += parse_arg_list(List$slice(args, I(n + 1), I(-1)), String(flag, ".", type->StructInfo.fields[i].name),
- dest, field_type, allow_dashes);
+ args = parse_arg_list(args, String(flag, ".", type->StructInfo.fields[i].name), dest, field_type,
+ allow_dashes);
dest += field_type->size;
}
- return n;
+ return args;
}
if (args.length == 0) print_err("No value provided for flag: ", flag);
const char *arg = *(const char **)args.data;
- int64_t n = 1;
if (type->tag == OptionalInfo) {
const TypeInfo_t *nonnull = type->OptionalInfo.type;
if (streq(arg, "none")) {
if (nonnull == &Num$info) *(double *)dest = (double)NAN;
else if (nonnull == &Num32$info) *(float *)dest = (float)NAN;
else memset(dest, 0, (size_t)type->size);
+ return List$from(args, I(2));
} else {
- n = parse_arg_list(args, flag, dest, nonnull, allow_dashes);
+ args = parse_arg_list(args, flag, dest, nonnull, allow_dashes);
if (nonnull == &Int64$info) ((OptionalInt64_t *)dest)->has_value = true;
else if (nonnull == &Int32$info) ((OptionalInt32_t *)dest)->has_value = true;
else if (nonnull == &Int16$info) ((OptionalInt16_t *)dest)->has_value = true;
@@ -209,8 +213,13 @@ static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const T
else if (nonnull == &Byte$info) ((OptionalByte_t *)dest)->has_value = true;
else if (nonnull->tag == StructInfo && nonnull != &Path$info) *(bool *)(dest + nonnull->size) = true;
else print_err("Unsupported type: ", generic_as_text(NULL, true, nonnull));
+ return args;
}
- } else if (type == &CString$info) {
+ }
+
+ List_t rest_of_args = List$from(args, I(2));
+
+ if (type == &CString$info) {
*(const char **)dest = arg;
} else if (type == &Int$info) {
OptionalInt_t parsed = Int$from_str(arg);
@@ -251,9 +260,9 @@ static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const T
} else if (type->tag == PointerInfo) {
// For pointers, we can just allocate memory for the value and then parse the value
void *value = GC_MALLOC((size_t)type->PointerInfo.pointed->size);
- n = parse_arg_list(args, flag, value, type->PointerInfo.pointed, allow_dashes);
+ args = parse_arg_list(args, flag, value, type->PointerInfo.pointed, allow_dashes);
*(void **)dest = value;
- return n;
+ return args;
} else if (type == &Path$info) {
*(Path_t *)dest = Path$from_str(arg);
} else if (type->tag == TextInfo) {
@@ -269,16 +278,15 @@ static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const T
*(int32_t *)dest = (t + 1);
// Simple tag (no associated data):
- if (!named.type || (named.type->tag == StructInfo && named.type->StructInfo.num_fields == 0)) return n;
+ if (!named.type || (named.type->tag == StructInfo && named.type->StructInfo.num_fields == 0))
+ return rest_of_args;
dest += sizeof(int32_t);
if (named.type->align > 0 && (size_t)dest % (size_t)named.type->align > 0)
dest += (size_t)named.type->align - ((size_t)dest % (size_t)named.type->align);
- n += parse_arg_list(List$slice(args, I(n + 1), I(-1)), String(flag, ".", named.name), dest, named.type,
- allow_dashes);
- return n;
+ return parse_arg_list(rest_of_args, String(flag, ".", named.name), dest, named.type, allow_dashes);
}
}
print_err("Invalid enum name for ", type->EnumInfo.name, ": ", arg,
@@ -287,7 +295,7 @@ static int64_t parse_arg_list(List_t args, const char *flag, void *dest, const T
Text_t t = generic_as_text(NULL, false, type);
print_err("Unsupported type for argument parsing: ", t);
}
- return n;
+ return rest_of_args;
}
bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, const TypeInfo_t *type) {
@@ -305,9 +313,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
// Case: --flag values...
if (i + 1 >= (int64_t)args->length) print_err("No value provided for flag: ", flag);
List_t values = List$slice(*args, I(i + 2), I(-1));
- int64_t n = parse_arg_list(values, flag, dest, type, false);
- if (n == 0) print_err("No value provided for flag: ", flag);
- List$remove_at(args, I(i + 1), I(n + 1), sizeof(const char *));
+ *args = parse_arg_list(values, flag, dest, type, false);
return true;
} else if (starts_with(arg + 2, flag) && arg[2 + strlen(flag)] == '=') {
// Case: --flag=...
@@ -323,9 +329,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
} else {
values = List(arg_value);
}
- if (parse_arg_list(values, flag, dest, type, false) == 0)
- print_err("No value provided for flag: ", flag);
- List$remove_at(args, I(i + 1), I(1), sizeof(const char *));
+ *args = parse_arg_list(values, flag, dest, type, false);
return true;
}
} else if (short_flag && arg[0] == '-' && arg[1] != '-' && strchr(arg + 1, short_flag)) {
@@ -346,8 +350,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
// Case: -f=value
values = List(arg_value);
}
- if (parse_arg_list(values, flag, dest, type, false) == 0)
- print_err("No value provided for flag: -", short_str);
+ values = parse_arg_list(values, flag, dest, type, false);
if (loc > arg + 1) {
// Case: -abcdef=... -> -abcde
@@ -363,17 +366,17 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
// Case: -...f value...
if (i + 1 >= (int64_t)args->length) print_err("No value provided for flag: -", short_str);
List_t values = List$slice(*args, I(i + 2), I(-1));
- int64_t n = parse_arg_list(values, flag, dest, type, false);
- if (n == 0) print_err("No value provided for flag: -", short_str);
+ List_t remaining_values = parse_arg_list(values, flag, dest, type, false);
if (loc == arg + 1) {
// Case: -f values...
- List$remove_at(args, I(i + 1), I(n + 1), sizeof(const char *));
+ *args = List$concat(List$to(*args, I(i)), remaining_values, sizeof(const char *));
} else {
// Case: -abcdef values... -> -abcde
char *remainder = String(string_slice(arg, (size_t)(loc - arg)));
if unlikely (args->data_refcount > 0) List$compact(args, sizeof(const char *));
- *(const char **)(args->data + i * args->stride) = remainder;
- List$remove_at(args, I(i + 2), I(n), sizeof(const char *));
+ *args = List$concat(List$to(*args, I(i)),
+ List$concat(List(remainder), remaining_values, sizeof(const char *)),
+ sizeof(const char *));
}
return true;
} else {
@@ -391,8 +394,7 @@ bool pop_cli_flag(List_t *args, char short_flag, const char *flag, void *dest, c
// Case: -fVALUE
values = List(arg_value);
}
- if (parse_arg_list(values, flag, dest, type, false) == 0)
- print_err("No value provided for flag: -", short_str);
+ (void)parse_arg_list(values, flag, dest, type, false);
if (loc > arg + 1) {
// Case: -abcdefVALUE -> -abcde;
// NOTE: adding a semicolon means that `-ab1 2` won't parse as b=1, then a=2
@@ -415,8 +417,6 @@ bool pop_cli_positional(List_t *args, const char *flag, void *dest, const TypeIn
print_err("No value provided for flag: ", flag);
return false;
}
- int64_t n = parse_arg_list(*args, flag, dest, type, allow_dashes);
- if (n == 0) print_err("No value provided for flag: ", flag);
- List$remove_at(args, I(1), I(n), sizeof(const char *));
+ *args = parse_arg_list(*args, flag, dest, type, allow_dashes);
return true;
}