remove old implementation of the roof operator; make tests green again; close #6292

This commit is contained in:
Andreas Rumpf 2017-10-29 20:36:07 +01:00
commit f1dab39086
13 changed files with 28 additions and 161 deletions

View file

@ -37,7 +37,6 @@ type
# in standalone ``except`` and ``finally`` # in standalone ``except`` and ``finally``
next*: PProcCon # used for stacking procedure contexts next*: PProcCon # used for stacking procedure contexts
wasForwarded*: bool # whether the current proc has a separate header wasForwarded*: bool # whether the current proc has a separate header
bracketExpr*: PNode # current bracket expression (for ^ support)
mapping*: TIdTable mapping*: TIdTable
TMatchedConcept* = object TMatchedConcept* = object

View file

@ -1189,7 +1189,6 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
tyCString: tyCString:
if n.len != 2: return nil if n.len != 2: return nil
n.sons[0] = makeDeref(n.sons[0]) n.sons[0] = makeDeref(n.sons[0])
c.p.bracketExpr = n.sons[0]
for i in countup(1, sonsLen(n) - 1): for i in countup(1, sonsLen(n) - 1):
n.sons[i] = semExprWithType(c, n.sons[i], n.sons[i] = semExprWithType(c, n.sons[i],
flags*{efInTypeof, efDetermineType}) flags*{efInTypeof, efDetermineType})
@ -1210,7 +1209,6 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
of tyTuple: of tyTuple:
if n.len != 2: return nil if n.len != 2: return nil
n.sons[0] = makeDeref(n.sons[0]) n.sons[0] = makeDeref(n.sons[0])
c.p.bracketExpr = n.sons[0]
# [] operator for tuples requires constant expression: # [] operator for tuples requires constant expression:
n.sons[1] = semConstExpr(c, n.sons[1]) n.sons[1] = semConstExpr(c, n.sons[1])
if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal, tyAlias}).kind in if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal, tyAlias}).kind in
@ -1248,17 +1246,13 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
of skType: of skType:
result = symNodeFromType(c, semTypeNode(c, n, nil), n.info) result = symNodeFromType(c, semTypeNode(c, n, nil), n.info)
else: else:
c.p.bracketExpr = n.sons[0] discard
else:
c.p.bracketExpr = n.sons[0]
proc semArrayAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = proc semArrayAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
let oldBracketExpr = c.p.bracketExpr
result = semSubscript(c, n, flags) result = semSubscript(c, n, flags)
if result == nil: if result == nil:
# overloaded [] operator: # overloaded [] operator:
result = semExpr(c, buildOverloadedSubscripts(n, getIdent"[]")) result = semExpr(c, buildOverloadedSubscripts(n, getIdent"[]"))
c.p.bracketExpr = oldBracketExpr
proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode = proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode =
var id = considerQuotedIdent(a[1], a) var id = considerQuotedIdent(a[1], a)
@ -1330,7 +1324,6 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
of nkBracketExpr: of nkBracketExpr:
# a[i] = x # a[i] = x
# --> `[]=`(a, i, x) # --> `[]=`(a, i, x)
let oldBracketExpr = c.p.bracketExpr
a = semSubscript(c, a, {efLValue}) a = semSubscript(c, a, {efLValue})
if a == nil: if a == nil:
result = buildOverloadedSubscripts(n.sons[0], getIdent"[]=") result = buildOverloadedSubscripts(n.sons[0], getIdent"[]=")
@ -1340,9 +1333,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
return n return n
else: else:
result = semExprNoType(c, result) result = semExprNoType(c, result)
c.p.bracketExpr = oldBracketExpr
return result return result
c.p.bracketExpr = oldBracketExpr
of nkCurlyExpr: of nkCurlyExpr:
# a{i} = x --> `{}=`(a, i, x) # a{i} = x --> `{}=`(a, i, x)
result = buildOverloadedSubscripts(n.sons[0], getIdent"{}=") result = buildOverloadedSubscripts(n.sons[0], getIdent"{}=")

View file

