diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 3f2e369f61442..eea8ac41c91e4 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -163,7 +163,7 @@ export export parsejson.JsonEventKind, parsejson.JsonError, JsonParser, JsonKindError, open, close, str, getInt, getFloat, kind, getColumn, getLine, getFilename, - errorMsg, errorMsgExpected, next, JsonParsingError, raiseParseErr, nimIdentNormalize + errorMsg, errorMsgExpected, next, JsonParsingError, raiseParseErr type JsonNodeKind* = enum ## possible JSON node types diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index a0a172430acc1..e999621fc582b 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -75,7 +75,6 @@ import parseutils from math import pow, floor, log10 from algorithm import reverse -import macros # for `parseEnum` when defined(nimVmExportFixed): from unicode import toLower, toUpper @@ -279,26 +278,6 @@ proc capitalizeAscii*(s: string): string {.noSideEffect, if s.len == 0: result = "" else: result = toUpperAscii(s[0]) & substr(s, 1) -proc nimIdentNormalize*(s: string): string = - ## Normalizes the string `s` as a Nim identifier. - ## - ## That means to convert to lower case and remove any '_' on all characters - ## except first one. - runnableExamples: - doAssert nimIdentNormalize("Foo_bar") == "Foobar" - result = newString(s.len) - if s.len > 0: - result[0] = s[0] - var j = 1 - for i in 1..len(s) - 1: - if s[i] in {'A'..'Z'}: - result[j] = chr(ord(s[i]) + (ord('a') - ord('A'))) - inc j - elif s[i] != '_': - result[j] = s[i] - inc j - if j != s.len: setLen(result, j) - proc normalize*(s: string): string {.noSideEffect, rtl, extern: "nsuNormalize".} = ## Normalizes the string `s`. @@ -1258,65 +1237,8 @@ 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()` - ) - -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(nnkDotExpr.newTree(argSym, - bindSym"nimIdentNormalize")) - # 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. + ## Parses an enum ``T``. ## ## Raises ``ValueError`` for an invalid value in `s`. The comparison is ## done in a style insensitive way. @@ -1332,11 +1254,13 @@ proc parseEnum*[T: enum](s: string): T = doAssertRaises(ValueError): echo parseEnum[MyEnum]("third") - genEnumStmt(T, s, default = nil) + for e in low(T)..high(T): + if cmpIgnoreStyle(s, $e) == 0: + return e + raise newException(ValueError, "invalid enum value: " & s) proc parseEnum*[T: enum](s: string, default: T): T = - ## Parses an enum ``T``. This errors at compile time, if the given enum - ## type contains multiple fields with the same string value. + ## Parses an enum ``T``. ## ## Uses `default` for an invalid value in `s`. The comparison is done in a ## style insensitive way. @@ -1351,7 +1275,10 @@ proc parseEnum*[T: enum](s: string, default: T): T = doAssert parseEnum[MyEnum]("second") == second doAssert parseEnum[MyEnum]("last", third) == third - genEnumStmt(T, s, default) + for e in low(T)..high(T): + if cmpIgnoreStyle(s, $e) == 0: + return e + result = default proc repeat*(c: char, count: Natural): string {.noSideEffect, rtl, extern: "nsuRepeatChar".} = diff --git a/tests/stdlib/tstrutil.nim b/tests/stdlib/tstrutil.nim index 5702b734103e9..1d5b15b5ec9e6 100644 --- a/tests/stdlib/tstrutil.nim +++ b/tests/stdlib/tstrutil.nim @@ -346,90 +346,3 @@ when true: main() #OUT ha/home/a1xyz/usr/bin - - -# `parseEnum`, ref issue #14030 -# check enum defined at top level -type - Foo = enum - A = -10 - B = "bb" - C = (-5, "ccc") - D = 15 - E = "ee" # check that we count enum fields correctly - -block: - let a = parseEnum[Foo]("A") - let b = parseEnum[Foo]("bb") - let c = parseEnum[Foo]("ccc") - let d = parseEnum[Foo]("D") - let e = parseEnum[Foo]("ee") - doAssert a == A - doAssert b == B - doAssert c == C - doAssert d == D - doAssert e == E - try: - let f = parseEnum[Foo]("Bar") - doAssert false - except ValueError: - discard - - # finally using default - let g = parseEnum[Foo]("Bar", A) - doAssert g == A - -block: - # check enum defined in block - type - Bar = enum - V - W = "ww" - X = (3, "xx") - Y = 10 - Z = "zz" # check that we count enum fields correctly - - let a = parseEnum[Bar]("V") - let b = parseEnum[Bar]("ww") - let c = parseEnum[Bar]("xx") - let d = parseEnum[Bar]("Y") - let e = parseEnum[Bar]("zz") - doAssert a == V - doAssert b == W - doAssert c == X - doAssert d == Y - doAssert e == Z - try: - let f = parseEnum[Bar]("Baz") - doAssert false - except ValueError: - discard - - # finally using default - let g = parseEnum[Bar]("Baz", V) - doAssert g == V - -block: - # check ambiguous enum fails to parse - type - Ambig = enum - f1 = "A" - f2 = "B" - f3 = "A" - - doAssert not compiles((let a = parseEnum[Ambig]("A"))) - -block: - # check almost ambiguous enum - type - AlmostAmbig = enum - f1 = "someA" - f2 = "someB" - f3 = "SomeA" - - let a = parseEnum[AlmostAmbig]("someA") - let b = parseEnum[AlmostAmbig]("someB") - let c = parseEnum[AlmostAmbig]("SomeA") - doAssert a == f1 - doAssert b == f2 - doAssert c == f3