Skip to content

Commit

Permalink
Predictably order fields and share methods
Browse files Browse the repository at this point in the history
Takes inspiration from JuliaLang/julia#30924.
  • Loading branch information
jmert committed Mar 10, 2019
1 parent 29e9444 commit f54f7e1
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 71 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ julia> @bitflag MyStyle::UInt8 S_NONE=0 S_BOLD S_ITALIC S_LARGE=8
Combinations can be made using standard binary operations:
```julia
julia> S_BOLD | S_LARGE
(S_BOLD | S_LARGE)::MyStyle = 0x09
(S_LARGE | S_BOLD)::MyStyle = 0x09

julia> ans & S_ITALIC
S_NONE::MyStyle = 0x00
Expand All @@ -54,7 +54,7 @@ julia> Integer(S_ITALIC) # Abstract Integer uses native UInt8 type
0x02

julia> MyStyle(9)
(S_BOLD | S_LARGE)::MyStyle = 0x09
(S_LARGE | S_BOLD)::MyStyle = 0x09

julia> MyStyle(4) # MyStyle does not have a flag at 4
ERROR: ArgumentError: invalid value for BitFlag MyStyle: 4
Expand All @@ -71,17 +71,17 @@ julia> S_BOLD
S_BOLD::MyStyle = 0x00000001

julia> S_BOLD | S_LARGE
(S_BOLD | S_LARGE)::MyStyle = 0x00000005
(S_LARGE | S_BOLD)::MyStyle = 0x00000005
```
In a compact context (such as in multi-dimensional arrays), the pretty-printing
takes on a shorter form:
```julia
julia> [S_NONE (S_BOLD | S_LARGE)]
1×2 Array{MyStyle,2}:
S_NONE S_BOLD|S_LARGE
S_NONE S_LARGE|S_BOLD

julia> show(IOContext(stdout, :compact => true), S_BOLD | S_LARGE)
S_BOLD|S_LARGE
S_LARGE|S_BOLD
```

## Input/Output
Expand All @@ -95,5 +95,5 @@ julia> write(io, UInt8(9));
julia> seekstart(io);

