Initial commit

This commit is contained in:
TEC 2024-03-13 02:09:16 +08:00
commit ee717d4ee9
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
7 changed files with 499 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/Manifest.toml

12
Project.toml Normal file
View File

@ -0,0 +1,12 @@
name = "About"
uuid = "69d22d85-9f48-4c46-bbbe-7ad8341ff72a"
authors = ["TEC <git@tecosaur.net>"]
version = "0.1.0"
[deps]
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
[compat]
InteractiveUtils = "1.11.0"
StyledStrings = "1.11.0"

33
src/About.jl Normal file
View File

@ -0,0 +1,33 @@
module About
using Base: AnnotatedString, AnnotatedIOBuffer
using StyledStrings: @styled_str, Face, face!
using InteractiveUtils
export about
include("utils.jl")
include("functions.jl")
include("types.jl")
include("values.jl")
"""
about(fn::Function, [signature::Tuple])
about(typ::Type)
about(obj::Any)
Display information on the particular nature of the argument, whether
it be a function, type, or value.
"""
function about end
about(x) = about(stderr, x)
function about(xs...)
if first(xs) == stderr
throw(MethodError(about, xs))
else
about(stderr, xs...)
end
end
end

67
src/functions.jl Normal file
View File

@ -0,0 +1,67 @@
function about(io::IO, fn::Function)
source = Main.InteractiveUtils.which(parentmodule(fn), Symbol(fn))
methodmodules = getproperty.(methods(fn).ms, :module)
others = setdiff(methodmodules, [source])
print(io, Base.summary(fn), styled"\n Defined in {bright_red:$source}")
if length(others) > 0
print(io, styled"{shadow:({emphasis:$(sum(Ref(source) .=== methodmodules))})} extended in ")
for (i, oth) in enumerate(others)
print(io, styled"{bright_red:$oth}{shadow:({emphasis:$(sum(Ref(oth) .=== methodmodules))})}")
if length(others) == 2 && i == 1
print(io, " and ")
elseif length(others) > 2 && i < length(others)-1
print(io, ", ")
elseif length(others) > 2 && i == length(others)-1
print(io, ", and ")
end
end
end
print(io, ".\n")
end
function about(io::IO, method::Method)
fn, sig = first(method.sig.types).instance, Tuple{map(Base.unwrap_unionall, method.sig.types[2:end])...}
show(io, method)
println(io)
print_effects(io, fn, sig)
end
function about(io::IO, fn::Function, @nospecialize(sig::Type{<:Tuple}))
about(io, fn); println(io)
ms = methods(fn, sig)
if isempty(ms)
print(io, styled" {error:!} No methods matched $fn($(join(collect(Tuple{Int64, Int64, Int64}.types), \", \")))")
return
end
println(io, styled" Matched {emphasis:$(length(ms))} method$(ifelse(length(ms) > 1, \"s\", \"\")):")
for method in ms
println(io, " ", sprint(show, method, context=IOContext(io)))
end
print_effects(io, fn, sig)
end
function print_effects(io::IO, fn::Function, @nospecialize(sig::Type{<:Tuple}))
effects = Base.infer_effects(fn, sig)
ATRUE, AFALSE = Core.Compiler.ALWAYS_TRUE, Core.Compiler.ALWAYS_FALSE
echar(t::UInt8) = get(Dict(ATRUE => '✔', AFALSE => '✗'), t, '?')
echar(b::Bool) = ifelse(b, '✔', '✗')
eface(t::UInt8) = get(Dict(ATRUE => :success, AFALSE => :error), t, :warning)
eface(b::Bool) = ifelse(b, :success, :error)
hedge(t::UInt8) = get(Dict(ATRUE => styled"guaranteed to",
AFALSE => styled"{italic:not} guaranteed to"), t,
styled"???")
hedge(b::Bool) = hedge(ifelse(b, ATRUE, AFALSE))
println(io)
for (effect, description) in
[(:consistent, "return or terminate consistently"),
(:effect_free, "be free from externally semantically visible side effects"),
(:nothrow, "never throw an exception"),
(:terminates, "terminate")]
e = getfield(effects, effect)
print(io, styled" {$(eface(e)):{bold:$(echar(e))} $(rpad(effect, 11))} {shadow:$(hedge(e)) $description}")
print('\n')
end
end
about(io::IO, fn::Function, sig::NTuple{N, <:Type}) where {N} = about(io, fn, Tuple{sig...})
about(io::IO, fn::Function, sig::Type...) = about(io, fn, sig)

116
src/types.jl Normal file
View File

