Skip to content

Commit

Permalink
Merge pull request #3 from jw3126/nomacro
Browse files Browse the repository at this point in the history
Nomacro
  • Loading branch information
jw3126 authored Dec 13, 2024
2 parents 7d2bd1a + 8b4c3c2 commit 83a19e9
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 69 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "EnumSets"
uuid = "6e6cbeda-52ee-45ac-a8dc-908a1b2c6a2c"
authors = ["Jan Weidner <[email protected]> and contributors"]
version = "1.1.0"
version = "1.2.0"

[compat]
julia = "1.6.7"
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,39 @@ julia> using EnumSets

julia> @enum Lang Python Julia C

julia> @enumset LangSet <: EnumSet{Lang}
julia> const LangSet = enumsettype(Lang)
EnumSets.EnumSet{Lang, UInt8, EnumSets.OffsetBasedPacking{0}}

julia> s = LangSet((Python, Julia))
LangSet with 2 elements:
EnumSets.EnumSet{Lang, UInt8, EnumSets.OffsetBasedPacking{0}} with 2 elements:
Python
Julia

julia> push(s, C) # s is immutable, but we can create modified copies
LangSet with 3 elements:
EnumSets.EnumSet{Lang, UInt8, EnumSets.OffsetBasedPacking{0}} with 3 elements:
Python
Julia
C

julia> s
LangSet with 2 elements:
EnumSets.EnumSet{Lang, UInt8, EnumSets.OffsetBasedPacking{0}} with 2 elements:
Python
Julia

julia> s2 = LangSet((C, Python))
LangSet with 2 elements:
EnumSets.EnumSet{Lang, UInt8, EnumSets.OffsetBasedPacking{0}} with 2 elements:
Python
C

julia> s s2
LangSet with 3 elements:
EnumSets.EnumSet{Lang, UInt8, EnumSets.OffsetBasedPacking{0}} with 3 elements:
Python
Julia
C

julia> s s2
LangSet with 1 element:
EnumSets.EnumSet{Lang, UInt8, EnumSets.OffsetBasedPacking{0}} with 1 element:
Python

...
```

Expand Down Expand Up @@ -72,7 +72,7 @@ function workout(sets)
s, b
end

@enumset AlphabetSet <: EnumSet{Alphabet}
const AlphabetSet = enumsettype(Alphabet)

sets = [AlphabetSet(rand(instances(Alphabet)) for _ in 0:length(instances(Alphabet))) for _ in 1:100]
basesets = map(Set, sets)
Expand Down
112 changes: 65 additions & 47 deletions src/EnumSets.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module EnumSets
export @enumset
export push
export push, enumsettype

function nbits(::Type{T}) where {T}
8 * sizeof(T)
Expand All @@ -22,7 +22,7 @@ end
abstract type PackingTrait end
struct InstanceBasedPacking <:PackingTrait
end
struct OffsetBasedPacking{offset} end
struct OffsetBasedPacking{offset} <:PackingTrait end

function get_offset(::OffsetBasedPacking{offset})::Int where {offset}
offset
Expand Down Expand Up @@ -84,7 +84,32 @@ function suggest_carriertype(E)
end
end

abstract type EnumSet{E, Carrier} <: AbstractSet{E} end
struct EnumSet{E, C, P <: PackingTrait} <: AbstractSet{E}
_data::C
function EnumSet{E,C,P}(data::C) where {E,C,P}
new{E,C,P}(data)
end
end

function EnumSet{E,C,P}()::EnumSet{E,C,P} where {E,C,P}
EnumSet{E,C,P}(zero(C))
end

function EnumSet{E,C,P}(s::EnumSet{E,C,P})::EnumSet{E,C,P} where {E,C,P}
s
end

function EnumSet{E,C,P}(itr)::EnumSet{E,C,P} where {E,C,P}
ret = EnumSet{E,C,P}()
for e in itr
ret = push(ret, convert(E, e))
end
ret
end

function PackingTrait(s::EnumSet{E,C,P})::PackingTrait where {E,C,P}
P()
end

function _get_data(s)
s._data
Expand Down Expand Up @@ -172,6 +197,9 @@ function make_error_msg(ex)
"""
end

"""
This macro is deprecate. Use `const MySet = enumsettype(MyEnum)` instead.
"""
macro enumset(ex)
if !Meta.isexpr(ex, :<:)
error(make_error_msg(ex))
Expand All @@ -182,73 +210,63 @@ macro enumset(ex)
end
if length(ex_EnumSet.args) == 2
symbol_EnumSet, E = ex_EnumSet.args
Carrier = nothing
C = nothing
elseif length(ex_EnumSet.args) == 3
symbol_EnumSet, E, Carrier = ex_EnumSet.args
symbol_EnumSet, E, C = ex_EnumSet.args
else
error(make_error_msg(ex_EnumSet))
end
E = __module__.eval(E)
Carrier = __module__.eval(Carrier)
if isnothing(Carrier)
Carrier = suggest_carriertype(E)
C = __module__.eval(C)
if isnothing(C)
C = suggest_carriertype(E)
end
esc(enumsetmacro(E, ESet, Carrier))
esc(:(const $ESet = $enumsettype($E; carrier=$C)))
end

function from_itr(::Type{ESet}, itr)::ESet where {ESet <: EnumSet}
ret = ESet()
E = eltype(ESet)
for e in itr
ret = push(ret, convert(E, e))
end
ret
end

function enum_fits_into_offset_packing(E, nbits)
lo, hi = extrema(Integer, instances(E))
# convert to Float64 to prevent overflow issues
(abs(hi - lo) < nbits) && (abs(Float64(hi) - Float64(lo)) < nbits)
end

function enumsetmacro(E::Type, ESet::Symbol, Carrier::Type)
"""
enumsettype(::Type{E})::Type
Return a subtype of `EnumSet` suitable for storing instances of an enum `E`.
Typical usage looks like this:
```julia
@enum Alphabet A B C
const MySet = enumsettype(Alphabet)
if length(instances(E)) > nbits(Carrier)
ab = MySet((A, B))
bc = MySet((B, C))
ab ∩ bc
...
```
"""
function enumsettype(::Type{E}; carrier::Union{Nothing, Type}=nothing)::Type where {E}
C = if isnothing(carrier)
suggest_carriertype(E)
else
carrier
end
if length(instances(E)) > nbits(C)
msg = """
Enum $E does not fit into carrier type $Carrier.
Enum $E does not fit into carrier type $C.
length(instances($E)) = $(length(instances(E)))
nbits($Carrier) = $(nbits(Carrier))
nbits($C) = $(nbits(C))
"""
return :(error($msg))
error(msg)
end
PTrait = if enum_fits_into_offset_packing(E, nbits(Carrier))
OffsetBasedPacking{1-minimum(Integer, instances(E))}
P = if enum_fits_into_offset_packing(E, nbits(C))
OffsetBasedPacking{-minimum(Int, instances(E))}
else
InstanceBasedPacking
end
M = @__MODULE__()
ret = quote
struct $ESet <: $EnumSet{$E, $Carrier}
_data::$Carrier
function $ESet(carrier::$Carrier)
new(carrier)
end
end
function $ESet()::$ESet
$ESet(zero($Carrier))
end
function $ESet(e::$ESet)::$ESet
e
end
function $ESet(itr)::$ESet
$from_itr($ESet, itr)
end
function $M.PackingTrait(::$ESet)::$PTrait
$PTrait()
end
end

ret
EnumSet{E, C, P}
end


end
63 changes: 51 additions & 12 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ function fuzz_booleans(ESet)
E = eltype(ESet)
es = instances(E)
n = length(es)
for e in es
@test e === only(ESet((e,)))
end
for _ in 1:100
n1 = rand(0:n)
n2 = rand(0:n)
Expand All @@ -29,14 +32,13 @@ function fuzz_booleans(ESet)
end
end

@enum Alphabet begin
A=1
B=2
C=3
end

@testset "simple enum" begin
@enumset ASet <: EnumSet{Alphabet}
@enum Alphabet begin
A=1
B=2
C=3
end
ASet = enumsettype(Alphabet)

s = ASet()
@test isbitstype(ASet)
Expand Down Expand Up @@ -88,9 +90,9 @@ end
c=typemin(Int128)
d=typemax(Int128)
end
@enumset NegativeSet <: EnumSet{Negative}

@testset "negative enum" begin
@enumset NegativeSet <: EnumSet{Negative}

@test NegativeSet((a,b, d)) != NegativeSet((a,))
@test NegativeSet((a,b, d)) NegativeSet((a,b, c, d))
Expand Down Expand Up @@ -170,9 +172,10 @@ end
WebsocketDisconnected
end

const ProgrammerExcuseSet = enumsettype(ProgrammerExcuse)

@testset "big enum" begin
@test length(instances(ProgrammerExcuse)) > 64
@enumset ProgrammerExcuseSet <: EnumSet{ProgrammerExcuse}
@test 8*sizeof(ProgrammerExcuseSet) >= length(instances(ProgrammerExcuse))
s = ProgrammerExcuseSet()
@test isempty(s)
Expand All @@ -185,11 +188,9 @@ end
@test collect(s) == collect(instances(ProgrammerExcuse))[1:i]
end
fuzz_booleans(ProgrammerExcuseSet)
@test_throws "Enum ProgrammerExcuse does not fit into carrier type UInt64." enumsettype(ProgrammerExcuse; carrier=UInt64)
end

@testset "Does not fit" begin
@test_throws "Enum ProgrammerExcuse does not fit into carrier type UInt64." @enumset DoesNotFitSet <: EnumSet{ProgrammerExcuse, UInt64}
end

@testset "blsr" begin
for T in [UInt8, UInt16, UInt32, UInt64, UInt128]
Expand All @@ -202,3 +203,41 @@ end
end
end
end

@testset "exhaustive enums" begin
@enum X1 X1_1 X1_2 X1_3 X1_4 X1_5 X1_6 X1_7 X1_8
@enum X2 X2_1 X2_2 X2_3 X2_4 X2_5 X2_6 X2_7 X2_8 X2_9 X2_10 X2_11 X2_12 X2_13 X2_14 X2_15 X2_16
@enum X4 X4_1 X4_2 X4_3 X4_4 X4_5 X4_6 X4_7 X4_8 X4_9 X4_10 X4_11 X4_12 X4_13 X4_14 X4_15 X4_16 X4_17 X4_18 X4_19 X4_20 X4_21 X4_22 X4_23 X4_24 X4_25 X4_26 X4_27 X4_28 X4_29 X4_30 X4_31 X4_32
@enum X8 X8_1 X8_2 X8_3 X8_4 X8_5 X8_6 X8_7 X8_8 X8_9 X8_10 X8_11 X8_12 X8_13 X8_14 X8_15 X8_16 X8_17 X8_18 X8_19 X8_20 X8_21 X8_22 X8_23 X8_24 X8_25 X8_26 X8_27 X8_28 X8_29 X8_30 X8_31 X8_32 X8_33 X8_34 X8_35 X8_36 X8_37 X8_38 X8_39 X8_40 X8_41 X8_42 X8_43 X8_44 X8_45 X8_46 X8_47 X8_48 X8_49 X8_50 X8_51 X8_52 X8_53 X8_54 X8_55 X8_56 X8_57 X8_58 X8_59 X8_60 X8_61 X8_62 X8_63 X8_64
@enum X16 X16_1 X16_2 X16_3 X16_4 X16_5 X16_6 X16_7 X16_8 X16_9 X16_10 X16_11 X16_12 X16_13 X16_14 X16_15 X16_16 X16_17 X16_18 X16_19 X16_20 X16_21 X16_22 X16_23 X16_24 X16_25 X16_26 X16_27 X16_28 X16_29 X16_30 X16_31 X16_32 X16_33 X16_34 X16_35 X16_36 X16_37 X16_38 X16_39 X16_40 X16_41 X16_42 X16_43 X16_44 X16_45 X16_46 X16_47 X16_48 X16_49 X16_50 X16_51 X16_52 X16_53 X16_54 X16_55 X16_56 X16_57 X16_58 X16_59 X16_60 X16_61 X16_62 X16_63 X16_64 X16_65 X16_66 X16_67 X16_68 X16_69 X16_70 X16_71 X16_72 X16_73 X16_74 X16_75 X16_76 X16_77 X16_78 X16_79 X16_80 X16_81 X16_82 X16_83 X16_84 X16_85 X16_86 X16_87 X16_88 X16_89 X16_90 X16_91 X16_92 X16_93 X16_94 X16_95 X16_96 X16_97 X16_98 X16_99 X16_100 X16_101 X16_102 X16_103 X16_104 X16_105 X16_106 X16_107 X16_108 X16_109 X16_110 X16_111 X16_112 X16_113 X16_114 X16_115 X16_116 X16_117 X16_118 X16_119 X16_120 X16_121 X16_122 X16_123 X16_124 X16_125 X16_126 X16_127 X16_128

@test length(instances(X1)) == 1*8
@test length(instances(X2)) == 2*8
@test length(instances(X4)) == 4*8
@test length(instances(X8)) == 8*8
@test length(instances(X16)) == 16*8

SetX1 = enumsettype(X1)
SetX2 = enumsettype(X2)
SetX4 = enumsettype(X4)
SetX8 = enumsettype(X8)
SetX16 = enumsettype(X16)

@test sizeof(SetX1) == 1
@test sizeof(SetX2) == 2
@test sizeof(SetX4) == 4
@test sizeof(SetX8) == 8
@test sizeof(SetX16) == 16

@test EnumSets.PackingTrait(SetX1()) == EnumSets.OffsetBasedPacking{0}()
@test EnumSets.PackingTrait(SetX2()) == EnumSets.OffsetBasedPacking{0}()
@test EnumSets.PackingTrait(SetX4()) == EnumSets.OffsetBasedPacking{0}()
@test EnumSets.PackingTrait(SetX8()) == EnumSets.OffsetBasedPacking{0}()
@test EnumSets.PackingTrait(SetX16()) == EnumSets.OffsetBasedPacking{0}()

fuzz_booleans(SetX1)
fuzz_booleans(SetX2)
fuzz_booleans(SetX4)
fuzz_booleans(SetX8)
fuzz_booleans(SetX16)
end

2 comments on commit 83a19e9

@jw3126
Copy link
Owner Author

@jw3126 jw3126 commented on 83a19e9 Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/121332

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.2.0 -m "<description of version>" 83a19e9c50173abfb2f94bfd9d31890db272d145
git push origin v1.2.0

Please sign in to comment.