############################################################################
#
# File: printf.icn
#
# Subject: Procedures for printf-style formatting
#
# Author: William H. Mitchell
#
# Date: March 18, 2021
#
############################################################################
#
# This file is in the public domain.
#
############################################################################
#
# Contributors: Cheyenne Wills, Phillip Lee Thomas,
# Michael Glass, Gregg M. Townsend,
# Clinton Jeffery
#
############################################################################
#
# This module provides a family of formatted output functions
# modeled on those found in the C language.
#
# printf(fmt:string, args[]) formats and writes arguments to &output.
# fprintf(f:file,fmt:string,args[]) formats and writes arguments to f.
# sprintf(fmt:string, args[]) : string formats arguments and returns a string.
#
# The format string fmt is modified by substituting arguments
# in place of the ``format specifiers'' within it, consisting of a
# percent sign followed by a specifier code. Specifier codes include
#
# specifier arg is printed the form of
# %d decimal integer
# %e scientific notation
# %i image
# %o octal
# %r real number. Exponential format if number is larger than int
# %s string
# %c character. Like %s only it only prints first letter.
# %x hexadecimal
#
# An hyphen after the percent sign indicates left justification,
# otherwise right justification is used. A number of digits after the
# percent sign may specify the width of the field to use, or after a
# period they specify a number of digits of precision. For example,
# printf("%-5.2r", x) specifies that real number x be
# formatted as a string of at least 5 characters, left justified,
# with two digits after the decimal point.
#
# Code contributions for %f and %g formats that work like
# C's printf() would be welcome.
#
# Possible new formats:
#
# %t -- print a real number as a time in hh:mm
# %R -- roman numerals
# %w -- integers in English
# %b -- binary
#
############################################################################
procedure sprintf(format, args[])
return _doprnt(format, args)
end
procedure fprintf(file, format, args[])
writes(file, _doprnt(format, args))
return
end
procedure printf(format, args[])
writes(&output, _doprnt(format, args))
return
end
procedure _doprnt(format, args)
local out, v, just, width, conv, prec, pad
out := ""
format ? repeat {
(out ||:= tab(upto('%'))) | (out ||:= tab(0) & break)
v := get(args)
move(1)
just := right
width := conv := prec := pad := &null
="-" & just := left
width := tab(many(&digits))
(\width)[1] == "0" & pad := "0"
="." & prec := tab(many(&digits))
conv := move(1)
##write("just: ",image(just),", width: ", width, ", prec: ",
## prec, ", conv: ", conv)
case conv of {
"d": {
v := string(integer(v))
}
"s": {
v := string(v[1:(\prec+1)|0])
}
"c": { # treat %c as %s except it only prints first letter
v := string(v[1:(\prec+1)|0])
v := v[1]
}
"x": v := hexstr(v)
"o": v := octstr(v)
"i": v := image(v)
"r": v := fixnum(v,prec)
"e": v := eformatstr(v, prec, width)
default: {
push(args, v)
v := conv
}
}
if \width & *v < width then {
v := just(v, width, pad)
}
out ||:= v
}
return out
end
procedure hexstr(n)
return _basestr(n, 4)
end
procedure octstr(n)
return _basestr(n, 3)
end
procedure _basestr(n, b)
local s, mask
n := integer(n) | return image(n)
if n = 0 then
return "0"
# backwards compatibility hack
# treat 31-bit negative integers as positive values
if -16r80000000 <= n <= -1 then
n +:= 16r100000000
s := ""
mask := ishift(1, b) - 1
while n ~= 0 & n ~= -1 do {
s := "0123456789abcdef" [1 + iand(n, mask)] || s
n := ishift(n, -b)
}
return s
end
procedure fixnum(x, prec)
local s
/prec := 6
x := real(x) | return image(x)
if x < 0 then {
s := "-"
x := -x
}
else
s := ""
x := string(integer(x * 10 ^ prec + 0.5))
if *x <= prec then
x := right(x, prec + 1, "0")
return s || x[1:-prec] || "." || x[-prec:0]
end
# e-format: [-]m.dddddde(+|-)xx
#
# Differs from C and Fortran E formats primarily in the
# details, among them:
#
# - Single-digit exponents are not padded out to two digits.
#
# - The precision (number of digits after the decimal point)
# is reduced if needed to make the number fit in the available
# width, if possible. The precision is never reduced-to-fit
# below 1 digit after the decimal point.
#
procedure eformatstr(x, prec, width)
local signpart, wholepart, fracpart, exppart
local choppart, shiftcount, toowide
local rslt, s
/prec := 6
/width := prec + 7
# Separate string representation of x into parts
#
s := string(real(x)) | return image(x)
s ? {
signpart := (=("-" | "+") | "")
wholepart := 1(tab(many(&digits)), any('.eE')) | return image(x)
fracpart := ((=".", tab(many(&digits))) | "")
exppart := integer((=("e"|"E"), tab(0)) | 0)
}
# When the integer part has more than 1 digit, shift it
# right into fractional part and scale the exponent
#
if *wholepart > 1 then {
exppart +:= *wholepart -1
fracpart := wholepart[2:0] || fracpart
wholepart := wholepart[1]
}
# If the the number is unnormalized, shift the fraction
# left into the whole part and scale the exponent
#
if wholepart == "0" then {
if shiftcount := upto('123456789', fracpart) then {
exppart -:= shiftcount
wholepart := fracpart[shiftcount]
fracpart := fracpart[shiftcount+1:0]
}
}
# Adjust the fractional part to the requested precision.
# If the carry causes the whole part to overflow from
# 9 to 10 then renormalize.
#
fracpart := adjustfracprec(fracpart, prec)
wholepart +:= fracpart[2]
fracpart := fracpart[1]
if *wholepart > 1 then {
wholepart := wholepart[1]
exppart +:= 1
}
# Assemble the final result.
# - Leading "+" dropped in mantissa
# - Leading "+" obligatory in exponent
# - Decimal "." included iff fractional part is non-empty
#
wholepart := (signpart == "-", "-") || wholepart
exppart := (exppart > 0, "+") || exppart
fracpart := (*fracpart > 0, ".") || fracpart
rslt := wholepart || fracpart || "e" || exppart
# Return the result.
# -- If too short, pad on the left with blanks (not zeros!).
# -- If too long try to shrink the precision
# -- If shrinking is not possible return a field of stars.
#
return (*rslt <= width, right(rslt, width)) |
(*rslt - width < prec, eformatstr(x, prec + width - *rslt, width)) |
repl("*", width)
end
# Zero-extend or round the fractional part to 'prec' digits.
#
# Returns a list:
#
# [ fracpart, carry ]
#
# where the fracpart has been adjusted to the requested
# precision, and the carry (result of possible rounding)
# is to be added into the whole number.
#
procedure adjustfracprec(fracpart, prec)
local choppart, carryout
# Zero-extend if needed.
if *fracpart < prec then return [left(fracpart, prec, "0"), 0]
# When the fractional part has more digits than the requested
# precision, chop off the extras and round.
#
carryout := 0
if *fracpart > prec then {
choppart := fracpart[prec+1:0]
fracpart := fracpart[1+:prec]
# If rounding up is needed...
#
if choppart[1] >>= "5" then {
# When the fractional part is .999s or the precision is 0,
# then round up overflows into the whole part.
#
if (prec = 0) | (string(cset(fracpart)) == "9") then {
fracpart := left("0", prec, "0")
carryout := 1
}
# In the usual case, round up simply increments the
# fractional part. (We put back any leading
# zeros that got lost.)
else {
fracpart := right(integer(fracpart)+1, prec, "0")
}
}
}
return [fracpart, carryout]
end
This page produced by UniDoc on 2021/04/15 @ 23:59:43.