Compare commits
24 Commits
39878d26ba
...
8d695e5e28
Author | SHA1 | Date |
---|---|---|
TEC | 8d695e5e28 | |
TEC | 4967bcd976 | |
TEC | d4e923c23c | |
TEC | 172a33cb1f | |
TEC | 9ab30d97f0 | |
TEC | 04869e3b4c | |
TEC | 1b563854d9 | |
TEC | 35aeef29b0 | |
TEC | 1305239f01 | |
TEC | 7dbce9529e | |
TEC | 4e23083d0f | |
TEC | 70871a6b60 | |
TEC | b0c7025372 | |
TEC | e0025cd0a2 | |
TEC | fa0a49b606 | |
TEC | 201d9db07d | |
TEC | 4cef33ae79 | |
TEC | 40f29a9bd0 | |
TEC | 19fe198ba4 | |
TEC | d67e9a3bce | |
TEC | 7dd9352d70 | |
TEC | ae3e6b7403 | |
Sasha Demin | df6f20fbf6 | |
Sasha Demin | 6ac75ded73 |
|
@ -5,8 +5,17 @@ version = "0.1.0"
|
|||
|
||||
[deps]
|
||||
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
|
||||
JuliaSyntaxHighlighting = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011"
|
||||
StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
|
||||
|
||||
[weakdeps]
|
||||
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
|
||||
|
||||
[extensions]
|
||||
PkgExt = "Pkg"
|
||||
|
||||
[compat]
|
||||
InteractiveUtils = "1.11.0"
|
||||
JuliaSyntaxHighlighting = "1.11.0"
|
||||
Pkg = "1.11.0"
|
||||
StyledStrings = "1.11.0"
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
module PkgExt
|
||||
|
||||
using Pkg
|
||||
using StyledStrings
|
||||
import About: about_pkg, columnlist
|
||||
|
||||
function about_pkg(io::IO, pkg::Base.PkgId, mod::Module)
|
||||
isnothing(pkgversion(mod)) ||
|
||||
print(io, styled" Version {about_module:$(pkgversion(mod))}")
|
||||
srcdir = pkgdir(mod)
|
||||
if isnothing(srcdir)
|
||||
print(io, styled" (builtin)")
|
||||
else
|
||||
srcdir = Base.fixup_stdlib_path(srcdir)
|
||||
srcdir = something(Base.find_source_file(srcdir), srcdir)
|
||||
srcdir = contractuser(srcdir)
|
||||
print(io, styled" loaded from {light,underline:$srcdir}")
|
||||
end
|
||||
println(io)
|
||||
isnothing(srcdir) && return
|
||||
manifest_file = Pkg.Types.manifestfile_path(pkgdir(mod))
|
||||
thedeps = if !isnothing(manifest_file) && isfile(manifest_file)
|
||||
Pkg.Types.read_manifest(manifest_file).deps
|
||||
else
|
||||
Pkg.dependencies()
|
||||
end
|
||||
directdeps = if haskey(thedeps, pkg.uuid)
|
||||
listdeps(thedeps, pkg.uuid)
|
||||
else
|
||||
collect(keys(thedeps))
|
||||
end
|
||||
isempty(directdeps) && return
|
||||
depstrs = map(directdeps) do dep
|
||||
nindirect = length(alldeps(thedeps, dep))
|
||||
if nindirect > 0
|
||||
styled"$(thedeps[dep].name) {shadow:(+$nindirect)}"
|
||||
else
|
||||
styled"$(thedeps[dep].name)"
|
||||
end
|
||||
end
|
||||
indirect_depcount = length(alldeps(thedeps, pkg.uuid) ∪ directdeps) - length(depstrs)
|
||||
indirect_info = if indirect_depcount > 0
|
||||
styled" {shadow:(+$indirect_depcount indirectly)}"
|
||||
else styled"" end
|
||||
println(io, styled"\n{bold:Directly depends on {emphasis:$(length(directdeps))} \
|
||||
package$(ifelse(length(directdeps) == 1, \"\", \"s\"))}$indirect_info:")
|
||||
columnlist(io, depstrs)
|
||||
end
|
||||
|
||||
function listdeps(deps::Dict{Base.UUID, Pkg.Types.PackageEntry}, pkg::Base.UUID)
|
||||
if haskey(deps, pkg)
|
||||
collect(values(deps[pkg].deps))
|
||||
else
|
||||
Base.UUID[]
|
||||
end
|
||||
end
|
||||
|
||||
function listdeps(deps::Dict{Base.UUID, Pkg.API.PackageInfo}, pkg::Base.UUID)
|
||||
if haskey(deps, pkg)
|
||||
collect(values(deps[pkg].dependencies))
|
||||
else
|
||||
Base.UUID[]
|
||||
end
|
||||
end
|
||||
|
||||
function alldeps(deps::Dict{Base.UUID, <:Union{Pkg.Types.PackageEntry, Pkg.API.PackageInfo}}, pkg::Base.UUID)
|
||||
depcheck = listdeps(deps, pkg)
|
||||
depcollection = Set{Base.UUID}()
|
||||
while !isempty(depcheck)
|
||||
id = popfirst!(depcheck)
|
||||
id in depcollection && continue
|
||||
append!(depcheck, listdeps(deps, id))
|
||||
push!(depcollection, id)
|
||||
end
|
||||
collect(depcollection)
|
||||
end
|
||||
|
||||
end
|
50
src/About.jl
50
src/About.jl
|
@ -1,9 +1,12 @@
|
|||
module About
|
||||
|
||||
using Base: AnnotatedString, AnnotatedIOBuffer
|
||||
using StyledStrings: @styled_str, Face, face!
|
||||
using StyledStrings: @styled_str, Face, face!, addface!
|
||||
using JuliaSyntaxHighlighting: highlight
|
||||
using InteractiveUtils
|
||||
|
||||
const var"@S_str" = var"@styled_str"
|
||||
|
||||
export about
|
||||
|
||||
include("utils.jl")
|
||||
|
@ -12,12 +15,13 @@ include("types.jl")
|
|||
include("values.jl")
|
||||
|
||||
"""
|
||||
about(fn::Function, [signature::Tuple])
|
||||
about(typ::Type)
|
||||
about(obj::Any)
|
||||
about([io::IO], fn::Function, [argtypes::Type...])
|
||||
about([io::IO], typ::Type)
|
||||
about([io::IO], val::Any)
|
||||
|
||||
Display information on the particular nature of the argument, whether
|
||||
it be a function, type, or value.
|
||||
Display information on the particular nature of the argument, whatever it may be.
|
||||
|
||||
TODO mention the mechanisms for extension here too.
|
||||
"""
|
||||
function about end
|
||||
|
||||
|
@ -30,4 +34,38 @@ function about(xs...)
|
|||
end
|
||||
end
|
||||
|
||||
"""
|
||||
memorylayout(io::IO, T::DataType)
|
||||
memorylayout(io::IO, val::T)
|
||||
|
||||
Print to `io` the memory layout of the type `T`, or `val` a particular instance
|
||||
of the type.
|
||||
|
||||
Specialised implementations should be implemented freely to enhance the utility
|
||||
and prettiness of the display.
|
||||
"""
|
||||
function memorylayout end
|
||||
|
||||
"""
|
||||
elaboration(::IO, x::Any)
|
||||
|
||||
Elaborate on `x` to io, providing extra information that might be of interest
|
||||
seperately from `about` or `memorylayout`.
|
||||
|
||||
Specialised implementations should be implemented freely to enhance the utility
|
||||
and prettiness of the display.
|
||||
|
||||
By convention, this is not invoked when displaying `x` compactly.
|
||||
"""
|
||||
elaboration(::IO, ::Any) = nothing
|
||||
|
||||
const ABOUT_FACES = [
|
||||
:about_module => Face(foreground=:bright_red),
|
||||
:about_pointer => Face(foreground=:cyan),
|
||||
:about_count => Face(weight=:bold),
|
||||
:about_bytes => Face(weight=:bold),
|
||||
]
|
||||
|
||||
__init__() = foreach(addface!, ABOUT_FACES)
|
||||
|
||||
end
|
||||
|
|
165
src/functions.jl
165
src/functions.jl
|
@ -2,11 +2,12 @@ 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}")
|
||||
fn_name, fn_extra = split(Base.summary(fn), ' ', limit=2)
|
||||
print(io, S"{julia_funcall:$fn_name} $fn_extra\n Defined in {about_module:$source}")
|
||||
if length(others) > 0
|
||||
print(io, styled"{shadow:({emphasis:$(sum(Ref(source) .=== methodmodules))})} extended in ")
|
||||
print(io, S"{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))})}")
|
||||
print(io, S"{about_module:$oth}{shadow:({emphasis:$(sum(Ref(oth) .=== methodmodules))})}")
|
||||
if length(others) == 2 && i == 1
|
||||
print(io, " and ")
|
||||
elseif length(others) > 2 && i < length(others)-1
|
||||
|
@ -19,6 +20,23 @@ function about(io::IO, fn::Function)
|
|||
print(io, ".\n")
|
||||
end
|
||||
|
||||
function about(io::IO, @nospecialize(cfn::ComposedFunction))
|
||||
print(io, S"{bold:Composed function:} ")
|
||||
fnstack = Function[]
|
||||
function decompose!(fnstk, c::ComposedFunction)
|
||||
decompose!(fnstk, c.outer)
|
||||
decompose!(fnstk, c.inner)
|
||||
end
|
||||
decompose!(fnstk, c::Function) = push!(fnstk, c)
|
||||
decompose!(fnstack, cfn)
|
||||
join(io, map(f -> S"{julia_funcall:$f}", fnstack), S" {julia_operator:∘} ")
|
||||
println(io)
|
||||
for fn in fnstack
|
||||
print(io, S" {emphasis:•} ")
|
||||
about(io, fn)
|
||||
end
|
||||
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)
|
||||
|
@ -26,40 +44,127 @@ function about(io::IO, method::Method)
|
|||
print_effects(io, fn, sig)
|
||||
end
|
||||
|
||||
function about(io::IO, fn::Function, @nospecialize(sig::Type{<:Tuple}))
|
||||
function about(io::IO, fn::Function, @nospecialize(argtypes::Type{<:Tuple}))
|
||||
about(io, fn); println(io)
|
||||
ms = methods(fn, sig)
|
||||
ms = methods(fn, argtypes)
|
||||
if isempty(ms)
|
||||
print(io, styled" {error:!} No methods matched $fn($(join(collect(Tuple{Int64, Int64, Int64}.types), \", \")))")
|
||||
fncall = highlight("$fn($(join(collect(argtypes.types), ", ")))")
|
||||
println(io, S" {error:!} No methods matched $fncall")
|
||||
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)))
|
||||
rinfo = let rtypes = Base.return_types(fn, argtypes) # HACK: this is technically private API
|
||||
unique!(rtypes)
|
||||
for i in eachindex(rtypes), j in eachindex(rtypes)
|
||||
Tᵢ, Tⱼ = rtypes[i], rtypes[j]
|
||||
if Tᵢ <: Tⱼ
|
||||
rtypes[i] = Tⱼ
|
||||
elseif Tⱼ <: Tᵢ
|
||||
rtypes[j] = Tᵢ
|
||||
end
|
||||
end
|
||||
unique!(rtypes)
|
||||
sort!(rtypes, by=length ∘ supertypes)
|
||||
join(map(t -> S"{julia_type:$t}", rtypes), ", ")
|
||||
end
|
||||
println(io, S" Matched {emphasis:$(length(ms))} method$(ifelse(length(ms) > 1, \"s\", \"\")) {julia_type:::} $rinfo")
|
||||
for method in ms
|
||||
mcall, msrc = split(sprint(show, method), " @ ")
|
||||
msrcinfo = match(r"^([A-Z][A-Za-z0-9\.]+) (.+)$", msrc)
|
||||
msrcpretty = if isnothing(msrcinfo)
|
||||
S"{shadow,underline:$msrc}"
|
||||
else
|
||||
mmod, mfile = msrcinfo.captures
|
||||
S"{about_module:$mmod} {shadow,underline:$mfile}"
|
||||
end
|
||||
println(io, S" {light:$(highlight(mcall))} {shadow,bold:@} $msrcpretty")
|
||||
end
|
||||
println(io)
|
||||
@static if VERSION >= v"1.8"
|
||||
about(io, Base.infer_effects(fn, argtypes))
|
||||
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')
|
||||
@static if VERSION >= v"1.8"
|
||||
struct CompatibleCoreCompilerConstants end
|
||||
|
||||
function Base.getproperty(::CompatibleCoreCompilerConstants, name::Symbol)
|
||||
if isdefined(Core.Compiler, name)
|
||||
getglobal(Core.Compiler, name)
|
||||
end
|
||||
end
|
||||
|
||||
const C4 = CompatibleCoreCompilerConstants()
|
||||
|
||||
function about(io::IO, effects::Core.Compiler.Effects)
|
||||
function effectinfo(io::IO, field::Symbol, name::String, labels::Pair{<:Union{UInt8, Bool, Nothing}, AnnotatedString{String}}...;
|
||||
prefix::AbstractString = "", suffix::AbstractString = "")
|
||||
hasproperty(effects, field) || return
|
||||
value = @static if VERSION >= v"1.9"
|
||||
getproperty(effects, field)
|
||||
else # v1.8
|
||||
getproperty(effects, field).state
|
||||
end
|
||||
icon, accent = if value == C4.ALWAYS_TRUE || value === true
|
||||
'✔', :success
|
||||
elseif value == C4.ALWAYS_FALSE || value === false
|
||||
'✗', :error
|
||||
else
|
||||
'~', :warning
|
||||
end
|
||||
msg = S"{bold,italic,grey:???}"
|
||||
for (id, label) in labels
|
||||
if id == value
|
||||
msg = label
|
||||
break
|
||||
end
|
||||
end
|
||||
name_pad_width = 13
|
||||
dispwidth = last(displaysize(io))
|
||||
declr = S" {bold,$accent:$icon $(rpad(name, name_pad_width))} "
|
||||
print(io, declr)
|
||||
indent = name_pad_width + 5
|
||||
desc = S"{grey:$prefix$(ifelse(isempty(prefix), \"\", \" \"))$msg$(ifelse(isempty(suffix), \"\", \" \"))$suffix}"
|
||||
desclines = wraplines(desc, dispwidth - indent, indent)
|
||||
for (i, line) in enumerate(desclines)
|
||||
i > 1 && print(io, ' '^indent)
|
||||
println(io, line)
|
||||
end
|
||||
end
|
||||
println(io, S"{bold:Method effects:}")
|
||||
effectinfo(io, :consistent, "consistent",
|
||||
C4.ALWAYS_TRUE => S"guaranteed to",
|
||||
C4.ALWAYS_FALSE => S"might {italic:not}",
|
||||
C4.CONSISTENT_IF_NOTRETURNED => S"when the return value never involved newly allocated mutable objects, will",
|
||||
C4.CONSISTENT_IF_INACCESSIBLEMEMONLY => S"when {code:inaccessible memory only} is also proven, will",
|
||||
suffix = "return or terminate consistently")
|
||||
effectinfo(io, :effect_free, "effect free",
|
||||
C4.ALWAYS_TRUE => S"guaranteed to be",
|
||||
C4.ALWAYS_FALSE => S"might {italic:not} be",
|
||||
C4.EFFECT_FREE_IF_INACCESSIBLEMEMONLY => S"when {code:inaccessible memory only} is also proven, is",
|
||||
suffix = "free from externally semantically visible side effects")
|
||||
effectinfo(io, :nothrow, "no throw",
|
||||
C4.ALWAYS_TRUE => S"guaranteed to never",
|
||||
C4.ALWAYS_FALSE => S"may",
|
||||
suffix = "throw an exception")
|
||||
effectinfo(io, :terminates, "terminates",
|
||||
C4.ALWAYS_TRUE => S"guaranteed to",
|
||||
C4.ALWAYS_FALSE => S"might {italic:not}",
|
||||
suffix = "always terminate")
|
||||
effectinfo(io, :notaskstate, "no task state",
|
||||
C4.ALWAYS_TRUE => S"guaranteed not to access task state (allowing migration between tasks)",
|
||||
C4.ALWAYS_FALSE => S"may access task state (preventing migration between tasks)")
|
||||
effectinfo(io, :inaccessiblememonly, "inaccessible memory only",
|
||||
C4.ALWAYS_TRUE => S"guaranteed to never access or modify externally accessible mutable memory",
|
||||
C4.ALWAYS_FALSE => S"may access or modify externally accessible mutable memory",
|
||||
C4.INACCESSIBLEMEM_OR_ARGMEMONLY => S"may access or modify mutable memory {italic:iff} pointed to by its call arguments")
|
||||
effectinfo(io, :noub, "no undefined behaviour",
|
||||
C4.ALWAYS_TRUE => S"guaranteed to never",
|
||||
C4.ALWAYS_FALSE => S"may",
|
||||
C4.NOUB_IF_NOINBOUNDS => S"so long as {code,julia_macro:@inbounds} is not used or propagated, will not",
|
||||
suffix = "execute undefined behaviour")
|
||||
effectinfo(io, :nonoverlayed, "non-overlayed",
|
||||
true => S"never calls any methods from an overlayed method table",
|
||||
false => S"{warning:may} call methods from an overlayed method table")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
79
src/types.jl
79
src/types.jl
|
@ -1,5 +1,3 @@
|
|||
const POINTER_FACE = :cyan # should not appear in `FACE_CYCLE`
|
||||
|
||||
struct FieldInfo
|
||||
i::Int
|
||||
face::Union{Symbol, Face}
|
||||
|
@ -11,19 +9,28 @@ struct FieldInfo
|
|||
type::Type
|
||||
end
|
||||
|
||||
function structinfo(T::DataType)
|
||||
function structinfo(T::Type)
|
||||
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
|
||||
if hassizeof(T)
|
||||
offset = fieldoffset(T, i) |> Int
|
||||
size = Int(if i < fieldcount(T)
|
||||
fieldoffset(T, i+1)
|
||||
else
|
||||
sizeof(T)
|
||||
end - fieldoffset(T, i))
|
||||
contentsize = if hassizeof(fieldtype(T, i))
|
||||
sizeof(fieldtype(T, i))
|
||||
else
|
||||
0
|
||||
end
|
||||
if contentsize > size # Pointer?
|
||||
contentsize = 0
|
||||
end
|
||||
else
|
||||
offset = size = contentsize = -1 # Cannot deduce easily
|
||||
end
|
||||
FieldInfo(i, FACE_CYCLE[i % length(FACE_CYCLE) + 1],
|
||||
fieldoffset(T, i) |> Int, # offset
|
||||
offset,
|
||||
size, contentsize,
|
||||
contentsize == 0, # ispointer
|
||||
fieldname(T, i), fieldtype(T, i))
|
||||
|
@ -36,7 +43,7 @@ function about(io::IO, type::Type)
|
|||
elseif isconcretetype(type)
|
||||
print(io, "Concrete ")
|
||||
if Base.datatype_haspadding(type)
|
||||
print(io, styled"{shadow:(padded)} ")
|
||||
print(io, S"{shadow:(padded)} ")
|
||||
end
|
||||
elseif isabstracttype(type)
|
||||
print(io, "Abstract ")
|
||||
|
@ -45,20 +52,22 @@ function about(io::IO, type::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))
|
||||
print(io, S" defined in {about_module:$(parentmodule(type))}, ")
|
||||
hassizeof(type) && print(io, "$(join(humansize(sizeof(type))))")
|
||||
print(io, "\n ")
|
||||
supertypeinfo(io, type)
|
||||
(!isstructtype(type) || fieldcount(type) == 0) && return
|
||||
println(io, styled"\nStruct with {bold:$(fieldcount(type))} fields:")
|
||||
println(io, S"\n\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")
|
||||
push!(fieldinfo, rpad(S"{$face:$name}", namepad) * S"{about_pointer:$(ifelse(ispointer, \"*\", \" \"))}$type")
|
||||
end
|
||||
else
|
||||
for (; name, type) in structinfo(type)
|
||||
push!(fieldinfo, styled"$name{shadow:::$type}")
|
||||
push!(fieldinfo, S"$name{shadow:::$type}")
|
||||
end
|
||||
end
|
||||
if length(fieldinfo) < 32
|
||||
|
@ -67,15 +76,18 @@ function about(io::IO, type::Type)
|
|||
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 supertypeinfo(io::IO, type::Type)
|
||||
typestr(t) = highlight(sprint(show, Base.unwrap_unionall(t)))
|
||||
join(io, map(typestr, supertypes(type)),
|
||||
S" {julia_comparator:<:} ")
|
||||
end
|
||||
|
||||
function memorylayout(io::IO, type::DataType)
|
||||
hassizeof(type) || return
|
||||
si = structinfo(type)
|
||||
!isempty(si) || return
|
||||
memstep = memstep = gcd((getfield.(si, :size), getfield.(si, :contentsize)) |>
|
||||
|
@ -84,33 +96,36 @@ function memorylayout(io::IO, type::DataType)
|
|||
bars = AnnotatedString[]
|
||||
descs = AnnotatedString[]
|
||||
for (; i, size, contentsize, ispointer) in si
|
||||
size <= 0 && continue
|
||||
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)
|
||||
cpad(S" {$color,bold:*} ", width)
|
||||
elseif contentsize < size
|
||||
csize, cunits = humansize(contentsize)
|
||||
psize, punits = humansize(size - contentsize)
|
||||
cpad(styled" {$color:$csize$cunits}{shadow:+$psize$punits} ", width, ' ', RoundUp)
|
||||
cpad(S" {$color:$csize$cunits}{shadow:+$psize$punits} ", width, ' ', RoundUp)
|
||||
else
|
||||
cpad(styled" {$color:$fsize$funits} ", width)
|
||||
cpad(S" {$color:$fsize$funits} ", width)
|
||||
end
|
||||
push!(descs, desc)
|
||||
width = textwidth(desc)
|
||||
contentwidth = round(Int, width * contentsize / size)
|
||||
bar = styled"{$color:$('■'^contentwidth)}"
|
||||
bar = S"{$color:$('■'^contentwidth)}"
|
||||
if contentsize < size
|
||||
color = if ispointer; :cyan else :light_black end
|
||||
paddwidth = width - contentwidth
|
||||
bar *= styled"{$color:$('■'^paddwidth)}"
|
||||
if ispointer
|
||||
bar *= S"{about_pointer,light:$('■'^paddwidth)}"
|
||||
else
|
||||
bar *= S"{shadow:$('■'^paddwidth)}"
|
||||
end
|
||||
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)
|
||||
multirow_wrap(io, permutedims(hcat(bars, descs)))
|
||||
if any(i -> i.ispointer, si)
|
||||
println(io, S"\n {about_pointer,bold:*} = {about_pointer:Pointer} {light:(8B)}")
|
||||
end
|
||||
end
|
||||
|
|
60
src/utils.jl
60
src/utils.jl
|
@ -10,10 +10,20 @@ function humansize(bytes::Integer)
|
|||
end, units[1+magnitude]
|
||||
end
|
||||
|
||||
function hassizeof(type::Type)
|
||||
!isconcretetype(type) && return false
|
||||
type <: GenericMemory && return false
|
||||
type in (Symbol, String, Core.SimpleVector) && return false
|
||||
true
|
||||
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
|
||||
|
||||
splural(n::Int) = ifelse(n == 1, "", "s")
|
||||
splural(c::Vector) = splural(length(c))
|
||||
|
||||
function struncate(str::AbstractString, maxwidth::Int, joiner::AbstractString = "…", mode::Symbol = :center)
|
||||
textwidth(str) <= maxwidth && return str
|
||||
left, right = firstindex(str) - 1, lastindex(str) + 1
|
||||
|
@ -33,17 +43,19 @@ end
|
|||
|
||||
function columnlist(io::IO, entries::Vector{<:AbstractString};
|
||||
maxcols::Int=8, maxwidth::Int=last(displaysize(io)),
|
||||
prefix::AbstractString = styled"{emphasis:•} ", spacing::Int=2)
|
||||
prefix::AbstractString = S"{emphasis:•} ", spacing::Int=2)
|
||||
isempty(entries) && return
|
||||
thecolumns = Vector{eltype(entries)}[]
|
||||
thecolwidths = Int[]
|
||||
for ncols in 1:maxcols
|
||||
columns = Vector{eltype(entries)}[]
|
||||
for col in Iterators.partition(entries, length(entries) ÷ ncols)
|
||||
for col in Iterators.partition(entries, div(length(entries), ncols, RoundUp))
|
||||
push!(columns, collect(col))
|
||||
end
|
||||
widths = map.(textwidth, columns)
|
||||
colwidths = map(maximum, widths)
|
||||
if sum(colwidths) + ncols * textwidth(prefix) + (1 - ncols) * spacing > maxwidth
|
||||
layoutwidth = sum(colwidths) + ncols * textwidth(prefix) + (ncols - 1) * spacing
|
||||
if layoutwidth > maxwidth
|
||||
break
|
||||
else
|
||||
thecolumns, thecolwidths = columns, colwidths
|
||||
|
@ -81,3 +93,45 @@ function multirow_wrap(io::IO, cells::Matrix{<:AbstractString};
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
wraplines(content::AnnotatedString, width::Integer = 80, column::Integer = 0)
|
||||
|
||||
Wrap `content` into a vector of lines of at most `width` (according to
|
||||
`textwidth`), with the first line starting at `column`.
|
||||
"""
|
||||
function wraplines(content::Union{Annot, SubString{<:Annot}}, width::Integer = 80, column::Integer = 0) where { Annot <: AnnotatedString}
|
||||
s, lines = String(content), SubString{Annot}[]
|
||||
i, lastwrap, slen = firstindex(s), 0, ncodeunits(s)
|
||||
most_recent_break_opportunity = 1
|
||||
while i < slen
|
||||
if isspace(s[i]) && s[i] != '\n'
|
||||
most_recent_break_opportunity = i
|
||||
elseif s[i] == '\n'
|
||||
push!(lines, content[nextind(s, lastwrap):prevind(s, i)])
|
||||
lastwrap = i
|
||||
column = 0
|
||||
elseif column >= width && most_recent_break_opportunity > 1
|
||||
if lastwrap == most_recent_break_opportunity
|
||||
nextbreak = findfirst(isspace, @view s[nextind(s, lastwrap):end])
|
||||
if isnothing(nextbreak)
|
||||
break
|
||||
else
|
||||
most_recent_break_opportunity = lastwrap + nextbreak
|
||||
end
|
||||
i = most_recent_break_opportunity
|
||||
else
|
||||
i = nextind(s, most_recent_break_opportunity)
|
||||
end
|
||||
push!(lines, content[nextind(s, lastwrap):prevind(s, most_recent_break_opportunity)])
|
||||
lastwrap = most_recent_break_opportunity
|
||||
column = 0
|
||||
end
|
||||
column += textwidth(s[i])
|
||||
i = nextind(s, i)
|
||||
end
|
||||
if lastwrap < slen
|
||||
push!(lines, content[nextind(s, lastwrap):end])
|
||||
end
|
||||
lines
|
||||
end
|
||||
|
|
352
src/values.jl
352
src/values.jl
|
@ -1,21 +1,49 @@
|
|||
const NUMBER_BIT_FACES = (
|
||||
sign = :bright_blue,
|
||||
exponent = :bright_green,
|
||||
mantissa = :bright_red
|
||||
)
|
||||
# ------------------
|
||||
# Structs, in general
|
||||
# ------------------
|
||||
|
||||
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)))}")
|
||||
# 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, styled" ({red:<:} ", supertypestr(supertype(T)), ")")
|
||||
println(io)
|
||||
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}
|
||||
|
@ -28,7 +56,12 @@ function memorylayout(io::IO, value::T) where {T}
|
|||
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[]
|
||||
|
@ -41,19 +74,24 @@ function memorylayout(io::IO, value::T) where {T}
|
|||
push!(ftypes, string(type))
|
||||
push!(fsizes, join(humansize(size)))
|
||||
aio = AnnotatedIOBuffer()
|
||||
if ispointer
|
||||
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(getfield(value, name))
|
||||
push!(freprs, styled"{$POINTER_FACE,light:$pt}")
|
||||
pt = pointer(fvalue)
|
||||
push!(freprs, S"{about_pointer:@ $(sprint(show, UInt64(pt)))}")
|
||||
catch
|
||||
push!(freprs, styled"{$POINTER_FACE:Ptr?}")
|
||||
push!(freprs, S"{about_pointer:Ptr?}")
|
||||
end
|
||||
else
|
||||
memorylayout(IOContext(aio, :compact => true), getfield(value, name))
|
||||
memorylayout(IOContext(aio, :compact => true), fvalue)
|
||||
push!(freprs, read(seekstart(aio), AnnotatedString))
|
||||
end
|
||||
truncate(aio, 0)
|
||||
show(IOContext(aio, :compact => true), getfield(value, name))
|
||||
show(IOContext(aio, :compact => true), fvalue)
|
||||
push!(fshows, read(seekstart(aio), AnnotatedString))
|
||||
end
|
||||
width = last(displaysize(io)) - 2
|
||||
|
@ -65,14 +103,99 @@ function memorylayout(io::IO, value::T) where {T}
|
|||
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))
|
||||
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
|
||||
println(io)
|
||||
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)
|
||||
|
@ -80,7 +203,7 @@ function memorylayout(io::IO, value::Bool)
|
|||
if get(io, :compact, false) == true
|
||||
print(io, bits)
|
||||
else
|
||||
println(io, "\n ", bits, styled" {bold:=} $value")
|
||||
println(io, "\n ", bits, S" {bold:=} $value")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -93,7 +216,7 @@ function memorylayout(io::IO, value::Union{UInt8, UInt16, UInt32, UInt64, UInt12
|
|||
print(io, bits)
|
||||
else
|
||||
println(io, "\n ", bits, ifelse(sizeof(value) > 4, "\n", ""),
|
||||
styled" {bold:=} $value")
|
||||
S" {bold:=} $value")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -111,7 +234,7 @@ function memorylayout(io::IO, value::Union{Int8, Int16, Int32, Int64, Int128})
|
|||
else
|
||||
signstr = ifelse(value < 0, '-', '+')
|
||||
println(io, "\n ", bits, ifelse(sizeof(value) > 4, "\n", ""),
|
||||
styled" {bold:=} {$(NUMBER_BIT_FACES.sign):$signstr}$value")
|
||||
S" {bold:=} {$(NUMBER_BIT_FACES.sign):$signstr}$(abs(value))")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -123,7 +246,7 @@ 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])}"
|
||||
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
|
||||
|
@ -147,28 +270,101 @@ function floatlayout(io::IO, float::AbstractFloat, expbits::Int)
|
|||
eright = (expbits - 3) - eleft
|
||||
fleft = (fracbits - 3) ÷ 2
|
||||
fright = (fracbits - 3) - fleft
|
||||
styled"{$fsign:╨}{$fexp:└$('─'^eleft)┬$('─'^eright)┘}{$fmant:└$('─'^fleft)┬$('─'^fright)┘}"
|
||||
S"{$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}"
|
||||
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,
|
||||
styled"\n {bold:=} ", if -8 < exponent < 8
|
||||
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 ")
|
||||
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)
|
||||
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
|
||||
|
@ -176,12 +372,86 @@ function memorylayout(io::IO, char::Char)
|
|||
println(io)
|
||||
for chunk in chunks
|
||||
byte = lpad(string(chunk, base=16), 2, '0')
|
||||
print(io, styled" {shadow:└─0x$(byte)─┘}")
|
||||
print(io, S" {shadow:└─0x$(byte)─┘}")
|
||||
end
|
||||
print(io, "\n = ", ustr)
|
||||
Base.isoverlong(char) && print(io, S" {error:[overlong]}")
|
||||
println(io)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO char
|
||||
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
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# TODO struct
|
||||
|
|
Loading…
Reference in New Issue