From bd15697da612a6ca8b87b2d2ac08b65c24cbb953 Mon Sep 17 00:00:00 2001 From: nitely Date: Fri, 17 Jan 2025 07:43:30 -0300 Subject: [PATCH 01/16] Sigil regex runtime compiled --- src/regex.nim | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/regex.nim b/src/regex.nim index 9f4aed6..c4b2a7d 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -502,6 +502,18 @@ when not defined(forceRegexAtRuntime): ## Parse and compile a regular expression at compile-time toRegex2 reCt(s, flags) +proc reCheck(s: string) {.compileTime.} = + try: + discard re2(s) + except RegexError: + raise newException(RegexError, getCurrentExceptionMsg()) + +func `~`*(s: static[string]): Regex2 = + static: reCheck(s) + {.cast(noSideEffect), cast(gcsafe).}: + var reg {.global.} = re2(s) + return reg + func group*(m: RegexMatch2, i: int): Slice[int] {.inline, raises: [].} = ## return slice for a given group. ## Slice of start > end are empty @@ -1555,6 +1567,9 @@ when isMainModule: doAssert(not match("A", re2"((?xi)) a")) doAssert(not match("A", re2"(?xi:(?xi) )a")) + doAssert ~"ab" in "abcd" + doAssert ~"zx" notin "abcd" + # bug: raises invalid utf8 regex in Nim 1.0 + js target when not defined(js) or NimMajor >= 2: block: @@ -1701,6 +1716,8 @@ when isMainModule: m.captures == @[0 .. 3, reNonCapture] doAssert match("aaab", re2"(\w+)|\w+(?<=^(\w)(\w+))b", m) and m.captures == @[0 .. 3, reNonCapture, reNonCapture] + doAssert ~"ab" in "abcd" + doAssert ~"zx" notin "abcd" block: var m = false var matches = newSeq[string]() From 3675e1f7b185c7445dfd1b210aacc724016784b2 Mon Sep 17 00:00:00 2001 From: nitely Date: Fri, 17 Jan 2025 08:25:18 -0300 Subject: [PATCH 02/16] wip --- src/regex.nim | 2 +- tests/tests2.nim | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/regex.nim b/src/regex.nim index c4b2a7d..31b7e9b 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -508,7 +508,7 @@ proc reCheck(s: string) {.compileTime.} = except RegexError: raise newException(RegexError, getCurrentExceptionMsg()) -func `~`*(s: static[string]): Regex2 = +func `~`*(s: static string): Regex2 = static: reCheck(s) {.cast(noSideEffect), cast(gcsafe).}: var reg {.global.} = re2(s) diff --git a/tests/tests2.nim b/tests/tests2.nim index 408ecc7..5003437 100644 --- a/tests/tests2.nim +++ b/tests/tests2.nim @@ -3346,3 +3346,20 @@ test "tvarflags": check match("a\Lb\L", re2"(?ms)a.b(?s-m:.)") check(not match("a\Lb\L", re2(r"a.b(?-sm:.)", {regexDotAll, regexMultiline}))) check(not match("a\Lb\L", re2"(?ms)a.b(?-sm:.)")) + +test "tsigil": + check ~"ab".match "ab" + check not ~"zx".match "ab" + check(~"ab" in "abcd") + check(~"zx" notin "abcd") + try: + discard ~"(+)" + doAssert false + except RegexError: + check getCurrentExceptionMsg() == + "Invalid `+` operator, nothing to repeat" + +test "tsigil_gcsafe": + func tsigil: bool {.gcsafe.} = + ~"foo".match "foo" + doAssert tsigil() From 5a2387f4915caf7500b9f2b94df42f9b80001721 Mon Sep 17 00:00:00 2001 From: nitely Date: Fri, 17 Jan 2025 08:30:24 -0300 Subject: [PATCH 03/16] wip --- src/regex.nim | 1 + tests/tests2.nim | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/regex.nim b/src/regex.nim index 31b7e9b..883dcb3 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -1569,6 +1569,7 @@ when isMainModule: doAssert ~"ab" in "abcd" doAssert ~"zx" notin "abcd" + doAssert not compiles(~"(+)") # bug: raises invalid utf8 regex in Nim 1.0 + js target when not defined(js) or NimMajor >= 2: diff --git a/tests/tests2.nim b/tests/tests2.nim index 5003437..244415f 100644 --- a/tests/tests2.nim +++ b/tests/tests2.nim @@ -20,7 +20,7 @@ template test(desc: string, body: untyped): untyped = echo "[CT/RT] " & desc body)() -template check(condition: bool) = +template check(condition: bool): untyped = doAssert(condition) template expect(exception: typedesc, body: untyped): untyped = @@ -3350,8 +3350,9 @@ test "tvarflags": test "tsigil": check ~"ab".match "ab" check not ~"zx".match "ab" - check(~"ab" in "abcd") - check(~"zx" notin "abcd") + check ~"ab" in "abcd" + check ~"zx" notin "abcd" + check not compiles(~"(+)") try: discard ~"(+)" doAssert false From 8ae70175325dab2a86654a4f144eb820b4807c31 Mon Sep 17 00:00:00 2001 From: nitely Date: Fri, 17 Jan 2025 08:33:56 -0300 Subject: [PATCH 04/16] wip --- tests/tests2.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/tests2.nim b/tests/tests2.nim index 244415f..cbc05f0 100644 --- a/tests/tests2.nim +++ b/tests/tests2.nim @@ -3348,8 +3348,8 @@ test "tvarflags": check(not match("a\Lb\L", re2"(?ms)a.b(?-sm:.)")) test "tsigil": - check ~"ab".match "ab" - check not ~"zx".match "ab" + check match(~"ab", "ab") + check not match(~"zx", "ab") check ~"ab" in "abcd" check ~"zx" notin "abcd" check not compiles(~"(+)") @@ -3362,5 +3362,5 @@ test "tsigil": test "tsigil_gcsafe": func tsigil: bool {.gcsafe.} = - ~"foo".match "foo" + match(~"foo", "foo") doAssert tsigil() From 2730a118c8b68c178be1e1d0be162957066111c0 Mon Sep 17 00:00:00 2001 From: nitely Date: Fri, 17 Jan 2025 08:39:36 -0300 Subject: [PATCH 05/16] wip --- src/regex.nim | 1 + tests/tests2.nim | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/regex.nim b/src/regex.nim index 883dcb3..74eb314 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -1566,6 +1566,7 @@ when isMainModule: doAssert match("A", re2"(?xi) a") doAssert(not match("A", re2"((?xi)) a")) doAssert(not match("A", re2"(?xi:(?xi) )a")) + doAssert not compiles(re2"(+)") doAssert ~"ab" in "abcd" doAssert ~"zx" notin "abcd" diff --git a/tests/tests2.nim b/tests/tests2.nim index cbc05f0..c083c82 100644 --- a/tests/tests2.nim +++ b/tests/tests2.nim @@ -3348,8 +3348,8 @@ test "tvarflags": check(not match("a\Lb\L", re2"(?ms)a.b(?-sm:.)")) test "tsigil": - check match(~"ab", "ab") - check not match(~"zx", "ab") + check "ab".match ~"ab" + check not "ab".match ~"zx" check ~"ab" in "abcd" check ~"zx" notin "abcd" check not compiles(~"(+)") @@ -3362,5 +3362,5 @@ test "tsigil": test "tsigil_gcsafe": func tsigil: bool {.gcsafe.} = - match(~"foo", "foo") + "foo".match ~"foo" doAssert tsigil() From 8fe70974c39c4bb33ae7343b2cab9e0a91531e6f Mon Sep 17 00:00:00 2001 From: nitely Date: Fri, 17 Jan 2025 09:34:45 -0300 Subject: [PATCH 06/16] wip --- src/regex.nim | 4 ++-- tests/tests2.nim | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/regex.nim b/src/regex.nim index 74eb314..883ee34 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -504,14 +504,14 @@ when not defined(forceRegexAtRuntime): proc reCheck(s: string) {.compileTime.} = try: - discard re2(s) + discard reCt(s) except RegexError: raise newException(RegexError, getCurrentExceptionMsg()) func `~`*(s: static string): Regex2 = static: reCheck(s) {.cast(noSideEffect), cast(gcsafe).}: - var reg {.global.} = re2(s) + var reg {.global.} = toRegex2 reImpl(s) return reg func group*(m: RegexMatch2, i: int): Slice[int] {.inline, raises: [].} = diff --git a/tests/tests2.nim b/tests/tests2.nim index c083c82..bbe870d 100644 --- a/tests/tests2.nim +++ b/tests/tests2.nim @@ -3353,12 +3353,6 @@ test "tsigil": check ~"ab" in "abcd" check ~"zx" notin "abcd" check not compiles(~"(+)") - try: - discard ~"(+)" - doAssert false - except RegexError: - check getCurrentExceptionMsg() == - "Invalid `+` operator, nothing to repeat" test "tsigil_gcsafe": func tsigil: bool {.gcsafe.} = From 39bb7776e383536e9c1011f482b7110821db00cf Mon Sep 17 00:00:00 2001 From: nitely Date: Fri, 17 Jan 2025 11:12:20 -0300 Subject: [PATCH 07/16] wip --- src/regex.nim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/regex.nim b/src/regex.nim index 883ee34..f5a916f 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -509,6 +509,11 @@ proc reCheck(s: string) {.compileTime.} = raise newException(RegexError, getCurrentExceptionMsg()) func `~`*(s: static string): Regex2 = + ## Compile a regex at runtime. + ## The compiled regex is cached and reused. + ## It gets compiled once in a program lifetime. + ## The regex is validated at compile-time, + ## and so it may only raise a `RegexError` at compile-time. static: reCheck(s) {.cast(noSideEffect), cast(gcsafe).}: var reg {.global.} = toRegex2 reImpl(s) From 5d5b167107d17dd3ee8accd3e0af01b932ce3746 Mon Sep 17 00:00:00 2001 From: nitely Date: Sat, 18 Jan 2025 03:01:45 -0300 Subject: [PATCH 08/16] ugh --- src/regex.nim | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/regex.nim b/src/regex.nim index f5a916f..7dd7270 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -508,16 +508,28 @@ proc reCheck(s: string) {.compileTime.} = except RegexError: raise newException(RegexError, getCurrentExceptionMsg()) -func `~`*(s: static string): Regex2 = +import locks +var sigils {.compileTime.} = newSeq[string]() +var sigilre = initTable[string, Regex2]() +var sigillock: Lock +initLock(sigillock) + +func `~`*(s: static string): Regex2 {.raises: [RegexError].} = ## Compile a regex at runtime. ## The compiled regex is cached and reused. ## It gets compiled once in a program lifetime. ## The regex is validated at compile-time, ## and so it may only raise a `RegexError` at compile-time. static: reCheck(s) - {.cast(noSideEffect), cast(gcsafe).}: - var reg {.global.} = toRegex2 reImpl(s) - return reg + when nimvm: + return toRegex2 reImpl(s) + else: + {.cast(noSideEffect), cast(gcsafe), cast(raises: [RegexError]).}: + withLock sigillock: + if s in sigilre: + return sigilre[s] + sigilre[s] = toRegex2 reImpl(s) + return sigilre[s] func group*(m: RegexMatch2, i: int): Slice[int] {.inline, raises: [].} = ## return slice for a given group. From b04f3f91fc8b60cf6adbf88fbecb46a8fb9605e5 Mon Sep 17 00:00:00 2001 From: nitely Date: Sat, 18 Jan 2025 03:52:10 -0300 Subject: [PATCH 09/16] nodestroy --- src/regex.nim | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/regex.nim b/src/regex.nim index 7dd7270..825fc4f 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -508,28 +508,16 @@ proc reCheck(s: string) {.compileTime.} = except RegexError: raise newException(RegexError, getCurrentExceptionMsg()) -import locks -var sigils {.compileTime.} = newSeq[string]() -var sigilre = initTable[string, Regex2]() -var sigillock: Lock -initLock(sigillock) - -func `~`*(s: static string): Regex2 {.raises: [RegexError].} = +func `~`*(s: static string): Regex2 {.nodestroy.} = ## Compile a regex at runtime. ## The compiled regex is cached and reused. ## It gets compiled once in a program lifetime. ## The regex is validated at compile-time, ## and so it may only raise a `RegexError` at compile-time. static: reCheck(s) - when nimvm: - return toRegex2 reImpl(s) - else: - {.cast(noSideEffect), cast(gcsafe), cast(raises: [RegexError]).}: - withLock sigillock: - if s in sigilre: - return sigilre[s] - sigilre[s] = toRegex2 reImpl(s) - return sigilre[s] + {.cast(noSideEffect), cast(gcsafe).}: + var reg {.global.} = toRegex2 reImpl(s) + return reg func group*(m: RegexMatch2, i: int): Slice[int] {.inline, raises: [].} = ## return slice for a given group. From 4433fe24810e3993d0a1073b446ebc2c6ccea48e Mon Sep 17 00:00:00 2001 From: nitely Date: Sat, 18 Jan 2025 06:47:48 -0300 Subject: [PATCH 10/16] wip --- src/regex.nim | 19 +++++++++++++++---- tests/tests2.nim | 8 ++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/regex.nim b/src/regex.nim index 825fc4f..ce263e0 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -428,6 +428,7 @@ To compile the regex at runtime pass the regex expression as a ``var/let``. ]## +import std/locks import std/tables import std/sequtils import std/unicode @@ -508,16 +509,26 @@ proc reCheck(s: string) {.compileTime.} = except RegexError: raise newException(RegexError, getCurrentExceptionMsg()) -func `~`*(s: static string): Regex2 {.nodestroy.} = +var tildes = initTable[string, Regex2]() +var tildelock: Lock +initLock(tildelock) + +func `~`*(s: static string): Regex2 {.raises: [RegexError].} = ## Compile a regex at runtime. ## The compiled regex is cached and reused. ## It gets compiled once in a program lifetime. ## The regex is validated at compile-time, ## and so it may only raise a `RegexError` at compile-time. static: reCheck(s) - {.cast(noSideEffect), cast(gcsafe).}: - var reg {.global.} = toRegex2 reImpl(s) - return reg + when nimvm: + return toRegex2 reImpl(s) + else: + {.cast(noSideEffect), cast(gcsafe), cast(raises: [RegexError]).}: + withLock tildelock: + if s in tildes: + return tildes[s] + tildes[s] = toRegex2 reImpl(s) + return tildes[s] func group*(m: RegexMatch2, i: int): Slice[int] {.inline, raises: [].} = ## return slice for a given group. diff --git a/tests/tests2.nim b/tests/tests2.nim index bbe870d..f88049a 100644 --- a/tests/tests2.nim +++ b/tests/tests2.nim @@ -3347,14 +3347,14 @@ test "tvarflags": check(not match("a\Lb\L", re2(r"a.b(?-sm:.)", {regexDotAll, regexMultiline}))) check(not match("a\Lb\L", re2"(?ms)a.b(?-sm:.)")) -test "tsigil": +test "ttilde": check "ab".match ~"ab" check not "ab".match ~"zx" check ~"ab" in "abcd" check ~"zx" notin "abcd" check not compiles(~"(+)") -test "tsigil_gcsafe": - func tsigil: bool {.gcsafe.} = +test "ttilde_gcsafe": + func ttilde: bool {.gcsafe.} = "foo".match ~"foo" - doAssert tsigil() + doAssert ttilde() From 9018bb924d48f8cab5edb73056e8749aa4cfd67f Mon Sep 17 00:00:00 2001 From: nitely Date: Sat, 18 Jan 2025 08:21:12 -0300 Subject: [PATCH 11/16] wip --- src/regex.nim | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/regex.nim b/src/regex.nim index ce263e0..205461c 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -355,6 +355,25 @@ regex is parsed as a byte sequence. The ``Ⓐ`` character is composed of multiple bytes (``\xe2\x92\xb6``), and only the last byte is affected by the ``+`` operator. +Tilde +##### + +The tilde operator (``~``) compiles a regex literal at runtime, +and it cache it for later usage. The regex is validated +at compile-time. Since it does not generate a compiled regex, +compilation time can be faster, and the resulting binary smaller. +Do not assign it to a ``const``. + +.. code-block:: nim + :test: + let text = "abc" + block: + doAssert match(text, ~".+") + block: + func myFn(s: string, r: Regex2) = + doAssert match(s, r) + myFn(text, ~".+") + Compile the regex at compile time ################################# @@ -399,17 +418,6 @@ Using a ``const`` can avoid confusion when passing flags: Compile the regex at runtime ############################ -.. note:: - Consider `compiling the regex at compile-time <#examples-compile-the-regex-at-compile-time>`_ - whenever possible. - -Most of the time compiling the regex at runtime can be avoided, -and it should be avoided. Nim has really good compile-time -capabilities like reading files, constructing strings, -and so on. However, it cannot be helped in cases where -the regex is passed to the program at runtime (from terminal input, -network, or text files). - To compile the regex at runtime pass the regex expression as a ``var/let``. .. code-block:: nim @@ -509,21 +517,25 @@ proc reCheck(s: string) {.compileTime.} = except RegexError: raise newException(RegexError, getCurrentExceptionMsg()) +var tildesct {.compileTime.} = initTable[string, Regex2]() var tildes = initTable[string, Regex2]() var tildelock: Lock initLock(tildelock) -func `~`*(s: static string): Regex2 {.raises: [RegexError].} = +func `~`*(s: static string): lent Regex2 {.raises: [RegexError].} = ## Compile a regex at runtime. - ## The compiled regex is cached and reused. + ## The compiled regex is cached for later usage. ## It gets compiled once in a program lifetime. ## The regex is validated at compile-time, ## and so it may only raise a `RegexError` at compile-time. static: reCheck(s) - when nimvm: - return toRegex2 reImpl(s) - else: - {.cast(noSideEffect), cast(gcsafe), cast(raises: [RegexError]).}: + {.cast(noSideEffect), cast(gcsafe), cast(raises: [RegexError]).}: + when nimvm: + if s in tildesct: + return tildesct[s] + tildesct[s] = toRegex2 reImpl(s) + return tildesct[s] + else: withLock tildelock: if s in tildes: return tildes[s] From 48d64086464a9b9cbf1a2373228ca75e503c9582 Mon Sep 17 00:00:00 2001 From: nitely Date: Sat, 18 Jan 2025 08:38:33 -0300 Subject: [PATCH 12/16] wip --- src/regex.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/regex.nim b/src/regex.nim index 205461c..5256493 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -359,8 +359,8 @@ Tilde ##### The tilde operator (``~``) compiles a regex literal at runtime, -and it cache it for later usage. The regex is validated -at compile-time. Since it does not generate a compiled regex, +and it caches it for later usage. The regex is validated +at compile-time. Since it does not generates code for the compiled regex, compilation time can be faster, and the resulting binary smaller. Do not assign it to a ``const``. From 0eb748e86f49d4102b9dc81f41dc11533364b87a Mon Sep 17 00:00:00 2001 From: nitely Date: Sat, 18 Jan 2025 22:13:27 -0300 Subject: [PATCH 13/16] just use threadvar the problem is this leaks --- src/regex.nim | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/regex.nim b/src/regex.nim index 5256493..9f4321e 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -518,29 +518,27 @@ proc reCheck(s: string) {.compileTime.} = raise newException(RegexError, getCurrentExceptionMsg()) var tildesct {.compileTime.} = initTable[string, Regex2]() -var tildes = initTable[string, Regex2]() -var tildelock: Lock -initLock(tildelock) +var tildes {.threadvar.}: Table[string, Regex2] -func `~`*(s: static string): lent Regex2 {.raises: [RegexError].} = +func `~`*(s: static string): Regex2 {.raises: [RegexError], gcsafe.} = ## Compile a regex at runtime. ## The compiled regex is cached for later usage. - ## It gets compiled once in a program lifetime. + ## It gets compiled once per thread. ## The regex is validated at compile-time, ## and so it may only raise a `RegexError` at compile-time. static: reCheck(s) - {.cast(noSideEffect), cast(gcsafe), cast(raises: [RegexError]).}: + {.cast(noSideEffect), cast(raises: [RegexError]).}: when nimvm: - if s in tildesct: + {.cast(gcsafe).}: + if s in tildesct: + return tildesct[s] + tildesct[s] = toRegex2 reImpl(s) return tildesct[s] - tildesct[s] = toRegex2 reImpl(s) - return tildesct[s] else: - withLock tildelock: - if s in tildes: - return tildes[s] - tildes[s] = toRegex2 reImpl(s) + if s in tildes: return tildes[s] + tildes[s] = toRegex2 reImpl(s) + return tildes[s] func group*(m: RegexMatch2, i: int): Slice[int] {.inline, raises: [].} = ## return slice for a given group. From 845feb02cffa1035b8af2acf7caafae2c24fbd87 Mon Sep 17 00:00:00 2001 From: nitely Date: Sat, 18 Jan 2025 22:29:34 -0300 Subject: [PATCH 14/16] wip --- src/regex.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/regex.nim b/src/regex.nim index 9f4321e..9cfed6f 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -517,7 +517,7 @@ proc reCheck(s: string) {.compileTime.} = except RegexError: raise newException(RegexError, getCurrentExceptionMsg()) -var tildesct {.compileTime.} = initTable[string, Regex2]() +var tildesct {.compileTime.}: Table[string, Regex2] var tildes {.threadvar.}: Table[string, Regex2] func `~`*(s: static string): Regex2 {.raises: [RegexError], gcsafe.} = From b41891f35992a7151d8ccd2db0986cecbe5197aa Mon Sep 17 00:00:00 2001 From: nitely Date: Sat, 18 Jan 2025 22:50:12 -0300 Subject: [PATCH 15/16] wip --- src/regex.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/src/regex.nim b/src/regex.nim index 9cfed6f..eb0cc87 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -436,7 +436,6 @@ To compile the regex at runtime pass the regex expression as a ``var/let``. ]## -import std/locks import std/tables import std/sequtils import std/unicode From d45b47ad7a77a50f030270c95fc330c0d293a662 Mon Sep 17 00:00:00 2001 From: nitely Date: Sun, 19 Jan 2025 07:55:10 -0300 Subject: [PATCH 16/16] wip --- src/regex.nim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/regex.nim b/src/regex.nim index eb0cc87..744acdf 100644 --- a/src/regex.nim +++ b/src/regex.nim @@ -539,6 +539,11 @@ func `~`*(s: static string): Regex2 {.raises: [RegexError], gcsafe.} = tildes[s] = toRegex2 reImpl(s) return tildes[s] +func regexDestroyCache* {.gcsafe.} = + ## Destroy tilde (``~``) cache. + {.cast(noSideEffect).}: + tildes = default(Table[string, Regex2]) + func group*(m: RegexMatch2, i: int): Slice[int] {.inline, raises: [].} = ## return slice for a given group. ## Slice of start > end are empty