* base `parseEnum` on a case statement, fixes #14030 * apply simplifactions / clean up, remove `norm` node, use strVal * export `normalize` in json.nim * cmp using nimIdentNormalize, error at CT if ambiguous enum found `nimIdentNormalize` provided by @cooldome. We track all names of the branches we have created so far and error if a duplicate is found. Dummy change to make github react... * fix docstring of `nimIdentNormalize` * make `typ` arg `typedesc`, add lineinfo, call norm. only once
This commit is contained in:
parent
c3f4b93060
commit
d42c5a575d
3 changed files with 171 additions and 11 deletions
|
|
@ -158,7 +158,7 @@ export
|
||||||
export
|
export
|
||||||
parsejson.JsonEventKind, parsejson.JsonError, JsonParser, JsonKindError,
|
parsejson.JsonEventKind, parsejson.JsonError, JsonParser, JsonKindError,
|
||||||
open, close, str, getInt, getFloat, kind, getColumn, getLine, getFilename,
|
open, close, str, getInt, getFloat, kind, getColumn, getLine, getFilename,
|
||||||
errorMsg, errorMsgExpected, next, JsonParsingError, raiseParseErr
|
errorMsg, errorMsgExpected, next, JsonParsingError, raiseParseErr, nimIdentNormalize
|
||||||
|
|
||||||
type
|
type
|
||||||
JsonNodeKind* = enum ## possible JSON node types
|
JsonNodeKind* = enum ## possible JSON node types
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@
|
||||||
import parseutils
|
import parseutils
|
||||||
from math import pow, floor, log10
|
from math import pow, floor, log10
|
||||||
from algorithm import reverse
|
from algorithm import reverse
|
||||||
|
import macros # for `parseEnum`
|
||||||
|
|
||||||
when defined(nimVmExportFixed):
|
when defined(nimVmExportFixed):
|
||||||
from unicode import toLower, toUpper
|
from unicode import toLower, toUpper
|
||||||
|
|
@ -278,6 +279,26 @@ proc capitalizeAscii*(s: string): string {.noSideEffect, procvar,
|
||||||
if s.len == 0: result = ""
|
if s.len == 0: result = ""
|
||||||
else: result = toUpperAscii(s[0]) & substr(s, 1)
|
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, procvar,
|
proc normalize*(s: string): string {.noSideEffect, procvar,
|
||||||
rtl, extern: "nsuNormalize".} =
|
rtl, extern: "nsuNormalize".} =
|
||||||
## Normalizes the string `s`.
|
## Normalizes the string `s`.
|
||||||
|
|
@ -1229,8 +1250,65 @@ proc parseBool*(s: string): bool =
|
||||||
of "n", "no", "false", "0", "off": result = false
|
of "n", "no", "false", "0", "off": result = false
|
||||||
else: raise newException(ValueError, "cannot interpret as a bool: " & s)
|
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 # string of current field
|
||||||
|
var fNum: BiggestInt # 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!")
|
||||||
|
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 =
|
proc parseEnum*[T: enum](s: string): T =
|
||||||
## Parses an enum ``T``.
|
## Parses an enum ``T``. This errors at compile time, if the given enum
|
||||||
|
## type contains multiple fields with the same string value.
|
||||||
##
|
##
|
||||||
## Raises ``ValueError`` for an invalid value in `s`. The comparison is
|
## Raises ``ValueError`` for an invalid value in `s`. The comparison is
|
||||||
## done in a style insensitive way.
|
## done in a style insensitive way.
|
||||||
|
|
@ -1246,13 +1324,11 @@ proc parseEnum*[T: enum](s: string): T =
|
||||||
doAssertRaises(ValueError):
|
doAssertRaises(ValueError):
|
||||||
echo parseEnum[MyEnum]("third")
|
echo parseEnum[MyEnum]("third")
|
||||||
|
|
||||||
for e in low(T)..high(T):
|
genEnumStmt(T, s, default = nil)
|
||||||
if cmpIgnoreStyle(s, $e) == 0:
|
|
||||||
return e
|
|
||||||
raise newException(ValueError, "invalid enum value: " & s)
|
|
||||||
|
|
||||||
proc parseEnum*[T: enum](s: string, default: T): T =
|
proc parseEnum*[T: enum](s: string, default: T): T =
|
||||||
## Parses an enum ``T``.
|
## Parses an enum ``T``. This errors at compile time, if the given enum
|
||||||
|
## type contains multiple fields with the same string value.
|
||||||
##
|
##
|
||||||
## Uses `default` for an invalid value in `s`. The comparison is done in a
|
## Uses `default` for an invalid value in `s`. The comparison is done in a
|
||||||
## style insensitive way.
|
## style insensitive way.
|
||||||
|
|
@ -1267,10 +1343,7 @@ proc parseEnum*[T: enum](s: string, default: T): T =
|
||||||
doAssert parseEnum[MyEnum]("second") == second
|
doAssert parseEnum[MyEnum]("second") == second
|
||||||
doAssert parseEnum[MyEnum]("last", third) == third
|
doAssert parseEnum[MyEnum]("last", third) == third
|
||||||
|
|
||||||
for e in low(T)..high(T):
|
genEnumStmt(T, s, default)
|
||||||
if cmpIgnoreStyle(s, $e) == 0:
|
|
||||||
return e
|
|
||||||
result = default
|
|
||||||
|
|
||||||
proc repeat*(c: char, count: Natural): string {.noSideEffect,
|
proc repeat*(c: char, count: Natural): string {.noSideEffect,
|
||||||
rtl, extern: "nsuRepeatChar".} =
|
rtl, extern: "nsuRepeatChar".} =
|
||||||
|
|
|
||||||
|
|
@ -348,3 +348,90 @@ when true:
|
||||||
|
|
||||||
main()
|
main()
|
||||||
#OUT ha/home/a1xyz/usr/bin
|
#OUT ha/home/a1xyz/usr/bin
|
||||||
|
|
||||||
|
|
||||||
|
# `parseEnum`, ref issue #14030
|
||||||
|
# check enum defined at top level
|
||||||
|
type
|
||||||
|
Foo = enum
|
||||||
|
A
|
||||||
|
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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue