About.jl/src/values.jl

455 lines
21 KiB
Julia
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ------------------
# Structs, in general
# ------------------
function about(io::IO, value::T) where {T}
# Type information
iotype = AnnotatedIOBuffer()
print(iotype, Base.summary(value))
ismutable(value) && print(iotype, " (mutable)")
print(iotype, S" ({julia_comparator:<:} ")
supertypeinfo(iotype, supertype(T))
print(iotype, ")")
infotype = read(seekstart(iotype), AnnotatedString)
# Size information
typesize = try sizeof(T) catch _ sizeof(value) end
datasize = sizeof(value)
netsize = Base.summarysize(value)
infosize = if typesize == datasize == netsize
S"{about_bytes:$(join(humansize(typesize)))}."
elseif typesize == datasize <= netsize
S"{about_bytes:$(join(humansize(typesize)))} directly \
(referencing {about_bytes:$(join(humansize(netsize)))} in total)"
elseif typesize == datasize > netsize
S"{about_bytes:$(join(humansize(typesize)))} directly \
({warning:!} referencing {about_bytes:$(join(humansize(netsize)))} in total, \
{warning:strangely less than the direct, \
{underline,link={https://github.com/tecosaur/About.jl}:\
please open an issue on About.jl with this example}})"
else # all different
S"{about_bytes:$(join(humansize(typesize)))} directly \
(referencing {about_bytes:$(join(humansize(netsize)))} in total, \
holding {about_bytes:$(join(humansize(datasize)))} of data)"
end
print(io, infotype)
if textwidth(infotype) < last(displaysize(io)) &&
textwidth(infotype) + textwidth(infosize) + 12 >= last(displaysize(io))
print(io, "\n Memory footprint: ")
else
print(io, ", occupies ")
end
println(io, infosize)
# Layout + elaboration
memorylayout(io, value)
if get(io, :compact, false) != true
elaboration(io, value)
end
end
function memorylayout(io::IO, value::T) where {T}
if isprimitivetype(T)
get(io, :compact, false) || print(io, "\n ")
print(io, bitstring(value))
return
end
if get(io, :compact, false) == true
print(io, "«struct»")
return
end
if Base.issingletontype(T)
println(io, S"{italic:singelton}")
return
end
sinfo = structinfo(T)
isempty(sinfo) && return
ffaces = Union{Face, Symbol}[]
fnames = String[]
ftypes = String[]
fsizes = String[]
freprs = AnnotatedString[]
fshows = AnnotatedString[]
for (; face, name, type, size, ispointer) in sinfo
push!(ffaces, face)
push!(fnames, string(name))
push!(ftypes, string(type))
push!(fsizes, join(humansize(size)))
aio = AnnotatedIOBuffer()
fvalue = getfield(value, name)
if Base.issingletontype(typeof(fvalue))
push!(freprs, S"{shadow:singleton}")
elseif size == 0
push!(freprs, S"{error:??}")
elseif ispointer
try
pt = pointer(fvalue)
push!(freprs, S"{about_pointer:@ $(sprint(show, UInt64(pt)))}")
catch
push!(freprs, S"{about_pointer:Ptr?}")
end
else
memorylayout(IOContext(aio, :compact => true), fvalue)
push!(freprs, read(seekstart(aio), AnnotatedString))
end
truncate(aio, 0)
show(IOContext(aio, :compact => true), fvalue)
push!(fshows, read(seekstart(aio), AnnotatedString))
end
width = last(displaysize(io)) - 2
namewidth = maximum(textwidth, fnames)
typewidth = min(maximum(textwidth, ftypes), width ÷ 4)
sizewidth = maximum(textwidth, fsizes)
width -= 1 + namewidth + 1 + typewidth + 2 + sizewidth
reprwidth = min((2 * width) ÷ 3, maximum(textwidth, freprs))
showwidth = width - reprwidth
for (face, name, type, size, brepr, shown) in zip(ffaces, fnames, ftypes, fsizes, freprs, fshows)
println(io, ' ',
S"{$face:$(lpad(name, namewidth)){shadow:::}$(rpad(struncate(type, typewidth, \"\", :right), typewidth)) $(lpad(size, sizewidth))}",
' ', rpad(struncate(brepr, reprwidth, S" {shadow:…} "), reprwidth),
' ', face!(struncate(shown, showwidth, S" {shadow:…} "), face))
end
memorylayout(io, T)
end
# ------------------
# Modules
# ------------------
function about(io::IO, mod::Module)
pkg = nothing
for (bpkg, m) in Base.loaded_modules
if m == mod
pkg = bpkg
break
end
end
!isnothing(pkg) && !applicable(about_pkg, io, pkg, mod) &&
Base.require(Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"))
print(io, S"{bold:Module {about_module:$mod}}")
if !isnothing(pkg)
println(io, S" {shadow:[$(something(pkg.uuid, \"no uuid\"))]}")
Base.invokelatest(about_pkg, io, pkg, mod)
else
println(io)
end
function classify(m::Module, name::Symbol)
val = getglobal(mod, name)
order, kind, face, parent = if val isa Module
0, :module, :about_module, val
elseif val isa Function && first(String(name)) == '@'
1, :macro, :julia_macro, parentmodule(val)
elseif val isa Function
2, :function, :julia_funcall, parentmodule(val)
elseif val isa Type
3, :type, :julia_type, if val isa UnionAll || val isa Union
m else parentmodule(val) end
else
4, :value, :julia_identifier, if Base.issingletontype(typeof(val))
parentmodule(typeof(m))
else
m
end
end
while parentmodule(parent) (parent, Main)
parent = parentmodule(parent)
end
(; name, str = S"{code,$face:$name}", kind, parent, order)
end
classify(m::Module, names::Vector{Symbol}) =
sort(map(Base.Fix1(classify, m), names), by=x->x.order)
allnames = classify(mod, names(mod))
exports = similar(allnames, 0)
reexports = similar(allnames, 0)
publics = similar(allnames, 0)
for exp in allnames
if exp.parent === mod && Base.isexported(mod, exp.name)
push!(exports, exp)
elseif exp.parent === mod && Base.ispublic(mod, exp.name)
push!(publics, exp)
elseif exp.parent !== mod
push!(reexports, exp)
end
end
if !isempty(exports)
println(io, S"\n{bold:Exports {emphasis:$(length(exports))} name$(splural(exports)):}")
columnlist(io, map(x->x.str, exports))
end
if !isempty(reexports)
parents = join(sort(map(p->S"{about_module:$p}", unique(map(x->x.parent, reexports)))), ", ")
println(io, S"\n{bold:Re-exports {emphasis:$(length(reexports))} name$(splural(reexports))} (from $parents){bold::}")
columnlist(io, map(x->x.str, reexports))
end
if !isempty(publics)
println(io, S"\n{bold:Public API ({emphasis:$(length(publics))} name$(splural(publics))):}")
columnlist(io, map(x->x.str, publics))
end
end
function about_pkg end # Implemented in `../ext/PkgExt.jl`
# ------------------
# Numeric types
# ------------------
const NUMBER_BIT_FACES = (
sign = :bright_blue,
exponent = :bright_green,
mantissa = :bright_red
)
function memorylayout(io::IO, value::Bool)
bits = AnnotatedString(bitstring(value))
face!(bits[1:end-1], :shadow)
face!(bits[end:end], NUMBER_BIT_FACES.sign)
if get(io, :compact, false) == true
print(io, bits)
else
println(io, "\n ", bits, S" {bold:=} $value")
end
end
function memorylayout(io::IO, value::Union{UInt8, UInt16, UInt32, UInt64, UInt128})
bits = AnnotatedString(bitstring(value))
for (; match) in eachmatch(r"0+", bits)
face!(match, :shadow)
end
if get(io, :compact, false) == true
print(io, bits)
else
println(io, "\n ", bits, ifelse(sizeof(value) > 4, "\n", ""),
S" {bold:=} $value")
end
end
function memorylayout(io::IO, value::Union{Int8, Int16, Int32, Int64, Int128})
bits = AnnotatedString(bitstring(value))
face!(bits[1:1], NUMBER_BIT_FACES.sign)
for (; match) in eachmatch(r"0+", bits)
if match.offset == 0
match = bits[2:match.ncodeunits]
end
face!(match, :shadow)
end
if get(io, :compact, false) == true
print(io, bits)
else
signstr = ifelse(value < 0, '-', '+')
println(io, "\n ", bits, ifelse(sizeof(value) > 4, "\n", ""),
S" {bold:=} {$(NUMBER_BIT_FACES.sign):$signstr}$(abs(value))")
end
end
memorylayout(io::IO, float::Float64) = floatlayout(io, float, 11)
memorylayout(io::IO, float::Float32) = floatlayout(io, float, 8)
memorylayout(io::IO, float::Float16) = floatlayout(io, float, 5)
memorylayout(io::IO, float::Core.BFloat16) = floatlayout(io, float, 8)
function floatlayout(io::IO, float::AbstractFloat, expbits::Int)
fsign, fexp, fmant = NUMBER_BIT_FACES.sign, NUMBER_BIT_FACES.exponent, NUMBER_BIT_FACES.mantissa
bitstr = bitstring(float)
hl_bits = S"{$fsign:$(bitstr[1])}{$fexp:$(bitstr[2:expbits+1])}{$fmant:$(bitstr[expbits+2:end])}"
if get(io, :compact, false) == true
print(io, hl_bits)
else
fracbits = 8 * sizeof(float) - expbits - 1
fracdp = round(Int, log10(2 ^ (fracbits + 1)))
maxexp = 2^(expbits - 1) - 1
sign = ifelse(bitstr[1] == '1', '-', '+')
bits = reinterpret(UInt64, Float64(float))
exponent = Int((bits >> 52) & Base.Ryu.EXP_MASK) - 1023
fraction = reinterpret(Float64, bits & Base.Ryu.MANTISSA_MASK | 0x3ff0000000000000)
expstr = cpad(if exponent == 1024
"Inf"
else "2^$exponent" end,
expbits - 1, ' ', RoundUp)
fracstr = cpad(if exponent == 1024
ifelse(fraction == 1.0, "1", "NaN")
else
Base.Ryu.writefixed(fraction, fracdp + 2)
end, fracbits, ' ', RoundUp)
hl_info = let eleft = (expbits - 3) ÷ 2
eright = (expbits - 3) - eleft
fleft = (fracbits - 3) ÷ 2
fright = (fracbits - 3) - fleft
S"{$fsign:╨}{$fexp:└$('─'^eleft)$('─'^eright)┘}{$fmant:└$('─'^fleft)$('─'^fright)┘}"
end
hl_vals = S"{$fsign,bold:$sign}{$fexp:$expstr}{bold:×}{$fmant:$fracstr}"
hl_more = S" {$fexp:exponent}$(' '^17){$fmant:mantissa / fraction}"
println(io, "\n ", hl_bits, " \n ", hl_info, "\n ", hl_vals,
S"\n {bold:=} ", if -8 < exponent < 8
Base.Ryu.writefixed(float, fracdp)
else Base.Ryu.writeexp(float, fracdp) end)
end
end
# ------------------
# Char/String
# ------------------
function memorylayout(io::IO, char::Char)
chunks = reinterpret(NTuple{4, UInt8}, reinterpret(UInt32, char) |> hton)
get(io, :compact, false) || print(io, "\n ")
nchunks = something(findlast(!iszero, chunks), 1)
byte0leading = [1, 3, 4, 5][nchunks]
ucodepoint = if Base.isoverlong(char)
Base.decode_overlong(char)
else
codepoint(char)
end
bit_spreads =
[[3, 4],
[3, 4, 4],
[4, 4, 4, 4],
[1, 4, 4, 4, 4, 4]
][nchunks]
ubytes = collect(uppercase(string(
ucodepoint, base=16, pad = length(bit_spreads))))
overlong_bytes = if Base.isoverlong(char)
1:min(something(findfirst(==('1'), ubytes), length(ubytes)) - 1,
length(ubytes) - 2)
else 1:0 end
chunk_coloring = [Pair{UnitRange{Int}, Symbol}[] for _ in 1:length(chunks)]
ustr = S"{bold:U+$(lpad(join(ubytes), 4, '0'))}"
for (i, b, color) in zip(1:length(ubytes),
collect(eachindex(ustr))[end-length(ubytes)+1:end],
Iterators.cycle(Iterators.reverse(FACE_CYCLE)))
if i in overlong_bytes
color = :error # overlong
end
face!(ustr[b:b], color)
end
if get(io, :compact, false) == true
print(io, ustr, ' ')
else let
current_bit = byte0leading
print(io, ' '^byte0leading)
for (i, ubyte, nbits, color) in zip(1:length(ubytes), ubytes, bit_spreads,
Iterators.cycle(Iterators.reverse(FACE_CYCLE)))
if i in overlong_bytes
color = :error # overlong
end
does_byte_jump = current_bit ÷ 8 < (current_bit + nbits) ÷ 8
clean_jump = does_byte_jump && (current_bit + nbits) % 8 == 0
next_bit = current_bit + nbits + does_byte_jump * 2
width = nbits + 3 * (does_byte_jump && !clean_jump)
byte_brace = if width <= 2
lpad(ubyte, width)
else
'┌' * cpad(ubyte, width-2, '─') * '┐'
end
print(io, S"{$color:$byte_brace}")
clean_jump && print(io, " ")
if does_byte_jump && !clean_jump
push!(chunk_coloring[1 + current_bit ÷ 8], (1 + current_bit % 8):8 => color)
push!(chunk_coloring[1 + next_bit ÷ 8], 3:mod1(next_bit, 8) => color)
else
push!(chunk_coloring[1 + current_bit ÷ 8],
(1 + current_bit % 8):mod1(current_bit + nbits, 8) => color)
end
current_bit = next_bit
end
print(io, "\n ")
end end
for (i, (chunk, coloring)) in enumerate(zip(chunks, chunk_coloring))
cbits = bitstring(chunk)
cstr = if i > nchunks
S"{shadow:$cbits}"
else
leadingbits = if i == 1; byte0leading else 2 end
leading = cbits[1:leadingbits]
rest = AnnotatedString(cbits[leadingbits+1:end])
for (; match) in eachmatch(r"1+", rest)
face!(match, :underline)
end
cstr = S"{shadow:$leading}$rest"
for (range, color) in coloring
face!(cstr, range, color)
end
cstr
end
print(io, cstr, ' ')
end
if get(io, :compact, false) != true
println(io)
for chunk in chunks
byte = lpad(string(chunk, base=16), 2, '0')
print(io, S" {shadow:└─0x$(byte)─┘}")
end
print(io, "\n = ", ustr)
Base.isoverlong(char) && print(io, S" {error:[overlong]}")
println(io)
end
end
const CONTROL_CHARACTERS =
('\x00' => ("NULL", "Null character", "Originally the code of blank paper tape and used as padding to slow transmission. Now often used to indicate the end of a string in C-like languages."),
'\x01' => ("SOH", "Start of Heading", ""),
'\x02' => ("SOT", "Start of Text", ""),
'\x03' => ("ETX", "End of Text", ""),
'\x04' => ("EOT", "End of Transmission", ""),
'\x05' => ("ENQ", "Enquiry", "Trigger a response at the receiving end, to see if it is still present."),
'\x06' => ("ACK", "Acknowledge", "Indication of successful receipt of a message."),
'\x07' => ("BEL", "Bell", "Call for attention from an operator."),
'\x08' => ("HBS", "Backspace", "Move one position leftwards. Next character may overprint or replace the character that was there."),
'\x09' => ("HT", "Horizontal Tab", "Move right to the next tab stop."),
'\x0a' => ("LF", "Line Feed", "Move down to the same position on the next line (some devices also moved to the left column)."),
'\x0b' => ("VT", "Vertical Tab", "Move down to the next vertical tab stop. "),
'\x0c' => ("FF", "Form Feed", "Move down to the top of the next page. "),
'\x0d' => ("CR", "Carriage Return", "Move to column zero while staying on the same line."),
'\x0e' => ("SO", "Shift Out", "Switch to an alternative character set."),
'\x0f' => ("SI", "Shift In", "Return to regular character set after SO."),
'\x10' => ("DLE", "Data Link Escape", "Cause a limited number of contiguously following characters to be interpreted in some different way."),
'\x11' => ("DC1", "Device Control One (XON)", "Used by teletype devices for the paper tape reader and tape punch. Became the de-facto standard for software flow control, now obsolete."),
'\x12' => ("DC2", "Device Control Two", "Used by teletype devices for the paper tape reader and tape punch. Became the de-facto standard for software flow control, now obsolete."),
'\x13' => ("DC3", "Device Control Three (XOFF)", "Used by teletype devices for the paper tape reader and tape punch. Became the de-facto standard for software flow control, now obsolete."),
'\x14' => ("DC4", "Device Control Four", "Used by teletype devices for the paper tape reader and tape punch. Became the de-facto standard for software flow control, now obsolete."),
'\x15' => ("NAK", "Negative Acknowledge", "Negative response to a sender, such as a detected error. "),
'\x16' => ("SYN", "Synchronous Idle", "A transmission control character used by a synchronous transmission system in the absence of any other character (idle condition) to provide a signal from which synchronism may be achieved or retained between data terminal equipment."),
'\x17' => ("ETB", "End of Transmission Block", "End of a transmission block of data when data are divided into such blocks for transmission purposes."),
'\x18' => ("CAN", "Cancel", "A character, or the first character of a sequence, indicating that the data preceding it is in error. As a result, this data is to be ignored. The specific meaning of this character must be defined for each application and/or between sender and recipient."),
'\x19' => ("EM", "End of Medium", "Indicates on paper or magnetic tapes that the end of the usable portion of the tape had been reached."),
'\x1a' => ("SUB", "Substitute/Control-Z", "A control character used in the place of a character that has been found to be invalid or in error. SUB is intended to be introduced by automatic means."),
'\x1b' => ("ESC", "Escape", "A control character which is used to provide additional control functions. It alters the meaning of a limited number of contiguously following bit combinations. The use of this character is specified in ISO-2022."),
'\x1c' => ("FS", "File Separator", "Used to separate and qualify data logically; its specific meaning has to be specified for each application. If this character is used in hierarchical order, it delimits a data item called a file. "),
'\x1d' => ("GS", "Group Separator", "Used to separate and qualify data logically; its specific meaning has to be specified for each application. If this character is used in hierarchical order, it delimits a data item called a group."),
'\x1e' => ("RG", "Record Separator", "Used to separate and qualify data logically; its specific meaning has to be specified for each application. If this character is used in hierarchical order, it delimits a data item called a record."),
'\x1f' => ("US", "Unit Separator", "Used to separate and qualify data logically; its specific meaning has to be specified for each application. If this character is used in hierarchical order, it delimits a data item called a unit."),
'\x7f' => ("DEL", "Delete", "Originally used to delete characters on punched tape by punching out all the holes."))
function elaboration(io::IO, char::Char)
c0index = findfirst(c -> first(c) == char, CONTROL_CHARACTERS)
stychr = S"{julia_char:$(sprint(show, char))}"
if !isnothing(c0index)
cshort, cname, cinfo = last(CONTROL_CHARACTERS[c0index])
println(io, "\n Control character ", stychr, ": ", cname, " ($cshort)",
ifelse(isempty(cinfo), "", "\n "), cinfo)
elseif isascii(char)
kind = if char in 'a':'z'
"lowercase letter"
elseif char in 'A':'Z'
"uppercase letter"
elseif char in '0':'9'
"numeral"
elseif char == ' '
"space"
elseif char in ('(', ')', '[', ']', '{', '}', '«', '»')
"parenthesis"
elseif char in ('!':'/'..., ':':'@'..., '\\', '^', '_', '`', '|', '~')
"punctuation"
end
println(io, "\n ASCII $kind ", stychr)
elseif char in ('Ç':'ø'..., 'Ø', 'á':'Ñ'..., 'Á':'À', 'ã', 'Ã', 'ð':'Ï'..., 'Ó':'Ý')
println(io, "\n Extended ASCII accented letter ", stychr,
S" ({julia_number:0x$(string(UInt8(char), base=16))})")
elseif Base.isoverlong(char)
elseif codepoint(char) in 128:255
println(io, "\n Extended ASCII symbol ", stychr,
S" ({shadow:0x$(string(Int(char), base=16))})")
else
catstr = Base.Unicode.category_string(char)
catabr = Base.Unicode.category_abbrev(char)
println(io, S"\n Unicode $stychr, category: $catstr ($catabr)")
end
end
# TODO struct