from std/sequtils import map

import ../src/regex

{.push warning[Deprecated]: off.}

template test(desc: string, body: untyped): untyped =
  when defined(runTestAtCT):
    static:
      (proc() =
        echo "[CT] " & desc
        body)()
  else:
    (proc() =
      when defined(forceRegexAtRuntime):
        echo "[RT] " & desc
      else:
        echo "[CT/RT] " & desc
      body)()

template check(condition: bool) =
  doAssert(condition)

template expect(exception: typedesc, body: untyped): untyped =
  doAssertRaises(exception):
    body

when defined(forceRegexAtRuntime):
  proc isMatch(s: string, pattern: Regex): bool =
    var m = RegexMatch()
    result = match(s, pattern, m)
else:
  proc isMatch(s: string, pattern: static Regex): bool =
    var m = RegexMatch()
    result = match(s, pattern, m)

proc raises(pattern: string): bool =
  result = false
  try:
    discard pattern.re()
  except RegexError:
    result = true

proc raisesMsg(pattern: string): string =
  result = ""
  try:
    discard pattern.re()
  except RegexError:
    result = getCurrentExceptionMsg()

proc toStrCaptures(m: RegexMatch, s: string): seq[seq[string]] =
  result = newSeq[seq[string]](m.groupsCount)
  var j = 0
  for i in 0 ..< m.groupsCount:
    result[i] = newSeq[string](m.group(i).len)
    j = 0
    for cbounds in m.group(i):
      result[i][j] = s[cbounds]
      inc j

proc matchWithCapt(s: string, pattern: static Regex): seq[seq[string]] =
  var m = RegexMatch()
  check match(s, pattern, m)
  result = m.toStrCaptures(s)

proc findWithCapt(s: string, pattern: Regex): seq[seq[string]] =
  var m = RegexMatch()
  check find(s, pattern, m)
  result = m.toStrCaptures(s)

func findAllCapt(s: string, reg: Regex): seq[seq[seq[Slice[int]]]] =
  result = map(
    findAll(s, reg),
    func (m: RegexMatch): seq[seq[Slice[int]]] =
      result = newSeq[seq[Slice[int]]]()
      for i in 0 .. m.groupsCount-1:
        result.add m.group(i))

test "tfull_match":
  check "".isMatch(re"")
  check "a".isMatch(re"a")
  check "ab".isMatch(re"(a)b")
  check "aa".isMatch(re"(a)*")
  check "aab".isMatch(re"((a)*b)")
  check "abbbbccccd".isMatch(re"a(b|c)*d")
  check "abbb".isMatch(re"((a)*(b)*)")
  check "abbb".isMatch(re"((a(b)*)*(b)*)")
  check "a".isMatch(re"a|b")
  check "b".isMatch(re"a|b")
  check(not "ab".isMatch(re"a(b|c)*d"))
  check(not "a".isMatch(re"b"))
  check(not "a".isMatch(re""))
  # raw string need double "" instead of \" to escape,
  # this is a Nim thing
  check " \"word\" ".isMatch(re"\s"".*""\s")

test "trepetition_cycle":
  check "**".isMatch(re"\**")
  check "++".isMatch(re"\++")
  check "??".isMatch(re"\?+")
  check "??".isMatch(re"\?*")
  check "?".isMatch(re"\??")
  check "?".isMatch(re"\???")
  check "**".isMatch(re"\**?")
  check "++".isMatch(re"\++?")
  check "??".isMatch(re"\?+?")
  check "??".isMatch(re"\?*?")
  check raises(r"a**")
  check raises(r"a++")
  check raises(r"a*+")
  check raises(r"a+*")
  check raises(r"a?+")
  check raises(r"a?*")
  check raises(r"a??+")
  check raises(r"a??*")
  check raises(r"a*??")
  check raises(r"a+??")
  check raises(r"a???")
  check raises(r"a{1,}*")
  check raises(r"a{1}*")
  check raises(r"a{1,}+")
  check raises(r"a{1}+")
  check raises(r"a{1,}??")
  check raises(r"a{1}??")
  check(not raises(r"a{1,}?"))
  check(not raises(r"a{1}?"))
  check "aaa".isMatch(re"(a*)*")
  check "aaabbbaaa".isMatch(re"((a*|b*))*")
  check raises(r"a*****")
  check raises(r"a*{,}")
  check "aaa".isMatch(re"(a?)*")
  check "aaaa".isMatch(re"((a)*(a)*)*")
  # Same as PCRE "^(a*)*?$"
  check "aa".matchWithCapt(re"(a*)*?") == @[@["aa"]]
  check "aa".matchWithCapt(re"(a*)*?(a*)*?") ==
    @[newSeq[string](), @["aa"]]
  check "".matchWithCapt(re"(a*)*?(a*)*?") ==
    @[newSeq[string](), newSeq[string]()]
  check "aa".matchWithCapt(re"(.*?)") == @[@["aa"]]
  check "aa".matchWithCapt(re"(.*)*") == @[@["aa", ""]]
  check "a".matchWithCapt(re"(a*)*") == @[@["a", ""]]
  check "a".matchWithCapt(re"(a*)*(a*)*") == @[@["a", ""], @[""]]
  check "".matchWithCapt(re"(a*)*") == @[@[""]]
  check "".matchWithCapt(re"(a*)*(a*)*") == @[@[""], @[""]]
  check "a".matchWithCapt(re"(a*)*?") == @[@["a"]]
  check "a".matchWithCapt(re"(a?)*?") == @[@["a"]]
  check "a".matchWithCapt(re"(a*?)*") == @[@["a", ""]]
  check "a".matchWithCapt(re"(a*?)*?") == @[@["a"]]
  check "a".matchWithCapt(re"(a??)*") == @[@["a", ""]]
  check "ab".matchWithCapt(re"(a??)*b") == @[@["a", ""]]
  check "".matchWithCapt(re"(a??)*") == @[@[""]]
  check "a".matchWithCapt(re"(a?)??") == @[@["a"]]
  check "a".matchWithCapt(re"(a*)??") == @[@["a"]]
  check "a".matchWithCapt(re"(a*)+?") == @[@["a"]]
  check "".matchWithCapt(re"(a?)+") == @[@["", ""]]
  check "".matchWithCapt(re"(a?)(a?)*") == @[@[""], @[""]]
  check "a".matchWithCapt(re"(a?)+") == @[@["a", ""]]
  check "".matchWithCapt(re"(a*)+") == @[@["", ""]]
  check "".matchWithCapt(re"(a*)(a*)*") == @[@[""], @[""]]
  check "a".matchWithCapt(re"(a*)+") == @[@["a", ""]]
  check "b".matchWithCapt(re"(a*)+b") == @[@["", ""]]
  check "b".matchWithCapt(re"(a*)+b*") == @[@["", ""]]
  check "ab".matchWithCapt(re"(a*)+b") == @[@["a", ""]]
  check "".matchWithCapt(re"(a?)*") == @[@[""]]
  check "a".matchWithCapt(re"(a?)*") == @[@["a", ""]]
  check "a".matchWithCapt(re"(a?)*(a?)*") == @[@["a", ""], @[""]]
  check "ab".matchWithCapt(re"(a?)*b") == @[@["a", ""]]
  check "".matchWithCapt(re"(a+)*") == @[newSeq[string]()]
  check "a".matchWithCapt(re"(?:a*)*") == newSeq[seq[string]]()
  check "a".matchWithCapt(re"(a?b?)*") == @[@["a", ""]]
  check "".matchWithCapt(re"(a?b?)*") == @[@[""]]
  # same as nre, but python returns ["", ""]
  check findAllBounds("a", re"(a*)+") == @[0 .. 0, 1 .. 0]
  check findAllBounds("a", re"(a*)(a*)*") == @[0 .. 0, 1 .. 0]
  check findAllBounds("", re"(a*)+") == @[0 .. -1]
  # same as Python finditer
  check findAllCapt("a", re"(a*)+") == @[
    @[@[0 .. 0, 1 .. 0]],
    @[@[1 .. 0, 1 .. 0]]]
  check findAllCapt("", re"(a*)+") == @[@[@[0 .. -1, 0 .. -1]]]
  # same as nre, but python returns ["", ""]
  check findAllBounds("a", re"(a*)*") == @[0 .. 0, 1 .. 0]
  check findAllBounds("", re"(a*)*") == @[0 .. -1]
  check findAllCapt("a", re"(a*)*") == @[
    @[@[0 .. 0, 1 .. 0]],
    @[@[1 .. 0]]]
  check findAllCapt("", re"(a*)*") == @[@[@[0 .. -1]]]
  # litopt
  check findAllBounds("@", re"@(a*)*") == @[0 .. 0]
  check findAllBounds("@", re"@(a*)*?") == @[0 .. 0]
  check findAllBounds("@", re"@(a*?)*") == @[0 .. 0]
  check findAllBounds("@", re"@(a*?)*?") == @[0 .. 0]
  check findAllBounds("@", re"@(a*)+") == @[0 .. 0]
  check findAllBounds("@", re"@(a*)+?") == @[0 .. 0]
  check findAllBounds("@", re"@(a*?)+") == @[0 .. 0]
  check findAllBounds("@", re"@(a*?)+?") == @[0 .. 0]
  check findAllBounds("@", re"(a*)*@") == @[0 .. 0]
  check findAllBounds("@", re"(a*)*?@") == @[0 .. 0]
  check findAllBounds("@", re"(a*?)*@") == @[0 .. 0]
  check findAllBounds("@", re"(a*?)*?@") == @[0 .. 0]
  check findAllBounds("@", re"(a*)+@") == @[0 .. 0]
  check findAllBounds("@", re"(a*)+?@") == @[0 .. 0]
  check findAllBounds("@", re"(a*?)+@") == @[0 .. 0]
  check findAllBounds("@", re"(a*?)+?@") == @[0 .. 0]

test "talternations":
  check raises(r"a|?")
  check raises(r"a|?b")
  check raises(r"?|?")
  check raises(r"a|*")
  check raises(r"a|*b")
  check raises(r"a|+")
  check raises(r"a|+b")

test "tcaptures":
  check "ab".matchWithCapt(re"(a)b") == @[@["a"]]
  check "aa".matchWithCapt(re"(a)*") == @[@["a", "a"]]
  check "aab".matchWithCapt(re"((a)*b)") ==
    @[@["aab"], @["a", "a"]]
  check "abbbbccccd".matchWithCapt(re"a(b|c)*d") ==
    @[@["b", "b", "b", "b", "c", "c", "c", "c"]]
  check "abbb".matchWithCapt(re"((a)*(b)*)") ==
    @[@["abbb"], @["a"], @["b", "b", "b"]]
  check "abbb".matchWithCapt(re"((a(b)*)*(b)*)") ==
    @[@["abbb"], @["abbb"], @["b", "b", "b"], @[]]
  check "aa".matchWithCapt(re"(a)+") == @[@["a", "a"]]
  check "abab".matchWithCapt(re"(ab)+") == @[@["ab", "ab"]]
  check "a".matchWithCapt(re"(a)?") == @[@["a"]]
  check "ab".matchWithCapt(re"(ab)?") == @[@["ab"]]
  check "aaabbbaaa".matchWithCapt(re"(a*|b*)*") ==
    @[@["aaa", "bbb", "aaa", ""]]
  check "abab".matchWithCapt(re"(a(b))*") ==
    @[@["ab", "ab"], @["b", "b"]]
  check "aaanasdnasd".matchWithCapt(re"((a)*n?(asd)*)*") ==
    @[@["aaanasd", "nasd", ""], @["a", "a", "a"], @["asd", "asd"]]
  check "aaanasdnasd".matchWithCapt(re"((a)*n?(asd))*") ==
    @[@["aaanasd", "nasd"], @["a", "a", "a"], @["asd", "asd"]]
  check "b".matchWithCapt(re"(a)?b") ==
    @[newSeq[string]()]
  check "ฅa".matchWithCapt(re"(\w)(a)") ==
    @[@["ฅ"], @["a"]]

test "tzero_or_more_op":
  check raisesMsg(r"*") ==
    "Invalid `*` operator, nothing to repeat"
  check raises(r"*abc")
  check(not raises(r"\b*"))

test "tone_or_more_op":
  check "aaaa".isMatch(re"a+")
  check "abb".isMatch(re"ab+")
  check "abaa".isMatch(re"aba+")
  check(not "".isMatch(re"a+"))
  check(not "b".isMatch(re"a+"))
  check(not "aab".isMatch(re"b+"))
  check raisesMsg(r"(+)") ==
    "Invalid `+` operator, nothing to repeat"
  check raises(r"+")
  check raises(r"+abc")
  check(not raises(r"\b+"))

test "tzero_or_one_op":
  check "a".isMatch(re"a?")
  check "".isMatch(re"a?")
  check "a".isMatch(re"ab?")
  check "ab".isMatch(re"ab?")
  check "aba".isMatch(re"ab?a")
  check "aa".isMatch(re"ab?a")
  check(not "aa".isMatch(re"a?"))
  check(not "b".isMatch(re"a?"))
  check(not "abb".isMatch(re"ab?"))
  check raisesMsg(r"?") ==
    "Invalid `?` operator, nothing to make optional"
  check raises(r"?abc")
  check(not raises(r"\b?"))

test "tescape":
  check "(a)".isMatch(re"\(a\)")
  check "a*b".isMatch(re"a\*b")
  check "a*bbb".isMatch(re"a\*b*")
  check "y".isMatch(re"\y")
  check "\\".isMatch(re"\\")
  check "\\\\".isMatch(re"\\\\")

test "talphanum_shorthand":
  check "a".isMatch(re"\w")
  check "abc123".isMatch(re"\w*")
  check "a".matchWithCapt(re"(\w)") == @[@["a"]]

test "tdigit":
  check "1".isMatch(re"\d")
  check "123".isMatch(re"\d*")
  check "۲".isMatch(re"\d")  # Kharosthi numeral
  check(not "⅕".isMatch(re"\d"))

test "twhite_space_shorthand":
  check " ".isMatch(re"\s")
  check "   ".isMatch(re"\s*")
  check " \t\r\f\v".isMatch(re"\s*")
  check "\u20".isMatch(re"\s")  # New Line
  check "\u2028".isMatch(re"\s")  # Line separator

test "talphanum_not_shorthand":
  check(not "a".isMatch(re"\W"))
  check(not "abc123".isMatch(re"\W*"))
  check "!@#".isMatch(re"\W+")

test "tnot_digit":
  check(not "1".isMatch(re"\D"))
  check(not "123".isMatch(re"\D*"))
  check(not "۲".isMatch(re"\D"))  # Kharosthi numeral
  check "⅕".isMatch(re"\D")
  check "!@#".isMatch(re"\D+")
  check "a".isMatch(re"\D")

test "tnot_white_space_shorthand":
  check "asd123!@#".isMatch(re"\S*")
  check(not " ".isMatch(re"\S"))
  check(not "   ".isMatch(re"\S*"))
  check(not "\t".isMatch(re"\S"))
  check(not "\u20".isMatch(re"\S"))
  check(not "\r".isMatch(re"\S"))
  check(not "\f".isMatch(re"\S"))
  check(not "\v".isMatch(re"\S"))
  check(not "\u2028".isMatch(re"\S"))  # Line separator

test "tset":
  check "a".isMatch(re"[a]")
  check "a".isMatch(re"[abc]")
  check "b".isMatch(re"[abc]")
  check "c".isMatch(re"[abc]")
  check(not "d".isMatch(re"[abc]"))
  check "a".isMatch(re"[\w]")
  check "1".isMatch(re"[\w]")
  check "1".isMatch(re"[\d]")
  check "*".isMatch(re"[*]")
  check "*".isMatch(re"[\*]")
  check "*".isMatch(re"[a*]")
  check "a".isMatch(re"[a*]")
  check "a".isMatch(re"[a-z]")
  check "f".isMatch(re"[a-z]")
  check "z".isMatch(re"[a-z]")
  check(not "A".isMatch(re"[a-z]"))
  check "0".isMatch(re"[0-9]")
  check "5".isMatch(re"[0-9]")
  check "9".isMatch(re"[0-9]")
  check(not "a".isMatch(re"[0-9]"))
  check "(".isMatch(re"[()[\]{}]")
  check ")".isMatch(re"[()[\]{}]")
  check "}".isMatch(re"[()[\]{}]")
  check "{".isMatch(re"[()[\]{}]")
  check "[".isMatch(re"[()[\]{}]")
  check "]".isMatch(re"[()[\]{}]")
  check "(".isMatch(re"[]()[{}]")
  check ")".isMatch(re"[]()[{}]")
  check "}".isMatch(re"[]()[{}]")
  check "{".isMatch(re"[]()[{}]")
  check "[".isMatch(re"[]()[{}]")
  check "]".isMatch(re"[]()[{}]")
  check "\\".isMatch(re"[\\]")
  check "\\".isMatch(re"[\\\]]")
  check "]".isMatch(re"[\\\]]")
  check "00".isMatch(re"[0-5][0-9]")
  check "59".isMatch(re"[0-5][0-9]")
  check(not "95".isMatch(re"[0-5][0-9]"))
  check "1".isMatch(re"[0-57-9]")
  check "8".isMatch(re"[0-57-9]")
  check(not "6".isMatch(re"[0-57-9]"))
  check "4".isMatch(re"[0-9A-Fa-f]")
  check "b".isMatch(re"[0-9A-Fa-f]")
  check "B".isMatch(re"[0-9A-Fa-f]")
  check(not "-".isMatch(re"[0-9A-Fa-f]"))
  check "-".isMatch(re"[a\-z]")
  check "a".isMatch(re"[a\-z]")
  check "z".isMatch(re"[a\-z]")
  check(not "b".isMatch(re"[a\-z]"))
  check "a".isMatch(re"[a-]")
  check "-".isMatch(re"[a-]")
  check "+".isMatch(re"[(+*)]")
  check "*".isMatch(re"[(+*)]")
  check "(".isMatch(re"[(+*)]")
  check "[".isMatch(re"[[-\]]")
  check "]".isMatch(re"[[-\]]")
  check(not "-".isMatch(re"[[-\]]"))
  check "(".isMatch(re"[(-\)]")
  check ")".isMatch(re"[(-\)]")
  check(not "-".isMatch(re"[(-\)]"))
  check "\\".isMatch(re"[\\-\\)]")
  check(not "-".isMatch(re"[\\-\\)]"))
  check "-".isMatch(re"[-]")
  check "-".isMatch(re"[\-]")
  check "-".isMatch(re"[\-\-]")
  check "-".isMatch(re"[\--]")
  check "-".isMatch(re"[\--\-]")
  check "-".isMatch(re"[\---]")
  check "b".isMatch(re"[\--\-a-z]")
  check "b".isMatch(re"[\---a-z]")
  check "b".isMatch(re"[-a-z]")
  check "-".isMatch(re"[-a-z]")
  check "a".isMatch(re"[-a]")
  check "-".isMatch(re"[-a]")
  check "b".isMatch(re"[a-d-z]")
  check "-".isMatch(re"[a-d-z]")
  check "z".isMatch(re"[a-d-z]")
  check(not "e".isMatch(re"[a-d-z]"))
  check "]".isMatch(re"[]]")
  check "]".isMatch(re"[\]]")
  check(not "[".isMatch(re"[]]"))
  check(not "]]".isMatch(re"[]]"))
  check(not "-".isMatch(re"[[-\]]"))
  check(not "b".isMatch(re"[c-d]"))
  check "-".isMatch(re"[a\w-\wz]")
  check "-".isMatch(re"[\w-a]")
  check "-".isMatch(re"[\w-]")
  check "a".isMatch(re"[\w-a]")
  check "1".isMatch(re"[\w-a]")
  check "-".isMatch(re"[db-c-f]")
  check(not "e".isMatch(re"[db-c-f]"))
  check(not "-".isMatch(re"[=-_]"))
  check "A".isMatch(re"[\A]")
  check "b".isMatch(re"[\b]")
  check "zz".isMatch(re"[\z][\z]")
  check(not "z".isMatch(re"[\z][\z]"))
  check raisesMsg(r"[a-\w]") ==
    "Invalid set range. Range can't contain " &
    "a character-class or assertion\n" &
    "[a-\\w]\n" &
    "   ^"
  check(not raises(r"[a-\b]"))
  check raisesMsg(r"[d-c]") ==
    "Invalid set range. " &
    "Start must be lesser than end\n" &
    "[d-c]\n" &
    "   ^"
  check raisesMsg(r"abc[]") ==
    "Invalid set. Missing `]`\n" &
    "abc[]\n" &
    "   ^"
  check raisesMsg(r"[]abc") ==
    "Invalid set. Missing `]`\n" &
    "[]abc\n" &
    "^"
  check raisesMsg(r"[abc") ==
    "Invalid set. Missing `]`\n" &
    "[abc\n" &
    "^"
  check raises(r"[a")
  check raises(r"[a-")
  check raises(r"[-a")
  check raises(r"[\\")
  check raises(r"[]")
  check raises(r"[^]")
  check raises(r"[]a")
  check raises(r"[-")
  check "a".isMatch(re"[\u0061]")
  check(not "b".isMatch(re"[\u0061]"))
  check "a".isMatch(re"[\U00000061]")
  check "a".isMatch(re"[\x61]")
  check "a".isMatch(re"[\x{61}]")
  check "abab".isMatch(re"[\x61-\x62]*")
  check "a".isMatch(re"[\141]")

test "tnot_set":
  check "a".matchWithCapt(re"([^b])") == @[@["a"]]
  check "asd".matchWithCapt(re"([^b]*)") == @[@["asd"]]
  check "ab".matchWithCapt(re"([^b]*b)") == @[@["ab"]]
  check "asd123".matchWithCapt(re"([^\d]*)(\d*)") ==
    @[@["asd"], @["123"]]
  check "asd123".matchWithCapt(re"([asd]*)([^asd]*)") ==
    @[@["asd"], @["123"]]
  check "<asd123!@#>".matchWithCapt(re"(<[^>]*>)") ==
    @[@["<asd123!@#>"]]
  check(not "a".isMatch(re"[^a]"))
  check raisesMsg(r"[^]") ==
    "Invalid set. Missing `]`\n" &
    "[^]\n" &
    "^"
  check "^".isMatch(re"[\^]")
  check "a".isMatch(re"[\^a]")
  check(not "^".isMatch(re"[^^]"))
  check "a".isMatch(re"[^^]")
  check "a".isMatch(re"[^-]")
  check(not "-".isMatch(re"[^-]"))

test "trepetition_range":
  check(not "".isMatch(re"a{0}"))
  check(not "".isMatch(re"a{0,0}"))
  check "".isMatch(re"a{0,2}")
  check "a".isMatch(re"a{0}")
  check "a".isMatch(re"a{0,0}")
  check "a".isMatch(re"a{1}")
  check "aa".isMatch(re"a{2}")
  check "aaa".isMatch(re"a{3}")
  check(not "aaaa".isMatch(re"a{3}"))
  check(not "".isMatch(re"a{1}"))
  check "a".isMatch(re"a{1,1}")
  check "a".isMatch(re"a{1,2}")
  check "aa".isMatch(re"a{1,2}")
  check(not "aaa".isMatch(re"a{1,2}"))
  check(not "a".isMatch(re"a{2,4}"))
  check "a".isMatch(re"a{1,}")
  check "aa".isMatch(re"a{1,}")
  check "aaa".isMatch(re"a{1,}")
  check "aaaaaaaaaa".isMatch(re"a{1,}")
  check "aa".isMatch(re"a{2,}")
  check "aaaaaaaaaa".isMatch(re"a{0,}")
  check "".isMatch(re"a{0,}")
  check(not "a".isMatch(re"a{2,}"))
  check "a{a,1}".isMatch(re"a{a,1}")
  check(not "a".isMatch(re"a{a,1}"))
  check(not "a1".isMatch(re"a{a,1}"))
  check "a{".isMatch(re"a{")
  check "a{{".isMatch(re"a{{")
  check "a{}".isMatch(re"a{}")
  check raises(r"a*{,}")
  check raises(r"a*{0}")
  check raises(r"a*{1}")
  check "aaa".matchWithCapt(re"(a){0,}") ==
    @[@["a", "a", "a"]]
  check "aaa".matchWithCapt(re"(a{0,}){0,}") == @[@["aaa", ""]]
  check "aaaaa".matchWithCapt(re"(a){5}") ==
    @[@["a", "a", "a", "a", "a"]]
  check "a".matchWithCapt(re"(a){1,5}") == @[@["a"]]
  check "aaa".matchWithCapt(re"(a){1,5}") ==
    @[@["a", "a", "a"]]
  check "".matchWithCapt(re"(a){0,}") ==
    @[newSeq[string]()]
  check "aaa".matchWithCapt(re"(a{0,}){0,}") == @[@["aaa", ""]]
  check "aaa".matchWithCapt(re"(a{1}){0,}") ==
    @[@["a", "a", "a"]]
  check "aaaa".matchWithCapt(re"(a{2}){0,}") ==
    @[@["aa", "aa"]]
  check "aaaa".matchWithCapt(re"(a{0,3}){0,}") ==
    @[@["aaa", "a", ""]]
  check "".matchWithCapt(re"(a{0,3}){0,}") ==
    @[@[""]]
  check "aaa".matchWithCapt(re"(a{1,}){0,}") ==
    @[@["aaa"]]
  check "".matchWithCapt(re"(a{1,}){0,}") ==
    @[newSeq[string]()]
  check(not "".isMatch(re"(a{1,})"))
  check "a".matchWithCapt(re"(a{1,})") == @[@["a"]]
  check "aaa".matchWithCapt(re"(a{1,})") == @[@["aaa"]]
  check "abab".matchWithCapt(re"(a(b)){2}") ==
    @[@["ab", "ab"], @["b", "b"]]
  check raisesMsg(r"a{0,bad}") ==
    "Invalid repetition range. Range can only contain digits\n" &
    "a{0,bad}\n" &
    " ^"
  check raisesMsg(r"a{1111111111}") ==
    "Invalid repetition range. Max value is 32767\n" &
    "a{1111111111}\n" &
    " ^"
  check raisesMsg(r"a{0,101}") ==
    "Invalid repetition range. Expected 100 repetitions " &
    "or less, but found: 101\n" &
    "a{0,101}\n" &
    " ^"
  check(not raises(r"a{1,101}"))
  check raises(r"a{0,a}")
  check raises(r"a{,}")
  check raises(r"a{,1}")
  check raises(r"a{1x}")
  check raises(r"a{1,x}")
  check raises(r"a{1")
  check raises(r"a{1,")
  check raises(r"a{1,,}")
  check raises(r"a{1,,2}")
  check raises(r"a{1,,,2}")
  check raisesMsg(r"{10}") ==
    "Invalid repeition range, " &
    "nothing to repeat"
  check raisesMsg(r"abc\A{10}") ==
    "Invalid repetition range, either " &
    "char, shorthand (i.e: \\w), group, or set " &
    "expected before repetition range"

test "tnon_capturing_groups":
  check "abab".matchWithCapt(re"(a(b))*") ==
    @[@["ab", "ab"], @["b", "b"]]
  check "abab".matchWithCapt(re"(?:a(b))*") ==
    @[@["b", "b"]]
  check "abab".matchWithCapt(re"(a(?:b))*") ==
    @[@["ab", "ab"]]
  check ")".matchWithCapt(re"(\))") == @[@[")"]]

test "tgreediness":
  check "a".matchWithCapt(re"(a)??") ==
    @[@["a"]]
  check "aaa".matchWithCapt(re"(a)*(a)*(a)*") ==
    @[@["a", "a", "a"], @[], @[]]
  check "aaa".matchWithCapt(re"(a)*?(a)*(a)*?") ==
    @[@[], @["a", "a", "a"], @[]]
  check "aaa".matchWithCapt(re"(a)*?(a)*?(a)*") ==
    @[@[], @[], @["a", "a", "a"]]
  check "aaa".matchWithCapt(re"(a)*?(a)*?(a)*?") ==
    @[@[], @[], @["a", "a", "a"]]
  check "aaaa".matchWithCapt(re"(a)*?(a)*?(a)*?") ==
    @[@[], @[], @["a", "a", "a", "a"]]
  check "aa".matchWithCapt(re"(a)?(aa?)") ==
    @[@["a"], @["a"]]
  check "aa".matchWithCapt(re"(a)??(a)") ==
    @[@["a"], @["a"]]
  check "aa".matchWithCapt(re"(a)??(aa?)") ==
    @[@[], @["aa"]]
  check "aaa".matchWithCapt(re"(a)+(a)+(a)?") ==
    @[@["a", "a"], @["a"], @[]]
  check "aaa".matchWithCapt(re"(a)+?(a)+(a)?") ==
    @[@["a"], @["a", "a"], @[]]
  check "aaa".matchWithCapt(re"(a)+?(a)+?(a)?") ==
    @[@["a"], @["a"], @["a"]]
  check "aaa".matchWithCapt(re"(a){0,}(a){0,}(a){0,}") ==
    @[@["a", "a", "a"], @[], @[]]
  check "aaa".matchWithCapt(re"(a){0,}?(a){0,}(a){0,}?") ==
    @[@[], @["a", "a", "a"], @[]]
  check "aaa".matchWithCapt(re"(a){0,}?(a){0,}?(a){0,}") ==
    @[@[], @[], @["a", "a", "a"]]
  check "aaa".matchWithCapt(re"(a){0,}?(a){0,}?(a){0,}?") ==
    @[@[], @[], @["a", "a", "a"]]
  check "aaa".matchWithCapt(re"(a){1,}(a){1,}(a)?") ==
    @[@["a", "a"], @["a"], @[]]
  check "aaa".matchWithCapt(re"(a){1,}?(a){1,}(a)?") ==
    @[@["a"], @["a", "a"], @[]]
  check "aaa".matchWithCapt(re"(a){1,}?(a){1,}?(a)?") ==
    @[@["a"], @["a"], @["a"]]
  block:
    var m = RegexMatch()
    check match("aaaa", re"(a*?)(a*?)(a*)", m)
    check m.toStrCaptures("aaaa") ==
      @[@[""], @[""], @["aaaa"]]
  block:
    var m = RegexMatch()
    check match("aaaa", re"(a*)(a*?)(a*?)", m)
    check m.toStrCaptures("aaaa") ==
      @[@["aaaa"], @[""], @[""]]

test "tassertions":
  check "bbaa aa".matchWithCapt(re"([\w ]*?)(\baa\b)") ==
    @[@["bbaa "], @["aa"]]
  check "aa bbaa".matchWithCapt(re"(\baa\b)([\w ]*)") ==
    @[@["aa"], @[" bbaa"]]
  check "This island is great".matchWithCapt(
      re"([\w ]*?)(\bis\b)([\w ]*?)") ==
    @[@["This island "], @["is"], @[" great"]]
  check "bbaabb".matchWithCapt(re"([\w ]*?)(\Baa\B)([\w ]*?)") ==
    @[@["bb"], @["aa"], @["bb"]]
  check "This is my sister".matchWithCapt(
      re"([\w ]*?)(\Bis\B)([\w ]*?)") ==
    @[@["This is my s"], @["is"], @["ter"]]
  check "aa".isMatch(re"\b\b\baa\b\b\b")
  check "bb".isMatch(re"^^^^bb$$$$")
  check "bb".isMatch(re"\A\A\A\Abb\z\z\z\z")

