aboutsummaryrefslogtreecommitdiff
path: root/src/libfmt/do.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libfmt/do.c')
-rw-r--r--src/libfmt/do.c730
1 files changed, 730 insertions, 0 deletions
diff --git a/src/libfmt/do.c b/src/libfmt/do.c
new file mode 100644
index 0000000..eaac0a3
--- /dev/null
+++ b/src/libfmt/do.c
@@ -0,0 +1,730 @@
+#include "internal.h"
+#include <stdatomic.h>
+
+#define atomic _Atomic
+#define MaxFmt 128
+#define atomic·load atomic_load
+#define atomic·store atomic_store
+
+// -----------------------------------------------------------------------
+// globals
+
+/* built in verbs */
+static int fmtflag(fmt·State *);
+static int fmtpercent(fmt·State *);
+static int fmtrune(fmt·State *);
+static int fmtfloat(fmt·State *);
+static int fmtutf8(fmt·State *);
+static int fmtint(fmt·State *);
+static int fmtchar(fmt·State *);
+static int fmtcount(fmt·State *);
+static int fmtstring(fmt·State *);
+static int fmterror(fmt·State *);
+
+static int badfmt(fmt·State *);
+
+static struct
+{
+ atomic int len;
+ Verb verb[MaxFmt];
+} formatter =
+{
+ ATOMIC_VAR_INIT(30),
+ {
+ {' ', fmtflag},
+ {'#', fmtflag},
+ {'%', fmtpercent},
+ {'\'',fmtflag},
+ {'+', fmtflag},
+ {',', fmtflag},
+ {'-', fmtflag},
+ {'C', fmtrune},
+ {'E', fmtfloat},
+ {'F', fmtfloat},
+ {'G', fmtfloat},
+ {'L', fmtflag},
+ {'S', fmtutf8},
+ {'X', fmtint},
+ {'b', fmtint},
+ {'c', fmtchar},
+ {'d', fmtint},
+ {'e', fmtfloat},
+ {'f', fmtfloat},
+ {'g', fmtfloat},
+ {'h', fmtflag},
+ {'i', fmtint},
+ {'l', fmtflag},
+ {'n', fmtcount},
+ {'o', fmtint},
+ {'p', fmtint},
+ {'r', fmterror},
+ {'s', fmtstring},
+ {'U', fmtflag},
+ {'u', fmtint},
+ {'x', fmtint},
+ }
+};
+
+// -----------------------------------------------------------------------
+// internal functions
+
+static Formatter
+format(int c)
+{
+ Verb *v, *e;
+ e = &formatter.verb[atomic·load(&formatter.len)];
+ for(v=e; v > formatter.verb; --v){
+ if(v->c == c)
+ return v->fmt;
+ }
+
+ return badfmt;
+}
+
+static char *
+dispatch(fmt·State *io, char *fmt)
+{
+ rune r;
+ int i, n;
+
+ io->flag = 0;
+ io->width = io->prec = 0;
+
+ /*
+ * the form of each print verb:
+ * % [flags] verb
+ * + the verb is a single character
+ * + each flag is either
+ * - a single character
+ * - a decimal numeric string
+ * - up to 2 decimal strings can be used
+ * - [width|*].[prec|*]
+ * - if missing, set to 0
+ * - if *, grab from varargs
+ */
+ for(;;){
+ fmt += utf8·decode(fmt, &r);
+ io->verb = r;
+ switch(r){
+ case 0:
+ return nil;
+ case '.':
+ io->flag |= fmt·Width|fmt·Prec;
+ continue;
+ case '0':
+ if(!(io->flag & fmt·Width)){
+ io->flag |= fmt·Zero;
+ continue;
+ }
+ /* fallthrough */
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ i = 0;
+ while('0' <= r && r <= '9'){
+ i = 10*i + (r-'0');
+ r = *fmt++;
+ }
+ fmt--;
+ number:
+ if(io->flag & fmt·Width){
+ io->flag |= fmt·Prec;
+ io->prec = i;
+ }else{
+ io->flag |= fmt·Width;
+ io->width = i;
+ }
+ continue;
+ case '*':
+ i = va_arg(io->args, int);
+ if(i < 0){
+ if(io->flag&fmt·Prec){
+ io->flag &= ~fmt·Prec;
+ io->prec = 0;
+ continue;
+ }
+ i = -i;
+ io->flag |= fmt·Left;
+ }
+ goto number;
+ }
+ n = format(r)(io);
+ if(n < 0)
+ return nil;
+ if(!n)
+ return fmt;
+ }
+}
+
+static char *
+flush(fmt·State *io, char *b, int len)
+{
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ if(!io->flush || !(*io->flush)(io) || io->buffer.cur + len >= io->buffer.end) {
+ io->buffer.end = io->buffer.cur;
+ return nil;
+ }
+ return io->buffer.cur;
+}
+
+static int
+pad(fmt·State *io, int n)
+{
+ int i;
+ char *b=io->buffer.cur, *e=io->buffer.end;
+
+ for(i=0; i<n; i++){
+ if(b>=e){
+ if(!(b=flush(io, b, 1)))
+ return -1;
+ e = io->buffer.end;
+ }
+ *b++ = ' ';
+ }
+
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ return 0;
+}
+
+static int
+copy(fmt·State *io, char *m, int sz, int n)
+{
+ ulong f;
+ rune r;
+ int nc, w, nb;
+ char *b, *e, *me;
+
+ w = 0;
+ f = io->flag;
+ me = m + sz;
+
+ if(f&fmt·Width)
+ w = io->width;
+ if(f&fmt·Prec && n > io->prec)
+ n = io->prec;
+ if(!(f&fmt·Left) && pad(io, w-n)<0)
+ return -1;
+
+ b = io->buffer.cur;
+ e = io->buffer.end;
+
+ for(nc=n; nc>0; nc--){
+ r = *(uchar *)m;
+ if(utf8·onebyte(r)){
+ nb=1;
+ m++;
+ }else if((me-m) >= UTFmax || utf8·canfit(m, me-m)){
+ nb=utf8·decode(m, &r);
+ m+=n;
+ }else
+ break;
+
+ if(b+n>e){
+ if(!(b=flush(io, b, nb)))
+ return -1;
+ e = io->buffer.end;
+ }
+ b += utf8·encode(&r, b);
+ }
+
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ if(f&fmt·Left && pad(io, w-n)<0)
+ return -1;
+
+ return 0;
+}
+
+static int
+copyrune(fmt·State *io, rune *m, int n)
+{
+ ulong f;
+ rune r, *me;
+ int w, nb;
+ char *b, *e;
+
+ w = 0;
+ f = io->flag;
+
+ if(f&fmt·Width)
+ w = io->width;
+ if(f&fmt·Prec && n > io->prec)
+ n = io->prec;
+
+ if(!(f&fmt·Left) && pad(io, w-n)<0)
+ return -1;
+
+ b = io->buffer.cur;
+ e = io->buffer.end;
+
+ for(me=m+n; m < me; m++){
+ r = *m;
+ nb = utf8·runelen(r);
+ if(b + nb > e){
+ if(!(b=flush(io, b, nb)))
+ return -1;
+ e = io->buffer.end;
+ }
+ b += utf8·encode(&r, b);
+ }
+
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ if(f&fmt·Left && pad(io, w-n)<0)
+ return -1;
+
+ return 0;
+}
+
+static int
+copystring(fmt·State *io, char *s)
+{
+ rune r;
+ int i,j;
+
+ if(!s)
+ return copy(io, "<nil>", 5, 5);
+
+ if(io->flag&fmt·Prec){
+ i = 0;
+ for(j=0; j < io->prec && s[i]; j++)
+ i += utf8·decode(s+i, &r);
+
+ return copy(io, s, i, j);
+ }
+ return copy(io, s, strlen(s), utf8·len(s));
+}
+
+static int
+copyutf8(fmt·State *io, rune *s)
+{
+ rune *e;
+ int n,p;
+
+ if(!s)
+ return copy(io, "<nil>", 5, 5);
+
+ if(io->flag & fmt·Prec){
+ p = io->prec;
+ for(n=0; n<p; n++)
+ if(!s[n])
+ break;
+ }else{
+ for(e=s; *e; e++)
+ ;
+ n = e - s;
+ }
+
+ return copyrune(io, s, n);
+}
+
+// -----------------------------------------------------------------------
+// format helpers
+
+static int
+needseperate(int *digits, char **groups)
+{
+ int group;
+
+ (*digits)++;
+ group = *(uchar *)*groups;
+
+ if(group == 0xFF || group == 0x7f || group == 0x00)
+ return 0;
+ if(*digits > group){
+ if((*groups)[1] != 0)
+ (*groups)++;
+ *digits = 1;
+ return 1;
+ }
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// formatters
+
+static int
+fmtchar(fmt·State *io)
+{
+ char x[1];
+ x[0] = va_arg(io->args, int);
+ io->prec = 1;
+
+ return copy(io, x, 1, 1);
+}
+
+static int
+fmtstring(fmt·State *io)
+{
+ char *s;
+ s = va_arg(io->args, char *);
+ return copystring(io, s);
+}
+
+static int
+fmterror(fmt·State *io)
+{
+ char *s;
+ s = strerror(errno);
+ return copystring(io, s);
+}
+
+static int
+fmtrune(fmt·State *io)
+{
+ rune x[1];
+
+ x[0] = va_arg(io->args, int);
+ return copyrune(io, x, 1);
+}
+
+static int
+fmtutf8(fmt·State *io)
+{
+ rune *s;
+
+ s = va_arg(io->args, rune *);
+ return copyutf8(io, s);
+}
+
+static int
+fmtpercent(fmt·State *io)
+{
+ rune x[1];
+
+ x[0] = io->verb;
+ io->prec = 1;
+ return copyrune(io, x, 1);
+}
+
+static int
+fmtint(fmt·State *io)
+{
+ union{
+ ulong u;
+ uvlong v;
+ } val;
+ int neg, base, i, n, f, w, isv;
+ int digits, bytes, runes, excess;
+ char *groups, *thousands;
+ char *p, *conv, buf[140];
+
+ f = io->flag;
+ neg = 0;
+ isv = 0;
+ val.u = 0;
+
+ switch(io->verb){
+ case 'o': case 'p': case 'u': case 'x': case 'X':
+ f |= fmt·Unsigned;
+ f &= ~(fmt·Sign|fmt·Space);
+ }
+
+ /* set flags */
+ if(io->verb=='p'){
+ val.u = (ulong)va_arg(io->args, void*);
+ io->verb = 'x';
+ f |= fmt·Unsigned;
+ }else if(f&fmt·Vlong){
+ isv=1;
+ if(f&fmt·Unsigned)
+ val.v = va_arg(io->args, uvlong);
+ else
+ val.v = va_arg(io->args, vlong);
+ }else if(f&fmt·Long){
+ if(f&fmt·Unsigned)
+ val.u = va_arg(io->args, ulong);
+ else
+ val.u = va_arg(io->args, long);
+ }else if(f&fmt·Byte){
+ if(f&fmt·Unsigned)
+ val.u = (uchar)va_arg(io->args, int);
+ else
+ val.u = (char)va_arg(io->args, int);
+ }else if(f&fmt·Short){
+ if(f&fmt·Unsigned)
+ val.u = (ushort)va_arg(io->args, int);
+ else
+ val.u = (short)va_arg(io->args, int);
+ }else{
+ if(f&fmt·Unsigned)
+ val.u = va_arg(io->args, uint);
+ else
+ val.u = va_arg(io->args, int);
+ }
+
+ conv = "0123456789abcdef";
+ groups = "\4";
+ thousands = io->thousands;
+ /* get base */
+ switch(io->verb){
+ case 'd': case 'i': case 'u':
+ base = 10;
+ groups = io->groups;
+ break;
+ case 'X':
+ conv = "0123456789ABCDEF";
+ /*fallthrough*/
+ case 'x':
+ base = 16;
+ thousands = ":";
+ break;
+ case 'b':
+ base = 2;
+ thousands = ":";
+ break;
+ case 'o':
+ base = 8;
+ break;
+ default:
+ return -1;
+ }
+
+ /* check for negativity */
+ if(!(f&fmt·Unsigned)){
+ if(isv && (vlong)val.v < 0){
+ val.v = -(vlong)val.v;
+ neg = 1;
+ }else if(!isv && (long)val.u < 0){
+ val.u = -(long)val.u;
+ neg = 1;
+ }
+ }
+
+ p = buf + sizeof(buf) - 1;
+ n = 0;
+ digits = 0;
+ excess = 0;
+ runes = utf8·len(thousands);
+ bytes = strlen(thousands);
+
+#define PARSE(VALUE) \
+ while((VALUE)){ \
+ i = (VALUE) % base; \
+ (VALUE) /= base; \
+ if((f&fmt·Comma) && n%4 == 3){ \
+ *p-- = ','; \
+ n++; \
+ } \
+ if((f&fmt·Apost) && needseperate(&digits, &groups)){ \
+ n += runes; \
+ excess += bytes - runes; \
+ p -= bytes; \
+ memmove(p+1, thousands, bytes); \
+ } \
+ *p-- = conv[i]; \
+ n++; \
+ }
+ if(isv)
+ PARSE(val.v)
+ else
+ PARSE(val.u)
+#undef PARSE
+
+ if(!n){
+ if(!(f&fmt·Prec) || io->prec != 0 || (io->verb == 'o' && (f&fmt·Sharp))){
+ *p-- = '0';
+ n = 1;
+ if(f&fmt·Apost)
+ needseperate(&digits,&groups);
+ }
+
+ if(io->verb == 'x' || io->verb == 'X')
+ f &= ~fmt·Sharp;
+ }
+
+ for(w = io->prec; n < w && p > buf+3; n++){
+ if((f&fmt·Apost) && needseperate(&digits, &groups)){
+ n += runes;
+ excess += bytes - runes;
+ p -= bytes;
+ memmove(p+1, thousands, bytes);
+ }
+ *p-- = '0';
+ }
+
+ if(neg || (f&(fmt·Sign|fmt·Space)))
+ n++;
+
+ if(f&fmt·Sharp){
+ if(base==16)
+ n += 2;
+ else if(base == 8){
+ if(p[1] == '0')
+ f &= ~fmt·Sharp;
+ else
+ n++;
+ }
+ }
+
+ if(f&fmt·Zero && !(f & (fmt·Left|fmt·Prec))){
+ w = 0;
+ if(f & fmt·Width)
+ w = io->width;
+ for(; n < w && p > buf+3; n++){
+ if((f & fmt·Apost) && needseperate(&digits, &groups)){
+ n += runes;
+ excess += bytes - runes;
+ p -= bytes;
+ memmove(p+1, thousands, bytes);
+ }
+ *p-- = '0';
+ }
+ io->flag &= ~fmt·Width;
+ }
+
+ if(f&fmt·Sharp){
+ if(base==16)
+ *p-- = io->verb;
+ if(base==16 || base == 8)
+ *p-- = '0';
+ }
+
+ if(neg)
+ *p-- = '-';
+ else if(f & fmt·Sign)
+ *p-- = '+';
+ else if (f & fmt·Space)
+ *p-- = ' ';
+
+ io->flag &= ~fmt·Prec;
+ return copy(io, p+1, n+excess, n);
+}
+
+static int
+fmtcount(fmt·State *io)
+{
+ void *p;
+ ulong f;
+
+ f = io->flag;
+ p = va_arg(io->args, void*);
+
+ if(f&fmt·Vlong)
+ *(vlong*)p = io->n;
+ else if(f&fmt·Long)
+ *(long*)p = io->n;
+ else if(f&fmt·Byte)
+ *(char*)p = io->n;
+ else if(f&fmt·Short)
+ *(short*)p = io->n;
+ else
+ *(int*)p = io->n;
+
+ return 0;
+}
+
+static int
+fmtflag(fmt·State *io)
+{
+ switch(io->verb){
+ case ',': io->flag |= fmt·Comma; break;
+ case '-': io->flag |= fmt·Left; break;
+ case '+': io->flag |= fmt·Sign; break;
+ case '#': io->flag |= fmt·Sharp; break;
+ case '\'': io->flag |= fmt·Apost; break;
+ case ' ': io->flag |= fmt·Space; break;
+ case 'u': io->flag |= fmt·Unsigned; break;
+ case 'L': io->flag |= fmt·Ldouble; break;
+ case 'h':
+ if(io->flag&fmt·Short)
+ io->flag |= fmt·Byte;
+ io->flag |= fmt·Short;
+ break;
+ case 'l':
+ if(io->flag&fmt·Long)
+ io->flag |= fmt·Vlong;
+ io->flag |= fmt·Long;
+ break;
+ }
+ return 1;
+}
+
+static int
+badfmt(fmt·State *io)
+{
+ int n;
+ char x[UTFmax+2];
+
+ x[0] = '%';
+ n = 1 + utf8·encode(&io->verb, x+1);
+ x[n++] = '%';
+ io->prec = n;
+ copy(io, x, n, n);
+
+ return 0;
+}
+
+#include "float.c"
+
+// -----------------------------------------------------------------------
+// exports
+
+int
+fmt·do(fmt·State *io, char *fmt)
+{
+ rune r;
+ int c, n;
+ char *b, *e;
+
+ for(;;){
+ b = io->buffer.cur;
+ e = io->buffer.end;
+ while((c = *(uchar *)fmt) && c != '%'){
+ if(utf8·onebyte(c)){
+ if(b >= e){
+ if(!(b=flush(io, b, 1)))
+ return -1;
+ e = io->buffer.end;
+ }
+ *b++ = *fmt++;
+ }else{
+ n = utf8·decode(fmt, &r);
+ if(b + n > e){
+ if(!(b=flush(io, b, n)))
+ return -1;
+ e = io->buffer.end;
+ }
+ while(n--)
+ *b++ = *fmt++;
+ }
+ }
+ fmt++;
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ if(!c) /* we hit our nul terminator */
+ return io->n - n;
+ io->buffer.end = e;
+
+ if(!(fmt=dispatch(io, fmt)))
+ return -1;
+ }
+}
+
+int
+fmt·install(int verb, Formatter func)
+{
+ Verb *v;
+ int i, ret;
+
+lock:
+ if(verb <= 0 || verb >= 65536){
+ ret = -1;
+ goto unlock;
+ }
+ if(!func)
+ func = badfmt;
+
+ if((i = atomic·load(&formatter.len))==MaxFmt)
+ return -1;
+
+ v = &formatter.verb[i];
+ v->c = verb;
+ v->fmt = func;
+
+ atomic·store(&formatter.len, i+1);
+ ret = 0;
+unlock:
+ return ret;
+}