-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[regression] parseEnum
does not work anymore for enums with holes
#14030
Comments
There are also issues with performance of this function. Too many allocations in the loop.Let's fix it PROPERLY, we should get rid of the loop. I see two possible solutions. First: generate case statement with string normalization using a macro. Second: case insensitive const table of string to enum. @Araq, what would be your preference? |
Something like this I guess? macro genEnumStmt(n, argIdent: typed,
default: typed): untyped =
let impl = getType(n)
expectKind impl[1], nnkEnumTy
let lower = bindSym"toLowerAscii"
result = nnkCaseStmt.newTree(nnkDotExpr.newTree(argIdent, lower))
for f in impl[1]:
case f.kind
of nnkSym, nnkIdent:
result.add nnkOfBranch.newTree(
nnkDotExpr.newTree(f.toStrLit,
lower),
f)
else: discard
# finally add else branch to raise or use default
expectKind(default, nnkSym)
if default == newLit false:
let raiseStmt = quote do:
raise newException(ValueError, "Invalid value: " & $`argIdent`)
result.add nnkElse.newTree(raiseStmt)
else:
result.add nnkElse.newTree(default)
proc parseEn[T: enum](x: string): T =
genEnumStmt(T, x, default = false)
proc parseEn[T: enum](x: string, default: T): T =
genEnumStmt(T, x, default) But both of your proposals would mean either to import macros in strutils or some other module in such a |
Replace let lower = bindSym"toLowerAscii" with let lower = bindSym"normalize" Also you need to handle four ways of defining enums: type
MyEnum = enum
mxA
mxB = "bb"
mxC = (5, "ccc")
mxD = 15 You will need |
let lower = bindSym"normalize" Ah, ok. I used
Yeah, good point. Didn't think about more complicated cases. This should do: import macros, strutils
type
Enum = enum
A
B = "bb"
C = (5, "ccc")
D = 15
proc addOfBranch(n, field, lower: NimNode): NimNode =
result = nnkOfBranch.newTree(
nnkDotExpr.newTree(n,
lower),
field)
macro genEnumStmt(n, argIdent: typed,
default: typed): untyped =
let impl = n.getTypeInst[1].getImpl[2]
expectKind impl, nnkEnumTy
let lower = bindSym"normalize"
result = nnkCaseStmt.newTree(nnkDotExpr.newTree(argIdent, lower))
for f in impl:
case f.kind
of nnkEmpty: discard
of nnkSym, nnkIdent: result.add addOfBranch(f.toStrLit, f, lower)
of nnkEnumFieldDef:
case f[1].kind
of nnkStrLit: result.add addOfBranch(f[1], f[0], lower)
of nnkTupleConstr: result.add addOfBranch(f[1][1], f[0], lower)
of nnkIntLit: result.add addOfBranch(f[0].toStrLit, f[0], lower)
else: error("Invalid tuple syntax!")
else: error("Invalid node for enum type!")
# finally add else branch to raise or use default
expectKind(default, nnkSym)
if default == newLit false:
let raiseStmt = quote do:
raise newException(ValueError, "Invalid value: " & $`argIdent`)
result.add nnkElse.newTree(raiseStmt)
else:
result.add nnkElse.newTree(default)
proc parseEn[T: enum](x: string): T =
genEnumStmt(T, x, default = false)
proc parseEn[T: enum](x: string, default: T): T =
genEnumStmt(T, x, default)
let a = parseEn[Enum]("A")
let b = parseEn[Enum]("bb")
let c = parseEn[Enum]("ccc")
let d = parseEn[Enum]("D") |
Macro producing a case statement. |
So we're fine importing macros in |
Yes, we are importing macros from strutils |
@Vindaar, please submit as PR. One last very minor comment, with case branches you can do directly: nnkOfBranch.newTree(normalize(str), enumVal) Instead of addOfBranch(f[1], f[0], lower) that will do normalize but later on after macro expansion In this case nnkCaseStmt.newTree(nnkDotExpr.newTree(argIdent, bindSym"normalize")) |
Already mentioned on IRC, but obviously worth an issue.
The fix 9874981 for #14001 broke
parseEnum
for enums with holes, since the current implementation ofparseEnum
is based on iterating over the enum usinglow(T) .. high(T)
.It may be fine to break
for x in MyEnum
, but imo there's no reason whyparseEnum
taking a string should be affected.Example
Current Output
Expected Output
Should compile and work.
Possible Solution
Either as mentioned by @timotheecour in #14001 by adding magics similar to the
fields
iterator or by adding a macro similar to:and using that for the loop in
parseEnum
instead oflow(T) .. high(T)
.The latter has the major disadvantage that one suddenly has to import macros in
strutils
/ import a new module. Adding those tosystem.nim
is also not a good idea.Finally, by adding magics one can also support
for x in HoledEnum
again (even if via explicit iterators).I tried to add a new magic, but I fail to even get that magic e.g.:
to match an enum in the first place, because I don't understand how to match
type MyEnum
.Additional Information
Worked before the aforementioned commit 9874981.
The text was updated successfully, but these errors were encountered: