ref: 1f645d217c2faf9cd08b5f4910ca10479821b434
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 nfmt, nvarargs, nparam var b, i nvarargs = ap.tc.nelt nfmt = 0 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], "") ;; nfmt++ if nfmt > nvarargs 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]) ;; fmtval(sb, vatype(ap), ap, param[:nparam]) nfmt++ | (`ParamArg, '\\'): buf[nbuf++] = fmt[i++] | (`ParamArg, chr): buf[nbuf++] = b ;; ;; if nfmt != nvarargs 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(params), true, (val : uint64), 8) | `Tyint16: var val : int16 = vanext(ap) intfmt(sb, intparams(params), true, (val : uint64), 16) | `Tyint: var val : int = vanext(ap) intfmt(sb, intparams(params), true, (val : uint64), 32) | `Tyint32: var val : int32 = vanext(ap) intfmt(sb, intparams(params), true, (val : uint64), 32) | `Tyint64: var val : int64 = vanext(ap) intfmt(sb, intparams(params), true, (val : uint64), 64) | `Tybyte: var val : byte = vanext(ap) intfmt(sb, intparams(params), false, (val : uint64), 8) | `Tyuint8: var val : uint8 = vanext(ap) intfmt(sb, intparams(params), false, (val : uint64), 8) | `Tyuint16: var val : uint16 = vanext(ap) intfmt(sb, intparams(params), false, (val : uint64), 16) | `Tyuint: var val : uint = vanext(ap) intfmt(sb, intparams(params), false, (val : uint64), 32) | `Tyuint32: var val : uint32 = vanext(ap) intfmt(sb, intparams(params), false, (val : uint64), 32) | `Tyuint64: var val : uint64 = vanext(ap) intfmt(sb, intparams(params), false, (val : uint64), 64) | `Tyflt32: var val : flt32 = vanext(ap) flt32bfmt(sb, fltparams(params), val) | `Tyflt64: var val : flt64 = vanext(ap) flt64bfmt(sb, fltparams(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, 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, "(") for var i = 0; i < subap.tc.nelt; i++ fmtval(sb, vatype(&subap), &subap, [][:]) if subap.tc.nelt == 1 sbfmt(sb, ",") elif i != subap.tc.nelt -1 sbfmt(sb, ", ") ;; ;; sbfmt(sb, ")") vabytes(ap) | `Tystruct nc: subap = vaenter(ap) sbfmt(sb, "[") for var i = 0; i < subap.tc.nelt; i++ (subname, subenc) = ncpeek(&subap.tc) sbfmt(sb, ".{}=", subname) fmtval(sb, vatype(&subap), &subap, [][:]) if subap.tc.nelt == 1 sbfmt(sb, ",") elif i != subap.tc.nelt -1 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 = {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): 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 = {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): 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, params var w, p, i, raw, esc p = ' ' w = 0 raw = false esc = false for pp : params match pp | ("w", wid): 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) ;; }