From 0396092b8461df3e653b223fcde020a1cb407a0c Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Wed, 11 Feb 2015 16:20:13 -0700 Subject: [PATCH 1/8] Add Enums module for enum support through the macro --- base/Enums.jl | 79 ++++++++++++++++++++++++++++ base/exports.jl | 3 +- base/sysimg.jl | 4 ++ test/enums.jl | 130 +++++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 2 +- 5 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 base/Enums.jl create mode 100644 test/enums.jl diff --git a/base/Enums.jl b/base/Enums.jl new file mode 100644 index 0000000000000..5afb161d5f14c --- /dev/null +++ b/base/Enums.jl @@ -0,0 +1,79 @@ +module Enums + +export @enum + +abstract Enum + +function Base.convert{T<:Integer}(::Type{T},x::Enum) + (x.n < typemin(T) || x.n > typemax(T)) && throw(InexactError()) + convert(T, x.n) +end +Base.convert(::Type{BigInt},x::Enum) = big(x.n) +function Base.convert{T<:Enum}(::Type{T},x::Integer) + (x < typemin(T).n || x > typemax(T).n) && 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,:n),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) + 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] + 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 + blk = quote + # enum definition + immutable $(esc(T)) <: $(esc(Enum)) + n::$(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.n && 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 diff --git a/base/exports.jl b/base/exports.jl index 1203408719618..d20efabd3753a 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1438,4 +1438,5 @@ export @noinline, @doc, @doc_str, - @doc_mstr + @doc_mstr, + @enum diff --git a/base/sysimg.jl b/base/sysimg.jl index c06826a328fbc..798886e46fddd 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -291,6 +291,10 @@ importall .Profile include("Dates.jl") import .Dates: Date, DateTime, now +# enums +include("Enums.jl") +import .Enums: @enum + # deprecated functions include("deprecated.jl") diff --git a/test/enums.jl b/test/enums.jl new file mode 100644 index 0000000000000..7e49609c16230 --- /dev/null +++ b/test/enums.jl @@ -0,0 +1,130 @@ +module TestEnums + +using Base.Test + +@test_throws MethodError convert(Base.Enums.Enum, 1.0) + +Base.Enums.@enum Fruit apple orange kiwi +@test typeof(Fruit) == DataType +@test isbits(Fruit) +@test typeof(apple) <: Fruit <: Base.Enums.Enum +@test typeof(apple.n) <: Int +@test int(apple) == 0 +@test int(orange) == 1 +@test int(kiwi) == 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{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 + +Base.Enums.@enum(QualityofFrenchFood, ReallyGood) +@test length(QualityofFrenchFood) == 1 +@test typeof(ReallyGood) <: QualityofFrenchFood <: Base.Enums.Enum +@test int(ReallyGood) == 0 + +Base.Enums.@enum Binary _zero=0 _one=1 _two=10 _three=11 +@test _zero.n === 0 +@test _one.n === 1 +@test _two.n === 10 +@test _three.n === 11 +Base.Enums.@enum Negative _neg1=-1 _neg2=-2 +@test _neg1.n === -1 +@test _neg2.n === -2 +Base.Enums.@enum Negative2 _neg5=-5 _neg4 _neg3 +@test _neg5.n === -5 +@test _neg4.n === -4 +@test _neg3.n === -3 + +# currently allow silent overflow +Base.Enums.@enum Uint8Overflow ff=0xff overflowed +@test ff.n === 0xff +@test overflowed.n === 0x00 + +@test_throws MethodError eval(macroexpand(:(Base.Enums.@enum Test1 _zerofp=0.0))) + +@test_throws InexactError eval(macroexpand(:(Base.Enums.@enum Test11 _zerofp2=0.5))) +# can't use non-identifiers as enum members +@test_throws ErrorException eval(macroexpand(:(Base.Enums.@enum Test2 1=2))) +# other Integer types of enum members +Base.Enums.@enum Test3 _one_Test3=0x01 _two_Test3=0x02 _three_Test3=0x03 +@test typeof(_one_Test3.n) <: UInt8 +@test _one_Test3.n === 0x01 +@test length(Test3) == 3 + +Base.Enums.@enum Test4 _one_Test4=0x01 _two_Test4=0x0002 _three_Test4=0x03 +@test typeof(_one_Test4.n) <: UInt16 + +Base.Enums.@enum Test5 _one_Test5=0x01 _two_Test5=0x00000002 _three_Test5=0x00000003 +@test typeof(_one_Test5.n) <: UInt32 + +Base.Enums.@enum Test6 _one_Test6=0x00000000000000000000000000000001 _two_Test6=0x00000000000000000000000000000002 +@test typeof(_one_Test6.n) <: UInt128 + +Base.Enums.@enum Test7 _zero_Test7=0b0 _one_Test7=0b1 _two_Test7=0b10 +@test typeof(_zero_Test7.n) <: UInt8 + +@test_throws MethodError eval(macroexpand(:(Base.Enums.@enum Test8 _zero="zero"))) +@test_throws MethodError eval(macroexpand(:(Base.Enums.@enum Test9 _zero='0'))) + +Base.Enums.@enum Test8 _zero_Test8=zero(Int64) +@test typeof(_zero_Test8.n) <: Int64 +@test _zero_Test8.n === Int64(0) + +Base.Enums.@enum Test9 _zero_Test9 _one_Test9=0x01 _two_Test9 +@test typeof(_zero_Test9.n) <: Int +@test _zero_Test9.n === 0 +@test typeof(_one_Test9.n) <: Int +@test _one_Test9.n === 1 +@test typeof(_two_Test9.n) <: Int +@test _two_Test9.n === 2 + +Base.Enums.@enum Test10 _zero_Test10=0x00 _one_Test10 _two_Test10 +@test typeof(_zero_Test10.n) <: UInt8 +@test _zero_Test10.n === 0x00 +@test typeof(_one_Test10.n) <: UInt8 +@test _one_Test10.n === 0x01 +@test typeof(_two_Test10.n) <: UInt8 +@test _two_Test10.n === 0x02 + +end # module \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index f499145432a14..afb15cd334ad3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,7 +12,7 @@ testnames = [ "lineedit", "replcompletions", "repl", "replutil", "sets", "test", "goto", "llvmcall", "grisu", "nullable", "meta", "profile", "libgit2", "docs", "markdown", "base64", "parser", "serialize", "functors", - "char", "misc" + "char", "misc", "enums" ] if isdir(joinpath(JULIA_HOME, Base.DOCDIR, "examples")) From 20ff9f5c2e5f8de65491ec89e284fb24e753ad11 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Wed, 11 Feb 2015 21:57:55 -0700 Subject: [PATCH 2/8] Change .n field to .val in enums --- base/Enums.jl | 12 +++++----- test/enums.jl | 64 +++++++++++++++++++++++++-------------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/base/Enums.jl b/base/Enums.jl index 5afb161d5f14c..3936fd1ca244c 100644 --- a/base/Enums.jl +++ b/base/Enums.jl @@ -5,12 +5,12 @@ export @enum abstract Enum function Base.convert{T<:Integer}(::Type{T},x::Enum) - (x.n < typemin(T) || x.n > typemax(T)) && throw(InexactError()) - convert(T, x.n) + (x.val < typemin(T) || x.val > typemax(T)) && throw(InexactError()) + convert(T, x.val) end -Base.convert(::Type{BigInt},x::Enum) = big(x.n) +Base.convert(::Type{BigInt},x::Enum) = big(x.val) function Base.convert{T<:Enum}(::Type{T},x::Integer) - (x < typemin(T).n || x > typemax(T).n) && throw(InexactError()) + (x < typemin(T).val || x > typemax(T).val) && throw(InexactError()) T(x) end Base.start{T<:Enum}(::Type{T}) = 1 @@ -47,7 +47,7 @@ macro enum(T,syms...) blk = quote # enum definition immutable $(esc(T)) <: $(esc(Enum)) - n::$(esc(enumT)) + val::$(esc(enumT)) end $(esc(:(Base.typemin)))(x::Type{$(esc(T))}) = $(esc(T))($lo) $(esc(:(Base.typemax)))(x::Type{$(esc(T))}) = $(esc(T))($hi) @@ -57,7 +57,7 @@ macro enum(T,syms...) vals = $vals for (sym, i) in vals i = convert($(esc(enumT)), i) - i == x.n && print(io, sym) + i == x.val && print(io, sym) end print(io, "::", T.name) end diff --git a/test/enums.jl b/test/enums.jl index 7e49609c16230..a714283fa71c6 100644 --- a/test/enums.jl +++ b/test/enums.jl @@ -8,7 +8,7 @@ Base.Enums.@enum Fruit apple orange kiwi @test typeof(Fruit) == DataType @test isbits(Fruit) @test typeof(apple) <: Fruit <: Base.Enums.Enum -@test typeof(apple.n) <: Int +@test typeof(apple.val) <: Int @test int(apple) == 0 @test int(orange) == 1 @test int(kiwi) == 2 @@ -64,22 +64,22 @@ Base.Enums.@enum(QualityofFrenchFood, ReallyGood) @test int(ReallyGood) == 0 Base.Enums.@enum Binary _zero=0 _one=1 _two=10 _three=11 -@test _zero.n === 0 -@test _one.n === 1 -@test _two.n === 10 -@test _three.n === 11 +@test _zero.val === 0 +@test _one.val === 1 +@test _two.val === 10 +@test _three.val === 11 Base.Enums.@enum Negative _neg1=-1 _neg2=-2 -@test _neg1.n === -1 -@test _neg2.n === -2 +@test _neg1.val === -1 +@test _neg2.val === -2 Base.Enums.@enum Negative2 _neg5=-5 _neg4 _neg3 -@test _neg5.n === -5 -@test _neg4.n === -4 -@test _neg3.n === -3 +@test _neg5.val === -5 +@test _neg4.val === -4 +@test _neg3.val === -3 # currently allow silent overflow Base.Enums.@enum Uint8Overflow ff=0xff overflowed -@test ff.n === 0xff -@test overflowed.n === 0x00 +@test ff.val === 0xff +@test overflowed.val === 0x00 @test_throws MethodError eval(macroexpand(:(Base.Enums.@enum Test1 _zerofp=0.0))) @@ -88,43 +88,43 @@ Base.Enums.@enum Uint8Overflow ff=0xff overflowed @test_throws ErrorException eval(macroexpand(:(Base.Enums.@enum Test2 1=2))) # other Integer types of enum members Base.Enums.@enum Test3 _one_Test3=0x01 _two_Test3=0x02 _three_Test3=0x03 -@test typeof(_one_Test3.n) <: UInt8 -@test _one_Test3.n === 0x01 +@test typeof(_one_Test3.val) <: UInt8 +@test _one_Test3.val === 0x01 @test length(Test3) == 3 Base.Enums.@enum Test4 _one_Test4=0x01 _two_Test4=0x0002 _three_Test4=0x03 -@test typeof(_one_Test4.n) <: UInt16 +@test typeof(_one_Test4.val) <: UInt16 Base.Enums.@enum Test5 _one_Test5=0x01 _two_Test5=0x00000002 _three_Test5=0x00000003 -@test typeof(_one_Test5.n) <: UInt32 +@test typeof(_one_Test5.val) <: UInt32 Base.Enums.@enum Test6 _one_Test6=0x00000000000000000000000000000001 _two_Test6=0x00000000000000000000000000000002 -@test typeof(_one_Test6.n) <: UInt128 +@test typeof(_one_Test6.val) <: UInt128 Base.Enums.@enum Test7 _zero_Test7=0b0 _one_Test7=0b1 _two_Test7=0b10 -@test typeof(_zero_Test7.n) <: UInt8 +@test typeof(_zero_Test7.val) <: UInt8 @test_throws MethodError eval(macroexpand(:(Base.Enums.@enum Test8 _zero="zero"))) @test_throws MethodError eval(macroexpand(:(Base.Enums.@enum Test9 _zero='0'))) Base.Enums.@enum Test8 _zero_Test8=zero(Int64) -@test typeof(_zero_Test8.n) <: Int64 -@test _zero_Test8.n === Int64(0) +@test typeof(_zero_Test8.val) <: Int64 +@test _zero_Test8.val === Int64(0) Base.Enums.@enum Test9 _zero_Test9 _one_Test9=0x01 _two_Test9 -@test typeof(_zero_Test9.n) <: Int -@test _zero_Test9.n === 0 -@test typeof(_one_Test9.n) <: Int -@test _one_Test9.n === 1 -@test typeof(_two_Test9.n) <: Int -@test _two_Test9.n === 2 +@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 Base.Enums.@enum Test10 _zero_Test10=0x00 _one_Test10 _two_Test10 -@test typeof(_zero_Test10.n) <: UInt8 -@test _zero_Test10.n === 0x00 -@test typeof(_one_Test10.n) <: UInt8 -@test _one_Test10.n === 0x01 -@test typeof(_two_Test10.n) <: UInt8 -@test _two_Test10.n === 0x02 +@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 \ No newline at end of file From 02cb5203e776e8d79f59b9ff4557a410dc3362a3 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Wed, 11 Feb 2015 21:59:13 -0700 Subject: [PATCH 3/8] Unqualify usage of @enum to test that export is working correctly --- test/enums.jl | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/enums.jl b/test/enums.jl index a714283fa71c6..8e38cf9c1cdee 100644 --- a/test/enums.jl +++ b/test/enums.jl @@ -4,7 +4,7 @@ using Base.Test @test_throws MethodError convert(Base.Enums.Enum, 1.0) -Base.Enums.@enum Fruit apple orange kiwi +@enum Fruit apple orange kiwi @test typeof(Fruit) == DataType @test isbits(Fruit) @test typeof(apple) <: Fruit <: Base.Enums.Enum @@ -58,60 +58,60 @@ for (i,enum) in enumerate(Fruit) @test enum == vals[i] end -Base.Enums.@enum(QualityofFrenchFood, ReallyGood) +@enum(QualityofFrenchFood, ReallyGood) @test length(QualityofFrenchFood) == 1 @test typeof(ReallyGood) <: QualityofFrenchFood <: Base.Enums.Enum @test int(ReallyGood) == 0 -Base.Enums.@enum Binary _zero=0 _one=1 _two=10 _three=11 +@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 -Base.Enums.@enum Negative _neg1=-1 _neg2=-2 +@enum Negative _neg1=-1 _neg2=-2 @test _neg1.val === -1 @test _neg2.val === -2 -Base.Enums.@enum Negative2 _neg5=-5 _neg4 _neg3 +@enum Negative2 _neg5=-5 _neg4 _neg3 @test _neg5.val === -5 @test _neg4.val === -4 @test _neg3.val === -3 # currently allow silent overflow -Base.Enums.@enum Uint8Overflow ff=0xff overflowed +@enum Uint8Overflow ff=0xff overflowed @test ff.val === 0xff @test overflowed.val === 0x00 -@test_throws MethodError eval(macroexpand(:(Base.Enums.@enum Test1 _zerofp=0.0))) +@test_throws MethodError eval(macroexpand(:(@enum Test1 _zerofp=0.0))) -@test_throws InexactError eval(macroexpand(:(Base.Enums.@enum Test11 _zerofp2=0.5))) +@test_throws InexactError eval(macroexpand(:(@enum Test11 _zerofp2=0.5))) # can't use non-identifiers as enum members -@test_throws ErrorException eval(macroexpand(:(Base.Enums.@enum Test2 1=2))) +@test_throws ErrorException eval(macroexpand(:(@enum Test2 1=2))) # other Integer types of enum members -Base.Enums.@enum Test3 _one_Test3=0x01 _two_Test3=0x02 _three_Test3=0x03 +@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 -Base.Enums.@enum Test4 _one_Test4=0x01 _two_Test4=0x0002 _three_Test4=0x03 +@enum Test4 _one_Test4=0x01 _two_Test4=0x0002 _three_Test4=0x03 @test typeof(_one_Test4.val) <: UInt16 -Base.Enums.@enum Test5 _one_Test5=0x01 _two_Test5=0x00000002 _three_Test5=0x00000003 +@enum Test5 _one_Test5=0x01 _two_Test5=0x00000002 _three_Test5=0x00000003 @test typeof(_one_Test5.val) <: UInt32 -Base.Enums.@enum Test6 _one_Test6=0x00000000000000000000000000000001 _two_Test6=0x00000000000000000000000000000002 +@enum Test6 _one_Test6=0x00000000000000000000000000000001 _two_Test6=0x00000000000000000000000000000002 @test typeof(_one_Test6.val) <: UInt128 -Base.Enums.@enum Test7 _zero_Test7=0b0 _one_Test7=0b1 _two_Test7=0b10 +@enum Test7 _zero_Test7=0b0 _one_Test7=0b1 _two_Test7=0b10 @test typeof(_zero_Test7.val) <: UInt8 -@test_throws MethodError eval(macroexpand(:(Base.Enums.@enum Test8 _zero="zero"))) -@test_throws MethodError eval(macroexpand(:(Base.Enums.@enum Test9 _zero='0'))) +@test_throws MethodError eval(macroexpand(:(@enum Test8 _zero="zero"))) +@test_throws MethodError eval(macroexpand(:(@enum Test9 _zero='0'))) -Base.Enums.@enum Test8 _zero_Test8=zero(Int64) +@enum Test8 _zero_Test8=zero(Int64) @test typeof(_zero_Test8.val) <: Int64 @test _zero_Test8.val === Int64(0) -Base.Enums.@enum Test9 _zero_Test9 _one_Test9=0x01 _two_Test9 +@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 @@ -119,7 +119,7 @@ Base.Enums.@enum Test9 _zero_Test9 _one_Test9=0x01 _two_Test9 @test typeof(_two_Test9.val) <: Int @test _two_Test9.val === 2 -Base.Enums.@enum Test10 _zero_Test10=0x00 _one_Test10 _two_Test10 +@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 From f2d7f0f200db049856c62ca6dd73d6c556d15801 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Wed, 11 Feb 2015 22:20:58 -0700 Subject: [PATCH 4/8] If no enum member values are given, pick the smallest signed integer to fit all values --- base/Enums.jl | 9 ++++++++- test/enums.jl | 28 +++++++++++++++++++++------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/base/Enums.jl b/base/Enums.jl index 3936fd1ca244c..75d0187f8396f 100644 --- a/base/Enums.jl +++ b/base/Enums.jl @@ -17,7 +17,7 @@ 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,:n),x)}) +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)) @@ -28,6 +28,7 @@ macro enum(T,syms...) hi = typemin(Int) i = -1 enumT = typeof(i) + hasexpr = false for s in syms i += one(typeof(i)) if isa(s,Symbol) @@ -35,6 +36,7 @@ macro enum(T,syms...) 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 @@ -44,6 +46,11 @@ macro enum(T,syms...) 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)) diff --git a/test/enums.jl b/test/enums.jl index 8e38cf9c1cdee..ce40649aabca4 100644 --- a/test/enums.jl +++ b/test/enums.jl @@ -8,16 +8,19 @@ using Base.Test @test typeof(Fruit) == DataType @test isbits(Fruit) @test typeof(apple) <: Fruit <: Base.Enums.Enum -@test typeof(apple.val) <: Int +@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{0}) == apple +@test Fruit(Val{Int8(0)}) == apple @test_throws ErrorException Fruit(Val{3}) @test Fruit(0x00) == apple @test Fruit(big(0)) == apple @@ -81,11 +84,11 @@ end @test ff.val === 0xff @test overflowed.val === 0x00 -@test_throws MethodError eval(macroexpand(:(@enum Test1 _zerofp=0.0))) +@test_throws MethodError eval(:(@enum Test1 _zerofp=0.0)) -@test_throws InexactError eval(macroexpand(:(@enum Test11 _zerofp2=0.5))) +@test_throws InexactError eval(:(@enum Test11 _zerofp2=0.5)) # can't use non-identifiers as enum members -@test_throws ErrorException eval(macroexpand(:(@enum Test2 1=2))) +@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 @@ -93,19 +96,30 @@ end @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(macroexpand(:(@enum Test8 _zero="zero"))) -@test_throws MethodError eval(macroexpand(:(@enum Test9 _zero='0'))) +@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 From cce9a74015b4cfe975eb64063f54cf35f7f725e4 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Thu, 12 Feb 2015 07:45:30 -0700 Subject: [PATCH 5/8] Remove enum.jl from examples --- examples/enum.jl | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 examples/enum.jl diff --git a/examples/enum.jl b/examples/enum.jl deleted file mode 100644 index dfa764984dc7f..0000000000000 --- a/examples/enum.jl +++ /dev/null @@ -1,16 +0,0 @@ -macro enum(T,syms...) - blk = quote - immutable $(esc(T)) - n::Int32 - $(esc(T))(n::Integer) = new(n) - end - Base.show(io::IO, x::$(esc(T))) = print(io, $syms[x.n+1]) - Base.show(io::IO, x::Type{$(esc(T))}) = print(io, $(string("enum ", T, ' ', '(', join(syms, ", "), ')'))) - end - for (i,sym) in enumerate(syms) - push!(blk.args, :(const $(esc(sym)) = $(esc(T))($(i-1)))) - end - push!(blk.args, :nothing) - blk.head = :toplevel - return blk -end From 9533436fcb0394f0f0f9febea20c1d680e20f808 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Thu, 12 Feb 2015 09:49:44 -0700 Subject: [PATCH 6/8] Remove running of examples/enum.jl from test suite --- test/examples.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/examples.jl b/test/examples.jl index bd12472027504..a6f99edfb880f 100644 --- a/test/examples.jl +++ b/test/examples.jl @@ -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")) From d004891b7223e680f282480a9ca7fe4dc8b4826b Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Fri, 20 Feb 2015 20:48:53 -0700 Subject: [PATCH 7/8] Add docs and NEWS entry --- NEWS.md | 2 ++ doc/stdlib/base.rst | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/NEWS.md b/NEWS.md index 5502f06a23fe7..723bbb40520c6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 ---------------- diff --git a/doc/stdlib/base.rst b/doc/stdlib/base.rst index e3ec13086fa1e..b572f1e383620 100644 --- a/doc/stdlib/base.rst +++ b/doc/stdlib/base.rst @@ -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 ----------------- From 5f346d98729549a5b5c658bf70319c4b513e6225 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Sat, 21 Feb 2015 00:19:19 -0700 Subject: [PATCH 8/8] Resolve merge conflicts --- test/choosetests.jl | 3 ++- test/runtests.jl | 50 --------------------------------------------- 2 files changed, 2 insertions(+), 51 deletions(-) diff --git a/test/choosetests.jl b/test/choosetests.jl index 8a5010c8a8b1d..1079338db50f0 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -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")) diff --git a/test/runtests.jl b/test/runtests.jl index 7f94026898cab..70d91476050cd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,55 +1,5 @@ -<<<<<<< HEAD -# linalg tests take the longest - start them off first -testnames = [ - "linalg", "core", "keywordargs", "numbers", "strings", "dates", - "dict", "hashing", "remote", "iobuffer", "staged", "arrayops", - "subarray", "reduce", "reducedim", "random", "intfuncs", - "simdloop", "blas", "fft", "dsp", "sparse", "bitarray", "copy", "math", - "fastmath", "functional", "bigint", "sorting", "statistics", "spawn", - "backtrace", "priorityqueue", "arpack", "file", "version", - "resolve", "pollfd", "mpfr", "broadcast", "complex", "socket", - "floatapprox", "readdlm", "reflection", "regex", "float16", "combinatorics", - "sysinfo", "rounding", "ranges", "mod2pi", "euler", "show", - "lineedit", "replcompletions", "repl", "replutil", "sets", "test", "goto", - "llvmcall", "grisu", "nullable", "meta", "profile", - "libgit2", "docs", "markdown", "base64", "parser", "serialize", "functors", - "char", "misc", "enums" -] - -if isdir(joinpath(JULIA_HOME, Base.DOCDIR, "examples")) - push!(testnames, "examples") -end -@unix_only push!(testnames, "unicode") - -# parallel tests depend on other workers - do them last -push!(testnames, "parallel") - -tests = (ARGS==["all"] || isempty(ARGS)) ? testnames : ARGS - -if "sparse" in tests - # specifically selected case - filter!(x -> x != "sparse", tests) - prepend!(tests, ["sparse/sparse", "sparse/cholmod", "sparse/umfpack"]) -end - -if "linalg" in tests - # specifically selected case - filter!(x -> x != "linalg", tests) - prepend!(tests, ["linalg1", "linalg2", "linalg3", "linalg4", "linalg/lapack", "linalg/triangular", "linalg/tridiag", "linalg/pinv", "linalg/givens"]) -end - -net_required_for = ["socket", "parallel"] -net_on = true -try - getipaddr() -catch - warn("Networking unavailable: Skipping tests [" * join(net_required_for, ", ") * "]") - net_on = false -end -======= include("choosetests.jl") tests, net_on = choosetests(ARGS) ->>>>>>> master cd(dirname(@__FILE__)) do n = 1