Change capture upper bounds to inclusive

This commit is contained in:
Oleh Prypin 2015-04-09 23:14:00 +03:00
commit 4e83fc5867
5 changed files with 29 additions and 30 deletions

View file

@ -118,11 +118,10 @@ at that id. If the value is invalid, then behavior is undefined. If the id is
- `"abc".match(re"(\w)\w").captures[-1] == "ab"` - `"abc".match(re"(\w)\w").captures[-1] == "ab"`
`captureBounds[]: Option[Slice[int]]` :: gets the bounds of the `captureBounds[]: Option[Slice[int]]` :: gets the bounds of the
given capture according to the same rules as the above. If the capture is not given capture according to the same rules as the above. If the capture is not
filled, then `None` is returned. The upper bound is exclusive, the lower bound filled, then `None` is returned. The bounds are both inclusive.
is inclusive. - `"abc".match(re"(\w)").captureBounds[0] == 0 .. 0`
- `"abc".match(re"(\w)").captureBounds[0] == 0..1` - `"abc".match(re"").captureBounds[-1] == 0 .. -1`
- `"abc".match(re"").captureBounds[-1] == 0..0` - `"abc".match(re"abc").captureBounds[-1] == 0 .. 2`
- `"abc".match(re"abc").captureBounds[-1] == 0..3`
`match: string` :: the full text of the match. `match: string` :: the full text of the match.
`matchBounds: Slice[int]` :: the bounds of the match, as in `captureBounds[]` `matchBounds: Slice[int]` :: the bounds of the match, as in `captureBounds[]`
`(captureBounds|captures).toTable` :: returns a table with each named capture `(captureBounds|captures).toTable` :: returns a table with each named capture

View file

@ -97,7 +97,7 @@ proc `[]`*(pattern: CaptureBounds, i: int): Option[Slice[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)) return Some(int(bounds.a) .. int(bounds.b-1))
else: else:
return None[Slice[int]]() return None[Slice[int]]()
@ -111,7 +111,7 @@ proc `[]`*(pattern: Captures, i: int): string =
if bounds: if bounds:
let bounds = bounds.get let bounds = bounds.get
return pattern.str.substr(bounds.a, bounds.b-1) return pattern.str.substr(bounds.a, bounds.b)
else: else:
return nil return nil
@ -343,7 +343,7 @@ iterator findIter*(str: string, pattern: Regex, start = 0, endpos = -1): RegexMa
var flags = 0 var flags = 0
if match and if match and
match.get.matchBounds.a == match.get.matchBounds.b: match.get.matchBounds.a > match.get.matchBounds.b:
# 0-len match # 0-len match
flags = pcre.NOTEMPTY_ATSTART or pcre.ANCHORED flags = pcre.NOTEMPTY_ATSTART or pcre.ANCHORED
@ -363,7 +363,7 @@ iterator findIter*(str: string, pattern: Regex, start = 0, endpos = -1): RegexMa
offset += str.runeLenAt(offset) offset += str.runeLenAt(offset)
assert(offset <= endpos) assert(offset <= endpos)
else: else:
offset = match.get.matchBounds.b offset = match.get.matchBounds.b + 1
yield match.get yield match.get
@ -387,7 +387,7 @@ proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string]
result = @[] result = @[]
var lastIdx = start var lastIdx = start
var splits = 0 var splits = 0
var bounds: Slice[int] var bounds = 0 .. -1
for match in str.findIter(pattern, start = start): for match in str.findIter(pattern, start = start):
# upper bound is exclusive, lower is inclusive: # upper bound is exclusive, lower is inclusive:
@ -400,11 +400,11 @@ proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string]
# "12".split("") would be @["", "1", "2"], but # "12".split("") would be @["", "1", "2"], but
# if we skip an empty first match, it's the correct # if we skip an empty first match, it's the correct
# @["1", "2"] # @["1", "2"]
if bounds.a < bounds.b or bounds.a > start: if bounds.a <= bounds.b or bounds.a > start:
result.add(str.substr(lastIdx, bounds.a - 1)) result.add(str.substr(lastIdx, bounds.a - 1))
splits += 1 splits += 1
lastIdx = bounds.b lastIdx = bounds.b + 1
for cap in match.captures: for cap in match.captures:
# if there are captures, include them in the result # if there are captures, include them in the result
@ -416,11 +416,11 @@ proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string]
# "12".split("\b") would be @["1", "2", ""], but # "12".split("\b") would be @["1", "2", ""], but
# if we skip an empty last match, it's the correct # if we skip an empty last match, it's the correct
# @["1", "2"] # @["1", "2"]
if bounds.a < bounds.b or bounds.b < str.len: if bounds.a <= bounds.b or bounds.b < str.high:
# last match: Each match takes the previous substring, # last match: Each match takes the previous substring,
# but "1 2".split(/ /) needs to return @["1", "2"]. # but "1 2".split(/ /) needs to return @["1", "2"].
# This handles "2" # This handles "2"
result.add(str.substr(bounds.b, str.len - 1)) result.add(str.substr(bounds.b + 1, str.high))
template replaceImpl(str: string, pattern: Regex, template replaceImpl(str: string, pattern: Regex,
replacement: expr): stmt {.immediate, dirty.} = replacement: expr): stmt {.immediate, dirty.} =
@ -435,7 +435,7 @@ template replaceImpl(str: string, pattern: Regex,
assert(nextVal != nil) assert(nextVal != nil)
result.add(nextVal) result.add(nextVal)
lastIdx = bounds.b lastIdx = bounds.b + 1
result.add(str.substr(lastIdx, str.len - 1)) result.add(str.substr(lastIdx, str.len - 1))
return result return result

View file

@ -8,17 +8,17 @@ suite "captures":
test "capture bounds are correct": test "capture bounds are correct":
let ex1 = re("([0-9])") let ex1 = re("([0-9])")
check("1 23".find(ex1).matchBounds == 0 .. 1) check("1 23".find(ex1).matchBounds == 0 .. 0)
check("1 23".find(ex1).captureBounds[0].get == 0 .. 1) check("1 23".find(ex1).captureBounds[0].get == 0 .. 0)
check("1 23".find(ex1, 1).matchBounds == 2 .. 3) check("1 23".find(ex1, 1).matchBounds == 2 .. 2)
check("1 23".find(ex1, 3).matchBounds == 3 .. 4) check("1 23".find(ex1, 3).matchBounds == 3 .. 3)
let ex2 = re("()()()()()()()()()()([0-9])") let ex2 = re("()()()()()()()()()()([0-9])")
check("824".find(ex2).captureBounds[0].get == 0 .. 0) check("824".find(ex2).captureBounds[0].get == 0 .. -1)
check("824".find(ex2).captureBounds[10].get == 0 .. 1) check("824".find(ex2).captureBounds[10].get == 0 .. 0)
let ex3 = re("([0-9]+)") let ex3 = re("([0-9]+)")
check("824".find(ex3).captureBounds[0].get == 0 .. 3) check("824".find(ex3).captureBounds[0].get == 0 .. 2)
test "named captures": test "named captures":
let ex1 = "foobar".find(re("(?<foo>foo)(?<bar>bar)")) let ex1 = "foobar".find(re("(?<foo>foo)(?<bar>bar)"))
@ -31,7 +31,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..3)) check(ex1.captureBounds["foo"] == Some(0..2))
check(ex1.captureBounds["bar"] == None[Slice[int]]()) check(ex1.captureBounds["bar"] == None[Slice[int]]())
test "capture count": test "capture count":
@ -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..3), "bar" : None[Slice[int]]()}.toTable()) check(ex1.captureBounds.toTable == {"foo" : Some(0..2), "bar" : None[Slice[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..3), None[Slice[int]]()]) check(ex1.captureBounds.toSeq == @[Some(0..2), None[Slice[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

@ -10,7 +10,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] = a.matchBounds
) == @[1..2, 3..4, 5..6, 7..8, 9..10]) ) == @[1..1, 3..3, 5..5, 7..7, 9..9])
test "overlapping find": test "overlapping find":
check("222".findAll(re"22") == @["22"]) check("222".findAll(re"22") == @["22"])

View file

@ -9,9 +9,9 @@ suite "match":
check("abc".match(re"(\w)").captures[0] == "a") check("abc".match(re"(\w)").captures[0] == "a")
check("abc".match(re"(?<letter>\w)").captures["letter"] == "a") check("abc".match(re"(?<letter>\w)").captures["letter"] == "a")
check("abc".match(re"(\w)\w").captures[-1] == "ab") check("abc".match(re"(\w)\w").captures[-1] == "ab")
check("abc".match(re"(\w)").captureBounds[0].get == 0..1) check("abc".match(re"(\w)").captureBounds[0].get == 0 .. 0)
check("abc".match(re"").captureBounds[-1].get == 0..0) check("abc".match(re"").captureBounds[-1].get == 0 .. -1)
check("abc".match(re"abc").captureBounds[-1].get == 0..3) check("abc".match(re"abc").captureBounds[-1].get == 0 .. 2)
test "match test cases": test "match test cases":
check("123".match(re"").matchBounds == 0..0) check("123".match(re"").matchBounds == 0 .. -1)