ref: 52eca667bbd1cf5a929d3f2352c3c1ee8e91625d
dir: /lib/std/fmt.myr/
use "alloc"
use "consts"
use "chartype"
use "die"
use "extremum"
use "fltfmt"
use "hashfuncs"
use "hasprefix"
use "htab"
use "intparse"
use "introspect"
use "memops"
use "option"
use "result"
use "sldup"
use "sleq"
use "slpush"
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
sb = mksb()
sbfmtv(sb, fmt, ap)
s = sbfin(sb)
match writeall(fd, s)
| (n, _):
slfree(s)
-> n
;;
}
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 c
nvarargs = ap.tc.nelt
nfmt = 0
startp = 0
starta = 0
nparam = 0
nbuf = 0
endp = 0
state = `Copy
while fmt.len != 0
(c, fmt) = charstep(fmt)
match (state, c)
/* raw bytes */
| (`Copy, '{'):
if decode(fmt) == '{'
(c, fmt) = charstep(fmt)
sbputc(sb, '{')
else
state = `ParamOpt
nparam = 0
startp = 0
starta = 0
nbuf = 0
;;
| (`Copy, '}'):
if decode(fmt) == '}'
sbputc(sb, '}')
;;
| (`Copy, chr):
sbputc(sb, chr)
/* {param */
| (`ParamOpt, '='):
state = `ParamArg
endp = nbuf
starta = nbuf
| (`ParamOpt, ','):
if startp != nbuf
param[nparam++] = (buf[startp:nbuf], "")
;;
| (`ParamOpt, '}'):
state = `Copy
if startp != nbuf
param[nparam++] = (buf[startp:nbuf], "")
;;
fmtval(sb, vatype(ap), ap, param[:nparam])
nfmt++
| (`ParamOpt, '\\'):
(c, fmt) = charstep(fmt)
nbuf += std.encode(buf[nbuf:], c)
| (`ParamOpt, chr):
nbuf += std.encode(buf[nbuf:], chr)
/* {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, '\\'):
(c, fmt) = charstep(fmt)
nbuf += std.encode(buf[nbuf:], c)
| (`ParamArg, chr):
nbuf += std.encode(buf[nbuf:], chr)
;;
:fmtdone
;;
if nfmt != nvarargs
die("too many params 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 t_val : bool
var b_val : int8, ub_val : uint8
var w_val : int16, uw_val : uint16
var i_val : int32, ui_val : uint32
var l_val : int64, ul_val : uint64
var z_val : size
var p_val : byte#
var f_val : intptr[2]
var c_val : char
var s_val : byte[:]
var f32_val : flt32, f64_val : flt64
var i8 : int8, i16: int16, i32 : int32
var by : byte
var i : int, i64 : int64
var ui8 : int8, ui16: int16, ui32 : int32
var ui : int, ui64 : int64
var subap, subenc, subname
var inf, p
match typedesc(tyenc)
/* shows up in a union with no body */
| `Tynone:
/* atomic types */
| `Tyvoid:
sbputs(sb, "void")
| `Tybool:
t_val = vanext(ap)
if t_val
sbputs(sb ,"true")
else
sbputs(sb, "false")
;;
| `Tychar:
c_val = vanext(ap)
sbputc(sb, c_val)
| `Tyint8:
b_val = vanext(ap)
intfmt(sb, intparams(params), true, b_val)
| `Tyint16:
w_val = vanext(ap)
intfmt(sb, intparams(params), true, w_val)
| `Tyint:
i_val = vanext(ap)
intfmt(sb, intparams(params), true, i_val)
| `Tyint32:
i_val = vanext(ap)
intfmt(sb, intparams(params), true, i_val)
| `Tyint64:
l_val = vanext(ap)
intfmt(sb, intparams(params), true, l_val)
| `Tybyte:
ub_val = vanext(ap)
intfmt(sb, intparams(params), false, ub_val)
| `Tyuint8:
ub_val = vanext(ap)
intfmt(sb, intparams(params), false, ub_val)
| `Tyuint16:
uw_val = vanext(ap)
intfmt(sb, intparams(params), false, uw_val)
| `Tyuint:
ui_val = vanext(ap)
intfmt(sb, intparams(params), false, ui_val)
| `Tyuint32:
ui_val = vanext(ap)
intfmt(sb, intparams(params), false, ui_val)
| `Tyuint64:
ul_val = vanext(ap)
intfmt(sb, intparams(params), false, ul_val)
| `Tyflt32:
f32_val = vanext(ap)
flt32bfmt(sb, f32_val, MNormal, 0)
| `Tyflt64:
f64_val = vanext(ap)
flt64bfmt(sb, f64_val, MNormal, 0)
| `Tyvalist:
sbputs(sb, "...")
/* compound types */
| `Typtr desc:
p_val = vanext(ap)
sbputs(sb, "0x")
intfmt(sb, \
[.base=16, .padto=2*sizeof(void#), .padfill='0'], \
false, (p_val : intptr))
| `Tyslice desc:
match typedesc(desc)
| `Tybyte:
s_val = vanext(ap)
strfmt(sb, s_val, params)
| _:
subap = vaenter(ap)
fmtslice(sb, subap, params)
vabytes(ap)
;;
| `Tyfunc tc:
f_val = vanext(ap)
sbputs(sb, "func{")
intfmt(sb, \
[.base=16, .padto=2*sizeof(void#), .padfill='0'], \
false, (f_val[0] : intptr))
sbputs(sb, ", ")
intfmt(sb, \
[.base=16, .padto=2*sizeof(void#), .padfill='0'], \
false, (f_val[1] : intptr))
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:
inf = typeinfo(tcpeek(&ap.tc))
p = (ap.args : size)
p = (p + inf.align - 1) & ~(inf.align - 1)
i_val = (p : int32#)#
subap = vaenterunion(ap, i_val)
for var i = 0; i < i_val; 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
sbfmt(sb, join)
;;
;;
if !joined
sbputs(sb, "]")
;;
}
type intparams = struct
base : size
padto : size
padfill : char
;;
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
| _: 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)
;;
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)
;;
;;
;;
else
for i = 0; i < w - strcellwidth(str); i++
sbputc(sb, p)
;;
sbputs(sb, str)
;;
}
const isprint = {b
-> b >= (' ' : byte) && b < ('~' : byte)
}
const digitchars = [
'0','1','2','3','4',
'5','6','7','8','9',
'a','b','c','d','e','f'
]
generic intfmt = {sb, opts, signed, bits : @a::(integral,numeric)
var isneg
var sval, val
var b : char[32]
var i, j, npad
var base
base = (opts.base : uint64)
if signed && bits < 0
sval = -(bits : int64)
val = (sval : uint64)
isneg = true
/* if its negative after inverting, we have int64 min */
if sval < 0
std.sbputs(sb, "-9223372036854775808")
-> void
;;
else
val = (bits : uint64)
val &= ~0 >> (8*(sizeof(uint64)-sizeof(@a::(integral,numeric))))
isneg = false
;;
i = 0
if val == 0
b[0] = '0'
i++
;;
while val != 0
b[i] = digitchars[val % base]
val /= base
i++
;;
npad = clamp(opts.padto - i, 0, opts.padto)
if isneg
npad--
;;
if opts.padfill == '0' && isneg
sbputc(sb, '-')
;;
for j = 0; j < npad; j++
sbputc(sb, opts.padfill)
;;
if opts.padfill != '0' && isneg
sbputc(sb, '-')
;;
for j = i; j != 0; j--
sbputc(sb, b[j - 1])
;;
}
/* would use std.get(), but that's a dependency loop */
const getint = {s, msg
match std.intparse(s)
| `Some w: -> w;
| `None: die(msg)
;;
}