forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Enums.jl
143 lines (130 loc) · 4.66 KB
/
Enums.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# This file is a part of Julia. License is MIT: http:https://julialang.org/license
module Enums
import Core.Intrinsics.box
export Enum, @enum
abstract Enum
Base.convert{T<:Integer}(::Type{T}, x::Enum) = convert(T, box(Int32, x))
Base.write(io::IO, x::Enum) = write(io, Int32(x))
Base.read{T<:Enum}(io::IO, ::Type{T}) = T(read(io, Int32))
# generate code to test whether expr is in the given set of values
function membershiptest(expr, values)
lo, hi = extrema(values)
if length(values) == hi - lo + 1
:($lo <= $expr <= $hi)
elseif length(values) < 20
foldl((x1,x2)->:($x1 || ($expr == $x2)), :($expr == $(values[1])), values[2:end])
else
:($expr in $(Set(values)))
end
end
@noinline enum_argument_error(typename, x) = throw(ArgumentError(string("invalid value for Enum $(typename): $x")))
"""
@enum EnumName EnumValue1[=x] EnumValue2[=y]
Create an `Enum` type with name `EnumName` and enum member values of
`EnumValue1` and `EnumValue2` with optional assigned values of `x` and `y`, respectively.
`EnumName` can be used just like other types and enum member values as regular values, such as
```jldoctest
julia> @enum FRUIT apple=1 orange=2 kiwi=3
julia> f(x::FRUIT) = "I'm a FRUIT with value: \$(Int(x))"
f (generic function with 1 method)
julia> f(apple)
"I'm a FRUIT with value: 1"
```
"""
macro enum(T,syms...)
if isempty(syms)
throw(ArgumentError("no arguments given for Enum $T"))
end
if !isa(T,Symbol)
throw(ArgumentError("invalid type expression for enum $T"))
end
typename = T
vals = Array{Tuple{Symbol,Integer}}(0)
lo = hi = 0
i = Int32(-1)
hasexpr = false
for s in syms
if isa(s,Symbol)
if i == typemax(typeof(i))
throw(ArgumentError("overflow in value \"$s\" of Enum $typename"))
end
i += one(i)
elseif isa(s,Expr) &&
(s.head == :(=) || s.head == :kw) &&
length(s.args) == 2 && isa(s.args[1],Symbol)
i = eval(current_module(),s.args[2]) # allow exprs, e.g. uint128"1"
if !isa(i, Integer)
throw(ArgumentError("invalid value for Enum $typename, $s=$i; values must be integers"))
end
i = convert(Int32, i)
s = s.args[1]
hasexpr = true
else
throw(ArgumentError(string("invalid argument for Enum ", typename, ": ", s)))
end
if !Base.isidentifier(s)
throw(ArgumentError("invalid name for Enum $typename; \"$s\" is not a valid identifier."))
end
push!(vals, (s,i))
if length(vals) == 1
lo = hi = i
else
lo = min(lo, i)
hi = max(hi, i)
end
end
values = Int32[i[2] for i in vals]
if hasexpr && values != unique(values)
throw(ArgumentError("values for Enum $typename are not unique"))
end
blk = quote
# enum definition
Base.@__doc__(bitstype 32 $(esc(T)) <: Enum)
function Base.convert(::Type{$(esc(typename))}, x::Integer)
$(membershiptest(:x, values)) || enum_argument_error($(Expr(:quote, typename)), x)
box($(esc(typename)), convert(Int32, x))
end
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(Int32(x), Int32(y))
let insts = ntuple(i->$(esc(typename))($values[i]), $(length(vals)))
Base.instances(::Type{$(esc(typename))}) = insts
end
function Base.print(io::IO, x::$(esc(typename)))
for (sym, i) in $vals
if i == Int32(x)
print(io, sym); break
end
end
end
function Base.show(io::IO, x::$(esc(typename)))
if get(io, :compact, false)
print(io, x)
else
print(io, x, "::")
showcompact(io, typeof(x))
print(io, " = ", Int(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, "Enum ")
Base.show_datatype(io, t)
print(io, ":")
for (sym, i) in $vals
print(io, "\n", sym, " = ", i)
end
end
end
if isa(T,Symbol)
for (sym,i) in vals
push!(blk.args, :(const $(esc(sym)) = $(esc(T))($i)))
end
end
push!(blk.args, :nothing)
blk.head = :toplevel
return blk
end
end # module