support cstring in case (#20130)

* implement case for cstring

for now just converts to string on C backend

* custom implementation for cstring

* remove leftover

* revert even more

* add nil + fix packages weird variant literal bug

* update docs
This commit is contained in:
metagn
2022-09-01 19:10:00 +03:00
committed by GitHub
parent 1f838d9af1
commit a95b6391fd
13 changed files with 126 additions and 18 deletions

View File

@@ -136,6 +136,10 @@
let foo: seq[(float, byte, cstring)] = @[(1, 2, "abc")]
```
- `cstring` is now accepted as a selector in `case` statements, removing the
need to convert to `string`. On the JS backend, this is translated directly
to a `switch` statement.
## Compiler changes
- The `gc` switch has been renamed to `mm` ("memory management") in order to reflect the

View File

@@ -837,17 +837,27 @@ template genCaseGeneric(p: BProc, t: PNode, d: var TLoc,
fixLabel(p, lend)
proc genCaseStringBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel,
stringKind: TTypeKind,
branches: var openArray[Rope]) =
var x: TLoc
for i in 0..<b.len - 1:
assert(b[i].kind != nkRange)
initLocExpr(p, b[i], x)
assert(b[i].kind in {nkStrLit..nkTripleStrLit})
var j = int(hashString(p.config, b[i].strVal) and high(branches))
appcg(p.module, branches[j], "if (#eqStrings($1, $2)) goto $3;$n",
var j: int
case b[i].kind
of nkStrLit..nkTripleStrLit:
j = int(hashString(p.config, b[i].strVal) and high(branches))
of nkNilLit: j = 0
else:
assert false, "invalid string case branch node kind"
if stringKind == tyCstring:
appcg(p.module, branches[j], "if (#eqCstrings($1, $2)) goto $3;$n",
[rdLoc(e), rdLoc(x), labl])
else:
appcg(p.module, branches[j], "if (#eqStrings($1, $2)) goto $3;$n",
[rdLoc(e), rdLoc(x), labl])
proc genStringCase(p: BProc, t: PNode, d: var TLoc) =
proc genStringCase(p: BProc, t: PNode, stringKind: TTypeKind, d: var TLoc) =
# count how many constant strings there are in the case:
var strings = 0
for i in 1..<t.len:
@@ -863,13 +873,17 @@ proc genStringCase(p: BProc, t: PNode, d: var TLoc) =
inc(p.labels)
if t[i].kind == nkOfBranch:
genCaseStringBranch(p, t[i], a, "LA" & rope(p.labels) & "_",
branches)
stringKind, branches)
else:
# else statement: nothing to do yet
# but we reserved a label, which we use later
discard
linefmt(p, cpsStmts, "switch (#hashString($1) & $2) {$n",
[rdLoc(a), bitMask])
if stringKind == tyCstring:
linefmt(p, cpsStmts, "switch (#hashCstring($1) & $2) {$n",
[rdLoc(a), bitMask])
else:
linefmt(p, cpsStmts, "switch (#hashString($1) & $2) {$n",
[rdLoc(a), bitMask])
for j in 0..high(branches):
if branches[j] != nil:
lineF(p, cpsStmts, "case $1: $n$2break;$n",
@@ -881,7 +895,10 @@ proc genStringCase(p: BProc, t: PNode, d: var TLoc) =
var lend = genCaseSecondPass(p, t, d, labId, t.len-1)
fixLabel(p, lend)
else:
genCaseGeneric(p, t, d, "", "if (#eqStrings($1, $2)) goto $3;$n")
if stringKind == tyCstring:
genCaseGeneric(p, t, d, "", "if (#eqCstrings($1, $2)) goto $3;$n")
else:
genCaseGeneric(p, t, d, "", "if (#eqStrings($1, $2)) goto $3;$n")
proc branchHasTooBigRange(b: PNode): bool =
for it in b:
@@ -954,7 +971,9 @@ proc genCase(p: BProc, t: PNode, d: var TLoc) =
getTemp(p, t.typ, d)
case skipTypes(t[0].typ, abstractVarRange).kind
of tyString:
genStringCase(p, t, d)
genStringCase(p, t, tyString, d)
of tyCstring:
genStringCase(p, t, tyCstring, d)
of tyFloat..tyFloat128:
genCaseGeneric(p, t, d, "if ($1 >= $2 && $1 <= $3) goto $4;$n",
"if ($1 == $2) goto $3;$n")

View File

@@ -966,7 +966,7 @@ proc allPathsAsgnResult(n: PNode): InitResultEnum =
if containsResult(n[0]): return InitRequired
result = InitSkippable
var exhaustive = skipTypes(n[0].typ,
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString}
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString, tyCstring}
for i in 1..<n.len:
let it = n[i]
allPathsInBranch(it.lastSon)

View File

@@ -142,3 +142,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasEnforceNoRaises")
defineSymbol("nimHasTopDownInference")
defineSymbol("nimHasTemplateRedefinitionPragma")
defineSymbol("nimHasCstringCase")

View File

@@ -461,7 +461,7 @@ proc genCase(c: var Con; n: PNode) =
# elsePart
# Lend:
let isExhaustive = skipTypes(n[0].typ,
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString}
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString, tyCstring}
# we generate endings as a set of chained gotos, this is a bit awkward but it
# ensures when recursively traversing the CFG for various analysis, we don't

View File

@@ -873,8 +873,9 @@ proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) =
totalRange = 0
genLineDir(p, n)
gen(p, n[0], cond)
let stringSwitch = skipTypes(n[0].typ, abstractVar).kind == tyString
if stringSwitch:
let typeKind = skipTypes(n[0].typ, abstractVar).kind
let anyString = typeKind in {tyString, tyCstring}
if typeKind == tyString:
useMagic(p, "toJSStr")
lineF(p, "switch (toJSStr($1)) {$n", [cond.rdLoc])
else:
@@ -899,10 +900,11 @@ proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) =
lineF(p, "case $1:$n", [cond.rdLoc])
inc(v.intVal)
else:
if stringSwitch:
if anyString:
case e.kind
of nkStrLit..nkTripleStrLit: lineF(p, "case $1:$n",
[makeJSString(e.strVal, false)])
of nkNilLit: lineF(p, "case null:$n", [])
else: internalError(p.config, e.info, "jsgen.genCaseStmt: 2")
else:
gen(p, e, cond)

View File

@@ -178,7 +178,7 @@ proc analyse(c: var Con; b: var BasicBlock; n: PNode) =
of nkCaseStmt:
let isExhaustive = skipTypes(n[0].typ,
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString} or
abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString, tyCstring} or
n[^1].kind == nkElse
analyse(c, b, n[0])

View File

@@ -684,7 +684,7 @@ proc trackCase(tracked: PEffects, n: PNode) =
let oldState = tracked.init.len
let oldFacts = tracked.guards.s.len
let stringCase = n[0].typ != nil and skipTypes(n[0].typ,
abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString}
abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString, tyCstring}
let interesting = not stringCase and interestingCaseExpr(n[0]) and
tracked.config.hasWarn(warnProveField)
var inter: TIntersection = @[]

View File

@@ -1067,7 +1067,7 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil
of tyRange:
if skipTypes(caseTyp[0], abstractInst).kind in shouldChckCovered:
chckCovered = true
of tyFloat..tyFloat128, tyString, tyError:
of tyFloat..tyFloat128, tyString, tyCstring, tyError:
discard
else:
popCaseContext(c)

View File

@@ -603,7 +603,9 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int,
checkMinSonsLen(t, 1, c.config)
var tmp = fitNode(c, t[0].typ, r, r.info)
# the call to fitNode may introduce a call to a converter
if tmp.kind in {nkHiddenCallConv}: tmp = semConstExpr(c, tmp)
if tmp.kind == nkHiddenCallConv or
(tmp.kind == nkHiddenStdConv and t[0].typ.kind == tyCstring):
tmp = semConstExpr(c, tmp)
branch[i] = skipConv(tmp)
inc(covered)
else:

View File

@@ -1432,6 +1432,8 @@ it can be modified:
s[0] = 'u' # This is ok
```
`cstring` values may also be used in case statements like strings.
Structured types
----------------
A variable of a structured type can hold multiple values at the same
@@ -3098,6 +3100,9 @@ This holds only for expressions of ordinal types.
"All possible values" of `expr` are determined by `expr`'s type.
To suppress the static error an `else: discard` should be used.
Only ordinal types, floats, strings and cstrings are allowed as values
in case statements.
For non-ordinal types, it is not possible to list every possible value and so
these always require an `else` part.
An exception to this rule is for the `string` type, which currently doesn't

View File

@@ -43,6 +43,29 @@ proc hashString(s: string): int {.compilerproc.} =
h = h + h shl 15
result = cast[int](h)
proc eqCstrings(a, b: cstring): bool {.inline, compilerproc.} =
if pointer(a) == pointer(b): result = true
elif a.isNil or b.isNil: result = false
else: result = c_strcmp(a, b) == 0
proc hashCstring(s: cstring): int {.compilerproc.} =
# the compiler needs exactly the same hash function!
# this used to be used for efficient generation of cstring case statements
if s.isNil: return 0
var h : uint = 0
var i = 0
while true:
let c = s[i]
if c == '\0': break
h = h + uint(c)
h = h + h shl 10
h = h xor (h shr 6)
inc i
h = h + h shl 3
h = h xor (h shr 11)
h = h + h shl 15
result = cast[int](h)
proc c_strtod(buf: cstring, endptr: ptr cstring): float64 {.
importc: "strtod", header: "<stdlib.h>", noSideEffect.}

View File

@@ -0,0 +1,52 @@
discard """
targets: "c cpp js"
"""
type Result = enum none, a, b, c, d, e, f
proc foo1(x: cstring): Result =
const y = cstring"hash"
const arr = [cstring"it", cstring"finally"]
result = none
case x
of "Andreas", "Rumpf": result = a
of cstring"aa", "bb": result = b
of "cc", y, "when": result = c
of "will", arr, "be", "generated": result = d
of nil: result = f
var results = [
foo1("Rumpf"), foo1("Andreas"),
foo1("aa"), foo1(cstring"bb"),
foo1("cc"), foo1("hash"),
foo1("finally"), foo1("generated"),
foo1("no"), foo1("another no"),
foo1(nil)]
doAssert results == [a, a, b, b, c, c, d, d, none, none, f], $results
proc foo2(x: cstring): Result =
const y = cstring"hash"
const arr = [cstring"it", cstring"finally"]
doAssert not (compiles do:
result = case x
of "Andreas", "Rumpf": a
of cstring"aa", "bb": b
of "cc", y, "when": c
of "will", arr, "be", "generated": d)
case x
of "Andreas", "Rumpf": a
of cstring"aa", "bb": b
of "cc", y, "when": c
of "will", arr, "be", "generated": d
of nil: f
else: e
results = [
foo2("Rumpf"), foo2("Andreas"),
foo2("aa"), foo2(cstring"bb"),
foo2("cc"), foo2("hash"),
foo2("finally"), foo2("generated"),
foo2("no"), foo2("another no"),
foo2(nil)]
doAssert results == [a, a, b, b, c, c, d, d, e, e, f], $results