test "tdot_any_matcher":
  check "a".isMatch(re".")
  check "asd123!@#".isMatch(re".*")
  check "| (•□•) | (❍ᴥ❍ʋ)".isMatch(re".*")
  check "ฅ^•ﻌ•^ฅ".matchWithCapt(re"(.*)") ==
    @[@["ฅ^•ﻌ•^ฅ"]]
  check "\t".isMatch(re".")
  check(not "\L".isMatch(re".*"))

test "tgroup":
  block:
    var m = RegexMatch()
    check "foobar".match(re"(\w*)", m)
    check m.group(0) == @[0..5]
  block:
    var m = RegexMatch()
    check "foobar".match(re"(?P<foo>\w*)", m)
    check m.group(0) == @[0..5]
  block:
    var m = RegexMatch()
    check "ab".match(re"(a)(b)", m)
    check m.group(0) == @[0..0]
    check m.group(1) == @[1..1]
  block:
    var m = RegexMatch()
    check match("ab", re"(a)(b)", m)
    check m.toStrCaptures("ab") ==
      @[@["a"], @["b"]]
  block:
    let
      expected = ["a", "b", "c"]
      text = "abc"
    var m = RegexMatch()
    check text.match(re"(?P<foo>\w)+", m)
    var i = 0
    for bounds in m.group("foo"):
      check expected[i] == text[bounds]
      inc i
  block:
    let
      expected = ["a", "b", "c"]
      text = "abc"
    var m = RegexMatch()
    check text.match(re"(\w)+", m)
    var i = 0
    for bounds in m.group(0):
      check expected[i] == text[bounds]
      inc i

test "tnamed_groups":
  block:
    var m = RegexMatch()
    check "foobar".match(re"(?P<foo>\w*)", m)
    check m.group("foo") == @[0..5]
  block:
    var m = RegexMatch()
    check "foobar".match(re"(?P<foo>(?P<bar>\w*))", m)
    check m.group("foo") == @[0..5]
    check m.group("bar") == @[0..5]
  block:
    var m = RegexMatch()
    check "aab".match(re"(?P<foo>(?P<bar>a)*b)", m)
    check m.group("foo") == @[0..2]
    check m.group("bar") == @[0..0, 1..1]
  block:
    var m = RegexMatch()
    check "aab".match(re"((?P<bar>a)*b)", m)
    check m.group("bar") == @[0..0, 1..1]

  check raisesMsg(r"abc(?Pabc)") ==
    "Invalid group name. Missing `<`\n" &
    "abc(?Pabc)\n" &
    "   ^"
  check raisesMsg(r"abc(?P<abc") ==
    "Invalid group name. Missing `>`\n" &
    "abc(?P<abc\n" &
    "   ^"
  check raisesMsg(r"a(?P<>abc)") ==
    "Invalid group name. Name can't be empty\n" &
    "a(?P<>abc)\n" &
    " ^"
  check raisesMsg(r"(a)b)") ==
    "Invalid capturing group. " &
    "Found too many closing symbols"
  check raisesMsg(r"(b(a)") ==
    "Invalid capturing group. " &
    "Found too many opening symbols"
  check raisesMsg(r"a(?P<asd)") ==
    "Invalid group name. Expected char in " &
    "{'a'..'z', 'A'..'Z', '0'..'9', '-', '_'}, " &
    "but found `)`\n" &
    "a(?P<asd)\n" &
    " ^"
  check(not raises(r"(?P<abcdefghijklmnopqrstuvwxyz" &
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_>abc)"))
  check(not raises(r"(\b)"))
  #[
  var manyGroups = newStringOfCap(int16.high * 3)
  for _ in 0 ..< int16.high - 1:
    manyGroups.add(r"(a)")
  check(not raises(manyGroups))
  manyGroups.add(r"(a)")
  check raisesMsg(manyGroups) ==
    "Invalid number of capturing " &
    "groups, the limit is 32766"
  ]#

test "tflags":
  check "foo\Lbar".isMatch(re"(?s).*")
  check "foo\Lbar".isMatch(re"(?s:.*)")
  check "foo\Lbar".isMatch(re"(?ssss).*")
  check(not "foo\Lbar".isMatch(re"(?s-s).*"))
  check(not "foo\Lbar".isMatch(re"(?-s-s-s).*"))
  check(not "foo\Lbar".isMatch(re"(?-ss).*"))
  check(not "foo\Lbar".isMatch(re"(?-ss-ss).*"))
  check(not "foo\Lbar".isMatch(re"(?-sssss-s).*"))
  check(not "foo\Lbar".isMatch(re"(?s-s:.*)"))
  check(not "foo\Lbar".isMatch(re"(?------s----s:.*)"))
  check "foo\Lbar".matchWithCapt(re"((?s:.*))") ==
    @[@["foo\Lbar"]]
  check "a".matchWithCapt(re"((?i:a))") == @[@["a"]]
  check "A".matchWithCapt(re"((?i:a))") == @[@["A"]]
  check "ABC".matchWithCapt(re"((?i:aBc))") ==
    @[@["ABC"]]
  check "a".matchWithCapt(re"((?-i:a))") == @[@["a"]]
  check(not "A".isMatch(re"((?-i:a))"))
  check(not "A".isMatch(re"((?-ii-i:a))"))
  check "a".matchWithCapt(re"((?i)a)") == @[@["a"]]
  check "A".matchWithCapt(re"((?i)a)") == @[@["A"]]
  check "a".matchWithCapt(re"((?-i)a)") == @[@["a"]]
  check(not "A".isMatch(re"((?-i)a)"))
  check "AaA".isMatch(re"(?i)a+")
  check "AaA".isMatch(re"(?i)A+")
  check "AbC".isMatch(re"(?i)abc")
  check(not "b".isMatch(re"(?i)a"))
  check "A".isMatch(re"(?-i)(?i)a")
  check(not "A".isMatch(re"(?i)(?-i)a"))
  check "AaA".matchWithCapt(re"((?i)a+)") == @[@["AaA"]]
  check "A".isMatch(re"(?i)[a]")
  check "a".isMatch(re"(?i)[a]")
  check(not "@".isMatch(re"(?i)[a]"))
  check "a".isMatch(re"(?i)[A]")
  check "A".isMatch(re"(?i)[A]")
  check "C".isMatch(re"(?i)[a-z]")
  check "c".isMatch(re"(?i)[a-z]")
  check(not "@".isMatch(re"(?i)[a-z]"))
  check "c".isMatch(re"(?i)[A-Z]")
  check "C".isMatch(re"(?i)[A-Z]")

  check "aa".matchWithCapt(re"((?U)a*)(a*)") ==
    @[@[""], @["aa"]]
  check "aa".matchWithCapt(re"((?U)a*?)(a*)") ==
    @[@["aa"], @[""]]
  check "aa".matchWithCapt(re"((?U-U)a*)(a*)") ==
    @[@["aa"], @[""]]
  # no empty matches
  check "aa".matchWithCapt(re"(?U:(a)*)(a)*") ==
    @[@[], @["a", "a"]]
  check "aa".matchWithCapt(re"((?U:a*))(a*)") ==
    @[@[""], @["aa"]]
  check "aa".matchWithCapt(re"((?U:a*?))(a*)") ==
    @[@["aa"], @[""]]
  check "aa".matchWithCapt(re"((?U-U:a*))(a*)") ==
    @[@["aa"], @[""]]

  check(not "a\Lb\L".isMatch(re"(?sm)a.b(?-sm:.)"))
  check "a\Lb\L".isMatch(re"(?ms)a.b(?s-m:.)")
  check "a\L".isMatch(re"(?s)a.")
  check(not "a\L\L".isMatch(re"(?s)a.$."))
  check "a\L\L".isMatch(re"(?sm)a.$.")
  check(not "a\L\L".isMatch(re"(?-sm)a.$."))
  check(not "a\L\L".isMatch(re"(?s-m)a.$."))
  check "a\L\L".isMatch(re"(?s-m)(?m:a.$.)")
  check(not "a\L\L".isMatch(re"(?i-sm)(?s:a.$.)"))
  check "a\L\L".isMatch(re"(?i-sm)(?sm:a.$.)")
  check(not "a\L\L".isMatch(re"(?-sm)(?sm)(?-sm:a.$.)"))
  check "a\L\L".isMatch(re"(?sm)(?-sm)(?sm:a.$.)")
  check(not "a\L\L".isMatch(re"(?-sm)(?sm:(?-sm:a.$.))"))
  check "a\L\L".isMatch(re"(?sm)(?-sm:(?sm:a.$.))")

  check "Ǝ".isMatch(re"\w")
  check "Ǝ".isMatch(re"(?u)\w")
  check(not "Ǝ".isMatch(re"(?-u)\w"))
  check "abczABCZ0129_".isMatch(re"(?-u)\w*")
  check(not "\t".isMatch(re"(?-u)\w"))
  # todo: test every ascii kind
  check "Ǝ".isMatch(re"(?u)[\w]")
  check(not "Ǝ".isMatch(re"(?u)[^\w]"))
  check "Ǝ".isMatch(re"(?-u)[^\w]")
  check(not "Ǝ".isMatch(re"(?-u)[\w]"))
  check(not "\t".isMatch(re"(?-u)[\w]"))
  check "ƎƎ".isMatch(re"(?-u)[^\w](?u)\w")

  check "a".isMatch(re"(?x)a")
  check "a".isMatch(re"(?x)a ")
  check "a".isMatch(re"(?x)a   ")
  check "a".isMatch(re"(?x) a ")
  check "a".isMatch(re("(?x)a\L   \L   \L"))
  check "a".isMatch(re("(?x)\L a \L"))
  check "a".isMatch(re"(?x: a )")
  check "a".isMatch(re"""(?x)a""")
  check "a".isMatch(re"""(?x)
    a
    """)
  check "a".isMatch(re"""(?x:
    a
    )""")
  check "a".isMatch(re"""(?x)(
    a
    )""")
  check "a".isMatch(re"""(?x)
    a  # should ignore this comment
    """)
  check "a".isMatch(re"""(?x:
    a  # should ignore this comment
    )""")
  check "aa ".isMatch(re"(?x)a  (?-x)a ")
  check "a a".isMatch(re"a (?x)a  ")
  check "aa".isMatch(re"((?x)a    )a")
  check "aa".isMatch(re"(?x:a    )a")
  check "a ".isMatch(re"(?x)a\ ")
  check "a ".isMatch(re"(?x)a\   ")
  check "a#".isMatch(re"(?x)a\#")
  check "a ".isMatch(re"(?x)a[ ]")
  check "a\n".isMatch(re"(?x)a\n")
  check "aa ".isMatch(re"""(?x)
    a    #    comment
    (?-x)a """)
  check "aaa".isMatch(re"""(?x)  # comment
    a  # comment
    a  # comment
    a  # comment
    # comment""")
  check "12.0".isMatch(re"""(?x)
    \d +  # the integral part
    \.    # the decimal point
    \d *  # some fractional digits""")
  check re"""(?x)    # verbose mode
    ^                   # beginning of string
    M{0,4}              # thousands - 0 to 4 M's
    (CM|CD|D?C{0,3})    # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
                        #            or 500-800 (D, followed by 0 to 3 C's)
    (XC|XL|L?X{0,3})    # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
                        #        or 50-80 (L, followed by 0 to 3 X's)
    (IX|IV|V?I{0,3})    # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
                        #        or 5-8 (V, followed by 0 to 3 I's)
    $                   # end of string
    """ in "MMMMDCCCLXXXVIII"

  check raisesMsg(r"(?uq)") ==
    "Invalid group flag, found q but " &
    "expected one of: i, m, s, U or u"
  check raisesMsg(r"(?u-q)") ==
    "Invalid group flag, found -q but " &
    "expected one of: -i, -m, -s, -U or -u"
  check raisesMsg(r"abc(?q)") ==
    "Invalid group. Unknown group type\n" &
    "abc(?q)\n" &
    "   ^"

test "tescaped_sequences":
  check "\x07".isMatch(re"\a")
  check "\x0C".isMatch(re"\f")
  check "\t".isMatch(re"\t")
  check "\L".isMatch(re"\n")
  check "\r".isMatch(re"\r")
  check "\x0B".isMatch(re"\v")
  check(not "a".isMatch(re"\a"))
  check ".+*?()|[]{}^$".isMatch(re"\.\+\*\?\(\)\|\[\]\{\}\^\$")

  check "\x07".isMatch(re"[\a]")
  check "\x07".isMatch(re"[\a-\a]")
  check(not "0".isMatch(re"[\a-\a]"))
  #check "|".isMatch(re"[a|b]")  # ????

test "tfind":
  block:
    var m = RegexMatch()
    check "abcd".find(re"bc", m)
  block:
    var m = RegexMatch()
    check(not "abcd".find(re"ac", m))
  block:
    var m = RegexMatch()
    check "a".find(re"", m)
  block:
    var m = RegexMatch()
    check "abcd".find(re"^abcd$", m)
  check "2222".findWithCapt(re"(22)*") ==
    @[@["22", "22"]]
  block:
    var m = RegexMatch()
    check "2222".find(re"(22)*", m)
    check m.group(0) == @[0 .. 1, 2 .. 3]
  block:
    var m = RegexMatch()
    check "abcd".find(re"(ab)", m)
    check m.group(0) == @[0 .. 1]
  block:
    var m = RegexMatch()
    check "abcd".find(re"(bc)", m)
    check m.group(0) == @[1 .. 2]
  block:
    var m = RegexMatch()
    check "abcd".find(re"(cd)", m)
    check m.group(0) == @[2 .. 3]
  block:
    var m = RegexMatch()
    check "abcd".find(re"bc", m)
    check m.boundaries == 1 .. 2
  block:
    var m = RegexMatch()
    check "aΪⒶ弢".find(re"Ϊ", m)
    check m.boundaries == 1 .. 2
  block:
    var m = RegexMatch()
    check "aΪⒶ弢".find(re"Ⓐ", m)
    check m.boundaries == 3 .. 5
  block:
    var m = RegexMatch()
    check "aΪⒶ弢".find(re"弢", m)
    check m.boundaries == 6 .. 9

test "tcontains":
  check re"bc" in "abcd"
  check re"bd" notin "abcd"
  check re"(23)+" in "2323"
  check re"(23)+" in "23232"
  check re"^(23)+$" notin "23232"

test "tsplit":
  check split("a,b,c", re",") == @["a", "b", "c"]
  check split("00232this02939is39an22example111", re"\d+") ==
    @["", "this", "is", "an", "example", ""]
  check split("AAA :   : BBB", re"\s*:\s*") == @["AAA", "", "BBB"]
  check split("AAA :   : BBB :   : CCC", re"\s*:\s*") ==
    @["AAA", "", "BBB", "", "CCC"]
  check split("", re",") == @[""]
  check split(",,", re",") == @["", "", ""]
  # nre's behaviour, differs from python
  check split("abc", re"") == @["a", "b", "c"]
  check split("ab", re"") == @["a", "b"]
  check split("ab", re"\b") == @["ab"]
  check split("a b", re" ") == @["a", "b"]
  check split(",a,Ϊ,Ⓐ,弢,", re",") ==
    @["", "a", "Ϊ", "Ⓐ", "弢", ""]
  check split("弢", re"\xAF") == @["弢"]  # "弢" == "\xF0\xAF\xA2\x94"
  block:
    var
      expected = ["", "a", "Ϊ", "Ⓐ", "弢", ""]
      i = 0
    for s in split("11a22Ϊ33Ⓐ44弢55", re"\d+"):
      check s == expected[i]
      inc i

  check split("Words, words, words.", re"\W+") ==
    @["Words", "words", "words", ""]
  check split("0a3B9", re"[a-fA-F]+") ==
    @["0", "3", "9"]
  check split("1 2 3 4 5 6 ", re" ") ==
    @["1", "2", "3", "4", "5", "6", ""]
  check split("1  2  ", re" ") == @["1", "", "2", "", ""]
  check split("1 2", re" ") == @["1", "2"]
  check split("foo", re"foo") == @["", ""]
  check split("", re"foo") == @[""]
  check split("bar", re"foo") == @["bar"]

  check "12".split(re"\w\b") == @["1", ""]
  check "12".split(re"\w\B") == @["", "2"]

test "tsplitIncl":
  check "a,b".splitIncl(re"(,)") == @["a", ",", "b"]
  check "12".splitIncl(re"(\d)") == @["", "1", "", "2", ""]
  check splitIncl("aΪⒶ弢", re"(\w)") ==
    @["", "a", "", "Ϊ", "", "Ⓐ", "", "弢", ""]
  check splitIncl("aΪⒶ弢", re"") == @["a", "Ϊ", "Ⓐ", "弢"]
  check splitIncl("...words, words...", re"(\W+)") ==
    @["", "...", "words", ", ", "words", "...", ""]
  check splitIncl("Words, words, words.", re"(\W+)") ==
    @["Words", ", ", "words", ", ", "words", ".", ""]

  # regular split stuff
  check splitIncl("a,b,c", re",") == @["a", "b", "c"]
  check splitIncl("00232this02939is39an22example111", re"\d+") ==
    @["", "this", "is", "an", "example", ""]
  check splitIncl("AAA :   : BBB", re"\s*:\s*") == @["AAA", "", "BBB"]
  check splitIncl("", re",") == @[""]
  check splitIncl(",,", re",") == @["", "", ""]
  check splitIncl("abc", re"") == @["a", "b", "c"]
  check splitIncl(",a,Ϊ,Ⓐ,弢,", re",") ==
    @["", "a", "Ϊ", "Ⓐ", "弢", ""]
  check splitIncl("弢", re"\xAF") == @["弢"]  # "弢" == "\xF0\xAF\xA2\x94"
  check splitIncl("Words, words, words.", re"\W+") ==
    @["Words", "words", "words", ""]
  check splitIncl("0a3B9", re"[a-fA-F]+") ==
    @["0", "3", "9"]
  check splitIncl("1 2 3 4 5 6 ", re" ") ==
    @["1", "2", "3", "4", "5", "6", ""]
  check splitIncl("1  2  ", re" ") == @["1", "", "2", "", ""]
  check splitIncl("1 2", re" ") == @["1", "2"]
  check splitIncl("foo", re"foo") == @["", ""]
  check splitIncl("", re"foo") == @[""]
  check splitIncl("ab", re"") == @["a", "b"]
  check splitIncl("ab", re"\b") == @["ab"]
  check splitIncl("a b", re" ") == @["a", "b"]

test "tfindall":
  check findAllBounds("abcabc abc", re"abc abc|abc") == @[0 .. 2, 3 .. 9]
  check findAllBounds("abcabc", re"bc") == @[1 .. 2, 4 .. 5]
  check findAllBounds("aa", re"a") == @[0 .. 0, 1 .. 1]
  check findAllBounds("a", re"a") == @[0 .. 0]
  check findAllBounds("a", re"b").len == 0
  check findAllBounds("", re"b").len == 0
  check findAllBounds("abc ab", re"\w+ *") == @[0 .. 3, 4 .. 5]
  check findAllBounds("AAA :   : BBB", re"\s*:\s*") == @[3 .. 7, 8 .. 9]
  check findAllBounds("a\n  b\n c\n  ", re"(?m)^") ==
    @[0 .. -1, 2 .. 1, 6 .. 5, 9 .. 8]
  check findAllBounds("a\n  b\n c\n  ", re"(?m)$") ==
    @[1 .. 0, 5 .. 4, 8 .. 7, 11 .. 10]
  check findAllBounds("\n\n", re"(?m)^") == @[0 .. -1, 1 .. 0, 2 .. 1]
  check findAllBounds("foobarbaz", re"(?<=o)b") == @[3 .. 3]
  check findAllBounds("foobarbaz", re"(?<!o)b") == @[6 .. 6]
  check findAllBounds("aaaabaaaa", re"(?<!a)a") == @[0 .. 0, 5 .. 5]
  check findAllBounds("foobar", re"o(?=b)") == @[2 .. 2]
  check findAllBounds("foobar", re"o(?!b)") == @[1 .. 1]
  check findAllBounds("aaaabaaaa", re"a(?!a)") == @[3 .. 3, 8 .. 8]
  check findAllBounds("aaa", re"\w+b|\w") == @[0 .. 0, 1 .. 1, 2 .. 2]
  # This follows nre's empty match behaviour
  check findAllBounds("a", re"") == @[0 .. -1, 1 .. 0]
  check findAllBounds("ab", re"") == @[0 .. -1, 1 .. 0, 2 .. 1]
  check findAllBounds("a", re"\b") == @[0 .. -1, 1 .. 0]
  check findAllBounds("aΪⒶ弢", re"Ϊ") == @[1 .. 2]
  check findAllBounds("aΪⒶ弢", re"Ⓐ") == @[3 .. 5]
  check findAllBounds("aΪⒶ弢", re"弢") == @[6 .. 9]
  check findAllBounds("aΪⒶ弢aΪⒶ弢", re"Ⓐ") == @[3 .. 5, 13 .. 15]
  # This is nre and Python's re behaviour,
  # they match aaa and then empty end
  check findAllBounds("aaa", re"a*") == @[0 .. 2, 3 .. 2]
  check findAllBounds("aaab", re"a*") == @[0 .. 2, 3 .. 2, 4 .. 3]
  check findAllBounds("aaa", re"a+") == @[0 .. 2]
  check findAllBounds("foo", re"") ==
    @[0 .. -1, 1 .. 0, 2 .. 1, 3 .. 2]
  check findAllBounds("abb", re"a*") == @[0 .. 0, 1 .. 0, 2 .. 1, 3 .. 2]
  check findAllBounds("abbbbccccdXabbbbccccdX", re"a(b|c)*d") ==
    @[0 .. 9, 11 .. 20]
  check findAllBounds("abbbXabbbX", re"((a)*(b)*)") ==
    @[0 .. 3, 4 .. 3, 5 .. 8, 9 .. 8, 10 .. 9]
  check findAllBounds("abbbXabbbX", re"((a)+(b)+)") ==
    @[0 .. 3, 5 .. 8]
  check findAllBounds("abbbXabbbX", re"((a(b)*)+(b)*)") ==
    @[0 .. 3, 5 .. 8]
  check findAllBounds("abXabX", re"a(b|c)*d").len == 0
  check findAllBounds("aaanasdnasdXaaanasdnasd", re"((a)*n?(asd)*)+") ==
    @[0 .. 10, 11 .. 10, 12 .. 22, 23 .. 22]
  check findAllBounds("1xxx", re"\d\w+x") == @[0 .. 3]
  check findAllBounds("xxxx", re"\d\w+x").len == 0

test "tfindandcaptureall":
  check findAndCaptureAll("abcabc", re"bc") == @["bc", "bc"]
  check findAndCaptureAll("a1b2c3a4b5c6", re"\d") ==
    @["1", "2", "3", "4", "5", "6"]
  check findAndCaptureAll("abbbbccccdXabbbbccccdX", re"a(b|c)*d") ==
    @["abbbbccccd", "abbbbccccd"]
  check findAndCaptureAll("abbbXabbbX", re"((a)*(b)*)") ==
    @["abbb", "", "abbb", "", ""]
  check findAndCaptureAll("abbbXabbbX", re"((a)+(b)+)") ==
    @["abbb", "abbb"]
  check findAndCaptureAll("abbbXabbbX", re"((a(b)*)+(b)*)") ==
    @["abbb", "abbb"]
  check findAndCaptureAll("abbbXbbXabbb", re"((a(b)*)*(b)*)") ==
    @["abbb", "", "bb", "", "abbb", ""]

test "tstarts_with":
  check "abc".startsWith(re"ab")
  check(not "abc".startsWith(re"bc"))
  check startsWith("弢ⒶΪ", re"弢Ⓐ")
  check startsWith("弢", re("\xF0\xAF\xA2\x94"))
  #check(not startsWith("弢", re("\xF0\xAF\xA2")))
  check "abc".startsWith(re"\w")
  check(not "abc".startsWith(re"\d"))
  check "abc".startsWith(re"(a|b)")
  check "bc".startsWith(re"(a|b)")
  check(not "c".startsWith(re"(a|b)"))
  check startsWith("abc", re"b", start = 1)
  check startsWith("abc", re"(?<=a)b", start = 1)
  check(not startsWith("abc", re"(?<=x)b", start = 1))
  check(not startsWith("abc", re"^b", start = 1))

test "tends_with":
  check "abc".endsWith(re"bc")
  check(not "abc".endsWith(re"ab"))
  check endsWith("弢ⒶΪ", re"ⒶΪ")
  check endsWith("弢", re("\xF0\xAF\xA2\x94"))
  #check(not endsWith("弢", re("\xAF\xA2\x94")))
  check "abc".endsWith(re"(b|c)")
  check "ab".endsWith(re"(b|c)")
  check(not "a".endsWith(re"(b|c)"))

test "tliterals":
  check "a".isMatch(re"\u0061")
  check(not "b".isMatch(re"\u0061"))
  check "b".isMatch(re"\u0062")
  check "Ⓐ".isMatch(re"\u24b6")
  check "Ⓐ".isMatch(re"\u24B6")
  check raisesMsg(r"\u123") ==
    "Invalid unicode literal. Expected 4 hex digits, but found 3\n" &
    "\\u123\n" &
    "^"
  check raisesMsg(r"\u123@abc") ==
    "Invalid unicode literal. Expected hex digit, but found @\n" &
    "\\u123@abc\n" &
    "^"
  check "a".isMatch(re"\U00000061")
  check(not "b".isMatch(re"\U00000061"))
  check "b".isMatch(re"\U00000062")
  check "弢".isMatch(re"\U0002f894")
  check "弢".isMatch(re"\U0002F894")
  check raisesMsg(r"\U123") ==
    "Invalid unicode literal. Expected 8 hex digits, but found 3\n" &
    "\\U123\n" &
    "^"
  check raisesMsg(r"\U123@a") ==
    "Invalid unicode literal. Expected hex digit, but found @\n" &
    "\\U123@a\n" &
    "^"
  check raisesMsg(r"\UFFFFFFFF") ==
    "Invalid unicode literal. FFFFFFFF value is too big\n" &
    "\\UFFFFFFFF\n" &
    "^"
  check "a".isMatch(re"\x{61}")
  check "a".isMatch(re"\x{061}")
  check(not "b".isMatch(re"\x{61}"))
  check "Ⓐ".isMatch(re"\x{24b6}")
  check "Ⓐ".isMatch(re"\x{000024b6}")
  check "弢".isMatch(re"\x{2f894}")
  check "弢".isMatch(re"\x{0002f894}")
  check raises(r"\x{FFFFFFFF}")
  check(not raises(r"\x{7fffffff}"))
  check raisesMsg(r"\x{2f894") ==
    "Invalid unicode literal. Expected `}`\n" &
    "\\x{2f894\n" &
    "^"
  check raisesMsg(r"\x{00000000A}") ==
    "Invalid unicode literal. Expected at most 8 chars, found 9\n" &
    "\\x{00000000A}\n" &
    "^"
  check raisesMsg(r"\x{61@}") ==
    "Invalid unicode literal. Expected hex digit, but found @\n" &
    "\\x{61@}\n" &
    " ^"
  check "a".isMatch(re"\x61")
  check "aa".isMatch(re"\x61a")
  check "a".isMatch(re"\x61")
  check "a".isMatch(re"\141")
  check(not "b".isMatch(re"\141"))
  check "aa".isMatch(re"\141a")
  check "\u1ff".isMatch(re"\777")
  check "888".isMatch(re"\888")
  check raisesMsg(r"\12") ==
    "Invalid octal literal. Expected 3 octal digits, but found 2\n" &
    "\\12\n" &
    "^"
  check raisesMsg(r"\12@") ==
    "Invalid octal literal. Expected octal digit, but found @\n" &
    "\\12@\n" &
    "^"

test "tchar_class":
  check "a".isMatch(re"\pL")
  check(not "a".isMatch(re"\PL"))
  check(not "1".isMatch(re"\pL"))
  check "1".isMatch(re"\PL")
  check "aa".isMatch(re"\pLa")
  check "1".isMatch(re"\pN")
  check "_".isMatch(re"\pP")
  check "+".isMatch(re"\pS")
  check " ".isMatch(re"\pZ")
  check raisesMsg(r"\pB") ==
    "Invalid unicode name. Found B\n" &
    "\\pB\n" &
    "^"
  check raisesMsg(r"\p11") ==
    "Invalid unicode name. Found 1\n" &
    "\\p11\n" &
    "^"
  check "a".isMatch(re"\p{L}")
  check "ǅ".isMatch(re"\p{Lt}")
  check(not "ǅ".isMatch(re"\P{Lt}"))
  check(not "a".isMatch(re"\p{Lt}"))
  check "a".isMatch(re"\P{Lt}")
  check raisesMsg(r"\p{Bb}") ==
    "Invalid unicode name. Found Bb\n" &
    "\\p{Bb}\n" &
    "^"
  check raisesMsg(r"\p{11}") ==
    "Invalid unicode name. Expected chars in {'a'..'z', 'A'..'Z'}\n" &
    "\\p{11}\n" &
    "^"
  check raisesMsg(r"\p{11") ==
    "Invalid unicode name. Expected `}`\n" &
    "\\p{11\n" &
    "^"

test "tascii_set":
  check "d".isMatch(re"[[:alnum:]]")
  check "5".isMatch(re"[[:alnum:]]")
  check(not "{".isMatch(re"[[:alnum:]]"))
  check "{".isMatch(re"[[:alnum:]{]")
  check "-".isMatch(re"[[:alnum:]-z]")
  check raisesMsg(r"[z-[:alnum:]]") ==
    "Invalid set range. " &
    "Start must be lesser than end\n" &
    "[z-[:alnum:]]\n" &
    "   ^"
  check "a".isMatch(re"[[[[:alnum:]]")
  check "[".isMatch(re"[[[:alnum:]]")
  check(not ":".isMatch(re"[[:alnum:]]"))
  check ":".isMatch(re"[:alnum:]")
  check(not "a".isMatch(re"[[:^alnum:]]"))
  check "{".isMatch(re"[[:^alnum:]]")
  check(not "5".isMatch(re"[[:alpha:]]"))
  check(not "a".isMatch(re"[[:digit:]]"))
  check "5".isMatch(re"[[:alpha:][:digit:]]")
  check "a".isMatch(re"[[:alpha:][:digit:]]")
  check raisesMsg(r"[[:abc:]]") ==
    "Invalid ascii set. `abc` is not a valid name\n" &
    "[[:abc:]]\n" &
    " ^"
  check raisesMsg(r"[[:alnum]]") ==
    "Invalid ascii set. Expected [:name:]\n" &
    "[[:alnum]]\n" &
    " ^"
  check raisesMsg(r"[[:alnum:") ==
    "Invalid ascii set. Expected [:name:]\n" &
    "[[:alnum:\n" &
    " ^"