@ -0,0 +1,116 @@
const POINTER_FACE = :cyan # should not appear in `FACE_CYCLE`
struct FieldInfo
i::Int
face::Union{Symbol, Face}
offset::Int
size::Int
contentsize::Int
ispointer::Bool
name::Union{Symbol, Int}
type::Type
end
function structinfo(T::DataType)
map(1:fieldcount(T)) do i
size = Int(if i < fieldcount(T)
fieldoffset(T, i+1)
else
sizeof(T)
end - fieldoffset(T, i))
contentsize = try sizeof(fieldtype(T, i)) catch _ 0 end
if contentsize > size # Pointer?
contentsize = 0
end
FieldInfo(i, FACE_CYCLE[i % length(FACE_CYCLE) + 1],
fieldoffset(T, i) |> Int, # offset
size, contentsize,
contentsize == 0, # ispointer
fieldname(T, i), fieldtype(T, i))
end
end
function about(io::IO, type::Type)
if isprimitivetype(type)
print(io, "Primitive ")
elseif isconcretetype(type)
print(io, "Concrete ")
if Base.datatype_haspadding(type)
print(io, styled"{shadow:(padded)} ")
end
elseif isabstracttype(type)
print(io, "Abstract ")
end
if Base.issingletontype(type)
print(io, "singleton ")
end
print(Base.summary(type))
println(io, styled" defined in {bright_red:$(type.name.module)}, $(join(humansize(sizeof(type))))",
"\n ", supertypestr(type))
(!isstructtype(type) || fieldcount(type) == 0) && return
println(io, styled"\nStruct with {bold:$(fieldcount(type))} fields:")
fieldinfo = AnnotatedString[]
if type isa DataType
sinfo = structinfo(type)
namepad = maximum(fi -> textwidth(string(fi.name)), sinfo) + 1
for (; face, name, type, ispointer) in sinfo
push!(fieldinfo, rpad(styled"{$face:$name}", namepad) * styled"{$POINTER_FACE:$(ifelse(ispointer, \"*\", \" \"))}$type")
end
else
for (; name, type) in structinfo(type)
push!(fieldinfo, styled"$name{shadow:::$type}")
end
end
if length(fieldinfo) < 32
columnlist(io, fieldinfo, maxcols=1)
else
columnlist(io, fieldinfo, spacing=3)
end
if type isa DataType
println(io)
memorylayout(io, type)
end
end
supertypestr(type::Type) =
join(string.(supertypes(type)), styled" {red:<:} ")
function memorylayout(io::IO, type::DataType)
si = structinfo(type)
!isempty(si) || return
memstep = memstep = gcd((getfield.(si, :size), getfield.(si, :contentsize)) |>
Iterators.flatten |> collect)
memscale = max(1, floor(Int, 70/(sizeof(type)/memstep)))
bars = AnnotatedString[]
descs = AnnotatedString[]
for (; i, size, contentsize, ispointer) in si
color = FACE_CYCLE[i % length(FACE_CYCLE) + 1]
width = max(2, memscale * size÷memstep)
color = FACE_CYCLE[i % length(FACE_CYCLE) + 1]
fsize, funits = humansize(size)
desc = if ispointer
cpad(styled" {$color:*} ", width)
elseif contentsize < size
csize, cunits = humansize(contentsize)
psize, punits = humansize(size - contentsize)
cpad(styled" {$color:$csize$cunits}{shadow:+$psize$punits} ", width, ' ', RoundUp)
else
cpad(styled" {$color:$fsize$funits} ", width)
end
push!(descs, desc)
width = textwidth(desc)
contentwidth = round(Int, width * contentsize / size)
bar = styled"{$color:$('■'^contentwidth)}"
if contentsize < size
color = if ispointer; :cyan else :light_black end
paddwidth = width - contentwidth
bar *= styled"{$color:$('■'^paddwidth)}"
end
push!(bars, bar)
end
multirow_wrap(io, permutedims(hcat(bars, descs)))
if any(getfield.(si, :ispointer))
print(io, styled"\n {$POINTER_FACE:*} = {$POINTER_FACE:Pointer} {light:(8B)}")
end
println(io)
end

83
src/utils.jl Normal file
View File

