EnumUtils, speed up findStr in compiler (#15777)
* add parseEnumRange * fix runnable example * update changelog * use parseEnumRange in compiler * reorganise code * add changelog, make single normalizer argument
This commit is contained in:
parent
b8bcf236dd
commit
d62f3627aa
6 changed files with 89 additions and 68 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
- Added `randState` template that exposes the default random number generator. Useful for library authors.
|
- 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
|
## Language changes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,11 +193,11 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass,
|
||||||
elif i < arg.len and (arg[i] in {':', '='}): inc(i)
|
elif i < arg.len and (arg[i] in {':', '='}): inc(i)
|
||||||
else: invalidCmdLineOption(conf, pass, orig, info)
|
else: invalidCmdLineOption(conf, pass, orig, info)
|
||||||
if state == wHint:
|
if state == wHint:
|
||||||
let x = findStr(hintMin..hintMax, id, errUnknown)
|
let x = findStr(hintMin, hintMax, id, errUnknown)
|
||||||
if x != errUnknown: n = TNoteKind(x)
|
if x != errUnknown: n = TNoteKind(x)
|
||||||
else: localError(conf, info, "unknown hint: " & id)
|
else: localError(conf, info, "unknown hint: " & id)
|
||||||
else:
|
else:
|
||||||
let x = findStr(warnMin..warnMax, id, errUnknown)
|
let x = findStr(warnMin, warnMax, id, errUnknown)
|
||||||
if x != errUnknown: n = TNoteKind(x)
|
if x != errUnknown: n = TNoteKind(x)
|
||||||
else: localError(conf, info, "unknown warning: " & id)
|
else: localError(conf, info, "unknown warning: " & id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -331,7 +331,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) =
|
||||||
|
|
||||||
proc processNote(c: PContext, n: PNode) =
|
proc processNote(c: PContext, n: PNode) =
|
||||||
template handleNote(enumVals, notes) =
|
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:
|
if x != errUnknown:
|
||||||
nk = TNoteKind(x)
|
nk = TNoteKind(x)
|
||||||
let x = c.semConstBoolExpr(c, n[1])
|
let x = c.semConstBoolExpr(c, n[1])
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@
|
||||||
# does not support strings. Without this the code would
|
# does not support strings. Without this the code would
|
||||||
# be slow and unreadable.
|
# be slow and unreadable.
|
||||||
|
|
||||||
from strutils import cmpIgnoreStyle
|
|
||||||
|
|
||||||
type
|
type
|
||||||
TSpecialWord* = enum
|
TSpecialWord* = enum
|
||||||
wInvalid = "",
|
wInvalid = "",
|
||||||
|
|
@ -125,8 +123,21 @@ const
|
||||||
wAsm, wBreak, wCase, wConst, wContinue, wDo, wElse, wEnum, wExport,
|
wAsm, wBreak, wCase, wConst, wContinue, wDo, wElse, wEnum, wExport,
|
||||||
wFor, wIf, wReturn, wStatic, wTemplate, wTry, wWhile, wUsing}
|
wFor, wIf, wReturn, wStatic, wTemplate, wTry, wWhile, wUsing}
|
||||||
|
|
||||||
proc findStr*[T:enum](a: Slice[T], s: string, default: T): T =
|
|
||||||
for i in a:
|
const enumUtilsExist = compiles:
|
||||||
if cmpIgnoreStyle($i, s) == 0:
|
import std/enumutils
|
||||||
return i
|
|
||||||
result = default
|
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
|
||||||
|
|
@ -76,7 +76,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`
|
import std/enumutils
|
||||||
|
|
||||||
when defined(nimVmExportFixed):
|
when defined(nimVmExportFixed):
|
||||||
from unicode import toLower, toUpper
|
from unicode import toLower, toUpper
|
||||||
|
|
@ -1264,61 +1264,6 @@ 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(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 =
|
proc parseEnum*[T: enum](s: string): T =
|
||||||
## Parses an enum ``T``. This errors at compile time, if the given enum
|
## Parses an enum ``T``. This errors at compile time, if the given enum
|
||||||
## type contains multiple fields with the same string value.
|
## type contains multiple fields with the same string value.
|
||||||
|
|
@ -1337,7 +1282,7 @@ proc parseEnum*[T: enum](s: string): T =
|
||||||
doAssertRaises(ValueError):
|
doAssertRaises(ValueError):
|
||||||
echo parseEnum[MyEnum]("third")
|
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 =
|
proc parseEnum*[T: enum](s: string, default: T): T =
|
||||||
## Parses an enum ``T``. This errors at compile time, if the given enum
|
## 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]("second") == second
|
||||||
doAssert parseEnum[MyEnum]("last", third) == third
|
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,
|
proc repeat*(c: char, count: Natural): string {.noSideEffect,
|
||||||
rtl, extern: "nsuRepeatChar".} =
|
rtl, extern: "nsuRepeatChar".} =
|
||||||
|
|
|
||||||
64
lib/std/enumutils.nim
Normal file
64
lib/std/enumutils.nim
Normal file
|
|
@ -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)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue