Skip to content

Commit

Permalink
Merge pull request #10168 from JuliaLang/jq/enum
Browse files Browse the repository at this point in the history
RFC: Add Enums module for enum support through the `@enum` macro
  • Loading branch information
quinnj committed Feb 21, 2015
2 parents 621011c + 5f346d9 commit 4319353
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 22 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ New language features
and macros in packages and user code ([#8791]). Type `?@doc` at the repl
to see the current syntax and more information.

* Enums are now supported through the `@enum EnumName EnumValue1 EnumValue2` syntax. Enum member values also support abitrary value assignment by the `@enum EnumName EnumValue1=1 EnumValue2=10 EnumValue3=20` syntax.

Language changes
----------------

Expand Down
86 changes: 86 additions & 0 deletions base/Enums.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
module Enums

export @enum

abstract Enum

function Base.convert{T<:Integer}(::Type{T},x::Enum)
(x.val < typemin(T) || x.val > typemax(T)) && throw(InexactError())
convert(T, x.val)
end
Base.convert(::Type{BigInt},x::Enum) = big(x.val)
function Base.convert{T<:Enum}(::Type{T},x::Integer)
(x < typemin(T).val || x > typemax(T).val) && throw(InexactError())
T(x)
end
Base.start{T<:Enum}(::Type{T}) = 1
Base.next{T<:Enum}(::Type{T},s) = Base.next(names(T),s)
Base.done{T<:Enum}(::Type{T},s) = Base.done(names(T),s)
# Pass Integer through to Enum constructor through Val{T}
call{T<:Enum}(::Type{T},x::Integer) = T(Val{convert(fieldtype(T,:val),x)})
# Catchall that errors when specific Enum(::Type{Val{n}}) hasn't been defined
call{T<:Enum,n}(::Type{T},::Type{Val{n}}) = error(string("invalid enum value for $T: ",n))

macro enum(T,syms...)
assert(!isempty(syms))
vals = Array((Symbol,Integer),0)
lo = typemax(Int)
hi = typemin(Int)
i = -1
enumT = typeof(i)
hasexpr = false
for s in syms
i += one(typeof(i))
if isa(s,Symbol)
# pass
elseif isa(s,Expr) && s.head == :(=) && length(s.args) == 2 && isa(s.args[1],Symbol)
i = eval(s.args[2]) # allow exprs, e.g. uint128"1"
s = s.args[1]
hasexpr = true
else
error(string("invalid syntax in @enum: ",s))
end
push!(vals, (s,i))
I = typeof(i)
enumT = ifelse(length(vals) == 1, I, promote_type(enumT,I))
i < lo && (lo = i)
i > hi && (hi = i)
end
if !hasexpr
n = length(vals)
enumT = n < 128 ? Int8 : n < 32768 ? Int16 :
n < 2147483648 ? Int32 : Int64
end
blk = quote
# enum definition
immutable $(esc(T)) <: $(esc(Enum))
val::$(esc(enumT))
end
$(esc(:(Base.typemin)))(x::Type{$(esc(T))}) = $(esc(T))($lo)
$(esc(:(Base.typemax)))(x::Type{$(esc(T))}) = $(esc(T))($hi)
$(esc(:(Base.length)))(x::Type{$(esc(T))}) = $(length(vals))
$(esc(:(Base.names)))(x::Type{$(esc(T))}) = $(esc(T))[]
function $(esc(:(Base.show))){T<:$(esc(T))}(io::IO,x::T)
vals = $vals
for (sym, i) in vals
i = convert($(esc(enumT)), i)
i == x.val && print(io, sym)
end
print(io, "::", T.name)
end
end
for (sym,i) in vals
i = convert(enumT, i)
# add inner constructors to Enum type definition for specific Val{T} values
push!(blk.args[2].args[3].args, :($(esc(T))(::Type{Val{$i}}) = new($i)))
# define enum member constants
push!(blk.args, :(const $(esc(sym)) = $(esc(T))($i)))
# add enum member value to names(T) function
push!(blk.args[10].args[2].args[2].args, :($(esc(sym))))
end
push!(blk.args, :nothing)
blk.head = :toplevel
return blk
end

end # module
3 changes: 2 additions & 1 deletion base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1443,4 +1443,5 @@ export
@inline,
@noinline,
@doc,
@doc_str
@doc_str,
@enum
4 changes: 4 additions & 0 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ importall .Profile
include("Dates.jl")
import .Dates: Date, DateTime, now

# enums
include("Enums.jl")
import .Enums: @enum

# deprecated functions
include("deprecated.jl")

Expand Down
14 changes: 14 additions & 0 deletions doc/stdlib/base.rst
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,20 @@ Types
(at compile-time) to an implementation ``f(::Type{Val{false}})``,
without having to test the boolean value at runtime.

.. function:: @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

.. doctest::

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"

Generic Functions
-----------------

Expand Down
16 changes: 0 additions & 16 deletions examples/enum.jl

This file was deleted.

3 changes: 2 additions & 1 deletion test/choosetests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function choosetests(choices = [])
"euler", "show", "lineedit", "replcompletions", "repl",
"replutil", "sets", "test", "goto", "llvmcall", "grisu",
"nullable", "meta", "profile", "libgit2", "docs", "markdown",
"base64", "parser", "serialize", "functors", "char", "misc"
"base64", "parser", "serialize", "functors", "char", "misc",
"enums"
]

if isdir(joinpath(JULIA_HOME, Base.DOCDIR, "examples"))
Expand Down
144 changes: 144 additions & 0 deletions test/enums.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
module TestEnums

using Base.Test

@test_throws MethodError convert(Base.Enums.Enum, 1.0)

@enum Fruit apple orange kiwi
@test typeof(Fruit) == DataType
@test isbits(Fruit)
@test typeof(apple) <: Fruit <: Base.Enums.Enum
@test typeof(apple.val) <: Int8
@test int(apple) == 0
@test int(orange) == 1
@test int(kiwi) == 2
@test apple.val === Int8(0)
@test orange.val === Int8(1)
@test kiwi.val === Int8(2)
@test Fruit(0) == apple
@test Fruit(1) == orange
@test Fruit(2) == kiwi
@test_throws ErrorException Fruit(3)
@test_throws ErrorException Fruit(-1)
@test Fruit(Val{Int8(0)}) == apple
@test_throws ErrorException Fruit(Val{3})
@test Fruit(0x00) == apple
@test Fruit(big(0)) == apple
@test_throws MethodError Fruit(0.0)
@test start(Fruit) == 1
@test next(Fruit,1) == (apple,2)
@test next(Fruit,2) == (orange,3)
@test next(Fruit,3) == (kiwi,4)
@test !done(Fruit,3)
@test done(Fruit,4)
@test length(Fruit) == 3
@test typemin(Fruit) == apple
@test typemax(Fruit) == kiwi
@test convert(Fruit,0) == apple
@test convert(Fruit,1) == orange
@test convert(Fruit,2) == kiwi
@test_throws InexactError convert(Fruit,3)
@test_throws InexactError convert(Fruit,-1)
@test convert(UInt8,apple) === 0x00
@test convert(UInt16,orange) === 0x0001
@test convert(UInt128,kiwi) === 0x00000000000000000000000000000002
@test typeof(convert(BigInt,apple)) <: BigInt
@test convert(BigInt,apple) == 0
@test convert(Bool,apple) == false
@test convert(Bool,orange) == true
@test_throws InexactError convert(Bool,kiwi)
@test names(Fruit) == [apple, orange, kiwi]

f(x::Fruit) = "hey, I'm a Fruit"
@test f(apple) == "hey, I'm a Fruit"

d = Dict(apple=>"apple",orange=>"orange",kiwi=>"kiwi")
@test d[apple] == "apple"
@test d[orange] == "orange"
@test d[kiwi] == "kiwi"
vals = [apple,orange,kiwi]
for (i,enum) in enumerate(Fruit)
@test enum == vals[i]
end

@enum(QualityofFrenchFood, ReallyGood)
@test length(QualityofFrenchFood) == 1
@test typeof(ReallyGood) <: QualityofFrenchFood <: Base.Enums.Enum
@test int(ReallyGood) == 0

@enum Binary _zero=0 _one=1 _two=10 _three=11
@test _zero.val === 0
@test _one.val === 1
@test _two.val === 10
@test _three.val === 11
@enum Negative _neg1=-1 _neg2=-2
@test _neg1.val === -1
@test _neg2.val === -2
@enum Negative2 _neg5=-5 _neg4 _neg3
@test _neg5.val === -5
@test _neg4.val === -4
@test _neg3.val === -3

# currently allow silent overflow
@enum Uint8Overflow ff=0xff overflowed
@test ff.val === 0xff
@test overflowed.val === 0x00

@test_throws MethodError eval(:(@enum Test1 _zerofp=0.0))

@test_throws InexactError eval(:(@enum Test11 _zerofp2=0.5))
# can't use non-identifiers as enum members
@test_throws ErrorException eval(:(@enum Test2 1=2))
# other Integer types of enum members
@enum Test3 _one_Test3=0x01 _two_Test3=0x02 _three_Test3=0x03
@test typeof(_one_Test3.val) <: UInt8
@test _one_Test3.val === 0x01
@test length(Test3) == 3

@enum Test4 _one_Test4=0x01 _two_Test4=0x0002 _three_Test4=0x03
@test _one_Test4.val === 0x0001
@test _two_Test4.val === 0x0002
@test _three_Test4.val === 0x0003
@test typeof(_one_Test4.val) <: UInt16

@enum Test5 _one_Test5=0x01 _two_Test5=0x00000002 _three_Test5=0x00000003
@test _one_Test5.val === 0x00000001
@test _two_Test5.val === 0x00000002
@test _three_Test5.val === 0x00000003
@test typeof(_one_Test5.val) <: UInt32

@enum Test6 _one_Test6=0x00000000000000000000000000000001 _two_Test6=0x00000000000000000000000000000002
@test _one_Test6.val === 0x00000000000000000000000000000001
@test _two_Test6.val === 0x00000000000000000000000000000002
@test typeof(_one_Test6.val) <: UInt128

@enum Test7 _zero_Test7=0b0 _one_Test7=0b1 _two_Test7=0b10
@test _zero_Test7.val === 0x00
@test _one_Test7.val === 0x01
@test _two_Test7.val === 0x02
@test typeof(_zero_Test7.val) <: UInt8

@test_throws MethodError eval(:(@enum Test8 _zero="zero"))
@test_throws MethodError eval(:(@enum Test9 _zero='0'))

@enum Test8 _zero_Test8=zero(Int64)
@test typeof(_zero_Test8.val) <: Int64
@test _zero_Test8.val === Int64(0)

@enum Test9 _zero_Test9 _one_Test9=0x01 _two_Test9
@test typeof(_zero_Test9.val) <: Int
@test _zero_Test9.val === 0
@test typeof(_one_Test9.val) <: Int
@test _one_Test9.val === 1
@test typeof(_two_Test9.val) <: Int
@test _two_Test9.val === 2

@enum Test10 _zero_Test10=0x00 _one_Test10 _two_Test10
@test typeof(_zero_Test10.val) <: UInt8
@test _zero_Test10.val === 0x00
@test typeof(_one_Test10.val) <: UInt8
@test _one_Test10.val === 0x01
@test typeof(_two_Test10.val) <: UInt8
@test _two_Test10.val === 0x02

end # module
4 changes: 0 additions & 4 deletions test/examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ include(joinpath(dir, "bubblesort.jl"))
a = rand(1:100,100)
@test issorted(sort!(a;alg=BubbleSort))

include(joinpath(dir, "enum.jl"))
@enum TestEnum TestEnum1 TestEnum2 TestEnum3
@test [TestEnum1.n,TestEnum2.n,TestEnum3.n] == [0,1,2]

include(joinpath(dir, "lru.jl"))
include(joinpath(dir, "lru_test.jl"))

Expand Down

0 comments on commit 4319353

Please sign in to comment.