improve runnableExamples and docs for std/nre (#18634)

* improve runnableExamples and docs for std/nre

* avoid too long lines in example
This commit is contained in:
Timothee Cour 2021-08-02 22:22:47 -07:00 • committed by GitHub
commit c0abdaa2b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -23,12 +23,12 @@ when defined(js):
## **Note**: If you love `sequtils.toSeq` we have bad news for you. This ## **Note**: If you love `sequtils.toSeq` we have bad news for you. This
## library doesn't work with it due to documented compiler limitations. As ## library doesn't work with it due to documented compiler limitations. As
## a workaround, use this: ## a workaround, use this:
## runnableExamples:
## .. code-block:: nim # either `import std/nre except toSeq` or fully qualify `sequtils.toSeq`:
## import std/sequtils
## import std/nre except toSeq iterator iota(n: int): int =
## for i in 0..<n: yield i
## assert sequtils.toSeq(iota(3)) == @[0, 1, 2]
## Licencing ## Licencing
## --------- ## ---------
## ##
@ -36,33 +36,28 @@ when defined(js):
## this module. ## this module.
## ##
## .. _`some additional terms`: http://pcre.sourceforge.net/license.txt ## .. _`some additional terms`: http://pcre.sourceforge.net/license.txt
##
runnableExamples: runnableExamples:
import std/sugar
let vowels = re"[aeoui]" let vowels = re"[aeoui]"
let bounds = collect:
let expectedResults = [ for match in "moiga".findIter(vowels): match.matchBounds
1 .. 1, assert bounds == @[1 .. 1, 2 .. 2, 4 .. 4]
2 .. 2, from std/sequtils import toSeq
4 .. 4, let s = sequtils.toSeq("moiga".findIter(vowels))
6 .. 6, # fully qualified to avoid confusion with nre.toSeq
7 .. 7, assert s.len == 3
]
var i = 0
for match in "moigagoo".findIter(vowels):
doAssert match.matchBounds == expectedResults[i]
inc i
let firstVowel = "foo".find(vowels) let firstVowel = "foo".find(vowels)
let hasVowel = firstVowel.isSome() let hasVowel = firstVowel.isSome()
if hasVowel: assert hasVowel
let matchBounds = firstVowel.get().captureBounds[-1] let matchBounds = firstVowel.get().captureBounds[-1]
doAssert matchBounds.a == 1 assert matchBounds.a == 1
## as with module `re`, unless specified otherwise, `start` parameter in each # as with module `re`, unless specified otherwise, `start` parameter in each
## proc indicates where the scan starts, but outputs are relative to the start # proc indicates where the scan starts, but outputs are relative to the start
## of the input string, not to `start`: # of the input string, not to `start`:
doAssert find("uxabc", re"(?<=x|y)ab", start = 1).get.captures[-1] == "ab" assert find("uxabc", re"(?<=x|y)ab", start = 1).get.captures[-1] == "ab"
doAssert find("uxabc", re"ab", start = 3).isNone assert find("uxabc", re"ab", start = 3).isNone
from pcre import nil from pcre import nil
import nre/private/util import nre/private/util
@ -145,7 +140,7 @@ type
## `DOLLAR_ENDONLY`, `FIRSTLINE`, `NO_AUTO_CAPTURE`, ## `DOLLAR_ENDONLY`, `FIRSTLINE`, `NO_AUTO_CAPTURE`,
## `JAVASCRIPT_COMPAT`, `U`, `NO_STUDY`. In other PCRE wrappers, you ## `JAVASCRIPT_COMPAT`, `U`, `NO_STUDY`. In other PCRE wrappers, you
## will need to pass these as separate flags to PCRE. ## will need to pass these as separate flags to PCRE.
pattern*: string ## not nil pattern*: string
pcreObj: ptr pcre.Pcre ## not nil pcreObj: ptr pcre.Pcre ## not nil
pcreExtra: ptr pcre.ExtraData ## nil pcreExtra: ptr pcre.ExtraData ## nil
@ -165,21 +160,12 @@ type
## the string value of whatever was captured at that id. If the value ## the string value of whatever was captured at that id. If the value
## is invalid, then behavior is undefined. If the id is `-1`, then ## is invalid, then behavior is undefined. If the id is `-1`, then
## the whole match is returned. If the given capture was not matched, ## the whole match is returned. If the given capture was not matched,
## `nil` is returned. ## `nil` is returned. See examples for `match`.
##
## - `"abc".match(re"(\w)").get.captures[0] == "a"`
## - `"abc".match(re"(?<letter>\w)").get.captures["letter"] == "a"`
## - `"abc".match(re"(\w)\w").get.captures[-1] == "ab"`
## ##
## `captureBounds[]: HSlice[int, int]` ## `captureBounds[]: HSlice[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. See examples for `match`.
##
## - `"abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0`
## - `0 in "abc".match(re"(\w)").get.captureBounds == true`
## - `"abc".match(re"").get.captureBounds[-1] == 0 .. -1`
## - `"abc".match(re"abc").get.captureBounds[-1] == 0 .. 2`
## ##
## `match: string` ## `match: string`
## the full text of the match. ## the full text of the match.
@ -198,7 +184,6 @@ type
pattern*: Regex ## The regex doing the matching. pattern*: Regex ## The regex doing the matching.
## Not nil. ## Not nil.
str*: string ## The string that was matched against. str*: string ## The string that was matched against.
## Not nil.
pcreMatchBounds: seq[HSlice[cint, cint]] ## First item is the bounds of the match pcreMatchBounds: seq[HSlice[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
@ -226,18 +211,6 @@ type
## for whatever reason. The message contains the error ## for whatever reason. The message contains the error
## code. ## code.
runnableExamples:
# This MUST be kept in sync with the examples in RegexMatch
doAssert "abc".match(re"(\w)").get.captures[0] == "a"
doAssert "abc".match(re"(?<letter>\w)").get.captures["letter"] == "a"
doAssert "abc".match(re"(\w)\w").get.captures[-1] == "ab"
doAssert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0
doAssert 0 in "abc".match(re"(\w)").get.captureBounds == true
doAssert "abc".match(re"").get.captureBounds[-1] == 0 .. -1
doAssert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2
proc destroyRegex(pattern: Regex) = proc destroyRegex(pattern: Regex) =
pcre.free_substring(cast[cstring](pattern.pcreObj)) pcre.free_substring(cast[cstring](pattern.pcreObj))
if pattern.pcreExtra != nil: if pattern.pcreExtra != nil:
@ -540,18 +513,27 @@ proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Opt
proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] = proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
## Like `find(...)<#find,string,Regex,int>`_, but anchored to the start of the ## Like `find(...)<#find,string,Regex,int>`_, but anchored to the start of the
## string. ## string.
##
runnableExamples: runnableExamples:
doAssert "foo".match(re"f").isSome assert "foo".match(re"f").isSome
doAssert "foo".match(re"o").isNone assert "foo".match(re"o").isNone
assert "abc".match(re"(\w)").get.captures[0] == "a"
assert "abc".match(re"(?<letter>\w)").get.captures["letter"] == "a"
assert "abc".match(re"(\w)\w").get.captures[-1] == "ab"
assert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0
assert 0 in "abc".match(re"(\w)").get.captureBounds
assert "abc".match(re"").get.captureBounds[-1] == 0 .. -1
assert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2
return str.matchImpl(pattern, start, endpos, pcre.ANCHORED) return str.matchImpl(pattern, start, endpos, pcre.ANCHORED)
iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch = iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch =
## Works the same as `find(...)<#find,string,Regex,int>`_, but finds every ## Works the same as `find(...)<#find,string,Regex,int>`_, but finds every
## non-overlapping match. `"2222".find(re"22")` is `"22", "22"`, not ## non-overlapping match:
## `"22", "22", "22"`. runnableExamples:
## import std/sugar
assert collect(for a in "2222".findIter(re"22"): a.match) == @["22", "22"]
# not @["22", "22", "22"]
## Arguments are the same as `find(...)<#find,string,Regex,int>`_ ## Arguments are the same as `find(...)<#find,string,Regex,int>`_
## ##
## Variants: ## Variants:
@ -619,11 +601,10 @@ proc contains*(str: string, pattern: Regex, start = 0, endpos = int.high): bool
## Determine if the string contains the given pattern between the end and ## Determine if the string contains the given pattern between the end and
## start positions: ## start positions:
## This function is equivalent to `isSome(str.find(pattern, start, endpos))`. ## This function is equivalent to `isSome(str.find(pattern, start, endpos))`.
##
runnableExamples: runnableExamples:
doAssert "abc".contains(re"bc") assert "abc".contains(re"bc")
doAssert not "abc".contains(re"cd") assert not "abc".contains(re"cd")
doAssert not "abc".contains(re"a", start = 1) assert not "abc".contains(re"a", start = 1)
return isSome(str.find(pattern, start, endpos)) return isSome(str.find(pattern, start, endpos))
@ -635,16 +616,16 @@ proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string]
## ##
runnableExamples: runnableExamples:
# - If the match is zero-width, then the string is still split: # - If the match is zero-width, then the string is still split:
doAssert "123".split(re"") == @["1", "2", "3"] assert "123".split(re"") == @["1", "2", "3"]
# - If the pattern has a capture in it, it is added after the string # - If the pattern has a capture in it, it is added after the string
# split: # split:
doAssert "12".split(re"(\d)") == @["", "1", "", "2", ""] assert "12".split(re"(\d)") == @["", "1", "", "2", ""]
# - If `maxsplit != -1`, then the string will only be split # - If `maxsplit != -1`, then the string will only be split
# `maxsplit - 1` times. This means that there will be `maxsplit` # `maxsplit - 1` times. This means that there will be `maxsplit`
# strings in the output seq. # strings in the output seq.
doAssert "1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"] assert "1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"]
result = @[] result = @[]
var lastIdx = start var lastIdx = start
@ -747,9 +728,9 @@ proc escapeRe*(str: string): string {.gcsafe.} =
## ##
## Escaped char: `\ + * ? [ ^ ] $ ( ) { } = ! < > | : -` ## Escaped char: `\ + * ? [ ^ ] $ ( ) { } = ! < > | : -`
runnableExamples: runnableExamples:
doAssert escapeRe("fly+wind") == "fly\\+wind" assert escapeRe("fly+wind") == "fly\\+wind"
doAssert escapeRe("!") == "\\!" assert escapeRe("!") == "\\!"
doAssert escapeRe("nim*") == "nim\\*" assert escapeRe("nim*") == "nim\\*"
#([\\+*?[^\]$(){}=!<>|:-]) #([\\+*?[^\]$(){}=!<>|:-])
const SpecialCharMatcher = {'\\', '+', '*', '?', '[', '^', ']', '$', '(', const SpecialCharMatcher = {'\\', '+', '*', '?', '[', '^', ']', '$', '(',