diff options
-rw-r--r-- | changelog.md | 1 | ||||
-rw-r--r-- | compiler/commands.nim | 4 | ||||
-rw-r--r-- | compiler/pragmas.nim | 2 | ||||
-rw-r--r-- | compiler/wordrecg.nim | 25 | ||||
-rw-r--r-- | lib/pure/strutils.nim | 61 | ||||
-rw-r--r-- | lib/std/enumutils.nim | 64 |
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 |