Skip to content

Commit f54f7e1

Browse files
committed
Predictably order fields and share methods
Takes inspiration from JuliaLang/julia#30924.
1 parent 29e9444 commit f54f7e1

File tree

3 files changed

+83
-71
lines changed

3 files changed

+83
-71
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ julia> @bitflag MyStyle::UInt8 S_NONE=0 S_BOLD S_ITALIC S_LARGE=8
3939
Combinations can be made using standard binary operations:
4040
```julia
4141
julia> S_BOLD | S_LARGE
42-
(S_BOLD | S_LARGE)::MyStyle = 0x09
42+
(S_LARGE | S_BOLD)::MyStyle = 0x09
4343

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

5656
julia> MyStyle(9)
57-
(S_BOLD | S_LARGE)::MyStyle = 0x09
57+
(S_LARGE | S_BOLD)::MyStyle = 0x09
5858

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

7373
julia> S_BOLD | S_LARGE
74-
(S_BOLD | S_LARGE)::MyStyle = 0x00000005
74+
(S_LARGE | S_BOLD)::MyStyle = 0x00000005
7575
```
7676
In a compact context (such as in multi-dimensional arrays), the pretty-printing
7777
takes on a shorter form:
7878
```julia
7979
julia> [S_NONE (S_BOLD | S_LARGE)]
8080
1×2 Array{MyStyle,2}:
81-
S_NONE S_BOLD|S_LARGE
81+
S_NONE S_LARGE|S_BOLD
8282

8383
julia> show(IOContext(stdout, :compact => true), S_BOLD | S_LARGE)
84-
S_BOLD|S_LARGE
84+
S_LARGE|S_BOLD
8585
```
8686

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

9797
julia> read(io, MyStyle)
98-
(S_BOLD | S_LARGE)::MyStyle = 0x09
98+
(S_LARGE | S_BOLD)::MyStyle = 0x09
9999
```

src/BitFlags.jl

Lines changed: 74 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,68 @@ module BitFlags
77
import Core.Intrinsics.bitcast
88
export BitFlag, @bitflag
99

10-
function basetype end
10+
function namemap end
11+
function haszero end
1112

1213
abstract type BitFlag{T<:Integer} end
1314

15+
basetype(::Type{<:BitFlag{T}}) where {T<:Integer} = T
16+
1417
(::Type{T})(x::BitFlag{T2}) where {T<:Integer,T2<:Unsigned} = T(bitcast(T2, x))::T
1518
Base.cconvert(::Type{T}, x::BitFlag{T2}) where {T<:Unsigned,T2<:Unsigned} = T(x)
1619
Base.write(io::IO, x::BitFlag{T}) where {T<:Unsigned} = write(io, T(x))
17-
Base.read(io::IO, ::Type{T}) where {T<:BitFlag} = T(read(io, BitFlags.basetype(T)))
20+
Base.read(io::IO, ::Type{T}) where {T<:BitFlag} = T(read(io, basetype(T)))
21+
22+
Base.isless(x::T, y::T) where {T<:BitFlag} = isless(basetype(T)(x), basetype(T)(y))
23+
Base.:|(x::T, y::T) where {T<:BitFlag} = T(Integer(x) | Integer(y))
24+
Base.:&(x::T, y::T) where {T<:BitFlag} = T(Integer(x) & Integer(y))
25+
26+
function Base.print(io::IO, x::T) where T<:BitFlag
27+
compact = get(io, :compact, false)
28+
xi = Integer(x)
29+
multi = (haszero(T) && !iszero(xi)) && !compact && !ispow2(xi)
30+
first = true
31+
sep = compact ? "|" : " | "
32+
for (i, sym) in Iterators.reverse(namemap(T))
33+
if haszero(T) && iszero(i) && iszero(xi)
34+
print(io, sym)
35+
break
36+
end
37+
if !iszero(i & xi)
38+
if first
39+
multi && print(io, "(")
40+
first = false
41+
else
42+
print(io, sep)
43+
end
44+
print(io, sym)
45+
end
46+
end
47+
multi && print(io, ")")
48+
nothing
49+
end
50+
function Base.show(io::IO, x::BitFlag)
51+
if get(io, :compact, false)
52+
print(io, x)
53+
else
54+
print(io, x, "::")
55+
show(IOContext(io, :compact => true), typeof(x))
56+
print(io, " = ")
57+
show(io, Integer(x))
58+
end
59+
end
60+
function Base.show(io::IO, t::Type{BitFlag})
61+
Base.show_datatype(io, t)
62+
end
63+
function Base.show(io::IO, ::MIME"text/plain", t::Type{<:BitFlag})
64+
print(io, "BitFlag ")
65+
Base.show_datatype(io, t)
66+
print(io, ":")
67+
for x in instances(t)
68+
print(io, "\n", Symbol(x), " = ")
69+
show(io, Integer(x))
70+
end
71+
end
1872

1973
# generate code to test whether expr is in the given set of values
2074
function membershiptest(expr, zmask)
@@ -86,7 +140,9 @@ macro bitflag(T, syms...)
86140
elseif !isa(T, Symbol)
87141
throw(ArgumentError("invalid type expression for bit flag $T"))
88142
end
89-
vals = Vector{Tuple{Symbol,Unsigned}}()
143+
values = basetype[]
144+
seen = Set{Symbol}()
145+
namemap = Vector{Tuple{basetype,Symbol}}()
90146
lo = hi = zero(basetype)
91147
maskzero, maskother = false, zero(basetype)
92148
i = oneunit(basetype)
@@ -120,26 +176,33 @@ macro bitflag(T, syms...)
120176
end
121177
if !Base.isidentifier(s)
122178
throw(ArgumentError("invalid name for BitFlag $typename; "
123-
* "\"$s\" is not a valid identifier."))
179+
* "\"$s\" is not a valid identifier"))
124180
end
125181
if (iszero(i) && maskzero) || (i & maskother) != 0
126182
throw(ArgumentError("values for BitFlag $typename are not unique"))
127183
end
128-
push!(vals, (s,i))
184+
push!(namemap, (i,s))
185+
push!(values, i)
186+
if s in seen
187+
throw(ArgumentError("name \"$s\" in BitFlag $typename is not unique"))
188+
end
189+
push!(seen, s)
129190
if iszero(i)
130191
maskzero = true
131192
else
132193
maskother |= i
133194
end
134-
if length(vals) == 1
195+
if length(values) == 1
135196
lo = hi = i
136197
else
137198
lo = min(lo, i)
138199
hi = max(hi, i)
139200
end
140201
i = iszero(i) ? oneunit(i) : two*i
141202
end
142-
values = basetype[i[2] for i in vals]
203+
order = sortperm([v[1] for v in namemap])
204+
permute!(namemap, order)
205+
permute!(values, order)
143206
blk = quote
144207
# bitflag definition
145208
Base.@__doc__(primitive type $(esc(typename)) <: BitFlag{$(basetype)} $(sizeof(basetype) * 8) end)
@@ -148,67 +211,16 @@ macro bitflag(T, syms...)
148211
bitflag_argument_error($(Expr(:quote, typename)), x)
149212
return bitcast($(esc(typename)), convert($(basetype), x))
150213
end
151-
BitFlags.basetype(::Type{$(esc(typename))}) = $(esc(basetype))
214+
BitFlags.namemap(::Type{$(esc(typename))}) = $(esc(namemap))
215+
BitFlags.haszero(::Type{$(esc(typename))}) = $(esc(maskzero))
152216
Base.typemin(x::Type{$(esc(typename))}) = $(esc(typename))($lo)
153217
Base.typemax(x::Type{$(esc(typename))}) = $(esc(typename))($hi)
154-
Base.isless(x::$(esc(typename)), y::$(esc(typename))) =
155-
isless($basetype(x), $basetype(y))
156-
Base.:|(x::$(esc(typename)), y::$(esc(typename))) =
157-
$(esc(typename))($basetype(x) | $basetype(y))
158-
Base.:&(x::$(esc(typename)), y::$(esc(typename))) =
159-
$(esc(typename))($basetype(x) & $basetype(y))
160-
let insts = ntuple(i->$(esc(typename))($values[i]), $(length(vals)))
218+
let insts = ntuple(i->$(esc(typename))($values[i]), $(length(values)))
161219
Base.instances(::Type{$(esc(typename))}) = insts
162220
end
163-
function Base.print(io::IO, x::$(esc(typename)))
164-
compact = get(io, :compact, false)
165-
xi = $(basetype)(x)
166-
multi = ($maskzero && !iszero(xi)) && !compact && !ispow2(xi)
167-
first = true
168-
sep = compact ? "|" : " | "
169-
for (sym, i) in $vals
170-
if $maskzero && iszero(i) && iszero(xi)
171-
print(io, sym)
172-
break
173-
end
174-
if !iszero(i & xi)
175-
if first
176-
multi && print(io, "(")
177-
first = false
178-
else
179-
print(io, sep)
180-
end
181-
print(io, sym)
182-
end
183-
end
184-
multi && print(io, ")")
185-
nothing
186-
end
187-
function Base.show(io::IO, x::$(esc(typename)))
188-
if get(io, :compact, false)
189-
print(io, x)
190-
else
191-
print(io, x, "::")
192-
show(IOContext(io, :compact => true), typeof(x))
193-
print(io, " = ")
194-
show(io, $basetype(x))
195-
end
196-
end
197-
function Base.show(io::IO, t::Type{$(esc(typename))})
198-
Base.show_datatype(io, t)
199-
end
200-
function Base.show(io::IO, ::MIME"text/plain", t::Type{$(esc(typename))})
201-
print(io, "BitFlag ")
202-
Base.show_datatype(io, t)
203-
print(io, ":")
204-
for (sym, i) in $vals
205-
print(io, "\n", sym, " = ")
206-
show(io, i)
207-
end
208-
end
209221
end
210222
if isa(typename, Symbol)
211-
for (sym,i) in vals
223+
for (i, sym) in namemap
212224
push!(blk.args, :(const $(esc(sym)) = $(esc(typename))($i)))
213225
end
214226
end

test/runtests.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ end
153153
#end
154154

155155
#@testset "String representations" begin
156-
@bitflag FilePerms::UInt8 NONE=0 EXEC WRITE READ
156+
@bitflag FilePerms::UInt8 NONE=0 READ=4 WRITE=2 EXEC=1
157157
@test string(FilePerms) == "FilePerms"
158158
@test string(NONE) == "NONE"
159159
@test repr("text/plain", FilePerms) ==
@@ -163,14 +163,14 @@ end
163163
WRITE = 0x02
164164
READ = 0x04"""
165165
@test repr(EXEC) == "EXEC::FilePerms = 0x01"
166-
@test repr(EXEC | READ) == "(EXEC | READ)::FilePerms = 0x05"
166+
@test repr(EXEC | READ) == "(READ | EXEC)::FilePerms = 0x05"
167167
@test repr(NONE | READ) == "READ::FilePerms = 0x04"
168168

169169
let io = IOBuffer(), ioc = IOContext(io, :compact => true)
170170
show(ioc, NONE)
171171
@test String(take!(io)) == "NONE"
172172
show(ioc, EXEC | READ)
173-
@test String(take!(io)) == "EXEC|READ"
173+
@test String(take!(io)) == "READ|EXEC"
174174
end
175175
#end
176176

0 commit comments

Comments
 (0)