#include "say.h"
#ifdef __has_include
# if __has_include(<unistd.h>)
# define _say_use_ansi
# define _say_use_fd
# endif
#elif defined(__unix__) || defined(__linux__) || defined(__unix)
# define _say_use_ansi
# define _say_use_fd
#endif
#ifdef _say_use_fd
# include <unistd.h>
#endif
static void buffer_pushm
( buffer* buf,
uintmax_t val,
bool neg,
uint8_t base,
bool ucase
) {
char nb [48] = {};
size_t digit = 48;
do {
uintmax_t v = val%base;
if (v < 0xa) {
nb[-- digit] = '0' + v;
} else {
if (base <= 36) {
nb[-- digit] = (ucase?'A':'a') + v - 10;
} else {
if (v<36) nb[-- digit] = 'a' + v - 10;
else nb[-- digit] = 'A' + v - 36;
}
}
val /= base;
} while(val > 0);
if (neg) nb[-- digit] = '-';
buffer_push(buf, (strp){(sizeof nb) - digit, &nb[digit]});
}
size_t saybuf(buffer* buf, FILE* dest, const char* fmt, say_arg* args) {
say_arg const* arg = args;
# ifdef _say_use_fd
bool rich = dest == nullptr? true : isatty(fileno(dest));
# elif defined(_say_use_ansi)
bool const rich = true;
# endif
# define pushraw(s) do{if(canPrint)buffer_pushl(buf, s);}while(0)
# define pushcond(ch, won, woff) \
case ch: if (on) pushraw(won); else pushraw(woff); break
# define ansisw(ch, num) pushcond(ch, "\x1b[" #num "m", "\x1b[2" #num "m")
# define ansiclr(ch, num) \
case (ch) : pushraw("\x1b[3" #num "m"); break; \
case (ch)-0x20: pushraw("\x1b[4" #num "m"); break; \
uint8_t base = 10;
enum {normal, only_when_rich, only_when_plain, silent} exclude = normal;
for (size_t i = 0; fmt[i] != 0; ++i) {
char m = fmt[i];
char n = fmt[i+1];
bool canPrint;
switch (exclude) {
case normal: canPrint = true; break;
case silent: canPrint = false; break;
case only_when_rich:
# ifdef _say_use_ansi
canPrint = rich;
# else
canPrint = false;
# endif
break;
case only_when_plain:
# ifdef _say_use_ansi
canPrint = !rich;
# else
canPrint = true;
# endif
break;
}
if (n == 0) goto push; /* prevent bugs on broken seqs */
if (m == '[') {
bool on = true;
size_t param = 0;
while((m=fmt[++i]) != ']') {
# ifdef _say_use_ansi
if (rich) {
switch(m) {
case '+': on = true; break;
case '-': on = false; break;
case '=': pushraw("\x1b[m"); break;
ansisw('e', 1); ansisw('d', 2);
ansisw('i', 3); ansisw('u', 4);
ansisw('h', 7);
ansiclr('k', 0); ansiclr('r', 1);
ansiclr('g', 2); ansiclr('y', 3);
ansiclr('b', 4); ansiclr('p', 5);
ansiclr('c', 6); ansiclr('w', 7);
ansiclr('x', 9);
case 'L': pushraw("\x1b[2J");
default: goto fallback;
}
continue;
}
# endif
fallback: switch (m) {
case '^': exclude = only_when_rich; break;
case '~': exclude = only_when_plain; break;
case '|': exclude = normal; break;
case '#': exclude = silent; break;
case '?': exclude = *((bool*)*(arg++)) ? normal : silent; break;
case '@': base = param; param = 0; break;
case '$': arg = args[param]; param = 0; break;
default: goto enter_param;
} continue;
enter_param: if (m >= '0' && m <= '9') {
param *= 10;
param += m - '0';
}
}
} else if (m == '%') {
++i;
switch (n) {
case '[': m = '[';
case '%': goto push;
case 'z': {
char const* str = (char const*)*(arg++);
if (canPrint) {
if (str == nullptr) buffer_pushs(buf, "<null>");
else buffer_pushs(buf, str);
}
break;
}
case 's': {
strp const* a = *(arg++);
if (a -> ptr == nullptr) buffer_pushs(buf, "<null>");
else buffer_push(buf, *a);
break;
}
case 'i': case 'I':
case 'u': case 'U': {
bool neg=false; uintmax_t mag;
if (n=='i') {
intmax_t s = *((intmax_t*)*(arg++));
if (s < 0) { mag = -s; neg = true; }
else mag = s;
} else mag = *((uintmax_t*)*(arg++));
buffer_pushm(buf, mag, neg, base, n < 'a');
break;
}
case 'f': case 'F': {// fraction
fraction f = *((fraction*)*(arg++));
buffer_pushm(buf, fractionNum(f), fractionNeg(f), base, n<'a');
buffer_pushs(buf, "/");
buffer_pushm(buf, fractionDenom(f), false, base, n<'a');
} break;
case 'x': case 'X': {// fixed
fixed f = *((fixed*)*(arg++));
bool neg = fixedVal(f) < 0;
f = fixedAbs(f);
buffer_pushm(buf, fixedIntPart(f), neg, base, n<'a');
uint32_t fracpt = fixedFracPart(f);
if (fracpt != 0) {
intmax_t mul = base;
/* find prec multiplicand */
while (mul < fixedPrec(f)) {
if (mul * base < mul) {
buffer_pushs(buf, "<fixed-point precision error>");
break;
}
mul *= base;
}
uintmax_t r = ((intmax_t)fracpt * mul*mul) / ((intmax_t)fixedPrec(f)*mul);
buffer_pushs(buf, ".");
buffer_pushm(buf, r, false, base, n<'a');
}
} break;
case 'n': case 'N': {// inexact
} break;
case '~': {
char const* subfmt = (char const*)*(arg++);
say_arg* subargs = (say_arg*)*(arg++);
saybuf(buf, dest, subfmt, subargs);
break;
}
}
} else goto push;
continue;
push: if (canPrint) buffer_push(buf, (strp){1, &m});
}
return arg - args;
# undef ansiclr
# undef ansisw
# undef pushcond
# undef pushraw
}
static inline void
sayprep(FILE* dest) {
# ifndef _say_use_fd
setvbuf(dest, nullptr, _IONBF, 0); // hax
# endif
}
void sayto(FILE* dest, char const* fmt, say_arg* args) {
static _Thread_local buffer buf = {};
if (buf.run == 0) buffer_init(&buf);
else buf.sz = 0;
saybuf(&buf, dest, fmt, args);
# ifdef _say_use_fd
write(fileno(dest), buf.ptr, buf.sz);
# else
fprintf(dest, "%.*s", (int)buf.sz, buf.ptr);
# endif
buffer_crush(&buf, 2048);
}
void say(char const* fmt, say_arg* args) {
static bool stprep = false;
if (!stprep) { stprep=true; sayprep(stderr); };
sayto(stderr, fmt, args);
}