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:
parent
1f838d9af1
commit
a95b6391fd
13 changed files with 126 additions and 18 deletions
|
|
@ -136,6 +136,10 @@
|
||||||
let foo: seq[(float, byte, cstring)] = @[(1, 2, "abc")]
|
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
|
## Compiler changes
|
||||||
|
|
||||||
- The `gc` switch has been renamed to `mm` ("memory management") in order to reflect the
|
- The `gc` switch has been renamed to `mm` ("memory management") in order to reflect the
|
||||||
|
|
|
||||||
|
|
@ -837,17 +837,27 @@ template genCaseGeneric(p: BProc, t: PNode, d: var TLoc,
|
||||||
fixLabel(p, lend)
|
fixLabel(p, lend)
|
||||||
|
|
||||||
proc genCaseStringBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel,
|
proc genCaseStringBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel,
|
||||||
|
stringKind: TTypeKind,
|
||||||
branches: var openArray[Rope]) =
|
branches: var openArray[Rope]) =
|
||||||
var x: TLoc
|
var x: TLoc
|
||||||
for i in 0..<b.len - 1:
|
for i in 0..<b.len - 1:
|
||||||
assert(b[i].kind != nkRange)
|
assert(b[i].kind != nkRange)
|
||||||
initLocExpr(p, b[i], x)
|
initLocExpr(p, b[i], x)
|
||||||
assert(b[i].kind in {nkStrLit..nkTripleStrLit})
|
var j: int
|
||||||
var j = int(hashString(p.config, b[i].strVal) and high(branches))
|
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",
|
appcg(p.module, branches[j], "if (#eqStrings($1, $2)) goto $3;$n",
|
||||||
[rdLoc(e), rdLoc(x), labl])
|
[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:
|
# count how many constant strings there are in the case:
|
||||||
var strings = 0
|
var strings = 0
|
||||||
for i in 1..<t.len:
|
for i in 1..<t.len:
|
||||||
|
|
@ -863,11 +873,15 @@ proc genStringCase(p: BProc, t: PNode, d: var TLoc) =
|
||||||
inc(p.labels)
|
inc(p.labels)
|
||||||
if t[i].kind == nkOfBranch:
|
if t[i].kind == nkOfBranch:
|
||||||
genCaseStringBranch(p, t[i], a, "LA" & rope(p.labels) & "_",
|
genCaseStringBranch(p, t[i], a, "LA" & rope(p.labels) & "_",
|
||||||
branches)
|
stringKind, branches)
|
||||||
else:
|
else:
|
||||||
# else statement: nothing to do yet
|
# else statement: nothing to do yet
|
||||||
# but we reserved a label, which we use later
|
# but we reserved a label, which we use later
|
||||||
discard
|
discard
|
||||||
|
if stringKind == tyCstring:
|
||||||
|
linefmt(p, cpsStmts, "switch (#hashCstring($1) & $2) {$n",
|
||||||
|
[rdLoc(a), bitMask])
|
||||||
|
else:
|
||||||
linefmt(p, cpsStmts, "switch (#hashString($1) & $2) {$n",
|
linefmt(p, cpsStmts, "switch (#hashString($1) & $2) {$n",
|
||||||
[rdLoc(a), bitMask])
|
[rdLoc(a), bitMask])
|
||||||
for j in 0..high(branches):
|
for j in 0..high(branches):
|
||||||
|
|
@ -880,6 +894,9 @@ proc genStringCase(p: BProc, t: PNode, d: var TLoc) =
|
||||||
# third pass: generate statements
|
# third pass: generate statements
|
||||||
var lend = genCaseSecondPass(p, t, d, labId, t.len-1)
|
var lend = genCaseSecondPass(p, t, d, labId, t.len-1)
|
||||||
fixLabel(p, lend)
|
fixLabel(p, lend)
|
||||||
|
else:
|
||||||
|
if stringKind == tyCstring:
|
||||||
|
genCaseGeneric(p, t, d, "", "if (#eqCstrings($1, $2)) goto $3;$n")
|
||||||
else:
|
else:
|
||||||
genCaseGeneric(p, t, d, "", "if (#eqStrings($1, $2)) goto $3;$n")
|
genCaseGeneric(p, t, d, "", "if (#eqStrings($1, $2)) goto $3;$n")
|
||||||
|
|
||||||
|
|
@ -954,7 +971,9 @@ proc genCase(p: BProc, t: PNode, d: var TLoc) =
|
||||||
getTemp(p, t.typ, d)
|
getTemp(p, t.typ, d)
|
||||||
case skipTypes(t[0].typ, abstractVarRange).kind
|
case skipTypes(t[0].typ, abstractVarRange).kind
|
||||||
of tyString:
|
of tyString:
|
||||||
genStringCase(p, t, d)
|
genStringCase(p, t, tyString, d)
|
||||||
|
of tyCstring:
|
||||||
|
genStringCase(p, t, tyCstring, d)
|
||||||
of tyFloat..tyFloat128:
|
of tyFloat..tyFloat128:
|
||||||
genCaseGeneric(p, t, d, "if ($1 >= $2 && $1 <= $3) goto $4;$n",
|
genCaseGeneric(p, t, d, "if ($1 >= $2 && $1 <= $3) goto $4;$n",
|
||||||
"if ($1 == $2) goto $3;$n")
|
"if ($1 == $2) goto $3;$n")
|
||||||
|
|
|
||||||
|
|
@ -966,7 +966,7 @@ proc allPathsAsgnResult(n: PNode): InitResultEnum =
|
||||||
if containsResult(n[0]): return InitRequired
|
if containsResult(n[0]): return InitRequired
|
||||||
result = InitSkippable
|
result = InitSkippable
|
||||||
var exhaustive = skipTypes(n[0].typ,
|
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:
|
for i in 1..<n.len:
|
||||||
let it = n[i]
|
let it = n[i]
|
||||||
allPathsInBranch(it.lastSon)
|
allPathsInBranch(it.lastSon)
|
||||||
|
|
|
||||||
|
|
@ -142,3 +142,4 @@ proc initDefines*(symbols: StringTableRef) =
|
||||||
defineSymbol("nimHasEnforceNoRaises")
|
defineSymbol("nimHasEnforceNoRaises")
|
||||||
defineSymbol("nimHasTopDownInference")
|
defineSymbol("nimHasTopDownInference")
|
||||||
defineSymbol("nimHasTemplateRedefinitionPragma")
|
defineSymbol("nimHasTemplateRedefinitionPragma")
|
||||||
|
defineSymbol("nimHasCstringCase")
|
||||||
|
|
|
||||||
|
|
@ -461,7 +461,7 @@ proc genCase(c: var Con; n: PNode) =
|
||||||
# elsePart
|
# elsePart
|
||||||
# Lend:
|
# Lend:
|
||||||
let isExhaustive = skipTypes(n[0].typ,
|
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
|
# 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
|
# ensures when recursively traversing the CFG for various analysis, we don't
|
||||||
|
|
|
||||||
|
|
@ -873,8 +873,9 @@ proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) =
|
||||||
totalRange = 0
|
totalRange = 0
|
||||||
genLineDir(p, n)
|
genLineDir(p, n)
|
||||||
gen(p, n[0], cond)
|
gen(p, n[0], cond)
|
||||||
let stringSwitch = skipTypes(n[0].typ, abstractVar).kind == tyString
|
let typeKind = skipTypes(n[0].typ, abstractVar).kind
|
||||||
if stringSwitch:
|
let anyString = typeKind in {tyString, tyCstring}
|
||||||
|
if typeKind == tyString:
|
||||||
useMagic(p, "toJSStr")
|
useMagic(p, "toJSStr")
|
||||||
lineF(p, "switch (toJSStr($1)) {$n", [cond.rdLoc])
|
lineF(p, "switch (toJSStr($1)) {$n", [cond.rdLoc])
|
||||||
else:
|
else:
|
||||||
|
|
@ -899,10 +900,11 @@ proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) =
|
||||||
lineF(p, "case $1:$n", [cond.rdLoc])
|
lineF(p, "case $1:$n", [cond.rdLoc])
|
||||||
inc(v.intVal)
|
inc(v.intVal)
|
||||||
else:
|
else:
|
||||||
if stringSwitch:
|
if anyString:
|
||||||
case e.kind
|
case e.kind
|
||||||
of nkStrLit..nkTripleStrLit: lineF(p, "case $1:$n",
|
of nkStrLit..nkTripleStrLit: lineF(p, "case $1:$n",
|
||||||
[makeJSString(e.strVal, false)])
|
[makeJSString(e.strVal, false)])
|
||||||
|
of nkNilLit: lineF(p, "case null:$n", [])
|
||||||
else: internalError(p.config, e.info, "jsgen.genCaseStmt: 2")
|
else: internalError(p.config, e.info, "jsgen.genCaseStmt: 2")
|
||||||
else:
|
else:
|
||||||
gen(p, e, cond)
|
gen(p, e, cond)
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ proc analyse(c: var Con; b: var BasicBlock; n: PNode) =
|
||||||
|
|
||||||
of nkCaseStmt:
|
of nkCaseStmt:
|
||||||
let isExhaustive = skipTypes(n[0].typ,
|
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
|
n[^1].kind == nkElse
|
||||||
|
|
||||||
analyse(c, b, n[0])
|
analyse(c, b, n[0])
|
||||||
|
|
|
||||||
|
|
@ -684,7 +684,7 @@ proc trackCase(tracked: PEffects, n: PNode) =
|
||||||
let oldState = tracked.init.len
|
let oldState = tracked.init.len
|
||||||
let oldFacts = tracked.guards.s.len
|
let oldFacts = tracked.guards.s.len
|
||||||
let stringCase = n[0].typ != nil and skipTypes(n[0].typ,
|
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
|
let interesting = not stringCase and interestingCaseExpr(n[0]) and
|
||||||
tracked.config.hasWarn(warnProveField)
|
tracked.config.hasWarn(warnProveField)
|
||||||
var inter: TIntersection = @[]
|
var inter: TIntersection = @[]
|
||||||
|
|
|
||||||
|
|
@ -1067,7 +1067,7 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags; expectedType: PType = nil
|
||||||
of tyRange:
|
of tyRange:
|
||||||
if skipTypes(caseTyp[0], abstractInst).kind in shouldChckCovered:
|
if skipTypes(caseTyp[0], abstractInst).kind in shouldChckCovered:
|
||||||
chckCovered = true
|
chckCovered = true
|
||||||
of tyFloat..tyFloat128, tyString, tyError:
|
of tyFloat..tyFloat128, tyString, tyCstring, tyError:
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
popCaseContext(c)
|
popCaseContext(c)
|
||||||
|
|
|
||||||
|
|
@ -603,7 +603,9 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int,
|
||||||
checkMinSonsLen(t, 1, c.config)
|
checkMinSonsLen(t, 1, c.config)
|
||||||
var tmp = fitNode(c, t[0].typ, r, r.info)
|
var tmp = fitNode(c, t[0].typ, r, r.info)
|
||||||
# the call to fitNode may introduce a call to a converter
|
# 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)
|
branch[i] = skipConv(tmp)
|
||||||
inc(covered)
|
inc(covered)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -1432,6 +1432,8 @@ it can be modified:
|
||||||
s[0] = 'u' # This is ok
|
s[0] = 'u' # This is ok
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`cstring` values may also be used in case statements like strings.
|
||||||
|
|
||||||
Structured types
|
Structured types
|
||||||
----------------
|
----------------
|
||||||
A variable of a structured type can hold multiple values at the same
|
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.
|
"All possible values" of `expr` are determined by `expr`'s type.
|
||||||
To suppress the static error an `else: discard` should be used.
|
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
|
For non-ordinal types, it is not possible to list every possible value and so
|
||||||
these always require an `else` part.
|
these always require an `else` part.
|
||||||
An exception to this rule is for the `string` type, which currently doesn't
|
An exception to this rule is for the `string` type, which currently doesn't
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,29 @@ proc hashString(s: string): int {.compilerproc.} =
|
||||||
h = h + h shl 15
|
h = h + h shl 15
|
||||||
result = cast[int](h)
|
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 {.
|
proc c_strtod(buf: cstring, endptr: ptr cstring): float64 {.
|
||||||
importc: "strtod", header: "<stdlib.h>", noSideEffect.}
|
importc: "strtod", header: "<stdlib.h>", noSideEffect.}
|
||||||
|
|
||||||
|
|
|
||||||
52
tests/casestmt/tcstring.nim
Normal file
52
tests/casestmt/tcstring.nim
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue