|
1 | 1 | module StructHelpers |
2 | 2 |
|
3 | 3 | export @batteries |
| 4 | +export @enumbatteries |
4 | 5 |
|
5 | 6 | import ConstructionBase: getproperties, constructorof, setproperties |
6 | 7 |
|
@@ -72,8 +73,7 @@ function doc_batteries_options() |
72 | 73 | join(lines, "\n") |
73 | 74 | end |
74 | 75 |
|
75 | | - |
76 | | -const ALLOWED_KW = keys(BATTERIES_DEFAULTS) |
| 76 | +const BATTERIES_ALLOWED_KW = keys(BATTERIES_DEFAULTS) |
77 | 77 |
|
78 | 78 | """ |
79 | 79 |
|
@@ -104,7 +104,7 @@ macro batteries(T, kw...) |
104 | 104 | error(""" |
105 | 105 | Unsupported keyword. |
106 | 106 | Offending Keyword: $pname |
107 | | - allowed: $ALLOWED_KW |
| 107 | + allowed: $BATTERIES_ALLOWED_KW |
108 | 108 | Got: $nt |
109 | 109 | """) |
110 | 110 | end |
@@ -193,4 +193,162 @@ function parse_all_macro_kw(kw) |
193 | 193 | (;pairs...) |
194 | 194 | end |
195 | 195 |
|
| 196 | +################################################################################ |
| 197 | +#### enum |
| 198 | +################################################################################ |
| 199 | +function ifelsechain( |
| 200 | + cond_code_pairs, |
| 201 | + rest |
| 202 | + ) |
| 203 | + if length(cond_code_pairs) == 0 |
| 204 | + return rest |
| 205 | + elseif length(cond_code_pairs) == 1 |
| 206 | + cond, code = only(cond_code_pairs) |
| 207 | + Expr(:if, cond, code, rest) |
| 208 | + else |
| 209 | + cond, code = cond_code_pairs[end] |
| 210 | + ifelsechain( |
| 211 | + cond_code_pairs[begin:end-1], |
| 212 | + Expr(:elseif, cond, code, rest), |
| 213 | + ) |
| 214 | + end |
| 215 | +end |
| 216 | + |
| 217 | +function enum_from_string end |
| 218 | +function enum_from_symbol end |
| 219 | +function string_from_enum(x)::String |
| 220 | + string(x) |
| 221 | +end |
| 222 | +function symbol_from_enum(x)::Symbol |
| 223 | + Symbol(string_from_enum(x)) |
| 224 | +end |
| 225 | + |
| 226 | +function def_enum_from_string(T)::Expr |
| 227 | + body = def_symbol_or_enum_from_string_body(string_from_enum, T) |
| 228 | + :( |
| 229 | + function StructHelpers.enum_from_string(::Type{$T}, s::String)::$T |
| 230 | + $body |
| 231 | + end |
| 232 | + ) |
| 233 | +end |
| 234 | +function def_enum_from_symbol(T)::Expr |
| 235 | + body = def_symbol_or_enum_from_string_body(QuoteNode∘symbol_from_enum, T) |
| 236 | + :( |
| 237 | + function StructHelpers.enum_from_symbol(::Type{$T}, s::Symbol)::$T |
| 238 | + $body |
| 239 | + end |
| 240 | + ) |
| 241 | +end |
| 242 | + |
| 243 | +@noinline function throw_no_matching_instance(f,T,s) |
| 244 | + msg = """ |
| 245 | + Cannot instaniate enum `T` from `s`. Got: |
| 246 | + s = $(repr(s)) |
| 247 | + T = $(T) |
| 248 | + allowed values for s = $(map(f, instances(T))) |
| 249 | + """ |
| 250 | + throw(ArgumentError(msg)) |
| 251 | +end |
| 252 | + |
| 253 | +function def_symbol_or_enum_from_string_body(f,T) |
| 254 | + err = :($throw_no_matching_instance($f,$T,s)) |
| 255 | + matcharms = [ |
| 256 | + :(s === $(f(inst))) => inst for inst in instances(T) |
| 257 | + ] |
| 258 | + ifelsechain(matcharms, err) |
| 259 | +end |
| 260 | + |
| 261 | +const ENUM_BATTERIES_DEFAULTS = ( |
| 262 | + string_conversion=false, |
| 263 | + symbol_conversion=false, |
| 264 | +) |
| 265 | + |
| 266 | +const ENUM_BATTERIES_DOCSTRINGS = ( |
| 267 | + string_conversion="Add `convert(MyEnum, ::String)`, `MyEnum(::String)`, `convert(String, ::MyEnum)` and `String(::MyEnum)`", |
| 268 | + symbol_conversion="Add `convert(MyEnum, ::Symbol)`, `MyEnum(::Symbol)`, `convert(Symbol, ::MyEnum)` and `Symbol(::MyEnum)`", |
| 269 | +) |
| 270 | + |
| 271 | +if (keys(ENUM_BATTERIES_DEFAULTS) != keys(ENUM_BATTERIES_DOCSTRINGS)) |
| 272 | + error(""" |
| 273 | + keys(ENUM_BATTERIES_DEFAULTS) == key(ENUM_BATTERIES_DOCSTRINGS) must hold. |
| 274 | + Got: |
| 275 | + keys(ENUM_BATTERIES_DEFAULTS) = $(keys(ENUM_BATTERIES_DEFAULTS)) |
| 276 | + keys(ENUM_BATTERIES_DOCSTRINGS) = $(keys(ENUM_BATTERIES_DOCSTRINGS)) |
| 277 | + """) |
196 | 278 | end |
| 279 | +@assert keys(ENUM_BATTERIES_DEFAULTS) == keys(ENUM_BATTERIES_DOCSTRINGS) |
| 280 | + |
| 281 | +function doc_enum_batteries_options() |
| 282 | + lines = map(propertynames(ENUM_BATTERIES_DEFAULTS)) do key |
| 283 | + "* **$key** = $(ENUM_BATTERIES_DEFAULTS[key]):\n $(ENUM_BATTERIES_DOCSTRINGS[key])" |
| 284 | + end |
| 285 | + join(lines, "\n") |
| 286 | +end |
| 287 | + |
| 288 | +const ENUM_BATTERIES_ALLOWED_KW = keys(ENUM_BATTERIES_DEFAULTS) |
| 289 | + |
| 290 | +""" |
| 291 | +
|
| 292 | + @enumbatteries T [options] |
| 293 | +
|
| 294 | +Automatically derive several methods for Enum type `T`. |
| 295 | +
|
| 296 | +# Example |
| 297 | +```julia |
| 298 | +@enum Color Red Blue Yellow |
| 299 | +@enumbatteries Color |
| 300 | +@enumbatteries Color hash=false # don't overload `Base.hash` |
| 301 | +@enumbatteries Color symbol_conversion=true # allow convert(Color, :Blue), Color(:Blue), convert(Symbol, Blue), Symbol(Blue) |
| 302 | +``` |
| 303 | +
|
| 304 | +Supported options and defaults are: |
| 305 | +
|
| 306 | +$(doc_enum_batteries_options()) |
| 307 | +""" |
| 308 | +macro enumbatteries(T, kw...) |
| 309 | + nt = parse_all_macro_kw(kw) |
| 310 | + for (pname, val) in pairs(nt) |
| 311 | + if !(pname in propertynames(ENUM_BATTERIES_DEFAULTS)) |
| 312 | + error(""" |
| 313 | + Unsupported keyword. |
| 314 | + Offending Keyword: $pname |
| 315 | + allowed: $ENUM_BATTERIES_ALLOWED_KW |
| 316 | + Got: $nt |
| 317 | + """) |
| 318 | + end |
| 319 | + if val isa Bool |
| 320 | + |
| 321 | + else |
| 322 | + error(""" |
| 323 | + Bad keyword argument value: |
| 324 | + Got: $nt |
| 325 | + Offending Keyword: $pname |
| 326 | + Offending value : $(repr(val)) |
| 327 | + """) |
| 328 | + end |
| 329 | + end |
| 330 | + nt = merge(ENUM_BATTERIES_DEFAULTS, nt) |
| 331 | + TT = Base.eval(__module__, T)::Type |
| 332 | + ret = quote end |
| 333 | + |
| 334 | + push!(ret.args, :(import StructHelpers)) |
| 335 | + push!(ret.args, def_enum_from_symbol(TT)) |
| 336 | + push!(ret.args, def_enum_from_string(TT)) |
| 337 | + if nt.string_conversion |
| 338 | + ex1 = :(Base.convert(::Type{$TT}, s::AbstractString) = StructHelpers.enum_from_string($TT, String(s))) |
| 339 | + ex2 = :($T(s::AbstractString) = StructHelpers.enum_from_string($TT, String(s))) |
| 340 | + ex3 = :(Base.convert(::Type{String}, x::$T) = StructHelpers.string_from_enum(x)) |
| 341 | + ex4 = :(Base.String(x::$T) = StructHelpers.string_from_enum(x)) |
| 342 | + push!(ret.args, ex1, ex2, ex3, ex4) |
| 343 | + end |
| 344 | + if nt.symbol_conversion |
| 345 | + ex1 = :(Base.convert(::Type{$T}, s::Symbol) = StructHelpers.enum_from_symbol($TT, Symbol(s))) |
| 346 | + ex2 = :($T(s::Symbol) = StructHelpers.enum_from_symbol($TT, Symbol(s))) |
| 347 | + ex3 = :(Base.convert(::Type{Symbol}, x::$T) = StructHelpers.symbol_from_enum(x)) |
| 348 | + ex4 = :(Base.Symbol(x::$T) = StructHelpers.symbol_from_enum(x)) |
| 349 | + push!(ret.args, ex1, ex2, ex3, ex4) |
| 350 | + end |
| 351 | + return esc(ret) |
| 352 | +end |
| 353 | + |
| 354 | +end #module |
0 commit comments