diff --git a/kyaa.h b/kyaa.h index ff8afcd..5efdda8 100644 --- a/kyaa.h +++ b/kyaa.h @@ -16,8 +16,8 @@ #endif #define KYAA_SETUP \ - /* dumb sanity checks */ \ - if (argc <= 0 || argv == NULL || argv[0] == NULL || argv[0][0] == '\0') { \ + /* sanity checks */ \ + if (argc <= 0 || argv == NULL || argv[0] == NULL) { \ fprintf(stderr, "You've met with a terrible fate.\n"); \ return KYAA_ERROR; \ } \ @@ -104,15 +104,6 @@ kyaa_flag = c; \ 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) \ } \ if (kyaa_helping) { \ diff --git a/kyaa.md b/kyaa.md index b271f4a..b68ae09 100644 --- a/kyaa.md +++ b/kyaa.md @@ -7,13 +7,11 @@ super hacky macro hacks for parsing arguments in C. C99 or greater. standard library headers: -* `errno.h` * `stdbool.h` * `stdio.h` -* `stdlib.h` * `string.h` -## tutorial/API +## tutorial ensure `argc` and `argv` are defined. kyaa doesn't actually care if it's in `main` or not. @@ -23,7 +21,7 @@ iterate over the arguments with `KYAA_LOOP`: ```c int main(int argc, char *argv[]) { 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] } return 0; @@ -115,6 +113,42 @@ kyaa prints error messages to `stderr`, and help text to `stdout`. * support `--var=42` argument style * 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? \ No newline at end of file +* 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) +``` diff --git a/kyaa_extra.h b/kyaa_extra.h new file mode 100644 index 0000000..75dba86 --- /dev/null +++ b/kyaa_extra.h @@ -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; \ + } \ +