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
Raw File
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
back to top