Recently I encountered a bottleneck while using `parseEnum` for some field
inside a large CSV file.
`parseEnum` represented about 60% of the runtime, most of it due to repeated
string allocation in a loop.
# strutils module.
proc parseEnum*[T: enum](s: string): T =
## Parses an enum ``T``.
##
## Raises ``ValueError`` for an invalid value in `s`. The comparison is
## done in a style insensitive way.
for e in low(T)..high(T):
if cmpIgnoreStyle(s, $e) == 0:
return e
raise newException(ValueError, "invalid enum value: " & s)
Since 'MyEnum' had many values, rather than hand-rolling a custom parseEnum
proc, I wrote it using macros to autogenerate the case switch.
import macros
macro case_list(typ: typedesc[enum]; has_error: static[bool]): untyped =
result = newStmtList()
let cas = newNimNode(nnkCaseStmt)
for node in getType(getTypeInst(typ)[1]):
if node.kind == nnkEmpty:
cas.add ident"s"
else:
cas.add newNimNode(nnkOfBranch).add( newStrLitNode($node),
newAssignment(ident"result", node)
)
if has_error:
cas.add newNimNode(nnkElse).add(
newNimNode(nnkRaiseStmt).add(
newCall(ident("newException"), ident("ValueError"),
infix(newStrLitNode("invalid enum value: "), "&",
ident("s"))
)
)
)
else:
cas.add newNimNode(nnkElse).add(
newAssignment(ident"result", ident("default"))
)
proc parseEnum[T: enum](s: string): T =
case_list(T, true)
proc parseEnum[T: enum](s: string; default: T): T =
case_list(T, false)
It would be possible to replace the parseEnum proc in strutils by those above,
but it would then import the macros module even if those function are not used.
It also substantially slowdown nim compilation to c.
I think this is the right approach but would probably need to be implemented as
compiler magic.
I have some idea how to do this, but would prefer an official okay to avoid
wasting time.