From f54f7e165aba7aca4892ce290b467aeb42f78a02 Mon Sep 17 00:00:00 2001 From: Justin Willmert Date: Sun, 10 Mar 2019 13:28:05 -0500 Subject: [PATCH] Predictably order fields and share methods Takes inspiration from JuliaLang/julia#30924. --- README.md | 12 ++--- src/BitFlags.jl | 136 ++++++++++++++++++++++++++--------------------- test/runtests.jl | 6 +-- 3 files changed, 83 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 09d6e29..b8de623 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 @@ -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 ``` diff --git a/src/BitFlags.jl b/src/BitFlags.jl index ee0d299..750294a 100644 --- a/src/BitFlags.jl +++ b/src/BitFlags.jl @@ -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) @@ -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) @@ -120,18 +176,23 @@ 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) @@ -139,7 +200,9 @@ macro bitflag(T, syms...) 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) @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index c4643ca..36420c0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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) == @@ -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