@ -38,9 +38,7 @@ proc skipAddr(n: PNode): PNode {.inline.} =
proc semArrGet(c: PContext; n: PNode; flags: TExprFlags): PNode = proc semArrGet(c: PContext; n: PNode; flags: TExprFlags): PNode =
result = newNodeI(nkBracketExpr, n.info) result = newNodeI(nkBracketExpr, n.info)
for i in 1..<n.len: result.add(n[i]) for i in 1..<n.len: result.add(n[i])
let oldBracketExpr = c.p.bracketExpr
result = semSubscript(c, result, flags) result = semSubscript(c, result, flags)
c.p.bracketExpr = oldBracketExpr
if result.isNil: if result.isNil:
let x = copyTree(n) let x = copyTree(n)
x.sons[0] = newIdentNode(getIdent"[]", n.info) x.sons[0] = newIdentNode(getIdent"[]", n.info)
@ -274,35 +272,7 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
of mDotDot: of mDotDot:
result = n result = n
of mRoof: of mRoof:
let bracketExpr = if n.len == 3: n.sons[2] else: c.p.bracketExpr localError(n.info, "builtin roof operator is not supported anymore")
if bracketExpr.isNil:
localError(n.info, "no surrounding array access context for '^'")
result = n.sons[1]
elif bracketExpr.checkForSideEffects != seNoSideEffect:
localError(n.info, "invalid context for '^' as '$#' has side effects" %
renderTree(bracketExpr))
result = n.sons[1]
elif bracketExpr.typ.isStrangeArray:
localError(n.info, "invalid context for '^' as len!=high+1 for '$#'" %
renderTree(bracketExpr))
result = n.sons[1]
else:
# ^x is rewritten to: len(a)-x
let lenExpr = newNodeI(nkCall, n.info)
lenExpr.add newIdentNode(getIdent"len", n.info)
lenExpr.add bracketExpr
let lenExprB = semExprWithType(c, lenExpr)
if lenExprB.typ.isNil or not isOrdinalType(lenExprB.typ):
localError(n.info, "'$#' has to be of an ordinal type for '^'" %
renderTree(lenExpr))
result = n.sons[1]
else:
result = newNodeIT(nkCall, n.info, getSysType(tyInt))
let subi = getSysMagic("-", mSubI)
#echo "got ", typeToString(subi.typ)
result.add newSymNode(subi, n.info)
result.add lenExprB
result.add n.sons[1]
of mPlugin: of mPlugin:
let plugin = getPlugin(n[0].sym) let plugin = getPlugin(n[0].sym)
if plugin.isNil: if plugin.isNil:

View file

@ -113,13 +113,9 @@ type
owner: PSym owner: PSym
cursorInBody: bool # only for nimsuggest cursorInBody: bool # only for nimsuggest
scopeN: int scopeN: int
bracketExpr: PNode
template withBracketExpr(ctx, x, body: untyped) = template withBracketExpr(ctx, x, body: untyped) =
let old = ctx.bracketExpr
ctx.bracketExpr = x
body body
ctx.bracketExpr = old
proc getIdentNode(c: var TemplCtx, n: PNode): PNode = proc getIdentNode(c: var TemplCtx, n: PNode): PNode =
case n.kind case n.kind
@ -304,18 +300,6 @@ proc semTemplBodySons(c: var TemplCtx, n: PNode): PNode =
for i in 0 ..< n.len: for i in 0 ..< n.len:
result.sons[i] = semTemplBody(c, n.sons[i]) result.sons[i] = semTemplBody(c, n.sons[i])
proc oprIsRoof(n: PNode): bool =
const roof = "^"
case n.kind
of nkIdent: result = n.ident.s == roof
of nkSym: result = n.sym.name.s == roof
of nkAccQuoted:
if n.len == 1:
result = oprIsRoof(n.sons[0])
of nkOpenSymChoice, nkClosedSymChoice:
result = oprIsRoof(n.sons[0])
else: discard
proc semTemplBody(c: var TemplCtx, n: PNode): PNode = proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
result = n result = n
semIdeForTemplateOrGenericCheck(n, c.cursorInBody) semIdeForTemplateOrGenericCheck(n, c.cursorInBody)
@ -506,8 +490,6 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
result = semTemplBodySons(c, n) result = semTemplBodySons(c, n)
of nkCallKinds-{nkPostfix}: of nkCallKinds-{nkPostfix}:
result = semTemplBodySons(c, n) result = semTemplBodySons(c, n)
if c.bracketExpr != nil and n.len == 2 and oprIsRoof(n.sons[0]):
result.add c.bracketExpr
of nkDotExpr, nkAccQuoted: of nkDotExpr, nkAccQuoted:
# dotExpr is ambiguous: note that we explicitly allow 'x.TemplateParam', # dotExpr is ambiguous: note that we explicitly allow 'x.TemplateParam',
# so we use the generic code for nkDotExpr too # so we use the generic code for nkDotExpr too

