aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Noll <nbnoll@eml.cc>2021-11-11 14:49:35 -0800
committerNicholas Noll <nbnoll@eml.cc>2021-11-11 14:49:35 -0800
commitc65794b50b1bc729e7a4e940b76a973afa3030b9 (patch)
treebc435fea0f3179ac9a4b8de9fdaf993dade9e4ff
parentcc7d219e080263da9813e570436bfd34ab8b1bff (diff)
feat: libfmt prototype added from plan9
-rw-r--r--include/libfmt.h50
-rw-r--r--sys/libfmt/buffer.c60
-rw-r--r--sys/libfmt/do.c627
-rw-r--r--sys/libfmt/esprint.c14
-rw-r--r--sys/libfmt/float.c1079
-rw-r--r--sys/libfmt/fprint.c14
-rw-r--r--sys/libfmt/internal.h2
-rw-r--r--sys/libfmt/locale.c16
-rw-r--r--sys/libfmt/nsprint.c14
-rw-r--r--sys/libfmt/open.c34
-rw-r--r--sys/libfmt/print.c13
-rw-r--r--sys/libfmt/rules.mk36
-rw-r--r--sys/libfmt/sprint.c19
-rw-r--r--sys/libfmt/test.c73
-rw-r--r--sys/libfmt/vesprint.c26
-rw-r--r--sys/libfmt/vfprint.c19
-rw-r--r--sys/libfmt/vnsprint.c26
-rw-r--r--sys/libfmt/vprint.c19
-rw-r--r--sys/libfmt/vwrite.c26
-rw-r--r--sys/libfmt/write.c22
-rw-r--r--sys/rules.mk3
21 files changed, 2131 insertions, 61 deletions
diff --git a/include/libfmt.h b/include/libfmt.h
index 17effc6..7ba40a0 100644
--- a/include/libfmt.h
+++ b/include/libfmt.h
@@ -9,13 +9,21 @@ struct fmt·State
char *cur;
char *end;
} buffer;
- int (*flush)(fmt·State *);
int n;
+
va_list args;
rune verb;
ulong flag;
int width;
int prec;
+ char *thousands, *groups, *decimal;
+
+ void *file;
+ int (*flush)(fmt·State *);
+ struct {
+ void *heap;
+ mem·Reallocator mem;
+ };
};
#define iota(x) (1 << (x))
@@ -41,22 +49,24 @@ enum
#undef iota
/* normal printing interface */
-int fmt·print(char *fmt, ...);
-int fmt·fprint(int fd, char *fmt, ...);
-
-int fmt·sprint(char *buf, char *fmt, ...);
-int fmt·nsprint(int len, char *buf, char *fmt, ...);
-int fmt·esprint(char *buf, char *end, char *fmt, ...);
-
-int fmt·vprint(char *fmt, va_list args);
-int fmt·vfprint(int fd, char *fmt, va_list args);
-int fmt·vwrite(char *buf, char *fmt, va_list args);
-int fmt·vnwrite(int len, char *buf, char *fmt, va_list args);
-int fmt·vewrite(char *buf, char *end, char *fmt, va_list args);
-
-/* low-level interface: custom printing verbs */
-int fmt·do(fmt·State *, char *fmt);
-int fmt·put(fmt·State *, char *fmt, ...);
-int fmt·vput(fmt·State *, char *fmt, va_list args);
-int fmt·install(fmt·State *, int (*put)(fmt·State*));
-int fmt·flush(fmt·State *);
+int fmt·print(char *fmt, ...);
+int fmt·fprint(int fd, char *fmt, ...);
+
+int fmt·sprint(char *buf, char *fmt, ...);
+int fmt·nsprint(int len, char *buf, char *fmt, ...);
+char *fmt·esprint(char *buf, char *end, char *fmt, ...);
+
+int fmt·vprint(char *fmt, va_list args);
+int fmt·vfprint(int fd, char *fmt, va_list args);
+int fmt·vnsprint(int len, char *buf, char *fmt, va_list args);
+char *fmt·vesprint(char *buf, char *end, char *fmt, va_list args);
+
+/* low-level interface: used for custom printing verbs */
+int fmt·open(int fd, int len, char *buf, fmt·State *); // creates a buffer on a file
+int fmt·make(mem·Reallocator mem, void *heap, fmt·State *); // creates an in-memory buffer
+void fmt·free(fmt·State *); // releases an in-memory buffer
+
+int fmt·do(fmt·State *, char *fmt);
+int fmt·write(fmt·State *, char *fmt, ...);
+int fmt·vwrite(fmt·State *, char *fmt, va_list args);
+int fmt·install(int c, int (*format)(fmt·State *));
diff --git a/sys/libfmt/buffer.c b/sys/libfmt/buffer.c
new file mode 100644
index 0000000..0099e72
--- /dev/null
+++ b/sys/libfmt/buffer.c
@@ -0,0 +1,60 @@
+#include "internal.h"
+
+static int
+flush(fmt·State *io)
+{
+ int n;
+ char *s;
+
+ void *heap = io->heap;
+ mem·Reallocator mem = io->mem;
+
+ if(!io->buffer.beg)
+ return 0;
+
+ n = 2*(uintptr)io->file;
+ s = io->buffer.beg;
+
+ io->buffer.beg = mem.realloc(heap, io->buffer.beg, n, 1);
+ if(!io->buffer.beg){
+ io->file = io->buffer.cur = io->buffer.end = nil;
+ mem.free(heap, s);
+ return 0;
+ }
+ io->file = (void*)(uintptr)n;
+ io->buffer.cur = io->buffer.beg + (io->buffer.cur - s);
+ io->buffer.end = io->buffer.beg + n - 1;
+
+ return 1;
+}
+
+int
+fmt·make(mem·Reallocator mem, void *heap, fmt·State *io)
+{
+ int n;
+
+ memset(io, 0, sizeof(*io));
+
+ n = 32;
+ io->buffer.beg = io->buffer.cur = mem.alloc(heap, n, 1);
+ if(!io->buffer.beg)
+ return -1;
+ io->buffer.end = io->buffer.beg + n - 1;
+
+ io->flush = flush;
+ io->file = (void*)(uintptr)n;
+ io->n = 0;
+
+ fmt·setlocale(io, nil, nil, nil);
+ return 0;
+}
+
+void
+fmt·free(fmt·State *io)
+{
+ void *heap = io->heap;
+ mem·Reallocator mem = io->mem;
+
+ mem.free(heap, io->buffer.beg);
+ io->buffer.beg = io->buffer.cur = io->buffer.end = nil;
+}
diff --git a/sys/libfmt/do.c b/sys/libfmt/do.c
index 3b8ff5a..0a930b0 100644
--- a/sys/libfmt/do.c
+++ b/sys/libfmt/do.c
@@ -6,45 +6,68 @@
#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(27),
+ ATOMIC_VAR_INIT(30),
{
- {' ', ·fmtflag},
- {'#', ·fmtflag},
- {'%', ·fmtpercent},
- {'\'',·fmtflag},
- {'+', ·fmtflag},
- {',', ·fmtflag},
- {'-', ·fmtflag},
- {'C', ·fmtrune},
- {'E', ·fmtfloat},
- {'G', ·fmtfloat},
- {'S', ·fmtutf8},
- {'X', ·fmtint},
- {'b', ·fmtint},
- {'c', ·fmtchar},
- {'d', ·fmtint},
- {'e', ·fmtfloat},
- {'f', ·fmtfloat},
- {'g', ·fmtfloat},
- {'h', ·fmtflag},
- {'l', ·fmtflag},
- {'n', ·fmtcount},
- {'o', ·fmtint},
- {'p', ·fmtint},
- {'r', ·fmterr},
- {'s', ·fmtstr},
- {'u', ·fmtflag},
- {'u', ·fmtint},
- {'x', ·fmtint},
+ {' ', 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)
{
@@ -55,7 +78,7 @@ format(int c)
return v->fmt;
}
- return ·fmterr;
+ return badfmt;
}
static char *
@@ -81,6 +104,7 @@ dispatch(fmt·State *io, char *fmt)
*/
for(;;){
fmt += utf8·decode(fmt, &r);
+
io->verb = r;
switch(r){
case 0:
@@ -115,7 +139,7 @@ dispatch(fmt·State *io, char *fmt)
i = va_arg(io->args, int);
if(i < 0){
if(io->flag&fmt·Prec){
- io->flag ^= fmt·Prec;
+ io->flag = ~fmt·Prec;
io->prec = 0;
}
i = -i;
@@ -123,6 +147,7 @@ dispatch(fmt·State *io, char *fmt)
}
goto number;
}
+
n = format(r)(io);
if(n < 0)
return nil;
@@ -143,6 +168,500 @@ flush(fmt·State *io, char *b, int len)
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)
{
@@ -153,11 +672,10 @@ fmt·do(fmt·State *io, char *fmt)
for(;;){
b = io->buffer.cur;
e = io->buffer.end;
-
while((c = *(uchar *)fmt) && c != '%'){
if(utf8·onebyte(c)){
- if(b + 1 > e){
- if(!(b=flush(io, b, sizeof(*b))))
+ if(b >= e){
+ if(!(b=flush(io, b, 1)))
return -1;
e = io->buffer.end;
}
@@ -165,22 +683,49 @@ fmt·do(fmt·State *io, char *fmt)
}else{
n = utf8·decode(fmt, &r);
if(b + n > e){
- if(!(b=flush(io, b, sizeof(*b))))
+ 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=='\0') /* we hit our nul terminator */
- return io->n = n;
- io->buffer.end = e;
}
+ 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;
+}
diff --git a/sys/libfmt/esprint.c b/sys/libfmt/esprint.c
new file mode 100644
index 0000000..6d97340
--- /dev/null
+++ b/sys/libfmt/esprint.c
@@ -0,0 +1,14 @@
+#include "internal.h"
+
+char *
+fmt·esprint(char *buf, char *end, char *fmt, ...)
+{
+ char *p;
+ va_list args;
+
+ va_start(args, fmt);
+ p = fmt·vesprint(buf, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
diff --git a/sys/libfmt/float.c b/sys/libfmt/float.c
new file mode 100644
index 0000000..f3b9d56
--- /dev/null
+++ b/sys/libfmt/float.c
@@ -0,0 +1,1079 @@
+#include "internal.h"
+
+#define FDIGIT 30
+#define FDEFLT 6
+#define NSIGNIF 17
+
+static uvlong uvnan = ((uvlong)0x7FF00000<<32)|0x00000001;
+static uvlong uvinf = ((uvlong)0x7FF00000<<32)|0x00000000;
+static uvlong uvneginf = ((uvlong)0xFFF00000<<32)|0x00000000;
+
+static char *special[] = { "NaN", "NaN", "+Inf", "+Inf", "-Inf", "-Inf" };
+
+static int
+isNaN(double val)
+{
+ union{
+ uvlong i;
+ double f;
+ }x;
+
+ x.f = val;
+ return (x.i&uvinf) == uvinf && (x.i&~uvneginf) != 0;
+}
+
+static double
+NaN(void)
+{
+ union{
+ uvlong i;
+ double f;
+ }x;
+ x.i = uvnan;
+ return x.f;
+}
+
+static int
+isInf(double val, int sign)
+{
+ union{
+ uvlong i;
+ double f;
+ }x;
+
+ x.f = val;
+ if(sign == 0)
+ return x.i == uvinf || x.i == uvneginf;
+ else if(sign == 1)
+ return x.i == uvinf;
+ else
+ return x.i == uvneginf;
+}
+
+static double pows10[] =
+{
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
+ 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
+ 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
+ 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39,
+ 1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49,
+ 1e50, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59,
+ 1e60, 1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69,
+ 1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79,
+ 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, 1e89,
+ 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, 1e98, 1e99,
+ 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109,
+ 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119,
+ 1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129,
+ 1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139,
+ 1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149,
+ 1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159,
+};
+
+static double
+fpow10(int n)
+{
+ double d;
+ int neg;
+
+ neg = 0;
+ if(n < 0){
+ neg = 1;
+ n = -n;
+ }
+
+ if(n<arrlen(pows10))
+ d = pows10[n];
+ else{
+ d = pows10[arrlen(pows10)-1];
+ for(;;){
+ n -= arrlen(pows10)- 1;
+ if(n < arrlen(pows10)){
+ d *= pows10[n];
+ break;
+ }
+ d *= pows10[arrlen(pows10)- 1];
+ }
+ }
+ if(neg)
+ return 1./d;
+ return d;
+}
+
+static int
+add1(char *a, int n)
+{
+ int c;
+ char *b;
+
+ if(n < 0 || n > NSIGNIF)
+ return 0;
+
+ for(b = a+n-1; b >= a; b--){
+ c = *b + 1;
+ if(c <= '9'){
+ *b = c;
+ return 0;
+ }
+ *b = '0';
+ }
+ /*
+ * need to overflow adding digit.
+ * shift number down and insert 1 at beginning.
+ * decimal is known to be 0s or we wouldn't
+ * have gotten this far. (e.g., 99999+1 => 00000)
+ */
+ a[0] = '1';
+ return 1;
+}
+
+static int
+sub1(char *a, int n)
+{
+ int c;
+ char *b;
+
+ if(n < 0 || n > NSIGNIF)
+ return 0;
+ for(b = a+n-1; b >= a; b--){
+ c = *b - 1;
+ if(c >= '0'){
+ if(c == '0' && b == a){
+ /*
+ * just zeroed the top digit; shift everyone up.
+ * decimal is known to be 9s or we wouldn't
+ * have gotten this far. (e.g., 10000-1 => 09999)
+ */
+ *b = '9';
+ return 1;
+ }
+ *b = c;
+ return 0;
+ }
+ *b = '9';
+ }
+ /*
+ * can't get here. the number a is always normalized
+ * so that it has a nonzero first digit.
+ */
+ abort();
+}
+
+// -----------------------------------------------------------------------
+// strtod
+
+#define Nbits 28
+#define Nmant 53
+#define Prec ((Nmant+Nbits+1)/Nbits)
+
+#define Sigbit (1<<(Prec*Nbits-Nmant)) /* first significant bit of Prec-th word */
+#define Ndig 1500
+#define One (ulong)(1<<Nbits)
+#define Half (ulong)(One>>1)
+#define Maxe 310
+
+#define Fsign (1<<0) /* found - */
+#define Fesign (1<<1) /* found e- */
+#define Fdpoint (1<<2) /* found . */
+
+#define S0 0 /* _ _S0 +S1 #S2 .S3 */
+#define S1 1 /* _+ #S2 .S3 */
+#define S2 2 /* _+# #S2 .S4 eS5 */
+#define S3 3 /* _+. #S4 */
+#define S4 4 /* _+#.# #S4 eS5 */
+#define S5 5 /* _+#.#e +S6 #S7 */
+#define S6 6 /* _+#.#e+ #S7 */
+#define S7 7 /* _+#.#e+# #S7 */
+
+typedef struct Tab Tab;
+struct Tab
+{
+ int bp;
+ int siz;
+ char *cmp;
+};
+
+static ulong
+umuldiv(ulong a, ulong b, ulong c)
+{
+ double d;
+
+ d = ((double)a * (double)b) / (double)c;
+ if(d >= 4294967295.)
+ d = 4294967295.;
+ return (ulong)d;
+}
+
+static void
+frnorm(ulong *f)
+{
+ int i, c;
+
+ c = 0;
+ for(i=Prec-1; i>0; i--) {
+ f[i] += c;
+ c = f[i] >> Nbits;
+ f[i] &= One-1;
+ }
+ f[0] += c;
+}
+
+static int
+fpcmp(char *a, ulong* f)
+{
+ ulong tf[Prec];
+ int i, d, c;
+
+ for(i=0; i<Prec; i++)
+ tf[i] = f[i];
+
+ for(;;) {
+ /* tf *= 10 */
+ for(i=0; i<Prec; i++)
+ tf[i] = tf[i]*10;
+ frnorm(tf);
+ d = (tf[0] >> Nbits) + '0';
+ tf[0] &= One-1;
+
+ /* compare next digit */
+ c = *a;
+ if(c == 0) {
+ if('0' < d)
+ return -1;
+ if(tf[0] != 0)
+ goto cont;
+ for(i=1; i<Prec; i++)
+ if(tf[i] != 0)
+ goto cont;
+ return 0;
+ }
+ if(c > d)
+ return +1;
+ if(c < d)
+ return -1;
+ a++;
+ cont:;
+}
+}
+
+static void
+divby(char *a, int *na, int b)
+{
+ int n, c;
+ char *p;
+
+ p = a;
+ n = 0;
+ while(n>>b == 0){
+ c = *a++;
+ if(c == 0) {
+ while(n) {
+ c = n*10;
+ if(c>>b)
+ break;
+ n = c;
+ }
+ goto xx;
+ }
+ n = n*10 + c-'0';
+ (*na)--;
+ }
+ for(;;){
+ c = n>>b;
+ n -= c<<b;
+ *p++ = c + '0';
+ c = *a++;
+ if(c == 0)
+ break;
+ n = n*10 + c-'0';
+ }
+ (*na)++;
+ xx:
+ while(n){
+ n = n*10;
+ c = n>>b;
+ n -= c<<b;
+ *p++ = c + '0';
+ (*na)++;
+ }
+ *p = 0;
+}
+
+static Tab tab1[] =
+{
+ 1, 0, "",
+ 3, 1, "7",
+ 6, 2, "63",
+ 9, 3, "511",
+ 13, 4, "8191",
+ 16, 5, "65535",
+ 19, 6, "524287",
+ 23, 7, "8388607",
+ 26, 8, "67108863",
+ 27, 9, "134217727",
+};
+
+static void
+divascii(char *a, int *na, int *dp, int *bp)
+{
+ int b, d;
+ Tab *t;
+
+ d = *dp;
+ if(d >= (int)(arrlen(tab1)))
+ d = (int)(arrlen(tab1))-1;
+ t = tab1 + d;
+ b = t->bp;
+ if(memcmp(a, t->cmp, t->siz) > 0)
+ d--;
+ *dp -= d;
+ *bp += b;
+ divby(a, na, b);
+}
+
+static void
+mulby(char *a, char *p, char *q, int b)
+{
+ int n, c;
+
+ n = 0;
+ *p = 0;
+ for(;;) {
+ q--;
+ if(q < a)
+ break;
+ c = *q - '0';
+ c = (c<<b) + n;
+ n = c/10;
+ c -= n*10;
+ p--;
+ *p = c + '0';
+ }
+ while(n) {
+ c = n;
+ n = c/10;
+ c -= n*10;
+ p--;
+ *p = c + '0';
+ }
+}
+
+static Tab tab2[] =
+{
+ 1, 1, "", /* dp = 0-0 */
+ 3, 3, "125",
+ 6, 5, "15625",
+ 9, 7, "1953125",
+ 13, 10, "1220703125",
+ 16, 12, "152587890625",
+ 19, 14, "19073486328125",
+ 23, 17, "11920928955078125",
+ 26, 19, "1490116119384765625",
+ 27, 19, "7450580596923828125", /* dp 8-9 */
+};
+
+static void
+mulascii(char *a, int *na, int *dp, int *bp)
+{
+ char *p;
+ int d, b;
+ Tab *t;
+
+ d = -*dp;
+ if(d >= (int)(arrlen(tab2)))
+ d = (int)(arrlen(tab2))-1;
+ t = tab2 + d;
+ b = t->bp;
+ if(memcmp(a, t->cmp, t->siz) < 0)
+ d--;
+ p = a + *na;
+ *bp -= b;
+ *dp += d;
+ *na += d;
+ mulby(a, p+d, p, b);
+}
+
+static int
+cmp(char *a, char *b)
+{
+ int c1, c2;
+
+ while((c1 = *b++) != '\0') {
+ c2 = *a++;
+ if(isupper(c2))
+ c2 = tolower(c2);
+ if(c1 != c2)
+ return 1;
+ }
+ return 0;
+}
+
+double
+fmtstrtod(char *as, char **aas)
+{
+ int na, ex, dp, bp, c, i, flag, state;
+ ulong low[Prec], hig[Prec], mid[Prec];
+ double d;
+ char *s, a[Ndig];
+
+ flag = 0; /* Fsign, Fesign, Fdpoint */
+ na = 0; /* number of digits of a[] */
+ dp = 0; /* na of decimal point */
+ ex = 0; /* exonent */
+
+ state = S0;
+ for(s=as;;s++){
+ c = *s;
+ if('0' <= c && c <= '9'){
+ switch(state){
+ case S0: case S1: case S2:
+ state = S2;
+ break;
+ case S3: case S4:
+ state = S4;
+ break;
+ case S5: case S6: case S7:
+ state = S7;
+ ex = ex*10 + (c-'0');
+ continue;
+ }
+
+ if(na == 0 && c == '0'){
+ dp--;
+ continue;
+ }
+ if(na < Ndig-50)
+ a[na++] = c;
+ continue;
+ }
+ switch(c){
+ case '\t': case '\n': case '\v': case '\f': case '\r': case ' ':
+ if(state == S0)
+ continue;
+ break;
+ case '-':
+ if(state == S0)
+ flag |= Fsign;
+ else
+ flag |= Fesign;
+ case '+':
+ if(state == S0)
+ state = S1;
+ else
+ if(state == S5)
+ state = S6;
+ else
+ break; /* syntax */
+ continue;
+ case '.':
+ flag |= Fdpoint;
+ dp = na;
+ if(state == S0 || state == S1){
+ state = S3;
+ continue;
+ }
+ if(state == S2){
+ state = S4;
+ continue;
+ }
+ break;
+ case 'e': case 'E':
+ if(state == S2 || state == S4){
+ state = S5;
+ continue;
+ }
+ break;
+ }
+ break;
+ }
+
+ /* clean up return char-pointer */
+ switch(state) {
+ case S0:
+ if(cmp(s, "nan") == 0){
+ if(aas != nil)
+ *aas = s+3;
+ goto retnan;
+ }
+ case S1:
+ if(cmp(s, "infinity") == 0){
+ if(aas != nil)
+ *aas = s+8;
+ goto retinf;
+ }
+ if(cmp(s, "inf") == 0){
+ if(aas != nil)
+ *aas = s+3;
+ goto retinf;
+ }
+ case S3:
+ if(aas != nil)
+ *aas = as;
+ goto ret0; /* no digits found */
+ case S6:
+ s--; /* back over +- */
+ case S5:
+ s--; /* back over e */
+ break;
+ }
+ if(aas != nil)
+ *aas = s;
+
+ if(flag & Fdpoint)
+ while(na > 0 && a[na-1] == '0')
+ na--;
+ if(na == 0)
+ goto ret0; /* zero */
+ a[na] = 0;
+ if(!(flag & Fdpoint))
+ dp = na;
+ if(flag & Fesign)
+ ex = -ex;
+ dp += ex;
+ if(dp < -Maxe){
+ errno = ERANGE;
+ goto ret0; /* underflow by exp */
+ } else
+ if(dp > +Maxe)
+ goto retinf; /* overflow by exp */
+
+ /*
+ * normalize the decimal ascii number
+ * to range .[5-9][0-9]* e0
+ */
+ bp = 0; /* binary exponent */
+ while(dp > 0)
+ divascii(a, &na, &dp, &bp);
+ while(dp < 0 || a[0] < '5')
+ mulascii(a, &na, &dp, &bp);
+
+ /* close approx by naive conversion */
+ mid[0] = 0;
+ mid[1] = 1;
+ for(i=0; (c=a[i]) != '\0'; i++) {
+ mid[0] = mid[0]*10 + (c-'0');
+ mid[1] = mid[1]*10;
+ if(i >= 8)
+ break;
+ }
+ low[0] = umuldiv(mid[0], One, mid[1]);
+ hig[0] = umuldiv(mid[0]+1, One, mid[1]);
+ for(i=1; i<Prec; i++) {
+ low[i] = 0;
+ hig[i] = One-1;
+ }
+
+ /* binary search for closest mantissa */
+ for(;;) {
+ /* mid = (hig + low) / 2 */
+ c = 0;
+ for(i=0; i<Prec; i++) {
+ mid[i] = hig[i] + low[i];
+ if(c)
+ mid[i] += One;
+ c = mid[i] & 1;
+ mid[i] >>= 1;
+ }
+ frnorm(mid);
+
+ /* compare */
+ c = fpcmp(a, mid);
+ if(c > 0) {
+ c = 1;
+ for(i=0; i<Prec; i++)
+ if(low[i] != mid[i]) {
+ c = 0;
+ low[i] = mid[i];
+ }
+ if(c)
+ break; /* between mid and hig */
+ continue;
+ }
+ if(c < 0) {
+ for(i=0; i<Prec; i++)
+ hig[i] = mid[i];
+ continue;
+ }
+
+ /* only hard part is if even/odd roundings wants to go up */
+ c = mid[Prec-1] & (Sigbit-1);
+ if(c == Sigbit/2 && (mid[Prec-1]&Sigbit) == 0)
+ mid[Prec-1] -= c;
+ break; /* exactly mid */
+ }
+
+ /* normal rounding applies */
+ c = mid[Prec-1] & (Sigbit-1);
+ mid[Prec-1] -= c;
+ if(c >= Sigbit/2) {
+ mid[Prec-1] += Sigbit;
+ frnorm(mid);
+ }
+ goto out;
+
+ret0:
+ return 0;
+
+retnan:
+ return NaN();
+
+retinf:
+ /* Unix strtod requires these. Plan 9 would return Inf(0) or Inf(-1). */
+ errno = ERANGE;
+ if(flag & Fsign)
+ return -HUGE_VAL;
+ return HUGE_VAL;
+
+out:
+ d = 0;
+ for(i=0; i<Prec; i++)
+ d = d*One + mid[i];
+ if(flag & Fsign)
+ d = -d;
+ d = ldexp(d, bp - Prec*Nbits);
+ if(d == 0) /* underflow */
+ errno = ERANGE;
+
+ return d;
+}
+
+#undef Nbits
+#undef Nmant
+#undef Prec
+
+#undef Sigbit
+#undef Ndig
+#undef One
+#undef Half
+#undef Maxe
+
+#undef Fsign
+#undef Fesign
+#undef Fdpoint
+
+#undef S0
+#undef S1
+#undef S2
+#undef S3
+#undef S4
+#undef S5
+#undef S6
+#undef S7
+
+static void
+fmtexp(char *p, int e, int ucase)
+{
+ int i;
+ char se[9];
+
+ *p++ = ucase ? 'E' : 'e';
+ if(e < 0){
+ *p++ = '-';
+ e = -e;
+ }else
+ *p++ = '+';
+
+ i = 0;
+ while(e){
+ se[i++] = e % 10 + '0';
+ e /= 10;
+ }
+
+ while(i < 2)
+ se[i++] = '0';
+ while(i > 0)
+ *p++ = se[--i];
+
+ *p++ = '\0';
+}
+
+/*
+ * compute decimal integer m, exp such that:
+ * f = m*10^exp
+ * m is as short as possible with losing exactness
+ * assumes special cases (NaN, +Inf, -Inf) have been handled.
+ */
+static void
+dtoa(double f, char *s, int *exp, int *neg, int *len)
+{
+ int c, d, e2, e, ee, i, ndigit, oerrno;
+ char buf[NSIGNIF+10];
+ double g;
+
+ oerrno = errno;
+
+ *neg = 0;
+ if(f < 0){
+ f = -f;
+ *neg = 1;
+ }
+
+ if(f == 0){
+ *exp = 0;
+ s[0] = '0';
+ s[1] = 0;
+ *len = 1;
+ return;
+ }
+
+ frexp(f, &e2);
+ e = (int)(e2 * .301029995664);
+ g = f * fpow10(-e);
+ while(g < 1) {
+ e--;
+ g = f * fpow10(-e);
+ }
+ while(g >= 10){
+ e++;
+ g = f * fpow10(-e);
+ }
+
+ /* convert nsignif digits as a first approximation */
+ for(i=0; i<NSIGNIF; i++){
+ d = (int)g;
+ s[i] = d+'0';
+ g = (g-d)*10;
+ }
+ s[i] = 0;
+
+ e -= NSIGNIF-1;
+ fmtexp(s+NSIGNIF, e, 0);
+
+ for(i=0; i<10; i++) {
+ g=fmtstrtod(s, nil);
+ if(f > g) {
+ if(add1(s, NSIGNIF)){
+ /* gained a digit */
+ e--;
+ fmtexp(s+NSIGNIF, e, 0);
+ }
+ continue;
+ }
+ if(f < g){
+ if(sub1(s, NSIGNIF)){
+ /* lost a digit */
+ e++;
+ fmtexp(s+NSIGNIF, e, 0);
+ }
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * bump last few digits down to 0 as we can.
+ */
+ for(i=NSIGNIF-1; i>=NSIGNIF-3; i--){
+ c = s[i];
+ if(c != '0'){
+ s[i] = '0';
+ g=fmtstrtod(s, nil);
+ if(g != f){
+ s[i] = c;
+ break;
+ }
+ }
+ }
+
+ /*
+ * remove trailing zeros.
+ */
+ ndigit = NSIGNIF;
+ while(ndigit > 1 && s[ndigit-1] == '0'){
+ e++;
+ --ndigit;
+ }
+ s[ndigit] = 0;
+ *exp = e;
+ *len = ndigit;
+
+ errno = oerrno;
+}
+
+
+static int
+fmtfloat(fmt·State *io)
+{
+ char buf[NSIGNIF+10], *dot, *digits, *p, *end, suf[10], *cur;
+ double val;
+ int c, verb, ndot, e, exp, f, ndigits, neg, newndigits;
+ int npad, pt, prec, realverb, sign, nsuf, ucase, n, z1, z2;
+
+ if(io->flag&fmt·Long)
+ val = va_arg(io->args, long double);
+ else
+ val = va_arg(io->args, double);
+
+ /* extract formatting flags */
+ f = io->flag;
+ io->flag = 0;
+ prec = FDEFLT;
+ if(f & fmt·Prec)
+ prec = io->prec;
+
+ verb = io->verb;
+ ucase = 0;
+ switch(verb) {
+ case 'A':
+ case 'E':
+ case 'F':
+ case 'G':
+ verb += 'a'-'A';
+ ucase = 1;
+ break;
+ }
+
+ /* pick off special numbers. */
+ if(isNaN(val)) {
+ end = special[0+ucase];
+ special:
+ io->flag = f & (fmt·Width|fmt·Left);
+ return copy(io, end, strlen(end), strlen(end));
+ }
+ if(isInf(val, 1)) {
+ end = special[2+ucase];
+ goto special;
+ }
+ if(isInf(val, -1)) {
+ end = special[4+ucase];
+ goto special;
+ }
+
+ /* get exact representation. */
+ digits = buf;
+ dtoa(val, digits, &exp, &neg, &ndigits);
+
+ /* get locale's decimal point. */
+ dot = io->decimal;
+ if(dot == nil)
+ dot = ".";
+ ndot = utf8·len(dot);
+
+ /*
+ * now the formatting fun begins.
+ * compute parameters for actual fmt:
+ *
+ * pad: number of spaces to insert before/after field.
+ * z1: number of zeros to insert before digits
+ * z2: number of zeros to insert after digits
+ * point: number of digits to print before decimal point
+ * ndigits: number of digits to use from digits[]
+ * suf: trailing suffix, like "e-5"
+ */
+ realverb = verb;
+ switch(verb){
+ case 'g':
+ /* convert to at most prec significant digits. (prec=0 means 1) */
+ if(prec == 0)
+ prec = 1;
+ if(ndigits > prec) {
+ if(digits[prec] >= '5' && add1(digits, prec))
+ exp++;
+ exp += ndigits-prec;
+ ndigits = prec;
+ }
+
+ /*
+ * extra rules for %g (implemented below):
+ * trailing zeros removed after decimal unless FmtSharp.
+ * decimal point only if digit follows.
+ */
+
+ /* fall through to %e */
+ default:
+ case 'e':
+ /* one significant digit before decimal, no leading zeros. */
+ pt = 1;
+ z1 = 0;
+
+ /*
+ * decimal point is after ndigits digits right now.
+ * slide to be after first.
+ */
+ e = exp + (ndigits-1);
+
+ /* if this is %g, check exponent and convert prec */
+ if(realverb == 'g') {
+ if(-4 <= e && e < prec)
+ goto casef;
+ prec--; /* one digit before decimal; rest after */
+ }
+
+ /* compute trailing zero padding or truncate digits. */
+ if(1+prec >= ndigits)
+ z2 = 1+prec - ndigits;
+ else {
+ /* truncate digits */
+ assert(realverb != 'g');
+ newndigits = 1+prec;
+ if(digits[newndigits] >= '5' && add1(digits, newndigits)) {
+ /* had 999e4, now have 100e5 */
+ e++;
+ }
+ ndigits = newndigits;
+ z2 = 0;
+ }
+ fmtexp(suf, e, ucase);
+ nsuf = strlen(suf);
+ break;
+
+ casef:
+ case 'f':
+ /* determine where digits go with respect to decimal point */
+ if(ndigits+exp > 0) {
+ pt = ndigits+exp;
+ z1 = 0;
+ } else {
+ pt = 1;
+ z1 = 1 + -(ndigits+exp);
+ }
+
+ /*
+ * %g specifies prec = number of significant digits
+ * convert to number of digits after decimal point
+ */
+ if(realverb == 'g')
+ prec += z1 - pt;
+
+ /* compute trailing zero padding or truncate digits. */
+ if(pt+prec >= z1+ndigits)
+ z2 = pt+prec - (z1+ndigits);
+ else{
+ /* truncate digits */
+ assert(realverb != 'g');
+ newndigits = pt+prec - z1;
+ if(newndigits < 0){
+ z1 += newndigits;
+ newndigits = 0;
+ }else if(newndigits == 0){
+ /* perhaps round up */
+ if(digits[0] >= '5'){
+ digits[0] = '1';
+ newndigits = 1;
+ goto newdigit;
+ }
+ }else if(digits[newndigits] >= '5' && add1(digits, newndigits)){
+ /* digits was 999, is now 100; make it 1000 */
+ digits[newndigits++] = '0';
+ newdigit:
+ /* account for new digit */
+ if(z1) /* 0.099 => 0.100 or 0.99 => 1.00*/
+ z1--;
+ else /* 9.99 => 10.00 */
+ pt++;
+ }
+ z2 = 0;
+ ndigits = newndigits;
+ }
+ nsuf = 0;
+ break;
+ }
+
+ /*
+ * if %g is given without FmtSharp, remove trailing zeros.
+ * must do after truncation, so that e.g. print %.3g 1.001
+ * produces 1, not 1.00. sorry, but them's the rules.
+ */
+ if(realverb == 'g' && !(f & fmt·Sharp)) {
+ if(z1+ndigits+z2 >= pt) {
+ if(z1+ndigits < pt)
+ z2 = pt - (z1+ndigits);
+ else{
+ z2 = 0;
+ while(z1+ndigits > pt && digits[ndigits-1] == '0')
+ ndigits--;
+ }
+ }
+ }
+
+ /*
+ * compute width of all digits and decimal point and suffix if any
+ */
+ n = z1+ndigits+z2;
+ if(n > pt)
+ n += ndot;
+ else if(n == pt){
+ if(f & fmt·Sharp)
+ n += ndot;
+ else
+ pt++; /* do not print any decimal point */
+ }
+ n += nsuf;
+
+ /*
+ * determine sign
+ */
+ sign = 0;
+ if(neg)
+ sign = '-';
+ else if(f & fmt·Sign)
+ sign = '+';
+ else if(f & fmt·Space)
+ sign = ' ';
+ if(sign)
+ n++;
+
+ /* compute padding */
+ npad = 0;
+ if((f & fmt·Width) && io->width > n)
+ npad = io->width - n;
+ if(npad && !(f & fmt·Left) && (f & fmt·Zero)){
+ z1 += npad;
+ pt += npad;
+ npad = 0;
+ }
+
+ /* format the actual field. too bad about doing this twice. */
+ if(npad && !(f & fmt·Left) && pad(io, npad < 0))
+ return -1;
+
+ cur = io->buffer.cur;
+ end = io->buffer.end;
+
+ if(sign){
+ if(cur+1 > end){
+ if(!(cur=flush(io,cur,1)))
+ return -1;
+ end = io->buffer.end;
+ }
+ *cur++ = sign;
+ }
+
+ while(z1>0 || ndigits>0 || z2>0){
+ if(z1 > 0){
+ z1--;
+ c = '0';
+ }else if(ndigits > 0){
+ ndigits--;
+ c = *digits++;
+ }else{
+ z2--;
+ c = '0';
+ }
+
+ if(cur+1 > end){
+ if(!(cur=flush(io,cur,1)))
+ return -1;
+ end = io->buffer.end;
+ }
+ *cur++ = c;
+
+ if(--pt == 0)
+ for(p=dot; *p; p++){
+ if(cur+1 > end){
+ if(!(cur=flush(io,cur,1)))
+ return -1;
+ end = io->buffer.end;
+ }
+ *cur++ = *p;
+ }
+ }
+ io->n += cur - (char*)io->buffer.cur;
+ io->buffer.cur = cur;
+ if(nsuf && copy(io, suf, nsuf, nsuf) < 0)
+ return -1;
+ if(npad && (f & fmt·Left) && pad(io, npad < 0))
+ return -1;
+
+ return 0;
+}
diff --git a/sys/libfmt/fprint.c b/sys/libfmt/fprint.c
new file mode 100644
index 0000000..26343f7
--- /dev/null
+++ b/sys/libfmt/fprint.c
@@ -0,0 +1,14 @@
+#include "internal.h"
+
+int
+fprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = fmt·vfprint(fd, fmt, args);
+ va_end(args);
+
+ return n;
+}
diff --git a/sys/libfmt/internal.h b/sys/libfmt/internal.h
index 56e64c8..725cfff 100644
--- a/sys/libfmt/internal.h
+++ b/sys/libfmt/internal.h
@@ -13,3 +13,5 @@ struct Verb
int c;
Formatter fmt;
};
+
+void fmt·setlocale(fmt·State *io, char *decimal, char *thousands, char *groups);
diff --git a/sys/libfmt/locale.c b/sys/libfmt/locale.c
new file mode 100644
index 0000000..437c61e
--- /dev/null
+++ b/sys/libfmt/locale.c
@@ -0,0 +1,16 @@
+#include "internal.h"
+
+void
+fmt·setlocale(fmt·State *io, char *decimal, char *thousands, char *groups)
+{
+ if(decimal == nil || decimal[0] == '\0')
+ decimal = ".";
+ if(thousands == nil)
+ thousands = ",";
+ if(groups == nil)
+ groups = "\3";
+
+ io->groups = groups;
+ io->decimal = decimal;
+ io->thousands = thousands;
+}
diff --git a/sys/libfmt/nsprint.c b/sys/libfmt/nsprint.c
new file mode 100644
index 0000000..90489e0
--- /dev/null
+++ b/sys/libfmt/nsprint.c
@@ -0,0 +1,14 @@
+#include "internal.h"
+
+int
+fmt·nsprint(int len, char *buf, char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = fmt·vnsprint(len, buf, fmt, args);
+ va_end(args);
+
+ return n;
+}
diff --git a/sys/libfmt/open.c b/sys/libfmt/open.c
new file mode 100644
index 0000000..8aadef5
--- /dev/null
+++ b/sys/libfmt/open.c
@@ -0,0 +1,34 @@
+#include "internal.h"
+
+static int
+flush(fmt·State *io)
+{
+ int n, fd;
+
+ fd = (uintptr)io->file;
+ n = io->buffer.cur - io->buffer.beg;
+ if(n && write(fd, io->buffer.beg, n) != n)
+ return -1;
+
+ io->buffer.cur = io->buffer.beg;
+ return io->n;
+}
+
+int
+fmt·open(int fd, int len, char *buf, fmt·State *io)
+{
+ io->buffer.beg = buf;
+ io->buffer.cur = buf;
+ io->buffer.end = buf+len;
+ io->flush = flush;
+ io->file = (void*)(uintptr)fd;
+ io->flag = 0;
+ io->n = 0;
+ /* no heap needed */
+ io->heap = nil;
+ io->mem = (mem·Reallocator){ 0 };
+
+ fmt·setlocale(io, nil, nil, nil);
+
+ return 0;
+}
diff --git a/sys/libfmt/print.c b/sys/libfmt/print.c
index e69de29..20b8e00 100644
--- a/sys/libfmt/print.c
+++ b/sys/libfmt/print.c
@@ -0,0 +1,13 @@
+#include "internal.h"
+
+int
+fmt·print(char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = fmt·vfprint(1, fmt, args);
+ va_end(args);
+ return n;
+}
diff --git a/sys/libfmt/rules.mk b/sys/libfmt/rules.mk
new file mode 100644
index 0000000..2b1b431
--- /dev/null
+++ b/sys/libfmt/rules.mk
@@ -0,0 +1,36 @@
+include share/push.mk
+
+# Iterate through subdirectory tree
+
+# Local sources
+SRCS_$(d) :=\
+ $(d)/buffer.c\
+ $(d)/do.c\
+ $(d)/esprint.c\
+ $(d)/fprint.c\
+ $(d)/locale.c\
+ $(d)/nsprint.c\
+ $(d)/open.c\
+ $(d)/print.c\
+ $(d)/sprint.c\
+ $(d)/vesprint.c\
+ $(d)/vfprint.c\
+ $(d)/vnsprint.c\
+ $(d)/vprint.c\
+ $(d)/vwrite.c\
+ $(d)/write.c
+
+LIBS_$(d) := $(d)/libfmt.a
+
+TSTS_$(d) := \
+ $(d)/test.c
+
+include share/paths.mk
+
+$(LIBS_$(d)): $(OBJS_$(d))
+ $(ARCHIVE)
+
+$(UNTS_$(d)): $(TOBJS_$(d)) $(LIBS_$(d)) $(OBJ_DIR)/sys/libutf/libutf.a $(OBJ_DIR)/sys/base/base.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/sys/libfmt/sprint.c b/sys/libfmt/sprint.c
new file mode 100644
index 0000000..f1be6dd
--- /dev/null
+++ b/sys/libfmt/sprint.c
@@ -0,0 +1,19 @@
+#include "internal.h"
+
+int
+fmt·sprint(char *buf, char *fmt, ...)
+{
+ int n;
+ uint len;
+ va_list args;
+
+ len = 1 << 30;
+ if(buf+len < buf)
+ len = -(uintptr)buf-1;
+
+ va_start(args, fmt);
+ n = fmt·vnsprint(len, buf, fmt, args);
+ va_end(args);
+
+ return n;
+}
diff --git a/sys/libfmt/test.c b/sys/libfmt/test.c
new file mode 100644
index 0000000..abe1d64
--- /dev/null
+++ b/sys/libfmt/test.c
@@ -0,0 +1,73 @@
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+#include <libfmt.h>
+
+typedef
+struct Complex
+{
+ double r, i;
+} Complex;
+
+int
+Xfmt(fmt·State *io)
+{
+ Complex c;
+ c = va_arg(io->args, Complex);
+
+ return fmt·write(io, "(real=%g,imag=%g)", c.r, c.i);
+}
+
+int
+main(int argc, char *argv[])
+{
+ fmt·print("basic tests\n");
+ fmt·print("\tx: %x\n", 0x87654321);
+ fmt·print("\tu: %u\n", 0x87654321);
+ fmt·print("\td: %d\n", 0x87654321);
+ fmt·print("\ts: %s\n", "hi there");
+ fmt·print("\tc: %c\n", '!');
+ fmt·print("\tg: %g %g %g\n", 3.14159, 3.14159e10, 3.14159e-10);
+ fmt·print("\te: %e %e %e\n", 3.14159, 3.14159e10, 3.14159e-10);
+ fmt·print("\tf: %f %f %f\n", 3.14159, 3.14159e10, 3.14159e-10);
+ fmt·print("\tsmiley: %C\n", (rune)0x263a);
+ fmt·print("\t%g %.18g\n", 2e25, 2e25);
+ fmt·print("\t%2.18g\n", 1.0);
+ fmt·print("\t%2.18f\n", 1.0);
+ fmt·print("\t%f\n", 3.1415927/4);
+ fmt·print("\t%d\n", 23);
+ fmt·print("\t%i\n", 23);
+ fmt·print("\t%0.10d\n", 12345);
+
+ fmt·print("%%4%%d tests\n");
+ fmt·print("\t%3$d %4$06d %2$d %1$d\n", 444, 333, 111, 222);
+ fmt·print("\t%3$d %4$06d %2$d %1$d\n", 444, 333, 111, 222);
+ fmt·print("\t%3$d %4$*5$06d %2$d %1$d\n", 444, 333, 111, 222, 20);
+ fmt·print("\t%3$hd %4$*5$06d %2$d %1$d\n", 444, 333, (short)111, 222, 20);
+ fmt·print("\t%3$lld %4$*5$06d %2$d %1$d\n", 444, 333, 111LL, 222, 20);
+
+ /* test %'d formats */
+ fmt·print("%%'%%d tests\n");
+ fmt·print("\t%'d %'d %'d\n", 1, 2222, 33333333);
+ fmt·print("\t%'019d\n", 0);
+ fmt·print("\t%08d %08d %08d\n", 1, 2222, 33333333);
+ fmt·print("\t%'08d %'08d %'08d\n", 1, 2222, 33333333);
+ fmt·print("\t%'x %'X %'b\n", 0x11111111, 0xabcd1234, 12345);
+ fmt·print("\t%'lld %'lld %'lld\n", 1LL, 222222222LL, 3333333333333LL);
+ fmt·print("\t%019lld %019lld %019lld\n", 1LL, 222222222LL, 3333333333333LL);
+ fmt·print("\t%'019lld %'019lld %'019lld\n", 1LL, 222222222LL, 3333333333333LL);
+ fmt·print("\t%'020lld %'020lld %'020lld\n", 1LL, 222222222LL, 3333333333333LL);
+ fmt·print("\t%'llx %'llX %'llb\n", 0x111111111111LL, 0xabcd12345678LL, 112342345LL);
+
+ /* test precision */
+ fmt·print("precision tests\n");
+ fmt·print("%020.10d\n", 100);
+
+ /* test install */
+ fmt·install('X', Xfmt);
+ Complex c = { 1.5, -2.3 };
+ fmt·print("x = %X\n", c);
+
+ return 0;
+
+}
diff --git a/sys/libfmt/vesprint.c b/sys/libfmt/vesprint.c
new file mode 100644
index 0000000..18f4dd2
--- /dev/null
+++ b/sys/libfmt/vesprint.c
@@ -0,0 +1,26 @@
+#include "internal.h"
+
+char*
+fmt·vesprint(char *buf, char *end, char *fmt, va_list args)
+{
+ fmt·State io;
+
+ if(end <= buf)
+ return nil;
+
+ io.n = 0;
+ io.buffer.beg = io.buffer.cur = buf;
+ io.buffer.end = end-1;
+ io.flush = nil;
+ io.file = nil;
+
+ va_copy(io.args, args);
+
+ fmt·setlocale(&io, nil, nil, nil);
+ fmt·do(&io, fmt);
+
+ va_end(io.args);
+
+ *(io.buffer.cur) = 0;
+ return io.buffer.cur;
+}
diff --git a/sys/libfmt/vfprint.c b/sys/libfmt/vfprint.c
new file mode 100644
index 0000000..4306ea7
--- /dev/null
+++ b/sys/libfmt/vfprint.c
@@ -0,0 +1,19 @@
+#include "internal.h"
+
+int
+fmt·vfprint(int fd, char *fmt, va_list args)
+{
+ int n;
+ fmt·State io;
+ char buf[256];
+
+ fmt·open(fd, sizeof(buf), buf, &io);
+
+ va_copy(io.args, args);
+ n = fmt·do(&io, fmt);
+ va_end(io.args);
+
+ if(n > 0 && io.flush(&io) < 0)
+ return -1;
+ return n;
+}
diff --git a/sys/libfmt/vnsprint.c b/sys/libfmt/vnsprint.c
new file mode 100644
index 0000000..7ded908
--- /dev/null
+++ b/sys/libfmt/vnsprint.c
@@ -0,0 +1,26 @@
+#include "internal.h"
+
+int
+fmt·vnsprint(int len, char *buf, char *fmt, va_list args)
+{
+ fmt·State io;
+
+ if(len <= 0)
+ return -1;
+
+ io.n = 0;
+ io.buffer.beg = io.buffer.cur = buf;
+ io.buffer.end = buf+len-1;
+ io.flush = nil;
+ io.file = nil;
+
+ va_copy(io.args, args);
+
+ fmt·setlocale(&io, nil, nil, nil);
+ fmt·do(&io, fmt);
+
+ va_end(io.args);
+
+ *(io.buffer.cur) = 0;
+ return io.buffer.cur - io.buffer.beg;
+}
diff --git a/sys/libfmt/vprint.c b/sys/libfmt/vprint.c
new file mode 100644
index 0000000..bb3076b
--- /dev/null
+++ b/sys/libfmt/vprint.c
@@ -0,0 +1,19 @@
+#include "internal.h"
+
+int
+fmt·vprint(char *fmt, va_list args)
+{
+ fmt·State io;
+ int n;
+ char buf[256];
+
+ fmt·open(1, sizeof(buf), buf, &io);
+
+ va_copy(io.args, args);
+ n = fmt·do(&io, fmt);
+ va_end(io.args);
+
+ if(n > 0 && io.flush(&io) < 0)
+ return -1;
+ return n;
+}
diff --git a/sys/libfmt/vwrite.c b/sys/libfmt/vwrite.c
new file mode 100644
index 0000000..cacdef2
--- /dev/null
+++ b/sys/libfmt/vwrite.c
@@ -0,0 +1,26 @@
+#include "internal.h"
+
+int
+fmt·vwrite(fmt·State *io, char *fmt, va_list args)
+{
+ int n;
+ va_list tmp;
+
+ io->flag = io->width = io->prec = 0;
+
+ va_copy(tmp, io->args);
+ va_end(io->args);
+
+ va_copy(io->args,args);
+ n = fmt·do(io, fmt);
+ va_end(io->args);
+
+ va_copy(io->args, tmp);
+ va_end(tmp);
+
+ io->flag = io->width = io->prec = 0;
+
+ if(n >= 0)
+ return 0;
+ return n;
+}
diff --git a/sys/libfmt/write.c b/sys/libfmt/write.c
new file mode 100644
index 0000000..9a77223
--- /dev/null
+++ b/sys/libfmt/write.c
@@ -0,0 +1,22 @@
+#include "internal.h"
+
+int
+fmt·write(fmt·State *io, char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ io->flag = io->width = io->prec = 0;
+
+ va_copy(args, io->args);
+ va_end(io->args);
+
+ va_start(io->args, fmt);
+ n = fmt·do(io, fmt);
+ va_end(io->args);
+
+ io->flag = io->width = io->prec = 0;
+ if(n >= 0)
+ return 0;
+ return n;
+}
diff --git a/sys/rules.mk b/sys/rules.mk
index cefa4a9..6d1dfa5 100644
--- a/sys/rules.mk
+++ b/sys/rules.mk
@@ -11,6 +11,9 @@ include $(DIR)/rules.mk
DIR := $(d)/libutf
include $(DIR)/rules.mk
+DIR := $(d)/libfmt
+include $(DIR)/rules.mk
+
DIR := $(d)/libmath
include $(DIR)/rules.mk