summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md1
-rw-r--r--compiler/commands.nim4
-rw-r--r--compiler/pragmas.nim2
-rw-r--r--compiler/wordrecg.nim25
-rw-r--r--lib/pure/strutils.nim61
-rw-r--r--lib/std/enumutils.nim64
6 files changed, 89 insertions, 68 deletions
diff --git a/changelog.md b/changelog.md
index 6bff75a44..d28edd34c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -17,6 +17,7 @@
 
 - Added `randState` template that exposes the default random number generator. Useful for library authors.
 
+- Added std/enumutils module containing `genEnumCaseStmt` macro that generates case statement to parse string to enum.
 
 ## Language changes
 
diff --git a/compiler/commands.nim b/compiler/commands.nim
index a35d1dae7..cb7517f06 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -193,11 +193,11 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass,
   elif i < arg.len and (arg[i] in {':', '='}): inc(i)
   else: invalidCmdLineOption(conf, pass, orig, info)
   if state == wHint:
-    let x = findStr(hintMin..hintMax, id, errUnknown)
+    let x = findStr(hintMin, hintMax, id, errUnknown)
     if x != errUnknown: n = TNoteKind(x)
     else: localError(conf, info, "unknown hint: " & id)
   else:
-    let x = findStr(warnMin..warnMax, id, errUnknown)
+    let x = findStr(warnMin, warnMax, id, errUnknown)
     if x != errUnknown: n = TNoteKind(x)
     else: localError(conf, info, "unknown warning: " & id)
 
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index c1d54c4a2..8affccbf3 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -331,7 +331,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) =
 
 proc processNote(c: PContext, n: PNode) =
   template handleNote(enumVals, notes) =
-    let x = findStr(enumVals, n[0][1].ident.s, errUnknown)
+    let x = findStr(enumVals.a, enumVals.b, n[0][1].ident.s, errUnknown)
     if x !=  errUnknown:
       nk = TNoteKind(x)
       let x = c.semConstBoolExpr(c, n[1])
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index e2e3a1d2f..170d04df1 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -13,8 +13,6 @@
 # does not support strings. Without this the code would
 # be slow and unreadable.
 
-from strutils import cmpIgnoreStyle
-
 type
   TSpecialWord* = enum
     wInvalid = "",
@@ -125,8 +123,21 @@ const
     wAsm, wBreak, wCase, wConst, wContinue, wDo, wElse, wEnum, wExport,
     wFor, wIf, wReturn, wStatic, wTemplate, wTry, wWhile, wUsing}
 
-proc findStr*[T:enum](a: Slice[T], s: string, default: T): T =  
-  for i in a:
-    if cmpIgnoreStyle($i, s) == 0:
-      return i
-  result = default
\ No newline at end of file
+
+const enumUtilsExist = compiles:
+  import std/enumutils
+
+when enumUtilsExist:
+  from std/enumutils import genEnumCaseStmt
+  from strutils import normalize
+  proc findStr*[T: enum](a, b: static[T], s: string, default: T): T =  
+    genEnumCaseStmt(T, s, default, ord(a), ord(b), normalize)
+
+else:
+  from strutils import cmpIgnoreStyle
+  proc findStr*[T: enum](a, b: static[T], s: string, default: T): T {.deprecated.} =  
+    # used for compiler bootstrapping only
+    for i in a..b:
+      if cmpIgnoreStyle($i, s) == 0:
+        return i
+    result = default 
\ No newline at end of file
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