re.nim 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2024 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. when defined(js):
  10. {.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".}
  11. ## Regular expression support for Nim.
  12. ##
  13. ## This module is implemented by providing a wrapper around the
  14. ## `PCRE (Perl-Compatible Regular Expressions) <https://www.pcre.org>`_
  15. ## C library. This means that your application will depend on the PCRE
  16. ## library's licence when using this module, which should not be a problem
  17. ## though.
  18. ##
  19. ## .. note:: There are also alternative nimble packages such as [tinyre](https://github.com/khchen/tinyre)
  20. ## and [regex](https://github.com/nitely/nim-regex).
  21. ##
  22. ## PCRE's licence follows:
  23. ##
  24. ## .. include:: ../../doc/regexprs.txt
  25. ##
  26. runnableExamples:
  27. ## Unless specified otherwise, `start` parameter in each proc indicates
  28. ## where the scan starts, but outputs are relative to the start of the input
  29. ## string, not to `start`:
  30. doAssert find("uxabc", re"(?<=x|y)ab", start = 1) == 2 # lookbehind assertion
  31. doAssert find("uxabc", re"ab", start = 3) == -1 # we're past `start` => not found
  32. doAssert not match("xabc", re"^abc$", start = 1)
  33. # can't match start of string since we're starting at 1
  34. import
  35. std/[strutils, rtarrays]
  36. import std/pcre2
  37. when defined(nimPreviewSlimSystem):
  38. import std/syncio
  39. const
  40. MaxSubpatterns* = 20
  41. ## defines the maximum number of subpatterns that can be captured.
  42. ## This limit still exists for `replacef` and `parallelReplace`.
  43. type
  44. RegexFlag* = enum ## options for regular expressions
  45. reIgnoreCase = 0, ## do caseless matching
  46. reMultiLine = 1, ## `^` and `$` match newlines within data
  47. reDotAll = 2, ## `.` matches anything including NL
  48. reExtended = 3, ## ignore whitespace and `#` comments
  49. reStudy = 4 ## study the expression (may be omitted if the
  50. ## expression will be used only once)
  51. RegexDesc = object
  52. h: ptr Pcre
  53. Regex* = ref RegexDesc ## a compiled regular expression
  54. RegexError* = object of ValueError
  55. ## is raised if the pattern is no valid regular expression.
  56. when defined(gcDestructors):
  57. when defined(nimAllowNonVarDestructor):
  58. proc `=destroy`(x: RegexDesc) =
  59. pcre2.code_free(x.h)
  60. else:
  61. proc `=destroy`(x: var RegexDesc) =
  62. pcre2.code_free(x.h)
  63. proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} =
  64. var e: ref RegexError
  65. new(e)
  66. e.msg = msg
  67. raise e
  68. proc rawCompile(pattern: string, flags: csize_t, options: uint32): ptr Pcre =
  69. var
  70. errorCode: cint = 0
  71. offset: csize_t = 0
  72. result = pcre2.compile(pattern.cstring, flags, options, addr(errorCode), addr(offset), nil)
  73. if result == nil:
  74. raiseInvalidRegex($errorCode & "\n" & pattern & "\n" & spaces(offset) & "^\n")
  75. proc finalizeRegEx(x: Regex) =
  76. # XXX This is a hack, but PCRE does not export its "free" function properly.
  77. # Sigh. The hack relies on PCRE's implementation (see `pcre_get.c`).
  78. # Fortunately the implementation is unlikely to change.
  79. pcre2.code_free(x.h)
  80. proc re*(s: string, flags = {reStudy}): Regex =
  81. ## Constructor of regular expressions.
  82. ##
  83. ## Note that Nim's
  84. ## extended raw string literals support the syntax `re"[abc]"` as
  85. ## a short form for `re(r"[abc]")`. Also note that since this
  86. ## compiles the regular expression, which is expensive, you should
  87. ## avoid putting it directly in the arguments of the functions like
  88. ## the examples show below if you plan to use it a lot of times, as
  89. ## this will hurt performance immensely. (e.g. outside a loop, ...)
  90. when defined(gcDestructors):
  91. result = Regex()
  92. else:
  93. new(result, finalizeRegEx)
  94. var options = 0'u32
  95. if reExtended in flags:
  96. options = options or EXTENDED
  97. if reIgnoreCase in flags:
  98. options = options or CASELESS
  99. result.h = rawCompile(s, cast[csize_t](ZERO_TERMINATED), options)
  100. if reStudy in flags: # TODO: add reJit
  101. var hasJit: cint = cint(0)
  102. if pcre2.config(pcre2.CONFIG_JIT, addr hasJit) == 0:
  103. if hasJit == 1'i32 and jit_compile(result.h, pcre2.JIT_COMPLETE) != 0:
  104. raiseInvalidRegex("JIT compilation failed.")
  105. proc rex*(s: string, flags = {reStudy, reExtended}): Regex =
  106. ## Constructor for extended regular expressions.
  107. ##
  108. ## The extended means that comments starting with `#` and
  109. ## whitespace are ignored.
  110. result = re(s, flags)
  111. proc bufSubstr(b: cstring, sPos, ePos: int): string {.inline.} =
  112. ## Return a Nim string built from a slice of a cstring buffer.
  113. ## Don't assume cstring is '\0' terminated
  114. let sz = ePos - sPos
  115. result = newString(sz+1)
  116. copyMem(addr(result[0]), unsafeAddr(b[sPos]), sz)
  117. result.setLen(sz)
  118. proc matchOrFind(buf: cstring, pattern: Regex, matches: var openArray[string],
  119. start, bufSize: int; options: uint32): int =
  120. var
  121. rtarray = initRtArray[csize_t]((matches.len+1)*3)
  122. rawMatches = rtarray.getRawData
  123. var matchData = match_data_create_from_pattern(pattern.h, nil)
  124. defer: match_data_free(matchData)
  125. var res = pcre2.match(pattern.h, buf, bufSize.csize_t, start.csize_t, options,
  126. matchData, nil)
  127. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  128. if res < 0: return res
  129. for i in 1..int(res)-1:
  130. var a = rawMatches[i * 2]
  131. var b = rawMatches[i * 2 + 1]
  132. if a != UNSET:
  133. matches[i-1] = bufSubstr(buf, int(a), int(b))
  134. else: matches[i-1] = ""
  135. return int(rawMatches[1]) - int(rawMatches[0])
  136. const MaxReBufSize* = high(cint)
  137. ## Maximum PCRE (API 1) buffer start/size equal to `high(cint)`, which even
  138. ## for 64-bit systems can be either 2`31`:sup:-1 or 2`63`:sup:-1.
  139. proc findBounds*(buf: cstring, pattern: Regex, matches: var openArray[string],
  140. start = 0, bufSize: int): tuple[first, last: int] =
  141. ## returns the starting position and end position of `pattern` in `buf`
  142. ## (where `buf` has length `bufSize` and is not necessarily `'\0'` terminated),
  143. ## and the captured
  144. ## substrings in the array `matches`. If it does not match, nothing
  145. ## is written into `matches` and `(-1,0)` is returned.
  146. ##
  147. ## Note: The memory for `matches` needs to be allocated before this function is
  148. ## called, otherwise it will just remain empty.
  149. var
  150. rtarray = initRtArray[csize_t]((matches.len+1)*3)
  151. rawMatches = rtarray.getRawData
  152. var matchData = match_data_create_from_pattern(pattern.h, nil)
  153. defer: match_data_free(matchData)
  154. var res = pcre2.match(pattern.h, buf, bufSize.csize_t, start.csize_t, 0'u32,
  155. matchData, nil)
  156. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  157. if res < 0: return (-1, 0)
  158. for i in 1..int(res)-1:
  159. var a = rawMatches[i * 2]
  160. var b = rawMatches[i * 2 + 1]
  161. if a != UNSET: matches[i-1] = bufSubstr(buf, int(a), int(b))
  162. else: matches[i-1] = ""
  163. return (rawMatches[0].int, rawMatches[1].int - 1)
  164. proc findBounds*(s: string, pattern: Regex, matches: var openArray[string],
  165. start = 0): tuple[first, last: int] {.inline.} =
  166. ## returns the starting position and end position of `pattern` in `s`
  167. ## and the captured substrings in the array `matches`.
  168. ## If it does not match, nothing
  169. ## is written into `matches` and `(-1,0)` is returned.
  170. ##
  171. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  172. runnableExamples:
  173. var matches = newSeq[string](1)
  174. let (first, last) = findBounds("Hello World", re"(W\w+)", matches)
  175. doAssert first == 6
  176. doAssert last == 10
  177. doAssert matches[0] == "World"
  178. result = findBounds(cstring(s), pattern, matches,
  179. min(start, MaxReBufSize), min(s.len, MaxReBufSize))
  180. proc findBounds*(buf: cstring, pattern: Regex,
  181. matches: var openArray[tuple[first, last: int]],
  182. start = 0, bufSize: int): tuple[first, last: int] =
  183. ## returns the starting position and end position of `pattern` in `buf`
  184. ## (where `buf` has length `bufSize` and is not necessarily `'\0'` terminated),
  185. ## and the captured substrings in the array `matches`.
  186. ## If it does not match, nothing is written into `matches` and
  187. ## `(-1,0)` is returned.
  188. ##
  189. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  190. var
  191. rtarray = initRtArray[csize_t]((matches.len+1)*3)
  192. rawMatches = rtarray.getRawData
  193. var matchData = match_data_create_from_pattern(pattern.h, nil)
  194. defer: match_data_free(matchData)
  195. var res = pcre2.match(pattern.h, buf, bufSize.csize_t, start.csize_t, 0'u32,
  196. matchData, nil)
  197. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  198. if res < 0'i32: return (-1, 0)
  199. for i in 1..int(res)-1:
  200. var a = rawMatches[i * 2]
  201. var b = rawMatches[i * 2 + 1]
  202. if a != UNSET: matches[i-1] = (int(a), int(b)-1)
  203. else: matches[i-1] = (-1,0)
  204. return (rawMatches[0].int, rawMatches[1].int - 1)
  205. proc findBounds*(s: string, pattern: Regex,
  206. matches: var openArray[tuple[first, last: int]],
  207. start = 0): tuple[first, last: int] {.inline.} =
  208. ## returns the starting position and end position of `pattern` in `s`
  209. ## and the captured substrings in the array `matches`.
  210. ## If it does not match, nothing is written into `matches` and
  211. ## `(-1,0)` is returned.
  212. ##
  213. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  214. runnableExamples:
  215. var matches = newSeq[tuple[first, last: int]](1)
  216. let (first, last) = findBounds("Hello World", re"(\w+)", matches)
  217. doAssert first == 0
  218. doAssert last == 4
  219. doAssert matches[0] == (0, 4)
  220. result = findBounds(cstring(s), pattern, matches,
  221. min(start, MaxReBufSize), min(s.len, MaxReBufSize))
  222. proc findBoundsImpl(buf: cstring, pattern: Regex,
  223. start = 0, bufSize = 0, options = 0'u32): tuple[first, last: int] =
  224. var rtarray = initRtArray[csize_t](3)
  225. var rawMatches = rtarray.getRawData
  226. var matchData = match_data_create_from_pattern(pattern.h, nil)
  227. defer: match_data_free(matchData)
  228. var res = pcre2.match(pattern.h, buf, bufSize.csize_t, start.csize_t, options,
  229. matchData, nil)
  230. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  231. if res < 0'i32:
  232. result = (-1, 0)
  233. else:
  234. result = (int(rawMatches[0]), int(rawMatches[1])-1)
  235. proc findBounds*(buf: cstring, pattern: Regex,
  236. start = 0, bufSize: int): tuple[first, last: int] =
  237. ## returns the `first` and `last` position of `pattern` in `buf`,
  238. ## where `buf` has length `bufSize` (not necessarily `'\0'` terminated).
  239. ## If it does not match, `(-1,0)` is returned.
  240. var
  241. rtarray = initRtArray[csize_t](3)
  242. var rawMatches = rtarray.getRawData
  243. var matchData = match_data_create_from_pattern(pattern.h, nil)
  244. defer: match_data_free(matchData)
  245. var res = pcre2.match(pattern.h, buf, bufSize.csize_t, start.csize_t, 0'u32,
  246. matchData, nil)
  247. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  248. if res < 0'i32: return (int(res), 0)
  249. return (int(rawMatches[0]), int(rawMatches[1]-1))
  250. proc findBounds*(s: string, pattern: Regex,
  251. start = 0): tuple[first, last: int] {.inline.} =
  252. ## returns the `first` and `last` position of `pattern` in `s`.
  253. ## If it does not match, `(-1,0)` is returned.
  254. ##
  255. ## Note: there is a speed improvement if the matches do not need to be captured.
  256. runnableExamples:
  257. assert findBounds("01234abc89", re"abc") == (5,7)
  258. result = findBounds(cstring(s), pattern,
  259. min(start, MaxReBufSize), min(s.len, MaxReBufSize))
  260. proc matchOrFind(buf: cstring, pattern: Regex, start, bufSize: int, options: uint32): int =
  261. var
  262. rtarray = initRtArray[csize_t](3)
  263. rawMatches = rtarray.getRawData
  264. var matchData = match_data_create_from_pattern(pattern.h, nil)
  265. defer: match_data_free(matchData)
  266. result = pcre2.match(pattern.h, buf, bufSize.csize_t, start.csize_t, options,
  267. matchData, nil)
  268. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  269. if result >= 0'i32:
  270. result = int(rawMatches[1]) - int(rawMatches[0])
  271. proc matchLen*(s: string, pattern: Regex, matches: var openArray[string],
  272. start = 0): int {.inline.} =
  273. ## the same as `match`, but it returns the length of the match,
  274. ## if there is no match, `-1` is returned. Note that a match length
  275. ## of zero can happen.
  276. ##
  277. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  278. result = matchOrFind(cstring(s), pattern, matches, start, s.len, pcre2.ANCHORED)
  279. proc matchLen*(buf: cstring, pattern: Regex, matches: var openArray[string],
  280. start = 0, bufSize: int): int {.inline.} =
  281. ## the same as `match`, but it returns the length of the match,
  282. ## if there is no match, `-1` is returned. Note that a match length
  283. ## of zero can happen.
  284. ##
  285. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  286. return matchOrFind(buf, pattern, matches, start, bufSize, pcre2.ANCHORED)
  287. proc matchLen*(s: string, pattern: Regex, start = 0): int {.inline.} =
  288. ## the same as `match`, but it returns the length of the match,
  289. ## if there is no match, `-1` is returned. Note that a match length
  290. ## of zero can happen.
  291. ##
  292. runnableExamples:
  293. doAssert matchLen("abcdefg", re"cde", 2) == 3
  294. doAssert matchLen("abcdefg", re"abcde") == 5
  295. doAssert matchLen("abcdefg", re"cde") == -1
  296. result = matchOrFind(cstring(s), pattern, start, s.len, pcre2.ANCHORED)
  297. proc matchLen*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int {.inline.} =
  298. ## the same as `match`, but it returns the length of the match,
  299. ## if there is no match, `-1` is returned. Note that a match length
  300. ## of zero can happen.
  301. result = matchOrFind(buf, pattern, start, bufSize, pcre2.ANCHORED)
  302. proc match*(s: string, pattern: Regex, start = 0): bool {.inline.} =
  303. ## returns `true` if `s[start..]` matches the `pattern`.
  304. result = matchLen(cstring(s), pattern, start, s.len) != -1
  305. proc match*(s: string, pattern: Regex, matches: var openArray[string],
  306. start = 0): bool {.inline.} =
  307. ## returns `true` if `s[start..]` matches the `pattern` and
  308. ## the captured substrings in the array `matches`. If it does not
  309. ## match, nothing is written into `matches` and `false` is
  310. ## returned.
  311. ##
  312. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  313. runnableExamples:
  314. import std/sequtils
  315. var matches: array[2, string]
  316. if match("abcdefg", re"c(d)ef(g)", matches, 2):
  317. doAssert toSeq(matches) == @["d", "g"]
  318. result = matchLen(cstring(s), pattern, matches, start, s.len) != -1
  319. proc match*(buf: cstring, pattern: Regex, matches: var openArray[string],
  320. start = 0, bufSize: int): bool {.inline.} =
  321. ## returns `true` if `buf[start..<bufSize]` matches the `pattern` and
  322. ## the captured substrings in the array `matches`. If it does not
  323. ## match, nothing is written into `matches` and `false` is
  324. ## returned.
  325. ## `buf` has length `bufSize` (not necessarily `'\0'` terminated).
  326. ##
  327. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  328. result = matchLen(buf, pattern, matches, start, bufSize) != -1
  329. proc find*(buf: cstring, pattern: Regex, matches: var openArray[string],
  330. start = 0, bufSize: int): int =
  331. ## returns the starting position of `pattern` in `buf` and the captured
  332. ## substrings in the array `matches`. If it does not match, nothing
  333. ## is written into `matches` and `-1` is returned.
  334. ## `buf` has length `bufSize` (not necessarily `'\0'` terminated).
  335. ##
  336. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  337. var
  338. rtarray = initRtArray[csize_t]((matches.len+1)*3)
  339. rawMatches = rtarray.getRawData
  340. var matchData = match_data_create_from_pattern(pattern.h, nil)
  341. defer: match_data_free(matchData)
  342. var res = pcre2.match(pattern.h, buf, bufSize.csize_t, start.csize_t, 0'u32,
  343. matchData, nil)
  344. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  345. if res < 0'i32: return res
  346. for i in 1..int(res)-1:
  347. var a = rawMatches[i * 2]
  348. var b = rawMatches[i * 2 + 1]
  349. if a != UNSET: matches[i-1] = bufSubstr(buf, int(a), int(b))
  350. else: matches[i-1] = ""
  351. return rawMatches[0].int
  352. proc find*(s: string, pattern: Regex, matches: var openArray[string],
  353. start = 0): int {.inline.} =
  354. ## returns the starting position of `pattern` in `s` and the captured
  355. ## substrings in the array `matches`. If it does not match, nothing
  356. ## is written into `matches` and `-1` is returned.
  357. ##
  358. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  359. result = find(cstring(s), pattern, matches, start, s.len)
  360. proc find*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int =
  361. ## returns the starting position of `pattern` in `buf`,
  362. ## where `buf` has length `bufSize` (not necessarily `'\0'` terminated).
  363. ## If it does not match, `-1` is returned.
  364. var
  365. rtarray = initRtArray[csize_t](3)
  366. rawMatches = rtarray.getRawData
  367. var matchData = match_data_create_from_pattern(pattern.h, nil)
  368. defer: match_data_free(matchData)
  369. var res = pcre2.match(pattern.h, buf, bufSize.csize_t, start.csize_t, 0'u32,
  370. matchData, nil)
  371. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  372. if res < 0'i32: return res
  373. return rawMatches[0].int
  374. proc find*(s: string, pattern: Regex, start = 0): int {.inline.} =
  375. ## returns the starting position of `pattern` in `s`. If it does not
  376. ## match, `-1` is returned. We start the scan at `start`.
  377. runnableExamples:
  378. doAssert find("abcdefg", re"cde") == 2
  379. doAssert find("abcdefg", re"abc") == 0
  380. doAssert find("abcdefg", re"zz") == -1 # not found
  381. doAssert find("abcdefg", re"cde", start = 2) == 2 # still 2
  382. doAssert find("abcdefg", re"cde", start = 3) == -1 # we're past the start position
  383. doAssert find("xabc", re"(?<=x|y)abc", start = 1) == 1
  384. # lookbehind assertion `(?<=x|y)` can look behind `start`
  385. result = find(cstring(s), pattern, start, s.len)
  386. iterator findAll*(s: string, pattern: Regex, start = 0): string =
  387. ## Yields all matching *substrings* of `s` that match `pattern`.
  388. ##
  389. ## Note that since this is an iterator you should not modify the string you
  390. ## are iterating over: bad things could happen.
  391. var
  392. i = start
  393. rtarray = initRtArray[csize_t](3)
  394. rawMatches = rtarray.getRawData
  395. var matchData = match_data_create_from_pattern(pattern.h, nil)
  396. defer: match_data_free(matchData)
  397. while true:
  398. let res = pcre2.match(pattern.h, s.cstring, len(s).csize_t, i.csize_t, 0'u32,
  399. matchData, nil)
  400. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  401. if res < 0'i32: break
  402. let a = rawMatches[0]
  403. let b = rawMatches[1]
  404. if a == b and a.int == i: break
  405. yield substr(s, int(a), int(b)-1)
  406. i = b.int
  407. iterator findAll*(buf: cstring, pattern: Regex, start = 0, bufSize: int): string =
  408. ## Yields all matching `substrings` of `s` that match `pattern`.
  409. ##
  410. ## Note that since this is an iterator you should not modify the string you
  411. ## are iterating over: bad things could happen.
  412. var
  413. i = int32(start)
  414. rtarray = initRtArray[csize_t](3)
  415. rawMatches = rtarray.getRawData
  416. var matchData = match_data_create_from_pattern(pattern.h, nil)
  417. defer: match_data_free(matchData)
  418. while true:
  419. let res = pcre2.match(pattern.h, buf, bufSize.csize_t, i.csize_t, 0'u32,
  420. matchData, nil)
  421. rawMatches = cast[ptr UncheckedArray[csize_t]](get_ovector_pointer(matchData))
  422. if res < 0'i32: break
  423. let a = rawMatches[0]
  424. let b = rawMatches[1]
  425. if a == b and a.int == i: break
  426. var str = newString(b-a)
  427. copyMem(str[0].addr, unsafeAddr(buf[a]), b-a)
  428. yield str
  429. i = b.int32
  430. proc findAll*(s: string, pattern: Regex, start = 0): seq[string] {.inline.} =
  431. ## returns all matching `substrings` of `s` that match `pattern`.
  432. ## If it does not match, `@[]` is returned.
  433. result = @[]
  434. for x in findAll(s, pattern, start): result.add x
  435. template `=~` *(s: string, pattern: Regex): untyped =
  436. ## This calls `match` with an implicit declared `matches` array that
  437. ## can be used in the scope of the `=~` call:
  438. runnableExamples:
  439. proc parse(line: string): string =
  440. if line =~ re"\s*(\w+)\s*\=\s*(\w+)": # matches a key=value pair:
  441. result = $(matches[0], matches[1])
  442. elif line =~ re"\s*(\#.*)": # matches a comment
  443. # note that the implicit `matches` array is different from 1st branch
  444. result = $(matches[0],)
  445. else: raiseAssert "unreachable"
  446. doAssert not declared(matches)
  447. doAssert parse("NAME = LENA") == """("NAME", "LENA")"""
  448. doAssert parse(" # comment ... ") == """("# comment ... ",)"""
  449. bind MaxSubpatterns
  450. when not declaredInScope(matches):
  451. var matches {.inject.}: array[MaxSubpatterns, string]
  452. match(s, pattern, matches)
  453. # ------------------------- more string handling ------------------------------
  454. proc contains*(s: string, pattern: Regex, start = 0): bool {.inline.} =
  455. ## same as `find(s, pattern, start) >= 0`
  456. return find(s, pattern, start) >= 0
  457. proc contains*(s: string, pattern: Regex, matches: var openArray[string],
  458. start = 0): bool {.inline.} =
  459. ## same as `find(s, pattern, matches, start) >= 0`
  460. ##
  461. ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
  462. return find(s, pattern, matches, start) >= 0
  463. proc startsWith*(s: string, prefix: Regex): bool {.inline.} =
  464. ## returns true if `s` starts with the pattern `prefix`
  465. result = matchLen(s, prefix) >= 0
  466. proc endsWith*(s: string, suffix: Regex): bool {.inline.} =
  467. ## returns true if `s` ends with the pattern `suffix`
  468. for i in 0 .. s.len-1:
  469. if matchLen(s, suffix, i) == s.len - i: return true
  470. proc replace*(s: string, sub: Regex, by = ""): string =
  471. ## Replaces `sub` in `s` by the string `by`. Captures cannot be
  472. ## accessed in `by`.
  473. runnableExamples:
  474. doAssert "var1=key; var2=key2".replace(re"(\w+)=(\w+)") == "; "
  475. doAssert "var1=key; var2=key2".replace(re"(\w+)=(\w+)", "?") == "?; ?"
  476. result = ""
  477. var prev = 0
  478. var flags = 0'u32
  479. while prev < s.len:
  480. var match = findBoundsImpl(s.cstring, sub, prev, s.len, flags)
  481. flags = 0
  482. if match.first < 0: break
  483. add(result, substr(s, prev, match.first-1))
  484. add(result, by)
  485. if match.first > match.last:
  486. # 0-len match
  487. flags = pcre2.NOTEMPTY_ATSTART
  488. prev = match.last + 1
  489. add(result, substr(s, prev))
  490. proc replacef*(s: string, sub: Regex, by: string): string =
  491. ## Replaces `sub` in `s` by the string `by`. Captures can be accessed in `by`
  492. ## with the notation `$i` and `$#` (see strutils.\`%\`).
  493. runnableExamples:
  494. doAssert "var1=key; var2=key2".replacef(re"(\w+)=(\w+)", "$1<-$2$2") ==
  495. "var1<-keykey; var2<-key2key2"
  496. result = ""
  497. var caps: array[MaxSubpatterns, string]
  498. var prev = 0
  499. while prev < s.len:
  500. var match = findBounds(s, sub, caps, prev)
  501. if match.first < 0: break
  502. add(result, substr(s, prev, match.first-1))
  503. addf(result, by, caps)
  504. if match.last + 1 == prev: break
  505. prev = match.last + 1
  506. add(result, substr(s, prev))
  507. proc multiReplace*(s: string, subs: openArray[
  508. tuple[pattern: Regex, repl: string]]): string =
  509. ## Returns a modified copy of `s` with the substitutions in `subs`
  510. ## applied in parallel.
  511. result = ""
  512. var i = 0
  513. var caps: array[MaxSubpatterns, string]
  514. while i < s.len:
  515. block searchSubs:
  516. for j in 0..high(subs):
  517. var x = matchLen(s, subs[j][0], caps, i)
  518. if x > 0:
  519. addf(result, subs[j][1], caps)
  520. inc(i, x)
  521. break searchSubs
  522. add(result, s[i])
  523. inc(i)
  524. # copy the rest:
  525. add(result, substr(s, i))
  526. proc transformFile*(infile, outfile: string,
  527. subs: openArray[tuple[pattern: Regex, repl: string]]) =
  528. ## reads in the file `infile`, performs a parallel replacement (calls
  529. ## `parallelReplace`) and writes back to `outfile`. Raises `IOError` if an
  530. ## error occurs. This is supposed to be used for quick scripting.
  531. var x = readFile(infile)
  532. writeFile(outfile, x.multiReplace(subs))
  533. iterator split*(s: string, sep: Regex; maxsplit = -1): string =
  534. ## Splits the string `s` into substrings.
  535. ##
  536. ## Substrings are separated by the regular expression `sep`
  537. ## (and the portion matched by `sep` is not returned).
  538. runnableExamples:
  539. import std/sequtils
  540. doAssert toSeq(split("00232this02939is39an22example111", re"\d+")) ==
  541. @["", "this", "is", "an", "example", ""]
  542. var last = 0
  543. var splits = maxsplit
  544. var x = -1
  545. if len(s) == 0:
  546. last = 1
  547. if matchLen(s, sep, 0) == 0:
  548. x = 0
  549. while last <= len(s):
  550. var first = last
  551. var sepLen = 1
  552. if x == 0:
  553. inc(last)
  554. while last < len(s):
  555. x = matchLen(s, sep, last)
  556. if x >= 0:
  557. sepLen = x
  558. break
  559. inc(last)
  560. if splits == 0: last = len(s)
  561. yield substr(s, first, last-1)
  562. if splits == 0: break
  563. dec(splits)
  564. inc(last, sepLen)
  565. proc split*(s: string, sep: Regex, maxsplit = -1): seq[string] {.inline.} =
  566. ## Splits the string `s` into a seq of substrings.
  567. ##
  568. ## The portion matched by `sep` is not returned.
  569. result = @[]
  570. for x in split(s, sep, maxsplit): result.add x
  571. proc escapeRe*(s: string): string =
  572. ## escapes `s` so that it is matched verbatim when used as a regular
  573. ## expression.
  574. result = ""
  575. for c in items(s):
  576. case c
  577. of 'a'..'z', 'A'..'Z', '0'..'9', '_':
  578. result.add(c)
  579. else:
  580. result.add("\\x")
  581. result.add(toHex(ord(c), 2))