Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1251 lines (1163 sloc) 37.7 KB
# This file is a part of Julia. License is MIT: http://julialang.org/license
module Printf
using Base.Grisu
export @printf, @sprintf
### printf formatter generation ###
const SmallFloatingPoint = Union{Float64,Float32,Float16}
const SmallNumber = Union{SmallFloatingPoint,Base.BitInteger}
function gen(s::AbstractString)
args = []
blk = Expr(:block, :(local neg, pt, len, exp, do_out, args))
for x in parse(s)
if isa(x,AbstractString)
push!(blk.args, :(write(out, $(length(x)==1 ? x[1] : x))))
else
c = lowercase(x[end])
f = c=='f' ? gen_f :
c=='e' ? gen_e :
c=='a' ? gen_a :
c=='g' ? gen_g :
c=='c' ? gen_c :
c=='s' ? gen_s :
c=='p' ? gen_p :
gen_d
arg, ex = f(x...)
push!(args, arg)
push!(blk.args, ex)
end
end
push!(blk.args, :nothing)
return args, blk
end
### printf format string parsing ###
function parse(s::AbstractString)
# parse format string in to stings and format tuples
list = []
i = j = start(s)
while !done(s,j)
c, k = next(s,j)
if c == '%'
isempty(s[i:j-1]) || push!(list, s[i:j-1])
flags, width, precision, conversion, k = parse1(s,k)
'\'' in flags && error("printf format flag ' not yet supported")
conversion == 'n' && error("printf feature %n not supported")
push!(list, conversion == '%' ? "%" : (flags,width,precision,conversion))
i = j = k
else
j = k
end
end
isempty(s[i:end]) || push!(list, s[i:end])
# coalesce adjacent strings
i = 1
while i < length(list)
if isa(list[i],AbstractString)
for j = i+1:length(list)
if !isa(list[j],AbstractString)
j -= 1
break
end
list[i] *= list[j]
end
deleteat!(list,i+1:j)
end
i += 1
end
return list
end
## parse a single printf specifier ##
# printf specifiers:
# % # start
# (\d+\$)? # arg (not supported)
# [\-\+#0' ]* # flags
# (\d+)? # width
# (\.\d*)? # precision
# (h|hh|l|ll|L|j|t|z|q)? # modifier (ignored)
# [diouxXeEfFgGaAcCsSp%] # conversion
next_or_die(s::AbstractString, k) = !done(s,k) ? next(s,k) :
throw(ArgumentError("invalid printf format string: $(repr(s))"))
function parse1(s::AbstractString, k::Integer)
j = k
width = 0
precision = -1
c, k = next_or_die(s,k)
# handle %%
if c == '%'
return "", width, precision, c, k
end
# parse flags
while c in "#0- + '"
c, k = next_or_die(s,k)
end
flags = String(s[j:k-2])
# parse width
while '0' <= c <= '9'
width = 10*width + c-'0'
c, k = next_or_die(s,k)
end
# parse precision
if c == '.'
c, k = next_or_die(s,k)
if '0' <= c <= '9'
precision = 0
while '0' <= c <= '9'
precision = 10*precision + c-'0'
c, k = next_or_die(s,k)
end
end
end
# parse length modifer (ignored)
if c == 'h' || c == 'l'
prev = c
c, k = next_or_die(s,k)
if c == prev
c, k = next_or_die(s,k)
end
elseif c in "Ljqtz"
c, k = next_or_die(s,k)
end
# validate conversion
if !(c in "diouxXDOUeEfFgGaAcCsSpn")
throw(ArgumentError("invalid printf format string: $(repr(s))"))
end
# TODO: warn about silly flag/conversion combinations
flags, width, precision, c, k
end
### printf formatter generation ###
function special_handler(flags::String, width::Int)
@gensym x
blk = Expr(:block)
pad = '-' in flags ? rpad : lpad
pos = '+' in flags ? "+" :
' ' in flags ? " " : ""
abn = quote
isnan($x) ? $(pad("NaN", width)) :
$x < 0 ? $(pad("-Inf", width)) :
$(pad("$(pos)Inf", width))
end
ex = :(isfinite($x) ? $blk : write(out, $abn))
x, ex, blk
end
function pad(m::Int, n, c::Char)
if m <= 1
:($n > 0 && write(out,$c))
else
@gensym i
quote
$i = $n
while $i > 0
write(out,$c)
$i -= 1
end
end
end
end
function dynamic_pad(m, val, c::Char)
@gensym i
quote
if $m <= 1
$val > 0 && write(out,$c)
else
$i = $val
while $i > 0
write(out,$c)
$i -= 1
end
end
end
end
function print_fixed(out, precision, pt, ndigits, trailingzeros=true)
pdigits = pointer(DIGITS)
if pt <= 0
# 0.0dddd0
write(out, '0')
write(out, '.')
precision += pt
while pt < 0
write(out, '0')
pt += 1
end
unsafe_write(out, pdigits, ndigits)
precision -= ndigits
elseif ndigits <= pt
# dddd000.000000
unsafe_write(out, pdigits, ndigits)
while ndigits < pt
write(out, '0')
ndigits += 1
end
if trailingzeros
write(out, '.')
end
else # 0 < pt < ndigits
# dd.dd0000
ndigits -= pt
unsafe_write(out, pdigits, pt)
write(out, '.')
unsafe_write(out, pdigits+pt, ndigits)
precision -= ndigits
end
if trailingzeros
while precision > 0
write(out, '0')
precision -= 1
end
end
end
function print_exp_e(out, exp::Integer)
write(out, exp < 0 ? '-' : '+')
exp = abs(exp)
d = div(exp,100)
if d > 0
if d >= 10
print(out, exp)
return
end
write(out, Char('0'+d))
end
exp = rem(exp,100)
write(out, Char('0'+div(exp,10)))
write(out, Char('0'+rem(exp,10)))
end
function print_exp_a(out, exp::Integer)
write(out, exp < 0 ? '-' : '+')
exp = abs(exp)
print(out, exp)
end
function gen_d(flags::String, width::Int, precision::Int, c::Char)
# print integer:
# [dDiu]: print decimal digits
# [o]: print octal digits
# [x]: print hex digits, lowercase
# [X]: print hex digits, uppercase
#
# flags:
# (#): prefix hex with 0x/0X; octal leads with 0
# (0): pad left with zeros
# (-): left justify
# ( ): precede non-negative values with " "
# (+): precede non-negative values with "+"
#
x, ex, blk = special_handler(flags,width)
# interpret the number
prefix = ""
if lowercase(c)=='o'
fn = '#' in flags ? :decode_0ct : :decode_oct
elseif c=='x'
'#' in flags && (prefix = "0x")
fn = :decode_hex
elseif c=='X'
'#' in flags && (prefix = "0X")
fn = :decode_HEX
else
fn = :decode_dec
end
push!(blk.args, :((do_out, args) = $fn(out, $x, $flags, $width, $precision, $c)))
ifblk = Expr(:if, :do_out, Expr(:block))
push!(blk.args, ifblk)
blk = ifblk.args[2]
push!(blk.args, :((len, pt, neg) = args))
# calculate padding
width -= length(prefix)
space_pad = width > max(1,precision) && '-' in flags ||
precision < 0 && width > 1 && !('0' in flags) ||
precision >= 0 && width > precision
padding = nothing
if precision < 1; precision = 1; end
if space_pad
if '+' in flags || ' ' in flags
width -= 1
if width > precision
padding = :($width-(pt > $precision ? pt : $precision))
end
else
if width > precision
padding = :($width-neg-(pt > $precision ? pt : $precision))
end
end
end
# print space padding
if padding !== nothing && !('-' in flags)
push!(blk.args, pad(width-precision, padding, ' '))
end
# print sign
'+' in flags ? push!(blk.args, :(write(out, neg?'-':'+'))) :
' ' in flags ? push!(blk.args, :(write(out, neg?'-':' '))) :
push!(blk.args, :(neg && write(out, '-')))
# print prefix
for ch in prefix
push!(blk.args, :(write(out, $ch)))
end
# print zero padding & leading zeros
if space_pad && precision > 1
push!(blk.args, pad(precision-1, :($precision-pt), '0'))
elseif !space_pad && width > 1
zeros = '+' in flags || ' ' in flags ? :($(width-1)-pt) : :($width-neg-pt)
push!(blk.args, pad(width-1, zeros, '0'))
end
# print integer
push!(blk.args, :(unsafe_write(out, pointer(DIGITS), pt)))
# print padding
if padding !== nothing && '-' in flags
push!(blk.args, pad(width-precision, padding, ' '))
end
# return arg, expr
:(($x)::Real), ex
end
function gen_f(flags::String, width::Int, precision::Int, c::Char)
# print to fixed trailing precision
# [fF]: the only choice
#
# flags
# (#): always print a decimal point
# (0): pad left with zeros
# (-): left justify
# ( ): precede non-negative values with " "
# (+): precede non-negative values with "+"
#
x, ex, blk = special_handler(flags,width)
# interpret the number
if precision < 0; precision = 6; end
push!(blk.args, :((do_out, args) = fix_dec(out, $x, $flags, $width, $precision, $c)))
ifblk = Expr(:if, :do_out, Expr(:block))
push!(blk.args, ifblk)
blk = ifblk.args[2]
push!(blk.args, :((len, pt, neg) = args))
# calculate padding
padding = nothing
if precision > 0 || '#' in flags
width -= precision+1
end
if '+' in flags || ' ' in flags
width -= 1
if width > 1
padding = :($width-(pt > 0 ? pt : 1))
end
else
if width > 1
padding = :($width-(pt > 0 ? pt : 1)-neg)
end
end
# print space padding
if padding !== nothing && !('-' in flags) && !('0' in flags)
push!(blk.args, pad(width-1, padding, ' '))
end
# print sign
'+' in flags ? push!(blk.args, :(write(out, neg?'-':'+'))) :
' ' in flags ? push!(blk.args, :(write(out, neg?'-':' '))) :
push!(blk.args, :(neg && write(out, '-')))
# print zero padding
if padding !== nothing && !('-' in flags) && '0' in flags
push!(blk.args, pad(width-1, padding, '0'))
end
# print digits
if precision > 0
push!(blk.args, :(print_fixed(out,$precision,pt,len)))
else
push!(blk.args, :(unsafe_write(out, pointer(DIGITS), len)))
push!(blk.args, :(while pt >= (len+=1) write(out,'0') end))
'#' in flags && push!(blk.args, :(write(out, '.')))
end
# print space padding
if padding !== nothing && '-' in flags
push!(blk.args, pad(width-1, padding, ' '))
end
# return arg, expr
:(($x)::Real), ex
end
function gen_e(flags::String, width::Int, precision::Int, c::Char, inside_g::Bool=false)
# print float in scientific form:
# [e]: use 'e' to introduce exponent
# [E]: use 'E' to introduce exponent
#
# flags:
# (#): always print a decimal point
# (0): pad left with zeros
# (-): left justify
# ( ): precede non-negative values with " "
# (+): precede non-negative values with "+"
#
x, ex, blk = if inside_g
@gensym x
blk = Expr(:block)
x, blk, blk
else
special_handler(flags,width)
end
# interpret the number
if precision < 0; precision = 6; end
ndigits = min(precision+1,length(DIGITS)-1)
push!(blk.args, :((do_out, args) = ini_dec(out,$x,$ndigits, $flags, $width, $precision, $c)))
ifblk = Expr(:if, :do_out, Expr(:block))
push!(blk.args, ifblk)
blk = ifblk.args[2]
push!(blk.args, :((len, pt, neg) = args))
push!(blk.args, :(exp = pt-1))
expmark = isupper(c) ? "E" : "e"
if precision==0 && '#' in flags
expmark = string(".",expmark)
end
# calculate padding
padding = nothing
width -= precision+length(expmark)+(precision>0)+4
# 4 = leading + expsign + 2 exp digits
if '+' in flags || ' ' in flags
width -= 1 # for the sign indicator
if width > 0
padding = quote
padn=$width
if (exp<=-100)|(100<=exp)
if isa($x,SmallNumber)
padn -= 1
else
padn -= Base.ndigits0z(exp) - 2
end
end
padn
end
end
else
if width > 0
padding = quote
padn=$width-neg
if (exp<=-100)|(100<=exp)
if isa($x,SmallNumber)
padn -= 1
else
padn -= Base.ndigits0z(exp) - 2
end
end
padn
end
end
end
# print space padding
if padding !== nothing && !('-' in flags) && !('0' in flags)
push!(blk.args, pad(width, padding, ' '))
end
# print sign
'+' in flags ? push!(blk.args, :(write(out, neg?'-':'+'))) :
' ' in flags ? push!(blk.args, :(write(out, neg?'-':' '))) :
push!(blk.args, :(neg && write(out, '-')))
# print zero padding
if padding !== nothing && !('-' in flags) && '0' in flags
push!(blk.args, pad(width, padding, '0'))
end
# print digits
push!(blk.args, :(write(out, DIGITS[1])))
if precision > 0
if inside_g && !('#' in flags)
push!(blk.args, :(endidx = $ndigits;
while endidx > 1 && DIGITS[endidx] == UInt8('0')
endidx -= 1
end;
if endidx > 1
write(out, '.')
unsafe_write(out, pointer(DIGITS)+1, endidx-1)
end
))
else
push!(blk.args, :(write(out, '.')))
push!(blk.args, :(unsafe_write(out, pointer(DIGITS)+1, $(ndigits-1))))
if ndigits < precision+1
n = precision+1-ndigits
push!(blk.args, pad(n, n, '0'))
end
end
end
for ch in expmark
push!(blk.args, :(write(out, $ch)))
end
push!(blk.args, :(print_exp_e(out, exp)))
# print space padding
if padding !== nothing && '-' in flags
push!(blk.args, pad(width, padding, ' '))
end
# return arg, expr
:(($x)::Real), ex
end
function gen_a(flags::String, width::Int, precision::Int, c::Char)
# print float in hexadecimal format
# [a]: lowercase hex float, e.g. -0x1.cfp-2
# [A]: uppercase hex float, e.g. -0X1.CFP-2
#
# flags:
# (#): always print a decimal point
# (0): pad left with zeros
# (-): left justify
# ( ): precede non-negative values with " "
# (+): precede non-negative values with "+"
#
x, ex, blk = special_handler(flags,width)
if c == 'A'
hexmark, expmark = "0X", "P"
fn = :ini_HEX
else
hexmark, expmark = "0x", "p"
fn = :ini_hex
end
# if no precision, print max non-zero
if precision < 0
push!(blk.args, :((do_out, args) = $fn(out,$x, $flags, $width, $precision, $c)))
else
ndigits = min(precision+1,length(DIGITS)-1)
push!(blk.args, :((do_out, args) = $fn(out,$x,$ndigits, $flags, $width, $precision, $c)))
end
ifblk = Expr(:if, :do_out, Expr(:block))
push!(blk.args, ifblk)
blk = ifblk.args[2]
push!(blk.args, :((len, exp, neg) = args))
if precision==0 && '#' in flags
expmark = string(".",expmark)
end
# calculate padding
padding = nothing
if precision > 0
width -= precision+length(hexmark)+length(expmark)+4
# 4 = leading + expsign + 1 exp digit + decimal
else
width -= length(hexmark)+length(expmark)+3+(precision<0 && '#' in flags)
# 3 = leading + expsign + 1 exp digit
end
if '+' in flags || ' ' in flags
width -= 1 # for the sign indicator
if width > 0
padding = :($(width+1) - Base.ndigits(exp))
end
else
if width > 0
padding = :($(width+1) - neg - Base.ndigits(exp))
end
end
if precision < 0 && width > 0
if '#' in flags
padding = :($padding - (len-1))
else
padding = :($padding - (len>1?len:0))
end
end
# print space padding
if padding !== nothing && !('-' in flags) && !('0' in flags)
push!(blk.args, pad(width, padding, ' '))
end
# print sign
'+' in flags ? push!(blk.args, :(write(out, neg?'-':'+'))) :
' ' in flags ? push!(blk.args, :(write(out, neg?'-':' '))) :
push!(blk.args, :(neg && write(out, '-')))
# hex prefix
for ch in hexmark
push!(blk.args, :(write(out, $ch)))
end
# print zero padding
if padding !== nothing && !('-' in flags) && '0' in flags
push!(blk.args, pad(width, padding, '0'))
end
# print digits
push!(blk.args, :(write(out, DIGITS[1])))
if precision > 0
push!(blk.args, :(write(out, '.')))
push!(blk.args, :(unsafe_write(out, pointer(DIGITS)+1, $(ndigits-1))))
if ndigits < precision+1
n = precision+1-ndigits
push!(blk.args, pad(n, n, '0'))
end
elseif precision < 0
ifvpblk = Expr(:if, :(len > 1), Expr(:block))
vpblk = ifvpblk.args[2]
if '#' in flags
push!(blk.args, :(write(out, '.')))
else
push!(vpblk.args, :(write(out, '.')))
end
push!(vpblk.args, :(unsafe_write(out, pointer(DIGITS)+1, len-1)))
push!(blk.args, ifvpblk)
end
for ch in expmark
push!(blk.args, :(write(out, $ch)))
end
push!(blk.args, :(print_exp_a(out, exp)))
# print space padding
if padding !== nothing && '-' in flags
push!(blk.args, pad(width, padding, ' '))
end
# return arg, expr
:(($x)::Real), ex
end
function gen_c(flags::String, width::Int, precision::Int, c::Char)
# print a character:
# [cC]: both the same for us (Unicode)
#
# flags:
# (0): pad left with zeros
# (-): left justify
#
@gensym x
blk = Expr(:block, :($x = Char($x)))
if width > 1 && !('-' in flags)
p = '0' in flags ? '0' : ' '
push!(blk.args, pad(width-1, :($width-charwidth($x)), p))
end
push!(blk.args, :(write(out, $x)))
if width > 1 && '-' in flags
push!(blk.args, pad(width-1, :($width-charwidth($x)), ' '))
end
:(($x)::Integer), blk
end
function _limit(s, prec)
prec >= sizeof(s) && return s
p = prevind(s, prec+1)
n = nextind(s, p)-1
s[1:(prec>=n?n:prevind(s,p))]
end
function gen_s(flags::String, width::Int, precision::Int, c::Char)
# print a string:
# [sS]: both the same for us (Unicode)
#
# flags:
# (-): left justify
#
@gensym x
blk = Expr(:block)
if width > 0
if !('#' in flags)
push!(blk.args, :($x = string($x)))
else
push!(blk.args, :($x = repr($x)))
end
if precision!=-1
push!(blk.args, :($x = _limit($x, $precision)))
end
if !('-' in flags)
push!(blk.args, pad(width, :($width-strwidth($x)), ' '))
end
push!(blk.args, :(write(out, $x)))
if '-' in flags
push!(blk.args, pad(width, :($width-strwidth($x)), ' '))
end
else
if precision!=-1
push!(blk.args, :(io = IOBuffer()))
else
push!(blk.args, :(io = out))
end
if !('#' in flags)
push!(blk.args, :(print(io, $x)))
else
push!(blk.args, :(show(io, $x)))
end
if precision!=-1
push!(blk.args, :(write(out, _limit(String(take!(io)), $precision))))
end
end
:(($x)::Any), blk
end
# TODO: faster pointer printing.
function gen_p(flags::String, width::Int, precision::Int, c::Char)
# print pointer:
# [p]: the only option
#
@gensym x
blk = Expr(:block)
ptrwidth = Sys.WORD_SIZE>>2
width -= ptrwidth+2
if width > 0 && !('-' in flags)
push!(blk.args, pad(width, width, ' '))
end
push!(blk.args, :(write(out, '0')))
push!(blk.args, :(write(out, 'x')))
push!(blk.args, :(write(out, String(hex(unsigned($x), $ptrwidth)))))
if width > 0 && '-' in flags
push!(blk.args, pad(width, width, ' '))
end
:(($x)::Ptr), blk
end
function gen_g(flags::String, width::Int, precision::Int, c::Char)
# print to fixed trailing precision
# [g]: lower case e on scientific
# [G]: Upper case e on scientific
#
# flags
# (#): always print a decimal point
# (0): pad left with zeros
# (-): left justify
# ( ): precede non-negative values with " "
# (+): precede non-negative values with "+"
#
x, ex, blk = special_handler(flags,width)
if precision < 0; precision = 6; end
ndigits = min(precision+1,length(DIGITS)-1)
# See if anyone else wants to handle it
push!(blk.args, :((do_out, args) = ini_dec(out,$x,$ndigits, $flags, $width, $precision, $c)))
ifblk = Expr(:if, :do_out, Expr(:block))
push!(blk.args, ifblk)
blk = ifblk.args[2]
push!(blk.args, :((len, pt, neg) = args))
push!(blk.args, :(exp = pt-1))
push!(blk.args, :(do_f = $precision > exp >= -4)) # Should we interpret like %f or %e?
feblk = Expr(:if, :do_f, Expr(:block), Expr(:block))
push!(blk.args, feblk)
fblk = feblk.args[2]
eblk = feblk.args[3]
### %f branch
# Follow the same logic as gen_f() but more work has to be deferred until runtime
# because precision is unknown until then.
push!(fblk.args, :(fprec = $precision - (exp+1)))
push!(fblk.args, :((do_out, args) = fix_dec(out, $x, $flags, $width, fprec, $c - 1)))
fifblk = Expr(:if, :do_out, Expr(:block))
push!(fblk.args, fifblk)
blk = fifblk.args[2]
push!(blk.args, :((len, pt, neg) = args))
push!(blk.args, :(padding = nothing))
push!(blk.args, :(width = $width))
# need to compute value before left-padding since trailing zeros are elided
push!(blk.args, :(tmpout = IOBuffer()))
push!(blk.args, :(print_fixed(tmpout,fprec,pt,len,$('#' in flags))))
push!(blk.args, :(tmpstr = String(take!(tmpout))))
push!(blk.args, :(width -= length(tmpstr)))
if '+' in flags || ' ' in flags
push!(blk.args, :(width -= 1))
else
push!(blk.args, :(if neg width -= 1; end))
end
push!(blk.args, :(if width >= 1 padding = width; end))
# print space padding
if !('-' in flags) && !('0' in flags)
padexpr = dynamic_pad(:width, :padding, ' ')
push!(blk.args, :(if padding !== nothing
$padexpr; end))
end
# print sign
'+' in flags ? push!(blk.args, :(write(out, neg?'-':'+'))) :
' ' in flags ? push!(blk.args, :(write(out, neg?'-':' '))) :
push!(blk.args, :(neg && write(out, '-')))
# print zero padding
if !('-' in flags) && '0' in flags
padexpr = dynamic_pad(:width, :padding, '0')
push!(blk.args, :(if padding !== nothing
$padexpr; end))
end
# finally print value
push!(blk.args, :(write(out,tmpstr)))
# print space padding
if '-' in flags
padexpr = dynamic_pad(:width, :padding, ' ')
push!(blk.args, :(if padding !== nothing
$padexpr; end))
end
### %e branch
# Here we can do all the work at macro expansion time
var, eex = gen_e(flags, width, precision-1, c, true)
push!(eblk.args, :($(var.args[1]) = $x))
push!(eblk.args, eex)
:(($x)::Real), ex
end
### core unsigned integer decoding functions ###
macro handle_zero(ex)
quote
if $(esc(ex)) == 0
DIGITS[1] = '0'
return Int32(1), Int32(1), $(esc(:neg))
end
end
end
decode_oct(out, d, flags::String, width::Int, precision::Int, c::Char) = (true, decode_oct(d))
decode_0ct(out, d, flags::String, width::Int, precision::Int, c::Char) = (true, decode_0ct(d))
decode_dec(out, d, flags::String, width::Int, precision::Int, c::Char) = (true, decode_dec(d))
decode_hex(out, d, flags::String, width::Int, precision::Int, c::Char) = (true, decode_hex(d))
decode_HEX(out, d, flags::String, width::Int, precision::Int, c::Char) = (true, decode_HEX(d))
fix_dec(out, d, flags::String, width::Int, precision::Int, c::Char) = (true, fix_dec(d, precision))
ini_dec(out, d, ndigits::Int, flags::String, width::Int, precision::Int, c::Char) = (true, ini_dec(d, ndigits))
ini_hex(out, d, ndigits::Int, flags::String, width::Int, precision::Int, c::Char) = (true, ini_hex(d, ndigits))
ini_HEX(out, d, ndigits::Int, flags::String, width::Int, precision::Int, c::Char) = (true, ini_HEX(d, ndigits))
ini_hex(out, d, flags::String, width::Int, precision::Int, c::Char) = (true, ini_hex(d))
ini_HEX(out, d, flags::String, width::Int, precision::Int, c::Char) = (true, ini_HEX(d))
# fallbacks for Real types without explicit decode_* implementation
decode_oct(d::Real) = decode_oct(Integer(d))
decode_0ct(d::Real) = decode_0ct(Integer(d))
decode_dec(d::Real) = decode_dec(Integer(d))
decode_hex(d::Real) = decode_hex(Integer(d))
decode_HEX(d::Real) = decode_HEX(Integer(d))
handlenegative(d::Unsigned) = (false, d)
function handlenegative(d::Integer)
if d < 0
return true, unsigned(oftype(d,-d))
else
return false, unsigned(d)
end
end
function decode_oct(d::Integer)
neg, x = handlenegative(d)
@handle_zero x
pt = i = div((sizeof(x)<<3)-leading_zeros(x)+2,3)
while i > 0
DIGITS[i] = '0'+(x&0x7)
x >>= 3
i -= 1
end
return Int32(pt), Int32(pt), neg
end
function decode_0ct(d::Integer)
neg, x = handlenegative(d)
# doesn't need special handling for zero
pt = i = div((sizeof(x)<<3)-leading_zeros(x)+5,3)
while i > 0
DIGITS[i] = '0'+(x&0x7)
x >>= 3
i -= 1
end
return Int32(pt), Int32(pt), neg
end
function decode_dec(d::Integer)
neg, x = handlenegative(d)
@handle_zero x
pt = i = Base.ndigits0z(x)
while i > 0
DIGITS[i] = '0'+rem(x,10)
x = div(x,10)
i -= 1
end
return Int32(pt), Int32(pt), neg
end
function decode_hex(d::Integer, symbols::Array{UInt8,1})
neg, x = handlenegative(d)
@handle_zero x
pt = i = (sizeof(x)<<1)-(leading_zeros(x)>>2)
while i > 0
DIGITS[i] = symbols[(x&0xf)+1]
x >>= 4
i -= 1
end
return Int32(pt), Int32(pt), neg
end
const hex_symbols = b"0123456789abcdef"
const HEX_symbols = b"0123456789ABCDEF"
decode_hex(x::Integer) = decode_hex(x,hex_symbols)
decode_HEX(x::Integer) = decode_hex(x,HEX_symbols)
function decode(b::Int, x::BigInt)
neg = x.size < 0
pt = Base.ndigits(x, abs(b))
length(DIGITS) < pt+1 && resize!(DIGITS, pt+1)
neg && (x.size = -x.size)
ccall((:__gmpz_get_str, :libgmp), Cstring,
(Ptr{UInt8}, Cint, Ptr{BigInt}), DIGITS, b, &x)
neg && (x.size = -x.size)
return Int32(pt), Int32(pt), neg
end
decode_oct(x::BigInt) = decode(8, x)
decode_dec(x::BigInt) = decode(10, x)
decode_hex(x::BigInt) = decode(16, x)
decode_HEX(x::BigInt) = decode(-16, x)
function decode_0ct(x::BigInt)
neg = x.size < 0
DIGITS[1] = '0'
if x.size == 0
return Int32(1), Int32(1), neg
end
pt = Base.ndigits0z(x, 8) + 1
length(DIGITS) < pt+1 && resize!(DIGITS, pt+1)
neg && (x.size = -x.size)
p = convert(Ptr{UInt8}, DIGITS) + 1
ccall((:__gmpz_get_str, :libgmp), Cstring,
(Ptr{UInt8}, Cint, Ptr{BigInt}), p, 8, &x)
neg && (x.size = -x.size)
return neg, Int32(pt), Int32(pt)
end
### decoding functions directly used by printf generated code ###
# decode_*(x)=> fixed precision, to 0th place, filled out
# fix_*(x,n) => fixed precision, to nth place, not filled out
# ini_*(x,n) => n initial digits, filled out
# alternate versions:
# *_0ct(x,n) => ensure that the first octal digits is zero
# *_HEX(x,n) => use uppercase digits for hexadecimal
# - returns (len, point, neg)
# - implies len = point
#
function decode_dec(x::SmallFloatingPoint)
if x == 0.0
DIGITS[1] = '0'
return (Int32(1), Int32(1), false)
end
len,pt,neg = grisu(x,Grisu.FIXED,0)
if len == 0
DIGITS[1] = '0'
return (Int32(1), Int32(1), false)
else
for i = len+1:pt
DIGITS[i] = '0'
end
end
return Int32(len), Int32(pt), neg
end
# TODO: implement decode_oct, decode_0ct, decode_hex, decode_HEX for SmallFloatingPoint
## fix decoding functions ##
#
# - returns (neg, point, len)
# - if len less than point, trailing zeros implied
#
# fallback for Real types without explicit fix_dec implementation
fix_dec(x::Real, n::Int) = fix_dec(float(x),n)
fix_dec(x::Integer, n::Int) = decode_dec(x)
function fix_dec(x::SmallFloatingPoint, n::Int)
if n > length(DIGITS)-1; n = length(DIGITS)-1; end
len,pt,neg = grisu(x,Grisu.FIXED,n)
if len == 0
DIGITS[1] = '0'
return (Int32(1), Int32(1), neg)
end
return Int32(len), Int32(pt), neg
end
## ini decoding functions ##
#
# - returns (neg, point, len)
# - implies len = n (requested digits)
#
# fallback for Real types without explicit fix_dec implementation
ini_dec(x::Real, n::Int) = ini_dec(float(x),n)
function ini_dec(d::Integer, n::Int)
neg, x = handlenegative(d)
k = ndigits(x)
if k <= n
pt = k
for i = k:-1:1
DIGITS[i] = '0'+rem(x,10)
x = div(x,10)
end
for i = k+1:n
DIGITS[i] = '0'
end
else
p = Base.powers_of_ten[k-n+1]
r = rem(x,p)
if r >= (p>>1)
x += p
if x >= Base.powers_of_ten[k+1]
p *= 10
k += 1
end
end
pt = k
x = div(x,p)
for i = n:-1:1
DIGITS[i] = '0'+rem(x,10)
x = div(x,10)
end
end
return n, pt, neg
end
function ini_dec(x::SmallFloatingPoint, n::Int)
if x == 0.0
ccall(:memset, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), DIGITS, '0', n)
return Int32(1), Int32(1), signbit(x)
else
len,pt,neg = grisu(x,Grisu.PRECISION,n)
end
return Int32(len), Int32(pt), neg
end
function ini_dec(x::BigInt, n::Int)
if x.size == 0
ccall(:memset, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), DIGITS, '0', n)
return Int32(1), Int32(1), false
end
d = Base.ndigits0z(x)
if d <= n
info = decode_dec(x)
d == n && return info
p = convert(Ptr{Void}, DIGITS) + info[2]
ccall(:memset, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), p, '0', n - info[2])
return info
end
return (n, d, decode_dec(round(BigInt,x/big(10)^(d-n)))[3])
end
ini_hex(x::Real, n::Int) = ini_hex(x,n,hex_symbols)
ini_HEX(x::Real, n::Int) = ini_hex(x,n,HEX_symbols)
ini_hex(x::Real) = ini_hex(x,hex_symbols)
ini_HEX(x::Real) = ini_hex(x,HEX_symbols)
ini_hex(x::Real, n::Int, symbols::Array{UInt8,1}) = ini_hex(float(x), n, symbols)
ini_hex(x::Real, symbols::Array{UInt8,1}) = ini_hex(float(x), symbols)
function ini_hex(x::SmallFloatingPoint, n::Int, symbols::Array{UInt8,1})
x = Float64(x)
if x == 0.0
ccall(:memset, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), DIGITS, '0', n)
return Int32(1), Int32(0), signbit(x)
else
s, p = frexp(x)
sigbits = 4*min(n-1,13)
s = 0.25*round(ldexp(s,1+sigbits))
# ensure last 2 exponent bits either 01 or 10
u = (reinterpret(UInt64,s) & 0x003f_ffff_ffff_ffff) >> (52-sigbits)
if n > 14
ccall(:memset, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), DIGITS, '0', n)
end
i = (sizeof(u)<<1)-(leading_zeros(u)>>2)
while i > 0
DIGITS[i] = symbols[(u&0xf)+1]
u >>= 4
i -= 1
end
# pt is the binary exponent
return Int32(n), Int32(p-1), x < 0.0
end
end
function ini_hex(x::SmallFloatingPoint, symbols::Array{UInt8,1})
x = Float64(x)
if x == 0.0
ccall(:memset, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), DIGITS, '0', 1)
return Int32(1), Int32(0), signbit(x)
else
s, p = frexp(x)
s *= 2.0
u = (reinterpret(UInt64,s) & 0x001f_ffff_ffff_ffff)
t = (trailing_zeros(u) >> 2)
u >>= (t<<2)
n = 14-t
for i = n:-1:1
DIGITS[i] = symbols[(u&0xf)+1]
u >>= 4
end
# pt is the binary exponent
return Int32(n), Int32(p-1), x < 0.0
end
end
function ini_hex(x::Integer)
len,pt,neg = decode_hex(x)
pt = (len-1)<<2
len,pt,neg
end
function ini_HEX(x::Integer)
len,pt,neg = decode_HEX(x)
pt = (len-1)<<2
len,pt,neg
end
# not implemented
ini_hex(x::Integer,ndigits::Int) = throw(MethodError(ini_hex,(x,ndigits)))
#BigFloat
fix_dec(out, d::BigFloat, flags::String, width::Int, precision::Int, c::Char) = bigfloat_printf(out, d, flags, width, precision, c)
ini_dec(out, d::BigFloat, ndigits::Int, flags::String, width::Int, precision::Int, c::Char) = bigfloat_printf(out, d, flags, width, precision, c)
ini_hex(out, d::BigFloat, ndigits::Int, flags::String, width::Int, precision::Int, c::Char) = bigfloat_printf(out, d, flags, width, precision, c)
ini_HEX(out, d::BigFloat, ndigits::Int, flags::String, width::Int, precision::Int, c::Char) = bigfloat_printf(out, d, flags, width, precision, c)
ini_hex(out, d::BigFloat, flags::String, width::Int, precision::Int, c::Char) = bigfloat_printf(out, d, flags, width, precision, c)
ini_HEX(out, d::BigFloat, flags::String, width::Int, precision::Int, c::Char) = bigfloat_printf(out, d, flags, width, precision, c)
function bigfloat_printf(out, d, flags::String, width::Int, precision::Int, c::Char)
fmt_len = sizeof(flags)+4
if width > 0
fmt_len += ndigits(width)
end
if precision >= 0
fmt_len += ndigits(precision)+1
end
fmt = IOBuffer(fmt_len)
write(fmt, '%')
write(fmt, flags)
if width > 0
print(fmt, width)
end
if precision == 0
write(fmt, '.')
write(fmt, '0')
elseif precision > 0
write(fmt, '.')
print(fmt, precision)
end
write(fmt, 'R')
write(fmt, c)
write(fmt, UInt8(0))
printf_fmt = take!(fmt)
@assert length(printf_fmt) == fmt_len
bufsiz = length(DIGITS) - 1
lng = ccall((:mpfr_snprintf,:libmpfr), Int32, (Ptr{UInt8}, Culong, Ptr{UInt8}, Ptr{BigFloat}...), DIGITS, bufsiz, printf_fmt, &d)
lng > 0 || error("invalid printf formatting for BigFloat")
unsafe_write(out, pointer(DIGITS), min(lng,bufsiz))
return (false, ())
end
### external printf interface ###
is_str_expr(ex) =
isa(ex,Expr) && (ex.head == :string || (ex.head == :macrocall && isa(ex.args[1],Symbol) &&
endswith(string(ex.args[1]),"str")))
function _printf(macroname, io, fmt, args)
isa(fmt, AbstractString) || throw(ArgumentError("$macroname: format must be a plain static string (no interpolation or prefix)"))
sym_args, blk = gen(fmt)
has_splatting = false
for arg in args
if isa(arg, Expr) && arg.head == :...
has_splatting = true
break
end
end
#
# Immediately check for corresponding arguments if there is no splatting
#
if !has_splatting && length(sym_args) != length(args)
throw(ArgumentError("$macroname: wrong number of arguments ($(length(args))) should be ($(length(sym_args)))"))
end
for i = length(sym_args):-1:1
var = sym_args[i].args[1]
if has_splatting
unshift!(blk.args, :($var = G[$i]))
else
unshift!(blk.args, :($var = $(esc(args[i]))))
end
end
#
# Delay generation of argument list and check until evaluation time instead of macro
# expansion time if there is splatting.
#
if has_splatting
x = Expr(:call,:tuple,args...)
unshift!(blk.args,
quote
G = $(esc(x))
if length(G) != $(length(sym_args))
throw(ArgumentError($macroname,": wrong number of arguments (",length(G),") should be (",$(length(sym_args)),")"))
end
end
)
end
unshift!(blk.args, :(out = $io))
Expr(:let, blk)
end
"""
@printf([io::IOStream], "%Fmt", args...)
Print `args` using C `printf()` style format specification string, with some caveats:
`Inf` and `NaN` are printed consistently as `Inf` and `NaN` for flags `%a`, `%A`,
`%e`, `%E`, `%f`, `%F`, `%g`, and `%G`. Furthermore, if a floating point number is
equally close to the numeric values of two possible output strings, the output
string further away from zero is chosen.
Optionally, an `IOStream`
may be passed as the first argument to redirect output.
# Examples
```jldoctest
julia> @printf("%f %F %f %F\\n", Inf, Inf, NaN, NaN)
Inf Inf NaN NaN\n
julia> @printf "%.0f %.1f %f\\n" 0.5 0.025 -0.0078125
1 0.0 -0.007813
```
"""
macro printf(args...)
isempty(args) && throw(ArgumentError("@printf: called with no arguments"))
if isa(args[1], AbstractString) || is_str_expr(args[1])
_printf("@printf", :STDOUT, args[1], args[2:end])
else
(length(args) >= 2 && (isa(args[2], AbstractString) || is_str_expr(args[2]))) ||
throw(ArgumentError("@printf: first or second argument must be a format string"))
_printf("@printf", esc(args[1]), args[2], args[3:end])
end
end
"""
@sprintf("%Fmt", args...)
Return `@printf` formatted output as string.
# Examples
```jldoctest
julia> s = @sprintf "this is a %s %15.1f" "test" 34.567;
julia> println(s)
this is a test 34.6
```
"""
macro sprintf(args...)
isempty(args) && throw(ArgumentError("@sprintf: called with zero arguments"))
isa(args[1], AbstractString) || is_str_expr(args[1]) ||
throw(ArgumentError("@sprintf: first argument must be a format string"))
letexpr = _printf("@sprintf", :(IOBuffer()), args[1], args[2:end])
push!(letexpr.args[1].args, :(String(take!(out))))
letexpr
end
end # module