mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-06 04:37:47 +08:00
d8d229b0fb
inorder to support tcc, I need to remove some math functions that tcc on windows doesn't support (v language is using openlibm). This is temproarly and the math functions should be moved to it's own math script module once the import system is refactored. TCC build tested with the binary downloaded from -- https://download.savannah.gnu.org/releases/tinycc/tcc-0.9.27-win64-bin.zip (download link from the https://bellard.org/tcc/ website), and this should be added to the CI workflow.
399 lines
11 KiB
C
399 lines
11 KiB
C
/**
|
|
* Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
|
|
* All rights reserved.
|
|
*
|
|
* Use of this source code is governed by a MIT-style license that can be found
|
|
* in the LICENSE file.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include "argparse.h"
|
|
|
|
#define OPT_UNSET 1
|
|
#define OPT_LONG (1 << 1)
|
|
|
|
static const char *
|
|
prefix_skip(const char *str, const char *prefix)
|
|
{
|
|
size_t len = strlen(prefix);
|
|
return strncmp(str, prefix, len) ? NULL : str + len;
|
|
}
|
|
|
|
static int
|
|
prefix_cmp(const char *str, const char *prefix)
|
|
{
|
|
for (;; str++, prefix++)
|
|
if (!*prefix) {
|
|
return 0;
|
|
} else if (*str != *prefix) {
|
|
return (unsigned char)*prefix - (unsigned char)*str;
|
|
}
|
|
}
|
|
|
|
static void
|
|
argparse_error(struct argparse *self, const struct argparse_option *opt,
|
|
const char *reason, int flags)
|
|
{
|
|
(void)self;
|
|
if (flags & OPT_LONG) {
|
|
fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason);
|
|
} else {
|
|
fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason);
|
|
}
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
static int
|
|
argparse_getvalue(struct argparse *self, const struct argparse_option *opt,
|
|
int flags)
|
|
{
|
|
const char *s = NULL;
|
|
if (!opt->value)
|
|
goto skipped;
|
|
switch (opt->type) {
|
|
case ARGPARSE_OPT_BOOLEAN:
|
|
if (flags & OPT_UNSET) {
|
|
*(int *)opt->value = *(int *)opt->value - 1;
|
|
} else {
|
|
*(int *)opt->value = *(int *)opt->value + 1;
|
|
}
|
|
if (*(int *)opt->value < 0) {
|
|
*(int *)opt->value = 0;
|
|
}
|
|
break;
|
|
case ARGPARSE_OPT_BIT:
|
|
if (flags & OPT_UNSET) {
|
|
*(int *)opt->value &= ~opt->data;
|
|
} else {
|
|
*(int *)opt->value |= opt->data;
|
|
}
|
|
break;
|
|
case ARGPARSE_OPT_STRING:
|
|
if (self->optvalue) {
|
|
*(const char **)opt->value = self->optvalue;
|
|
self->optvalue = NULL;
|
|
} else if (self->argc > 1) {
|
|
self->argc--;
|
|
*(const char **)opt->value = *++self->argv;
|
|
} else {
|
|
argparse_error(self, opt, "requires a value", flags);
|
|
}
|
|
break;
|
|
case ARGPARSE_OPT_INTEGER:
|
|
errno = 0;
|
|
if (self->optvalue) {
|
|
*(int *)opt->value = strtol(self->optvalue, (char **)&s, 0);
|
|
self->optvalue = NULL;
|
|
} else if (self->argc > 1) {
|
|
self->argc--;
|
|
*(int *)opt->value = strtol(*++self->argv, (char **)&s, 0);
|
|
} else {
|
|
argparse_error(self, opt, "requires a value", flags);
|
|
}
|
|
if (errno == ERANGE)
|
|
argparse_error(self, opt, "numerical result out of range", flags);
|
|
if (s[0] != '\0') // no digits or contains invalid characters
|
|
argparse_error(self, opt, "expects an integer value", flags);
|
|
break;
|
|
case ARGPARSE_OPT_FLOAT:
|
|
errno = 0;
|
|
if (self->optvalue) {
|
|
// -- pocketlang start --
|
|
// Not sure why but tcc cause an error "tcc: error: undefined symbol 'strtof'".
|
|
#if defined(__TINYC__)
|
|
*(float*)opt->value = (float)strtod(self->optvalue, (char**)&s);
|
|
#else
|
|
*(float *)opt->value = strtof(self->optvalue, (char **)&s);
|
|
#endif
|
|
self->optvalue = NULL;
|
|
} else if (self->argc > 1) {
|
|
self->argc--;
|
|
#if defined(__TINYC__)
|
|
*(float*)opt->value = (float)strtod(*++self->argv, (char**)&s);
|
|
#else
|
|
*(float *)opt->value = strtof(*++self->argv, (char **)&s);
|
|
#endif
|
|
// -- pocketlang end --
|
|
} else {
|
|
argparse_error(self, opt, "requires a value", flags);
|
|
}
|
|
if (errno == ERANGE)
|
|
argparse_error(self, opt, "numerical result out of range", flags);
|
|
if (s[0] != '\0') // no digits or contains invalid characters
|
|
argparse_error(self, opt, "expects a numerical value", flags);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
skipped:
|
|
if (opt->callback) {
|
|
return opt->callback(self, opt);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
argparse_options_check(const struct argparse_option *options)
|
|
{
|
|
for (; options->type != ARGPARSE_OPT_END; options++) {
|
|
switch (options->type) {
|
|
case ARGPARSE_OPT_END:
|
|
case ARGPARSE_OPT_BOOLEAN:
|
|
case ARGPARSE_OPT_BIT:
|
|
case ARGPARSE_OPT_INTEGER:
|
|
case ARGPARSE_OPT_FLOAT:
|
|
case ARGPARSE_OPT_STRING:
|
|
case ARGPARSE_OPT_GROUP:
|
|
continue;
|
|
default:
|
|
fprintf(stderr, "wrong option type: %d", options->type);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
argparse_short_opt(struct argparse *self, const struct argparse_option *options)
|
|
{
|
|
for (; options->type != ARGPARSE_OPT_END; options++) {
|
|
if (options->short_name == *self->optvalue) {
|
|
self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL;
|
|
return argparse_getvalue(self, options, 0);
|
|
}
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
static int
|
|
argparse_long_opt(struct argparse *self, const struct argparse_option *options)
|
|
{
|
|
for (; options->type != ARGPARSE_OPT_END; options++) {
|
|
const char *rest;
|
|
int opt_flags = 0;
|
|
if (!options->long_name)
|
|
continue;
|
|
|
|
rest = prefix_skip(self->argv[0] + 2, options->long_name);
|
|
if (!rest) {
|
|
// negation disabled?
|
|
if (options->flags & OPT_NONEG) {
|
|
continue;
|
|
}
|
|
// only OPT_BOOLEAN/OPT_BIT supports negation
|
|
if (options->type != ARGPARSE_OPT_BOOLEAN && options->type !=
|
|
ARGPARSE_OPT_BIT) {
|
|
continue;
|
|
}
|
|
|
|
if (prefix_cmp(self->argv[0] + 2, "no-")) {
|
|
continue;
|
|
}
|
|
rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name);
|
|
if (!rest)
|
|
continue;
|
|
opt_flags |= OPT_UNSET;
|
|
}
|
|
if (*rest) {
|
|
if (*rest != '=')
|
|
continue;
|
|
self->optvalue = rest + 1;
|
|
}
|
|
return argparse_getvalue(self, options, opt_flags | OPT_LONG);
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
int
|
|
argparse_init(struct argparse *self, struct argparse_option *options,
|
|
const char *const *usages, int flags)
|
|
{
|
|
memset(self, 0, sizeof(*self));
|
|
self->options = options;
|
|
self->usages = usages;
|
|
self->flags = flags;
|
|
self->description = NULL;
|
|
self->epilog = NULL;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
argparse_describe(struct argparse *self, const char *description,
|
|
const char *epilog)
|
|
{
|
|
self->description = description;
|
|
self->epilog = epilog;
|
|
}
|
|
|
|
int
|
|
argparse_parse(struct argparse *self, int argc, const char **argv)
|
|
{
|
|
self->argc = argc - 1;
|
|
self->argv = argv + 1;
|
|
self->out = argv;
|
|
|
|
argparse_options_check(self->options);
|
|
|
|
for (; self->argc; self->argc--, self->argv++) {
|
|
const char *arg = self->argv[0];
|
|
if (arg[0] != '-' || !arg[1]) {
|
|
if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) {
|
|
goto end;
|
|
}
|
|
// if it's not option or is a single char '-', copy verbatim
|
|
self->out[self->cpidx++] = self->argv[0];
|
|
continue;
|
|
}
|
|
// short option
|
|
if (arg[1] != '-') {
|
|
self->optvalue = arg + 1;
|
|
switch (argparse_short_opt(self, self->options)) {
|
|
case -1:
|
|
break;
|
|
case -2:
|
|
goto unknown;
|
|
}
|
|
while (self->optvalue) {
|
|
switch (argparse_short_opt(self, self->options)) {
|
|
case -1:
|
|
break;
|
|
case -2:
|
|
goto unknown;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
// if '--' presents
|
|
if (!arg[2]) {
|
|
self->argc--;
|
|
self->argv++;
|
|
break;
|
|
}
|
|
// long option
|
|
switch (argparse_long_opt(self, self->options)) {
|
|
case -1:
|
|
break;
|
|
case -2:
|
|
goto unknown;
|
|
}
|
|
continue;
|
|
|
|
unknown:
|
|
fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]);
|
|
argparse_usage(self);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
end:
|
|
// -- pocketlang start --
|
|
memmove((void*)(self->out + self->cpidx), (void*)(self->argv),
|
|
self->argc * sizeof(*self->out));
|
|
// -- pocketlang end --
|
|
self->out[self->cpidx + self->argc] = NULL;
|
|
|
|
return self->cpidx + self->argc;
|
|
}
|
|
|
|
void
|
|
argparse_usage(struct argparse *self)
|
|
{
|
|
if (self->usages) {
|
|
fprintf(stdout, "Usage: %s\n", *self->usages++);
|
|
while (*self->usages && **self->usages)
|
|
fprintf(stdout, " or: %s\n", *self->usages++);
|
|
} else {
|
|
fprintf(stdout, "Usage:\n");
|
|
}
|
|
|
|
// print description
|
|
if (self->description)
|
|
fprintf(stdout, "%s\n", self->description);
|
|
|
|
// -- pocketlang start --
|
|
// fputc('\n', stdout);
|
|
// -- pocketlang end --
|
|
|
|
const struct argparse_option *options;
|
|
|
|
// figure out best width
|
|
size_t usage_opts_width = 0;
|
|
size_t len;
|
|
options = self->options;
|
|
for (; options->type != ARGPARSE_OPT_END; options++) {
|
|
len = 0;
|
|
if ((options)->short_name) {
|
|
len += 2;
|
|
}
|
|
if ((options)->short_name && (options)->long_name) {
|
|
len += 2; // separator ", "
|
|
}
|
|
if ((options)->long_name) {
|
|
len += strlen((options)->long_name) + 2;
|
|
}
|
|
if (options->type == ARGPARSE_OPT_INTEGER) {
|
|
len += strlen("=<int>");
|
|
}
|
|
if (options->type == ARGPARSE_OPT_FLOAT) {
|
|
len += strlen("=<flt>");
|
|
} else if (options->type == ARGPARSE_OPT_STRING) {
|
|
len += strlen("=<str>");
|
|
}
|
|
len = (len + 3) - ((len + 3) & 3);
|
|
if (usage_opts_width < len) {
|
|
usage_opts_width = len;
|
|
}
|
|
}
|
|
usage_opts_width += 4; // 4 spaces prefix
|
|
|
|
options = self->options;
|
|
for (; options->type != ARGPARSE_OPT_END; options++) {
|
|
size_t pos = 0;
|
|
size_t pad = 0;
|
|
if (options->type == ARGPARSE_OPT_GROUP) {
|
|
fputc('\n', stdout);
|
|
fprintf(stdout, "%s", options->help);
|
|
fputc('\n', stdout);
|
|
continue;
|
|
}
|
|
pos = fprintf(stdout, " ");
|
|
if (options->short_name) {
|
|
pos += fprintf(stdout, "-%c", options->short_name);
|
|
}
|
|
if (options->long_name && options->short_name) {
|
|
pos += fprintf(stdout, ", ");
|
|
}
|
|
if (options->long_name) {
|
|
pos += fprintf(stdout, "--%s", options->long_name);
|
|
}
|
|
if (options->type == ARGPARSE_OPT_INTEGER) {
|
|
pos += fprintf(stdout, "=<int>");
|
|
} else if (options->type == ARGPARSE_OPT_FLOAT) {
|
|
pos += fprintf(stdout, "=<flt>");
|
|
} else if (options->type == ARGPARSE_OPT_STRING) {
|
|
pos += fprintf(stdout, "=<str>");
|
|
}
|
|
if (pos <= usage_opts_width) {
|
|
pad = usage_opts_width - pos;
|
|
} else {
|
|
fputc('\n', stdout);
|
|
pad = usage_opts_width;
|
|
}
|
|
fprintf(stdout, "%*s%s\n", (int)pad + 2, "", options->help);
|
|
}
|
|
|
|
// print epilog
|
|
if (self->epilog)
|
|
fprintf(stdout, "%s\n", self->epilog);
|
|
}
|
|
|
|
int
|
|
argparse_help_cb(struct argparse *self, const struct argparse_option *option)
|
|
{
|
|
(void)option;
|
|
argparse_usage(self);
|
|
exit(EXIT_SUCCESS);
|
|
} |