.
This commit is contained in:
parent
82010d49d8
commit
f361a02852
3 changed files with 251 additions and 18 deletions
13
kyaa.h
13
kyaa.h
|
@ -16,8 +16,8 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define KYAA_SETUP \
|
#define KYAA_SETUP \
|
||||||
/* dumb sanity checks */ \
|
/* sanity checks */ \
|
||||||
if (argc <= 0 || argv == NULL || argv[0] == NULL || argv[0][0] == '\0') { \
|
if (argc <= 0 || argv == NULL || argv[0] == NULL) { \
|
||||||
fprintf(stderr, "You've met with a terrible fate.\n"); \
|
fprintf(stderr, "You've met with a terrible fate.\n"); \
|
||||||
return KYAA_ERROR; \
|
return KYAA_ERROR; \
|
||||||
} \
|
} \
|
||||||
|
@ -104,15 +104,6 @@
|
||||||
kyaa_flag = c; \
|
kyaa_flag = c; \
|
||||||
kyaa_any = true; \
|
kyaa_any = true; \
|
||||||
|
|
||||||
#define KYAA_FLAG_LONG(c, name, description) \
|
|
||||||
KYAA_FLAG_ARG(c, name, description) \
|
|
||||||
errno = 0; \
|
|
||||||
long kyaa_flag_arg = strtol(kyaa_etc, NULL, 0); \
|
|
||||||
if (errno) { \
|
|
||||||
perror(NULL); \
|
|
||||||
return KYAA_ERROR; \
|
|
||||||
} \
|
|
||||||
|
|
||||||
#define KYAA_HELP(description) \
|
#define KYAA_HELP(description) \
|
||||||
} \
|
} \
|
||||||
if (kyaa_helping) { \
|
if (kyaa_helping) { \
|
||||||
|
|
46
kyaa.md
46
kyaa.md
|
@ -7,13 +7,11 @@ super hacky macro hacks for parsing arguments in C.
|
||||||
C99 or greater.
|
C99 or greater.
|
||||||
|
|
||||||
standard library headers:
|
standard library headers:
|
||||||
* `errno.h`
|
|
||||||
* `stdbool.h`
|
* `stdbool.h`
|
||||||
* `stdio.h`
|
* `stdio.h`
|
||||||
* `stdlib.h`
|
|
||||||
* `string.h`
|
* `string.h`
|
||||||
|
|
||||||
## tutorial/API
|
## tutorial
|
||||||
|
|
||||||
ensure `argc` and `argv` are defined.
|
ensure `argc` and `argv` are defined.
|
||||||
kyaa doesn't actually care if it's in `main` or not.
|
kyaa doesn't actually care if it's in `main` or not.
|
||||||
|
@ -23,7 +21,7 @@ iterate over the arguments with `KYAA_LOOP`:
|
||||||
```c
|
```c
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
KYAA_LOOP {
|
KYAA_LOOP {
|
||||||
// i, kyaa_name, kyaa_read_stdin, and kyaa_flag are exposed here.
|
// KYAA_ITER, kyaa_name, kyaa_read_stdin, and kyaa_flag are exposed here.
|
||||||
// [other code goes here]
|
// [other code goes here]
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -115,6 +113,42 @@ kyaa prints error messages to `stderr`, and help text to `stdout`.
|
||||||
|
|
||||||
* support `--var=42` argument style
|
* support `--var=42` argument style
|
||||||
* fix overlapping names, e.g. `KYAA_FLAG` vs `kyaa_flag`, `KYAA_FLAG_ARG` vs `kyaa_flag_arg`, etc.
|
* fix overlapping names, e.g. `KYAA_FLAG` vs `kyaa_flag`, `KYAA_FLAG_ARG` vs `kyaa_flag_arg`, etc.
|
||||||
* replace `strtol` with something more user-friendly (removes `stdlib.h` and `errno.h` requirements)
|
|
||||||
* move `KYAA_FLAG_LONG` to `kyaa_extend.h` or something; write similar macros.
|
|
||||||
* maybe pass `argc`/`argv` manually?
|
* maybe pass `argc`/`argv` manually?
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### kyaa.h
|
||||||
|
|
||||||
|
```
|
||||||
|
KYAA_OKAY DEFINE int
|
||||||
|
KYAA_ERROR DEFINE int
|
||||||
|
KYAA_ITER DEFINE int
|
||||||
|
KYAA_SETUP MACRO
|
||||||
|
KYAA_LOOP MACRO
|
||||||
|
KYAA_BEGIN MACRO
|
||||||
|
KYAA_END MACRO
|
||||||
|
KYAA_DESCRIBE MACRO
|
||||||
|
KYAA_FLAG MACRO
|
||||||
|
KYAA_FLAG_ARG MACRO
|
||||||
|
KYAA_HELP MACRO
|
||||||
|
|
||||||
|
kyaa_name char *
|
||||||
|
kyaa_read_stdin bool
|
||||||
|
kyaa_flag char
|
||||||
|
kyaa_keep_parsing bool
|
||||||
|
kyaa_parse_next bool
|
||||||
|
kyaa_arg char *
|
||||||
|
kyaa_no_more bool
|
||||||
|
kyaa_helping bool
|
||||||
|
kyaa_any bool
|
||||||
|
kyaa_flag_arg char *
|
||||||
|
```
|
||||||
|
|
||||||
|
### kyaa\_extra.h
|
||||||
|
|
||||||
|
```
|
||||||
|
KYAA_FLAG_LONG MACRO
|
||||||
|
|
||||||
|
char *kyaa_skip_spaces(char *str)
|
||||||
|
const char *kyaa_str_to_long(char *str, long *p_out)
|
||||||
|
```
|
||||||
|
|
208
kyaa_extra.h
Normal file
208
kyaa_extra.h
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
static char *kyaa_skip_spaces(char *str) {
|
||||||
|
/* iterates str to first non-space character according to the C locale */
|
||||||
|
while (*str != '\0') {
|
||||||
|
switch (*str) {
|
||||||
|
case ' ':
|
||||||
|
case '\f':
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
case '\t':
|
||||||
|
case '\v': { str++; } break;
|
||||||
|
default: return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *kyaa__base_2(char **p_str, long *p_out) {
|
||||||
|
char *str = *p_str;
|
||||||
|
long out = *p_out;
|
||||||
|
char c;
|
||||||
|
while ((c = *str++) != '\0') {
|
||||||
|
switch (c) {
|
||||||
|
case '0': case '1': {
|
||||||
|
long digit = (long)(c - '0');
|
||||||
|
if (out < (LONG_MIN + digit) / 2) {
|
||||||
|
return "out of range for long integer";
|
||||||
|
}
|
||||||
|
out = out * 2 - digit;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '2': case '3': case '4': case '5':
|
||||||
|
case '6': case '7': case '8': case '9':
|
||||||
|
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
|
||||||
|
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
||||||
|
case '.': return "invalid character for base 2 integer";
|
||||||
|
|
||||||
|
default: goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit:
|
||||||
|
*p_str = str;
|
||||||
|
*p_out = out;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *kyaa__base_8(char **p_str, long *p_out) {
|
||||||
|
char *str = *p_str;
|
||||||
|
long out = *p_out;
|
||||||
|
char c;
|
||||||
|
while ((c = *str++) != '\0') {
|
||||||
|
switch (c) {
|
||||||
|
case '0': case '1': case '2': case '3':
|
||||||
|
case '4': case '5': case '6': case '7': {
|
||||||
|
long digit = (long)(c - '0');
|
||||||
|
if (out < (LONG_MIN + digit) / 8) {
|
||||||
|
return "out of range for long integer";
|
||||||
|
}
|
||||||
|
out = out * 8 - digit;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '8': case '9':
|
||||||
|
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
|
||||||
|
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
||||||
|
case '.': return "invalid character for base 8 integer";
|
||||||
|
|
||||||
|
default: goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit:
|
||||||
|
*p_str = str;
|
||||||
|
*p_out = out;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *kyaa__base_10(char **p_str, long *p_out) {
|
||||||
|
char *str = *p_str;
|
||||||
|
long out = *p_out;
|
||||||
|
char c;
|
||||||
|
while ((c = *str++) != '\0') {
|
||||||
|
switch (c) {
|
||||||
|
case '0': case '1': case '2': case '3': case '4':
|
||||||
|
case '5': case '6': case '7': case '8': case '9': {
|
||||||
|
long digit = (long)(c - '0');
|
||||||
|
if (out < (LONG_MIN + digit) / 10) {
|
||||||
|
return "out of range for long integer";
|
||||||
|
}
|
||||||
|
out = out * 10 - digit;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
|
||||||
|
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
||||||
|
case '.': return "invalid character for base 10 integer";
|
||||||
|
|
||||||
|
default: goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit:
|
||||||
|
*p_str = str;
|
||||||
|
*p_out = out;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *kyaa__base_16(char **p_str, long *p_out) {
|
||||||
|
char *str = *p_str;
|
||||||
|
long out = *p_out;
|
||||||
|
char c;
|
||||||
|
while ((c = *str++) != '\0') {
|
||||||
|
switch (c) {
|
||||||
|
case '0': case '1': case '2': case '3': case '4':
|
||||||
|
case '5': case '6': case '7': case '8': case '9': {
|
||||||
|
long digit = (long)(c - '0');
|
||||||
|
if (out < (LONG_MIN + digit) / 16) {
|
||||||
|
return "out of range for long integer";
|
||||||
|
}
|
||||||
|
out = out * 16 - digit;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': {
|
||||||
|
long digit = (long)(c - 'A') + 10;
|
||||||
|
if (out < (LONG_MIN + digit) / 16) {
|
||||||
|
return "out of range for long integer";
|
||||||
|
}
|
||||||
|
out = out * 16 - digit;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': {
|
||||||
|
long digit = (long)(c - 'a') + 10;
|
||||||
|
if (out < (LONG_MIN + digit) / 16) {
|
||||||
|
return "out of range for long integer";
|
||||||
|
}
|
||||||
|
out = out * 16 - digit;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '.': return "invalid character for base 16 integer";
|
||||||
|
|
||||||
|
default: goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit:
|
||||||
|
*p_str = str;
|
||||||
|
*p_out = out;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *kyaa_str_to_long(char *str, long *p_out) {
|
||||||
|
/* returns error message or NULL */
|
||||||
|
long out = 0;
|
||||||
|
int base = 10;
|
||||||
|
bool negated = false;
|
||||||
|
|
||||||
|
str = kyaa_skip_spaces(str);
|
||||||
|
|
||||||
|
switch (*str) {
|
||||||
|
case '-': { negated = true; str++; } break;
|
||||||
|
case '+': { str++; } break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (*str) {
|
||||||
|
case '#': { base = 10; str++; } break;
|
||||||
|
case '$': { base = 8; str++; } break;
|
||||||
|
case '%': { base = 2; str++; } break;
|
||||||
|
case '0': { base = -1; str++; } break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base == -1) {
|
||||||
|
switch (*str) {
|
||||||
|
case '\0': { *p_out = 0; } return NULL;
|
||||||
|
case 'b': { base = 2; str++; } break;
|
||||||
|
case 'h': { base = 16; str++; } break;
|
||||||
|
case 'o': { base = 8; str++; } break;
|
||||||
|
case 'x': { base = 16; str++; } break;
|
||||||
|
default: { base = 8; } break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*str == '\0') return "no number given";
|
||||||
|
|
||||||
|
// TODO: comment on how we go towards negatives instead of positives
|
||||||
|
const char *err;
|
||||||
|
switch (base) {
|
||||||
|
case 2: { err = kyaa__base_2( &str, &out); } break;
|
||||||
|
case 8: { err = kyaa__base_8( &str, &out); } break;
|
||||||
|
case 10: { err = kyaa__base_10(&str, &out); } break;
|
||||||
|
case 16: { err = kyaa__base_16(&str, &out); } break;
|
||||||
|
default: return "internal error";
|
||||||
|
}
|
||||||
|
if (err != NULL) return err;
|
||||||
|
|
||||||
|
str = kyaa_skip_spaces(str);
|
||||||
|
if (*str != '\0') return "unexpected character";
|
||||||
|
|
||||||
|
// assuming two's complement
|
||||||
|
if (!negated && out == LONG_MIN) return "out of range for long integer";
|
||||||
|
*p_out = negated ? out : -out;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define KYAA_FLAG_LONG(c, name, description) \
|
||||||
|
KYAA_FLAG_ARG(c, name, description) \
|
||||||
|
long kyaa_flag_arg; \
|
||||||
|
const char *err = kyaa_str_to_long(kyaa_etc, &kyaa_flag_arg); \
|
||||||
|
if (err) { \
|
||||||
|
fprintf(stderr, "%s\n", err); \
|
||||||
|
return KYAA_ERROR; \
|
||||||
|
} \
|
||||||
|
|
Loading…
Reference in a new issue