Revision 3711749292ba9c29ad2e3b9eaee90995f8c8290a authored by Keno Fischer on 11 October 2023, 14:41:22 UTC, committed by GitHub on 11 October 2023, 14:41:22 UTC
This should be NFC and is intended to allow the optimizer to delete :enter statements (by replacing them with `nothing`), without leaving dangling `:leave`s around. This is accomplished by having `leave` take (a variable number of) `:enter` tokens (that are already being used by `:pop_exception`). The semantics are that a literal `nothing` or an SSAValue pointing to a `nothing` statement are ignored, and one exception handler is popped for each remaining argument. The actual value of the token is ignored, except that the verifier asserts that it belongs to an `:enter`. Note that we don't need to do the same for :pop_exception, because the token generated by an `:enter` is semantically only in scope for :pop_exception during its catch block. If we determine the `:enter` is dead, then its catch block is guaranteed to not be executed and will be deleted wholesale by cfg liveness. I was considering doing something fancier where :leave is changed back to taking an integer after optimization, but the case where the IR size is bigger after this change (when we are `:leave`ing many handlers) is fairly rare and likely not worth the additional complexity or time cost to do anything special. If it does show up in size benchmarks, I'd rather give `:leave` a special, compact encoding.
1 parent 8180240
terminfo.jl
# This file is a part of Julia. License is MIT: https://julialang.org/license
include("terminfo_data.jl")
"""
struct TermInfoRaw
A structured representation of a terminfo file, without any knowledge of
particular capabilities, solely based on `term(5)`.
!!! warning
This is not part of the public API, and thus subject to change without notice.
# Fields
- `names::Vector{String}`: The names this terminal is known by.
- `flags::BitVector`: A list of 0–$(length(TERM_FLAGS)) flag values.
- `numbers::Union{Vector{UInt16}, Vector{UInt32}}`: A list of 0–$(length(TERM_NUMBERS))
number values. A value of `typemax(eltype(numbers))` is used to skip over
unspecified capabilities while ensuring value indices are correct.
- `strings::Vector{Union{String, Nothing}}`: A list of 0–$(length(TERM_STRINGS))
string values. A value of `nothing` is used to skip over unspecified
capabilities while ensuring value indices are correct.
- `extended::Union{Nothing, Dict{Symbol, Union{Bool, Int, String}}}`: Should an
extended info section exist, this gives the entire extended info as a
dictionary. Otherwise `nothing`.
See also: `TermInfo` and `TermCapability`.
"""
struct TermInfoRaw
names::Vector{String}
flags::BitVector
numbers::Union{Vector{UInt16}, Vector{UInt32}}
strings::Vector{Union{String, Nothing}}
extended::Union{Nothing, Dict{Symbol, Union{Bool, Int, String}}}
end
"""
struct TermInfo
A parsed terminfo paired with capability information.
!!! warning
This is not part of the public API, and thus subject to change without notice.
# Fields
- `names::Vector{String}`: The names this terminal is known by.
- `flags::Int`: The number of flags specified.
- `numbers::BitVector`: A mask indicating which of `TERM_NUMBERS` have been
specified.
- `strings::BitVector`: A mask indicating which of `TERM_STRINGS` have been
specified.
- `extensions::Vector{Symbol}`: A list of extended capability variable names.
- `capabilities::Dict{Symbol, Union{Bool, Int, String}}`: The capability values
themselves.
See also: `TermInfoRaw` and `TermCapability`.
"""
struct TermInfo
names::Vector{String}
flags::Int
numbers::BitVector
strings::BitVector
extensions::Vector{Symbol}
capabilities::Dict{Symbol, Union{Bool, Int, String}}
end
TermInfo() = TermInfo([], 0, [], [], [], Dict())
function read(data::IO, ::Type{TermInfoRaw})
# Parse according to `term(5)`
# Header
magic = read(data, UInt16) |> ltoh
NumInt = if magic == 0o0432
UInt16
elseif magic == 0o01036
UInt32
else
throw(ArgumentError("Terminfo data did not start with the magic number 0o0432 or 0o01036"))
end
name_bytes = read(data, UInt16) |> ltoh
flag_bytes = read(data, UInt16) |> ltoh
numbers_count = read(data, UInt16) |> ltoh
string_count = read(data, UInt16) |> ltoh
table_bytes = read(data, UInt16) |> ltoh
# Terminal Names
term_names = split(String(read(data, name_bytes - 1)), '|') .|> String
0x00 == read(data, UInt8) ||
throw(ArgumentError("Terminfo data did not contain a null byte after the terminal names section"))
# Boolean Flags
flags = read(data, flag_bytes) .== 0x01
if position(data) % 2 != 0
0x00 == read(data, UInt8) ||
throw(ArgumentError("Terminfo did not contain a null byte after the flag section, expected to position the start of the numbers section on an even byte"))
end
# Numbers, Strings, Table
numbers = map(ltoh, reinterpret(NumInt, read(data, numbers_count * sizeof(NumInt))))
string_indices = map(ltoh, reinterpret(UInt16, read(data, string_count * sizeof(UInt16))))
strings_table = read(data, table_bytes)
strings = map(string_indices) do idx
if idx ∉ (0xffff, 0xfffe)
len = findfirst(==(0x00), view(strings_table, 1+idx:length(strings_table)))
!isnothing(len) ||
throw(ArgumentError("Terminfo string table entry does not terminate with a null byte"))
String(strings_table[1+idx:idx+len-1])
end
end
TermInfoRaw(term_names, flags, numbers, strings,
if !eof(data) extendedterminfo(data, NumInt) end)
end
"""
extendedterminfo(data::IO; NumInt::Union{Type{UInt16}, Type{UInt32}})
Read an extended terminfo section from `data`, with `NumInt` as the numbers type.
This will accept any terminfo content that conforms with `term(5)`.
See also: `read(::IO, ::Type{TermInfoRaw})`
"""
function extendedterminfo(data::IO, NumInt::Union{Type{UInt16}, Type{UInt32}})
# Extended info
if position(data) % 2 != 0
0x00 == read(data, UInt8) ||
throw(ArgumentError("Terminfo did not contain a null byte before the extended section, expected to position the start on an even byte"))
end
# Extended header
flag_bytes = read(data, UInt16) |> ltoh
numbers_count = read(data, UInt16) |> ltoh
string_count = read(data, UInt16) |> ltoh
table_count = read(data, UInt16) |> ltoh
table_bytes = read(data, UInt16) |> ltoh
# Extended flags/numbers/strings
flags = read(data, flag_bytes) .== 0x01
if flag_bytes % 2 != 0
0x00 == read(data, UInt8) ||
throw(ArgumentError("Terminfo did not contain a null byte after the extended flag section, expected to position the start of the numbers section on an even byte"))
end
numbers = map(n -> Int(ltoh(n)), reinterpret(NumInt, read(data, numbers_count * sizeof(NumInt))))
table_indices = map(ltoh, reinterpret(UInt16, read(data, table_count * sizeof(UInt16))))
table_strings = [String(readuntil(data, 0x00)) for _ in 1:length(table_indices)]
info = Dict{Symbol, Union{Bool, Int, String}}()
strings = table_strings[1:string_count]
labels = table_strings[string_count+1:end]
for (label, val) in zip(labels, vcat(flags, numbers, strings))
info[Symbol(label)] = val
end
return info
end
"""
TermInfo(raw::TermInfoRaw)
Construct a `TermInfo` from `raw`, using known terminal capabilities (as of
NCurses 6.3, see `TERM_FLAGS`, `TERM_NUMBERS`, and `TERM_STRINGS`).
"""
function TermInfo(raw::TermInfoRaw)
capabilities = Dict{Symbol, Union{Bool, Int, String}}()
sizehint!(capabilities, 2 * (length(raw.flags) + length(raw.numbers) + length(raw.strings)))
for (flag, value) in zip(TERM_FLAGS, raw.flags)
capabilities[flag.short] = value
capabilities[flag.long] = value
end
for (num, value) in zip(TERM_NUMBERS, raw.numbers)
if value != typemax(eltype(raw.numbers))
capabilities[num.short] = Int(value)
capabilities[num.long] = Int(value)
end
end
for (str, value) in zip(TERM_STRINGS, raw.strings)
if !isnothing(value)
capabilities[str.short] = value
capabilities[str.long] = value
end
end
extensions = if !isnothing(raw.extended)
capabilities = merge(capabilities, raw.extended)
keys(raw.extended) |> collect
else
Symbol[]
end
TermInfo(raw.names, length(raw.flags),
map(n-> n != typemax(typeof(n)), raw.numbers),
map(!isnothing, raw.strings),
extensions, capabilities)
end
getindex(ti::TermInfo, key::Symbol) = ti.capabilities[key]
get(ti::TermInfo, key::Symbol, default::D) where D<:Union{Bool, Int, String} =
get(ti.capabilities, key, default)::D
get(ti::TermInfo, key::Symbol, default) = get(ti.capabilities, key, default)
keys(ti::TermInfo) = keys(ti.capabilities)
haskey(ti::TermInfo, key::Symbol) = haskey(ti.capabilities, key)
function show(io::IO, ::MIME"text/plain", ti::TermInfo)
print(io, "TermInfo(", ti.names, "; ", ti.flags, " flags, ",
sum(ti.numbers), " numbers, ", sum(ti.strings), " strings")
!isempty(ti.extensions) > 0 &&
print(io, ", ", length(ti.extensions), " extended capabilities")
print(io, ')')
end
"""
find_terminfo_file(term::String)
Locate the terminfo file for `term`, return `nothing` if none could be found.
The lookup policy is described in `terminfo(5)` "Fetching Compiled
Descriptions".
"""
function find_terminfo_file(term::String)
isempty(term) && return
chr, chrcode = string(first(term)), string(Int(first(term)), base=16)
terminfo_dirs = if haskey(ENV, "TERMINFO")
[ENV["TERMINFO"]]
elseif isdir(joinpath(homedir(), ".terminfo"))
[joinpath(homedir(), ".terminfo")]
elseif haskey(ENV, "TERMINFO_DIRS")
split(ENV["TERMINFO_DIRS"], ':')
elseif Sys.isunix()
["/usr/share/terminfo"]
else
String[]
end
for dir in terminfo_dirs
if isfile(joinpath(dir, chr, term))
return joinpath(dir, chr, term)
elseif isfile(joinpath(dir, chrcode, term))
return joinpath(dir, chrcode, term)
end
end
end
"""
load_terminfo(term::String)
Load the `TermInfo` for `term`, falling back on a blank `TermInfo`.
"""
function load_terminfo(term::String)
file = find_terminfo_file(term)
isnothing(file) && return TermInfo()
try
TermInfo(read(file, TermInfoRaw))
catch err
if err isa ArgumentError || err isa IOError
TermInfo()
else
rethrow()
end
end
end
"""
The terminfo of the current terminal.
"""
current_terminfo::TermInfo = TermInfo()
# Legacy/TTY methods and the `:color` parameter
if Sys.iswindows()
ttyhascolor(term_type = nothing) = true
else
function ttyhascolor(term_type = get(ENV, "TERM", ""))
startswith(term_type, "xterm") ||
haskey(current_terminfo, :setaf)
end
end
"""
ttyhastruecolor()
Return a boolean signifying whether the current terminal supports 24-bit colors.
This uses the `COLORTERM` environment variable if possible, returning true if it
is set to either `"truecolor"` or `"24bit"`.
As a fallback, first on unix systems the `colors` terminal capability is checked
— should more than 256 colors be reported, this is taken to signify 24-bit
support.
"""
function ttyhastruecolor()
get(ENV, "COLORTERM", "") ∈ ("truecolor", "24bit") ||
@static if Sys.isunix()
get(current_terminfo, :colors, 0) > 256
else
false
end
end
function get_have_color()
global have_color
have_color === nothing && (have_color = ttyhascolor())
return have_color::Bool
end
function get_have_truecolor()
global have_truecolor
have_truecolor === nothing && (have_truecolor = ttyhastruecolor())
return have_truecolor::Bool
end
in(key_value::Pair{Symbol,Bool}, ::TTY) = key_value.first === :color && key_value.second === get_have_color()
haskey(::TTY, key::Symbol) = key === :color
getindex(::TTY, key::Symbol) = key === :color ? get_have_color() : throw(KeyError(key))
get(::TTY, key::Symbol, default) = key === :color ? get_have_color() : default
Computing file changes ...