Browse Source

Support custom values.

pull/8/head
Fredrik Ekre 4 years ago
parent
commit
f991da9204
  1. 53
      src/EnumX.jl
  2. 70
      test/runtests.jl

53
src/EnumX.jl

@ -6,6 +6,8 @@ export @enumx
abstract type Enum{T} <: Base.Enum{T} end abstract type Enum{T} <: Base.Enum{T} end
panic(x) = throw(ArgumentError(x))
macro enumx(args...) macro enumx(args...)
return enumx(__module__, args...) return enumx(__module__, args...)
end end
@ -14,11 +16,12 @@ function enumx(_module_, name, args...)
if name isa Symbol if name isa Symbol
modname = name modname = name
baseT = Int32 baseT = Int32
elseif name isa Expr && name.head == :(::) && name.args[1] isa Symbol && length(name.args) == 2 elseif name isa Expr && name.head == :(::) && name.args[1] isa Symbol &&
length(name.args) == 2
modname = name.args[1] modname = name.args[1]
baseT = Core.eval(_module_, name.args[2]) baseT = Core.eval(_module_, name.args[2])
else else
throw(ArgumentError("invalid EnumX.@enumx type specification: $(name)")) panic("invalid EnumX.@enumx type specification: $(name).")
end end
name = modname name = modname
if length(args) == 1 && args[1] isa Expr && args[1].head === :block if length(args) == 1 && args[1] isa Expr && args[1].head === :block
@ -27,18 +30,49 @@ function enumx(_module_, name, args...)
syms = args syms = args
end end
namemap = Dict{baseT,Symbol}() namemap = Dict{baseT,Symbol}()
next = 0 next = zero(baseT)
for s in syms for s in syms
s isa LineNumberNode && continue s isa LineNumberNode && continue
s isa Symbol || throw(ArgumentError("invalid member expression: $(s)")) local sym
namemap[next] = s if s isa Symbol
next += 1 if next == typemin(baseT)
panic("value overflow for Enum $(modname): $(modname).$(s) = $(next).")
end
sym = s
elseif s isa Expr && s.head === :(=) && s.args[1] isa Symbol && length(s.args) == 2
nx = Core.eval(_module_, s.args[2])
if !(nx isa Integer && typemin(baseT) <= nx <= typemax(baseT))
panic(
"invalid value for Enum $(modname){$(baseT)}: " *
"$(modname).$(s.args[1]) = $(repr(nx))."
)
end
next = convert(baseT, nx)
sym = s.args[1]
else
panic("invalid EnumX.@enumx entry: $(s)")
end
if next in keys(namemap)
panic(
"duplicate value for Enum $(modname): $(modname).$(sym) = $(next)," *
" value already used for $(modname).$(namemap[next]) = $(next)."
)
elseif sym in values(namemap)
value = findfirst(x -> x === sym, namemap)
panic(
"duplicate name for Enum $(modname): $(modname).$(sym) = $(next)," *
" name already used for $(modname).$(namemap[value]) = $(value)."
)
end
namemap[next] = sym
next += oneunit(baseT)
end end
module_block = quote module_block = quote
primitive type Type <: Enum{$(baseT)} $(sizeof(baseT) * 8) end primitive type Type <: Enum{$(baseT)} $(sizeof(baseT) * 8) end
let namemap = $(namemap) let namemap = $(namemap)
check_valid(x) = x in keys(namemap) || check_valid(x) = x in keys(namemap) ||
throw(ArgumentError("invalid value $(x) for Enum $($(QuoteNode(modname)))")) throw(ArgumentError("invalid value for Enum $($(QuoteNode(modname))): $(x)."))
global function $(esc(:Type))(x::Integer) global function $(esc(:Type))(x::Integer)
check_valid(x) check_valid(x)
return Base.bitcast($(esc(:Type)), convert($(baseT), x)) return Base.bitcast($(esc(:Type)), convert($(baseT), x))
@ -68,7 +102,10 @@ function Base.show(io::IO, ::MIME"text/plain", ::Base.Type{E}) where E <: Enum
string("$(nameof(parentmodule(E))).", v) => k for (k, v) in Base.Enums.namemap(E) string("$(nameof(parentmodule(E))).", v) => k for (k, v) in Base.Enums.namemap(E)
) )
mx = maximum(textwidth, keys(stringmap); init = 0) mx = maximum(textwidth, keys(stringmap); init = 0)
print(iob, "Enum type $(nameof(parentmodule(E))).Type <: Enum{$(Base.Enums.basetype(E))} with $(n) instance$(n == 1 ? "" : "s"):") print(iob,
"Enum type $(nameof(parentmodule(E))).Type <: ",
"Enum{$(Base.Enums.basetype(E))} with $(n) instance$(n == 1 ? "" : "s"):"
)
for (k, v) in stringmap for (k, v) in stringmap
print(iob, "\n", rpad(k, mx), " = $(v)") print(iob, "\n", rpad(k, mx), " = $(v)")
end end