test "treplace":
  check "a".replace(re"(a)", "m($1)") ==
    "m(a)"
  check "a".replace(re"(a)", "m($1) m($1)") ==
    "m(a) m(a)"
  check "aaa".replace(re"(a*)", "m($1)") ==
    "m(aaa)m()"  # nre's behaviour
  check "abc".replace(re"(a(b)c)", "m($1) m($2)") ==
    "m(abc) m(b)"
  check "abc".replace(re"(a(b))(c)", "m($1) m($2) m($3)") ==
    "m(ab) m(b) m(c)"
  check "abcabc".replace(re"(abc)*", "m($1)") ==
    "m(abcabc)m()"  # nre's behaviour
  check "abcabc".replace(re"(abc)", "m($1)") ==
    "m(abc)m(abc)"
  check "abcabc".replace(re"(abc)", "m($1)") ==
    "m(abc)m(abc)"
  check "abcab".replace(re"(abc)", "m($1)") ==
    "m(abc)ab"
  check "abcabc".replace(re"((abc)*)", "m($1) m($2)") ==
    "m(abcabc) m(abcabc)m() m()"  # nre's behaviour
  check "abcabc".replace(re"((a)bc)*", "m($1) m($2)") ==
    "m(abcabc) m(aa)m() m()"
  check "abc".replace(re"(b)", "m($1)") == "am(b)c"
  check "abc".replace(re"d", "m($1)") == "abc"
  check "abc".replace(re"(d)", "m($1)") == "abc"
  check "aaa".replace(re"a", "b") == "bbb"
  check "aaa".replace(re"a", "b", 1) == "baa"
  check "Nim is awesome!".replace(re"(\w\B)", "$1_") ==
    "N_i_m i_s a_w_e_s_o_m_e!"

  block:
    proc by(m: RegexMatch, s: string): string =
      result = "m("
      for g in 0 ..< m.groupsCount:
        for sl in m.group(g):
          result.add(s[sl])
          result.add(',')
      result.add(')')

    check "abc".replace(re"(b)", by) == "am(b,)c"
    check "aaa".replace(re"(a*)", by) == "m(aaa,)m(,)"
    check "aaa".replace(re"(a)*", by) == "m(a,a,a,)m()"

  block:
    proc removeEvenWords(m: RegexMatch, s: string): string =
      if m.group(1).len mod 2 != 0:
        result = s[m.group(0)[0]]
      else:
        result = ""

    let
      text = "Es macht Spaß, alle geraden Wörter zu entfernen!"
      expected = "macht , geraden entfernen!"
    check text.replace(re"((\w)+\s*)", removeEvenWords) == expected

test "tmisc":
  block:
    var m = RegexMatch()
    check "abc".match(re"[^^]+", m)
    check m.boundaries == 0 .. 2
  check(not "^".isMatch(re"[^^]+"))
  block:
    var m = RegexMatch()
    check "kpd".match(re"[^al-obc]+", m)
    check m.boundaries == 0 .. 2
  check(not "abc".isMatch(re"[^al-obc]+"))
  block:
    var m = RegexMatch()
    check "almocb".match(re"[al-obc]+", m)
    check m.boundaries == 0 .. 5
  check(not "defzx".isMatch(re"[al-obc]+"))

  # From http://www.regular-expressions.info/examples.html
  # Grabbing HTML Tags
  block:
    var m = RegexMatch()
    check "one<TAG>two</TAG>three".find(re"<TAG\b[^>]*>(.*?)</TAG>", m)
    check m.boundaries == 3 .. 16
  check("one<TAG>two</TAG>three".findWithCapt(
    re"<TAG\b[^>]*>(.*?)</TAG>") == @[@["two"]])
  # IP Addresses
  block:
    const ip = re"""(?x)
    \b
    (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
    \.
    (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
    \.
    (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
    \.
    (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
    \b
    """
    check "127.0.0.1".findWithCapt(ip) ==
      @[@["127"], @["0"], @["0"], @["1"]]
    check(not "127.0.0.999".isMatch(ip))
  # Floating Point Numbers
  block:
    var m = RegexMatch()
    check "3.14".find(re"^[-+]?[0-9]*\.?[0-9]+$", m)
    check m.boundaries == 0 .. 3
  check "1.602e-19".findWithCapt(
    re"^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$") == @[@["e-19"]]
  # E-mail Addresses
  block:
    const email = re"""(?x)
    \b
    [a-zA-Z0-9._%+-]+
    @
    (?:[a-zA-Z0-9-]+\.)+
    [a-zA-Z]{2,4}
    \b
    """
    var m = RegexMatch()
    check "john@server.department.company.com".find(email, m)
    check m.boundaries == 0 .. 33
    check(not "john@aol...com".isMatch(email))
  block:
    const email = re"""(?x)
    [a-z0-9!#$%&'*+/=?^_`{|}~-]+
    (?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*
    @
    (?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+
    [a-z0-9](?:[a-z0-9-]*[a-z0-9])?
    """
    check "john@server.department.company.com".isMatch(email)
    check(not "john@aol...com".isMatch(email))
  block:
    const email = re"""(?x)
    (?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+
    (?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"
    (?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|
    \\[\x01-\x09\x0b\x0c\x0e-\x7f])*"
    )@
    (?:
    (?:[a-z0-9]
    (?:[a-z0-9-]*[a-z0-9])?\.
    )+[a-z0-9]
    (?:[a-z0-9-]*[a-z0-9])?|\[
    (?:
    (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.
    ){3}
    (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:
    (?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|
    \\[\x01-\x09\x0b\x0c\x0e-\x7f])+
    )\]
    )
    """
    check "john@server.department.company.com".isMatch(email)
    check(not "john@aol...com".isMatch(email))
  # Date Validation
  block:
    const date = re"""(?x)
    ^((?:19|20)\d\d)[- /.]
    (0[1-9]|1[012])[- /.]
    (0[1-9]|[12][0-9]|3[01])$
    """
    check "1999-01-01".findWithCapt(date) ==
      @[@["1999"], @["01"], @["01"]]
    check "1999/01-01".findWithCapt(date) ==
      @[@["1999"], @["01"], @["01"]]
    check(not "1999-13-33".isMatch(date))
  # Near operator emulation
  block:
    const nope = re"\bword1\W+(?:\w+\W+){1,6}?word2\b"
    check(not "word1 word2".isMatch(nope))
    check "word1 1 word2".isMatch(nope)
    check "word1 1 2 3 4 5 6 word2".isMatch(nope)
    check(not "word1 1 2 3 4 5 6 7 word".isMatch(nope))

  # Unicode
  block:
    var m = RegexMatch()
    check "①②③".find(re"①②③", m)
    check m.boundaries == 0 ..< "①②③".len
  block:
    var m = RegexMatch()
    check "①②③④⑤".find(re"①②③", m)
    check m.boundaries == 0 ..< "①②③".len
  block:
    var m = RegexMatch()
    check "①②③".find(re"①(②)③", m)
    check m.boundaries == 0 ..< "①②③".len
  check "①②③".findWithCapt(re"①(②)③") == @[@["②"]]
  block:
    var m = RegexMatch()
    check "①②③".find(re"[①②③]*", m)
    check m.boundaries == 0 ..< "①②③".len
  #
  block:
    var m = RegexMatch()
    check "①②③".find(re"[^④⑤]*", m)
    check m.boundaries == 0 ..< "①②③".len

test "tlook_around":
  check "ab".isMatch(re"a(?=b)\w")
  check(not "ab".isMatch(re"a(?=b)"))
  check(not "ab".isMatch(re"a(?=c)\w"))
  check "ab".matchWithCapt(re"(a(?=b))b") == @[@["a"]]
  check "ab".isMatch(re"\w(?<=a)b")
  check(not "ab".isMatch(re"\w(?<=a)"))
  check(not "ab".isMatch(re"\w(?<=c)b"))
  check "ab".matchWithCapt(re"(a(?<=a))b") == @[@["a"]]

test "tnegative_look_around":
  check "ab".isMatch(re"a(?!c)\w")
  check(not "ab".isMatch(re"a(?!c)"))
  check(not "ab".isMatch(re"a(?!b)\w"))
  check "ab".matchWithCapt(re"(a(?!c))\w") == @[@["a"]]
  check "ab".isMatch(re"\w(?<!c)b")
  check(not "ab".isMatch(re"\w(?<!c)"))
  check(not "ab".isMatch(re"\w(?<!a)b"))
  check "ab".matchWithCapt(re"(\w(?<!c))b") == @[@["a"]]

