shithub: mc

ref: 0b5b219f27ff55efda4f33df0ca3cd87140be712
dir: /lib/std/fmt.myr/

View raw version
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
}