nre.nim 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. #
  2. # Nim's Runtime Library
  3. # (c) Copyright 2015 Nim Contributors
  4. #
  5. # See the file "copying.txt", included in this
  6. # distribution, for details about the copyright.
  7. #
  8. from pcre import nil
  9. import nre/private/util
  10. import tables
  11. from strutils import `%`
  12. from math import ceil
  13. import options
  14. from unicode import runeLenAt
  15. export options
  16. ## What is NRE?
  17. ## ============
  18. ##
  19. ## A regular expression library for Nim using PCRE to do the hard work.
  20. ##
  21. ## For documentation on how to write patterns, there exists `the official PCRE
  22. ## pattern documentation
  23. ## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_. You can also
  24. ## search the internet for a wide variety of third-party documentation and
  25. ## tools.
  26. ##
  27. ## **Note**: If you love ``sequtils.toSeq`` we have bad news for you. This
  28. ## library doesn't work with it due to documented compiler limitations. As
  29. ## a workaround, use this:
  30. ##
  31. ## .. code-block:: nim
  32. ##
  33. ## import nre except toSeq
  34. ##
  35. ##
  36. ## Licencing
  37. ## ---------
  38. ##
  39. ## PCRE has `some additional terms`_ that you must agree to in order to use
  40. ## this module.
  41. ##
  42. ## .. _`some additional terms`: http://pcre.sourceforge.net/license.txt
  43. ##
  44. runnableExamples:
  45. let vowels = re"[aeoui]"
  46. let expectedResults = [
  47. 1 .. 1,
  48. 2 .. 2,
  49. 4 .. 4,
  50. 6 .. 6,
  51. 7 .. 7,
  52. ]
  53. var i = 0
  54. for match in "moigagoo".findIter(vowels):
  55. doAssert match.matchBounds == expectedResults[i]
  56. inc i
  57. let firstVowel = "foo".find(vowels)
  58. let hasVowel = firstVowel.isSome()
  59. if hasVowel:
  60. let matchBounds = firstVowel.get().captureBounds[-1]
  61. doAssert matchBounds.a == 1
  62. # Type definitions {{{
  63. type
  64. Regex* = ref object
  65. ## Represents the pattern that things are matched against, constructed with
  66. ## ``re(string)``. Examples: ``re"foo"``, ``re(r"(*ANYCRLF)(?x)foo #
  67. ## comment".``
  68. ##
  69. ## ``pattern: string``
  70. ## the string that was used to create the pattern. For details on how
  71. ## to write a pattern, please see `the official PCRE pattern
  72. ## documentation.
  73. ## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_
  74. ##
  75. ## ``captureCount: int``
  76. ## the number of captures that the pattern has.
  77. ##
  78. ## ``captureNameId: Table[string, int]``
  79. ## a table from the capture names to their numeric id.
  80. ##
  81. ##
  82. ## Options
  83. ## .......
  84. ##
  85. ## The following options may appear anywhere in the pattern, and they affect
  86. ## the rest of it.
  87. ##
  88. ## - ``(?i)`` - case insensitive
  89. ## - ``(?m)`` - multi-line: ``^`` and ``$`` match the beginning and end of
  90. ## lines, not of the subject string
  91. ## - ``(?s)`` - ``.`` also matches newline (*dotall*)
  92. ## - ``(?U)`` - expressions are not greedy by default. ``?`` can be added
  93. ## to a qualifier to make it greedy
  94. ## - ``(?x)`` - whitespace and comments (``#``) are ignored (*extended*)
  95. ## - ``(?X)`` - character escapes without special meaning (``\w`` vs.
  96. ## ``\a``) are errors (*extra*)
  97. ##
  98. ## One or a combination of these options may appear only at the beginning
  99. ## of the pattern:
  100. ##
  101. ## - ``(*UTF8)`` - treat both the pattern and subject as UTF-8
  102. ## - ``(*UCP)`` - Unicode character properties; ``\w`` matches ``я``
  103. ## - ``(*U)`` - a combination of the two options above
  104. ## - ``(*FIRSTLINE*)`` - fails if there is not a match on the first line
  105. ## - ``(*NO_AUTO_CAPTURE)`` - turn off auto-capture for groups;
  106. ## ``(?<name>...)`` can be used to capture
  107. ## - ``(*CR)`` - newlines are separated by ``\r``
  108. ## - ``(*LF)`` - newlines are separated by ``\n`` (UNIX default)
  109. ## - ``(*CRLF)`` - newlines are separated by ``\r\n`` (Windows default)
  110. ## - ``(*ANYCRLF)`` - newlines are separated by any of the above
  111. ## - ``(*ANY)`` - newlines are separated by any of the above and Unicode
  112. ## newlines:
  113. ##
  114. ## single characters VT (vertical tab, U+000B), FF (form feed, U+000C),
  115. ## NEL (next line, U+0085), LS (line separator, U+2028), and PS
  116. ## (paragraph separator, U+2029). For the 8-bit library, the last two
  117. ## are recognized only in UTF-8 mode.
  118. ## — man pcre
  119. ##
  120. ## - ``(*JAVASCRIPT_COMPAT)`` - JavaScript compatibility
  121. ## - ``(*NO_STUDY)`` - turn off studying; study is enabled by default
  122. ##
  123. ## For more details on the leading option groups, see the `Option
  124. ## Setting <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#OPTION_SETTING>`_
  125. ## and the `Newline
  126. ## Convention <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#NEWLINE_CONVENTION>`_
  127. ## sections of the `PCRE syntax
  128. ## manual <http://man7.org/linux/man-pages/man3/pcresyntax.3.html>`_.
  129. ##
  130. ## Some of these options are not part of PCRE and are converted by nre
  131. ## into PCRE flags. These include ``NEVER_UTF``, ``ANCHORED``,
  132. ## ``DOLLAR_ENDONLY``, ``FIRSTLINE``, ``NO_AUTO_CAPTURE``,
  133. ## ``JAVASCRIPT_COMPAT``, ``U``, ``NO_STUDY``. In other PCRE wrappers, you
  134. ## will need to pass these as seperate flags to PCRE.
  135. pattern*: string ## not nil
  136. pcreObj: ptr pcre.Pcre ## not nil
  137. pcreExtra: ptr pcre.ExtraData ## nil
  138. captureNameToId: Table[string, int]
  139. RegexMatch* = object
  140. ## Usually seen as Option[RegexMatch], it represents the result of an
  141. ## execution. On failure, it is none, on success, it is some.
  142. ##
  143. ## ``pattern: Regex``
  144. ## the pattern that is being matched
  145. ##
  146. ## ``str: string``
  147. ## the string that was matched against
  148. ##
  149. ## ``captures[]: string``
  150. ## the string value of whatever was captured at that id. If the value
  151. ## is invalid, then behavior is undefined. If the id is ``-1``, then
  152. ## the whole match is returned. If the given capture was not matched,
  153. ## ``nil`` is returned.
  154. ##
  155. ## - ``"abc".match(re"(\w)").get.captures[0] == "a"``
  156. ## - ``"abc".match(re"(?<letter>\w)").get.captures["letter"] == "a"``
  157. ## - ``"abc".match(re"(\w)\w").get.captures[-1] == "ab"``
  158. ##
  159. ## ``captureBounds[]: HSlice[int, int]``
  160. ## gets the bounds of the given capture according to the same rules as
  161. ## the above. If the capture is not filled, then ``None`` is returned.
  162. ## The bounds are both inclusive.
  163. ##
  164. ## - ``"abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0``
  165. ## - ``0 in "abc".match(re"(\w)").get.captureBounds == true``
  166. ## - ``"abc".match(re"").get.captureBounds[-1] == 0 .. -1``
  167. ## - ``"abc".match(re"abc").get.captureBounds[-1] == 0 .. 2``
  168. ##
  169. ## ``match: string``
  170. ## the full text of the match.
  171. ##
  172. ## ``matchBounds: HSlice[int, int]``
  173. ## the bounds of the match, as in ``captureBounds[]``
  174. ##
  175. ## ``(captureBounds|captures).toTable``
  176. ## returns a table with each named capture as a key.
  177. ##
  178. ## ``(captureBounds|captures).toSeq``
  179. ## returns all the captures by their number.
  180. ##
  181. ## ``$: string``
  182. ## same as ``match``
  183. pattern*: Regex ## The regex doing the matching.
  184. ## Not nil.
  185. str*: string ## The string that was matched against.
  186. ## Not nil.
  187. pcreMatchBounds: seq[HSlice[cint, cint]] ## First item is the bounds of the match
  188. ## Other items are the captures
  189. ## `a` is inclusive start, `b` is exclusive end
  190. Captures* = distinct RegexMatch
  191. CaptureBounds* = distinct RegexMatch
  192. RegexError* = ref object of Exception
  193. RegexInternalError* = ref object of RegexError
  194. ## Internal error in the module, this probably means that there is a bug
  195. InvalidUnicodeError* = ref object of RegexError
  196. ## Thrown when matching fails due to invalid unicode in strings
  197. pos*: int ## the location of the invalid unicode in bytes
  198. SyntaxError* = ref object of RegexError
  199. ## Thrown when there is a syntax error in the
  200. ## regular expression string passed in
  201. pos*: int ## the location of the syntax error in bytes
  202. pattern*: string ## the pattern that caused the problem
  203. StudyError* = ref object of RegexError
  204. ## Thrown when studying the regular expression failes
  205. ## for whatever reason. The message contains the error
  206. ## code.
  207. runnableExamples:
  208. # This MUST be kept in sync with the examples in RegexMatch
  209. doAssert "abc".match(re"(\w)").get.captures[0] == "a"
  210. doAssert "abc".match(re"(?<letter>\w)").get.captures["letter"] == "a"
  211. doAssert "abc".match(re"(\w)\w").get.captures[-1] == "ab"
  212. doAssert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0
  213. doAssert 0 in "abc".match(re"(\w)").get.captureBounds == true
  214. doAssert "abc".match(re"").get.captureBounds[-1] == 0 .. -1
  215. doAssert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2
  216. # }}}
  217. proc getinfo[T](pattern: Regex, opt: cint): T =
  218. let retcode = pcre.fullinfo(pattern.pcreObj, pattern.pcreExtra, opt, addr result)
  219. if retcode < 0:
  220. # XXX Error message that doesn't expose implementation details
  221. raise newException(FieldError, "Invalid getinfo for $1, errno $2" % [$opt, $retcode])
  222. # Regex accessors {{{
  223. proc captureCount*(pattern: Regex): int =
  224. return getinfo[cint](pattern, pcre.INFO_CAPTURECOUNT)
  225. proc captureNameId*(pattern: Regex): Table[string, int] =
  226. return pattern.captureNameToId
  227. proc matchesCrLf(pattern: Regex): bool =
  228. let flags = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS))
  229. let newlineFlags = flags and (pcre.NEWLINE_CRLF or
  230. pcre.NEWLINE_ANY or
  231. pcre.NEWLINE_ANYCRLF)
  232. if newLineFlags > 0u32:
  233. return true
  234. # get flags from build config
  235. var confFlags: cint
  236. if pcre.config(pcre.CONFIG_NEWLINE, addr confFlags) != 0:
  237. assert(false, "CONFIG_NEWLINE apparently got screwed up")
  238. case confFlags
  239. of 13: return false
  240. of 10: return false
  241. of (13 shl 8) or 10: return true
  242. of -2: return true
  243. of -1: return true
  244. else: return false
  245. # }}}
  246. # Capture accessors {{{
  247. func captureBounds*(pattern: RegexMatch): CaptureBounds = return CaptureBounds(pattern)
  248. func captures*(pattern: RegexMatch): Captures = return Captures(pattern)
  249. func contains*(pattern: CaptureBounds, i: int): bool =
  250. let pattern = RegexMatch(pattern)
  251. pattern.pcreMatchBounds[i + 1].a != -1
  252. func contains*(pattern: Captures, i: int): bool =
  253. i in CaptureBounds(pattern)
  254. func `[]`*(pattern: CaptureBounds, i: int): HSlice[int, int] =
  255. let pattern = RegexMatch(pattern)
  256. if not (i in pattern.captureBounds):
  257. raise newException(IndexError, "Group '" & $i & "' was not captured")
  258. let bounds = pattern.pcreMatchBounds[i + 1]
  259. int(bounds.a)..int(bounds.b-1)
  260. func `[]`*(pattern: Captures, i: int): string =
  261. let pattern = RegexMatch(pattern)
  262. let bounds = pattern.captureBounds[i]
  263. pattern.str.substr(bounds.a, bounds.b)
  264. func match*(pattern: RegexMatch): string =
  265. return pattern.captures[-1]
  266. func matchBounds*(pattern: RegexMatch): HSlice[int, int] =
  267. return pattern.captureBounds[-1]
  268. func contains*(pattern: CaptureBounds, name: string): bool =
  269. let pattern = RegexMatch(pattern)
  270. let nameToId = pattern.pattern.captureNameToId
  271. if not (name in nameToId):
  272. return false
  273. nameToId[name] in pattern.captureBounds
  274. func contains*(pattern: Captures, name: string): bool =
  275. name in CaptureBounds(pattern)
  276. func checkNamedCaptured(pattern: RegexMatch, name: string): void =
  277. if not (name in pattern.captureBounds):
  278. raise newException(KeyError, "Group '" & name & "' was not captured")
  279. func `[]`*(pattern: CaptureBounds, name: string): HSlice[int, int] =
  280. let pattern = RegexMatch(pattern)
  281. checkNamedCaptured(pattern, name)
  282. pattern.captureBounds[pattern.pattern.captureNameToId[name]]
  283. func `[]`*(pattern: Captures, name: string): string =
  284. let pattern = RegexMatch(pattern)
  285. checkNamedCaptured(pattern, name)
  286. return pattern.captures[pattern.pattern.captureNameToId[name]]
  287. template toTableImpl() {.dirty.} =
  288. for key in RegexMatch(pattern).pattern.captureNameId.keys:
  289. if key in pattern:
  290. result[key] = pattern[key]
  291. func toTable*(pattern: Captures): Table[string, string] =
  292. result = initTable[string, string]()
  293. toTableImpl()
  294. func toTable*(pattern: CaptureBounds): Table[string, HSlice[int, int]] =
  295. result = initTable[string, HSlice[int, int]]()
  296. toTableImpl()
  297. template itemsImpl() {.dirty.} =
  298. for i in 0 ..< RegexMatch(pattern).pattern.captureCount:
  299. # done in this roundabout way to avoid multiple yields (potential code
  300. # bloat)
  301. let nextYieldVal = if i in pattern:
  302. some(pattern[i])
  303. else:
  304. default
  305. yield nextYieldVal
  306. iterator items*(pattern: CaptureBounds,
  307. default = none(HSlice[int, int])): Option[HSlice[int, int]] =
  308. itemsImpl()
  309. iterator items*(pattern: Captures,
  310. default: Option[string] = none(string)): Option[string] =
  311. itemsImpl()
  312. proc toSeq*(pattern: CaptureBounds,
  313. default = none(HSlice[int, int])): seq[Option[HSlice[int, int]]] =
  314. accumulateResult(pattern.items(default))
  315. proc toSeq*(pattern: Captures,
  316. default: Option[string] = none(string)): seq[Option[string]] =
  317. accumulateResult(pattern.items(default))
  318. proc `$`*(pattern: RegexMatch): string =
  319. return pattern.captures[-1]
  320. proc `==`*(a, b: Regex): bool =
  321. if not a.isNil and not b.isNil:
  322. return a.pattern == b.pattern and
  323. a.pcreObj == b.pcreObj and
  324. a.pcreExtra == b.pcreExtra
  325. else:
  326. return system.`==`(a, b)
  327. proc `==`*(a, b: RegexMatch): bool =
  328. return a.pattern == b.pattern and
  329. a.str == b.str
  330. # }}}
  331. # Creation & Destruction {{{
  332. # PCRE Options {{{
  333. const PcreOptions = {
  334. "NEVER_UTF": pcre.NEVER_UTF,
  335. "ANCHORED": pcre.ANCHORED,
  336. "DOLLAR_ENDONLY": pcre.DOLLAR_ENDONLY,
  337. "FIRSTLINE": pcre.FIRSTLINE,
  338. "NO_AUTO_CAPTURE": pcre.NO_AUTO_CAPTURE,
  339. "JAVASCRIPT_COMPAT": pcre.JAVASCRIPT_COMPAT,
  340. "U": pcre.UTF8 or pcre.UCP
  341. }.toTable
  342. # Options that are supported inside regular expressions themselves
  343. const SkipOptions = [
  344. "LIMIT_MATCH=", "LIMIT_RECURSION=", "NO_AUTO_POSSESS", "NO_START_OPT",
  345. "UTF8", "UTF16", "UTF32", "UTF", "UCP",
  346. "CR", "LF", "CRLF", "ANYCRLF", "ANY", "BSR_ANYCRLF", "BSR_UNICODE"
  347. ]
  348. proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study: bool] =
  349. result = ("", 0, true)
  350. var optionStart = 0
  351. var equals = false
  352. for i, c in pattern:
  353. if optionStart == i:
  354. if c != '(':
  355. break
  356. optionStart = i
  357. elif optionStart == i-1:
  358. if c != '*':
  359. break
  360. elif c == ')':
  361. let name = pattern[optionStart+2 .. i-1]
  362. if equals or name in SkipOptions:
  363. result.pattern.add pattern[optionStart .. i]
  364. elif PcreOptions.hasKey name:
  365. result.flags = result.flags or PcreOptions[name]
  366. elif name == "NO_STUDY":
  367. result.study = false
  368. else:
  369. break
  370. optionStart = i+1
  371. equals = false
  372. elif not equals:
  373. if c == '=':
  374. equals = true
  375. if pattern[optionStart+2 .. i] notin SkipOptions:
  376. break
  377. elif c notin {'A'..'Z', '0'..'9', '_'}:
  378. break
  379. result.pattern.add pattern[optionStart .. pattern.high]
  380. # }}}
  381. proc destroyRegex(pattern: Regex) =
  382. pcre.free_substring(cast[cstring](pattern.pcreObj))
  383. pattern.pcreObj = nil
  384. if pattern.pcreExtra != nil:
  385. pcre.free_study(pattern.pcreExtra)
  386. proc getNameToNumberTable(pattern: Regex): Table[string, int] =
  387. let entryCount = getinfo[cint](pattern, pcre.INFO_NAMECOUNT)
  388. let entrySize = getinfo[cint](pattern, pcre.INFO_NAMEENTRYSIZE)
  389. let table = cast[ptr UncheckedArray[uint8]](
  390. getinfo[int](pattern, pcre.INFO_NAMETABLE))
  391. result = initTable[string, int]()
  392. for i in 0 ..< entryCount:
  393. let pos = i * entrySize
  394. let num = (int(table[pos]) shl 8) or int(table[pos + 1]) - 1
  395. var name = ""
  396. var idx = 2
  397. while table[pos + idx] != 0:
  398. name.add(char(table[pos + idx]))
  399. idx += 1
  400. result[name] = num
  401. proc initRegex(pattern: string, flags: int, study = true): Regex =
  402. new(result, destroyRegex)
  403. result.pattern = pattern
  404. var errorMsg: cstring
  405. var errOffset: cint
  406. result.pcreObj = pcre.compile(cstring(pattern),
  407. # better hope int is at least 4 bytes..
  408. cint(flags), addr errorMsg,
  409. addr errOffset, nil)
  410. if result.pcreObj == nil:
  411. # failed to compile
  412. raise SyntaxError(msg: $errorMsg, pos: errOffset, pattern: pattern)
  413. if study:
  414. var options: cint = 0
  415. var hasJit: cint
  416. if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0:
  417. if hasJit == 1'i32:
  418. options = pcre.STUDY_JIT_COMPILE
  419. result.pcreExtra = pcre.study(result.pcreObj, options, addr errorMsg)
  420. if errorMsg != nil:
  421. raise StudyError(msg: $errorMsg)
  422. result.captureNameToId = result.getNameToNumberTable()
  423. proc re*(pattern: string): Regex =
  424. let (pattern, flags, study) = extractOptions(pattern)
  425. initRegex(pattern, flags, study)
  426. # }}}
  427. # Operations {{{
  428. proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Option[RegexMatch] =
  429. var myResult = RegexMatch(pattern : pattern, str : str)
  430. # See PCRE man pages.
  431. # 2x capture count to make room for start-end pairs
  432. # 1x capture count as slack space for PCRE
  433. let vecsize = (pattern.captureCount() + 1) * 3
  434. # div 2 because each element is 2 cints long
  435. # plus 1 because we need the ceiling, not the floor
  436. myResult.pcreMatchBounds = newSeq[HSlice[cint, cint]]((vecsize + 1) div 2)
  437. myResult.pcreMatchBounds.setLen(vecsize div 3)
  438. let strlen = if endpos == int.high: str.len else: endpos+1
  439. doAssert(strlen <= str.len) # don't want buffer overflows
  440. let execRet = pcre.exec(pattern.pcreObj,
  441. pattern.pcreExtra,
  442. cstring(str),
  443. cint(strlen),
  444. cint(start),
  445. cint(flags),
  446. cast[ptr cint](addr myResult.pcreMatchBounds[0]),
  447. cint(vecsize))
  448. if execRet >= 0:
  449. return some(myResult)
  450. case execRet:
  451. of pcre.ERROR_NOMATCH:
  452. return none(RegexMatch)
  453. of pcre.ERROR_NULL:
  454. raise newException(AccessViolationError, "Expected non-null parameters")
  455. of pcre.ERROR_BADOPTION:
  456. raise RegexInternalError(msg : "Unknown pattern flag. Either a bug or " &
  457. "outdated PCRE.")
  458. of pcre.ERROR_BADUTF8, pcre.ERROR_SHORTUTF8, pcre.ERROR_BADUTF8_OFFSET:
  459. raise InvalidUnicodeError(msg : "Invalid unicode byte sequence",
  460. pos : myResult.pcreMatchBounds[0].a)
  461. else:
  462. raise RegexInternalError(msg : "Unknown internal error: " & $execRet)
  463. proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
  464. ## Like ```find(...)`` <#proc-find>`_, but anchored to the start of the
  465. ## string.
  466. ##
  467. runnableExamples:
  468. doAssert "foo".match(re"f").isSome
  469. doAssert "foo".match(re"o").isNone
  470. return str.matchImpl(pattern, start, endpos, pcre.ANCHORED)
  471. iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch =
  472. ## Works the same as ```find(...)`` <#proc-find>`_, but finds every
  473. ## non-overlapping match. ``"2222".find(re"22")`` is ``"22", "22"``, not
  474. ## ``"22", "22", "22"``.
  475. ##
  476. ## Arguments are the same as ```find(...)`` <#proc-find>`_
  477. ##
  478. ## Variants:
  479. ##
  480. ## - ``proc findAll(...)`` returns a ``seq[string]``
  481. # see pcredemo for explanation
  482. let matchesCrLf = pattern.matchesCrLf()
  483. let unicode = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS) and
  484. pcre.UTF8) > 0u32
  485. let strlen = if endpos == int.high: str.len else: endpos+1
  486. var offset = start
  487. var match: Option[RegexMatch]
  488. var neverMatched = true
  489. while true:
  490. var flags = 0
  491. if match.isSome and
  492. match.get.matchBounds.a > match.get.matchBounds.b:
  493. # 0-len match
  494. flags = pcre.NOTEMPTY_ATSTART
  495. match = str.matchImpl(pattern, offset, endpos, flags)
  496. if match.isNone:
  497. # either the end of the input or the string
  498. # cannot be split here - we also need to bail
  499. # if we've never matched and we've already tried to...
  500. if offset >= strlen or neverMatched:
  501. break
  502. if matchesCrLf and offset < (str.len - 1) and
  503. str[offset] == '\r' and str[offset + 1] == '\L':
  504. # if PCRE treats CrLf as newline, skip both at the same time
  505. offset += 2
  506. elif unicode:
  507. # XXX what about invalid unicode?
  508. offset += str.runeLenAt(offset)
  509. assert(offset <= strlen)
  510. else:
  511. offset += 1
  512. else:
  513. neverMatched = false
  514. offset = match.get.matchBounds.b + 1
  515. yield match.get
  516. proc find*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
  517. ## Finds the given pattern in the string between the end and start
  518. ## positions.
  519. ##
  520. ## ``start``
  521. ## The start point at which to start matching. ``|abc`` is ``0``;
  522. ## ``a|bc`` is ``1``
  523. ##
  524. ## ``endpos``
  525. ## The maximum index for a match; ``int.high`` means the end of the
  526. ## string, otherwise it’s an inclusive upper bound.
  527. return str.matchImpl(pattern, start, endpos, 0)
  528. proc findAll*(str: string, pattern: Regex, start = 0, endpos = int.high): seq[string] =
  529. result = @[]
  530. for match in str.findIter(pattern, start, endpos):
  531. result.add(match.match)
  532. proc contains*(str: string, pattern: Regex, start = 0, endpos = int.high): bool =
  533. ## Determine if the string contains the given pattern between the end and
  534. ## start positions:
  535. ## This function is equivalent to ``isSome(str.find(pattern, start, endpos))``.
  536. ##
  537. runnableExamples:
  538. doAssert "abc".contains(re"bc") == true
  539. doAssert "abc".contains(re"cd") == false
  540. doAssert "abc".contains(re"a", start = 1) == false
  541. return isSome(str.find(pattern, start, endpos))
  542. proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string] =
  543. ## Splits the string with the given regex. This works according to the
  544. ## rules that Perl and Javascript use.
  545. ##
  546. ## ``start`` behaves the same as in ```find(...)`` <#proc-find>`_.
  547. ##
  548. runnableExamples:
  549. # - If the match is zero-width, then the string is still split:
  550. doAssert "123".split(re"") == @["1", "2", "3"]
  551. # - If the pattern has a capture in it, it is added after the string
  552. # split:
  553. doAssert "12".split(re"(\d)") == @["", "1", "", "2", ""]
  554. # - If ``maxsplit != -1``, then the string will only be split
  555. # ``maxsplit - 1`` times. This means that there will be ``maxsplit``
  556. # strings in the output seq.
  557. doAssert "1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"]
  558. result = @[]
  559. var lastIdx = start
  560. var splits = 0
  561. var bounds = 0 .. -1
  562. var never_ran = true
  563. for match in str.findIter(pattern, start = start):
  564. never_ran = false
  565. # bounds are inclusive:
  566. #
  567. # 0123456
  568. # ^^^
  569. # (1, 3)
  570. bounds = match.matchBounds
  571. # "12".split("") would be @["", "1", "2"], but
  572. # if we skip an empty first match, it's the correct
  573. # @["1", "2"]
  574. if bounds.a <= bounds.b or bounds.a > start:
  575. result.add(str.substr(lastIdx, bounds.a - 1))
  576. splits += 1
  577. lastIdx = bounds.b + 1
  578. for cap in match.captures:
  579. # if there are captures, include them in the result
  580. if cap.isSome:
  581. result.add(cap.get)
  582. if splits == maxSplit - 1:
  583. break
  584. # "12".split("\b") would be @["1", "2", ""], but
  585. # if we skip an empty last match, it's the correct
  586. # @["1", "2"]
  587. # If matches were never found, then the input string is the result
  588. if bounds.a <= bounds.b or bounds.b < str.high or never_ran:
  589. # last match: Each match takes the previous substring,
  590. # but "1 2".split(/ /) needs to return @["1", "2"].
  591. # This handles "2"
  592. result.add(str.substr(bounds.b + 1, str.high))
  593. template replaceImpl(str: string, pattern: Regex,
  594. replacement: untyped) {.dirty.} =
  595. # XXX seems very similar to split, maybe I can reduce code duplication
  596. # somehow?
  597. result = ""
  598. var lastIdx = 0
  599. for match {.inject.} in str.findIter(pattern):
  600. let bounds = match.matchBounds
  601. result.add(str.substr(lastIdx, bounds.a - 1))
  602. let nextVal = replacement
  603. result.add(nextVal)
  604. lastIdx = bounds.b + 1
  605. result.add(str.substr(lastIdx, str.len - 1))
  606. return result
  607. proc replace*(str: string, pattern: Regex,
  608. subproc: proc (match: RegexMatch): string): string =
  609. ## Replaces each match of Regex in the string with ``subproc``, which should
  610. ## never be or return ``nil``.
  611. ##
  612. ## If ``subproc`` is a ``proc (RegexMatch): string``, then it is executed with
  613. ## each match and the return value is the replacement value.
  614. ##
  615. ## If ``subproc`` is a ``proc (string): string``, then it is executed with the
  616. ## full text of the match and and the return value is the replacement
  617. ## value.
  618. ##
  619. ## If ``subproc`` is a string, the syntax is as follows:
  620. ##
  621. ## - ``$$`` - literal ``$``
  622. ## - ``$123`` - capture number ``123``
  623. ## - ``$foo`` - named capture ``foo``
  624. ## - ``${foo}`` - same as above
  625. ## - ``$1$#`` - first and second captures
  626. ## - ``$#`` - first capture
  627. ## - ``$0`` - full match
  628. ##
  629. ## If a given capture is missing, ``IndexError`` thrown for un-named captures
  630. ## and ``KeyError`` for named captures.
  631. replaceImpl(str, pattern, subproc(match))
  632. proc replace*(str: string, pattern: Regex,
  633. subproc: proc (match: string): string): string =
  634. replaceImpl(str, pattern, subproc(match.match))
  635. proc replace*(str: string, pattern: Regex, sub: string): string =
  636. # - 1 because the string numbers are 0-indexed
  637. replaceImpl(str, pattern,
  638. formatStr(sub, match.captures[name], match.captures[id - 1]))
  639. # }}}
  640. let SpecialCharMatcher = re"([\\+*?[^\]$(){}=!<>|:-])"
  641. proc escapeRe*(str: string): string =
  642. ## Escapes the string so it doesn’t match any special characters.
  643. ## Incompatible with the Extra flag (``X``).
  644. str.replace(SpecialCharMatcher, "\\$1")