shithub: mc

ref: 2dae34046c10ad0d59d3a9f056cd133a49ce091b
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 "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 "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), \
		optdesc : (byte[:], bool)[:] \
		-> void)

	$noret const fatal	: (fmt : byte[:], args : ... -> void)
	$noret const fatalv	: (fmt : byte[:], ap : valist# -> void)
;;

const __init__ = {
	fmtmap = mkht(strhash, streq)
}

type fmtdesc = struct
	fn	: (sb : strbuf#, ap : valist#, opts : (byte[:],byte[:])[:] -> void)
	optdesc	: (byte[:], bool)[:]
;;

/* 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, optdesc
	match std.htget(fmtmap, ty)
	| `std.Some _:	std.fatal("doubly installed format\n")
	| `std.None:	htput(fmtmap, ty, [.fn=fn, .optdesc=sldup(optdesc)])
	;;
}

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 nfmt, nparams, orig
	var c, params

	orig = fmt
	nparams = ap.tc.nelt
	nfmt = 0
	while fmt.len != 0
		(c, fmt) = strstep(fmt)
		match c
		| '{':
			if decode(fmt) == '{'
				(c, fmt) = strstep(fmt)
				sbputc(sb, '{')
			else
				(params, fmt) = getparams(fmt)
				nfmt++
				if nfmt > nparams
					die("too few params for fmt\n")
				;;

				fmtval(sb, vatype(ap), ap, params)
			;;
		| '}':
			if decode(fmt) == '}'
				sbputc(sb, '}')
			;;
		| chr:
			sbputc(sb, chr)
		;;
:fmtdone
	;;
	if nfmt != nparams
		write(1, orig)
		die("too many params for fmt\n")
	;;
	-> sb.len
}

const fmtval = {sb, ty, ap, params
	var pl

	match htget(fmtmap, ty)
	| `Some f:
		pl = parseparams(params, f.optdesc)
		f.fn(sb, ap, pl)
		std.slfree(pl)
	| `None:
		fallbackfmt(sb, params, ty, ap)
	;;
}

const parseparams = {paramstr, optdesc
	var params, opts
	var o, a, ha, gotarg, found

	opts = [][:]
	if optdesc.len == 0 && paramstr.len > 0
		std.fatal("invalid format options {}\n")
	;;
	params = strsplit(paramstr, ",")
	for p : params
		/* parse out the key/value pair */
		match std.strfind(p, "=")
		| `std.Some idx:
			o = p[:idx]
			a = p[idx+1:]
			gotarg = true
		| `std.None:
			o = p
			a = ""
			gotarg = false
		;;

		found = false
		/* verify and add the arg */
		for (opt, hasarg) : optdesc
			if !std.sleq(opt, o)
				continue
			;;
			found = true
			ha = hasarg
			if ha == gotarg
				std.slpush(&opts, (o, a))
			else
				std.fatal("invalid arg for option {} ", a, o)
			;;
		;;
		if !found
			std.put("options: \n")
			for (opt, hasarg) : optdesc
				std.put("\t'{}', hasarg={}\n", opt, hasarg)
			;;
			std.put("invalid option '{}' ", o)
			std.die("dying")
		;;
	;;
	slfree(params)
	-> opts
}


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 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:
		p_val = vanext(ap)
		sbputs(sb, "func{")
		intfmt(sb, \
			[.base=16, .padto=2*sizeof(void#), .padfill='0'], \
			false, (p_val : intptr))
		sbputs(sb, "}")
		vabytes(ap)
	| `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)
		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 opts, join, joined

	opts = parseparams(params, [
		("j", true),
	][:])
	join = ", "
	joined = false
	for o : opts
		match o
		| ("j", j):
			joined = true
			join = j
		| _:	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, "]")
	;;
}

const getparams = {fmt
	var i

	for i = 0; i < fmt.len; i++
		if fmt[i] == ('}' : byte)
			goto foundparams
		;;
	;;
	die("invalid format string")
:foundparams
	-> (fmt[:i], fmt[i+1:])
}

type intparams = struct
	base	: size
	padto	: size
	padfill	: char
;;

const intparams = {params
	var ip : intparams
	var opts

	ip = [
		.base = 10,
		.padfill = ' ',
		.padto = 0
	]

	opts = parseparams(params, [
		("x", false),
		("w", true),
		("p", true)][:])
	for o : opts
		match o
		| ("x", ""):	ip.base = 16
		| ("w", wid):	ip.padto = getint(wid, "fmt: width must be integer")
		| ("p", pad):	ip.padfill = decode(pad)
		| _:	std.die("unreachable")
		;;
	;;
	iassert(ip.padto >= 0, "pad must be >= 0")
	std.slfree(opts)
	-> ip
}

const strfmt = {sb, str, params
	var opts
	var w, p, i, raw, esc

	p = ' '
	w = 0
	raw = false
	esc = false
	opts = parseparams(params, [
		("w", true),
		("p", true),
		("r", false),
		("e", false),
	][:])

	for o : opts
		match o
		| ("w", wid):	w = getint(wid, "fmt: width must be integer")
		| ("p", pad):	p = decode(pad)
		| ("r", ""):	raw = true
		| ("e", ""):	esc = true
		| _:	std.die("unreachable")
		;;
	;;
	iassert(p >= 0, "pad must be >= 0")
	std.slfree(opts)
	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 - graphemewidth(str); i++
			sbputc(sb, p)
		;;
		sbputs(sb, str)
	;;
}

const isprint = {b
	-> b >= (' ' : byte) && b < ('~' : byte)
}

/*
Hah. like we're going to put in the work to actually
count graphemes.
*/
const graphemewidth = {str
	-> str.len
}

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)
	;;
}