test "tfull_lookarounds":
  var m = RegexMatch()
  check match("ab", re"a(?=b)\w")
  check(not match("ab", re"a(?=x)\w"))
  check(not match("ab", re"ab(?=x)"))
  check match("ab", re"ab(?=$)")
  check match("abc", re"a(?=b)\w+")
  check match("abc", re"a(?=(bc))\w+", m) and
    m.captures == @[@[1 .. 2]]
  check match("ab", re"\w(?<=a)b")
  check(not match("ab", re"\w(?<=x)b"))
  check(not match("ab", re"(?<=x)ab"))
  check match("ab", re"(?<=^)ab")
  check match("abc", re"\w\w(?<=(b))c")
  check match("abc", re"\w\w(?<=(b))c", m) and
    m.captures == @[@[1 .. 1]]
  check match("abc", re"\w\w(?<=(ab))c")
  check match("abc", re"\w\w(?<=(ab))c", m) and
    m.captures == @[@[0 .. 1]]
  check match("a", re"\w(?<=a)")
  check(not match("a", re"\w(?=a)"))
  check match("ab", re"\w(?<=a(?<=a))b")
  check match("ab", re"\w(?<=a(?<=a(?<=a)))b")
  check match("ab", re"\w(?<=a(?=b))b")
  check(not match("ab", re"\w(?=b(?=b))b"))  # JS, D
  check(not match("ab", re"\w(?<=a(?=b(?=b)))b"))  # JS, D
  check match("ab", re"\w(?<=a(?<=a)(?=b))b")
  check match("ab", re"\w(?<=a(?<=a(?=b)))b")
  check(not match("ab", re"\w(?<=(?<=a)a)b"))  # JS, D
  check match("aab", re"\w\w(?<=aa(?=b))b")
  block:
    check match("aaab", re"(\w*)(\w*?)", m) and
      m.captures == @[@[0 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w*)(\w*?)$)", m) and
      m.captures == @[@[0 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w*)(\w*)$)", m) and
      m.captures == @[@[0 .. -1], @[0 .. 3]]
    check match("aaab", re"(\w*?)(\w*)(\w*?)", m) and
      m.captures == @[@[0 .. -1], @[0 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w*?)(\w*)(\w*?)$)", m) and
      m.captures == @[@[0 .. -1], @[0 .. 3], @[4 .. 3]]
    check match("aaab", re"(\w*)(\w??)", m) and
      m.captures == @[@[0 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w*)(\w??)$)", m) and
      m.captures == @[@[0 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w*)(\w?)$)", m) and
      m.captures == @[@[0 .. 2], @[3 .. 3]]
    check match("aaab", re".*(?<=^(\w*?)(\w?)(\w*?)$)", m) and
      m.captures == @[@[0 .. 2], @[3 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w*?)(\w??)(\w*?)$)", m) and
      m.captures == @[@[0 .. 3], @[4 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w??)(\w*?)$)", m) and
      m.captures == @[@[0 .. 0], @[1 .. 3]]
    check match("aaab", re".*(?<=^(\w?)(\w*?)$)", m) and
      m.captures == @[@[0 .. 0], @[1 .. 3]]
    check match("aaab", re".*(?<=^(\w*?)(\w??)$)", m) and
      m.captures == @[@[0 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w*)(\w??)$)", m) and
      m.captures == @[@[0 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w*?)(\w?)$)", m) and
      m.captures == @[@[0 .. 2], @[3 .. 3]]
    check match("aaab", re".*(?<=^(\w*)(\w?)$)", m) and
      m.captures == @[@[0 .. 2], @[3 .. 3]]
    check match("aaab", re".*(?<=^(\w*?)(\w*?)(\w??)$)", m) and
      m.captures == @[@[0 .. 3], @[4 .. 3], @[4 .. 3]]
    check match("aaab", re".*(?<=^(\w*?)(\w*)(\w??)$)", m) and
      m.captures == @[@[0 .. -1], @[0 .. 3], @[4 .. 3]]
  block:
    check match("aaab", re".*(?<=^(\w)\w+|(\w)\w+$)", m) and
      m.captures == @[@[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(\d)\w+|(\w)\w+$)", m) and
      m.captures == @[@[], @[0 .. 0]]
    check match("aaab", re".*(?<=^(\w)\w+|(\w)\w+|(\w)\w+$)", m) and
      m.captures == @[@[0 .. 0], @[], @[]]
    check match("aaab", re".*(?<=^(\d)\w+|(\w)\w+|(\w)\w+$)", m) and
      m.captures == @[@[], @[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(\d)\w+|(\d)\w+|(\w)\w+$)", m) and
      m.captures == @[@[], @[], @[0 .. 0]]
  block:
    check match("aaab", re".*(?<=^(?:(\w)\w+|(\w)\w+)+?$)", m) and
      m.captures == @[@[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(?:(\d)\w+|(\w)\w+)+?$)", m) and
      m.captures == @[@[], @[0 .. 0]]
    check match("aaab", re".*(?<=^(?:(\w)\w+|(\w)\w+|(\w)\w+)+?$)", m) and
      m.captures == @[@[0 .. 0], @[], @[]]
    check match("aaab", re".*(?<=^(?:(\d)\w+|(\w)\w+|(\w)\w+)+?$)", m) and
      m.captures == @[@[], @[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(?:(\d)\w+|(\d)\w+|(\w)\w+)+?$)", m) and
      m.captures == @[@[], @[], @[0 .. 0]]
    check match("aaab", re".*(?<=^(?:(\w)\w+|(\w)\w+)*?$)", m) and
      m.captures == @[@[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(?:(\d)\w+|(\w)\w+)*?$)", m) and
      m.captures == @[@[], @[0 .. 0]]
    check match("aaab", re".*(?<=^(?:(\w)\w+|(\w)\w+|(\w)\w+)*?$)", m) and
      m.captures == @[@[0 .. 0], @[], @[]]
    check match("aaab", re".*(?<=^(?:(\d)\w+|(\w)\w+|(\w)\w+)*?$)", m) and
      m.captures == @[@[], @[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(?:(\d)\w+|(\d)\w+|(\w)\w+)*?$)", m) and
      m.captures == @[@[], @[], @[0 .. 0]]
    check match("aaab", re".*(?<=^(?:(\w)\w+|(\w)\w+)??$)", m) and
      m.captures == @[@[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(?:(\d)\w+|(\w)\w+)??$)", m) and
      m.captures == @[@[], @[0 .. 0]]
    check match("aaab", re".*(?<=^(?:(\w)\w+|(\w)\w+|(\w)\w+)??$)", m) and
      m.captures == @[@[0 .. 0], @[], @[]]
    check match("aaab", re".*(?<=^(?:(\d)\w+|(\w)\w+|(\w)\w+)??$)", m) and
      m.captures == @[@[], @[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(?:(\d)\w+|(\d)\w+|(\w)\w+)??$)", m) and
      m.captures == @[@[], @[], @[0 .. 0]]
  block:
    check match("aaab", re".*(?<=^(\w)\w+?|(\w)\w+?$)", m) and
      m.captures == @[@[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(\w)\w+|(\w)\w+$)", m) and
      m.captures == @[@[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(\w)\w+?|(\w)\w+$)", m) and
      m.captures == @[@[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(\w)\w+|(\w)\w+?$)", m) and
      m.captures == @[@[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(\w+|\w{4}|\w{4}|\w+)*$)", m) and
      m.captures == @[@[0 .. 3]]
    check match("aaab", re"(\w*|\w{4}|\w{4}|\w*)*", m) and
      m.captures == @[@[0 .. 3, 4 .. 3]]
    check match("aaab", re".*(?<=^(\w*|\w{4}|\w{4}|\w*)*$)", m) and
      m.captures == @[@[0 .. -1, 0 .. 3]]
    check match("aaab", re".*(?<=^(\w+|\w{4}|\w{4}|\w+)+$)", m) and
      m.captures == @[@[0 .. 3]]
    check match("aaab", re".*(?<=^(\w)\w+|\w{4}|\w{4}|(\w)\w+$)", m) and
      m.captures == @[@[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(\d)\w+|(\w{4})|(\w{4})|(\w)\w+$)", m) and
      m.captures == @[@[], @[0 .. 3], @[], @[]]
    check match("aaab", re".*(?<=^(\d)\w+|(\w)\w{3}|(\w)\w{3}|(\w)\w+$)", m) and
      m.captures == @[@[], @[0 .. 0], @[], @[]]
    check match("aaab", re".*(?<=^(\d)\w+|(\d)\w{3}|(\w)\w{3}|(\w)\w+$)", m) and
      m.captures == @[@[], @[], @[0 .. 0], @[]]
    check match("aaab", re".*(?<=^(\d)\w+|(\d)\w{3}|(\d)\w{3}|(\w)\w+$)", m) and
      m.captures == @[@[], @[], @[], @[0 .. 0]]
    check match("aaab", re"(\w*|\w{4}|\w{4}|\w*)+", m) and
      m.captures == @[@[0 .. 3, 4 .. 3]]
    check match("aaab", re".*(?<=^(\w*|\w{4}|\w{4}|\w*)+$)", m) and
      m.captures == @[@[0 .. -1, 0 .. 3]]
    check match("aaab", re".*(?<=^(\w+)|(\w{4})|(\w{10})$)", m) and
      m.captures == @[@[0 .. 3], @[], @[]]
    check match("aaab", re".*(?<=^(\w{10})|(\w{4})|(\w+)$)", m) and
      m.captures == @[@[], @[0 .. 3], @[]]
    check match("aaab", re".*(?<=^((\w{10})|(\w{4})|(\w+))+$)", m) and
      m.captures == @[@[0 .. 3], @[], @[0 .. 3], @[]]
    check match("aaab", re".*(?<=^((\w{10})|(\w{4})|(\w+))*$)", m) and
      m.captures == @[@[0 .. 3], @[], @[0 .. 3], @[]]
    check match("aaab", re".*(?<=^(\w+|\w{4}|\w{4}|\w+)*?$)", m) and
      m.captures == @[@[0 .. 3]]
    check match("aaab", re".*(?<=^(\w*|\w{4}|\w{4}|\w*)*?$)", m) and
      m.captures == @[@[0 .. 3]]
    check match("aaab", re".*(?<=^(\w+|\w{4}|\w{4}|\w+)+?$)", m) and
      m.captures == @[@[0 .. 3]]
  check findAllBounds(r"1abab", re"(?<=\d\w*)ab") ==
    @[1 .. 2, 3 .. 4]
  check findAllBounds(r"abab", re"(?<=\d\w*)ab").len == 0
  check findAllBounds(r"abab1", re"ab(?=\w*\d)") ==
    @[0 .. 1, 2 .. 3]
  check findAllBounds(r"abab", re"ab(?=\w*\d)").len == 0
  check findAllBounds("foo\nbar\nbar", re"bar(?=$)") ==
    @[8 .. 10]
  check findAllBounds("foo\nbar\nbar", re"(?m)bar(?=$)") ==
    @[4 .. 6, 8 .. 10]
  check findAllBounds("bar\nfoo\nbar", re"(?<=^)bar") ==
    @[0 .. 2]
  check findAllBounds("bar\nfoo\nbar", re"(?m)(?<=^)bar") ==
    @[0 .. 2, 8 .. 10]
  block:
    # There is a difference in how nesting is
    # handled by JS vs D; in D all of them start
    # from the outermost one, while in JS they
    # start from the outer one/parent. I think the JS way
    # is less mind blending, and it makes more sense to me
    #
    # These 2 match in D, but not in JS
    check(not match("ab", re"\w(?<=a(?=b(?<=a)))b"))  # JS, !D
    check(not match("ab", re"\w(?<=a(?<=a(?=b(?<=a))))b"))  # JS, !D
    # These 2 match in JS, but not in D
    check match("ab", re"\w(?<=a(?=b(?<=b)))b")  # JS, !D
    check match("ab", re"\w(?<=a(?<=a(?=b(?<=b))))b")  # JS, !D
  block:
    let asterisk = re"(?<=(?<!\\)(?:\\\\)*)\*"
    check findAllBounds(r"*foo*", asterisk) ==
      @[0 .. 0, 4 .. 4]
    check findAllBounds(r"\*foo\*", asterisk).len == 0
    check findAllBounds(r"\\*foo\\*", asterisk) ==
      @[2 .. 2, 8 .. 8]
    check findAllBounds(r"\\\*foo\\\*", asterisk).len == 0
    check findAllBounds(r"\\\\*foo\\\\*", asterisk) ==
      @[4 .. 4, 12 .. 12]
    check findAllBounds(r"\\\\\*foo\\\\\*", asterisk).len == 0
  block:
    let asterisk = re".*(?<=(?<!\\)(?:\\\\)*)\*"
    check match(r"*", asterisk)
    check(not match(r"\*", asterisk))
    check match(r"\\*", asterisk)
    check(not match(r"\\\*", asterisk))
    check match(r"\\\\*", asterisk)
    check(not match(r"\\\\\*", asterisk))
  block:
    check findAllBounds("foobarbaz", re"(?<=o)b") == @[3 .. 3]
    check findAllBounds("foobar", re"o(?=b)") == @[2 .. 2]
    check findAllBounds("100 in USD100", re"(?<=USD)\d{3}") ==
      @[10 .. 12]
    check findAllBounds("100 in USD100", re"\d{3}(?<=USD\d{3})") ==
      @[10 .. 12]
  block:
    check match("弢b", re"弢(?=b)\w")
    check match("a弢", re"a(?=弢)\w")
    check match("Ⓐb", re"Ⓐ(?=b)\w")
    check match("aⒶ", re"a(?=Ⓐ)\w")
    check match("Ⓐb", re"Ⓐ(?=b)\w")
    check match("aΪ", re"a(?=Ϊ)\w")
    check match("Ϊb", re"Ϊ(?=b)\w")
    check match("弢Ⓐ", re"弢(?=Ⓐ)\w")
  block:
    check match("a弢", re"\w(?<=a)弢")
    check match("弢b", re"\w(?<=弢)b")
    check match("aⒶ", re"\w(?<=a)Ⓐ")
    check match("Ⓐb", re"\w(?<=Ⓐ)b")
    check match("aΪ", re"\w(?<=a)Ϊ")
    check match("Ϊb", re"\w(?<=Ϊ)b")
    check match("弢Ⓐ", re"\w(?<=弢)Ⓐ")
  block:  # Follows Nim re's behaviour
    check(not match("abc", re"^bc", m, start = 1))
    check match("abc", re"(?<=a)bc", m, start = 1)
    check(not match("abc", re"(?<=x)bc", m, start = 1))
  block:
    check match("abcdefg", re"\w+(?<=(ab)cd(?<=(cd)))\w+", m) and
      m.captures == @[@[0 .. 1], @[2 .. 3]]
    check match("abcdefg", re"\w+(?<=(ab)(?=(cd)))\w+", m) and
      m.captures == @[@[0 .. 1], @[2 .. 3]]
    check match("abcdefg", re"\w+(?<=(ab)(?=(cd)(?<=(cd))))\w+", m) and
      m.captures == @[@[0 .. 1], @[2 .. 3], @[2 .. 3]]
    check match("abcdefg", re"\w+(?=(cd)(?<=(cd)))\w+", m) and
      m.captures == @[@[2 .. 3], @[2 .. 3]]

test "tpretty_errors":
  block:
    var exp = ""
    for _ in 0 ..< 30:
      exp.add('x')
    exp.add("(?Pabc")
    check raisesMsg(exp) ==
      "Invalid group name. Missing `<`\n" &
      "~16 chars~xxxxxxxxxxxxxx(?Pabc\n" &
      "                        ^"
  block:
    var exp = "(?Pabc"
    for _ in 0 ..< 30:
      exp.add('x')
    check raisesMsg(exp) ==
      "Invalid group name. Missing `<`\n" &
      "(?Pabcxxxxxxxxxxxxxxxxxxxxxxxx~6 chars~\n" &
      "^"
  block:
    var exp = ""
    for _ in 0 ..< 30:
      exp.add('x')
    exp.add("(?Pabc")
    for _ in 0 ..< 30:
      exp.add('x')
    check raisesMsg(exp) ==
      "Invalid group name. Missing `<`\n" &
      "~16 chars~xxxxxxxxxxxxxx(?Pabcxxxxxxxxxx~20 chars~\n" &
      "                        ^"
  check(raisesMsg(r"""(?x)  # comment
      (?Pabc  # comment
      # comment""") ==
    "Invalid group name. Missing `<`\n" &
    "~8 chars~comment       (?Pabc  # commen~17 chars~\n" &
    "                       ^")
  check raisesMsg(r"aaa(?Pabc") ==
    "Invalid group name. Missing `<`\n" &
    "aaa(?Pabc\n" &
    "   ^"
  check raisesMsg(r"(?Pabc") ==
    "Invalid group name. Missing `<`\n" &
    "(?Pabc\n" &
    "^"
  # unicode chars may have a wider width,
  # so better to just truncate them
  check raisesMsg(r"弢(?Pabc") ==
    "Invalid group name. Missing `<`\n" &
    "~1 chars~(?Pabc\n" &
    "         ^"
  check raisesMsg(r"弢弢弢(?Pabc") ==
    "Invalid group name. Missing `<`\n" &
    "~3 chars~(?Pabc\n" &
    "         ^"
  check raisesMsg(r"弢aaa(?Pabc") ==
    "Invalid group name. Missing `<`\n" &
    "~4 chars~(?Pabc\n" &
    "         ^"

test "treuse_regex_match":
  block:
    var m = RegexMatch()
    check "2222".find(re"(22)*", m)
    check m.group(0) == @[0 .. 1, 2 .. 3]

    check "abcd".find(re"(ab)", m)
    check m.group(0) == @[0 .. 1]

    check "abcd".find(re"(bc)", m)
    check m.group(0) == @[1 .. 2]

    check "abcd".find(re"(cd)", m)
    check m.group(0) == @[2 .. 3]

  block:
    var m = RegexMatch()
    check "foobar".match(re"(?P<foo>(?P<bar>\w*))", m)
    check m.group("foo") == @[0..5]
    check m.group("bar") == @[0..5]

    check "foobar".match(re"(?P<foo>\w*)", m)
    check m.group("foo") == @[0..5]
    expect(ValueError):
      discard m.group("bar")

test "tisInitialized":
  block:
    var re: Regex
    check(not re.isInitialized)
    re = re"foo"
    check re.isInitialized

test "capturingGroupsNames":
  block:
    let text = "hello world"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello) (?P<who>world)", m)
    check m.groupsCount == 2
    for name in @["greet", "who"]:
      check m.groupNames.contains(name)
  block:
    let text = "hello world"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello) (?P<who>world)", m)
    check m.group("greet", text) == @["hello"]
    check m.group("who", text) == @["world"]
  block:
    let text = "hello world foo bar"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello) (?:(?P<who>[^\s]+)\s?)+", m)
    check m.group("greet", text) == @["hello"]
    let whoGroups = m.group("who", text)
    for w in @["foo", "bar", "world"]:
      check whoGroups.contains(w)
  block:
    let text = "hello world"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello) (?P<who>world)", m)
    check m.groupFirstCapture("greet", text) == "hello"
    check m.groupFirstCapture("who", text) == "world"
  block:
    let text = "hello world her"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello) (?P<who>world) (?P<who>her)", m)
    check m.groupFirstCapture("greet", text) == "hello"
  block:
    let text = "hello world foo bar"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello) (?:(?P<who>[^\s]+)\s?)+", m)
    # "who" captures @["world", "foo", "bar"]
    check m.groupFirstCapture("who", text) == "world"
  block:
    let text = "hello"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello)\s?(?P<who>world)?", m)
    check m.groupFirstCapture("greet", text) == "hello"
    check m.groupFirstCapture("who", text) == ""
  block:
    let text = "hello world her"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello) (?P<who>world) (?P<who>her)", m)
    check m.groupLastCapture("who", text) == "her"
  block:
    let text = "hello world foo bar"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello) (?:(?P<who>[^\s]+)\s?)+", m)
    # "who" captures @["world", "foo", "bar"]
    check m.groupLastCapture("who", text) == "bar"
  block:
    let text = "hello"
    var m = RegexMatch()
    check text.match(re"(?P<greet>hello)\s?(?P<who>world)?", m)
    check m.groupLastCapture("greet", text) == "hello"
    check m.groupLastCapture("who", text) == ""
  block:
    let text = "hello"
    var m = RegexMatch()
    check text.match(re"(hello)\s?(world)?", m)
    check m.groupLastCapture(0, text) == "hello"
    check m.groupLastCapture(1, text) == ""
  block:
    let text = ""
    var m = RegexMatch()
    check text.match(re"(hello)?\s?(world)?", m)
    check m.groupLastCapture(0, text) == ""
    check m.groupLastCapture(1, text) == ""
  block:
    let text = "hello world foo bar"
    var m = RegexMatch()
    check text.match(re"(hello) (?:([^\s]+)\s?)+", m)
    # "who" captures @["world", "foo", "bar"]
    check m.groupLastCapture(1, text) == "bar"
  block:
    let text = "hello"
    var m = RegexMatch()
    check text.match(re"(hello)\s?(world)?", m)
    check m.groupFirstCapture(0, text) == "hello"
    check m.groupFirstCapture(1, text) == ""
  block:
    let text = ""
    var m = RegexMatch()
    check text.match(re"(hello)?\s?(world)?", m)
    check m.groupFirstCapture(0, text) == ""
    check m.groupFirstCapture(1, text) == ""
  block:
    let text = "hello world foo bar"
    var m = RegexMatch()
    check text.match(re"(hello) (?:([^\s]+)\s?)+", m)
    # "who" captures @["world", "foo", "bar"]
    check m.groupFirstCapture(1, text) == "world"

# XXX raise a compile error when regex contains unicode
#     in ascii mode
test "tflags":
  var m = RegexMatch()
  #check match("abc", re(r"abc", {reAscii}), m)
  check match("弢弢弢", re"\w{3}", m)
  #check(not match("弢弢弢", re(r"\w{3}", {reAscii}), m))
  check re"\w" in "弢"
  #check re(r"\w", {reAscii}) notin "弢"
  #check re(r"\w", {reAscii}) in "a"
  #check "%ab%".find(re(r"\w{2}", {reAscii}), m)
  check "%弢弢%".find(re"\w{2}", m)
  #check(not "%弢弢%".find(re(r"\w{2}", {reAscii}), m))

test "tfindopt":
  var m = RegexMatch()
  check(not find("bar", re"foo", m))
  check(not find("bar", re"baz", m))
  check "abcd".find(re"bc", m)
  check m.boundaries == 1 .. 2
  check "bcd".find(re"bc", m)
  check m.boundaries == 0 .. 1
  check "bc".find(re"bc", m)
  check m.boundaries == 0 .. 1
  check "ababcd".find(re"bc", m)
  check m.boundaries == 3 .. 4
  check "abc@xyz".find(re"\w@", m)
  check m.boundaries == 2 .. 3
  check "ab1c@xyz".find(re"\d\w@", m)
  check m.boundaries == 2 .. 4
  check "##axyz##".find(re"(a|b)xyz", m)
  check m.boundaries == 2 .. 5
  check "##bxyz##".find(re"(a|b)xyz", m)
  check m.boundaries == 2 .. 5
  check "##x#ax#axy#bxyz##".find(re"(a|b)xyz", m)
  check m.boundaries == 11 .. 14
  check "##z#xyz#yz#bxyz##".find(re"(a|b)xyz", m)
  check m.boundaries == 11 .. 14
  check "#xabcx#abc#".find(re"\babc\b", m)
  check m.boundaries == 7 .. 9
  check "#foo://#".find(re"[\w]+://", m)
  check m.boundaries == 1 .. 6
  check "x#foo://#".find(re"[\w]+://", m)
  check m.boundaries == 2 .. 7

test "tfindallopt":
  check findAllBounds("bar", re"foo").len == 0
  check findAllBounds("bar", re"baz").len == 0
  check findAllBounds("abcd", re"bc") ==
    @[1 .. 2]
  check findAllBounds("bcd", re"bc") ==
    @[0 .. 1]
  check findAllBounds("bc", re"bc") ==
    @[0 .. 1]
  check findAllBounds("ababcd", re"bc") ==
    @[3 .. 4]
  check findAllBounds("abcdbcbc", re"bc") ==
    @[1 .. 2, 4 .. 5, 6 .. 7]
  check findAllBounds("abc@xyz", re"\w@") ==
    @[2 .. 3]
  check findAllBounds("abc@xyz@@", re"\w@") ==
    @[2 .. 3, 6 .. 7]
  check findAllBounds("ab1c@xyz", re"\d\w@") ==
    @[2 .. 4]
  check findAllBounds("ab1c@xy1z@z@", re"\d\w@") ==
    @[2 .. 4, 7 .. 9]
  check findAllBounds("##axyz##", re"(a|b)xyz") ==
    @[2 .. 5]
  check findAllBounds("##bxyz##", re"(a|b)xyz") ==
    @[2 .. 5]
  check findAllBounds("##axyz##bxyz", re"(a|b)xyz") ==
    @[2 .. 5, 8 .. 11]
  check findAllBounds("##x#ax#axy#bxyz##", re"(a|b)xyz") ==
    @[11 .. 14]
  check findAllBounds("##z#xyz#yz#bxyz##", re"(a|b)xyz") ==
    @[11 .. 14]
  check findAllBounds("#xabcx#abc#", re"\babc\b") ==
    @[7 .. 9]
  check findAllBounds("#xabcx#abc#abc", re"\babc\b") ==
    @[7 .. 9, 11 .. 13]
  check findAllBounds("#foo://#", re"[\w]+://") ==
    @[1 .. 6]
  check findAllBounds("x#foo://#", re"[\w]+://") ==
    @[2 .. 7]
  check findAllBounds("foobarbaz", re"(?<=o)b") ==
    @[3 .. 3]
  check findAllBounds("foobarbaz", re"(?<=r)b") ==
    @[6 .. 6]
  check findAllBounds("foobarbaz", re"(?<!o)b") ==
    @[6 .. 6]
  check findAllBounds("foobarbaz", re"(?<!r)b") ==
    @[3 .. 3]
  check findAllBounds("x@x@y@y@y@x@xy@yx@@", re"(?<!x)@\w+") ==
    @[5 .. 6, 7 .. 8, 9 .. 10, 14 .. 16]
  check findAllBounds("foobar", re"o(?=b)") ==
    @[2 .. 2]
  check findAllBounds("foobarbaz", re"a(?=r)") ==
    @[4 .. 4]
  check findAllBounds("foobar", re"o(?!b)") ==
    @[1 .. 1]
  check findAllBounds("foobarbaz", re"a(?!r)") ==
    @[7 .. 7]
  check findAllBounds("x@x@y@y@y@x@xy@yx@@", re"\w+@(?!x)") ==
    @[2 .. 3, 4 .. 5, 6 .. 7, 12 .. 14, 15 .. 17]
  check findAllBounds("abcdef", re"^abcd") ==
    @[0 .. 3]
  check findAllBounds("abcdef", re"cdef$") ==
    @[2 .. 5]
  check findAllBounds("abcdef", re"^abcdef$") ==
    @[0 .. 5]
  check findAllBounds("abcdef", re"^abcx").len == 0
  check findAllBounds("abcdef", re"xcdef$").len == 0
  check findAllBounds("abc\nabc\na", re"(?m)^a") ==
    @[0 .. 0, 4 .. 4, 8 .. 8]
  check findAllBounds("x\nabcxx\nabcxx", re"(?m)x$\n?[^x]*") ==
    @[0 .. 4, 6 .. 10, 12 .. 12]
  check findAllBounds("#f1o2o3@bar#", re"(\w\d)*?@\w+") ==
    @[1 .. 10]
  check findAllBounds("foo@bar@baz", re"\w+@\w+") ==
    @[0 .. 6]
  check findAllBounds("foo@111@111", re"\w+@\d+") ==
    @[0 .. 6]
  check findAllBounds("foo@111@111", re"\w*@\d+") ==
    @[0 .. 6, 7 .. 10]
  check findAllBounds("foo@111@@111", re"\w+@\d+") ==
    @[0 .. 6]
  check findAllBounds("foo@111foo@111", re"\w+@\d+") ==
    @[0 .. 6, 7 .. 13]
  check findAllBounds("111@111@111", re"\d+@\w+") ==
    @[0 .. 6]
  check findAllBounds("abbbbccccd@abbbbccccd@", re"\w(b|c)*d@") ==
    @[0 .. 10, 11 .. 21]
  check findAllBounds("abXabX", re"\w(b|c)*@").len == 0
  check findAllBounds("abbcXabbcX", re"((a(b)*)+(c))") ==
    @[0 .. 3, 5 .. 8]
  check findAllBounds("aaanasdnasd@aaanasdnasd@", re"((a)*n?(asd)*)+@") ==
    @[0 .. 11, 12 .. 23]
  check findAllBounds("a1@a1a1@", re"(\w\d)+@") ==
    @[0 .. 2, 3 .. 7]
  check findAllBounds("a1@a1a1@a@a", re"(\w\d)+@") ==
    @[0 .. 2, 3 .. 7]
  check findAllBounds("a1@a1a1@a@a1a@1@a@a1@", re"(\w\d)+@") ==
    @[0 .. 2, 3 .. 7, 18 .. 20]
  check findAllBounds("a1@a1a1@", re"(\w\d)*@") ==
    @[0 .. 2, 3 .. 7]
  check findAllBounds("a1@a1a1@a@a", re"(\w\d)*@") ==
    @[0 .. 2, 3 .. 7, 9 .. 9]
  check findAllBounds("a1@a1a1@", re"(\w\d)+?@") ==
    @[0 .. 2, 3 .. 7]
  check findAllBounds("a1@a1a1@", re"(\w\d)*?@") ==
    @[0 .. 2, 3 .. 7]
  check findAllBounds("a1@a1a1@", re"\w+\d+?@") ==
    @[0 .. 2, 3 .. 7]
  check findAllBounds("a1@a1a1@", re"\w+?\d+@") ==
    @[0 .. 2, 3 .. 7]
  check findAllBounds("a1@a1a1@", re"\w+?\d+?@") ==
    @[0 .. 2, 3 .. 7]
  check findAllBounds("ab@&%", re"(a|ab)\w@&%") ==
    @[0 .. 4]
  check findAllBounds("abc@&%", re"(a|ab)\w@&%") ==
    @[0 .. 5]
  check findAllBounds("ab@&%abc@&%", re"(a|ab)\w@&%") ==
    @[0 .. 4, 5 .. 10]
  check findAllCapt("ab@&%", re"(?:(a)|(ab))\w@&%") ==
    @[@[@[0 .. 0], @[]]]
  check findAllCapt("a#b@&%", re"(?:(a)|(a#))\w@&%") ==
    @[@[@[], @[0 .. 1]]]
  check findAllCapt("abc@&%", re"(?:(a)|(ab))\w@&%") ==
    @[@[@[], @[0 .. 1]]]
  check findAllCapt("aba@&%", re"(?:(ab)|(a))\w@&%") ==
    @[@[@[0 .. 1], @[]]]
  check findAllCapt("ab@&%abc@&%", re"(?:(a)|(ab))\w@&%") ==
    @[@[@[0 .. 0], @[]], @[@[], @[5 .. 6]]]
  check findAllCapt("abc@&%ab@&%", re"(?:(a)|(ab))\w@&%") ==
    @[@[@[], @[0 .. 1]], @[@[6 .. 6], @[]]]
  check findAllBounds("x@xxzx@xxz", re"\w+@\w+(?=z)") == @[0 .. 3, 4 .. 8]
  check findAllBounds("1x@11zx@xxz", re"\d+\w+@\w+(?=z)") == @[0 .. 4]
  check findAllBounds("1x@11zx@xxz", re"\d+\w+@\w+") == @[0 .. 6]
  check findAllBounds("1x@1x@1x", re"\d+\w+@\w+") == @[0 .. 4]
  check findAllBounds("1x@1x@1x", re"\d+\w+@(\d\w)+") == @[0 .. 4]
  check findAllBounds("2222", re"22") == @[0 .. 1, 2 .. 3]
  block overlapTests:
    check findAllBounds("1x@1xx@1x", re"\d+\w+@(1\w)+") == @[0 .. 4]
    check findAllBounds("1x@1x1xx@1x", re"\d+\w+@(1\w)+") == @[0 .. 6]
    check findAllBounds("1x@1xx1x@1x", re"\d+\w+@(1\w)+") == @[0 .. 4, 6 .. 10]
    check findAllBounds("1x@1xx@1x", re"\d\w+@(\d\w)+") == @[0 .. 4]
    check findAllBounds("2x1x@xx", re"(1\w)+@\w+") == @[2 .. 6]
    check findAllBounds("2x1x1x@xx", re"(1\w)+@\w+") == @[2 .. 8]
  check findAllBounds("bcbc#bc", re"\bbc\B") == @[0 .. 1]
  check findAllBounds("bcbc#bca", re"\bbc\B") == @[0 .. 1, 5 .. 6]
  check findAllBounds("bcbc#bc#xbcx#bcbcbc#bc", re"\bbc\B") ==
    @[0 .. 1, 13 .. 14]
  check findAllBounds("bcbc#bc#xbcx#bcbcbc#bc", re"\Bbc\b") ==
    @[2 .. 3, 17 .. 18]
  check findAllBounds("bcbc", re"\Bbc") == @[2 .. 3]
  check findAllBounds("bcbcbc", re"\Bbc") == @[2 .. 3, 4 .. 5]
  check findAllBounds("bc#bc#xbcx", re"\Bbc\B") == @[7 .. 8]
  check findAllBounds("bcbc#bca", re"\Bbc\b") == @[2 .. 3]
  check findAllBounds("bcbc#bc", re"\Bbc\b") == @[2 .. 3]
  check findAllBounds("abcabc", re"bc") == @[1 .. 2, 4 .. 5]
  check findAllBounds("bcbc#bc", re"\bbc\b") == @[5 .. 6]
  check findAllBounds("bcbc", re"\bbc") == @[0 .. 1]
  check findAllBounds("bc#bc", re"\bbc\b") == @[0 .. 1, 3 .. 4]
  check findAllBounds("bcabc", re"^bc") == @[0 .. 1]
  check findAllBounds("bcbc", re"^bc") == @[0 .. 1]
  check findAllBounds("bcabc\nbc\nabc\nbcbc", re"(?m)^bc") ==
    @[0 .. 1, 6 .. 7, 13 .. 14]
  check findAllBounds("弢@弢@弢", re"\w+@\w+") == @[0 .. 8]
  check findAllBounds("弢ⒶΪ@弢ⒶΪ@弢ⒶΪ", re"\w+@\w+") == @[0 .. 18]
  check findAllBounds("۲@弢۲⅕@弢", re"\d+@\w+") == @[0 .. 8]
  check findAllBounds("۲۲@弢ⒶΪ11@弢ⒶΪ", re"\d+@弢ⒶΪ") ==
    @[0 .. 13, 14 .. 25]
  check findAllBounds("#xa弢ⒶΪx#a弢ⒶΪ#", re"\ba弢ⒶΪ\b") == @[14 .. 23]
  check findAllBounds("#xa弢ⒶΪx#a弢ⒶΪ#a弢ⒶΪ", re"\ba弢ⒶΪ\b") ==
    @[14 .. 23, 25 .. 34]
  check findAllBounds("۲Ⓐ@۲弢Ϊ@۲弢", re"\d+\w+@(۲\w)+") == @[0 .. 11]
  check findAllBounds("۲弢@۲弢۲ΪΪ@۲弢", re"\d+\w+@(۲\w)+") == @[0 .. 16]
  check findAllBounds("۲Ϊ@۲弢Ⓐ۲Ⓐ@۲弢", re"\d+\w+@(۲\w)+") ==
    @[0 .. 10, 14 .. 25]

test "tmisc2":
  var m = RegexMatch()
  check "one<TAG>two</TAG>tree".find(re"<TAG>.*?</TAG>", m)
  check m.boundaries == 3 .. 16
  check "one<TAG>two</TAG>tree".find(re"<TAG>[\w<>/]*?</TAG>", m)
  check m.boundaries == 3 .. 16
  check "<TAG>two</TAG>".match(re"<TAG>.*?</TAG>", m)
  check match("abc", re"abc", m)
  check match("ab", re"a(b|c)", m)
  check match("ac", re"a(b|c)", m)
  check(not match("ad", re"a(b|c)", m))
  check match("ab", re"(ab)*", m)
  check match("abab", re"(ab)*", m)
  check(not match("ababc", re"(ab)*", m))
  check(not match("a", re"(ab)*", m))
  check match("ab", re"(ab)+", m)
  check match("abab", re"(ab)+", m)
  check(not match("ababc", re"(ab)+", m))
  check(not match("a", re"(ab)+", m))
  check match("aa", re"\b\b\baa\b\b\b", m)
  check(not match("cac", re"c\ba\bc", m))
  check match("abc", re"[abc]+", m)
  check match("abc", re"[\w]+", m)
  check match("弢弢弢", re"[\w]+", m)
  check(not match("abc", re"[\d]+", m))
  check match("123", re"[\d]+", m)
  check match("abc$%&", re".+", m)
  check(not match("abc$%&\L", re"(.+)", m))
  check(not match("abc$%&\L", re".+", m))
  check(not match("弢", re"\W", m))
  check match("$%&", re"\W+", m)
  check match("abc123", re"[^\W]+", m)
  check match("aabcd", re"(aa)bcd", m) and
    m.captures == @[@[0 .. 1]]
  check match("aabc", re"(aa)(bc)", m) and
    m.captures == @[@[0 .. 1], @[2 .. 3]]
  check match("ab", re"a(b|c)", m) and
    m.captures == @[@[1 .. 1]]
  check match("ab", re"(ab)*", m) and
    m.captures == @[@[0 .. 1]]
  check match("abab", re"(ab)*", m) and
    m.captures == @[@[0 .. 1, 2 .. 3]]
  check match("ab", re"((a))b", m) and
    m.captures == @[@[0 .. 0], @[0 .. 0]]
  check match("c", re"((ab)*)c", m) and
    m.captures == @[@[0 .. -1], @[]]
  check match("aab", re"((a)*b)", m) and
    m.captures == @[@[0 .. 2], @[0 .. 0, 1 .. 1]]
  check match("abbbbcccc", re"a(b|c)*", m) and
    m.captures == @[@[1 .. 1, 2 .. 2, 3 .. 3, 4 .. 4, 5 .. 5, 6 .. 6, 7 .. 7, 8 .. 8]]
  check match("ab", re"(a*)(b*)", m) and
    m.captures == @[@[0 .. 0], @[1 .. 1]]
  check match("ab", re"(a)*(b)*", m) and
    m.captures == @[@[0 .. 0], @[1 .. 1]]
  check match("ab", re"(a)*b*", m) and
    m.captures == @[@[0 .. 0]]
  check match("abbb", re"((a(b)*)*(b)*)", m) and
    m.captures == @[@[0 .. 3], @[0 .. 3], @[1 .. 1, 2 .. 2, 3 .. 3], @[]]
  check match("aa", re"(a)+", m) and
    m.captures == @[@[0 .. 0, 1 .. 1]]
  check match("abab", re"(ab)+", m) and
    m.captures == @[@[0 .. 1, 2 .. 3]]
  check match("a", re"(a)?", m) and
    m.captures == @[@[0 .. 0]]
  check match("ab", re"(ab)?", m) and
    m.captures == @[@[0 .. 1]]
  check match("aaabbbaaa", re"(a*|b*)*", m) and
    m.captures == @[@[0 .. 2, 3 .. 5, 6 .. 8, 9 .. 8]]
  check match("abab", re"(a(b))*", m) and
    m.captures == @[@[0 .. 1, 2 .. 3], @[1 .. 1, 3 .. 3]]
  check match("aaanasdnasd", re"((a)*n?(asd)*)*", m) and
    m.captures == @[
      @[0 .. 6, 7 .. 10, 11 .. 10],
      @[0 .. 0, 1 .. 1, 2 .. 2],
      @[4 .. 6, 8 .. 10]]
  check match("aaanasdnasd", re"((a)*n?(asd))*", m) and
    m.captures == @[@[0 .. 6, 7 .. 10], @[0 .. 0, 1 .. 1, 2 .. 2], @[4 .. 6, 8 .. 10]]

test "tmisc2_5":
  var m = RegexMatch()
  check match("abd", re"((ab)c)|((ab)d)", m) and
    m.captures == @[@[], @[], @[0 .. 2], @[0 .. 1]]
  check match("aaa", re"(a*)", m) and
    m.captures == @[@[0 .. 2]]
  check match("aaaa", re"(a*)(a*)", m) and
    m.captures == @[@[0 .. 3], @[4 .. 3]]
  check match("aaaa", re"(a*?)(a*?)", m) and
    m.captures == @[@[0 .. -1], @[0 .. 3]]
  check match("aaaa", re"(a)*(a)", m) and
    m.captures == @[@[0 .. 0, 1 .. 1, 2 .. 2], @[3 .. 3]]
  check "11222211".find(re"(22)+", m) and
    m.group(0) == @[2 .. 3, 4 .. 5]
  check match("650-253-0001", re"[0-9]+-[0-9]+-[0-9]+", m)
  check(not match("abc-253-0001", re"[0-9]+-[0-9]+-[0-9]+", m))
  check(not match("650-253", re"[0-9]+-[0-9]+-[0-9]+", m))
  check(not match("650-253-0001-abc", re"[0-9]+-[0-9]+-[0-9]+", m))
  check match("650-253-0001", re"[0-9]+..*", m)
  check(not match("abc-253-0001", re"[0-9]+..*", m))
  check(not match("6", re"[0-9]+..*", m))
  block:
    const re1 = re"((11)*)+(111)*"
    check match("", re1)
    check match("11", re1)
    check match("111", re1)
    check match("11111", re1)
    check match("1111111", re1)
    check match("1111111111", re1)
    check(not match("1", re1))
  block:
    const re1 = re"(11)+(111)*"
    check(not match("", re1))
    check match("11", re1)
    check(not match("111", re1))
    check match("11111", re1)

test "tmisc2_6":
  block:
    const re1 = re"(aabb)(ab)*"
    check match("aabb", re1)
    check match("aabbab", re1)
    check match("aabbabab", re1)
    check(not match("ab", re1))
    check(not match("aabbaba", re1))
  block:
    const re1 = re"0(10)*"
    check match("0", re1)
    check match("010", re1)
    check(not match("", re1))
    check(not match("0101", re1))
    check(not match("0100", re1))
    check(not match("00", re1))
    check(not match("000", re1))
  block:
    const re1 = re"(11)*|(111)*"
    check match("", re1)
    check match("11", re1)
    check match("111", re1)
    check match("1111", re1)
    check match("111111", re1)
    check(not match("1", re1))

test "tmisc3":
  var m = RegexMatch()
  block:  # issue #61
    const a = "void __mingw_setusermatherr (int (__attribute__((__cdecl__)) *)(struct _exception *));"
    check replace(a, re"__attribute__[ ]*\(\(.*?\)\)([ ,;])", "$1") ==
      "void __mingw_setusermatherr (int ( *)(struct _exception *));"
    check replace(a, re"__attribute__[ ]*\(\(.*?\)\)(.*?[ ,;])", "$1") ==
      "void __mingw_setusermatherr (int ( *)(struct _exception *));"
    check find(a, re"__attribute__[ ]*\(\(.*?\)\)([ ,;])", m) and
      a[m.boundaries] == "__attribute__((__cdecl__)) "
    check find(a, re"__attribute__[ ]*\(\(.*?\)\)(.*?[ ,;])", m) and
      a[m.boundaries] == "__attribute__((__cdecl__)) "
    # non-greedy
    check find(a, re"__attribute__[ ]*\(\(.*\)\)([ ,;])", m) and
      a[m.boundaries] == "__attribute__((__cdecl__)) *)(struct _exception *));"
  # issue #29
  check replace("foo", re"", "-") == "-f-o-o-"
  block:  # issue #13
    const input = """foo
              bar
      baxx
                bazz
    """
    const expected = """//foo
//              bar
//      baxx
//                bazz
//    """
    check replace(input, re"(?m)^", "//") == expected
  check replace("bar", re"^", "foo") == "foobar"
  check replace("foo", re"$", "bar") == "foobar"
  block:
    const input = """foo
              bar
      baxx
                bazz
    """
    const expected = """foo//
              bar//
      baxx//
                bazz//
    //"""
    check replace(input, re"(?m)$", "//") == expected
  check(not find("foobarbar", re"^bar", m, start=3))
  check find("foobar\nbar", re"(?m)^bar", m, start=3) and
    m.boundaries == 7 .. 9
  check find("foo\nbar\nbar", re"(?m)^bar", m, start=3) and
    m.boundaries == 4 .. 6
  check find("foo\nbar\nbar", re"(?m)^bar", m, start=4) and
    m.boundaries == 4 .. 6
  block:
    # The bounds must contain the empty match index
    check find("foo\nbar\nbar", re"(?m)^", m) and
      m.boundaries == 0 .. -1
    check find("foo\nbar\nbar", re"(?m)^", m, start=1) and
      m.boundaries == 4 .. 3
    check find("foo\nbar\nbar", re"(?m)^", m, start=4) and
      m.boundaries == 4 .. 3
    check find("foo\nbar\nbar", re"(?m)^", m, start=5) and
      m.boundaries == 8 .. 7
    check find("foo\nbar\nbar", re"(?m)^", m, start=8) and
      m.boundaries == 8 .. 7
    check(not find("foo\nbar\nbar", re"(?m)^", m, start=9))
    check find("foo\nbar\nbar", re"(?m)$", m) and
      m.boundaries == 3 .. 2
    check find("foo\nbar\nbar", re"(?m)$", m, start=3) and
      m.boundaries == 3 .. 2
    check find("foo\nbar\nbar", re"(?m)$", m, start=4) and
      m.boundaries == 7 .. 6
    check find("foo\nbar\nbar", re"(?m)$", m, start=7) and
      m.boundaries == 7 .. 6
    check find("foo\nbar\nbar", re"(?m)$", m, start=8) and
      m.boundaries == 11 .. 10
    check find("foo\nbar\nbar", re"(?m)$", m, start=11) and
      m.boundaries == 11 .. 10
    # start is out of bounds, but this is what Nim's re
    # does, nre throws an error
    #check find("foo\nbar\nbar", re"(?m)$", m, start=12) and
    #  m.boundaries == 12 .. 11
  # XXX make this return false?
  check match("abc", re"(?m)$", m, start=50)
  check match("abc", re"(?m)^", m, start=50)
  # this should be valid though
  check match("", re"(?m)^", m)
  check match("", re"(?m)$", m)
  block:
    const input = """foo
              bar
      baxx
                bazz
    """
    const expected = @[
      "foo\n",
      "              bar\n",
      "      baxx\n",
      "                bazz\n",
      "    "
    ]
    check split(input, re"(?m)^") == expected
  block:
    const input = """foo
              bar
      baxx
                bazz
    """
    const expected = @[
      "foo",
      "\n              bar",
      "\n      baxx",
      "\n                bazz",
      "\n    "
    ]
    check split(input, re"(?m)$") == expected
  check split("acb\nb\nc\n", re"(?m)^") == @["acb\n", "b\n", "c\n"]
  check split("a b", re"\b") == @["a", " ", "b"]
  check split("ab", re"\b") == @["ab"]
  check split("iaiaiai", re"i") == @["", "a", "a", "a", ""]
  check split("aiaia", re"i") == @["a", "a", "a"]
  check split("aaa", re"a") == @["", "", "", ""]
  check split("a\na\na", re"(?m)^") == @["a\n", "a\n", "a"]
  check split("\n\n", re"(?m)^") == @["\n", "\n"]
  check split("foobar", re"(?<=o)b") == @["foo", "ar"]
  check split("foobarbaz", re"(?<!o)b") == @["foobar", "az"]
  check split("foobar", re"o(?=b)") == @["fo", "bar"]
  check split("foobar", re"o(?!b)") == @["f", "obar"]
  block:
    var m = RegexMatch()
    check find("abcxyz", re"(abc)|\w+", m)
    check m.boundaries == 0 .. 2
    check find("xyzabc", re"(abc)|\w+", m)
    check m.boundaries == 0 .. 5
  check findAndCaptureAll(
    "He was carefully disguised but captured quickly by police.",
    re"\w+ly") == @["carefully", "quickly"]

test "fix#83":
  block:
    let pattern = "^src/(?:[^\\/]*(?:\\/|$))*[^/]*\\.nim$"
    check "src/dir/foo.nim".match(pattern.re)
  check match("foo", re"(?:$)*\w*")
  check match("foo", re"($)*\w*")
  check match("foo", re"^*\w*")
  check match("foo", re"([^/]*$)*\w*")
  check match("src/dir/foo.nim", re"^src/(?:[^/]*(?:/|$))*[^/]*\.nim$")
  check(not match("foo", re"$($*)*(\w*)"))
  check(not match("foo", re"$($$*$*)*(\w*)"))
  check(not match("foo", re"($|$)($*)*(\w*)"))
  check(not match("foo", re"($|$$)($*)*(\w*)"))
  check(not match("foo", re"($$|$)($*)*(\w*)"))
  check(not match("foo", re"($|$)(\w*)"))
  check(not match("foo", re"($|$$)(\w*)"))
  check(not match("foo", re"($$|$)(\w*)"))
  check(not match("foo", re"(^$|$)(\w*)"))
  check(not match("foo", re"($|^$)(\w*)"))
  check match("", re"$*\w*")
  check match("foo", re"($*?)(\w*)")
  check match("foo", re"($*)(\w*)")
  check match("foox", re"($*)(\w*)x")
  check match("foo", re"(a*?$*?)*?(\w*)")
  check match("foox", re"(a*$*)*(\w*)x")
  check match("aaa", re"($|^)(a*)")
  check match("aaa", re"(^|$)(a*)")
  block:
    check findAllBounds("aaaxaaa", re"^(a*)") == @[0 .. 2]
    check findAllBounds("aaaxaaa", re"$(a*)") == @[7 .. 6]
    check findAllBounds("aaaxaaa", re"($|^)(a*)") == @[0 .. 2, 7 .. 6]
    check findAllBounds("aaaxaaa", re"(^|$)(a*)") == @[0 .. 2, 7 .. 6]

test "escapeRe":
  check escapeRe("abc") == "abc"
  check escapeRe("123") == "123"
  check escapeRe("!") == "!"
  check escapeRe("*") == r"\*"
  check escapeRe" #$&()*+-.?[\]^{|}~" ==
    r"\ \#\$\&\(\)\*\+\-\.\?\[\\\]\^\{\|\}\~"
  check escapeRe("\L") == "\\\L"
  check escapeRe"aΪⒶ弢" == "aΪⒶ弢"
  check escapeRe"$Ϊ$Ⓐ$弢$" == r"\$Ϊ\$Ⓐ\$弢\$"
  check match("$", re(escapeRe"$"))
  block:
    var s = ""
    for c in 0 .. 127:
      s.add c.char
    discard re(escapeRe(s))

test "issue_98":
  check match("", re"|")
  check match("a", re"a|")
  check match("", re"a|")
  check(not match("b", re"a|"))
  check match("b", re"|b")
  check match("", re"|b")
  check(not match("a", re"|b"))
  check match("", re"(|)")
  check match("a", re"(a|)")
  check match("", re"(a|)")
  check(not match("b", re"(a|)"))
  check match("b", re"(|b)")
  check match("", re"(|b)")
  check(not match("a", re"(|b)"))
  check match("", re"||")
  check match("a", re"a||")
  check match("", re"a||")
  check match("b", re"||b")
  check match("", re"||b")
  check match("", re"a||b")
  check match("a", re"a||b")
  check match("b", re"a||b")
  check match("", re"|||")
  check match("a", re"a|||")
  check match("", re"a|||")
  check match("b", re"|||b")
  check match("", re"|||b")
  check match("a", re"a|||b")
  check match("b", re"a|||b")
  check match("", re"(||)")
  check match("a", re"(a||)")
  check match("", re"(a||)")
  check match("b", re"(||b)")
  check match("", re"(||b)")
  check match("1.1.1.1", re"(\d+)\.(\d+)(\.(\d+)|)(\.(\d+)|)")
  check match("1.1.1", re"(\d+)\.(\d+)(\.(\d+)|)(\.(\d+)|)")
  check match("1.1", re"(\d+)\.(\d+)(\.(\d+)|)(\.(\d+)|)")

test "issue_101":
  var m = RegexMatch()
  check match("TXT1/TXT2.1", re"(TXT1)/TXT2()\.(\d+)")
  check match("TXT1/TXT2.1", re"(TXT1)/TXT2(?:)\.(\d+)")
  check match("TXT1/TXT2.1", re"(TXT1)/TXT2(?i:)\.(\d+)")
  check match("TXT1/TXT2.1", re"(TXT1)/TXT2(?P<foo>)\.(\d+)")
  check match("TXT1/TXT2.1", re"(TXT1)/TXT2()\.(\d+)", m) and
    m.captures == @[@[0 .. 3], @[9 .. 8], @[10 .. 10]]
  check match(" ", re"(?x)     (?-x) ")
  check match("aa", re"((?x)a    )a")
  check match("aa", re"((?x)   a    )a")
  check match(" ", re"((?x)) ")
  check match(" ", re"((?x)     ) ")
  check match(" ", re"(?x:(?x)     ) ")
  check match(" ", re"(?x:) ")
  check match(" ", re"(?x:   ) ")
  check match(" ", re"((?x:)) ")
  check match("A", re"(?xi)     a")
  check(not match("A", re"((?xi))     a"))
  check(not match("A", re"(?xi:(?xi)     )a"))

{.pop.}  # {.push warning[Deprecated]: off.}
