/* [ʞ] ord.c - integer converter
* ~ lexi hale <lexi@hale.su>
* © AGPLv3
* * ord has no dependencies except for libc.
* ? ord converts integers to ascii characters
* and back. written because the only fucking
* way to do this in shell is FUCKING PRINTF.
* $ cc ord.c -o ord [-D_IO=(LIBC|POSIX)]
* - the flag D_IO will instruct ord.c whether
* to use POSIX io primitives (write and read)
* instead of libc primitives (printf). if
* you're on a UNIX system, POSIX primitives
* will be used by default, but you can block
* them with LIBC or force them with POSIX.
* if you are on a POSIX- compliant system,
* you *should* use POSIX IO, for improved
* performance and safety.
TODO: take full advantage of write(2) by storing
output in single string & making single
write call */
#if (defined(__unix__) && _IO != LIBC) || (_IO == POSIX)
# define _POSIX_IO
#endif
#ifdef _POSIX_IO
# include <unistd.h>
# define say(x) (write(2, (x), (sizeof (x))))
# define print(sz,x) (write(1, (x), (sz)))
# define forposix(x) x
# define forlibc(x)
#else
# include <stdio.h>
# define say(x) (fprintf(stderr, (x)))
# define print(x) (printf("%s",(x)))
# define forposix(x)
# define forlibc(x) x
#endif
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
#define sz(x) ( sizeof (x) / sizeof (x) [0] )
enum /* constants */ {
null = 0,
};
typedef unsigned long long word;
typedef _Bool bool;
enum { false = 0, true = 1 };
#define error_list \
e(domain, "bad argument passed for domain") \
e(find, "could not find key in table") \
e(syntax, "invalid syntax") \
e(base, "that base is out of range") \
e(overflow, "a memory overflow has occurred") \
e(ebcdic, "nice try, mr ibm-san")
typedef enum bad {
ok = 0, fail = 1,
# define e(name, desc) bad_##name,
error_list
# undef e
} bad;
typedef enum {
tbl_ok = ok, tbl_error = bad_find
} tbl_error_type;
typedef unsigned char tbl_word_type;
#include "clib/tbl.c"
typedef struct tblrow pair;
enum argument {
arg_to, arg_set, arg_base,
arg_asc,
arg_bin, arg_trn, arg_oct, arg_dec,
arg_duo, arg_hex, arg_b32, arg_b64,
switch_prefix, param_prefix,
switch_lowercase,
arg_ebcdic,
};
word bases[] = {
[arg_asc] = 0,
[arg_bin] = 2,
[arg_trn] = 3,
[arg_oct] = 8,
[arg_dec] = 10,
[arg_duo] = 12,
[arg_hex] = 16,
[arg_b32] = 32,
};
const char* prefixes [] = { null,
[ 0] = "\1" "@",
[ 2] = "\2" "0b",
[ 3] = "\2" "0t",
[ 8] = "\1" "0",
[12] = "\2" "0d",
[16] = "\2" "0x",
};
const pair argtbl[] = {
{arg_to, "to"},
{arg_base, "base"},
{arg_set, "--"}, {arg_set, "raw"},
{arg_asc, "asc"}, {arg_asc, "ascii"},
{arg_bin, "bin"}, {arg_bin, "binary"},
{arg_trn, "trn"}, {arg_trn, "tern"}, {arg_trn, "ternary"}, {arg_trn, "trinary"},
{arg_oct, "oct"}, {arg_oct, "octal"},
{arg_dec, "dec"}, {arg_dec, "decimal"},
{arg_duo, "duo"}, {arg_duo, "duodecimal"},
{arg_hex, "hex"}, {arg_hex, "hexadecimal"},
{arg_hex, "b32"}, {arg_hex, "base32"}, /* not padded! */
{switch_prefix, "-p"}, {switch_prefix, "--prefix"},
{switch_lowercase, "-l"}, {switch_lowercase, "--lowercase"},
{param_prefix, "-m"}, {param_prefix, "--manual-prefix"},
{arg_ebcdic, "ebcdic"},
};
/* import the conversion utilities */
typedef bad iaia_error_type;
typedef word iaia_word_type;
enum /* iaia synonyms */ {
iaia_e_ok = ok,
iaia_e_domain = bad_domain,
iaia_e_base = bad_base,
iaia_e_overflow = bad_overflow,
};
#include "clib/iaia.c"
bad run(const int argc, const char** argv) {
# ifndef _POSIX_IO
/* fuck your buffering, it only ever makes
* things worse */
setvbuf(stdout,null,_IONBF);
# endif
enum { set_in, set_out, _set_sz } curset = set_in;
word base[_set_sz] = { 10, 0 };
const char* in_vals[argc]; *in_vals = null; /* null-terminated! */
const char** invalp = in_vals;
const char* pfxstr;
forposix(size_t pfxstrlen);
bool raw = false;
bool prefix = false;
bool lowercase = false;
for (const char** arg = argv + 1; *arg != null; ++arg) {
uint8_t tblval;
if (*arg[0] == '%') { ++ *arg; goto number; } else
if (!raw && (tblget(sz(argtbl),argtbl, *arg, &tblval) == ok)) {
enum argument symbol = (enum argument) tblval;
switch (symbol) {
case arg_to: {
if (curset == set_out) return bad_syntax;
else curset = set_out;
} break;
/* treat all further arguments as numbers */
case arg_set: { raw = true; } break;
case arg_ebcdic: { return bad_ebcdic; } break;
/* specify base with numeral */
case arg_base: {
if (arg[1] == null) return bad_syntax;
word basekind;
bad e = atoi(10, arg[1], &basekind);
if (e == ok) {
if (basekind > maxbase) return bad_base;
base[curset] = basekind;
} else return e;
++arg;
} break;
/* specify an output prefix */
case param_prefix: {
if (arg[1] == null) return bad_syntax;
prefix = true; pfxstr = arg[1];
forposix(pfxstrlen = strlen(pfxstr));
++arg;
} break;
/* specify an automatic output prefix */
case switch_prefix: { prefix = true; pfxstr = null; } break;
case switch_lowercase: { lowercase = true; } break;
default: {
/* assume base shorthand */
base[curset] = bases[symbol];
}
}
} else /* bad_find */ number: {
/* we assume it's a number - error checking will
* happen once we know how to interpret it */
*invalp++=*arg; *invalp=null;
}
}
/* if an ascii string was passed, change to hexadecimal output */
if (base[set_in] == 0 && curset != set_out) base[set_out] = 16;
size_t max_numeral_len = 0;
/* 0 = ascii rep (0 .. 127); one char = 7 bits */
if (base[set_out] == 0) max_numeral_len = (sizeof(word) * CHAR_BIT) / 7; else
if (base[set_out] == 1) max_numeral_len = 1024; /* pls don't */ else
/* note for unary: actual max is ((word) -1) but we cannot actually allocate
* that much fucking memory, so we limit to 1KiB and crash if it needs more */
if (base[set_out] <= 2) max_numeral_len = (sizeof(word) * CHAR_BIT); else
if (base[set_out] <= 8) max_numeral_len = (sizeof(word) * CHAR_BIT) / 3; else
if (base[set_out] <= 16) max_numeral_len = (sizeof(word) * CHAR_BIT) / 4; else
/* (base[set_out] <= 32) */ max_numeral_len = (sizeof(word) * CHAR_BIT) / 5;
/* this is i think the only sane-ish way to do it that
* doesn't involve *shudder* logarithms
TODO: find a better way to do this??? */
size_t bufmax = (invalp - in_vals) * max_numeral_len;
char buf [bufmax];
char* ptr = (buf + bufmax) - 1;
forposix(char* lastptr = ptr);
for (const char** s = in_vals; *s != null; ++s) {
word val;
bad e = atoi(base[set_in], *s, &val);
if (e == ok) {
bad e = itoa(base[set_out], val, buf, ptr, &ptr, lowercase);
if (e != ok) return e;
if (prefix) {
if (pfxstr != null) { print(pfxstrlen, pfxstr); }
else if (base[set_out] < sz(prefixes)) {
print((size_t)prefixes[base[set_out]][0],
prefixes[base[set_out]] + 1);
}
}
print(lastptr-ptr, ptr);
print(1, "\n");
forposix(lastptr = ptr);
} else {
return e;
}
}
return ok;
}
void usage(const char* name) {
# ifdef _POSIX_IO
typedef struct pstr { size_t len; const char* str; } pstr;
# define p(x) {sizeof (x "\n"), (x "\n")}
size_t namelen = strlen(name);
# else
typedef const char* pstr;
# define p(x) (x "\n")
# endif
# define OR "\x1b[34m|\x1b[93m"
# define plus "\x1b[94m+\x1b[m"
# define par(s) "<\x1b[4m" s "\x1b[24m>"
# define lit(l) "\x1b[3m" l "\x1b[23m"
# define box(s) "\x1b[94m[\x1b[93m" s "\x1b[94m]\x1b[m"
const pstr forms[] = {
p(box("options") " " box(par("in:spec")) " " par("value:int") plus " "
box(lit("to") " " box(par("out:spec")))),
p(box("options") " " box(par("in:spec")) " " box(lit("to") " " box(par("out:spec")))
" " lit("--") " " par("value:int") plus),
}, specs[] = {
p(box(lit("bin") OR lit("tern") OR lit("oct")
OR lit("dec") OR lit("hex") OR
lit("base") " " box("0-9") plus OR "asc")),
}, ints[] = {
p("default base: \x1b[94m.+\x1b[m"),
p("binary literal: "lit("0b") box("01") plus),
p("ternary literal: "lit("0t") box("012") plus),
p("hex literal: "lit("0x") box("0-9A-Fa-f") plus),
p("ascii literal: "lit("@") "\x1b[94m.+\x1b[m"),
p("interpret any string (e.g. a keyword) as integer: " lit("%") box("0-9A-Za-z") plus),
}, opts[] = {
p("-p --prefix : print known prefix codes on output strings"),
p("-m --manual-prefix: specify a manual prefix to print before each number"),
p("-l --lowercase : prefer lowercase for case-insensitive bases"),
};
# undef p
# undef OR
# undef plus
# define hl_on "\x1b[;1m"
# define hl_off "\x1b[m"
enum { ansilen = sizeof (hl_on hl_off) };
# define hl(x) (hl_on x hl_off)
const char form_head []= hl("usage: ");
const char spec_head []= hl("- spec: ");
const char int_head []= hl("- int: ");
const char opt_head []= hl("- options: ");
const char space []= " "; /* sigh */
# undef hl
# undef hl_on
# undef hl_off
# ifdef _POSIX_IO
# define _say(sz, s) write(2, (s), (sz));
# define vsay _say
# define display(hd) _say(sizeof (hd), (hd));
# define pline(l) _say((l).len, (l).str);
# else
# define _say(sz, s) printf("%.*s", sz, s);
# define display(hd) printf("%s",(hd));
# define vsay(sz, s) display(s)
# define pline(l) display(l);
# endif
# define space(x) _say(x, space);
# define glow(x) say("\x1b[95m"); { x }; say("\x1b[m");
# define section(x,prefix) display(x##_head); \
for(size_t i = 0; i < sz(x##s); ++ i) { \
if (i>0) space(sizeof x##_head - ansilen); \
{ prefix; }; pline(x##s[i]); }
section(form,glow(vsay(namelen, name)); space(1));
section(spec,);
section(int,);
section(opt,);
}
int main(int argc, const char** argv) {
if (argc == 0) return -1;
if (argc == 1) usage(argv[0]);
bad e = run(argc, argv);
switch (e) {
case ok: return 0;
case fail: return -1;
# define e(kind, desc) case bad_##kind:\
say("\x1b[31;1merror:\x1b[m "); say(desc "\n"); return e;
error_list
# undef e
}
}