View file

@ -155,7 +155,7 @@ type
## - ``"abc".match(re"(?<letter>\w)").captures["letter"] == "a"`` ## - ``"abc".match(re"(?<letter>\w)").captures["letter"] == "a"``
## - ``"abc".match(re"(\w)\w").captures[-1] == "ab"`` ## - ``"abc".match(re"(\w)\w").captures[-1] == "ab"``
## ##
## ``captureBounds[]: Option[Slice[int]]`` ## ``captureBounds[]: Option[Slice[int, int]]``
## gets the bounds of the given capture according to the same rules as ## gets the bounds of the given capture according to the same rules as
## the above. If the capture is not filled, then ``None`` is returned. ## the above. If the capture is not filled, then ``None`` is returned.
## The bounds are both inclusive. ## The bounds are both inclusive.
@ -167,7 +167,7 @@ type
## ``match: string`` ## ``match: string``
## the full text of the match. ## the full text of the match.
## ##
## ``matchBounds: Slice[int]`` ## ``matchBounds: Slice[int, int]``
## the bounds of the match, as in ``captureBounds[]`` ## the bounds of the match, as in ``captureBounds[]``
## ##
## ``(captureBounds|captures).toTable`` ## ``(captureBounds|captures).toTable``
@ -182,9 +182,9 @@ type
## Not nil. ## Not nil.
str*: string ## The string that was matched against. str*: string ## The string that was matched against.
## Not nil. ## Not nil.
pcreMatchBounds: seq[Slice[cint]] ## First item is the bounds of the match pcreMatchBounds: seq[Slice[cint, cint]] ## First item is the bounds of the match
## Other items are the captures ## Other items are the captures
## `a` is inclusive start, `b` is exclusive end ## `a` is inclusive start, `b` is exclusive end
Captures* = distinct RegexMatch Captures* = distinct RegexMatch
CaptureBounds* = distinct RegexMatch CaptureBounds* = distinct RegexMatch
@ -251,13 +251,13 @@ proc captureBounds*(pattern: RegexMatch): CaptureBounds = return CaptureBounds(p
proc captures*(pattern: RegexMatch): Captures = return Captures(pattern) proc captures*(pattern: RegexMatch): Captures = return Captures(pattern)
proc `[]`*(pattern: CaptureBounds, i: int): Option[Slice[int]] = proc `[]`*(pattern: CaptureBounds, i: int): Option[Slice[int, int]] =
let pattern = RegexMatch(pattern) let pattern = RegexMatch(pattern)
if pattern.pcreMatchBounds[i + 1].a != -1: if pattern.pcreMatchBounds[i + 1].a != -1:
let bounds = pattern.pcreMatchBounds[i + 1] let bounds = pattern.pcreMatchBounds[i + 1]
return some(int(bounds.a) .. int(bounds.b-1)) return some(int(bounds.a) .. int(bounds.b-1))
else: else:
return none(Slice[int]) return none(Slice[int, int])
proc `[]`*(pattern: Captures, i: int): string = proc `[]`*(pattern: Captures, i: int): string =
let pattern = RegexMatch(pattern) let pattern = RegexMatch(pattern)
@ -272,10 +272,10 @@ proc `[]`*(pattern: Captures, i: int): string =
proc match*(pattern: RegexMatch): string = proc match*(pattern: RegexMatch): string =
return pattern.captures[-1] return pattern.captures[-1]
proc matchBounds*(pattern: RegexMatch): Slice[int] = proc matchBounds*(pattern: RegexMatch): Slice[int, int] =
return pattern.captureBounds[-1].get return pattern.captureBounds[-1].get
proc `[]`*(pattern: CaptureBounds, name: string): Option[Slice[int]] = proc `[]`*(pattern: CaptureBounds, name: string): Option[Slice[int, int]] =
let pattern = RegexMatch(pattern) let pattern = RegexMatch(pattern)
return pattern.captureBounds[pattern.pattern.captureNameToId.fget(name)] return pattern.captureBounds[pattern.pattern.captureNameToId.fget(name)]
@ -295,13 +295,13 @@ proc toTable*(pattern: Captures, default: string = nil): Table[string, string] =
result = initTable[string, string]() result = initTable[string, string]()
toTableImpl(nextVal == nil) toTableImpl(nextVal == nil)
proc toTable*(pattern: CaptureBounds, default = none(Slice[int])): proc toTable*(pattern: CaptureBounds, default = none(Slice[int, int])):
Table[string, Option[Slice[int]]] = Table[string, Option[Slice[int, int]]] =
result = initTable[string, Option[Slice[int]]]() result = initTable[string, Option[Slice[int, int]]]()
toTableImpl(nextVal.isNone) toTableImpl(nextVal.isNone)
template itemsImpl(cond: untyped) {.dirty.} = template itemsImpl(cond: untyped) {.dirty.} =
for i in 0 .. <RegexMatch(pattern).pattern.captureCount: for i in 0 ..< RegexMatch(pattern).pattern.captureCount:
let nextVal = pattern[i] let nextVal = pattern[i]
# done in this roundabout way to avoid multiple yields (potential code # done in this roundabout way to avoid multiple yields (potential code
# bloat) # bloat)
@ -309,13 +309,13 @@ template itemsImpl(cond: untyped) {.dirty.} =
yield nextYieldVal yield nextYieldVal
iterator items*(pattern: CaptureBounds, default = none(Slice[int])): Option[Slice[int]] = iterator items*(pattern: CaptureBounds, default = none(Slice[int, int])): Option[Slice[int, int]] =
itemsImpl(nextVal.isNone) itemsImpl(nextVal.isNone)
iterator items*(pattern: Captures, default: string = nil): string = iterator items*(pattern: Captures, default: string = nil): string =
itemsImpl(nextVal == nil) itemsImpl(nextVal == nil)
proc toSeq*(pattern: CaptureBounds, default = none(Slice[int])): seq[Option[Slice[int]]] = proc toSeq*(pattern: CaptureBounds, default = none(Slice[int, int])): seq[Option[Slice[int, int]]] =
accumulateResult(pattern.items(default)) accumulateResult(pattern.items(default))
proc toSeq*(pattern: Captures, default: string = nil): seq[string] = proc toSeq*(pattern: Captures, default: string = nil): seq[string] =
@ -396,8 +396,6 @@ proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study:
# }}} # }}}
type UncheckedArray {.unchecked.}[T] = array[0 .. 0, T]
proc destroyRegex(pattern: Regex) = proc destroyRegex(pattern: Regex) =
pcre.free_substring(cast[cstring](pattern.pcreObj)) pcre.free_substring(cast[cstring](pattern.pcreObj))
pattern.pcreObj = nil pattern.pcreObj = nil
@ -412,7 +410,7 @@ proc getNameToNumberTable(pattern: Regex): Table[string, int] =
result = initTable[string, int]() result = initTable[string, int]()
for i in 0 .. <entryCount: for i in 0 ..< entryCount:
let pos = i * entrySize let pos = i * entrySize
let num = (int(table[pos]) shl 8) or int(table[pos + 1]) - 1 let num = (int(table[pos]) shl 8) or int(table[pos + 1]) - 1
var name = "" var name = ""
@ -464,7 +462,7 @@ proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Opt
# 1x capture count as slack space for PCRE # 1x capture count as slack space for PCRE
let vecsize = (pattern.captureCount() + 1) * 3 let vecsize = (pattern.captureCount() + 1) * 3
# div 2 because each element is 2 cints long # div 2 because each element is 2 cints long
myResult.pcreMatchBounds = newSeq[Slice[cint]](ceil(vecsize / 2).int) myResult.pcreMatchBounds = newSeq[Slice[cint, cint]](ceil(vecsize / 2).int)
myResult.pcreMatchBounds.setLen(vecsize div 3) myResult.pcreMatchBounds.setLen(vecsize div 3)
let strlen = if endpos == int.high: str.len else: endpos+1 let strlen = if endpos == int.high: str.len else: endpos+1

View file

@ -48,7 +48,7 @@ proc validEmailAddress*(s: string): bool {.noSideEffect,
"aero", "jobs", "museum": return true "aero", "jobs", "museum": return true
else: return false else: return false
proc parseInt*(s: string, value: var int, validRange: Slice[int]) {. proc parseInt*(s: string, value: var int, validRange: Slice[int, int]) {.
noSideEffect, rtl, extern: "nmatchParseInt".} = noSideEffect, rtl, extern: "nmatchParseInt".} =
## parses `s` into an integer in the range `validRange`. If successful, ## parses `s` into an integer in the range `validRange`. If successful,
## `value` is modified to contain the result. Otherwise no exception is ## `value` is modified to contain the result. Otherwise no exception is

View file

@ -323,7 +323,7 @@ proc `..`*[T, U](a: T, b: U): Slice[T, U] {.noSideEffect, inline, magic: "DotDot
result.a = a result.a = a
result.b = b result.b = b
proc `..`*[T](b: T): Slice[T, T] {.noSideEffect, inline, magic: "DotDot".} = proc `..`*[T](b: T): Slice[int, T] {.noSideEffect, inline, magic: "DotDot".} =
## `slice`:idx: operator that constructs an interval ``[default(T), b]`` ## `slice`:idx: operator that constructs an interval ``[default(T), b]``
result.b = b result.b = b

View file

@ -1,7 +1,6 @@
discard """ discard """
output: '''@[2, 3, 4]321 output: '''@[2, 3, 4]321
9.0 4.0 9.0 4.0'''
(a: 1.0, b: 2.0, c: 8.0)2.0'''
""" """
proc foo[T](x, y: T): T = x proc foo[T](x, y: T): T = x
@ -10,27 +9,3 @@ var a = @[1, 2, 3, 4]
var b: array[3, array[2, float]] = [[1.0,2], [3.0,4], [8.0,9]] var b: array[3, array[2, float]] = [[1.0,2], [3.0,4], [8.0,9]]
echo a[1.. ^1], a[^2], a[^3], a[^4] echo a[1.. ^1], a[^2], a[^3], a[^4]
echo b[^1][^1], " ", (b[^2]).foo(b[^1])[^1] echo b[^1][^1], " ", (b[^2]).foo(b[^1])[^1]
type
MyArray = object
a, b, c: float
var
ma = MyArray(a: 1.0, b: 2.0, c: 3.0)
proc len(x: MyArray): int = 3
proc `[]=`(x: var MyArray; idx: range[0..2]; val: float) =
case idx
of 0: x.a = val
of 1: x.b = val
of 2: x.c = val
proc `[]`(x: var MyArray; idx: range[0..2]): float =
case idx
of 0: result = x.a
of 1: result = x.b
of 2: result = x.c
ma[^1] = 8.0
echo ma, ma[^2]

View file

@ -1,10 +0,0 @@
discard """
errormsg: "invalid context for '^' as 'foo()' has side effects"
line: "9"
"""
# XXX This needs to be fixed properly!
proc foo(): seq[int] {.sideEffect.} =
echo "ha"
let f = foo()[^1]

View file

@ -1,8 +1,7 @@
discard """ discard """
errormsg: "invalid context for '^' as len!=high+1 for 'a'" output: '''c'''
line: "8"
""" """
var a: array[1..3, string] var a: array['a'..'c', string] = ["a", "b", "c"]
echo a[^1] echo a[^1]

View file

@ -1,37 +0,0 @@
discard """
errormsg: "no surrounding array access context for '^'"
line: "37"
"""
proc foo[T](x, y: T): T = x
var a = @[1, 2, 3, 4]
var b: array[3, array[2, float]] = [[1.0,2], [3.0,4], [8.0,9]]
echo a[1.. ^1], a[^2], a[^3], a[^4]
echo b[^1][^1], " ", (b[^2]).foo(b[^1])[^1]
type
MyArray = object
a, b, c: float
var
ma = MyArray(a: 1.0, b: 2.0, c: 3.0)
proc len(x: MyArray): int = 3
proc `[]=`(x: var MyArray; idx: range[0..2]; val: float) =
case idx
of 0: x.a = val
of 1: x.b = val
of 2: x.c = val
proc `[]`(x: var MyArray; idx: range[0..2]): float =
case idx
of 0: result = x.a
of 1: result = x.b
of 2: result = x.c
ma[^1] = 8.0
echo ma, ma[^2]
echo(^1)

View file

@ -32,7 +32,7 @@ suite "captures":
test "named capture bounds": test "named capture bounds":
let ex1 = "foo".find(re("(?<foo>foo)(?<bar>bar)?")) let ex1 = "foo".find(re("(?<foo>foo)(?<bar>bar)?"))
check(ex1.captureBounds["foo"] == some(0..2)) check(ex1.captureBounds["foo"] == some(0..2))
check(ex1.captureBounds["bar"] == none(Slice[int])) check(ex1.captureBounds["bar"] == none(Slice[int, int]))
test "capture count": test "capture count":
let ex1 = re("(?<foo>foo)(?<bar>bar)?") let ex1 = re("(?<foo>foo)(?<bar>bar)?")
@ -42,7 +42,7 @@ suite "captures":
test "named capture table": test "named capture table":
let ex1 = "foo".find(re("(?<foo>foo)(?<bar>bar)?")) let ex1 = "foo".find(re("(?<foo>foo)(?<bar>bar)?"))
check(ex1.captures.toTable == {"foo" : "foo", "bar" : nil}.toTable()) check(ex1.captures.toTable == {"foo" : "foo", "bar" : nil}.toTable())
check(ex1.captureBounds.toTable == {"foo" : some(0..2), "bar" : none(Slice[int])}.toTable()) check(ex1.captureBounds.toTable == {"foo" : some(0..2), "bar" : none(Slice[int, int])}.toTable())
check(ex1.captures.toTable("") == {"foo" : "foo", "bar" : ""}.toTable()) check(ex1.captures.toTable("") == {"foo" : "foo", "bar" : ""}.toTable())
let ex2 = "foobar".find(re("(?<foo>foo)(?<bar>bar)?")) let ex2 = "foobar".find(re("(?<foo>foo)(?<bar>bar)?"))
@ -51,7 +51,7 @@ suite "captures":
test "capture sequence": test "capture sequence":
let ex1 = "foo".find(re("(?<foo>foo)(?<bar>bar)?")) let ex1 = "foo".find(re("(?<foo>foo)(?<bar>bar)?"))
check(ex1.captures.toSeq == @["foo", nil]) check(ex1.captures.toSeq == @["foo", nil])
check(ex1.captureBounds.toSeq == @[some(0..2), none(Slice[int])]) check(ex1.captureBounds.toSeq == @[some(0..2), none(Slice[int, int])])
check(ex1.captures.toSeq("") == @["foo", ""]) check(ex1.captures.toSeq("") == @["foo", ""])
let ex2 = "foobar".find(re("(?<foo>foo)(?<bar>bar)?")) let ex2 = "foobar".find(re("(?<foo>foo)(?<bar>bar)?"))

View file

@ -12,7 +12,7 @@ suite "find":
test "find bounds": test "find bounds":
check(toSeq(findIter("1 2 3 4 5 ", re" ")).map( check(toSeq(findIter("1 2 3 4 5 ", re" ")).map(
proc (a: RegexMatch): Slice[int] = a.matchBounds proc (a: RegexMatch): Slice[int, int] = a.matchBounds
) == @[1..1, 3..3, 5..5, 7..7, 9..9]) ) == @[1..1, 3..3, 5..5, 7..7, 9..9])
test "overlapping find": test "overlapping find":