julia> read(io, MyStyle)
(S_BOLD | S_LARGE)::MyStyle = 0x09
(S_LARGE | S_BOLD)::MyStyle = 0x09
```
136 changes: 74 additions & 62 deletions src/BitFlags.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,68 @@ module BitFlags
import Core.Intrinsics.bitcast
export BitFlag, @bitflag

function basetype end
function namemap end
function haszero end

abstract type BitFlag{T<:Integer} end

basetype(::Type{<:BitFlag{T}}) where {T<:Integer} = T

(::Type{T})(x::BitFlag{T2}) where {T<:Integer,T2<:Unsigned} = T(bitcast(T2, x))::T
Base.cconvert(::Type{T}, x::BitFlag{T2}) where {T<:Unsigned,T2<:Unsigned} = T(x)
Base.write(io::IO, x::BitFlag{T}) where {T<:Unsigned} = write(io, T(x))
Base.read(io::IO, ::Type{T}) where {T<:BitFlag} = T(read(io, BitFlags.basetype(T)))
Base.read(io::IO, ::Type{T}) where {T<:BitFlag} = T(read(io, basetype(T)))

Base.isless(x::T, y::T) where {T<:BitFlag} = isless(basetype(T)(x), basetype(T)(y))
Base.:|(x::T, y::T) where {T<:BitFlag} = T(Integer(x) | Integer(y))
Base.:&(x::T, y::T) where {T<:BitFlag} = T(Integer(x) & Integer(y))

function Base.print(io::IO, x::T) where T<:BitFlag
compact = get(io, :compact, false)
xi = Integer(x)
multi = (haszero(T) && !iszero(xi)) && !compact && !ispow2(xi)
first = true
sep = compact ? "|" : " | "
for (i, sym) in Iterators.reverse(namemap(T))
if haszero(T) && iszero(i) && iszero(xi)
print(io, sym)
break
end
if !iszero(i & xi)
if first
multi && print(io, "(")
first = false
else
print(io, sep)
end
print(io, sym)
end
end
multi && print(io, ")")
nothing
end
function Base.show(io::IO, x::BitFlag)
if get(io, :compact, false)
print(io, x)
else
print(io, x, "::")
show(IOContext(io, :compact => true), typeof(x))
print(io, " = ")
show(io, Integer(x))
end
end
function Base.show(io::IO, t::Type{BitFlag})
Base.show_datatype(io, t)
end
function Base.show(io::IO, ::MIME"text/plain", t::Type{<:BitFlag})
print(io, "BitFlag ")
Base.show_datatype(io, t)
print(io, ":")
for x in instances(t)
print(io, "\n", Symbol(x), " = ")
show(io, Integer(x))
end
end

# generate code to test whether expr is in the given set of values
function membershiptest(expr, zmask)
Expand Down Expand Up @@ -86,7 +140,9 @@ macro bitflag(T, syms...)
elseif !isa(T, Symbol)
throw(ArgumentError("invalid type expression for bit flag $T"))
end
vals = Vector{Tuple{Symbol,Unsigned}}()
values = basetype[]
seen = Set{Symbol}()
namemap = Vector{Tuple{basetype,Symbol}}()
lo = hi = zero(basetype)
maskzero, maskother = false, zero(basetype)
i = oneunit(basetype)
Expand Down Expand Up @@ -120,26 +176,33 @@ macro bitflag(T, syms...)
end
if !Base.isidentifier(s)
throw(ArgumentError("invalid name for BitFlag $typename; "
* "\"$s\" is not a valid identifier."))
* "\"$s\" is not a valid identifier"))
end
if (iszero(i) && maskzero) || (i & maskother) != 0
throw(ArgumentError("values for BitFlag $typename are not unique"))
end
push!(vals, (s,i))
push!(namemap, (i,s))
push!(values, i)
if s in seen
throw(ArgumentError("name \"$s\" in BitFlag $typename is not unique"))
end
push!(seen, s)
if iszero(i)
maskzero = true
else
maskother |= i
end
if length(vals) == 1
if length(values) == 1
lo = hi = i
else
lo = min(lo, i)
hi = max(hi, i)
end
i = iszero(i) ? oneunit(i) : two*i
end
values = basetype[i[2] for i in vals]
order = sortperm([v[1] for v in namemap])
permute!(namemap, order)
permute!(values, order)
blk = quote
# bitflag definition
Base.@__doc__(primitive type $(esc(typename)) <: BitFlag{$(basetype)} $(sizeof(basetype) * 8) end)
Expand All @@ -148,67 +211,16 @@ macro bitflag(T, syms...)
bitflag_argument_error($(Expr(:quote, typename)), x)
return bitcast($(esc(typename)), convert($(basetype), x))
end
BitFlags.basetype(::Type{$(esc(typename))}) = $(esc(basetype))
BitFlags.namemap(::Type{$(esc(typename))}) = $(esc(namemap))
BitFlags.haszero(::Type{$(esc(typename))}) = $(esc(maskzero))
Base.typemin(x::Type{$(esc(typename))}) = $(esc(typename))($lo)
Base.typemax(x::Type{$(esc(typename))}) = $(esc(typename))($hi)
Base.isless(x::$(esc(typename)), y::$(esc(typename))) =
isless($basetype(x), $basetype(y))
Base.:|(x::$(esc(typename)), y::$(esc(typename))) =
$(esc(typename))($basetype(x) | $basetype(y))
Base.:&(x::$(esc(typename)), y::$(esc(typename))) =
$(esc(typename))($basetype(x) & $basetype(y))
let insts = ntuple(i->$(esc(typename))($values[i]), $(length(vals)))
let insts = ntuple(i->$(esc(typename))($values[i]), $(length(values)))
Base.instances(::Type{$(esc(typename))}) = insts
end
function Base.print(io::IO, x::$(esc(typename)))
compact = get(io, :compact, false)
xi = $(basetype)(x)
multi = ($maskzero && !iszero(xi)) && !compact && !ispow2(xi)
first = true
sep = compact ? "|" : " | "
for (sym, i) in $vals
if $maskzero && iszero(i) && iszero(xi)
print(io, sym)
break
end
if !iszero(i & xi)
if first
multi && print(io, "(")
first = false
else
print(io, sep)
end
print(io, sym)
end
end
multi && print(io, ")")
nothing
end
function Base.show(io::IO, x::$(esc(typename)))
if get(io, :compact, false)
print(io, x)
else
print(io, x, "::")
show(IOContext(io, :compact => true), typeof(x))
print(io, " = ")
show(io, $basetype(x))
end
end
function Base.show(io::IO, t::Type{$(esc(typename))})
Base.show_datatype(io, t)
end
function Base.show(io::IO, ::MIME"text/plain", t::Type{$(esc(typename))})
print(io, "BitFlag ")
Base.show_datatype(io, t)
print(io, ":")
for (sym, i) in $vals
print(io, "\n", sym, " = ")
show(io, i)
end
end
end
if isa(typename, Symbol)
for (sym,i) in vals
for (i, sym) in namemap
push!(blk.args, :(const $(esc(sym)) = $(esc(typename))($i)))
end
end
Expand Down
6 changes: 3 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ end
#end

#@testset "String representations" begin
@bitflag FilePerms::UInt8 NONE=0 EXEC WRITE READ
@bitflag FilePerms::UInt8 NONE=0 READ=4 WRITE=2 EXEC=1
@test string(FilePerms) == "FilePerms"
@test string(NONE) == "NONE"
@test repr("text/plain", FilePerms) ==
Expand All @@ -163,14 +163,14 @@ end
WRITE = 0x02
READ = 0x04"""
@test repr(EXEC) == "EXEC::FilePerms = 0x01"
@test repr(EXEC | READ) == "(EXEC | READ)::FilePerms = 0x05"
@test repr(EXEC | READ) == "(READ | EXEC)::FilePerms = 0x05"
@test repr(NONE | READ) == "READ::FilePerms = 0x04"

let io = IOBuffer(), ioc = IOContext(io, :compact => true)
show(ioc, NONE)
@test String(take!(io)) == "NONE"
show(ioc, EXEC | READ)
@test String(take!(io)) == "EXEC|READ"
@test String(take!(io)) == "READ|EXEC"
end
#end

0 comments on commit f54f7e1

Please sign in to comment.