70
test/runtests.jl

@ -40,8 +40,8 @@ getInt64() = Int64
@test Fruit.Type(Int32(0)) === Fruit.Type(0) === Fruit.Apple @test Fruit.Type(Int32(0)) === Fruit.Type(0) === Fruit.Apple
@test Fruit.Type(Int32(1)) === Fruit.Type(1) === Fruit.Banana @test Fruit.Type(Int32(1)) === Fruit.Type(1) === Fruit.Banana
@test_throws ArgumentError("invalid value 123 for Enum Fruit") Fruit.Type(Int32(123)) @test_throws ArgumentError("invalid value for Enum Fruit: 123.") Fruit.Type(Int32(123))
@test_throws ArgumentError("invalid value 123 for Enum Fruit") Fruit.Type(123) @test_throws ArgumentError("invalid value for Enum Fruit: 123.") Fruit.Type(123)
@test Fruit.Apple < Fruit.Banana @test Fruit.Apple < Fruit.Banana
@ -56,7 +56,7 @@ let io = IOBuffer()
seekstart(io) seekstart(io)
write(io, Int32(123)) write(io, Int32(123))
seekstart(io) seekstart(io)
@test_throws ArgumentError("invalid value 123 for Enum Fruit") read(io, Fruit.Type) @test_throws ArgumentError("invalid value for Enum Fruit: 123.") read(io, Fruit.Type)
end end
let io = IOBuffer() let io = IOBuffer()
@ -93,7 +93,7 @@ try
catch err catch err
err isa LoadError && (err = err.error) err isa LoadError && (err = err.error)
@test err isa ArgumentError @test err isa ArgumentError
@test err.msg == "invalid EnumX.@enumx type specification: Fr + uit" @test err.msg == "invalid EnumX.@enumx type specification: Fr + uit."
end end
@ -114,4 +114,66 @@ end
@test FruitBlock8.Apple === FruitBlock8.Type(0) @test FruitBlock8.Apple === FruitBlock8.Type(0)
@test FruitBlock8.Banana === FruitBlock8.Type(1) @test FruitBlock8.Banana === FruitBlock8.Type(1)
# Custom values
@enumx FruitValues Apple = 1 Banana = (1 + 2) Orange
@test FruitValues.Apple === FruitValues.Type(1)
@test FruitValues.Banana === FruitValues.Type(3)
@test FruitValues.Orange === FruitValues.Type(4)
@enumx FruitValues8::Int8 Apple = -1 Banana = (1 + 2) Orange
@test FruitValues8.Apple === FruitValues8.Type(-1)
@test FruitValues8.Banana === FruitValues8.Type(3)
@test FruitValues8.Orange === FruitValues8.Type(4)
@enumx FruitValuesBlock begin
Apple = sum((1, 2, 3))
Banana
end
@test FruitValuesBlock.Apple === FruitValuesBlock.Type(6)
@test FruitValuesBlock.Banana === FruitValuesBlock.Type(7)
try
@macroexpand @enumx Fruit::Int8 Apple=typemax(Int8) Banana
catch err
err isa LoadError && (err = err.error)
@test err isa ArgumentError
@test err.msg == "value overflow for Enum Fruit: Fruit.Banana = -128."
end
try
@macroexpand @enumx Fruit::Int8 Apple="apple"
catch err
err isa LoadError && (err = err.error)
@test err isa ArgumentError
@test err.msg == "invalid value for Enum Fruit{Int8}: Fruit.Apple = \"apple\"."
end
try
@macroexpand @enumx Fruit::Int8 Apple=128
catch err
err isa LoadError && (err = err.error)
@test err isa ArgumentError
@test err.msg == "invalid value for Enum Fruit{Int8}: Fruit.Apple = 128."
end
try
@macroexpand @enumx Fruit::Int8 Apple()
catch err
err isa LoadError && (err = err.error)
@test err isa ArgumentError
@test err.msg == "invalid EnumX.@enumx entry: Apple()"
end
try
@macroexpand @enumx Fruit Apple=0 Banana=0
catch err
err isa LoadError && (err = err.error)
@test err isa ArgumentError
@test err.msg == "duplicate value for Enum Fruit: Fruit.Banana = 0, value already used for Fruit.Apple = 0."
end
try
@macroexpand @enumx Fruit Apple Apple
catch err
err isa LoadError && (err = err.error)
@test err isa ArgumentError
@test err.msg == "duplicate name for Enum Fruit: Fruit.Apple = 1, name already used for Fruit.Apple = 0."
end
end # testset end # testset

Loading…
Cancel
Save