diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/pure/strutils.nim | 61 | ||||
-rw-r--r-- | lib/std/enumutils.nim | 64 |
2 files changed, 67 insertions, 58 deletions
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 491760137..19a37425e 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -76,7 +76,7 @@ import parseutils from math import pow, floor, log10 from algorithm import reverse -import macros # for `parseEnum` +import std/enumutils when defined(nimVmExportFixed): from unicode import toLower, toUpper @@ -1264,61 +1264,6 @@ proc parseBool*(s: string): bool = of "n", "no", "false", "0", "off": result = false else: raise newException(ValueError, "cannot interpret as a bool: " & s) -proc addOfBranch(s: string, field, enumType: NimNode): NimNode = - result = nnkOfBranch.newTree( - newLit s, - nnkCall.newTree(enumType, field) # `T(<fieldValue>)` - ) - -macro genEnumStmt(typ: typedesc, argSym: typed, default: typed): untyped = - # generates a case stmt, which assigns the correct enum field given - # a normalized string comparison to the `argSym` input. - # NOTE: for an enum with fields Foo, Bar, ... we cannot generate - # `of "Foo".nimIdentNormalize: Foo`. - # This will fail, if the enum is not defined at top level (e.g. in a block). - # Thus we check for the field value of the (possible holed enum) and convert - # the integer value to the generic argument `typ`. - let typ = typ.getTypeInst[1] - let impl = typ.getImpl[2] - expectKind impl, nnkEnumTy - result = nnkCaseStmt.newTree(newCall(bindSym"nimIdentNormalize", argSym)) - # stores all processed field strings to give error msg for ambiguous enums - var foundFields: seq[string] = @[] - var fStr = "" # string of current field - var fNum = BiggestInt(0) # int value of current field - for f in impl: - case f.kind - of nnkEmpty: continue # skip first node of `enumTy` - of nnkSym, nnkIdent: fStr = f.strVal - of nnkEnumFieldDef: - case f[1].kind - of nnkStrLit: fStr = f[1].strVal - of nnkTupleConstr: - fStr = f[1][1].strVal - fNum = f[1][0].intVal - of nnkIntLit: - fStr = f[0].strVal - fNum = f[1].intVal - else: error("Invalid tuple syntax!", f[1]) - else: error("Invalid node for enum type!", f) - # add field if string not already added - fStr = nimIdentNormalize(fStr) - if fStr notin foundFields: - result.add addOfBranch(fStr, newLit fNum, typ) - foundFields.add fStr - else: - error("Ambiguous enums cannot be parsed, field " & $fStr & - " appears multiple times!", f) - inc fNum - # finally add else branch to raise or use default - if default == nil: - let raiseStmt = quote do: - raise newException(ValueError, "Invalid enum value: " & $`argSym`) - result.add nnkElse.newTree(raiseStmt) - else: - expectKind(default, nnkSym) - result.add nnkElse.newTree(default) - proc parseEnum*[T: enum](s: string): T = ## Parses an enum ``T``. This errors at compile time, if the given enum ## type contains multiple fields with the same string value. @@ -1337,7 +1282,7 @@ proc parseEnum*[T: enum](s: string): T = doAssertRaises(ValueError): echo parseEnum[MyEnum]("third") - genEnumStmt(T, s, default = nil) + genEnumCaseStmt(T, s, default = nil, ord(low(T)), ord(high(T)), nimIdentNormalize) proc parseEnum*[T: enum](s: string, default: T): T = ## Parses an enum ``T``. This errors at compile time, if the given enum @@ -1356,7 +1301,7 @@ proc parseEnum*[T: enum](s: string, default: T): T = doAssert parseEnum[MyEnum]("second") == second doAssert parseEnum[MyEnum]("last", third) == third - genEnumStmt(T, s, default) + genEnumCaseStmt(T, s, default, ord(low(T)), ord(high(T)), nimIdentNormalize) proc repeat*(c: char, count: Natural): string {.noSideEffect, rtl, extern: "nsuRepeatChar".} = diff --git a/lib/std/enumutils.nim b/lib/std/enumutils.nim new file mode 100644 index 000000000..704e42de5 --- /dev/null +++ b/lib/std/enumutils.nim @@ -0,0 +1,64 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2020 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +import macros + +macro genEnumCaseStmt*(typ: typedesc, argSym: typed, default: typed, + userMin, userMax: static[int], normalizer: static[proc(s :string): string]): untyped = + # generates a case stmt, which assigns the correct enum field given + # a normalized string comparison to the `argSym` input. + # string normalization is done using passed normalizer. + # NOTE: for an enum with fields Foo, Bar, ... we cannot generate + # `of "Foo".nimIdentNormalize: Foo`. + # This will fail, if the enum is not defined at top level (e.g. in a block). + # Thus we check for the field value of the (possible holed enum) and convert + # the integer value to the generic argument `typ`. + let typ = typ.getTypeInst[1] + let impl = typ.getImpl[2] + expectKind impl, nnkEnumTy + let normalizerNode = quote: `normalizer` + expectKind normalizerNode, nnkSym + result = nnkCaseStmt.newTree(newCall(normalizerNode, argSym)) + # stores all processed field strings to give error msg for ambiguous enums + var foundFields: seq[string] = @[] + var fStr = "" # string of current field + var fNum = BiggestInt(0) # int value of current field + for f in impl: + case f.kind + of nnkEmpty: continue # skip first node of `enumTy` + of nnkSym, nnkIdent: fStr = f.strVal + of nnkEnumFieldDef: + case f[1].kind + of nnkStrLit: fStr = f[1].strVal + of nnkTupleConstr: + fStr = f[1][1].strVal + fNum = f[1][0].intVal + of nnkIntLit: + fStr = f[0].strVal + fNum = f[1].intVal + else: error("Invalid tuple syntax!", f[1]) + else: error("Invalid node for enum type!", f) + # add field if string not already added + if fNum >= userMin and fNum <= userMax: + fStr = normalizer(fStr) + if fStr notin foundFields: + result.add nnkOfBranch.newTree(newLit fStr, nnkCall.newTree(typ, newLit fNum)) + foundFields.add fStr + else: + error("Ambiguous enums cannot be parsed, field " & $fStr & + " appears multiple times!", f) + inc fNum + # finally add else branch to raise or use default + if default == nil: + let raiseStmt = quote do: + raise newException(ValueError, "Invalid enum value: " & $`argSym`) + result.add nnkElse.newTree(raiseStmt) + else: + expectKind(default, nnkSym) + result.add nnkElse.newTree(default) \ No newline at end of file |