re.nim 24 KB

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