/* [ʞ] iaia.c
 *  ~ lexi hale <lexi@hale.su>
 *  # include "iaia.c"
 *  # define _IAIA_FN_{ATOI,ITOA,ASCTOI,ITOASC}
 *           _IAIA_EXP_ASCFORM
 *  : typedef iaia_word_type,
 *            iaia_error_type
 *  : enum iaia_e_domain,
 *         iaia_e_base,
 *         iaia_e_overflow
 *  ? arbitrary-base integer conversion functions
 */

#ifndef _IAIA_FN_ATOI
#	define _IAIA_FN_ATOI atoi
#endif
#ifndef _IAIA_FN_ITOA
#	define _IAIA_FN_ITOA itoa
#endif
#ifndef _IAIA_FN_ASCTOI
#	define _IAIA_FN_ASCTOI asctoi
#endif
#ifndef _IAIA_FN_ITOASC
#	define _IAIA_FN_ITOASC itoasc
#endif
#ifndef _IAIA_EXP_ASCFORM
/* this is an expression that will be evaluated to
 * determine whether to compress ascii to a 7-bit
 * representation or to store it in 8-bit space.
 *  1 = 7-bit (compressed)
 *  0 = 8-bit (uncompressed) */
#	define _IAIA_EXP_ASCFORM (0)
#endif
#ifndef NULL
#	define NULL ((void*)0)
#endif

enum /* constants */ {
	base7bit = 128,

	/* ascii address space */
	numspace        = (0x39 - 0x30) + 1, /* 10 */
	alphaspace      = (0x5a - 0x41) + 1, /* 26 */
	smallalphaspace = (0x7a - 0x61) + 1, /* 26 */

	/* base representations */
	imaxbase = numspace + alphaspace,    /* 36 */
	maxbase = imaxbase + smallalphaspace /* 62 */
};
#ifndef _IAIA_EXTERNAL_TYPES
	typedef enum iaia_error_type {
		iaia_e_ok,
		iaia_e_domain,
		iaia_e_base,
		iaia_e_overflow,
	} iaia_error_type;
	typedef unsigned long long iaia_word_type;
#endif

/* -- string to integer converters -- */

iaia_error_type
_IAIA_FN_ASCTOI(const char* s, iaia_word_type* ret) {
	iaia_word_type val = 0;

	for (;*s!=0;++s) {
		uint8_t v = *s;
		if (v > base7bit) return iaia_e_domain;

		if (_IAIA_EXP_ASCFORM)
			val *= base7bit;
			else val <<= 8;

		val += v;
	}

	*ret = val;
	return iaia_e_ok;
}

iaia_error_type
_IAIA_FN_ATOI(iaia_word_type base, const char* s, iaia_word_type* ret) {
	/* s must be a NUL-terminated ASCII numeral string */
	if (base > maxbase) return iaia_e_base;

	/* override the default base if it's a basèd literal */
	if (s[0] == '@' || base == 0) return _IAIA_FN_ASCTOI(s + (s[0]=='@'),ret);
	else if (s[0] == '0' && s[1] == 'x') base = 16, s += 2;
	else if (s[0] == '0' && s[1] == 'd') base = 10, s += 2;
	else if (s[0] == '0' && s[1] == 'b') base =  2, s += 2;
	else if (s[0] == '0' && s[1] == 't') base =  3, s += 2;
	else if (s[0] == '0')                base =  8, s += 1;

	bool insens = (base <= imaxbase);
	iaia_word_type val = 0;

	for (;*s!=0;++s) {
		uint8_t v = *s;
		if(v >= 0x30 && v <= 0x39) v -= 0x30; else {
			if(v >= 0x61 && v <= 0x7a) {
				if (insens) v -= 0x20; else {
					v = numspace + alphaspace + (v - 0x61);
					goto checkval;
				}
			}
			if(v >= 0x41 && v <= 0x5a) v = numspace + (v - 0x41);
				else return iaia_e_domain;
		}
		checkval: if (v >= base) return iaia_e_domain;

		val *= base;
		val += v;
	}

	*ret = val;
	return iaia_e_ok;
}


/* -- integer to string converters -- */

/* needed for efficiency's sake, but really sucky -
 * this table needs to be kept in sync with the
 * itoa algorithm manually. unfortunately, given C's
 * abject lack of metaprogramming, we have to do this
 * by hand. */
const char iaia_ref_table[] = /* numerals[10] */ "0123456789"
	/* bigalpha[26] */ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	/* smallalpha[26] */ "abcdefghijklmnopqrstuvwxyz";
_Static_assert (sizeof iaia_ref_table - 1 == maxbase, "tables out of sync");

iaia_error_type
_IAIA_FN_ITOASC(iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf) {
	char* ptr = buf_end;

	*ptr-- = 0;
	while(val > 0) {
		if (ptr < buf_start) return iaia_e_overflow;
		iaia_word_type rem = val % 128;
		if (_IAIA_EXP_ASCFORM)
			val /= base7bit;
			else val >>= 8;
		*ptr-- = (char)rem;
	}

	if (newbuf != NULL) *newbuf = ptr + 1;
	return iaia_e_ok;
}

iaia_error_type
_IAIA_FN_ITOA(iaia_word_type base, iaia_word_type val, const char* buf_start,
		char* buf_end, char** newbuf, bool lowercase) {

	char* ptr = buf_end;

	if (base > maxbase) return iaia_e_base;
	if (base == 0) return _IAIA_FN_ITOASC(val, buf_start, buf_end, newbuf);

	*ptr-- = 0;
	if (val == 0) *ptr-- = '0';
	else while(val > 0) {
		if (ptr < buf_start) return iaia_e_overflow;
		iaia_word_type rem = val % base;
		val /= base;
		char out = iaia_ref_table[rem];
		if (lowercase && base <= imaxbase)
			if (out >= 'A' && out <= 'Z')
				out += ('a' - 'A');
		*ptr-- = out;
	}

	if (newbuf != NULL) *newbuf = ptr + 1;
	return iaia_e_ok;
}