@ -0,0 +1,83 @@
const FACE_CYCLE = [:bright_blue, :bright_green, :bright_yellow, :bright_magenta]
function humansize(bytes::Integer)
units = ("B", "kB", "MB", "GB")
magnitude = floor(Int, log(1024, 1 + bytes))
if 10 < bytes < 10*1024^magnitude
round(bytes / 1024^magnitude, digits=1)
else
round(Int, bytes / 1024^magnitude)
end, units[1+magnitude]
end
function cpad(s, n::Integer, pad::Union{AbstractString, AbstractChar}=' ', r::RoundingMode = RoundToZero)
rpad(lpad(s, div(n+textwidth(s), 2, r), pad), n, pad)
end
function struncate(str::AbstractString, maxwidth::Int, joiner::AbstractString = "", mode::Symbol = :center)
textwidth(str) <= maxwidth && return str
left, right = firstindex(str) - 1, lastindex(str) + 1
width = textwidth(joiner)
while width < maxwidth
if mode (:right, :center)
left = nextind(str, left)
width += textwidth(str[left])
end
if mode (:left, :center) && width < maxwidth
right = prevind(str, right)
width += textwidth(str[right])
end
end
str[begin:left] * joiner * str[right:end]
end
function columnlist(io::IO, entries::Vector{<:AbstractString};
maxcols::Int=8, maxwidth::Int=last(displaysize(io)),
prefix::AbstractString = styled"{emphasis:•} ", spacing::Int=2)
thecolumns = Vector{eltype(entries)}[]
thecolwidths = Int[]
for ncols in 1:maxcols
columns = Vector{eltype(entries)}[]
for col in Iterators.partition(entries, length(entries) ÷ ncols)
push!(columns, collect(col))
end
widths = map.(textwidth, columns)
colwidths = map(maximum, widths)
if sum(colwidths) + ncols * textwidth(prefix) + (1 - ncols) * spacing > maxwidth
break
else
thecolumns, thecolwidths = columns, colwidths
end
end
for rnum in 1:length(first(thecolumns))
for cnum in 1:length(thecolumns)
rnum > length(thecolumns[cnum]) && continue
cnum > 1 && print(io, ' '^spacing)
print(io, prefix, rpad(thecolumns[cnum][rnum], thecolwidths[cnum]))
end
println(io)
end
end
function multirow_wrap(io::IO, cells::Matrix{<:AbstractString};
indent::AbstractString = " ", maxwidth::Int=last(displaysize(io)))
widths = map(textwidth, cells)
colwidths = maximum(widths, dims=1)
thiscol = textwidth(indent)
segments = UnitRange{Int}[1:0]
for (i, (col, width)) in enumerate(zip(eachcol(cells), colwidths))
if thiscol + width > maxwidth
push!(segments, last(last(segments))+1:i-1)
thiscol = textwidth(indent) + width
else
thiscol += width
end
end
push!(segments, last(last(segments))+1:size(cells, 2))
filter!(!isempty, segments)
for segment in segments
for row in eachrow(cells[:, segment])
println(io, indent, join(row))
end
end
end

187
src/values.jl Normal file
View File

@ -0,0 +1,187 @@
const NUMBER_BIT_FACES = (
sign = :bright_blue,
exponent = :bright_green,
mantissa = :bright_red
)
function about(io::IO, value::T) where {T}
print(io, Base.summary(value))
ismutable(value) && print(io, " (mutable)")
directbytes = sizeof(value)
indirectbytes = Base.summarysize(value)
print(io, styled", {bold:$(join(humansize(directbytes)))}")
if indirectbytes > directbytes
print(io, styled" referencing {bold:$(join(humansize(indirectbytes)))}")
end
print(io, styled" ({red:<:} ", supertypestr(supertype(T)), ")")
println(io)
memorylayout(io, value)
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
sinfo = structinfo(T)
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()
if ispointer
try
pt = pointer(getfield(value, name))
push!(freprs, styled"{$POINTER_FACE,light:$pt}")
catch
push!(freprs, styled"{$POINTER_FACE:Ptr?}")
end
else
memorylayout(IOContext(aio, :compact => true), getfield(value, name))
push!(freprs, read(seekstart(aio), AnnotatedString))
end
truncate(aio, 0)
show(IOContext(aio, :compact => true), getfield(value, name))
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, ' ',
styled"{$face:$(lpad(name, namewidth)){shadow:::}$(rpad(struncate(type, typewidth, \"\", :right), typewidth)) $(lpad(size, sizewidth))}",
' ', rpad(struncate(brepr, reprwidth, styled" {shadow:…} "), reprwidth),
' ', face!(struncate(shown, showwidth, styled" {shadow:…} "), face))
end
println(io)
memorylayout(io, T)
end
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, styled" {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", ""),
styled" {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", ""),
styled" {bold:=} {$(NUMBER_BIT_FACES.sign):$signstr}$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 = styled"{$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
styled"{$fsign:╨}{$fexp:└$('─'^eleft)$('─'^eright)┘}{$fmant:└$('─'^fleft)$('─'^fright)┘}"
end
hl_vals = styled"{$fsign,bold:$sign}{$fexp:$expstr}{bold:×}{$fmant:$fracstr}"
hl_more = styled" {$fexp:exponent}$(' '^17){$fmant:mantissa / fraction}"
println(io, "\n ", hl_bits, " \n ", hl_info, "\n ", hl_vals,
styled"\n {bold:=} ", if -8 < exponent < 8
Base.Ryu.writefixed(float, fracdp)
else Base.Ryu.writeexp(float, fracdp) end)
end
end
function memorylayout(io::IO, char::Char)
chunks = reinterpret(NTuple{4, UInt8}, reinterpret(UInt32, char) |> hton)
get(io, :compact, false) || print(io, "\n ")
for chunk in chunks
cstr = AnnotatedString(bitstring(chunk))
if iszero(chunk)
face!(cstr, :shadow)
else
for (; match) in eachmatch(r"1+", cstr)
face!(match, :bright_green)
end
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, styled" {shadow:└─0x$(byte)─┘}")
end
println(io)
end
end
# TODO char
# TODO struct