ref: e3682d0bb7437dc3cbb6a6fcf8d82806d98c73da
dir: /lib/std/fmt.myr/
use "alloc" use "consts" use "chartype" use "die" use "extremum" use "fltfmt" use "hashfuncs" use "hasprefix" use "htab" use "intfmt" use "intparse" use "introspect" use "memops" use "option" use "result" use "sldup" use "slpush" use "sleq" use "strbuf" use "strfind" use "striter" use "strsplit" use "syswrap" use "syswrap-ss" use "traits" use "types" use "utf" use "varargs" use "writeall" pkg std = /* write to fd */ const put : (fmt : byte[:], args : ... -> size) const fput : (fd : fd, fmt : byte[:], args : ... -> size) const putv : (fmt : byte[:], ap : valist# -> size) const fputv : (fd : fd, fmt : byte[:], ap : valist# -> size) /* write to buffer */ const fmt : (fmt : byte[:], args : ... -> byte[:]) const fmtv : (fmt : byte[:], ap : valist# -> byte[:]) const bfmt : (buf : byte[:], fmt : byte[:], args : ... -> byte[:]) const bfmtv : (buf : byte[:], fmt : byte[:], ap : valist# -> byte[:]) /* write to strbuf */ const sbfmt : (buf : strbuf#, fmt : byte[:], args : ... -> size) const sbfmtv : (buf : strbuf#, fmt : byte[:], ap : valist# -> size) /* add a formatter function */ const fmtinstall : (ty : byte[:], \ fn : (sb : strbuf#, ap : valist#, opts : (byte[:],byte[:])[:] -> void) \ -> void) $noret const fatal : (fmt : byte[:], args : ... -> void) $noret const fatalv : (fmt : byte[:], ap : valist# -> void) ;; type parsestate = union `Copy `ParamOpt `ParamArg ;; const __init__ = { fmtmap = mkht() } type fmtdesc = struct fn : (sb : strbuf#, ap : valist#, opts : (byte[:],byte[:])[:] -> void) ;; /* same as 'put', but exits the program after printing */ const fatal = {fmt, args var ap ap = vastart(&args) fputv(Err, fmt, &ap) exit(1) } /* same as 'putv', but exits the program after printing */ const fatalv = {fmt, ap fputv(Err, fmt, ap) exit(1) } var fmtmap : htab(byte[:], fmtdesc)# const fmtinstall = {ty, fn match std.htget(fmtmap, ty) | `std.Some _: std.fatal("doubly installed format\n") | `std.None: htput(fmtmap, ty, [.fn=fn]) ;; } const put = {fmt, args var ap ap = vastart(&args) -> fputv(1, fmt, &ap) } const putv = {fmt, ap -> fputv(1, fmt, ap) } const fput = {fd, fmt, args var ap ap = vastart(&args) -> fputv(fd, fmt, &ap) } const fputv = {fd, fmt, ap var sb, s, nw sb = mksb() sbfmtv(sb, fmt, ap) s = sbfin(sb) match writeall(fd, s) | `std.Ok n: nw += n | `std.Err (n, _): nw += n ;; slfree(s) -> nw } const fmt = {fmt, args var ap ap = vastart(&args) -> fmtv(fmt, &ap) } const fmtv = {fmt, ap var sb sb = mksb() sbfmtv(sb, fmt, ap) -> sbfin(sb) } const bfmt = {buf, fmt, args var ap ap = vastart(&args) -> bfmtv(buf, fmt, &ap) } const bfmtv = {buf, fmt, ap var sb sb = mkbufsb(buf) sbfmtv(sb, fmt, ap) -> sbfin(sb) } const sbfmt = {sb, fmt, args var ap ap = vastart(&args) -> sbfmtv(sb, fmt, &ap) } const sbfmtv = {sb, fmt, ap -> size var buf : byte[256], param : (byte[:], byte[:])[8] var state, startp, endp, starta, nbuf var nparam var b, i startp = 0 starta = 0 nparam = 0 nbuf = 0 endp = 0 state = `Copy i = 0 while i != fmt.len b = fmt[i++] match (state, (b : char)) /* raw bytes */ | (`Copy, '{'): if (fmt[i] : char) == '{' b = fmt[i++] sbputb(sb, ('{' : byte)) else state = `ParamOpt nparam = 0 startp = 0 starta = 0 nbuf = 0 ;; | (`Copy, '}'): if i == fmt.len || (fmt[i] : char) != '}' die("unescaped '}'\n") ;; sbputb(sb, ('}' : byte)) i++ | (`Copy, _): sbputb(sb, b) /* {param */ | (`ParamOpt, '='): state = `ParamArg endp = nbuf starta = nbuf | (`ParamOpt, ','): if startp != nbuf param[nparam++] = (buf[startp:nbuf], "") ;; startp = nbuf | (`ParamOpt, '}'): state = `Copy if startp != nbuf param[nparam++] = (buf[startp:nbuf], "") ;; if ap.tc.nelt < 1 die("too few values for fmt\n") ;; fmtval(sb, vatype(ap), ap, param[:nparam]) | (`ParamOpt, '\\'): buf[nbuf++] = fmt[i++] | (`ParamOpt, chr): buf[nbuf++] = b /* {param=arg} */ | (`ParamArg, ','): state = `ParamOpt param[nparam++] = (buf[startp:endp], buf[starta:nbuf]) startp = nbuf endp = nbuf | (`ParamArg, '}'): state = `Copy if startp != nbuf param[nparam++] = (buf[startp:endp], buf[starta:nbuf]) ;; if ap.tc.nelt < 1 die("too few values for fmt\n") ;; fmtval(sb, vatype(ap), ap, param[:nparam]) | (`ParamArg, '\\'): buf[nbuf++] = fmt[i++] | (`ParamArg, chr): buf[nbuf++] = b ;; ;; if ap.tc.nelt != 0 die("too many values for fmt\n") ;; -> sb.len } const fmtval = {sb, ty, ap, params match htget(fmtmap, ty) | `Some f: f.fn(sb, ap, params) | `None: fallbackfmt(sb, params, ty, ap) ;; } const fallbackfmt = {sb, params, tyenc, ap : valist# -> void /* value types */ var subap, subenc, subname var inf, p match typedesc(tyenc) /* shows up in a union with no body */ | `Tynone: /* atomic types */ | `Tyvoid: var val : void = vanext(ap) sbputs(sb, "void") | `Tybool: var val : bool = vanext(ap) if val sbputs(sb ,"true") else sbputs(sb, "false") ;; | `Tychar: var val : char = vanext(ap) sbputc(sb, val) | `Tyint8: var val : int8 = vanext(ap) intfmt(sb, intparams(ap, params), true, (val : uint64), 8) | `Tyint16: var val : int16 = vanext(ap) intfmt(sb, intparams(ap, params), true, (val : uint64), 16) | `Tyint: var val : int = vanext(ap) intfmt(sb, intparams(ap, params), true, (val : uint64), 32) | `Tyint32: var val : int32 = vanext(ap) intfmt(sb, intparams(ap, params), true, (val : uint64), 32) | `Tyint64: var val : int64 = vanext(ap) intfmt(sb, intparams(ap, params), true, (val : uint64), 64) | `Tybyte: var val : byte = vanext(ap) intfmt(sb, intparams(ap, params), false, (val : uint64), 8) | `Tyuint8: var val : uint8 = vanext(ap) intfmt(sb, intparams(ap, params), false, (val : uint64), 8) | `Tyuint16: var val : uint16 = vanext(ap) intfmt(sb, intparams(ap, params), false, (val : uint64), 16) | `Tyuint: var val : uint = vanext(ap) intfmt(sb, intparams(ap, params), false, (val : uint64), 32) | `Tyuint32: var val : uint32 = vanext(ap) intfmt(sb, intparams(ap, params), false, (val : uint64), 32) | `Tyuint64: var val : uint64 = vanext(ap) intfmt(sb, intparams(ap, params), false, (val : uint64), 64) | `Tyflt32: var val : flt32 = vanext(ap) flt32bfmt(sb, fltparams(ap, params), val) | `Tyflt64: var val : flt64 = vanext(ap) flt64bfmt(sb, fltparams(ap, params), val) | `Tyvalist: sbputs(sb, "...") /* compound types */ | `Typtr desc: var val : void# = vanext(ap) sbputs(sb, "0x") intfmt(sb, [.base=16, .padto=2*sizeof(void#), .padfill='0'], false, (val : uint64), 64) | `Tyslice desc: match typedesc(desc) | `Tybyte: var val : byte[:] = vanext(ap) strfmt(sb, val, ap, params) | _: subap = vaenter(ap) fmtslice(sb, subap, params) vabytes(ap) ;; | `Tyfunc tc: var val : intptr[2] = vanext(ap) sbputs(sb, "func{") intfmt(sb, [.base=16, .padto=2*sizeof(void#), .padfill='0'], false, (val[0] : uint64), 0) sbputs(sb, ", ") intfmt(sb, [.base=16, .padto=2*sizeof(void#), .padfill='0'], false, (val[1] : uint64), 0) sbputs(sb, "}") | `Tyarray (sz, desc): subap = vaenter(ap) sbputs(sb, "[") while subap.tc.nelt != 0 fmtval(sb, vatype(&subap), &subap, [][:]) if subap.tc.nelt > 0 sbfmt(sb, ", ") ;; ;; sbputs(sb, "]") vabytes(ap) /* aggregate types */ | `Tytuple tc: subap = vaenter(ap) sbfmt(sb, "(") var extracomma = subap.tc.nelt == 1 while subap.tc.nelt != 0 fmtval(sb, vatype(&subap), &subap, [][:]) if subap.tc.nelt > 0 sbfmt(sb, ", ") ;; ;; if extracomma sbfmt(sb, ",") ;; sbfmt(sb, ")") vabytes(ap) | `Tystruct nc: subap = vaenter(ap) sbfmt(sb, "[") while subap.tc.nelt != 0 (subname, subenc) = ncpeek(&subap.tc) sbfmt(sb, ".{}=", subname) fmtval(sb, vatype(&subap), &subap, [][:]) if subap.tc.nelt > 0 sbfmt(sb, ", ") ;; ;; sbfmt(sb, "]") vabytes(ap) | `Tyunion nc: var tag : int32 inf = typeinfo(tcpeek(&ap.tc)) p = (ap.args : size) p = (p + inf.align - 1) & ~(inf.align - 1) tag = (p : int32#)# subap = vaenterunion(ap, tag) for var i = 0; i < tag; i++ ncnext(&nc) ;; (subname, subenc) = ncnext(&nc) sbfmt(sb, "`{}", subname) match typedesc(subenc) | `Tynone: | _: sbputc(sb, ' ') fmtval(sb, subenc, &subap, [][:]) ;; vabytes(ap) | `Tyname (name, desc): subap = vaenter(ap) fmtval(sb, desc, &subap, params) vabytes(ap) ;; } const fmtslice = {sb, subap, params var join, joined join = ", " joined = false for p : params match p | ("j", j): joined = true join = j | (opt, arg): std.write(2, "fmt: \0") std.write(2, opt) std.write(2, "\0arg: ") std.write(2, arg) std.die("unreacahable") ;; ;; if !joined sbputs(sb, "[") ;; while subap.tc.nelt != 0 fmtval(sb, vatype(&subap), &subap, [][:]) if subap.tc.nelt > 0 sbputs(sb, join) ;; ;; if !joined sbputs(sb, "]") ;; } const fltparams = {ap, params var fp : fltparams fp = [ .mode = MNormal, .cutoff = 0, .scientific = false, .padfill = ' ', .padto = 0, ] for p : params match p | ("e", ""): fp.scientific = true | ("w", wid): if eq(wid, "?") fp.padto = pullint(ap, "fmt: width = ? must be integer") else fp.padto = getint(wid, "fmt: width must be integer") ;; | ("p", pad): fp.padfill = decode(pad) | ("s", sig): fp.mode = MRelative fp.cutoff = getint(sig, "fmt: significant figures must be integer") | (opt, arg): std.write(2, "fmt: ") std.write(2, opt) std.write(2, "arg: ") std.write(2, arg) std.die("\nunreachable\n") ;; ;; iassert(fp.padto >= 0, "pad must be >= 0") -> fp } const intparams = {ap, params var ip : intparams ip = [ .base = 10, .padfill = ' ', .padto = 0 ] for p : params match p | ("b", bas): ip.base = getint(bas, "fmt: base must be integer") | ("x", ""): ip.base = 16 | ("w", wid): if eq(wid, "?") ip.padto = pullint(ap, "fmt: width = ? must be integer") else ip.padto = getint(wid, "fmt: width must be integer") ;; | ("p", pad): ip.padfill = decode(pad) | (opt, arg): std.write(2, "fmt: ") std.write(2, opt) std.write(2, "arg: ") std.write(2, arg) std.die("\nunreachable\n") ;; ;; iassert(ip.padto >= 0, "pad must be >= 0") -> ip } const strfmt = {sb, str, ap, params var w, p, i, raw, esc p = ' ' w = 0 raw = false esc = false for pp : params match pp | ("w", wid): if eq(wid, "?") w = pullint(ap, "fmt: width = ? must be integer") else w = getint(wid, "fmt: width must be integer") ;; | ("p", pad): p = decode(pad) | ("r", ""): raw = true | ("e", ""): esc = true | (opt, arg): std.write(2, "fmt: ") std.write(2, opt) std.write(2, "arg: ") std.write(2, arg) std.die("unreachable\n") ;; ;; iassert(p >= 0, "pad must be >= 0") if raw for b : str if esc sbputs(sb, "\\x") ;; intfmt(sb, [.padto=2, .padfill='0', .base=16], false, (b : uint64), 8) ;; elif esc for b : str if isprint(b) sbputb(sb, b) else match (b : char) | '\n': sbputs(sb, "\\n") | '\r': sbputs(sb, "\\r") | '\t': sbputs(sb, "\\t") | '\b': sbputs(sb, "\\b") | '\"': sbputs(sb, "\\\"") | '\'': sbputs(sb, "\\\'") | '\\': sbputs(sb, "\\\\") | '\0': sbputs(sb, "\\0") | _: sbputs(sb, "\\x") intfmt(sb, [.padto=2, .padfill='0', .base=16], false, (b : uint64), 8) ;; ;; ;; else for i = 0; i < w - strcellwidth(str); i++ sbputc(sb, p) ;; sbputs(sb, str) ;; } const isprint = {b -> b >= (' ' : byte) && b < ('~' : byte) } /* would use std.get(), but that's a dependency loop */ const getint = {s, msg match std.intparse(s) | `Some w: -> w; | `None: die(msg) ;; } const pullint = {ap, msg if ap.tc.nelt < 1 die("expected argument where none was provided") ;; match typedesc(vatype(ap)) | `Tyint8: var val : int8 = vanext(ap) -> val < 0 ? 0 : (val : size) | `Tyint16: var val : int16 = vanext(ap) -> val < 0 ? 0 : (val : size) | `Tyint: var val : int = vanext(ap) -> val < 0 ? 0 : (val : size) | `Tyint32: var val : int32 = vanext(ap) -> val < 0 ? 0 : (val : size) | `Tyint64: var val : int64 = vanext(ap) -> val < 0 ? 0 : (val : size) | `Tyuint8: var val : uint8 = vanext(ap) -> (val : size) | `Tyuint16: var val : uint16 = vanext(ap) -> (val : size) | `Tyuint: var val : uint = vanext(ap) -> (val : size) | `Tyuint32: var val : uint32 = vanext(ap) -> (val : size) | `Tyuint64: var val : uint64 = vanext(ap) -> (val : size) | `Tyname (_, desc): /* This is primarily for handling std.size */ var subap = vaenter(ap) var ret = pullint(&subap, msg) vabytes(ap) /* Pull one element out to keep ap synchronized with subap */ -> ret | _: die(msg) ;; -> 0 }