Skip to content

Commit

Permalink
Revert "base parseEnum on a case statement, fixes #14030 (#14046)"
Browse files Browse the repository at this point in the history
This reverts commit d42c5a5.
  • Loading branch information
narimiran committed Aug 28, 2020
1 parent fb58066 commit 2484311
Show file tree
Hide file tree
Showing 3 changed files with 11 additions and 171 deletions.
2 changes: 1 addition & 1 deletion lib/pure/json.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 10 additions & 83 deletions lib/pure/strutils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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(<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(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.
Expand All @@ -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.
Expand All @@ -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".} =
Expand Down
87 changes: 0 additions & 87 deletions tests/stdlib/tstrutil.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 2484311

Please sign in to comment.