commit ee717d4ee98344558d073ff37e094e9286c0982b Author: TEC Date: Wed Mar 13 02:09:16 2024 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b067edd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/Manifest.toml diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..7ca94c6 --- /dev/null +++ b/Project.toml @@ -0,0 +1,12 @@ +name = "About" +uuid = "69d22d85-9f48-4c46-bbbe-7ad8341ff72a" +authors = ["TEC "] +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" diff --git a/src/About.jl b/src/About.jl new file mode 100644 index 0000000..ea00fec --- /dev/null +++ b/src/About.jl @@ -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 diff --git a/src/functions.jl b/src/functions.jl new file mode 100644 index 0000000..ab15254 --- /dev/null +++ b/src/functions.jl @@ -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) diff --git a/src/types.jl b/src/types.jl new file mode 100644 index 0000000..19c7150 --- /dev/null +++ b/src/types.jl @@ -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 diff --git a/src/utils.jl b/src/utils.jl new file mode 100644 index 0000000..fca4705 --- /dev/null +++ b/src/utils.jl @@ -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 diff --git a/src/values.jl b/src/values.jl new file mode 100644 index 0000000..900ce63 --- /dev/null +++ b/src/values.jl @@ -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