From 6d8130f9be3bcf274c807a7108382230098091b7 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 14 Oct 2026 11:18:34 -0600 Subject: [PATCH 1/5] updated for v1.9.0.0 --- .../Contributed/tbRegExp/RegExp/Execute.md | 4 +- .../Contributed/tbRegExp/RegExp/IsValid.md | 14 +- .../Contributed/tbRegExp/RegExp/LastError.md | 2 +- .../tbRegExp/RegExp/LastErrorNumber.md | 6 +- .../Contributed/tbRegExp/RegExp/Replace.md | 9 +- .../tbRegExp/RegExp/SearchStepLimit.md | 81 ++++++++ .../Contributed/tbRegExp/RegExp/Split.md | 4 +- .../Contributed/tbRegExp/RegExp/Test.md | 4 +- .../Contributed/tbRegExp/RegExp/index.md | 4 +- docs/Reference/Contributed/tbRegExp/index.md | 175 ++++++++++-------- .../tbRegExpold/Match/FirstIndex.md | 46 +++++ .../Contributed/tbRegExpold/Match/Length.md | 40 ++++ .../tbRegExpold/Match/SubMatches.md | 48 +++++ .../Contributed/tbRegExpold/Match/Value.md | 48 +++++ .../Contributed/tbRegExpold/Match/index.md | 38 ++++ .../tbRegExpold/MatchCollection/Count.md | 42 +++++ .../tbRegExpold/MatchCollection/Item.md | 66 +++++++ .../tbRegExpold/MatchCollection/NewEnum.md | 51 +++++ .../tbRegExpold/MatchCollection/index.md | 51 +++++ .../Contributed/tbRegExpold/RegExp/DotAll.md | 57 ++++++ .../Contributed/tbRegExpold/RegExp/Execute.md | 82 ++++++++ .../Contributed/tbRegExpold/RegExp/Flags.md | 72 +++++++ .../Contributed/tbRegExpold/RegExp/Global.md | 60 ++++++ .../tbRegExpold/RegExp/IgnoreCase.md | 56 ++++++ .../Contributed/tbRegExpold/RegExp/IsValid.md | 53 ++++++ .../tbRegExpold/RegExp/LastError.md | 53 ++++++ .../tbRegExpold/RegExp/LastErrorNumber.md | 65 +++++++ .../tbRegExpold/RegExp/MultiLine.md | 60 ++++++ .../Contributed/tbRegExpold/RegExp/Pattern.md | 56 ++++++ .../Contributed/tbRegExpold/RegExp/Replace.md | 91 +++++++++ .../RegExp/SearchAborted.md | 0 .../Contributed/tbRegExpold/RegExp/Split.md | 99 ++++++++++ .../Contributed/tbRegExpold/RegExp/Test.md | 58 ++++++ .../Contributed/tbRegExpold/RegExp/index.md | 77 ++++++++ .../tbRegExpold/SubMatches/Count.md | 50 +++++ .../tbRegExpold/SubMatches/Item.md | 85 +++++++++ .../tbRegExpold/SubMatches/NewEnum.md | 55 ++++++ .../tbRegExpold/SubMatches/index.md | 48 +++++ .../Contributed/tbRegExpold/index.md | 143 ++++++++++++++ 39 files changed, 1953 insertions(+), 100 deletions(-) create mode 100644 docs/Reference/Contributed/tbRegExp/RegExp/SearchStepLimit.md create mode 100644 docs/Reference/Contributed/tbRegExpold/Match/FirstIndex.md create mode 100644 docs/Reference/Contributed/tbRegExpold/Match/Length.md create mode 100644 docs/Reference/Contributed/tbRegExpold/Match/SubMatches.md create mode 100644 docs/Reference/Contributed/tbRegExpold/Match/Value.md create mode 100644 docs/Reference/Contributed/tbRegExpold/Match/index.md create mode 100644 docs/Reference/Contributed/tbRegExpold/MatchCollection/Count.md create mode 100644 docs/Reference/Contributed/tbRegExpold/MatchCollection/Item.md create mode 100644 docs/Reference/Contributed/tbRegExpold/MatchCollection/NewEnum.md create mode 100644 docs/Reference/Contributed/tbRegExpold/MatchCollection/index.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/DotAll.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Execute.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Flags.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Global.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/IgnoreCase.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/IsValid.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/LastError.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/LastErrorNumber.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/MultiLine.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Pattern.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Replace.md rename docs/Reference/Contributed/{tbRegExp => tbRegExpold}/RegExp/SearchAborted.md (100%) create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Split.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Test.md create mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/index.md create mode 100644 docs/Reference/Contributed/tbRegExpold/SubMatches/Count.md create mode 100644 docs/Reference/Contributed/tbRegExpold/SubMatches/Item.md create mode 100644 docs/Reference/Contributed/tbRegExpold/SubMatches/NewEnum.md create mode 100644 docs/Reference/Contributed/tbRegExpold/SubMatches/index.md create mode 100644 docs/Reference/Contributed/tbRegExpold/index.md diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/Execute.md b/docs/Reference/Contributed/tbRegExp/RegExp/Execute.md index 30cfea8d..e9acb873 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/Execute.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/Execute.md @@ -36,8 +36,8 @@ Debug.Print re.Execute("fooBarBaz").Count ' 2 Debug.Print re.Execute("fooBarBaz")(1).FirstIndex ' 6 ``` - -A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging. The collection then holds the matches found before it gave up, and [**SearchAborted**](SearchAborted) returns **True**. + +A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Execute** then raises an error. The matches found before it gave up are not returned: a part of the matches, handed back as though it were all of them, is worse than no answer. See [**SearchStepLimit**](SearchStepLimit). Each **Match** gives the matched text ([**Value**](../Match/Value)), the position where it starts ([**FirstIndex**](../Match/FirstIndex), counted from 0), its length, and the text of each group of the pattern ([**SubMatches**](../Match/SubMatches)). A group written `(?...)` can be read by its name. diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/IsValid.md b/docs/Reference/Contributed/tbRegExp/RegExp/IsValid.md index 8929b93a..d8992c2a 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/IsValid.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/IsValid.md @@ -24,12 +24,12 @@ The property compiles the pattern if it has not been compiled since [**Pattern** Check **IsValid** before [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) or [**Split**](Split) when the pattern comes from outside the program, because each of those raises the compile error. -> [!NOTE] 1.8.0.0 -> Although nothing is raised, the compile error of an invalid pattern stays in the **Err** object after the call that compiled the pattern returns. A program that tests **Err** for other errors calls **Err.Clear** first. +Nothing is raised and nothing is left behind. The **Err** object is clear when the property returns, even for a pattern that failed to compile, so a program that runs `On Error Resume Next` and tests **Err** for its own errors does not have to call **Err.Clear** first. The number and the message are kept in [**LastErrorNumber**](LastErrorNumber) and [**LastError**](LastError). + -> [!NOTE] 1.8.0.0 -> A quantifier whose lower count is above its upper count, such as `a{2,1}`, is not found when the pattern is compiled, so **IsValid** returns **True** for it and [**LastErrorNumber**](LastErrorNumber) returns 0. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) then raise error 3000 when a search reaches the quantifier, with the description that Windows gives that number, *The specified print monitor is unknown.* +A quantifier whose lower count is above its upper count, such as `a{2,1}`, is found when the pattern is compiled, like any other fault: **IsValid** returns **False**, [**LastErrorNumber**](LastErrorNumber) gives the invalid-quantifier number, and [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise it. A `{` that starts no quantifier at all, such as the one in `a{x}`, is not a fault but an ordinary character. + ## Example @@ -37,9 +37,9 @@ Check **IsValid** before [**Execute**](Execute), [**Test**](Test), [**Replace**] Dim re As RegExp Set re = New RegExp re.Pattern = "(\d+" -Debug.Print re.IsValid ' False -Debug.Print Err.Number = re.LastErrorNumber ' True -Err.Clear +Debug.Print re.IsValid ' False +Debug.Print Err.Number ' 0 -- nothing left in Err +Debug.Print re.LastErrorNumber - vbObjectError ' 3009 re.Pattern = "(\d+)" Debug.Print re.IsValid ' True diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/LastError.md b/docs/Reference/Contributed/tbRegExp/RegExp/LastError.md index 5935dc1a..c63e96aa 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/LastError.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/LastError.md @@ -20,7 +20,7 @@ Returns a **String**: the description of the compile error, or an empty string w ## Remarks -The property compiles the pattern if it has not been compiled since [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) last changed. The compilation traps its error, so **LastError** raises nothing, but the error stays in the **Err** object (see [**IsValid**](IsValid)). The text is a sentence that names the fault, such as *Unterminated character class: a '[' without a matching ']'.* for the pattern `[abc`. [**LastErrorNumber**](LastErrorNumber) is the number of the error and lists the faults. +The property compiles the pattern if it has not been compiled since [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) last changed. The compilation traps its error, so **LastError** raises nothing and leaves the **Err** object clear. The text is a sentence that names the fault, such as *Unterminated character class: a '[' without a matching ']'.* for the pattern `[abc`. [**LastErrorNumber**](LastErrorNumber) is the number of the error and lists the faults. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise an error with this description when the pattern is invalid. diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md b/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md index 14204816..d0857363 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md @@ -22,7 +22,7 @@ Returns a **Long**: the number of the compile error, or 0 when the pattern is va The property compiles the pattern if it has not been compiled since [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) last changed. The compilation traps its error, so **LastErrorNumber** raises nothing. The value is the `Number` of the error that the compilation raised; [**LastError**](LastError) is its description. -The engine raises its pattern errors with numbers of the form `vbObjectError + 3000 + n`, where *n* identifies the fault, as in the example. The compilation traps the error, so **LastErrorNumber** raises nothing, but the error stays in the **Err** object (see [**IsValid**](IsValid)). +The engine raises its pattern errors with numbers of the form `vbObjectError + 3000 + n`, where *n* identifies the fault, as in the example. The compilation traps the error, so **LastErrorNumber** raises nothing and leaves the **Err** object clear. | *n* | Fault | Example | @@ -32,7 +32,7 @@ The engine raises its pattern errors with numbers of the form `vbObjectError + 3 | 3 | A range in a character class that runs backwards | `[z-a]` | | 4 | A character class without its `]` | `[abc` | | 6 | A group construct `(?<` that is neither a lookbehind nor a valid name | `(?<1a>x)` | -| 8 | A `{` that does not start a quantifier, or a count that is too large | `a{2`, `\p{L}` | +| 8 | A quantifier whose upper count is below its lower count, or a count that is too large | `a{2,1}`, `a{99999999999}` | | 9 | A group without its `)` | `(\d+` | | 11 | A `)` that closes no group | `)` | | 12 | A quantifier with nothing to repeat | `*a` | @@ -41,6 +41,8 @@ The engine raises its pattern errors with numbers of the form `vbObjectError + 3 [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise an error with this number when the pattern is invalid. +Those four methods can also raise `vbObjectError + 3016`, which is not a compile error and so never appears here: it means a valid pattern ran out of its step budget before the search finished. See [**SearchStepLimit**](SearchStepLimit). + ## Example ```tb check_build check_run diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/Replace.md b/docs/Reference/Contributed/tbRegExp/RegExp/Replace.md index 0d68f492..1fa00e03 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/Replace.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/Replace.md @@ -35,13 +35,12 @@ A `$` in *replaceString* starts a substitution item. Any other text is copied as | `$'` | The part of *sourceString* after the match. | | `$`*n* or `$`*nn* | The text of numbered group *n*, counting from 1. Two digits are read as the number when the pattern has a group with that number, otherwise one. It is an empty string when the group matched nothing. When the pattern has no group *n*, the `$` is copied, and so are the digits: `$0` and `$00` are never items, and `$3` in a pattern of two groups is the text `$3`. | | `$<`*name*`>` | The text of the group written `(?...)`. It is an empty string when the group matched nothing or no group has the name. When the pattern has no named group, or the `>` is missing, or the name is empty, the `$` is copied. | -| `$~` | Nothing. The item is dropped. | A `$` that starts no item raises no error, so a replacement string can hold a literal `$` as it is, at the end or before any other character. Before a character that would make an item, write `$$`: `$$$&` inserts a `$` and then the matched text, as the last example does. -> [!NOTE] 1.8.0.0 -> Items are read as in VBScript's **RegExp**, with two differences: `$<`*name*`>` is an addition of this package, and `$~` is dropped, where VBScript copies `$~` into the result. +> [!NOTE] 1.9.0.0 +> Items are read as in VBScript's **RegExp**, with one difference: `$<`*name*`>` is an addition of this package. A `$~`, which earlier versions dropped, is copied into the result as VBScript copies it. ## Remarks @@ -57,8 +56,8 @@ re.Pattern = "^" Debug.Print Replace(re.Replace("a" & vbLf & "b" & vbLf & "c", "> "), vbLf, "/") ' > a/> b/> c ``` - -A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Replace** then replaces only the matches found before it gave up. [**SearchAborted**](SearchAborted) returns **True** in that case. + +A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Replace** then raises an error. No partly replaced string is returned. See [**SearchStepLimit**](SearchStepLimit). An invalid pattern raises the compile error, with the number in [**LastErrorNumber**](LastErrorNumber) and the description in [**LastError**](LastError). Check [**IsValid**](IsValid) first to avoid it. diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/SearchStepLimit.md b/docs/Reference/Contributed/tbRegExp/RegExp/SearchStepLimit.md new file mode 100644 index 00000000..b119b33c --- /dev/null +++ b/docs/Reference/Contributed/tbRegExp/RegExp/SearchStepLimit.md @@ -0,0 +1,81 @@ +--- +title: SearchStepLimit +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/SearchStepLimit +has_toc: false +--- + +# SearchStepLimit +{: .no_toc } + +Returns or sets the number of steps a search of this object may take before it gives up. + +Syntax: *object*.**SearchStepLimit** [= *steps*] + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +*steps* +: *optional* A **Long**, the budget for one search, or 0 for the budget the package works out on its own. A negative value raises error 5. + +Returns a **Long**: the value that was set, or 0 when the budget is left to the package. The default is 0. + +## Remarks + +A search is given a budget of steps, where a step is one operation of the compiled pattern. A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, needs twice as many steps for each `a` it is given, and the budget is what stops it. VBScript's **RegExp** has no such limit and for that string does not return in any reasonable time. + +When the budget runs out, [**Test**](Test), [**Execute**](Execute), [**Replace**](Replace) and [**Split**](Split) raise `vbObjectError + 3016` and return nothing at all -- not even the matches found before the search stopped. A search that ran out of steps never established what the rest of the string holds, so a short result is a wrong answer rather than an incomplete one, and the error is the only honest report. The description names the budget that was spent, the length of the string, and what to do about it. + + +With the property left at 0, the budget is ten steps for each character of the string squared, at least 1,000,000 and at most 100,000,000. It is worked out for the search of one match, so with [**Global**](Global) set to **True** each match is given a budget of its own and the first search that runs out ends the call. The square matters: an ordinary pattern that backtracks, such as `\d+x` on a string of digits with no `x`, costs about two and a quarter steps for each character squared, so it finishes at any length a search is likely to be given. A simple pattern needs about three steps for each character it matches, so a long match is never affected: `a*` on a string of 1,000,000 `a` returns all of it in about half a second. + + +Set the property when a pattern of that kind is given a string long enough to reach the cap. The message of the error suggests a value. Nothing covers a pattern whose cost grows faster than the square of the string, and when the budget already exceeded what a pattern of that kind would need, the message says to simplify the pattern instead of naming this property. + +The value that **SearchStepLimit** returns is the one that was set, not the budget a search would be given: with the property at 0 there is no single number to return, because the budget depends on the string. Setting it does not compile the pattern again. + +## Example + +This example searches a string of thirty `a` for `(a+)+b`, which cannot match and takes a very long time to prove it. The search gives up after about a third of a second and raises. The same pattern against a string it can match finishes. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(a+)+b" + +On Error Resume Next +re.Test String(30, "a") +Debug.Print Err.Number - vbObjectError ' 3016 +On Error GoTo 0 +Err.Clear + +Debug.Print re.Test("aaab") ' True +``` + + +This example gives a search too few steps on purpose, then raises the limit so that the same search finishes. `\d+x` on a string of 1,000 digits with no `x` costs about 2,250,000 steps. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "\d+x" +Debug.Print re.SearchStepLimit ' 0 + +re.SearchStepLimit = 100000 +On Error Resume Next +re.Test String(1000, "1") +Debug.Print Err.Number - vbObjectError ' 3016 +On Error GoTo 0 +Err.Clear + +re.SearchStepLimit = 3000000 +Debug.Print re.Test(String(1000, "1")) ' False +Debug.Print re.SearchStepLimit ' 3000000 +``` + + +## See Also + +- [Test](Test) method -- returns whether a string contains a match +- [Execute](Execute) method -- returns the matches +- [Replace](Replace) method -- replaces the matches +- [Split](Split) method -- splits a string at the matches +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/Split.md b/docs/Reference/Contributed/tbRegExp/RegExp/Split.md index b1563cfa..cc45d9f3 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/Split.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/Split.md @@ -43,8 +43,8 @@ Debug.Print parts.Count ' 3 Debug.Print parts(1) & "|" & parts(2) & "|" & parts(3) ' foo|Bar|Baz ``` - -A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Split** then splits only at the matches found before it gave up. [**SearchAborted**](SearchAborted) returns **True** in that case. + +A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Split** then raises an error. No partly split collection is returned. See [**SearchStepLimit**](SearchStepLimit). When **Pattern** is not a valid regular expression, **Split** raises the compile error. Its number is [**LastErrorNumber**](LastErrorNumber), its description is [**LastError**](LastError), and its source is `RegExp`. Read [**IsValid**](IsValid) first to avoid the error. diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/Test.md b/docs/Reference/Contributed/tbRegExp/RegExp/Test.md index 49358f94..3cd8cfaf 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/Test.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/Test.md @@ -26,8 +26,8 @@ Returns **True** if [**Pattern**](Pattern) finds a match in *sourceString*, othe [**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) apply to the matching. [**Global**](Global) makes no difference, because one match is enough. - -A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Test** then returns **False**. [**SearchAborted**](SearchAborted) tells that **False** from a string that has no match. + +A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Test** then raises an error rather than returning a value: a search that ran out of steps did not establish whether the pattern matches, so there is no answer to return. See [**SearchStepLimit**](SearchStepLimit). When **Pattern** is not a valid regular expression, **Test** raises the compile error. Its number is [**LastErrorNumber**](LastErrorNumber), its description is [**LastError**](LastError), and its source is `RegExp`. Read [**IsValid**](IsValid) first to avoid the error. diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/index.md b/docs/Reference/Contributed/tbRegExp/RegExp/index.md index 413728d0..8f6a616f 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/index.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/index.md @@ -47,7 +47,7 @@ All four are **False** in a new instance. The pattern is compiled the first time a method or one of [**IsValid**](IsValid), [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) needs it. It is compiled again after [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) is set to a different value. [**Global**](Global), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) are applied when a match is made and do not cause a new compilation. -A pattern that does not compile does not raise an error at once. [**IsValid**](IsValid) returns **False**, and [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) describe the failure. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) then raise that error, with the number in **LastErrorNumber**, the description in **LastError** and the source `RegExp`. When **IsValid**, **LastError** or **LastErrorNumber** is the call that compiles an invalid pattern, it raises nothing, but the compile error stays in the **Err** object after it returns; [**IsValid**](IsValid) says what to do about it. +A pattern that does not compile does not raise an error at once. [**IsValid**](IsValid) returns **False**, and [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) describe the failure. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) then raise that error, with the number in **LastErrorNumber**, the description in **LastError** and the source `RegExp`. When **IsValid**, **LastError** or **LastErrorNumber** is the call that compiles an invalid pattern, it raises nothing and leaves the **Err** object clear. ## Properties @@ -60,7 +60,7 @@ A pattern that does not compile does not raise an error at once. [**IsValid**](I - [LastErrorNumber](LastErrorNumber) -- returns the number of the compile error - [MultiLine](MultiLine) -- returns or sets whether `^` and `$` match at line boundaries - [Pattern](Pattern) -- returns or sets the regular expression -- [SearchAborted](SearchAborted) -- returns whether the last search gave up before it finished +- [SearchStepLimit](SearchStepLimit) -- returns or sets the step budget that stops a runaway search ## Methods diff --git a/docs/Reference/Contributed/tbRegExp/index.md b/docs/Reference/Contributed/tbRegExp/index.md index 231c2623..10392543 100644 --- a/docs/Reference/Contributed/tbRegExp/index.md +++ b/docs/Reference/Contributed/tbRegExp/index.md @@ -1,39 +1,43 @@ ---- +\--- + title: tbRegExp Package parent: Contributed Packages -has_toc: false -exclude_from_docs: - - StaticRegex -indexed_from: 1.8.0.0 +has\_toc: false +exclude\_from\_docs: + +* StaticRegex +indexed\_from: 1.9.0.0 permalink: /tB/Packages/Contributed/tbRegExp/ ---- + +\--- # tbRegExp Package -{: .no_toc } -**Author:** GCuser99. **Version:** 1.8.0.0. **On GitHub:** [GCuser99/tbRegExp](https://github.com/GCuser99/tbRegExp), in the folder *Package*. +{: .no\_toc } + +**Author:** GCuser99. **Version:** 1.9.0.0. **On GitHub:** [GCuser99/tbRegExp](https://github.com/GCuser99/tbRegExp), in the folder *Package*. The **tbRegExp** contributed package provides regular expressions for twinBASIC projects, as a replacement for the **RegExp** object of VBScript. It follows the same object model. A [**RegExp**](RegExp/) is given a pattern and options, its [**Execute**](RegExp/Execute) method returns a [**MatchCollection**](MatchCollection/) of [**Match**](Match/) objects, and each **Match** holds the text captured by the groups of the pattern as a [**SubMatches**](SubMatches/) object. Positions are zero-based, as in VBScript. The matching is done by the [vba-regex](https://github.com/sihlfall/vba-regex) engine of sihlfall, which the package includes as a private module, **StaticRegex**. -Beyond VBScript's object, **RegExp** has the [**DotAll**](RegExp/DotAll) and [**Flags**](RegExp/Flags) options, the [**Split**](RegExp/Split) method, [**IsValid**](RegExp/IsValid), [**LastError**](RegExp/LastError) and [**LastErrorNumber**](RegExp/LastErrorNumber), which report an invalid pattern before it is used, and [**SearchAborted**](RegExp/SearchAborted), which reports a search that gave up. [**Replace**](RegExp/Replace) reads a `$` in the replacement string as VBScript does, and adds `$` for a named group. +Beyond VBScript's object, **RegExp** has the [**DotAll**](RegExp/DotAll) and [**Flags**](RegExp/Flags) options, the [**Split**](RegExp/Split) method, [**IsValid**](RegExp/IsValid), [**LastError**](RegExp/LastError) and [**LastErrorNumber**](RegExp/LastErrorNumber), which report an invalid pattern before it is used, and [**SearchStepLimit**](RegExp/SearchStepLimit), which bounds a runaway search. [**Replace**](RegExp/Replace) reads a `$` in the replacement string as VBScript does, and adds `$` for a named group. ## Pattern syntax A pattern is written as for VBScript's **RegExp**. **tbRegExp** also accepts: -- named groups, `(?...)`: [**SubMatches.Item**](SubMatches/Item) returns one by its name, and [**Replace**](RegExp/Replace) inserts one as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores; a pattern with any other name is not valid; -- lookbehind, `(?<=...)` and `(?...)`: [**SubMatches.Item**](SubMatches/Item) returns one by its name, and [**Replace**](RegExp/Replace) inserts one as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores; a pattern with any other name is not valid; +* lookbehind, `(?<=...)` and `(?\d{4})-(?\d{2})" +re.Pattern = "(?\\d{4})-(?\\d{2})" Debug.Print re.Execute("2026-10")(0).SubMatches("month") ' 10 Debug.Print re.Replace("2026-10", "$/$") ' 10/2026 -re.Pattern = "(?<=\$)\d+" +re.Pattern = "(?<=\\$)\\d+" Debug.Print re.Execute("costs $123")(0).Value ' 123 re.Pattern = "(?i:abc)DEF" @@ -41,103 +45,120 @@ Debug.Print re.Test("ABCDEF") ' True Debug.Print re.Test("ABCdef") ' False ``` - -> [!NOTE] 1.8.0.0 -> A pattern that holds an atomic group `(?>...)` or a conditional is not valid: [**IsValid**](RegExp/IsValid) returns **False**. A named backreference `\k`, `\A`, `\Z` and `\p{...}` are not supported either, but they do not make the pattern invalid, because a backslash before a letter that names no escape stands for the letter itself, as in VBScript: `\k` matches the text `k`, and `\A` matches `A`. The pattern `\p{L}` is invalid for another reason: `{L}` does not start a quantifier (see below). + + +> \[!NOTE] 1.9.0.0 +> A pattern that holds an atomic group `(?>...)` or a conditional is not valid: \[\*\*IsValid\*\*](RegExp/IsValid) returns \*\*False\*\*. A named backreference `\\k`, `\\A`, `\\Z` and `\\p{...}` are not supported either, but they do not make the pattern invalid, because a backslash before a letter that names no escape stands for the letter itself, as in VBScript: `\\k` matches the text `k`, and `\\A` matches `A`. So `\\p{L}` matches the text `p{L}`: the `\\p` is the letter, and `{L}` starts no quantifier and is text as well (see below). The escapes that name a control character or a character code are read as in VBScript: -- `\c` followed by a letter, in either case, is a control character: `\cJ` and `\CJ` are the line feed. A `\c` followed by anything else makes the pattern invalid; -- `\0` is the character with code 0, `\0` followed by octal digits, such as `\012`, is that code, and `\8` and `\9` are the digits themselves; -- `\1` to `\9` and longer numbers are backreferences when the pattern has that many groups. Otherwise the digits are read as an octal character code, so `\101` is `A` and `(a)\2` is `a` followed by the character with code 2; [**IsValid**](RegExp/IsValid) returns **True** for both; -- `\f`, `\n`, `\r` and `\t`, inside a character class as well, are the form feed, line feed, carriage return and tab. +* `\\c` followed by a letter, in either case, is a control character: `\\cJ` and `\\CJ` are the line feed. A `\\c` followed by anything else makes the pattern invalid; +* `\\0` is the character with code 0, `\\0` followed by octal digits, such as `\\012`, is that code, and `\\8` and `\\9` are the digits themselves; +* `\\1` to `\\9` and longer numbers are backreferences when the group they name has already been opened, that is, when its `(` stands earlier in the pattern. Otherwise the digits are read as an octal character code, so `\\101` is `A` and `(a)\\2` is `a` followed by the character with code 2; [**IsValid**](RegExp/IsValid) returns **True** for both; +* `\\f`, `\\n`, `\\r` and `\\t`, inside a character class as well, are the form feed, line feed, carriage return and tab. -```tb check_build check_run +```tb check\_build check\_run Dim re As New RegExp -re.Pattern = "\101" +re.Pattern = "\\101" Debug.Print re.Test("A") ' True -re.Pattern = "\CJ" +re.Pattern = "\\CJ" Debug.Print re.Test(vbLf) ' True -re.Pattern = "\k" +re.Pattern = "\\k" Debug.Print re.IsValid ' True Debug.Print re.Test("k") ' True -re.Pattern = "(a)\2" -Debug.Print re.Test("a" & ChrW(2)) ' True +re.Pattern = "(a)\\2" +Debug.Print re.Test("a" \& ChrW(2)) ' True ``` - -> [!NOTE] 1.8.0.0 -> A backreference to a group that has not yet taken part in the match, such as `\1(a)`, matches no characters, so the pattern finds `a` in the string `a`. VBScript's **RegExp** finds no match for such a pattern. + + +Digits that name a group declared *later* in the pattern are therefore not a backreference at all. `\\1(a)` is the character with code 1 followed by `a`, and finds no match in the string `a`, as in VBScript's **RegExp**. A group that is open but has not yet taken part, such as the `\\1` in `(\\1a)`, is a real backreference and matches no characters. + + + +`\\w` matches only the ASCII letters and digits and `\_`, as in VBScript, so `\\w+` finds `caf` in `café`. -`\w` matches only the ASCII letters and digits and `_`, as in VBScript, so `\w+` finds `caf` in `café`. +A `{` starts a quantifier when it is followed by `{n}`, `{n,}` or `{n,m}`. A `{` that starts none, such as the one in `a{`, `a{x}` or `a{,2}`, is an ordinary character, as in VBScript; `\\{` for the character is accepted too but not needed. A `]` or `}` that closes nothing is an ordinary character as well. -A `{` has to start a quantifier: `{n}`, `{n,}`, `{n,m}` or `{,m}`, which means `{0,m}`. A pattern in which a `{` starts none, such as `a{` or `a{x}`, is not valid; write `\{` for the character. A `]` or `}` that closes nothing is an ordinary character, as in VBScript. + - -> [!NOTE] 1.8.0.0 -> VBScript's **RegExp** reads a `{` that starts no quantifier, and `{,m}`, as ordinary text: `a{x}` is a valid pattern there that finds the text `a{x}`, and `a{,2}` finds the text `a{,2}` and not `aa`. In **tbRegExp** the first is not valid, so a search with it raises the compile error, and `a{,2}` finds up to two `a`. +`{,m}` is text and not a quantifier, because a quantifier needs a count before the comma: `a{,2}` finds the text `a{,2}` and not up to two `a`. A `{` that does start a quantifier with counts that cannot be used is still an error, though: `a{2,1}`, whose upper count is below its lower one, makes the pattern invalid. -```tb check_build check_run + + +```tb check\_build check\_run Dim re As New RegExp -re.Pattern = "(?<_part2>\d)" +re.Pattern = "(?<\_part2>\\d)" Debug.Print re.IsValid ' True -re.Pattern = "(?<2part>\d)" +re.Pattern = "(?<2part>\\d)" Debug.Print re.IsValid ' False re.Pattern = "a{x}" -Debug.Print re.IsValid ' False -re.Pattern = "a\{x}" +Debug.Print re.IsValid ' True Debug.Print re.Execute("a{x}")(0).Value ' a{x} +re.Pattern = "a{2,1}" +Debug.Print re.IsValid ' False ``` ## Searches that give up - -A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, does not hang a search as it does in VBScript's **RegExp**. Every search has a budget of steps, which a program cannot change, and when it runs out the search stops and raises no error. Its result is then short or empty, and a program that has to tell it from a true result reads [**SearchAborted**](RegExp/SearchAborted) after the call. A long match is no problem: `a*` and `^.*$` match a string of 1,000,000 `a` whole. + + +A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, does not hang a search as it does in VBScript's **RegExp**. Every search is given a budget of steps, and when it runs out the search raises an error. Nothing is returned, not even the part that was found: a search that ran out of steps never established whether the rest of the string matches, so a short result would be a wrong answer rather than an incomplete one. The message says how much budget was spent and what to try. A long match is no problem: `a\*` and `^.\*$` match a string of 1,000,000 `a` whole. + -```tb check_build check_run +```tb check\_build check\_run Dim re As New RegExp re.Pattern = "(a+)+b" -Debug.Print re.Test(String(30, "a")) ' False -Debug.Print re.SearchAborted ' True +On Error Resume Next +re.Test String(30, "a") +Debug.Print Err.Number - vbObjectError ' 3016 +On Error GoTo 0 +Err.Clear -re.Pattern = "^.*$" +re.Pattern = "^.\*$" Debug.Print re.Test(String(100000, "a")) ' True -Debug.Print re.SearchAborted ' False ``` +The budget grows with the square of the length of the string, so an ordinary pattern that backtracks -- `\\d+x` on a string of digits with no `x`, say -- finishes at any length a search is likely to be given. Only a pattern whose cost grows faster than that is stopped. [**SearchStepLimit**](RegExp/SearchStepLimit) raises the ceiling when a search genuinely needs more. + ## Classes -- [Match](Match/) -- holds one match that a RegExp found in a string: the matched text, where it starts, how many characters it has, and the text captured by its groups - - [FirstIndex](Match/FirstIndex) -- returns the zero-based position where the match starts - - [Length](Match/Length) -- returns the number of characters in the match - - [SubMatches](Match/SubMatches) -- returns the text captured by the groups of the pattern - - [Value](Match/Value) -- returns the matched text (default member) -- [MatchCollection](MatchCollection/) -- holds the Match objects that a regular expression finds in a string - - [Count](MatchCollection/Count) -- returns the number of matches - - [Item](MatchCollection/Item) -- returns one match by position (the default member) - - [NewEnum](MatchCollection/NewEnum) -- returns the enumerator that For Each uses (hidden) -- [RegExp](RegExp/) -- searches strings with a regular expression: finds the matches, tests for one, replaces them and splits a string at them - - [DotAll](RegExp/DotAll) -- returns or sets whether `.` matches a line feed - - [Execute](RegExp/Execute) -- returns the matches in a string - - [Flags](RegExp/Flags) -- returns or sets the four options as a string of letters - - [Global](RegExp/Global) -- returns or sets whether all matches are used - - [IgnoreCase](RegExp/IgnoreCase) -- returns or sets whether letter case is ignored - - [IsValid](RegExp/IsValid) -- returns whether the pattern compiles - - [LastError](RegExp/LastError) -- returns the message of the compile error - - [LastErrorNumber](RegExp/LastErrorNumber) -- returns the number of the compile error - - [MultiLine](RegExp/MultiLine) -- returns or sets whether `^` and `$` match at line boundaries - - [Pattern](RegExp/Pattern) -- returns or sets the regular expression - - [Replace](RegExp/Replace) -- replaces the matches in a string - - [SearchAborted](RegExp/SearchAborted) -- returns whether the last search gave up before it finished - - [Split](RegExp/Split) -- splits a string at the matches - - [Test](RegExp/Test) -- returns whether a string contains a match -- [SubMatches](SubMatches/) -- holds the strings that the capture groups of a pattern captured in one Match - - [Count](SubMatches/Count) -- returns the number of strings - - [Item](SubMatches/Item) -- returns one string by position or by group name (the default member) - - [NewEnum](SubMatches/NewEnum) -- returns the enumerator that For Each uses (hidden) +* [Match](Match/) -- holds one match that a RegExp found in a string: the matched text, where it starts, how many characters it has, and the text captured by its groups + + * [FirstIndex](Match/FirstIndex) -- returns the zero-based position where the match starts + * [Length](Match/Length) -- returns the number of characters in the match + * [SubMatches](Match/SubMatches) -- returns the text captured by the groups of the pattern + * [Value](Match/Value) -- returns the matched text (default member) +* [MatchCollection](MatchCollection/) -- holds the Match objects that a regular expression finds in a string + + * [Count](MatchCollection/Count) -- returns the number of matches + * [Item](MatchCollection/Item) -- returns one match by position (the default member) + * [NewEnum](MatchCollection/NewEnum) -- returns the enumerator that For Each uses (hidden) +* [RegExp](RegExp/) -- searches strings with a regular expression: finds the matches, tests for one, replaces them and splits a string at them + + * [DotAll](RegExp/DotAll) -- returns or sets whether `.` matches a line feed + * [Execute](RegExp/Execute) -- returns the matches in a string + * [Flags](RegExp/Flags) -- returns or sets the four options as a string of letters + * [Global](RegExp/Global) -- returns or sets whether all matches are used + * [IgnoreCase](RegExp/IgnoreCase) -- returns or sets whether letter case is ignored + * [IsValid](RegExp/IsValid) -- returns whether the pattern compiles + * [LastError](RegExp/LastError) -- returns the message of the compile error + * [LastErrorNumber](RegExp/LastErrorNumber) -- returns the number of the compile error + * [MultiLine](RegExp/MultiLine) -- returns or sets whether `^` and `$` match at line boundaries + * [Pattern](RegExp/Pattern) -- returns or sets the regular expression + * [Replace](RegExp/Replace) -- replaces the matches in a string + * [SearchStepLimit](RegExp/SearchStepLimit) -- returns or sets the step budget that stops a runaway search + * [Split](RegExp/Split) -- splits a string at the matches + * [Test](RegExp/Test) -- returns whether a string contains a match +* [SubMatches](SubMatches/) -- holds the strings that the capture groups of a pattern captured in one Match + + * [Count](SubMatches/Count) -- returns the number of strings + * [Item](SubMatches/Item) -- returns one string by position or by group name (the default member) + * [NewEnum](SubMatches/NewEnum) -- returns the enumerator that For Each uses (hidden) ## Test cases -The notes and warnings of this section are checked by the test cases in [tbRegExp-tests.twinproj](tbRegExp-tests.twinproj), a twinBASIC project. It uses tbRegExp as a [linked package](../../../../Features/Packages/Linked#manual-management): put the package's `.twinpack` in `%APPDATA%\twinBASIC\packages`, then run the project, and each case prints whether it passes. +The notes and warnings of this section are checked by the test cases in [tbRegExp-tests.twinproj](tbRegExp-tests.twinproj), a twinBASIC project. It uses tbRegExp as a [linked package](../../../../Features/Packages/Linked#manual-management): put the package's `.twinpack` in `%APPDATA%\\twinBASIC\\packages`, then run the project, and each case prints whether it passes. + diff --git a/docs/Reference/Contributed/tbRegExpold/Match/FirstIndex.md b/docs/Reference/Contributed/tbRegExpold/Match/FirstIndex.md new file mode 100644 index 00000000..e5e29502 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/Match/FirstIndex.md @@ -0,0 +1,46 @@ +--- +title: FirstIndex +parent: Match +permalink: /tB/Packages/Contributed/tbRegExp/Match/FirstIndex +has_toc: false +--- + +# FirstIndex +{: .no_toc } + +Returns the zero-based position in the source string where the match starts. + +Syntax: *object*.**FirstIndex** + +*object* +: *required* An object expression that evaluates to a **Match**. + +Returns a **Long**. The property is read-only. + +## Remarks + +The first character of the source string is at position 0, so the property counts the characters before the match. The index is zero-based, as in the VBScript regular expression object model that the package replaces. + +The **Mid$** function counts from 1. To cut the match out of the source string with **Mid$**, pass `FirstIndex + 1` as its start position and [**Length**](Length) as its length. + +## Example + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "b+" + +Dim text As String +text = "aabbbc" + +Dim m As Match +Set m = re.Execute(text).Item(0) +Debug.Print m.FirstIndex ' 2 +Debug.Print Mid$(text, m.FirstIndex + 1, m.Length) ' bbb +``` + +## See Also + +- [Length](Length) property -- the number of characters in the match +- [Value](Value) property -- the matched text +- [Execute](../RegExp/Execute) method -- creates the **Match** objects +- [Match](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/Match/Length.md b/docs/Reference/Contributed/tbRegExpold/Match/Length.md new file mode 100644 index 00000000..95287fbf --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/Match/Length.md @@ -0,0 +1,40 @@ +--- +title: Length +parent: Match +permalink: /tB/Packages/Contributed/tbRegExp/Match/Length +has_toc: false +--- + +# Length +{: .no_toc } + +Returns the number of characters in the match. + +Syntax: *object*.**Length** + +*object* +: *required* An object expression that evaluates to a **Match**. + +Returns a **Long**. The property is read-only. + +## Remarks + +The value is the length of the matched text that [**Value**](Value) returns. Together with [**FirstIndex**](FirstIndex) it gives the range of the source string that the match covers. + +## Example + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "b+" + +Dim m As Match +Set m = re.Execute("aabbbc").Item(0) +Debug.Print m.Length ' 3 +Debug.Print Len(m.Value) ' 3 +``` + +## See Also + +- [FirstIndex](FirstIndex) property -- the position where the match starts +- [Value](Value) property -- the matched text +- [Match](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/Match/SubMatches.md b/docs/Reference/Contributed/tbRegExpold/Match/SubMatches.md new file mode 100644 index 00000000..09ed373a --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/Match/SubMatches.md @@ -0,0 +1,48 @@ +--- +title: SubMatches +parent: Match +permalink: /tB/Packages/Contributed/tbRegExp/Match/SubMatches +has_toc: false +--- + +# SubMatches +{: .no_toc } + +Returns the text captured by the groups of the pattern in this match. + +Syntax: *object*.**SubMatches** + +*object* +: *required* An object expression that evaluates to a **Match**. + +Returns a [**SubMatches**](../SubMatches/) object. The property is read-only. + +## Remarks + +The returned object holds one string for each capture group in the pattern, counted by [**Count**](../SubMatches/Count) and read with [**Item**](../SubMatches/Item) by position or, for a named group, by name. + +When the pattern has no capture groups, the property returns an empty **SubMatches** object with a **Count** of 0. Every match of such a pattern returns the same empty object. + +## Example + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(\d+)-(\d+)" + +Dim m As Match +Set m = re.Execute("pages 10-25").Item(0) +Debug.Print m.Value ' 10-25 +Debug.Print m.SubMatches.Count ' 2 +Debug.Print m.SubMatches.Item(0) ' 10 +Debug.Print m.SubMatches.Item(1) ' 25 + +re.Pattern = "\d+" +Set m = re.Execute("pages 10-25").Item(0) +Debug.Print m.SubMatches.Count ' 0 +``` + +## See Also + +- [Value](Value) property -- the text of the whole match +- [SubMatches](../SubMatches/) class -- the object that the property returns +- [Match](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/Match/Value.md b/docs/Reference/Contributed/tbRegExpold/Match/Value.md new file mode 100644 index 00000000..2300b203 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/Match/Value.md @@ -0,0 +1,48 @@ +--- +title: Value +parent: Match +permalink: /tB/Packages/Contributed/tbRegExp/Match/Value +has_toc: false +--- + +# Value +{: .no_toc } + +Returns the text of the whole match. + +Syntax: *object*.**Value** + +*object* +: *required* An object expression that evaluates to a **Match**. + +Returns a **String**. The property is read-only. + +## Remarks + +**Value** is the default member of **Match**, so an expression that needs a **String** can name the object alone. + +The text is the part of the source string that begins at [**FirstIndex**](FirstIndex) and has [**Length**](Length) characters. The text captured by the groups of the pattern is in [**SubMatches**](SubMatches). + +## Example + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "[a-z]+" + +Dim m As Match +Set m = re.Execute("42 apples").Item(0) + +Dim text As String +text = m.Value +Debug.Print text ' apples + +text = m ' the default member is Value +Debug.Print text ' apples +``` + +## See Also + +- [FirstIndex](FirstIndex) property -- the position where the match starts +- [Length](Length) property -- the number of characters in the match +- [SubMatches](SubMatches) property -- the text captured by the groups of the pattern +- [Match](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/Match/index.md b/docs/Reference/Contributed/tbRegExpold/Match/index.md new file mode 100644 index 00000000..b62da770 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/Match/index.md @@ -0,0 +1,38 @@ +--- +title: Match +parent: tbRegExp Package +permalink: /tB/Packages/Contributed/tbRegExp/Match/ +has_toc: false +--- + +# Match class +{: .no_toc } + +Holds one match that a **RegExp** found in a string: the matched text, where it starts, how many characters it has, and the text captured by the groups of the pattern. + +[**RegExp.Execute**](../RegExp/Execute) creates one **Match** for each match it finds and returns them in a [**MatchCollection**](../MatchCollection/), in the order found. All four properties are read-only, and the values are set once, when **Execute** creates the **Match**. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "[a-z]+" + +Dim m As Match +Set m = re.Execute("42 apples").Item(0) +Debug.Print m.Value ' apples +Debug.Print m.FirstIndex ' 3 +Debug.Print m.Length ' 6 +``` + +## Properties + +- [FirstIndex](FirstIndex) -- returns the zero-based position where the match starts +- [Length](Length) -- returns the number of characters in the match +- [SubMatches](SubMatches) -- returns the text captured by the groups of the pattern +- [Value](Value) -- returns the matched text (default member) + +## See Also + +- [RegExp](../RegExp/) class -- searches a string and creates the **Match** objects +- [MatchCollection](../MatchCollection/) class -- holds the matches that **Execute** finds +- [SubMatches](../SubMatches/) class -- holds the text captured by the groups of one match +- [tbRegExp package](../) -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/MatchCollection/Count.md b/docs/Reference/Contributed/tbRegExpold/MatchCollection/Count.md new file mode 100644 index 00000000..233e711c --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/MatchCollection/Count.md @@ -0,0 +1,42 @@ +--- +title: Count +parent: MatchCollection +permalink: /tB/Packages/Contributed/tbRegExp/MatchCollection/Count +has_toc: false +--- + +# Count +{: .no_toc } + +Returns the number of [**Match**](../Match/) objects in the collection. + +Syntax: *object*.**Count** + +*object* +: *required* An object expression that evaluates to a **MatchCollection**. + +Returns a **Long**. The property is read-only. + +## Remarks + +For the collection that the **Execute** method of [**RegExp**](../RegExp/) returns, **Count** is the number of matches found in the source string. It is 0 when the pattern finds nothing. When the **Global** property of the expression is **False**, the collection holds the first match only, so **Count** is at most 1. + +The positions that [**Item**](Item) accepts run from 0 to **Count** - 1. + +## Example + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "[a-z]+" +re.Global = True +Debug.Print re.Execute("one two three").Count ' 3 +Debug.Print re.Execute("1 2 3").Count ' 0 + +re.Global = False +Debug.Print re.Execute("one two three").Count ' 1 +``` + +## See Also + +- [Item](Item) property -- returns a match by position +- [MatchCollection](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/MatchCollection/Item.md b/docs/Reference/Contributed/tbRegExpold/MatchCollection/Item.md new file mode 100644 index 00000000..19a76e1e --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/MatchCollection/Item.md @@ -0,0 +1,66 @@ +--- +title: Item +parent: MatchCollection +permalink: /tB/Packages/Contributed/tbRegExp/MatchCollection/Item +has_toc: false +--- + +# Item +{: .no_toc } + +Returns the [**Match**](../Match/) at a position in the collection. + +Syntax: *object*.**Item** ( *pItemIndex* ) + +*object* +: *required* An object expression that evaluates to a **MatchCollection**. + +*pItemIndex* +: *required* A **Variant** holding the zero-based position of the match. The first match is 0 and the last is [**Count**](Count) - 1. + +Returns a [**Match**](../Match/). The property is read-only. + +## Remarks + +**Item** is the default member of the class, so `matches(1)` is the same as `matches.Item(1)`. + +The position is zero-based, as in the VBScript object model that the package replaces. A match has no key, so the argument is always a position. + +A position below 0, or not below [**Count**](Count), raises run-time error `vbObjectError + 9` with the message *Requested item not found in Matches collection*. + +## Example + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "[a-z]+" +re.Global = True + +Dim matches As MatchCollection +Set matches = re.Execute("one two three") +Debug.Print matches.Item(0).Value ' one +Debug.Print matches(2).Value ' three +``` + +This example asks for a position that the collection does not have. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "[a-z]+" +re.Global = True + +Dim matches As MatchCollection +Set matches = re.Execute("one two three") + +Dim m As Match +On Error Resume Next +Set m = matches.Item(3) +Debug.Print (Err.Number = vbObjectError + 9) ' True +Debug.Print Err.Description ' Requested item not found in Matches collection +``` + +## See Also + +- [Count](Count) property -- the number of matches +- [NewEnum](NewEnum) method -- the enumerator for **For Each** +- [Match](../Match/) class -- one match +- [MatchCollection](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/MatchCollection/NewEnum.md b/docs/Reference/Contributed/tbRegExpold/MatchCollection/NewEnum.md new file mode 100644 index 00000000..62186d6f --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/MatchCollection/NewEnum.md @@ -0,0 +1,51 @@ +--- +title: NewEnum +parent: MatchCollection +permalink: /tB/Packages/Contributed/tbRegExp/MatchCollection/NewEnum +has_toc: false +--- + +# NewEnum +{: .no_toc } + +Returns the enumerator that **For Each** uses to go through the [**Match**](../Match/) objects of the collection. + +Syntax: *object*.**NewEnum** **As IUnknown** + +*object* +: *required* An object expression that evaluates to a **MatchCollection**. + +Returns an **IUnknown** that refers to a new enumerator over the matches, which starts at the first one. + +## Remarks + +**NewEnum** is declared with the [**Enumerator**](../../../../Core/Attributes#enumerator) attribute, which makes it the member that [**For Each**](../../../../Core/For-Each-Next) calls to start an enumeration. [Custom Enumerators](../../../../../Features/Language/Custom-Enumerators) describes the rule. It is also declared [**Hidden**](../../../../Core/Attributes#hidden), so it does not appear in certain IntelliSense lists. + +Each element is a **Match**, and the matches come in the order they were found. A collection can be enumerated any number of times, and an enumeration inside another one is independent. The element variable of a **For Each** loop can be declared **As Match**, as in the example. + +A program does not need to call **NewEnum**. **For Each** does it. + +## Example + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "[a-z]+" +re.Global = True + +Dim matches As MatchCollection +Set matches = re.Execute("one two three") + +Dim s As String +Dim m As Match +For Each m In matches + s = s & "[" & m.Value & "]" +Next +Debug.Print s ' [one][two][three] +``` + +## See Also + +- [Item](Item) property -- returns a match by position +- [Count](Count) property -- the number of matches +- [For Each...Next](../../../../Core/For-Each-Next) statement -- uses the enumerator +- [MatchCollection](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/MatchCollection/index.md b/docs/Reference/Contributed/tbRegExpold/MatchCollection/index.md new file mode 100644 index 00000000..a6e5f8ee --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/MatchCollection/index.md @@ -0,0 +1,51 @@ +--- +title: MatchCollection +parent: tbRegExp Package +permalink: /tB/Packages/Contributed/tbRegExp/MatchCollection/ +has_toc: false +--- + +# MatchCollection class +{: .no_toc } + +Holds the [**Match**](../Match/) objects that a regular expression finds in a string. + +The **Execute** method of [**RegExp**](../RegExp/) returns a **MatchCollection**. When the **Global** property of the expression is **True**, the collection holds every match. Otherwise it holds the first match only. A string in which the pattern finds nothing gives an empty collection. + +The collection is read by position, and the position is zero-based, as in the VBScript object model that the package replaces. [**Item**](Item) is the default member, so `matches(0)` is the same as `matches.Item(0)`. **For Each** goes through the matches in the order they were found. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "\d+" +re.Global = True + +Dim matches As MatchCollection +Set matches = re.Execute("a1 b22 c333") +Debug.Print matches.Count ' 3 +Debug.Print matches(1).Value ' 22 + +Dim s As String +Dim m As Match +For Each m In matches + s = s & "[" & m.Value & "]" +Next +Debug.Print s ' [1][22][333] +``` + +The class has no member that adds or removes a match: only **Execute** fills a **MatchCollection**. + +## Properties + +- [Count](Count) -- returns the number of matches +- [Item](Item) -- returns one match by position (the default member) + +## Methods + +- [NewEnum](NewEnum) -- returns the enumerator that **For Each** uses (hidden) + +## See Also + +- [Match](../Match/) class -- one match +- [RegExp](../RegExp/) class -- its **Execute** method returns the collection +- [For Each...Next](../../../../Core/For-Each-Next) statement -- goes through the matches +- [tbRegExp package](../) -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/DotAll.md b/docs/Reference/Contributed/tbRegExpold/RegExp/DotAll.md new file mode 100644 index 00000000..b74783e5 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/DotAll.md @@ -0,0 +1,57 @@ +--- +title: DotAll +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/DotAll +has_toc: false +--- + +# DotAll +{: .no_toc } + +Returns or sets whether `.` in the pattern matches a line feed. + +## Get + +Returns a **Boolean**. + +Syntax: *object*.**DotAll** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +The default value is **False**. + +## Let + +Sets the value. + +Syntax: *object*.**DotAll** **=** *value* + +*value* +: A **Boolean**. **True** lets `.` match any character, a line feed included. **False** makes `.` match any character except a line feed. + +## Remarks + +As in VBScript's **RegExp**, only the line feed (`vbLf`) is excluded when the value is **False**. The carriage return (`vbCr`) and the characters U+2028 and U+2029 are matched by `.` whatever the value. [**MultiLine**](MultiLine) treats all four as line terminators for `^` and `$`. + +The value is applied when [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) or [**Split**](Split) makes a match. Changing it does not compile the pattern again. The flag `s` of [**Flags**](Flags) reads and sets the same value. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "a.b" +Debug.Print re.Test("a" & vbLf & "b") ' False +Debug.Print re.Test("a" & vbCr & "b") ' True + +re.DotAll = True +Debug.Print re.Test("a" & vbLf & "b") ' True +``` + +## See Also + +- [Flags](Flags) property -- sets this option with the letter `s` +- [MultiLine](MultiLine) property -- the option for `^` and `$` +- [Pattern](Pattern) property -- the regular expression +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Execute.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Execute.md new file mode 100644 index 00000000..30cfea8d --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/Execute.md @@ -0,0 +1,82 @@ +--- +title: Execute +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Execute +has_toc: false +--- + +# Execute +{: .no_toc } + +Searches a string for the pattern and returns the matches. + +Syntax: *object*.**Execute** ( *sourceString* ) **As MatchCollection** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +*sourceString* +: *required* A **String**, the text to search. + +Returns a [**MatchCollection**](../MatchCollection/) that holds one [**Match**](../Match/) for each match of [**Pattern**](Pattern) in *sourceString*, in the order they occur. The collection is empty when there is no match. + +## Remarks + +With [**Global**](Global) set to **False**, the collection holds at most one match, the first. With **Global** set to **True** it holds every match. + +[**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) change what counts as a match. + +A match can have no characters, as `(?=[A-Z])` has before each capital letter, and each such match is returned once. `fooBarBaz` has two capitals, so the collection holds two matches, at positions 3 and 6. + +```tb check_build check_run +Dim re As New RegExp +re.Global = True +re.Pattern = "(?=[A-Z])" +Debug.Print re.Execute("fooBarBaz").Count ' 2 +Debug.Print re.Execute("fooBarBaz")(1).FirstIndex ' 6 +``` + + +A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging. The collection then holds the matches found before it gave up, and [**SearchAborted**](SearchAborted) returns **True**. + + +Each **Match** gives the matched text ([**Value**](../Match/Value)), the position where it starts ([**FirstIndex**](../Match/FirstIndex), counted from 0), its length, and the text of each group of the pattern ([**SubMatches**](../Match/SubMatches)). A group written `(?...)` can be read by its name. + +An invalid pattern raises the compile error, with the number in [**LastErrorNumber**](LastErrorNumber) and the description in [**LastError**](LastError). Check [**IsValid**](IsValid) first to avoid it. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "(?\d{4})-(?\d{2})" +re.Global = True + +Dim matches As MatchCollection +Set matches = re.Execute("2026-10 and 2027-01") +Debug.Print matches.Count + +Dim m As Match +For Each m In matches + Debug.Print m.Value & " at " & m.FirstIndex & ": " & m.SubMatches.Item("year") & "/" & m.SubMatches.Item("month") +Next + +re.Global = False +Debug.Print re.Execute("2026-10 and 2027-01").Count +Debug.Print re.Execute("no date").Count +' Output: +' 2 +' 2026-10 at 0: 2026/10 +' 2027-01 at 12: 2027/01 +' 1 +' 0 +``` + +## See Also + +- [Test](Test) method -- returns whether a string contains a match +- [Replace](Replace) method -- replaces the matches +- [Split](Split) method -- splits a string at the matches +- [Global](Global) property -- all matches or the first +- [MatchCollection](../MatchCollection/) class -- the result +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Flags.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Flags.md new file mode 100644 index 00000000..616e9c9b --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/Flags.md @@ -0,0 +1,72 @@ +--- +title: Flags +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Flags +has_toc: false +--- + +# Flags +{: .no_toc } + +Returns or sets the four matching options as one string of letters. + +## Get + +Returns a **String** that holds the letter of each option that is **True**, always in the order `g`, `m`, `i`, `s`. It is an empty string when all four are **False**. + +Syntax: *object*.**Flags** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +## Let + +Sets all four options at once. + +Syntax: *object*.**Flags** **=** *value* + +*value* +: A **String** of letters. The letters are not case-sensitive. + +| Letter | Option | +|--------|--------| +| `g` | [**Global**](Global) | +| `m` | [**MultiLine**](MultiLine) | +| `i` | [**IgnoreCase**](IgnoreCase) | +| `s` | [**DotAll**](DotAll) | + +## Remarks + +An option whose letter is missing from *value* is set to **False**, so `vbNullString` turns all four off. Other characters in *value* are ignored, and the order and repetition of the letters do not matter. + +The `i` letter is applied through [**IgnoreCase**](IgnoreCase), so the pattern is compiled again the next time it is needed when that option changes. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Flags = "GI" +Debug.Print re.Global ' True +Debug.Print re.IgnoreCase ' True +Debug.Print re.MultiLine ' False +Debug.Print re.Flags ' gi + +re.DotAll = True +Debug.Print re.Flags ' gis + +re.Flags = "m" +Debug.Print re.Flags ' m +Debug.Print re.Global ' False + +re.Flags = vbNullString +Debug.Print Len(re.Flags) ' 0 +``` + +## See Also + +- [Global](Global) property -- the `g` option +- [MultiLine](MultiLine) property -- the `m` option +- [IgnoreCase](IgnoreCase) property -- the `i` option +- [DotAll](DotAll) property -- the `s` option +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Global.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Global.md new file mode 100644 index 00000000..4482d911 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/Global.md @@ -0,0 +1,60 @@ +--- +title: Global +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Global +has_toc: false +--- + +# Global +{: .no_toc } + +Returns or sets whether all matches are used, or only the first. + +## Get + +Returns a **Boolean**. + +Syntax: *object*.**Global** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +The default value is **False**. + +## Let + +Sets the value. + +Syntax: *object*.**Global** **=** *value* + +*value* +: A **Boolean**. **True** makes [**Execute**](Execute) return every match, [**Replace**](Replace) replace every match, and [**Split**](Split) split at every match. **False** makes each of them use only the first match. + +## Remarks + +[**Test**](Test) does not depend on the value, because it only reports whether a match exists. + +The value is applied when a match is made. Changing it does not compile the pattern again. The flag `g` of [**Flags**](Flags) reads and sets the same value. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "\d+" + +Debug.Print re.Execute("a1b22c333").Count ' 1 +Debug.Print re.Replace("a1b22c333", "#") ' a#b22c333 + +re.Global = True +Debug.Print re.Execute("a1b22c333").Count ' 3 +Debug.Print re.Replace("a1b22c333", "#") ' a#b#c# +``` + +## See Also + +- [Flags](Flags) property -- sets this option with the letter `g` +- [Execute](Execute) method -- returns the matches +- [Replace](Replace) method -- replaces the matches +- [Split](Split) method -- splits a string at the matches +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/IgnoreCase.md b/docs/Reference/Contributed/tbRegExpold/RegExp/IgnoreCase.md new file mode 100644 index 00000000..9c4cfc47 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/IgnoreCase.md @@ -0,0 +1,56 @@ +--- +title: IgnoreCase +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/IgnoreCase +has_toc: false +--- + +# IgnoreCase +{: .no_toc } + +Returns or sets whether letter case is ignored when the pattern is matched. + +## Get + +Returns a **Boolean**. + +Syntax: *object*.**IgnoreCase** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +The default value is **False**. + +## Let + +Sets the value. + +Syntax: *object*.**IgnoreCase** **=** *value* + +*value* +: A **Boolean**. **True** makes matches case-insensitive. **False** makes them case-sensitive. + +## Remarks + +Unlike [**Global**](Global), [**MultiLine**](MultiLine) and [**DotAll**](DotAll), this option is part of the compiled pattern. Setting it to a different value makes the pattern compile again the next time it is needed, and [**IsValid**](IsValid), [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) then refer to the new compilation. Setting it to its current value does nothing. + +The flag `i` of [**Flags**](Flags) reads and sets the same value. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "hello" +Debug.Print re.Test("Hello World") ' False + +re.IgnoreCase = True +Debug.Print re.Test("Hello World") ' True +``` + +## See Also + +- [Flags](Flags) property -- sets this option with the letter `i` +- [Pattern](Pattern) property -- the regular expression +- [IsValid](IsValid) property -- whether the pattern compiles +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/IsValid.md b/docs/Reference/Contributed/tbRegExpold/RegExp/IsValid.md new file mode 100644 index 00000000..8929b93a --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/IsValid.md @@ -0,0 +1,53 @@ +--- +title: IsValid +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/IsValid +has_toc: false +--- + +# IsValid +{: .no_toc } + +Returns whether the current pattern compiles. Raises no error. + +Syntax: *object*.**IsValid** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +Returns a **Boolean**: **True** when [**Pattern**](Pattern) compiles, **False** when it does not. + +## Remarks + +The property compiles the pattern if it has not been compiled since [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) last changed, and keeps the result. Nothing is raised when the compilation fails. For a pattern that fails, [**LastError**](LastError) holds the message and [**LastErrorNumber**](LastErrorNumber) the number. + +Check **IsValid** before [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) or [**Split**](Split) when the pattern comes from outside the program, because each of those raises the compile error. + + +> [!NOTE] 1.8.0.0 +> Although nothing is raised, the compile error of an invalid pattern stays in the **Err** object after the call that compiled the pattern returns. A program that tests **Err** for other errors calls **Err.Clear** first. + + +> [!NOTE] 1.8.0.0 +> A quantifier whose lower count is above its upper count, such as `a{2,1}`, is not found when the pattern is compiled, so **IsValid** returns **True** for it and [**LastErrorNumber**](LastErrorNumber) returns 0. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) then raise error 3000 when a search reaches the quantifier, with the description that Windows gives that number, *The specified print monitor is unknown.* + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "(\d+" +Debug.Print re.IsValid ' False +Debug.Print Err.Number = re.LastErrorNumber ' True +Err.Clear + +re.Pattern = "(\d+)" +Debug.Print re.IsValid ' True +``` + +## See Also + +- [LastError](LastError) property -- the message of the compile error +- [LastErrorNumber](LastErrorNumber) property -- the number of the compile error +- [Pattern](Pattern) property -- the regular expression +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/LastError.md b/docs/Reference/Contributed/tbRegExpold/RegExp/LastError.md new file mode 100644 index 00000000..5935dc1a --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/LastError.md @@ -0,0 +1,53 @@ +--- +title: LastError +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/LastError +has_toc: false +--- + +# LastError +{: .no_toc } + +Returns the message of the error that compiling the current pattern raised. + +Syntax: *object*.**LastError** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +Returns a **String**: the description of the compile error, or an empty string when the pattern is valid. + +## Remarks + + +The property compiles the pattern if it has not been compiled since [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) last changed. The compilation traps its error, so **LastError** raises nothing, but the error stays in the **Err** object (see [**IsValid**](IsValid)). The text is a sentence that names the fault, such as *Unterminated character class: a '[' without a matching ']'.* for the pattern `[abc`. [**LastErrorNumber**](LastErrorNumber) is the number of the error and lists the faults. + + +[**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise an error with this description when the pattern is invalid. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "(\d+)" +Debug.Print Len(re.LastError) ' 0 + +re.Pattern = "(\d+" +Debug.Print re.IsValid ' False +Debug.Print Len(re.LastError) > 0 ' True + +re.Pattern = "[abc" +Debug.Print re.LastError ' Unterminated character class: a '[' without a matching ']'. + +On Error Resume Next +re.Test "123" +Debug.Print Err.Description = re.LastError ' True +``` + +## See Also + +- [LastErrorNumber](LastErrorNumber) property -- the number of the compile error +- [IsValid](IsValid) property -- whether the pattern compiles +- [Pattern](Pattern) property -- the regular expression +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/LastErrorNumber.md b/docs/Reference/Contributed/tbRegExpold/RegExp/LastErrorNumber.md new file mode 100644 index 00000000..14204816 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/LastErrorNumber.md @@ -0,0 +1,65 @@ +--- +title: LastErrorNumber +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/LastErrorNumber +has_toc: false +--- + +# LastErrorNumber +{: .no_toc } + +Returns the number of the error that compiling the current pattern raised. + +Syntax: *object*.**LastErrorNumber** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +Returns a **Long**: the number of the compile error, or 0 when the pattern is valid. + +## Remarks + +The property compiles the pattern if it has not been compiled since [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) last changed. The compilation traps its error, so **LastErrorNumber** raises nothing. The value is the `Number` of the error that the compilation raised; [**LastError**](LastError) is its description. + + +The engine raises its pattern errors with numbers of the form `vbObjectError + 3000 + n`, where *n* identifies the fault, as in the example. The compilation traps the error, so **LastErrorNumber** raises nothing, but the error stays in the **Err** object (see [**IsValid**](IsValid)). + + +| *n* | Fault | Example | +|-----|-------|---------| +| 1 | A backslash at the end, a `\c` without a letter, or a `\0` followed by `8` or `9` | `\`, `\c1`, `\08` | +| 2 | A `\x` or `\u` escape without its hexadecimal digits | `\u00` | +| 3 | A range in a character class that runs backwards | `[z-a]` | +| 4 | A character class without its `]` | `[abc` | +| 6 | A group construct `(?<` that is neither a lookbehind nor a valid name | `(?<1a>x)` | +| 8 | A `{` that does not start a quantifier, or a count that is too large | `a{2`, `\p{L}` | +| 9 | A group without its `)` | `(\d+` | +| 11 | A `)` that closes no group | `)` | +| 12 | A quantifier with nothing to repeat | `*a` | +| 13 | A group name with a character that a name cannot hold | `(?x)` | +| 14 | An inline option that is not `i`, `m` or `s`, which includes an atomic group and a conditional | `(?x)`, `(?>a)` | + +[**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise an error with this number when the pattern is invalid. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "(\d+)" +Debug.Print re.LastErrorNumber ' 0 + +re.Pattern = "(\d+" +Debug.Print re.LastErrorNumber - vbObjectError ' 3009 + +On Error Resume Next +re.Test "123" +Debug.Print Err.Number = re.LastErrorNumber ' True +``` + +## See Also + +- [LastError](LastError) property -- the message of the compile error +- [IsValid](IsValid) property -- whether the pattern compiles +- [Pattern](Pattern) property -- the regular expression +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/MultiLine.md b/docs/Reference/Contributed/tbRegExpold/RegExp/MultiLine.md new file mode 100644 index 00000000..4cfe6971 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/MultiLine.md @@ -0,0 +1,60 @@ +--- +title: MultiLine +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/MultiLine +has_toc: false +--- + +# MultiLine +{: .no_toc } + +Returns or sets whether `^` and `$` in the pattern match at the boundaries of each line. + +## Get + +Returns a **Boolean**. + +Syntax: *object*.**MultiLine** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +The default value is **False**. + +## Let + +Sets the value. + +Syntax: *object*.**MultiLine** **=** *value* + +*value* +: A **Boolean**. **True** lets `^` match at the start of the string and after a line terminator, and `$` match at the end of the string and before a line terminator. **False** lets `^` match only at the start of the whole string and `$` only at its end. + +## Remarks + +The line terminators are the line feed (`vbLf`), the carriage return (`vbCr`), and the characters U+2028 and U+2029. + +The value is applied when [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) or [**Split**](Split) makes a match. Changing it does not compile the pattern again. The flag `m` of [**Flags**](Flags) reads and sets the same value. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "^\w+$" +re.Global = True + +Dim text As String +text = "one" & vbLf & "two" +Debug.Print re.Execute(text).Count ' 0 + +re.MultiLine = True +Debug.Print re.Execute(text).Count ' 2 +``` + +## See Also + +- [Flags](Flags) property -- sets this option with the letter `m` +- [DotAll](DotAll) property -- the option for `.` +- [Pattern](Pattern) property -- the regular expression +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Pattern.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Pattern.md new file mode 100644 index 00000000..b14a1210 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/Pattern.md @@ -0,0 +1,56 @@ +--- +title: Pattern +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Pattern +has_toc: false +--- + +# Pattern +{: .no_toc } + +Returns or sets the regular expression. + +## Get + +Returns a **String**. + +Syntax: *object*.**Pattern** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +The default value is an empty string. + +## Let + +Sets the regular expression. + +Syntax: *object*.**Pattern** **=** *value* + +*value* +: A **String** that holds the regular expression. + +## Remarks + +Setting a different pattern makes it compile again the next time it is needed. Setting the current pattern does nothing. A pattern that does not compile raises no error when it is set: [**IsValid**](IsValid), [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) report the failure, and [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise it. + +A named group is written `(?...)`. Its text is read from the [**SubMatches**](../SubMatches/) of a [**Match**](../Match/) by name, and used in [**Replace**](Replace) as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores. The [overview of the package](../#pattern-syntax) lists the other differences from VBScript's pattern syntax. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +Debug.Print re.Pattern = vbNullString ' True + +re.Pattern = "b+" +Debug.Print re.Pattern ' b+ +Debug.Print re.Replace("abbbc", "-") ' a-c +``` + +## See Also + +- [IsValid](IsValid) property -- whether the pattern compiles +- [IgnoreCase](IgnoreCase) property -- the option that changes how the pattern is compiled +- [Execute](Execute) method -- returns the matches +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Replace.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Replace.md new file mode 100644 index 00000000..0d68f492 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/Replace.md @@ -0,0 +1,91 @@ +--- +title: Replace +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Replace +has_toc: false +--- + +# Replace +{: .no_toc } + +Replaces the matches of the pattern in a string and returns the result. + +Syntax: *object*.**Replace** ( *sourceString*, *replaceString* ) **As String** + +*object* +: *required* An object expression that evaluates to a **RegExp**. + +*sourceString* +: *required* A **String**, the text to search. The argument is not changed. + +*replaceString* +: *required* A **String**, the text that replaces each match. It can hold the substitution items in the table below. + +Returns a new **String** with the replacements made. It is *sourceString* unchanged when there is no match. + +## Substitution items + +A `$` in *replaceString* starts a substitution item. Any other text is copied as it is, and so is a `$` that starts no item. + +| Item | Is replaced with | +|------|------------------| +| `$$` | A single `$`. | +| `$&` | The matched text. | +| `` $` `` | The part of *sourceString* before the match. | +| `$'` | The part of *sourceString* after the match. | +| `$`*n* or `$`*nn* | The text of numbered group *n*, counting from 1. Two digits are read as the number when the pattern has a group with that number, otherwise one. It is an empty string when the group matched nothing. When the pattern has no group *n*, the `$` is copied, and so are the digits: `$0` and `$00` are never items, and `$3` in a pattern of two groups is the text `$3`. | +| `$<`*name*`>` | The text of the group written `(?...)`. It is an empty string when the group matched nothing or no group has the name. When the pattern has no named group, or the `>` is missing, or the name is empty, the `$` is copied. | +| `$~` | Nothing. The item is dropped. | + +A `$` that starts no item raises no error, so a replacement string can hold a literal `$` as it is, at the end or before any other character. Before a character that would make an item, write `$$`: `$$$&` inserts a `$` and then the matched text, as the last example does. + + +> [!NOTE] 1.8.0.0 +> Items are read as in VBScript's **RegExp**, with two differences: `$<`*name*`>` is an addition of this package, and `$~` is dropped, where VBScript copies `$~` into the result. + +## Remarks + +With [**Global**](Global) set to **False**, only the first match is replaced. With **Global** set to **True**, every match is. [**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) change what counts as a match. + +A match of no characters is replaced once, wherever it is found. In this example the pattern `^` puts `> ` at the start of each of three lines. + +```tb check_build check_run +Dim re As New RegExp +re.Global = True +re.MultiLine = True +re.Pattern = "^" +Debug.Print Replace(re.Replace("a" & vbLf & "b" & vbLf & "c", "> "), vbLf, "/") ' > a/> b/> c +``` + + +A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Replace** then replaces only the matches found before it gave up. [**SearchAborted**](SearchAborted) returns **True** in that case. + + +An invalid pattern raises the compile error, with the number in [**LastErrorNumber**](LastErrorNumber) and the description in [**LastError**](LastError). Check [**IsValid**](IsValid) first to avoid it. + +## Example + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "(\w+)@(\w+)\.com" +Debug.Print re.Replace("ann@example.com, bob@test.com", "$2:$1") ' example:ann, bob@test.com + +re.Global = True +Debug.Print re.Replace("ann@example.com, bob@test.com", "$2:$1") ' example:ann, test:bob + +re.Pattern = "\d+" +Debug.Print re.Replace("5 and 12", "$$$&") ' $5 and $12 +Debug.Print re.Replace("x5", "$ $x $3") ' x$ $x $3 + +re.Pattern = "(?\w+) (?\w+)" +Debug.Print re.Replace("Ada Lovelace", "$, $") ' Lovelace, Ada +``` + +## See Also + +- [Execute](Execute) method -- returns the matches +- [Split](Split) method -- splits a string at the matches +- [Global](Global) property -- all matches or the first +- [Pattern](Pattern) property -- the regular expression +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/SearchAborted.md b/docs/Reference/Contributed/tbRegExpold/RegExp/SearchAborted.md similarity index 100% rename from docs/Reference/Contributed/tbRegExp/RegExp/SearchAborted.md rename to docs/Reference/Contributed/tbRegExpold/RegExp/SearchAborted.md diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Split.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Split.md new file mode 100644 index 00000000..b1563cfa --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/Split.md @@ -0,0 +1,99 @@ +--- +title: Split +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Split +has_toc: false +--- + +# Split +{: .no_toc } + +Splits a string at the matches of the pattern and returns the pieces as a **Collection**. + +Syntax: *object*.**Split** ( *sourceString* ) **As Collection** + +*object* +: *required* An object expression that evaluates to a [**RegExp**](./). + +*sourceString* +: *required* A **String**, the text to split. + +Returns a new **Collection** of **String** values, numbered from 1. It holds the text before the first match, the text between each match and the next, and the text after the last match. The matched text itself is not in the collection. + +## Remarks + +[**Global**](Global) sets how many matches split the string. With **Global** set to **False**, the default, only the first match splits it, so the collection holds the text before that match and the text after it. With **Global** set to **True**, every match splits it. + +A match that has capture groups adds the text of each group to the collection, in order, straight after the text before that match. A group that took no part in the match, or matched nothing, adds an empty string. + +When [**Pattern**](Pattern) finds no match, the collection holds one item, the whole of *sourceString*. No piece is dropped: a match at the start of the string gives an empty first string, and two matches next to each other give an empty string between them. + +[**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) apply to the matching. + +A match of no characters splits the string at its position, once. In this example the string is split before each capital letter. + +```tb check_build check_run +Dim re As New RegExp +re.Global = True +re.Pattern = "(?=[A-Z])" + +Dim parts As Collection +Set parts = re.Split("fooBarBaz") +Debug.Print parts.Count ' 3 +Debug.Print parts(1) & "|" & parts(2) & "|" & parts(3) ' foo|Bar|Baz +``` + + +A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Split** then splits only at the matches found before it gave up. [**SearchAborted**](SearchAborted) returns **True** in that case. + + +When **Pattern** is not a valid regular expression, **Split** raises the compile error. Its number is [**LastErrorNumber**](LastErrorNumber), its description is [**LastError**](LastError), and its source is `RegExp`. Read [**IsValid**](IsValid) first to avoid the error. + +## Example + +This example splits a list at each comma, first at the first match only, then at every match. + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = ",\s*" + +Dim parts As Collection +Set parts = re.Split("red, green,blue") +Debug.Print parts.Count ' 2 +Debug.Print parts(1) ' red +Debug.Print parts(2) ' green,blue + +re.Global = True +Set parts = re.Split("red, green,blue") +Debug.Print parts.Count ' 3 +Debug.Print parts(1) ' red +Debug.Print parts(2) ' green +Debug.Print parts(3) ' blue +``` + +This example shows the text of a capture group in the result. + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "(-)" +re.Global = True + +Dim parts As Collection +Set parts = re.Split("a-b-c") +Debug.Print parts.Count ' 5 +Debug.Print parts(1) ' a +Debug.Print parts(2) ' - +Debug.Print parts(3) ' b +Debug.Print parts(4) ' - +Debug.Print parts(5) ' c +``` + +## See Also + +- [Execute](Execute) method -- returns the matches themselves +- [Replace](Replace) method -- replaces the matches +- [Global](Global) property -- all matches or only the first +- [Pattern](Pattern) property -- the regular expression +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Test.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Test.md new file mode 100644 index 00000000..49358f94 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/Test.md @@ -0,0 +1,58 @@ +--- +title: Test +parent: RegExp +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Test +has_toc: false +--- + +# Test +{: .no_toc } + +Returns whether the pattern finds a match in a string. + +Syntax: *object*.**Test** ( *sourceString* ) **As Boolean** + +*object* +: *required* An object expression that evaluates to a [**RegExp**](./). + +*sourceString* +: *required* A **String**, the text to search. + +Returns **True** if [**Pattern**](Pattern) finds a match in *sourceString*, otherwise **False**. + +## Remarks + +**Test** only reports whether a match exists. To read the matches, use [**Execute**](Execute). + +[**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) apply to the matching. [**Global**](Global) makes no difference, because one match is enough. + + +A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Test** then returns **False**. [**SearchAborted**](SearchAborted) tells that **False** from a string that has no match. + + +When **Pattern** is not a valid regular expression, **Test** raises the compile error. Its number is [**LastErrorNumber**](LastErrorNumber), its description is [**LastError**](LastError), and its source is `RegExp`. Read [**IsValid**](IsValid) first to avoid the error. + +## Example + +This example tests two strings against a pattern, then shows the effect of **IgnoreCase**. + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "\d{3}-\d{4}" +Debug.Print re.Test("Call 555-0142 today") ' True +Debug.Print re.Test("Call us today") ' False + +re.Pattern = "^hello" +Debug.Print re.Test("Hello") ' False +re.IgnoreCase = True +Debug.Print re.Test("Hello") ' True +``` + +## See Also + +- [Execute](Execute) method -- returns the matches themselves +- [Replace](Replace) method -- replaces the matches +- [Split](Split) method -- splits a string at the matches +- [IsValid](IsValid) property -- whether the pattern compiles +- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/index.md b/docs/Reference/Contributed/tbRegExpold/RegExp/index.md new file mode 100644 index 00000000..413728d0 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/RegExp/index.md @@ -0,0 +1,77 @@ +--- +title: RegExp +parent: tbRegExp Package +permalink: /tB/Packages/Contributed/tbRegExp/RegExp/ +has_toc: false +--- + +# RegExp class +{: .no_toc } + +Searches strings with a regular expression: finds the matches, tests for one, replaces them and splits a string at them. + +**RegExp** is a replacement for the regular expression object of VBScript. It follows the same object model: [**Execute**](Execute) returns a [**MatchCollection**](../MatchCollection/) of [**Match**](../Match/) objects, and the indexes of both are zero-based. The matching is done by the vba-regex engine, which the package includes as the **StaticRegex** module. + +The class is not predeclared. Create an instance with **New**, set [**Pattern**](Pattern) and the options, then call a method. + +```tb check_build check_run +Dim re As RegExp +Set re = New RegExp +re.Pattern = "(\d+)-(\d+)" +re.Global = True + +Dim matches As MatchCollection +Set matches = re.Execute("10-20 and 30-40") +Debug.Print matches.Count ' 2 +Debug.Print matches.Item(0).Value ' 10-20 +Debug.Print matches.Item(1).SubMatches.Item(0) ' 30 + +Debug.Print re.Test("none") ' False +Debug.Print re.Replace("10-20", "$2-$1") ' 20-10 +``` + +## Options + +Four properties change how the pattern is matched. [**Flags**](Flags) reads and sets all four as one string of letters. + +| Property | Flag | When **True** | +|----------|------|---------------| +| [**Global**](Global) | `g` | All matches are used. When **False**, [**Execute**](Execute), [**Replace**](Replace) and [**Split**](Split) use only the first match. | +| [**MultiLine**](MultiLine) | `m` | `^` and `$` also match at the start and end of each line. | +| [**IgnoreCase**](IgnoreCase) | `i` | Letter case is ignored. | +| [**DotAll**](DotAll) | `s` | `.` also matches a line feed. | + +All four are **False** in a new instance. + +## Invalid patterns + +The pattern is compiled the first time a method or one of [**IsValid**](IsValid), [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) needs it. It is compiled again after [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) is set to a different value. [**Global**](Global), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) are applied when a match is made and do not cause a new compilation. + +A pattern that does not compile does not raise an error at once. [**IsValid**](IsValid) returns **False**, and [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) describe the failure. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) then raise that error, with the number in **LastErrorNumber**, the description in **LastError** and the source `RegExp`. When **IsValid**, **LastError** or **LastErrorNumber** is the call that compiles an invalid pattern, it raises nothing, but the compile error stays in the **Err** object after it returns; [**IsValid**](IsValid) says what to do about it. + +## Properties + +- [DotAll](DotAll) -- returns or sets whether `.` matches a line feed +- [Flags](Flags) -- returns or sets the four options as a string of letters +- [Global](Global) -- returns or sets whether all matches are used +- [IgnoreCase](IgnoreCase) -- returns or sets whether letter case is ignored +- [IsValid](IsValid) -- returns whether the pattern compiles +- [LastError](LastError) -- returns the message of the compile error +- [LastErrorNumber](LastErrorNumber) -- returns the number of the compile error +- [MultiLine](MultiLine) -- returns or sets whether `^` and `$` match at line boundaries +- [Pattern](Pattern) -- returns or sets the regular expression +- [SearchAborted](SearchAborted) -- returns whether the last search gave up before it finished + +## Methods + +- [Execute](Execute) -- returns the matches in a string +- [Replace](Replace) -- replaces the matches in a string +- [Split](Split) -- splits a string at the matches +- [Test](Test) -- returns whether a string contains a match + +## See Also + +- [MatchCollection](../MatchCollection/) class -- the matches that **Execute** returns +- [Match](../Match/) class -- one match +- [SubMatches](../SubMatches/) class -- the groups of one match +- [tbRegExp package](../) -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/SubMatches/Count.md b/docs/Reference/Contributed/tbRegExpold/SubMatches/Count.md new file mode 100644 index 00000000..0186963a --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/SubMatches/Count.md @@ -0,0 +1,50 @@ +--- +title: Count +parent: SubMatches +grand_parent: tbRegExp Package +permalink: /tB/Packages/Contributed/tbRegExp/SubMatches/Count +has_toc: false +--- + +# Count +{: .no_toc } + +Returns the number of strings in the collection, one for each capture group of the pattern. + +Syntax: *object*.**Count** + +*object* +: *required* An object expression that evaluates to a **SubMatches**. + +Returns a **Long**. The property is read-only. + +## Remarks + +**Count** is the number of capture groups in the pattern, not the number of groups that took part in the match. A group that took no part still has its position, and its string is empty. It is 0 for a pattern with no capture groups. + +The positions that [**Item**](Item) accepts run from 0 to **Count** - 1. + +## Example + +```tb check_build check_run +Dim re As New RegExp +Dim matches As MatchCollection +Dim sm As SubMatches + +re.Pattern = "\d+" +Set matches = re.Execute("a1") +Set sm = matches(0).SubMatches +Debug.Print sm.Count ' 0 + +re.Pattern = "(a)|(b)" +Set matches = re.Execute("b") +Set sm = matches(0).SubMatches +Debug.Print sm.Count ' 2 +Debug.Print Len(sm(0)) ' 0 +Debug.Print sm(1) ' b +``` + +## See Also + +- [Item](Item) property -- returns a string by position or by name +- [SubMatches](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/SubMatches/Item.md b/docs/Reference/Contributed/tbRegExpold/SubMatches/Item.md new file mode 100644 index 00000000..a8863f85 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/SubMatches/Item.md @@ -0,0 +1,85 @@ +--- +title: Item +parent: SubMatches +grand_parent: tbRegExp Package +permalink: /tB/Packages/Contributed/tbRegExp/SubMatches/Item +has_toc: false +--- + +# Item +{: .no_toc } + +Returns the string captured by one capture group, by its position or by its name. + +Syntax: *object*.**Item** ( *pItemIndex* ) + +*object* +: *required* An object expression that evaluates to a **SubMatches**. + +*pItemIndex* +: *required* A **Variant**. A number is the zero-based position of the group: the first group is 0 and the last is [**Count**](Count) - 1. A value that is not numeric is the name of a group. + +Returns a **String**. The property is read-only. + +## Remarks + +**Item** is the default member of the class, so `sm(0)` is the same as `sm.Item(0)`. + +*pItemIndex* is taken as a position when **IsNumeric** returns **True** for it, and as a name otherwise. + +A position below 0, or not below [**Count**](Count), raises run-time error `vbObjectError + 9` with the message *Requested item not found in SubMatches collection*. + +A name is the one given to a group in the pattern, written `(?...)`. **Item** returns the string of the group that has that name. Letter case counts: `sm("YEAR")` does not find a group named `year`. A name that no group has returns an empty string and raises no error. A name starts with a letter or an underscore and goes on with letters, digits and underscores. + +When several groups have the same name, **Item** returns the string of the last of them that took part in the match, as the last example shows. + +An empty result has three possible causes: a group that took no part in the match, a group that captured no characters, and a name that no group has. + +## Example + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(?\d{4})-(?\d{2})" + +Dim matches As MatchCollection +Set matches = re.Execute("2026-10") + +Dim sm As SubMatches +Set sm = matches(0).SubMatches +Debug.Print sm(0) ' 2026 +Debug.Print sm.Item("month") ' 10 +Debug.Print Len(sm("day")) ' 0 +``` + +This example asks for a position that the collection does not have. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(\d+)" + +Dim matches As MatchCollection +Set matches = re.Execute("a1") + +Dim sm As SubMatches +Set sm = matches(0).SubMatches + +On Error Resume Next +Debug.Print sm(1) +Debug.Print Err.Number & ": " & Err.Description ' -2147221495: Requested item not found in SubMatches collection +``` + +This example gives two groups the same name. In `ab` both groups took part, so the second one is returned. In `a` only the first group did. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(?a)(?b)?" + +Debug.Print re.Execute("ab")(0).SubMatches("n") ' b +Debug.Print re.Execute("a")(0).SubMatches("n") ' a +``` + +## See Also + +- [Count](Count) property -- the number of strings +- [NewEnum](NewEnum) method -- the enumerator for **For Each** +- [SubMatches](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/SubMatches/NewEnum.md b/docs/Reference/Contributed/tbRegExpold/SubMatches/NewEnum.md new file mode 100644 index 00000000..9e295865 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/SubMatches/NewEnum.md @@ -0,0 +1,55 @@ +--- +title: NewEnum +parent: SubMatches +grand_parent: tbRegExp Package +permalink: /tB/Packages/Contributed/tbRegExp/SubMatches/NewEnum +has_toc: false +--- + +# NewEnum +{: .no_toc } + +Returns the enumerator that **For Each** uses to go through the strings of the collection. + +Syntax: *object*.**NewEnum** **As IUnknown** + +*object* +: *required* An object expression that evaluates to a **SubMatches**. + +Returns an **IUnknown** that refers to a new enumerator over the strings of the capture groups, which starts at the first one. + +## Remarks + +**NewEnum** is declared with the [**Enumerator**](../../../../Core/Attributes#enumerator) attribute, which makes it the member that [**For Each**](../../../../Core/For-Each-Next) calls to start an enumeration. [Custom Enumerators](../../../../../Features/Language/Custom-Enumerators) describes the rule. It is also declared [**Hidden**](../../../../Core/Attributes#hidden), so it does not appear in certain IntelliSense lists. + +The elements come in the order of their positions, from 0 to [**Count**](Count) - 1, and each is the string of one group. A group that took no part in the match gives an empty string. A **Variant** variable receives each element, as in the example. + +A program does not need to call **NewEnum**. **For Each** does it. + +## Example + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(\w+)@(\w+)\.com" + +Dim matches As MatchCollection +Set matches = re.Execute("ann@example.com") + +Dim sm As SubMatches +Set sm = matches(0).SubMatches + +Dim part As Variant +For Each part In sm + Debug.Print part +Next +' Output: +' ann +' example +``` + +## See Also + +- [Item](Item) property -- returns a string by position or by name +- [Count](Count) property -- the number of strings +- [For Each...Next](../../../../Core/For-Each-Next) statement -- uses the enumerator +- [SubMatches](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/SubMatches/index.md b/docs/Reference/Contributed/tbRegExpold/SubMatches/index.md new file mode 100644 index 00000000..95f29906 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/SubMatches/index.md @@ -0,0 +1,48 @@ +--- +title: SubMatches +parent: tbRegExp Package +permalink: /tB/Packages/Contributed/tbRegExp/SubMatches/ +has_toc: false +--- + +# SubMatches class +{: .no_toc } + +Holds the strings that the capture groups of a pattern captured in one [**Match**](../Match/). + +The [**SubMatches**](../Match/SubMatches) property of a **Match** returns a **SubMatches** object. It holds one string for each capture group in the pattern, in the order of the groups. A pattern with no capture groups gives an object whose [**Count**](Count) is 0. A group that took no part in the match, such as one side of an alternation that did not match, still has its position, and its string is empty. + +The strings are read by position or by name. The position is zero-based, as in the VBScript object model that the package replaces. A group written `(?...)` has a name, and its string can be read by that name as well. [**Item**](Item) is the default member, so `sm(0)` is the same as `sm.Item(0)`. **For Each** goes through the strings in the order of their positions. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(?\d{4})-(?\d{2})-(?\d{2})" + +Dim matches As MatchCollection +Set matches = re.Execute("Released on 2026-10-11.") + +Dim sm As SubMatches +Set sm = matches(0).SubMatches +Debug.Print sm.Count ' 3 +Debug.Print sm(0) ' 2026 +Debug.Print sm("month") ' 10 +``` + +The class has no member that adds or removes a string: the package fills a **SubMatches** from the groups of the match it belongs to. + +## Properties + +- [Count](Count) -- returns the number of strings +- [Item](Item) -- returns one string by position or by group name (the default member) + +## Methods + +- [NewEnum](NewEnum) -- returns the enumerator that **For Each** uses (hidden) + +## See Also + +- [Match](../Match/) class -- one match; its **SubMatches** property returns this object +- [MatchCollection](../MatchCollection/) class -- the matches of a search +- [RegExp](../RegExp/) class -- runs the search +- [For Each...Next](../../../../Core/For-Each-Next) statement -- goes through the strings +- [tbRegExp package](../) -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/index.md b/docs/Reference/Contributed/tbRegExpold/index.md new file mode 100644 index 00000000..231c2623 --- /dev/null +++ b/docs/Reference/Contributed/tbRegExpold/index.md @@ -0,0 +1,143 @@ +--- +title: tbRegExp Package +parent: Contributed Packages +has_toc: false +exclude_from_docs: + - StaticRegex +indexed_from: 1.8.0.0 +permalink: /tB/Packages/Contributed/tbRegExp/ +--- + +# tbRegExp Package +{: .no_toc } + +**Author:** GCuser99. **Version:** 1.8.0.0. **On GitHub:** [GCuser99/tbRegExp](https://github.com/GCuser99/tbRegExp), in the folder *Package*. + +The **tbRegExp** contributed package provides regular expressions for twinBASIC projects, as a replacement for the **RegExp** object of VBScript. It follows the same object model. A [**RegExp**](RegExp/) is given a pattern and options, its [**Execute**](RegExp/Execute) method returns a [**MatchCollection**](MatchCollection/) of [**Match**](Match/) objects, and each **Match** holds the text captured by the groups of the pattern as a [**SubMatches**](SubMatches/) object. Positions are zero-based, as in VBScript. + +The matching is done by the [vba-regex](https://github.com/sihlfall/vba-regex) engine of sihlfall, which the package includes as a private module, **StaticRegex**. + +Beyond VBScript's object, **RegExp** has the [**DotAll**](RegExp/DotAll) and [**Flags**](RegExp/Flags) options, the [**Split**](RegExp/Split) method, [**IsValid**](RegExp/IsValid), [**LastError**](RegExp/LastError) and [**LastErrorNumber**](RegExp/LastErrorNumber), which report an invalid pattern before it is used, and [**SearchAborted**](RegExp/SearchAborted), which reports a search that gave up. [**Replace**](RegExp/Replace) reads a `$` in the replacement string as VBScript does, and adds `$` for a named group. + +## Pattern syntax + +A pattern is written as for VBScript's **RegExp**. **tbRegExp** also accepts: + +- named groups, `(?...)`: [**SubMatches.Item**](SubMatches/Item) returns one by its name, and [**Replace**](RegExp/Replace) inserts one as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores; a pattern with any other name is not valid; +- lookbehind, `(?<=...)` and `(?\d{4})-(?\d{2})" +Debug.Print re.Execute("2026-10")(0).SubMatches("month") ' 10 +Debug.Print re.Replace("2026-10", "$/$") ' 10/2026 + +re.Pattern = "(?<=\$)\d+" +Debug.Print re.Execute("costs $123")(0).Value ' 123 + +re.Pattern = "(?i:abc)DEF" +Debug.Print re.Test("ABCDEF") ' True +Debug.Print re.Test("ABCdef") ' False +``` + + +> [!NOTE] 1.8.0.0 +> A pattern that holds an atomic group `(?>...)` or a conditional is not valid: [**IsValid**](RegExp/IsValid) returns **False**. A named backreference `\k`, `\A`, `\Z` and `\p{...}` are not supported either, but they do not make the pattern invalid, because a backslash before a letter that names no escape stands for the letter itself, as in VBScript: `\k` matches the text `k`, and `\A` matches `A`. The pattern `\p{L}` is invalid for another reason: `{L}` does not start a quantifier (see below). + +The escapes that name a control character or a character code are read as in VBScript: + +- `\c` followed by a letter, in either case, is a control character: `\cJ` and `\CJ` are the line feed. A `\c` followed by anything else makes the pattern invalid; +- `\0` is the character with code 0, `\0` followed by octal digits, such as `\012`, is that code, and `\8` and `\9` are the digits themselves; +- `\1` to `\9` and longer numbers are backreferences when the pattern has that many groups. Otherwise the digits are read as an octal character code, so `\101` is `A` and `(a)\2` is `a` followed by the character with code 2; [**IsValid**](RegExp/IsValid) returns **True** for both; +- `\f`, `\n`, `\r` and `\t`, inside a character class as well, are the form feed, line feed, carriage return and tab. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "\101" +Debug.Print re.Test("A") ' True +re.Pattern = "\CJ" +Debug.Print re.Test(vbLf) ' True +re.Pattern = "\k" +Debug.Print re.IsValid ' True +Debug.Print re.Test("k") ' True +re.Pattern = "(a)\2" +Debug.Print re.Test("a" & ChrW(2)) ' True +``` + + +> [!NOTE] 1.8.0.0 +> A backreference to a group that has not yet taken part in the match, such as `\1(a)`, matches no characters, so the pattern finds `a` in the string `a`. VBScript's **RegExp** finds no match for such a pattern. + +`\w` matches only the ASCII letters and digits and `_`, as in VBScript, so `\w+` finds `caf` in `café`. + +A `{` has to start a quantifier: `{n}`, `{n,}`, `{n,m}` or `{,m}`, which means `{0,m}`. A pattern in which a `{` starts none, such as `a{` or `a{x}`, is not valid; write `\{` for the character. A `]` or `}` that closes nothing is an ordinary character, as in VBScript. + + +> [!NOTE] 1.8.0.0 +> VBScript's **RegExp** reads a `{` that starts no quantifier, and `{,m}`, as ordinary text: `a{x}` is a valid pattern there that finds the text `a{x}`, and `a{,2}` finds the text `a{,2}` and not `aa`. In **tbRegExp** the first is not valid, so a search with it raises the compile error, and `a{,2}` finds up to two `a`. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(?<_part2>\d)" +Debug.Print re.IsValid ' True +re.Pattern = "(?<2part>\d)" +Debug.Print re.IsValid ' False + +re.Pattern = "a{x}" +Debug.Print re.IsValid ' False +re.Pattern = "a\{x}" +Debug.Print re.Execute("a{x}")(0).Value ' a{x} +``` + +## Searches that give up + + +A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, does not hang a search as it does in VBScript's **RegExp**. Every search has a budget of steps, which a program cannot change, and when it runs out the search stops and raises no error. Its result is then short or empty, and a program that has to tell it from a true result reads [**SearchAborted**](RegExp/SearchAborted) after the call. A long match is no problem: `a*` and `^.*$` match a string of 1,000,000 `a` whole. + + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(a+)+b" +Debug.Print re.Test(String(30, "a")) ' False +Debug.Print re.SearchAborted ' True + +re.Pattern = "^.*$" +Debug.Print re.Test(String(100000, "a")) ' True +Debug.Print re.SearchAborted ' False +``` + +## Classes + +- [Match](Match/) -- holds one match that a RegExp found in a string: the matched text, where it starts, how many characters it has, and the text captured by its groups + - [FirstIndex](Match/FirstIndex) -- returns the zero-based position where the match starts + - [Length](Match/Length) -- returns the number of characters in the match + - [SubMatches](Match/SubMatches) -- returns the text captured by the groups of the pattern + - [Value](Match/Value) -- returns the matched text (default member) +- [MatchCollection](MatchCollection/) -- holds the Match objects that a regular expression finds in a string + - [Count](MatchCollection/Count) -- returns the number of matches + - [Item](MatchCollection/Item) -- returns one match by position (the default member) + - [NewEnum](MatchCollection/NewEnum) -- returns the enumerator that For Each uses (hidden) +- [RegExp](RegExp/) -- searches strings with a regular expression: finds the matches, tests for one, replaces them and splits a string at them + - [DotAll](RegExp/DotAll) -- returns or sets whether `.` matches a line feed + - [Execute](RegExp/Execute) -- returns the matches in a string + - [Flags](RegExp/Flags) -- returns or sets the four options as a string of letters + - [Global](RegExp/Global) -- returns or sets whether all matches are used + - [IgnoreCase](RegExp/IgnoreCase) -- returns or sets whether letter case is ignored + - [IsValid](RegExp/IsValid) -- returns whether the pattern compiles + - [LastError](RegExp/LastError) -- returns the message of the compile error + - [LastErrorNumber](RegExp/LastErrorNumber) -- returns the number of the compile error + - [MultiLine](RegExp/MultiLine) -- returns or sets whether `^` and `$` match at line boundaries + - [Pattern](RegExp/Pattern) -- returns or sets the regular expression + - [Replace](RegExp/Replace) -- replaces the matches in a string + - [SearchAborted](RegExp/SearchAborted) -- returns whether the last search gave up before it finished + - [Split](RegExp/Split) -- splits a string at the matches + - [Test](RegExp/Test) -- returns whether a string contains a match +- [SubMatches](SubMatches/) -- holds the strings that the capture groups of a pattern captured in one Match + - [Count](SubMatches/Count) -- returns the number of strings + - [Item](SubMatches/Item) -- returns one string by position or by group name (the default member) + - [NewEnum](SubMatches/NewEnum) -- returns the enumerator that For Each uses (hidden) + +## Test cases + +The notes and warnings of this section are checked by the test cases in [tbRegExp-tests.twinproj](tbRegExp-tests.twinproj), a twinBASIC project. It uses tbRegExp as a [linked package](../../../../Features/Packages/Linked#manual-management): put the package's `.twinpack` in `%APPDATA%\twinBASIC\packages`, then run the project, and each case prints whether it passes. From 8987ee17bbbe3a306e42220d6307510425a7ed21 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 14 Oct 2026 11:37:28 -0600 Subject: [PATCH 2/5] updated with correct line-endings --- .../Contributed/tbRegExp-docs-updated.zip | Bin 0 -> 37621 bytes docs/Reference/Contributed/tbRegExp/index.md | 152 ++++++++---------- 2 files changed, 68 insertions(+), 84 deletions(-) create mode 100644 docs/Reference/Contributed/tbRegExp-docs-updated.zip diff --git a/docs/Reference/Contributed/tbRegExp-docs-updated.zip b/docs/Reference/Contributed/tbRegExp-docs-updated.zip new file mode 100644 index 0000000000000000000000000000000000000000..4f0dc5b69141e7c53965e381be863de698a782ad GIT binary patch literal 37621 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%0DoT4I=V+39tl z%_TF)OiJo3w)%rR@}Psj{ib*C_9a!&Q1YW2v7i8le2u_VYZJPZ8ouJ!R4! zdVx9`jHqdSa@pnA^lc>P1sAy&Y#f}h@#5?c^A>3iB9Kk6i0u?o)xsMcfX469=ap12 zYWlqkrJ~y-jMpK2u=nS?B-MHfBdk8F-uape&Z z$*;E4%R6N9omamJwb4=Qll*F@dMlTE5w0}K z7F-S_-)n_SNZl4snDMR!rnS#|*?3X}qSl54iy{YId!+^(MZ!5L1-@ee8I%Mip6cmB zn^MR7CitHDjTq6bV zr=7jj`}J4CjKjOEn9f&bmTfnJA^hVgEafStZN@jQys<<3G({Pv5%lAk`8gHR@s=XxpXM3m5p zo@(Xn?z?OOt(@@rt|^!6=U_tQorHRv)KJs)nZC;DB!_joakSSg`Fve{%IhXVJJ90lfwkTOdcf7}&lrC95B@#FkJbFOG4{_48vya1KV$e=SN?x3#J`@H9}5AP z?ccr-e_7&9fS2<(j{kJx{riFc0R6QO@6Q9L1iVB37wCU=<^5+2zxF!)nSmVe4Ez_v z?~D9@v88{;@N2ippBW%9{=o2i@5z4#{%beDpTT>v{s8_{Z@_@iR>c)&HRRr-#MAh<^B^{4uPKaAJE2K!@;e^m(ld9Wyae?9lszzOZ<=cec%kYEY*WAdT86wR7$nbln#Vhi~kRH z+^_j5KQj#2{*mG5Y?Z%XfghjX*YNSr_+d_e#Q$f9=...)`: [**SubMatches.Item**](SubMatches/Item) returns one by its name, and [**Replace**](RegExp/Replace) inserts one as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores; a pattern with any other name is not valid; -* lookbehind, `(?<=...)` and `(?...)`: [**SubMatches.Item**](SubMatches/Item) returns one by its name, and [**Replace**](RegExp/Replace) inserts one as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores; a pattern with any other name is not valid; +- lookbehind, `(?<=...)` and `(?\\d{4})-(?\\d{2})" +re.Pattern = "(?\d{4})-(?\d{2})" Debug.Print re.Execute("2026-10")(0).SubMatches("month") ' 10 Debug.Print re.Replace("2026-10", "$/$") ' 10/2026 -re.Pattern = "(?<=\\$)\\d+" +re.Pattern = "(?<=\$)\d+" Debug.Print re.Execute("costs $123")(0).Value ' 123 re.Pattern = "(?i:abc)DEF" @@ -45,52 +41,47 @@ Debug.Print re.Test("ABCDEF") ' True Debug.Print re.Test("ABCdef") ' False ``` - - -> \[!NOTE] 1.9.0.0 -> A pattern that holds an atomic group `(?>...)` or a conditional is not valid: \[\*\*IsValid\*\*](RegExp/IsValid) returns \*\*False\*\*. A named backreference `\\k`, `\\A`, `\\Z` and `\\p{...}` are not supported either, but they do not make the pattern invalid, because a backslash before a letter that names no escape stands for the letter itself, as in VBScript: `\\k` matches the text `k`, and `\\A` matches `A`. So `\\p{L}` matches the text `p{L}`: the `\\p` is the letter, and `{L}` starts no quantifier and is text as well (see below). + +> [!NOTE] 1.9.0.0 +> A pattern that holds an atomic group `(?>...)` or a conditional is not valid: [**IsValid**](RegExp/IsValid) returns **False**. A named backreference `\k`, `\A`, `\Z` and `\p{...}` are not supported either, but they do not make the pattern invalid, because a backslash before a letter that names no escape stands for the letter itself, as in VBScript: `\k` matches the text `k`, and `\A` matches `A`. So `\p{L}` matches the text `p{L}`: the `\p` is the letter, and `{L}` starts no quantifier and is text as well (see below). The escapes that name a control character or a character code are read as in VBScript: -* `\\c` followed by a letter, in either case, is a control character: `\\cJ` and `\\CJ` are the line feed. A `\\c` followed by anything else makes the pattern invalid; -* `\\0` is the character with code 0, `\\0` followed by octal digits, such as `\\012`, is that code, and `\\8` and `\\9` are the digits themselves; -* `\\1` to `\\9` and longer numbers are backreferences when the group they name has already been opened, that is, when its `(` stands earlier in the pattern. Otherwise the digits are read as an octal character code, so `\\101` is `A` and `(a)\\2` is `a` followed by the character with code 2; [**IsValid**](RegExp/IsValid) returns **True** for both; -* `\\f`, `\\n`, `\\r` and `\\t`, inside a character class as well, are the form feed, line feed, carriage return and tab. +- `\c` followed by a letter, in either case, is a control character: `\cJ` and `\CJ` are the line feed. A `\c` followed by anything else makes the pattern invalid; +- `\0` is the character with code 0, `\0` followed by octal digits, such as `\012`, is that code, and `\8` and `\9` are the digits themselves; +- `\1` to `\9` and longer numbers are backreferences when the group they name has already been opened, that is, when its `(` stands earlier in the pattern. Otherwise the digits are read as an octal character code, so `\101` is `A` and `(a)\2` is `a` followed by the character with code 2; [**IsValid**](RegExp/IsValid) returns **True** for both; +- `\f`, `\n`, `\r` and `\t`, inside a character class as well, are the form feed, line feed, carriage return and tab. -```tb check\_build check\_run +```tb check_build check_run Dim re As New RegExp -re.Pattern = "\\101" +re.Pattern = "\101" Debug.Print re.Test("A") ' True -re.Pattern = "\\CJ" +re.Pattern = "\CJ" Debug.Print re.Test(vbLf) ' True -re.Pattern = "\\k" +re.Pattern = "\k" Debug.Print re.IsValid ' True Debug.Print re.Test("k") ' True -re.Pattern = "(a)\\2" -Debug.Print re.Test("a" \& ChrW(2)) ' True +re.Pattern = "(a)\2" +Debug.Print re.Test("a" & ChrW(2)) ' True ``` - - -Digits that name a group declared *later* in the pattern are therefore not a backreference at all. `\\1(a)` is the character with code 1 followed by `a`, and finds no match in the string `a`, as in VBScript's **RegExp**. A group that is open but has not yet taken part, such as the `\\1` in `(\\1a)`, is a real backreference and matches no characters. - + +Digits that name a group declared *later* in the pattern are therefore not a backreference at all. `\1(a)` is the character with code 1 followed by `a`, and finds no match in the string `a`, as in VBScript's **RegExp**. A group that is open but has not yet taken part, such as the `\1` in `(\1a)`, is a real backreference and matches no characters. -`\\w` matches only the ASCII letters and digits and `\_`, as in VBScript, so `\\w+` finds `caf` in `café`. - -A `{` starts a quantifier when it is followed by `{n}`, `{n,}` or `{n,m}`. A `{` that starts none, such as the one in `a{`, `a{x}` or `a{,2}`, is an ordinary character, as in VBScript; `\\{` for the character is accepted too but not needed. A `]` or `}` that closes nothing is an ordinary character as well. +`\w` matches only the ASCII letters and digits and `_`, as in VBScript, so `\w+` finds `caf` in `café`. - +A `{` starts a quantifier when it is followed by `{n}`, `{n,}` or `{n,m}`. A `{` that starts none, such as the one in `a{`, `a{x}` or `a{,2}`, is an ordinary character, as in VBScript; `\{` for the character is accepted too but not needed. A `]` or `}` that closes nothing is an ordinary character as well. + `{,m}` is text and not a quantifier, because a quantifier needs a count before the comma: `a{,2}` finds the text `a{,2}` and not up to two `a`. A `{` that does start a quantifier with counts that cannot be used is still an error, though: `a{2,1}`, whose upper count is below its lower one, makes the pattern invalid. - -```tb check\_build check\_run +```tb check_build check_run Dim re As New RegExp -re.Pattern = "(?<\_part2>\\d)" +re.Pattern = "(?<_part2>\d)" Debug.Print re.IsValid ' True -re.Pattern = "(?<2part>\\d)" +re.Pattern = "(?<2part>\d)" Debug.Print re.IsValid ' False re.Pattern = "a{x}" @@ -102,13 +93,11 @@ Debug.Print re.IsValid ' False ## Searches that give up - - -A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, does not hang a search as it does in VBScript's **RegExp**. Every search is given a budget of steps, and when it runs out the search raises an error. Nothing is returned, not even the part that was found: a search that ran out of steps never established whether the rest of the string matches, so a short result would be a wrong answer rather than an incomplete one. The message says how much budget was spent and what to try. A long match is no problem: `a\*` and `^.\*$` match a string of 1,000,000 `a` whole. - + +A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, does not hang a search as it does in VBScript's **RegExp**. Every search is given a budget of steps, and when it runs out the search raises an error. Nothing is returned, not even the part that was found: a search that ran out of steps never established whether the rest of the string matches, so a short result would be a wrong answer rather than an incomplete one. The message says how much budget was spent and what to try. A long match is no problem: `a*` and `^.*$` match a string of 1,000,000 `a` whole. -```tb check\_build check\_run +```tb check_build check_run Dim re As New RegExp re.Pattern = "(a+)+b" On Error Resume Next @@ -117,48 +106,43 @@ Debug.Print Err.Number - vbObjectError ' 3016 On Error GoTo 0 Err.Clear -re.Pattern = "^.\*$" +re.Pattern = "^.*$" Debug.Print re.Test(String(100000, "a")) ' True ``` -The budget grows with the square of the length of the string, so an ordinary pattern that backtracks -- `\\d+x` on a string of digits with no `x`, say -- finishes at any length a search is likely to be given. Only a pattern whose cost grows faster than that is stopped. [**SearchStepLimit**](RegExp/SearchStepLimit) raises the ceiling when a search genuinely needs more. +The budget grows with the square of the length of the string, so an ordinary pattern that backtracks -- `\d+x` on a string of digits with no `x`, say -- finishes at any length a search is likely to be given. Only a pattern whose cost grows faster than that is stopped. [**SearchStepLimit**](RegExp/SearchStepLimit) raises the ceiling when a search genuinely needs more. ## Classes -* [Match](Match/) -- holds one match that a RegExp found in a string: the matched text, where it starts, how many characters it has, and the text captured by its groups - - * [FirstIndex](Match/FirstIndex) -- returns the zero-based position where the match starts - * [Length](Match/Length) -- returns the number of characters in the match - * [SubMatches](Match/SubMatches) -- returns the text captured by the groups of the pattern - * [Value](Match/Value) -- returns the matched text (default member) -* [MatchCollection](MatchCollection/) -- holds the Match objects that a regular expression finds in a string - - * [Count](MatchCollection/Count) -- returns the number of matches - * [Item](MatchCollection/Item) -- returns one match by position (the default member) - * [NewEnum](MatchCollection/NewEnum) -- returns the enumerator that For Each uses (hidden) -* [RegExp](RegExp/) -- searches strings with a regular expression: finds the matches, tests for one, replaces them and splits a string at them - - * [DotAll](RegExp/DotAll) -- returns or sets whether `.` matches a line feed - * [Execute](RegExp/Execute) -- returns the matches in a string - * [Flags](RegExp/Flags) -- returns or sets the four options as a string of letters - * [Global](RegExp/Global) -- returns or sets whether all matches are used - * [IgnoreCase](RegExp/IgnoreCase) -- returns or sets whether letter case is ignored - * [IsValid](RegExp/IsValid) -- returns whether the pattern compiles - * [LastError](RegExp/LastError) -- returns the message of the compile error - * [LastErrorNumber](RegExp/LastErrorNumber) -- returns the number of the compile error - * [MultiLine](RegExp/MultiLine) -- returns or sets whether `^` and `$` match at line boundaries - * [Pattern](RegExp/Pattern) -- returns or sets the regular expression - * [Replace](RegExp/Replace) -- replaces the matches in a string - * [SearchStepLimit](RegExp/SearchStepLimit) -- returns or sets the step budget that stops a runaway search - * [Split](RegExp/Split) -- splits a string at the matches - * [Test](RegExp/Test) -- returns whether a string contains a match -* [SubMatches](SubMatches/) -- holds the strings that the capture groups of a pattern captured in one Match - - * [Count](SubMatches/Count) -- returns the number of strings - * [Item](SubMatches/Item) -- returns one string by position or by group name (the default member) - * [NewEnum](SubMatches/NewEnum) -- returns the enumerator that For Each uses (hidden) +- [Match](Match/) -- holds one match that a RegExp found in a string: the matched text, where it starts, how many characters it has, and the text captured by its groups + - [FirstIndex](Match/FirstIndex) -- returns the zero-based position where the match starts + - [Length](Match/Length) -- returns the number of characters in the match + - [SubMatches](Match/SubMatches) -- returns the text captured by the groups of the pattern + - [Value](Match/Value) -- returns the matched text (default member) +- [MatchCollection](MatchCollection/) -- holds the Match objects that a regular expression finds in a string + - [Count](MatchCollection/Count) -- returns the number of matches + - [Item](MatchCollection/Item) -- returns one match by position (the default member) + - [NewEnum](MatchCollection/NewEnum) -- returns the enumerator that For Each uses (hidden) +- [RegExp](RegExp/) -- searches strings with a regular expression: finds the matches, tests for one, replaces them and splits a string at them + - [DotAll](RegExp/DotAll) -- returns or sets whether `.` matches a line feed + - [Execute](RegExp/Execute) -- returns the matches in a string + - [Flags](RegExp/Flags) -- returns or sets the four options as a string of letters + - [Global](RegExp/Global) -- returns or sets whether all matches are used + - [IgnoreCase](RegExp/IgnoreCase) -- returns or sets whether letter case is ignored + - [IsValid](RegExp/IsValid) -- returns whether the pattern compiles + - [LastError](RegExp/LastError) -- returns the message of the compile error + - [LastErrorNumber](RegExp/LastErrorNumber) -- returns the number of the compile error + - [MultiLine](RegExp/MultiLine) -- returns or sets whether `^` and `$` match at line boundaries + - [Pattern](RegExp/Pattern) -- returns or sets the regular expression + - [Replace](RegExp/Replace) -- replaces the matches in a string + - [SearchStepLimit](RegExp/SearchStepLimit) -- returns or sets the step budget that stops a runaway search + - [Split](RegExp/Split) -- splits a string at the matches + - [Test](RegExp/Test) -- returns whether a string contains a match +- [SubMatches](SubMatches/) -- holds the strings that the capture groups of a pattern captured in one Match + - [Count](SubMatches/Count) -- returns the number of strings + - [Item](SubMatches/Item) -- returns one string by position or by group name (the default member) + - [NewEnum](SubMatches/NewEnum) -- returns the enumerator that For Each uses (hidden) ## Test cases -The notes and warnings of this section are checked by the test cases in [tbRegExp-tests.twinproj](tbRegExp-tests.twinproj), a twinBASIC project. It uses tbRegExp as a [linked package](../../../../Features/Packages/Linked#manual-management): put the package's `.twinpack` in `%APPDATA%\\twinBASIC\\packages`, then run the project, and each case prints whether it passes. - +The notes and warnings of this section are checked by the test cases in [tbRegExp-tests.twinproj](tbRegExp-tests.twinproj), a twinBASIC project. It uses tbRegExp as a [linked package](../../../../Features/Packages/Linked#manual-management): put the package's `.twinpack` in `%APPDATA%\twinBASIC\packages`, then run the project, and each case prints whether it passes. From 86b7534a406dd16d548ae7b0ce5c9f9c1746a817 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 14 Oct 2026 12:16:00 -0600 Subject: [PATCH 3/5] updated by contributor --- .../Contributed/tbRegExp-docs-updated.zip | Bin 37621 -> 0 bytes .../Contributed/tbRegExp/PatternSyntax.md | 98 ++++++++++++ .../tbRegExp/RegExp/LastErrorNumber.md | 4 + .../Contributed/tbRegExp/RegExp/Pattern.md | 2 +- .../tbRegExp/RegExp/SearchStepLimit.md | 10 ++ docs/Reference/Contributed/tbRegExp/index.md | 77 ++-------- .../tbRegExpold/Match/FirstIndex.md | 46 ------ .../Contributed/tbRegExpold/Match/Length.md | 40 ----- .../tbRegExpold/Match/SubMatches.md | 48 ------ .../Contributed/tbRegExpold/Match/Value.md | 48 ------ .../Contributed/tbRegExpold/Match/index.md | 38 ----- .../tbRegExpold/MatchCollection/Count.md | 42 ----- .../tbRegExpold/MatchCollection/Item.md | 66 -------- .../tbRegExpold/MatchCollection/NewEnum.md | 51 ------- .../tbRegExpold/MatchCollection/index.md | 51 ------- .../Contributed/tbRegExpold/RegExp/DotAll.md | 57 ------- .../Contributed/tbRegExpold/RegExp/Execute.md | 82 ---------- .../Contributed/tbRegExpold/RegExp/Flags.md | 72 --------- .../Contributed/tbRegExpold/RegExp/Global.md | 60 -------- .../tbRegExpold/RegExp/IgnoreCase.md | 56 ------- .../Contributed/tbRegExpold/RegExp/IsValid.md | 53 ------- .../tbRegExpold/RegExp/LastError.md | 53 ------- .../tbRegExpold/RegExp/LastErrorNumber.md | 65 -------- .../tbRegExpold/RegExp/MultiLine.md | 60 -------- .../Contributed/tbRegExpold/RegExp/Pattern.md | 56 ------- .../Contributed/tbRegExpold/RegExp/Replace.md | 91 ----------- .../tbRegExpold/RegExp/SearchAborted.md | 79 ---------- .../Contributed/tbRegExpold/RegExp/Split.md | 99 ------------ .../Contributed/tbRegExpold/RegExp/Test.md | 58 ------- .../Contributed/tbRegExpold/RegExp/index.md | 77 ---------- .../tbRegExpold/SubMatches/Count.md | 50 ------ .../tbRegExpold/SubMatches/Item.md | 85 ----------- .../tbRegExpold/SubMatches/NewEnum.md | 55 ------- .../tbRegExpold/SubMatches/index.md | 48 ------ .../Contributed/tbRegExpold/index.md | 143 ------------------ 35 files changed, 127 insertions(+), 1893 deletions(-) delete mode 100644 docs/Reference/Contributed/tbRegExp-docs-updated.zip create mode 100644 docs/Reference/Contributed/tbRegExp/PatternSyntax.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/Match/FirstIndex.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/Match/Length.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/Match/SubMatches.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/Match/Value.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/Match/index.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/MatchCollection/Count.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/MatchCollection/Item.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/MatchCollection/NewEnum.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/MatchCollection/index.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/DotAll.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Execute.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Flags.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Global.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/IgnoreCase.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/IsValid.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/LastError.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/LastErrorNumber.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/MultiLine.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Pattern.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Replace.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/SearchAborted.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Split.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/Test.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/RegExp/index.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/SubMatches/Count.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/SubMatches/Item.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/SubMatches/NewEnum.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/SubMatches/index.md delete mode 100644 docs/Reference/Contributed/tbRegExpold/index.md diff --git a/docs/Reference/Contributed/tbRegExp-docs-updated.zip b/docs/Reference/Contributed/tbRegExp-docs-updated.zip deleted file mode 100644 index 4f0dc5b69141e7c53965e381be863de698a782ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37621 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%0DoT4I=V+39tl z%_TF)OiJo3w)%rR@}Psj{ib*C_9a!&Q1YW2v7i8le2u_VYZJPZ8ouJ!R4! zdVx9`jHqdSa@pnA^lc>P1sAy&Y#f}h@#5?c^A>3iB9Kk6i0u?o)xsMcfX469=ap12 zYWlqkrJ~y-jMpK2u=nS?B-MHfBdk8F-uape&Z z$*;E4%R6N9omamJwb4=Qll*F@dMlTE5w0}K z7F-S_-)n_SNZl4snDMR!rnS#|*?3X}qSl54iy{YId!+^(MZ!5L1-@ee8I%Mip6cmB zn^MR7CitHDjTq6bV zr=7jj`}J4CjKjOEn9f&bmTfnJA^hVgEafStZN@jQys<<3G({Pv5%lAk`8gHR@s=XxpXM3m5p zo@(Xn?z?OOt(@@rt|^!6=U_tQorHRv)KJs)nZC;DB!_joakSSg`Fve{%IhXVJJ90lfwkTOdcf7}&lrC95B@#FkJbFOG4{_48vya1KV$e=SN?x3#J`@H9}5AP z?ccr-e_7&9fS2<(j{kJx{riFc0R6QO@6Q9L1iVB37wCU=<^5+2zxF!)nSmVe4Ez_v z?~D9@v88{;@N2ippBW%9{=o2i@5z4#{%beDpTT>v{s8_{Z@_@iR>c)&HRRr-#MAh<^B^{4uPKaAJE2K!@;e^m(ld9Wyae?9lszzOZ<=cec%kYEY*WAdT86wR7$nbln#Vhi~kRH z+^_j5KQj#2{*mG5Y?Z%XfghjX*YNSr_+d_e#Q$f9=...)`: [**SubMatches.Item**](SubMatches/Item) returns one by its name, and [**Replace**](RegExp/Replace) inserts one as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores; a pattern with any other name is not valid; +- lookbehind, `(?<=...)` and `(?\d{4})-(?\d{2})" +Debug.Print re.Execute("2026-10")(0).SubMatches("month") ' 10 +Debug.Print re.Replace("2026-10", "$/$") ' 10/2026 + +re.Pattern = "(?<=\$)\d+" +Debug.Print re.Execute("costs $123")(0).Value ' 123 + +re.Pattern = "(?i:abc)DEF" +Debug.Print re.Test("ABCDEF") ' True +Debug.Print re.Test("ABCdef") ' False +``` + +## Escapes + +The escapes that name a control character or a character code are read as in VBScript: + +- `\c` followed by a letter, in either case, is a control character: `\cJ` and `\CJ` are the line feed. A `\c` followed by anything else makes the pattern invalid; +- `\0` is the character with code 0, `\0` followed by octal digits, such as `\012`, is that code, and `\8` and `\9` are the digits themselves; +- `\1` to `\9` and longer numbers are backreferences when the group they name has already been opened, that is, when its `(` stands earlier in the pattern. Otherwise the digits are read as an octal character code, so `\101` is `A` and `(a)\2` is `a` followed by the character with code 2; [**IsValid**](RegExp/IsValid) returns **True** for both; +- `\f`, `\n`, `\r` and `\t`, inside a character class as well, are the form feed, line feed, carriage return and tab. + + +A backslash before a letter that names no escape stands for the letter itself, as in VBScript. The constructs that other engines spell that way are therefore ordinary text here, not errors: `\k` matches `k`, `\A` matches `A`, and `\p{L}` matches `p{L}`, the braces being text as well. + + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "\101" +Debug.Print re.Test("A") ' True +re.Pattern = "\CJ" +Debug.Print re.Test(vbLf) ' True +re.Pattern = "\k" +Debug.Print re.IsValid ' True +Debug.Print re.Test("k") ' True +re.Pattern = "(a)\2" +Debug.Print re.Test("a" & ChrW(2)) ' True +``` + + +Digits that name a group declared *later* in the pattern are therefore not a backreference at all. `\1(a)` is the character with code 1 followed by `a`, and finds no match in the string `a`, as in VBScript's **RegExp**. A group that is open but has not yet taken part, such as the `\1` in `(\1a)`, is a real backreference and matches no characters. + + +## Character classes + +`\w` matches only the ASCII letters and digits and `_`, as in VBScript, so `\w+` finds `caf` in `café`. + +## Quantifiers and braces + +A `{` starts a quantifier when it is followed by `{n}`, `{n,}` or `{n,m}`. A `{` that starts none, such as the one in `a{`, `a{x}` or `a{,2}`, is an ordinary character, as in VBScript; `\{` for the character is accepted too but not needed. A `]` or `}` that closes nothing is an ordinary character as well. + + +`{,m}` is text and not a quantifier, because a quantifier needs a count before the comma: `a{,2}` finds the text `a{,2}` and not up to two `a`. A `{` that does start a quantifier with counts that cannot be used is still an error, though: `a{2,1}`, whose upper count is below its lower one, makes the pattern invalid. + + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "(?<_part2>\d)" +Debug.Print re.IsValid ' True +re.Pattern = "(?<2part>\d)" +Debug.Print re.IsValid ' False + +re.Pattern = "a{x}" +Debug.Print re.IsValid ' True +Debug.Print re.Execute("a{x}")(0).Value ' a{x} +re.Pattern = "a{2,1}" +Debug.Print re.IsValid ' False +``` + +## See Also + +- [Pattern](RegExp/Pattern) property -- the regular expression +- [IsValid](RegExp/IsValid) property -- whether a pattern compiles +- [LastErrorNumber](RegExp/LastErrorNumber) property -- the faults a pattern can have, with an example of each +- [tbRegExp Package](./) -- overview diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md b/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md index d0857363..c07e97df 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md @@ -39,6 +39,10 @@ The engine raises its pattern errors with numbers of the form `vbObjectError + 3 | 13 | A group name with a character that a name cannot hold | `(?x)` | | 14 | An inline option that is not `i`, `m` or `s`, which includes an atomic group and a conditional | `(?x)`, `(?>a)` | + +An atomic group `(?>...)` and a conditional `(?(`*n*`)`*yes*`|`*no*`)` fall under *n* = 14. VBScript's **RegExp** does not accept either construct, so a pattern that uses one is invalid in both. + + [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise an error with this number when the pattern is invalid. Those four methods can also raise `vbObjectError + 3016`, which is not a compile error and so never appears here: it means a valid pattern ran out of its step budget before the search finished. See [**SearchStepLimit**](SearchStepLimit). diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/Pattern.md b/docs/Reference/Contributed/tbRegExp/RegExp/Pattern.md index b14a1210..a27927f1 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/Pattern.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/Pattern.md @@ -34,7 +34,7 @@ Syntax: *object*.**Pattern** **=** *value* Setting a different pattern makes it compile again the next time it is needed. Setting the current pattern does nothing. A pattern that does not compile raises no error when it is set: [**IsValid**](IsValid), [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) report the failure, and [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise it. -A named group is written `(?...)`. Its text is read from the [**SubMatches**](../SubMatches/) of a [**Match**](../Match/) by name, and used in [**Replace**](Replace) as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores. The [overview of the package](../#pattern-syntax) lists the other differences from VBScript's pattern syntax. +A named group is written `(?...)`. Its text is read from the [**SubMatches**](../SubMatches/) of a [**Match**](../Match/) by name, and used in [**Replace**](Replace) as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores. [**Pattern syntax**](../PatternSyntax) covers the rest of the pattern syntax. ## Example diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/SearchStepLimit.md b/docs/Reference/Contributed/tbRegExp/RegExp/SearchStepLimit.md index b119b33c..6a8a0d67 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/SearchStepLimit.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/SearchStepLimit.md @@ -34,6 +34,16 @@ Set the property when a pattern of that kind is given a string long enough to re The value that **SearchStepLimit** returns is the one that was set, not the budget a search would be given: with the property at 0 there is no single number to return, because the budget depends on the string. Setting it does not compile the pattern again. +## The error + +The description of the error says what was spent and what to do about it. The search of a thousand digits in the second example below, given 100,000 steps when it needs about 2,250,000, reports: + +> The regular expression search was abandoned after 100,000 steps on an input of 1,000 characters, so whether it matches is not known. This pattern backtracks heavily at this length. A pattern like this typically needs about 3,000,000 steps at this length - roughly 30 times the work already done. Set **SearchStepLimit** to 3000000 to allow it. If that still fails, the pattern's cost grows faster than the square of the input and no limit will help. + +A pattern that is worse than quadratic is told the opposite, because for it a larger budget is not the answer. `(a+)+b` on thirty `a` is given the floor of 1,000,000 steps, far more than the 2,700 a quadratic pattern of that length would need, so the message does not name this property at all: + +> The regular expression search was abandoned after 1,000,000 steps on an input of 30 characters, so whether it matches is not known. This pattern backtracks heavily at this length. It was already allowed more than a quadratic pattern needs here, so its cost grows faster than the square of the input and no step limit will cover it. Simplify the pattern. + ## Example This example searches a string of thirty `a` for `(a+)+b`, which cannot match and takes a very long time to prove it. The search gives up after about a third of a second and raises. The same pattern against a string it can match finishes. diff --git a/docs/Reference/Contributed/tbRegExp/index.md b/docs/Reference/Contributed/tbRegExp/index.md index 056d2131..7149faeb 100644 --- a/docs/Reference/Contributed/tbRegExp/index.md +++ b/docs/Reference/Contributed/tbRegExp/index.md @@ -17,84 +17,35 @@ The **tbRegExp** contributed package provides regular expressions for twinBASIC The matching is done by the [vba-regex](https://github.com/sihlfall/vba-regex) engine of sihlfall, which the package includes as a private module, **StaticRegex**. +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "\d+" +re.Global = True +Debug.Print re.Test("a1b22c333") ' True +Debug.Print re.Execute("a1b22c333").Count ' 3 +Debug.Print re.Execute("a1b22c333")(1).Value ' 22 +Debug.Print re.Replace("a1b22c333", "#") ' a#b#c# +``` + Beyond VBScript's object, **RegExp** has the [**DotAll**](RegExp/DotAll) and [**Flags**](RegExp/Flags) options, the [**Split**](RegExp/Split) method, [**IsValid**](RegExp/IsValid), [**LastError**](RegExp/LastError) and [**LastErrorNumber**](RegExp/LastErrorNumber), which report an invalid pattern before it is used, and [**SearchStepLimit**](RegExp/SearchStepLimit), which bounds a runaway search. [**Replace**](RegExp/Replace) reads a `$` in the replacement string as VBScript does, and adds `$` for a named group. ## Pattern syntax -A pattern is written as for VBScript's **RegExp**. **tbRegExp** also accepts: - -- named groups, `(?...)`: [**SubMatches.Item**](SubMatches/Item) returns one by its name, and [**Replace**](RegExp/Replace) inserts one as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores; a pattern with any other name is not valid; -- lookbehind, `(?<=...)` and `(?\d{4})-(?\d{2})" Debug.Print re.Execute("2026-10")(0).SubMatches("month") ' 10 Debug.Print re.Replace("2026-10", "$/$") ' 10/2026 - -re.Pattern = "(?<=\$)\d+" -Debug.Print re.Execute("costs $123")(0).Value ' 123 - -re.Pattern = "(?i:abc)DEF" -Debug.Print re.Test("ABCDEF") ' True -Debug.Print re.Test("ABCdef") ' False ``` - -> [!NOTE] 1.9.0.0 -> A pattern that holds an atomic group `(?>...)` or a conditional is not valid: [**IsValid**](RegExp/IsValid) returns **False**. A named backreference `\k`, `\A`, `\Z` and `\p{...}` are not supported either, but they do not make the pattern invalid, because a backslash before a letter that names no escape stands for the letter itself, as in VBScript: `\k` matches the text `k`, and `\A` matches `A`. So `\p{L}` matches the text `p{L}`: the `\p` is the letter, and `{L}` starts no quantifier and is text as well (see below). - -The escapes that name a control character or a character code are read as in VBScript: - -- `\c` followed by a letter, in either case, is a control character: `\cJ` and `\CJ` are the line feed. A `\c` followed by anything else makes the pattern invalid; -- `\0` is the character with code 0, `\0` followed by octal digits, such as `\012`, is that code, and `\8` and `\9` are the digits themselves; -- `\1` to `\9` and longer numbers are backreferences when the group they name has already been opened, that is, when its `(` stands earlier in the pattern. Otherwise the digits are read as an octal character code, so `\101` is `A` and `(a)\2` is `a` followed by the character with code 2; [**IsValid**](RegExp/IsValid) returns **True** for both; -- `\f`, `\n`, `\r` and `\t`, inside a character class as well, are the form feed, line feed, carriage return and tab. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "\101" -Debug.Print re.Test("A") ' True -re.Pattern = "\CJ" -Debug.Print re.Test(vbLf) ' True -re.Pattern = "\k" -Debug.Print re.IsValid ' True -Debug.Print re.Test("k") ' True -re.Pattern = "(a)\2" -Debug.Print re.Test("a" & ChrW(2)) ' True -``` - - -Digits that name a group declared *later* in the pattern are therefore not a backreference at all. `\1(a)` is the character with code 1 followed by `a`, and finds no match in the string `a`, as in VBScript's **RegExp**. A group that is open but has not yet taken part, such as the `\1` in `(\1a)`, is a real backreference and matches no characters. - - -`\w` matches only the ASCII letters and digits and `_`, as in VBScript, so `\w+` finds `caf` in `café`. - -A `{` starts a quantifier when it is followed by `{n}`, `{n,}` or `{n,m}`. A `{` that starts none, such as the one in `a{`, `a{x}` or `a{,2}`, is an ordinary character, as in VBScript; `\{` for the character is accepted too but not needed. A `]` or `}` that closes nothing is an ordinary character as well. - - -`{,m}` is text and not a quantifier, because a quantifier needs a count before the comma: `a{,2}` finds the text `a{,2}` and not up to two `a`. A `{` that does start a quantifier with counts that cannot be used is still an error, though: `a{2,1}`, whose upper count is below its lower one, makes the pattern invalid. - - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(?<_part2>\d)" -Debug.Print re.IsValid ' True -re.Pattern = "(?<2part>\d)" -Debug.Print re.IsValid ' False - -re.Pattern = "a{x}" -Debug.Print re.IsValid ' True -Debug.Print re.Execute("a{x}")(0).Value ' a{x} -re.Pattern = "a{2,1}" -Debug.Print re.IsValid ' False -``` +[**Pattern syntax**](PatternSyntax) covers those in full, along with the escapes, the character classes and the brace rules. ## Searches that give up -A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, does not hang a search as it does in VBScript's **RegExp**. Every search is given a budget of steps, and when it runs out the search raises an error. Nothing is returned, not even the part that was found: a search that ran out of steps never established whether the rest of the string matches, so a short result would be a wrong answer rather than an incomplete one. The message says how much budget was spent and what to try. A long match is no problem: `a*` and `^.*$` match a string of 1,000,000 `a` whole. +A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, does not hang a search as it does in VBScript's **RegExp**. Every search is given a budget of steps, and when it runs out the search raises an error rather than returning a result it never established. A long match is no problem: `a*` and `^.*$` match a string of 1,000,000 `a` whole. ```tb check_build check_run @@ -110,7 +61,7 @@ re.Pattern = "^.*$" Debug.Print re.Test(String(100000, "a")) ' True ``` -The budget grows with the square of the length of the string, so an ordinary pattern that backtracks -- `\d+x` on a string of digits with no `x`, say -- finishes at any length a search is likely to be given. Only a pattern whose cost grows faster than that is stopped. [**SearchStepLimit**](RegExp/SearchStepLimit) raises the ceiling when a search genuinely needs more. +[**SearchStepLimit**](RegExp/SearchStepLimit) explains the budget, shows the message, and raises the ceiling when a search genuinely needs more. ## Classes diff --git a/docs/Reference/Contributed/tbRegExpold/Match/FirstIndex.md b/docs/Reference/Contributed/tbRegExpold/Match/FirstIndex.md deleted file mode 100644 index e5e29502..00000000 --- a/docs/Reference/Contributed/tbRegExpold/Match/FirstIndex.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: FirstIndex -parent: Match -permalink: /tB/Packages/Contributed/tbRegExp/Match/FirstIndex -has_toc: false ---- - -# FirstIndex -{: .no_toc } - -Returns the zero-based position in the source string where the match starts. - -Syntax: *object*.**FirstIndex** - -*object* -: *required* An object expression that evaluates to a **Match**. - -Returns a **Long**. The property is read-only. - -## Remarks - -The first character of the source string is at position 0, so the property counts the characters before the match. The index is zero-based, as in the VBScript regular expression object model that the package replaces. - -The **Mid$** function counts from 1. To cut the match out of the source string with **Mid$**, pass `FirstIndex + 1` as its start position and [**Length**](Length) as its length. - -## Example - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "b+" - -Dim text As String -text = "aabbbc" - -Dim m As Match -Set m = re.Execute(text).Item(0) -Debug.Print m.FirstIndex ' 2 -Debug.Print Mid$(text, m.FirstIndex + 1, m.Length) ' bbb -``` - -## See Also - -- [Length](Length) property -- the number of characters in the match -- [Value](Value) property -- the matched text -- [Execute](../RegExp/Execute) method -- creates the **Match** objects -- [Match](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/Match/Length.md b/docs/Reference/Contributed/tbRegExpold/Match/Length.md deleted file mode 100644 index 95287fbf..00000000 --- a/docs/Reference/Contributed/tbRegExpold/Match/Length.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: Length -parent: Match -permalink: /tB/Packages/Contributed/tbRegExp/Match/Length -has_toc: false ---- - -# Length -{: .no_toc } - -Returns the number of characters in the match. - -Syntax: *object*.**Length** - -*object* -: *required* An object expression that evaluates to a **Match**. - -Returns a **Long**. The property is read-only. - -## Remarks - -The value is the length of the matched text that [**Value**](Value) returns. Together with [**FirstIndex**](FirstIndex) it gives the range of the source string that the match covers. - -## Example - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "b+" - -Dim m As Match -Set m = re.Execute("aabbbc").Item(0) -Debug.Print m.Length ' 3 -Debug.Print Len(m.Value) ' 3 -``` - -## See Also - -- [FirstIndex](FirstIndex) property -- the position where the match starts -- [Value](Value) property -- the matched text -- [Match](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/Match/SubMatches.md b/docs/Reference/Contributed/tbRegExpold/Match/SubMatches.md deleted file mode 100644 index 09ed373a..00000000 --- a/docs/Reference/Contributed/tbRegExpold/Match/SubMatches.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: SubMatches -parent: Match -permalink: /tB/Packages/Contributed/tbRegExp/Match/SubMatches -has_toc: false ---- - -# SubMatches -{: .no_toc } - -Returns the text captured by the groups of the pattern in this match. - -Syntax: *object*.**SubMatches** - -*object* -: *required* An object expression that evaluates to a **Match**. - -Returns a [**SubMatches**](../SubMatches/) object. The property is read-only. - -## Remarks - -The returned object holds one string for each capture group in the pattern, counted by [**Count**](../SubMatches/Count) and read with [**Item**](../SubMatches/Item) by position or, for a named group, by name. - -When the pattern has no capture groups, the property returns an empty **SubMatches** object with a **Count** of 0. Every match of such a pattern returns the same empty object. - -## Example - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(\d+)-(\d+)" - -Dim m As Match -Set m = re.Execute("pages 10-25").Item(0) -Debug.Print m.Value ' 10-25 -Debug.Print m.SubMatches.Count ' 2 -Debug.Print m.SubMatches.Item(0) ' 10 -Debug.Print m.SubMatches.Item(1) ' 25 - -re.Pattern = "\d+" -Set m = re.Execute("pages 10-25").Item(0) -Debug.Print m.SubMatches.Count ' 0 -``` - -## See Also - -- [Value](Value) property -- the text of the whole match -- [SubMatches](../SubMatches/) class -- the object that the property returns -- [Match](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/Match/Value.md b/docs/Reference/Contributed/tbRegExpold/Match/Value.md deleted file mode 100644 index 2300b203..00000000 --- a/docs/Reference/Contributed/tbRegExpold/Match/Value.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Value -parent: Match -permalink: /tB/Packages/Contributed/tbRegExp/Match/Value -has_toc: false ---- - -# Value -{: .no_toc } - -Returns the text of the whole match. - -Syntax: *object*.**Value** - -*object* -: *required* An object expression that evaluates to a **Match**. - -Returns a **String**. The property is read-only. - -## Remarks - -**Value** is the default member of **Match**, so an expression that needs a **String** can name the object alone. - -The text is the part of the source string that begins at [**FirstIndex**](FirstIndex) and has [**Length**](Length) characters. The text captured by the groups of the pattern is in [**SubMatches**](SubMatches). - -## Example - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "[a-z]+" - -Dim m As Match -Set m = re.Execute("42 apples").Item(0) - -Dim text As String -text = m.Value -Debug.Print text ' apples - -text = m ' the default member is Value -Debug.Print text ' apples -``` - -## See Also - -- [FirstIndex](FirstIndex) property -- the position where the match starts -- [Length](Length) property -- the number of characters in the match -- [SubMatches](SubMatches) property -- the text captured by the groups of the pattern -- [Match](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/Match/index.md b/docs/Reference/Contributed/tbRegExpold/Match/index.md deleted file mode 100644 index b62da770..00000000 --- a/docs/Reference/Contributed/tbRegExpold/Match/index.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Match -parent: tbRegExp Package -permalink: /tB/Packages/Contributed/tbRegExp/Match/ -has_toc: false ---- - -# Match class -{: .no_toc } - -Holds one match that a **RegExp** found in a string: the matched text, where it starts, how many characters it has, and the text captured by the groups of the pattern. - -[**RegExp.Execute**](../RegExp/Execute) creates one **Match** for each match it finds and returns them in a [**MatchCollection**](../MatchCollection/), in the order found. All four properties are read-only, and the values are set once, when **Execute** creates the **Match**. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "[a-z]+" - -Dim m As Match -Set m = re.Execute("42 apples").Item(0) -Debug.Print m.Value ' apples -Debug.Print m.FirstIndex ' 3 -Debug.Print m.Length ' 6 -``` - -## Properties - -- [FirstIndex](FirstIndex) -- returns the zero-based position where the match starts -- [Length](Length) -- returns the number of characters in the match -- [SubMatches](SubMatches) -- returns the text captured by the groups of the pattern -- [Value](Value) -- returns the matched text (default member) - -## See Also - -- [RegExp](../RegExp/) class -- searches a string and creates the **Match** objects -- [MatchCollection](../MatchCollection/) class -- holds the matches that **Execute** finds -- [SubMatches](../SubMatches/) class -- holds the text captured by the groups of one match -- [tbRegExp package](../) -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/MatchCollection/Count.md b/docs/Reference/Contributed/tbRegExpold/MatchCollection/Count.md deleted file mode 100644 index 233e711c..00000000 --- a/docs/Reference/Contributed/tbRegExpold/MatchCollection/Count.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Count -parent: MatchCollection -permalink: /tB/Packages/Contributed/tbRegExp/MatchCollection/Count -has_toc: false ---- - -# Count -{: .no_toc } - -Returns the number of [**Match**](../Match/) objects in the collection. - -Syntax: *object*.**Count** - -*object* -: *required* An object expression that evaluates to a **MatchCollection**. - -Returns a **Long**. The property is read-only. - -## Remarks - -For the collection that the **Execute** method of [**RegExp**](../RegExp/) returns, **Count** is the number of matches found in the source string. It is 0 when the pattern finds nothing. When the **Global** property of the expression is **False**, the collection holds the first match only, so **Count** is at most 1. - -The positions that [**Item**](Item) accepts run from 0 to **Count** - 1. - -## Example - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "[a-z]+" -re.Global = True -Debug.Print re.Execute("one two three").Count ' 3 -Debug.Print re.Execute("1 2 3").Count ' 0 - -re.Global = False -Debug.Print re.Execute("one two three").Count ' 1 -``` - -## See Also - -- [Item](Item) property -- returns a match by position -- [MatchCollection](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/MatchCollection/Item.md b/docs/Reference/Contributed/tbRegExpold/MatchCollection/Item.md deleted file mode 100644 index 19a76e1e..00000000 --- a/docs/Reference/Contributed/tbRegExpold/MatchCollection/Item.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Item -parent: MatchCollection -permalink: /tB/Packages/Contributed/tbRegExp/MatchCollection/Item -has_toc: false ---- - -# Item -{: .no_toc } - -Returns the [**Match**](../Match/) at a position in the collection. - -Syntax: *object*.**Item** ( *pItemIndex* ) - -*object* -: *required* An object expression that evaluates to a **MatchCollection**. - -*pItemIndex* -: *required* A **Variant** holding the zero-based position of the match. The first match is 0 and the last is [**Count**](Count) - 1. - -Returns a [**Match**](../Match/). The property is read-only. - -## Remarks - -**Item** is the default member of the class, so `matches(1)` is the same as `matches.Item(1)`. - -The position is zero-based, as in the VBScript object model that the package replaces. A match has no key, so the argument is always a position. - -A position below 0, or not below [**Count**](Count), raises run-time error `vbObjectError + 9` with the message *Requested item not found in Matches collection*. - -## Example - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "[a-z]+" -re.Global = True - -Dim matches As MatchCollection -Set matches = re.Execute("one two three") -Debug.Print matches.Item(0).Value ' one -Debug.Print matches(2).Value ' three -``` - -This example asks for a position that the collection does not have. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "[a-z]+" -re.Global = True - -Dim matches As MatchCollection -Set matches = re.Execute("one two three") - -Dim m As Match -On Error Resume Next -Set m = matches.Item(3) -Debug.Print (Err.Number = vbObjectError + 9) ' True -Debug.Print Err.Description ' Requested item not found in Matches collection -``` - -## See Also - -- [Count](Count) property -- the number of matches -- [NewEnum](NewEnum) method -- the enumerator for **For Each** -- [Match](../Match/) class -- one match -- [MatchCollection](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/MatchCollection/NewEnum.md b/docs/Reference/Contributed/tbRegExpold/MatchCollection/NewEnum.md deleted file mode 100644 index 62186d6f..00000000 --- a/docs/Reference/Contributed/tbRegExpold/MatchCollection/NewEnum.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: NewEnum -parent: MatchCollection -permalink: /tB/Packages/Contributed/tbRegExp/MatchCollection/NewEnum -has_toc: false ---- - -# NewEnum -{: .no_toc } - -Returns the enumerator that **For Each** uses to go through the [**Match**](../Match/) objects of the collection. - -Syntax: *object*.**NewEnum** **As IUnknown** - -*object* -: *required* An object expression that evaluates to a **MatchCollection**. - -Returns an **IUnknown** that refers to a new enumerator over the matches, which starts at the first one. - -## Remarks - -**NewEnum** is declared with the [**Enumerator**](../../../../Core/Attributes#enumerator) attribute, which makes it the member that [**For Each**](../../../../Core/For-Each-Next) calls to start an enumeration. [Custom Enumerators](../../../../../Features/Language/Custom-Enumerators) describes the rule. It is also declared [**Hidden**](../../../../Core/Attributes#hidden), so it does not appear in certain IntelliSense lists. - -Each element is a **Match**, and the matches come in the order they were found. A collection can be enumerated any number of times, and an enumeration inside another one is independent. The element variable of a **For Each** loop can be declared **As Match**, as in the example. - -A program does not need to call **NewEnum**. **For Each** does it. - -## Example - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "[a-z]+" -re.Global = True - -Dim matches As MatchCollection -Set matches = re.Execute("one two three") - -Dim s As String -Dim m As Match -For Each m In matches - s = s & "[" & m.Value & "]" -Next -Debug.Print s ' [one][two][three] -``` - -## See Also - -- [Item](Item) property -- returns a match by position -- [Count](Count) property -- the number of matches -- [For Each...Next](../../../../Core/For-Each-Next) statement -- uses the enumerator -- [MatchCollection](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/MatchCollection/index.md b/docs/Reference/Contributed/tbRegExpold/MatchCollection/index.md deleted file mode 100644 index a6e5f8ee..00000000 --- a/docs/Reference/Contributed/tbRegExpold/MatchCollection/index.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: MatchCollection -parent: tbRegExp Package -permalink: /tB/Packages/Contributed/tbRegExp/MatchCollection/ -has_toc: false ---- - -# MatchCollection class -{: .no_toc } - -Holds the [**Match**](../Match/) objects that a regular expression finds in a string. - -The **Execute** method of [**RegExp**](../RegExp/) returns a **MatchCollection**. When the **Global** property of the expression is **True**, the collection holds every match. Otherwise it holds the first match only. A string in which the pattern finds nothing gives an empty collection. - -The collection is read by position, and the position is zero-based, as in the VBScript object model that the package replaces. [**Item**](Item) is the default member, so `matches(0)` is the same as `matches.Item(0)`. **For Each** goes through the matches in the order they were found. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "\d+" -re.Global = True - -Dim matches As MatchCollection -Set matches = re.Execute("a1 b22 c333") -Debug.Print matches.Count ' 3 -Debug.Print matches(1).Value ' 22 - -Dim s As String -Dim m As Match -For Each m In matches - s = s & "[" & m.Value & "]" -Next -Debug.Print s ' [1][22][333] -``` - -The class has no member that adds or removes a match: only **Execute** fills a **MatchCollection**. - -## Properties - -- [Count](Count) -- returns the number of matches -- [Item](Item) -- returns one match by position (the default member) - -## Methods - -- [NewEnum](NewEnum) -- returns the enumerator that **For Each** uses (hidden) - -## See Also - -- [Match](../Match/) class -- one match -- [RegExp](../RegExp/) class -- its **Execute** method returns the collection -- [For Each...Next](../../../../Core/For-Each-Next) statement -- goes through the matches -- [tbRegExp package](../) -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/DotAll.md b/docs/Reference/Contributed/tbRegExpold/RegExp/DotAll.md deleted file mode 100644 index b74783e5..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/DotAll.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: DotAll -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/DotAll -has_toc: false ---- - -# DotAll -{: .no_toc } - -Returns or sets whether `.` in the pattern matches a line feed. - -## Get - -Returns a **Boolean**. - -Syntax: *object*.**DotAll** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -The default value is **False**. - -## Let - -Sets the value. - -Syntax: *object*.**DotAll** **=** *value* - -*value* -: A **Boolean**. **True** lets `.` match any character, a line feed included. **False** makes `.` match any character except a line feed. - -## Remarks - -As in VBScript's **RegExp**, only the line feed (`vbLf`) is excluded when the value is **False**. The carriage return (`vbCr`) and the characters U+2028 and U+2029 are matched by `.` whatever the value. [**MultiLine**](MultiLine) treats all four as line terminators for `^` and `$`. - -The value is applied when [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) or [**Split**](Split) makes a match. Changing it does not compile the pattern again. The flag `s` of [**Flags**](Flags) reads and sets the same value. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "a.b" -Debug.Print re.Test("a" & vbLf & "b") ' False -Debug.Print re.Test("a" & vbCr & "b") ' True - -re.DotAll = True -Debug.Print re.Test("a" & vbLf & "b") ' True -``` - -## See Also - -- [Flags](Flags) property -- sets this option with the letter `s` -- [MultiLine](MultiLine) property -- the option for `^` and `$` -- [Pattern](Pattern) property -- the regular expression -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Execute.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Execute.md deleted file mode 100644 index 30cfea8d..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/Execute.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Execute -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Execute -has_toc: false ---- - -# Execute -{: .no_toc } - -Searches a string for the pattern and returns the matches. - -Syntax: *object*.**Execute** ( *sourceString* ) **As MatchCollection** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -*sourceString* -: *required* A **String**, the text to search. - -Returns a [**MatchCollection**](../MatchCollection/) that holds one [**Match**](../Match/) for each match of [**Pattern**](Pattern) in *sourceString*, in the order they occur. The collection is empty when there is no match. - -## Remarks - -With [**Global**](Global) set to **False**, the collection holds at most one match, the first. With **Global** set to **True** it holds every match. - -[**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) change what counts as a match. - -A match can have no characters, as `(?=[A-Z])` has before each capital letter, and each such match is returned once. `fooBarBaz` has two capitals, so the collection holds two matches, at positions 3 and 6. - -```tb check_build check_run -Dim re As New RegExp -re.Global = True -re.Pattern = "(?=[A-Z])" -Debug.Print re.Execute("fooBarBaz").Count ' 2 -Debug.Print re.Execute("fooBarBaz")(1).FirstIndex ' 6 -``` - - -A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging. The collection then holds the matches found before it gave up, and [**SearchAborted**](SearchAborted) returns **True**. - - -Each **Match** gives the matched text ([**Value**](../Match/Value)), the position where it starts ([**FirstIndex**](../Match/FirstIndex), counted from 0), its length, and the text of each group of the pattern ([**SubMatches**](../Match/SubMatches)). A group written `(?...)` can be read by its name. - -An invalid pattern raises the compile error, with the number in [**LastErrorNumber**](LastErrorNumber) and the description in [**LastError**](LastError). Check [**IsValid**](IsValid) first to avoid it. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "(?\d{4})-(?\d{2})" -re.Global = True - -Dim matches As MatchCollection -Set matches = re.Execute("2026-10 and 2027-01") -Debug.Print matches.Count - -Dim m As Match -For Each m In matches - Debug.Print m.Value & " at " & m.FirstIndex & ": " & m.SubMatches.Item("year") & "/" & m.SubMatches.Item("month") -Next - -re.Global = False -Debug.Print re.Execute("2026-10 and 2027-01").Count -Debug.Print re.Execute("no date").Count -' Output: -' 2 -' 2026-10 at 0: 2026/10 -' 2027-01 at 12: 2027/01 -' 1 -' 0 -``` - -## See Also - -- [Test](Test) method -- returns whether a string contains a match -- [Replace](Replace) method -- replaces the matches -- [Split](Split) method -- splits a string at the matches -- [Global](Global) property -- all matches or the first -- [MatchCollection](../MatchCollection/) class -- the result -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Flags.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Flags.md deleted file mode 100644 index 616e9c9b..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/Flags.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Flags -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Flags -has_toc: false ---- - -# Flags -{: .no_toc } - -Returns or sets the four matching options as one string of letters. - -## Get - -Returns a **String** that holds the letter of each option that is **True**, always in the order `g`, `m`, `i`, `s`. It is an empty string when all four are **False**. - -Syntax: *object*.**Flags** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -## Let - -Sets all four options at once. - -Syntax: *object*.**Flags** **=** *value* - -*value* -: A **String** of letters. The letters are not case-sensitive. - -| Letter | Option | -|--------|--------| -| `g` | [**Global**](Global) | -| `m` | [**MultiLine**](MultiLine) | -| `i` | [**IgnoreCase**](IgnoreCase) | -| `s` | [**DotAll**](DotAll) | - -## Remarks - -An option whose letter is missing from *value* is set to **False**, so `vbNullString` turns all four off. Other characters in *value* are ignored, and the order and repetition of the letters do not matter. - -The `i` letter is applied through [**IgnoreCase**](IgnoreCase), so the pattern is compiled again the next time it is needed when that option changes. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Flags = "GI" -Debug.Print re.Global ' True -Debug.Print re.IgnoreCase ' True -Debug.Print re.MultiLine ' False -Debug.Print re.Flags ' gi - -re.DotAll = True -Debug.Print re.Flags ' gis - -re.Flags = "m" -Debug.Print re.Flags ' m -Debug.Print re.Global ' False - -re.Flags = vbNullString -Debug.Print Len(re.Flags) ' 0 -``` - -## See Also - -- [Global](Global) property -- the `g` option -- [MultiLine](MultiLine) property -- the `m` option -- [IgnoreCase](IgnoreCase) property -- the `i` option -- [DotAll](DotAll) property -- the `s` option -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Global.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Global.md deleted file mode 100644 index 4482d911..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/Global.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Global -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Global -has_toc: false ---- - -# Global -{: .no_toc } - -Returns or sets whether all matches are used, or only the first. - -## Get - -Returns a **Boolean**. - -Syntax: *object*.**Global** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -The default value is **False**. - -## Let - -Sets the value. - -Syntax: *object*.**Global** **=** *value* - -*value* -: A **Boolean**. **True** makes [**Execute**](Execute) return every match, [**Replace**](Replace) replace every match, and [**Split**](Split) split at every match. **False** makes each of them use only the first match. - -## Remarks - -[**Test**](Test) does not depend on the value, because it only reports whether a match exists. - -The value is applied when a match is made. Changing it does not compile the pattern again. The flag `g` of [**Flags**](Flags) reads and sets the same value. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "\d+" - -Debug.Print re.Execute("a1b22c333").Count ' 1 -Debug.Print re.Replace("a1b22c333", "#") ' a#b22c333 - -re.Global = True -Debug.Print re.Execute("a1b22c333").Count ' 3 -Debug.Print re.Replace("a1b22c333", "#") ' a#b#c# -``` - -## See Also - -- [Flags](Flags) property -- sets this option with the letter `g` -- [Execute](Execute) method -- returns the matches -- [Replace](Replace) method -- replaces the matches -- [Split](Split) method -- splits a string at the matches -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/IgnoreCase.md b/docs/Reference/Contributed/tbRegExpold/RegExp/IgnoreCase.md deleted file mode 100644 index 9c4cfc47..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/IgnoreCase.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: IgnoreCase -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/IgnoreCase -has_toc: false ---- - -# IgnoreCase -{: .no_toc } - -Returns or sets whether letter case is ignored when the pattern is matched. - -## Get - -Returns a **Boolean**. - -Syntax: *object*.**IgnoreCase** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -The default value is **False**. - -## Let - -Sets the value. - -Syntax: *object*.**IgnoreCase** **=** *value* - -*value* -: A **Boolean**. **True** makes matches case-insensitive. **False** makes them case-sensitive. - -## Remarks - -Unlike [**Global**](Global), [**MultiLine**](MultiLine) and [**DotAll**](DotAll), this option is part of the compiled pattern. Setting it to a different value makes the pattern compile again the next time it is needed, and [**IsValid**](IsValid), [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) then refer to the new compilation. Setting it to its current value does nothing. - -The flag `i` of [**Flags**](Flags) reads and sets the same value. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "hello" -Debug.Print re.Test("Hello World") ' False - -re.IgnoreCase = True -Debug.Print re.Test("Hello World") ' True -``` - -## See Also - -- [Flags](Flags) property -- sets this option with the letter `i` -- [Pattern](Pattern) property -- the regular expression -- [IsValid](IsValid) property -- whether the pattern compiles -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/IsValid.md b/docs/Reference/Contributed/tbRegExpold/RegExp/IsValid.md deleted file mode 100644 index 8929b93a..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/IsValid.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: IsValid -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/IsValid -has_toc: false ---- - -# IsValid -{: .no_toc } - -Returns whether the current pattern compiles. Raises no error. - -Syntax: *object*.**IsValid** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -Returns a **Boolean**: **True** when [**Pattern**](Pattern) compiles, **False** when it does not. - -## Remarks - -The property compiles the pattern if it has not been compiled since [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) last changed, and keeps the result. Nothing is raised when the compilation fails. For a pattern that fails, [**LastError**](LastError) holds the message and [**LastErrorNumber**](LastErrorNumber) the number. - -Check **IsValid** before [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) or [**Split**](Split) when the pattern comes from outside the program, because each of those raises the compile error. - - -> [!NOTE] 1.8.0.0 -> Although nothing is raised, the compile error of an invalid pattern stays in the **Err** object after the call that compiled the pattern returns. A program that tests **Err** for other errors calls **Err.Clear** first. - - -> [!NOTE] 1.8.0.0 -> A quantifier whose lower count is above its upper count, such as `a{2,1}`, is not found when the pattern is compiled, so **IsValid** returns **True** for it and [**LastErrorNumber**](LastErrorNumber) returns 0. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) then raise error 3000 when a search reaches the quantifier, with the description that Windows gives that number, *The specified print monitor is unknown.* - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "(\d+" -Debug.Print re.IsValid ' False -Debug.Print Err.Number = re.LastErrorNumber ' True -Err.Clear - -re.Pattern = "(\d+)" -Debug.Print re.IsValid ' True -``` - -## See Also - -- [LastError](LastError) property -- the message of the compile error -- [LastErrorNumber](LastErrorNumber) property -- the number of the compile error -- [Pattern](Pattern) property -- the regular expression -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/LastError.md b/docs/Reference/Contributed/tbRegExpold/RegExp/LastError.md deleted file mode 100644 index 5935dc1a..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/LastError.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: LastError -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/LastError -has_toc: false ---- - -# LastError -{: .no_toc } - -Returns the message of the error that compiling the current pattern raised. - -Syntax: *object*.**LastError** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -Returns a **String**: the description of the compile error, or an empty string when the pattern is valid. - -## Remarks - - -The property compiles the pattern if it has not been compiled since [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) last changed. The compilation traps its error, so **LastError** raises nothing, but the error stays in the **Err** object (see [**IsValid**](IsValid)). The text is a sentence that names the fault, such as *Unterminated character class: a '[' without a matching ']'.* for the pattern `[abc`. [**LastErrorNumber**](LastErrorNumber) is the number of the error and lists the faults. - - -[**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise an error with this description when the pattern is invalid. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "(\d+)" -Debug.Print Len(re.LastError) ' 0 - -re.Pattern = "(\d+" -Debug.Print re.IsValid ' False -Debug.Print Len(re.LastError) > 0 ' True - -re.Pattern = "[abc" -Debug.Print re.LastError ' Unterminated character class: a '[' without a matching ']'. - -On Error Resume Next -re.Test "123" -Debug.Print Err.Description = re.LastError ' True -``` - -## See Also - -- [LastErrorNumber](LastErrorNumber) property -- the number of the compile error -- [IsValid](IsValid) property -- whether the pattern compiles -- [Pattern](Pattern) property -- the regular expression -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/LastErrorNumber.md b/docs/Reference/Contributed/tbRegExpold/RegExp/LastErrorNumber.md deleted file mode 100644 index 14204816..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/LastErrorNumber.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: LastErrorNumber -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/LastErrorNumber -has_toc: false ---- - -# LastErrorNumber -{: .no_toc } - -Returns the number of the error that compiling the current pattern raised. - -Syntax: *object*.**LastErrorNumber** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -Returns a **Long**: the number of the compile error, or 0 when the pattern is valid. - -## Remarks - -The property compiles the pattern if it has not been compiled since [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) last changed. The compilation traps its error, so **LastErrorNumber** raises nothing. The value is the `Number` of the error that the compilation raised; [**LastError**](LastError) is its description. - - -The engine raises its pattern errors with numbers of the form `vbObjectError + 3000 + n`, where *n* identifies the fault, as in the example. The compilation traps the error, so **LastErrorNumber** raises nothing, but the error stays in the **Err** object (see [**IsValid**](IsValid)). - - -| *n* | Fault | Example | -|-----|-------|---------| -| 1 | A backslash at the end, a `\c` without a letter, or a `\0` followed by `8` or `9` | `\`, `\c1`, `\08` | -| 2 | A `\x` or `\u` escape without its hexadecimal digits | `\u00` | -| 3 | A range in a character class that runs backwards | `[z-a]` | -| 4 | A character class without its `]` | `[abc` | -| 6 | A group construct `(?<` that is neither a lookbehind nor a valid name | `(?<1a>x)` | -| 8 | A `{` that does not start a quantifier, or a count that is too large | `a{2`, `\p{L}` | -| 9 | A group without its `)` | `(\d+` | -| 11 | A `)` that closes no group | `)` | -| 12 | A quantifier with nothing to repeat | `*a` | -| 13 | A group name with a character that a name cannot hold | `(?x)` | -| 14 | An inline option that is not `i`, `m` or `s`, which includes an atomic group and a conditional | `(?x)`, `(?>a)` | - -[**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise an error with this number when the pattern is invalid. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "(\d+)" -Debug.Print re.LastErrorNumber ' 0 - -re.Pattern = "(\d+" -Debug.Print re.LastErrorNumber - vbObjectError ' 3009 - -On Error Resume Next -re.Test "123" -Debug.Print Err.Number = re.LastErrorNumber ' True -``` - -## See Also - -- [LastError](LastError) property -- the message of the compile error -- [IsValid](IsValid) property -- whether the pattern compiles -- [Pattern](Pattern) property -- the regular expression -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/MultiLine.md b/docs/Reference/Contributed/tbRegExpold/RegExp/MultiLine.md deleted file mode 100644 index 4cfe6971..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/MultiLine.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: MultiLine -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/MultiLine -has_toc: false ---- - -# MultiLine -{: .no_toc } - -Returns or sets whether `^` and `$` in the pattern match at the boundaries of each line. - -## Get - -Returns a **Boolean**. - -Syntax: *object*.**MultiLine** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -The default value is **False**. - -## Let - -Sets the value. - -Syntax: *object*.**MultiLine** **=** *value* - -*value* -: A **Boolean**. **True** lets `^` match at the start of the string and after a line terminator, and `$` match at the end of the string and before a line terminator. **False** lets `^` match only at the start of the whole string and `$` only at its end. - -## Remarks - -The line terminators are the line feed (`vbLf`), the carriage return (`vbCr`), and the characters U+2028 and U+2029. - -The value is applied when [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) or [**Split**](Split) makes a match. Changing it does not compile the pattern again. The flag `m` of [**Flags**](Flags) reads and sets the same value. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "^\w+$" -re.Global = True - -Dim text As String -text = "one" & vbLf & "two" -Debug.Print re.Execute(text).Count ' 0 - -re.MultiLine = True -Debug.Print re.Execute(text).Count ' 2 -``` - -## See Also - -- [Flags](Flags) property -- sets this option with the letter `m` -- [DotAll](DotAll) property -- the option for `.` -- [Pattern](Pattern) property -- the regular expression -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Pattern.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Pattern.md deleted file mode 100644 index b14a1210..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/Pattern.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Pattern -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Pattern -has_toc: false ---- - -# Pattern -{: .no_toc } - -Returns or sets the regular expression. - -## Get - -Returns a **String**. - -Syntax: *object*.**Pattern** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -The default value is an empty string. - -## Let - -Sets the regular expression. - -Syntax: *object*.**Pattern** **=** *value* - -*value* -: A **String** that holds the regular expression. - -## Remarks - -Setting a different pattern makes it compile again the next time it is needed. Setting the current pattern does nothing. A pattern that does not compile raises no error when it is set: [**IsValid**](IsValid), [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) report the failure, and [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) raise it. - -A named group is written `(?...)`. Its text is read from the [**SubMatches**](../SubMatches/) of a [**Match**](../Match/) by name, and used in [**Replace**](Replace) as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores. The [overview of the package](../#pattern-syntax) lists the other differences from VBScript's pattern syntax. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -Debug.Print re.Pattern = vbNullString ' True - -re.Pattern = "b+" -Debug.Print re.Pattern ' b+ -Debug.Print re.Replace("abbbc", "-") ' a-c -``` - -## See Also - -- [IsValid](IsValid) property -- whether the pattern compiles -- [IgnoreCase](IgnoreCase) property -- the option that changes how the pattern is compiled -- [Execute](Execute) method -- returns the matches -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Replace.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Replace.md deleted file mode 100644 index 0d68f492..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/Replace.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -title: Replace -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Replace -has_toc: false ---- - -# Replace -{: .no_toc } - -Replaces the matches of the pattern in a string and returns the result. - -Syntax: *object*.**Replace** ( *sourceString*, *replaceString* ) **As String** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -*sourceString* -: *required* A **String**, the text to search. The argument is not changed. - -*replaceString* -: *required* A **String**, the text that replaces each match. It can hold the substitution items in the table below. - -Returns a new **String** with the replacements made. It is *sourceString* unchanged when there is no match. - -## Substitution items - -A `$` in *replaceString* starts a substitution item. Any other text is copied as it is, and so is a `$` that starts no item. - -| Item | Is replaced with | -|------|------------------| -| `$$` | A single `$`. | -| `$&` | The matched text. | -| `` $` `` | The part of *sourceString* before the match. | -| `$'` | The part of *sourceString* after the match. | -| `$`*n* or `$`*nn* | The text of numbered group *n*, counting from 1. Two digits are read as the number when the pattern has a group with that number, otherwise one. It is an empty string when the group matched nothing. When the pattern has no group *n*, the `$` is copied, and so are the digits: `$0` and `$00` are never items, and `$3` in a pattern of two groups is the text `$3`. | -| `$<`*name*`>` | The text of the group written `(?...)`. It is an empty string when the group matched nothing or no group has the name. When the pattern has no named group, or the `>` is missing, or the name is empty, the `$` is copied. | -| `$~` | Nothing. The item is dropped. | - -A `$` that starts no item raises no error, so a replacement string can hold a literal `$` as it is, at the end or before any other character. Before a character that would make an item, write `$$`: `$$$&` inserts a `$` and then the matched text, as the last example does. - - -> [!NOTE] 1.8.0.0 -> Items are read as in VBScript's **RegExp**, with two differences: `$<`*name*`>` is an addition of this package, and `$~` is dropped, where VBScript copies `$~` into the result. - -## Remarks - -With [**Global**](Global) set to **False**, only the first match is replaced. With **Global** set to **True**, every match is. [**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) change what counts as a match. - -A match of no characters is replaced once, wherever it is found. In this example the pattern `^` puts `> ` at the start of each of three lines. - -```tb check_build check_run -Dim re As New RegExp -re.Global = True -re.MultiLine = True -re.Pattern = "^" -Debug.Print Replace(re.Replace("a" & vbLf & "b" & vbLf & "c", "> "), vbLf, "/") ' > a/> b/> c -``` - - -A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Replace** then replaces only the matches found before it gave up. [**SearchAborted**](SearchAborted) returns **True** in that case. - - -An invalid pattern raises the compile error, with the number in [**LastErrorNumber**](LastErrorNumber) and the description in [**LastError**](LastError). Check [**IsValid**](IsValid) first to avoid it. - -## Example - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "(\w+)@(\w+)\.com" -Debug.Print re.Replace("ann@example.com, bob@test.com", "$2:$1") ' example:ann, bob@test.com - -re.Global = True -Debug.Print re.Replace("ann@example.com, bob@test.com", "$2:$1") ' example:ann, test:bob - -re.Pattern = "\d+" -Debug.Print re.Replace("5 and 12", "$$$&") ' $5 and $12 -Debug.Print re.Replace("x5", "$ $x $3") ' x$ $x $3 - -re.Pattern = "(?\w+) (?\w+)" -Debug.Print re.Replace("Ada Lovelace", "$, $") ' Lovelace, Ada -``` - -## See Also - -- [Execute](Execute) method -- returns the matches -- [Split](Split) method -- splits a string at the matches -- [Global](Global) property -- all matches or the first -- [Pattern](Pattern) property -- the regular expression -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/SearchAborted.md b/docs/Reference/Contributed/tbRegExpold/RegExp/SearchAborted.md deleted file mode 100644 index b66672fe..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/SearchAborted.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: SearchAborted -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/SearchAborted -has_toc: false ---- - -# SearchAborted -{: .no_toc } - -Returns whether the last search of this object gave up before it finished. - -Syntax: *object*.**SearchAborted** - -*object* -: *required* An object expression that evaluates to a **RegExp**. - -Returns a **Boolean**: **True** when the most recent call of [**Test**](Test), [**Execute**](Execute), [**Replace**](Replace) or [**Split**](Split) on *object* stopped because its search ran out of steps, **False** otherwise. The property is read-only. It is **False** for an object that has not searched yet. - -## Remarks - -A search is given a budget of steps, where a step is one operation of the compiled pattern. A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, needs twice as many steps for each `a` it is given, and the budget is what stops it. VBScript's **RegExp** has no such limit and for that string does not return in any reasonable time. - -When the budget runs out, the search stops and nothing is raised. Whatever was found before is returned, and the result can be short or empty: [**Test**](Test) returns **False**, [**Execute**](Execute) returns the matches found before the search stopped, [**Replace**](Replace) returns *sourceString* with only those matches replaced, and [**Split**](Split) splits only at them. A program that cannot tell this from a string without a match reads **SearchAborted** after the call. - -The budget is 5,000 steps for each character of the string, at least 1,000,000 and at most 50,000,000. It is not a property of the object, and a program cannot change it. It applies to the search for one match, so with [**Global**](Global) set to **True** each match has a budget of its own, and the first search that runs out ends the call. A simple pattern needs about three steps for each character it matches, so a long match is not affected: `a*` on a string of 1,000,000 `a` returns all of it in about half a second. - -Each call of **Test**, **Execute**, **Replace** or **Split** sets the value again: a call that finishes its search sets it to **False**, and so does a call that raises an error because the pattern is not valid, because no search was made. The value belongs to the object. A search on another **RegExp** does not change it, and a new object returns **False**. - - -> [!NOTE] 1.8.0.0 -> A pattern that is not catastrophic can also run out of steps, if it tries again from each start position and the string is long. `\d+x` on a string of 5,000 digits and no `x` takes a few seconds and then gives up, with the same result as for a string that has no match, and **SearchAborted** returns **True**. A string of 2,000 digits finishes in a second or two and returns **False**. - -## Example - -This example searches a string of thirty `a` for `(a+)+b`, which cannot match and takes a very long time to prove it. The search gives up after about a fifth of a second, and **SearchAborted** says that the **False** from **Test** is not an answer. A search of a string that has no `b`, where the pattern is not catastrophic, finishes. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(a+)+b" -Debug.Print re.SearchAborted ' False - -Debug.Print re.Test(String(30, "a")) ' False -Debug.Print re.SearchAborted ' True - -Debug.Print re.Test("aaab") ' True -Debug.Print re.SearchAborted ' False - -re.Pattern = "a+b" -Debug.Print re.Test(String(30, "a")) ' False -Debug.Print re.SearchAborted ' False -``` - -This example shows that the value is per object, and that a search which gave up still returns the matches found before it. - -```tb check_build check_run -Dim re As New RegExp -Dim other As New RegExp -re.Pattern = "x|(a+)+b" -re.Global = True -other.Pattern = "a" - -Dim matches As MatchCollection -Set matches = re.Execute("xx" & String(30, "a")) -Debug.Print matches.Count ' 2 -Debug.Print re.SearchAborted ' True - -Debug.Print other.Test("a") ' True -Debug.Print other.SearchAborted ' False -Debug.Print re.SearchAborted ' True -``` - -## See Also - -- [Test](Test) method -- returns whether a string contains a match -- [Execute](Execute) method -- returns the matches -- [Replace](Replace) method -- replaces the matches -- [Split](Split) method -- splits a string at the matches -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Split.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Split.md deleted file mode 100644 index b1563cfa..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/Split.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: Split -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Split -has_toc: false ---- - -# Split -{: .no_toc } - -Splits a string at the matches of the pattern and returns the pieces as a **Collection**. - -Syntax: *object*.**Split** ( *sourceString* ) **As Collection** - -*object* -: *required* An object expression that evaluates to a [**RegExp**](./). - -*sourceString* -: *required* A **String**, the text to split. - -Returns a new **Collection** of **String** values, numbered from 1. It holds the text before the first match, the text between each match and the next, and the text after the last match. The matched text itself is not in the collection. - -## Remarks - -[**Global**](Global) sets how many matches split the string. With **Global** set to **False**, the default, only the first match splits it, so the collection holds the text before that match and the text after it. With **Global** set to **True**, every match splits it. - -A match that has capture groups adds the text of each group to the collection, in order, straight after the text before that match. A group that took no part in the match, or matched nothing, adds an empty string. - -When [**Pattern**](Pattern) finds no match, the collection holds one item, the whole of *sourceString*. No piece is dropped: a match at the start of the string gives an empty first string, and two matches next to each other give an empty string between them. - -[**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) apply to the matching. - -A match of no characters splits the string at its position, once. In this example the string is split before each capital letter. - -```tb check_build check_run -Dim re As New RegExp -re.Global = True -re.Pattern = "(?=[A-Z])" - -Dim parts As Collection -Set parts = re.Split("fooBarBaz") -Debug.Print parts.Count ' 3 -Debug.Print parts(1) & "|" & parts(2) & "|" & parts(3) ' foo|Bar|Baz -``` - - -A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Split** then splits only at the matches found before it gave up. [**SearchAborted**](SearchAborted) returns **True** in that case. - - -When **Pattern** is not a valid regular expression, **Split** raises the compile error. Its number is [**LastErrorNumber**](LastErrorNumber), its description is [**LastError**](LastError), and its source is `RegExp`. Read [**IsValid**](IsValid) first to avoid the error. - -## Example - -This example splits a list at each comma, first at the first match only, then at every match. - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = ",\s*" - -Dim parts As Collection -Set parts = re.Split("red, green,blue") -Debug.Print parts.Count ' 2 -Debug.Print parts(1) ' red -Debug.Print parts(2) ' green,blue - -re.Global = True -Set parts = re.Split("red, green,blue") -Debug.Print parts.Count ' 3 -Debug.Print parts(1) ' red -Debug.Print parts(2) ' green -Debug.Print parts(3) ' blue -``` - -This example shows the text of a capture group in the result. - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "(-)" -re.Global = True - -Dim parts As Collection -Set parts = re.Split("a-b-c") -Debug.Print parts.Count ' 5 -Debug.Print parts(1) ' a -Debug.Print parts(2) ' - -Debug.Print parts(3) ' b -Debug.Print parts(4) ' - -Debug.Print parts(5) ' c -``` - -## See Also - -- [Execute](Execute) method -- returns the matches themselves -- [Replace](Replace) method -- replaces the matches -- [Global](Global) property -- all matches or only the first -- [Pattern](Pattern) property -- the regular expression -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/Test.md b/docs/Reference/Contributed/tbRegExpold/RegExp/Test.md deleted file mode 100644 index 49358f94..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/Test.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Test -parent: RegExp -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/Test -has_toc: false ---- - -# Test -{: .no_toc } - -Returns whether the pattern finds a match in a string. - -Syntax: *object*.**Test** ( *sourceString* ) **As Boolean** - -*object* -: *required* An object expression that evaluates to a [**RegExp**](./). - -*sourceString* -: *required* A **String**, the text to search. - -Returns **True** if [**Pattern**](Pattern) finds a match in *sourceString*, otherwise **False**. - -## Remarks - -**Test** only reports whether a match exists. To read the matches, use [**Execute**](Execute). - -[**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) apply to the matching. [**Global**](Global) makes no difference, because one match is enough. - - -A search that backtracks without end, such as `(a+)+b` on thirty `a`, gives up instead of hanging, and **Test** then returns **False**. [**SearchAborted**](SearchAborted) tells that **False** from a string that has no match. - - -When **Pattern** is not a valid regular expression, **Test** raises the compile error. Its number is [**LastErrorNumber**](LastErrorNumber), its description is [**LastError**](LastError), and its source is `RegExp`. Read [**IsValid**](IsValid) first to avoid the error. - -## Example - -This example tests two strings against a pattern, then shows the effect of **IgnoreCase**. - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "\d{3}-\d{4}" -Debug.Print re.Test("Call 555-0142 today") ' True -Debug.Print re.Test("Call us today") ' False - -re.Pattern = "^hello" -Debug.Print re.Test("Hello") ' False -re.IgnoreCase = True -Debug.Print re.Test("Hello") ' True -``` - -## See Also - -- [Execute](Execute) method -- returns the matches themselves -- [Replace](Replace) method -- replaces the matches -- [Split](Split) method -- splits a string at the matches -- [IsValid](IsValid) property -- whether the pattern compiles -- [RegExp](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/RegExp/index.md b/docs/Reference/Contributed/tbRegExpold/RegExp/index.md deleted file mode 100644 index 413728d0..00000000 --- a/docs/Reference/Contributed/tbRegExpold/RegExp/index.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: RegExp -parent: tbRegExp Package -permalink: /tB/Packages/Contributed/tbRegExp/RegExp/ -has_toc: false ---- - -# RegExp class -{: .no_toc } - -Searches strings with a regular expression: finds the matches, tests for one, replaces them and splits a string at them. - -**RegExp** is a replacement for the regular expression object of VBScript. It follows the same object model: [**Execute**](Execute) returns a [**MatchCollection**](../MatchCollection/) of [**Match**](../Match/) objects, and the indexes of both are zero-based. The matching is done by the vba-regex engine, which the package includes as the **StaticRegex** module. - -The class is not predeclared. Create an instance with **New**, set [**Pattern**](Pattern) and the options, then call a method. - -```tb check_build check_run -Dim re As RegExp -Set re = New RegExp -re.Pattern = "(\d+)-(\d+)" -re.Global = True - -Dim matches As MatchCollection -Set matches = re.Execute("10-20 and 30-40") -Debug.Print matches.Count ' 2 -Debug.Print matches.Item(0).Value ' 10-20 -Debug.Print matches.Item(1).SubMatches.Item(0) ' 30 - -Debug.Print re.Test("none") ' False -Debug.Print re.Replace("10-20", "$2-$1") ' 20-10 -``` - -## Options - -Four properties change how the pattern is matched. [**Flags**](Flags) reads and sets all four as one string of letters. - -| Property | Flag | When **True** | -|----------|------|---------------| -| [**Global**](Global) | `g` | All matches are used. When **False**, [**Execute**](Execute), [**Replace**](Replace) and [**Split**](Split) use only the first match. | -| [**MultiLine**](MultiLine) | `m` | `^` and `$` also match at the start and end of each line. | -| [**IgnoreCase**](IgnoreCase) | `i` | Letter case is ignored. | -| [**DotAll**](DotAll) | `s` | `.` also matches a line feed. | - -All four are **False** in a new instance. - -## Invalid patterns - -The pattern is compiled the first time a method or one of [**IsValid**](IsValid), [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) needs it. It is compiled again after [**Pattern**](Pattern) or [**IgnoreCase**](IgnoreCase) is set to a different value. [**Global**](Global), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) are applied when a match is made and do not cause a new compilation. - -A pattern that does not compile does not raise an error at once. [**IsValid**](IsValid) returns **False**, and [**LastError**](LastError) and [**LastErrorNumber**](LastErrorNumber) describe the failure. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) then raise that error, with the number in **LastErrorNumber**, the description in **LastError** and the source `RegExp`. When **IsValid**, **LastError** or **LastErrorNumber** is the call that compiles an invalid pattern, it raises nothing, but the compile error stays in the **Err** object after it returns; [**IsValid**](IsValid) says what to do about it. - -## Properties - -- [DotAll](DotAll) -- returns or sets whether `.` matches a line feed -- [Flags](Flags) -- returns or sets the four options as a string of letters -- [Global](Global) -- returns or sets whether all matches are used -- [IgnoreCase](IgnoreCase) -- returns or sets whether letter case is ignored -- [IsValid](IsValid) -- returns whether the pattern compiles -- [LastError](LastError) -- returns the message of the compile error -- [LastErrorNumber](LastErrorNumber) -- returns the number of the compile error -- [MultiLine](MultiLine) -- returns or sets whether `^` and `$` match at line boundaries -- [Pattern](Pattern) -- returns or sets the regular expression -- [SearchAborted](SearchAborted) -- returns whether the last search gave up before it finished - -## Methods - -- [Execute](Execute) -- returns the matches in a string -- [Replace](Replace) -- replaces the matches in a string -- [Split](Split) -- splits a string at the matches -- [Test](Test) -- returns whether a string contains a match - -## See Also - -- [MatchCollection](../MatchCollection/) class -- the matches that **Execute** returns -- [Match](../Match/) class -- one match -- [SubMatches](../SubMatches/) class -- the groups of one match -- [tbRegExp package](../) -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/SubMatches/Count.md b/docs/Reference/Contributed/tbRegExpold/SubMatches/Count.md deleted file mode 100644 index 0186963a..00000000 --- a/docs/Reference/Contributed/tbRegExpold/SubMatches/Count.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Count -parent: SubMatches -grand_parent: tbRegExp Package -permalink: /tB/Packages/Contributed/tbRegExp/SubMatches/Count -has_toc: false ---- - -# Count -{: .no_toc } - -Returns the number of strings in the collection, one for each capture group of the pattern. - -Syntax: *object*.**Count** - -*object* -: *required* An object expression that evaluates to a **SubMatches**. - -Returns a **Long**. The property is read-only. - -## Remarks - -**Count** is the number of capture groups in the pattern, not the number of groups that took part in the match. A group that took no part still has its position, and its string is empty. It is 0 for a pattern with no capture groups. - -The positions that [**Item**](Item) accepts run from 0 to **Count** - 1. - -## Example - -```tb check_build check_run -Dim re As New RegExp -Dim matches As MatchCollection -Dim sm As SubMatches - -re.Pattern = "\d+" -Set matches = re.Execute("a1") -Set sm = matches(0).SubMatches -Debug.Print sm.Count ' 0 - -re.Pattern = "(a)|(b)" -Set matches = re.Execute("b") -Set sm = matches(0).SubMatches -Debug.Print sm.Count ' 2 -Debug.Print Len(sm(0)) ' 0 -Debug.Print sm(1) ' b -``` - -## See Also - -- [Item](Item) property -- returns a string by position or by name -- [SubMatches](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/SubMatches/Item.md b/docs/Reference/Contributed/tbRegExpold/SubMatches/Item.md deleted file mode 100644 index a8863f85..00000000 --- a/docs/Reference/Contributed/tbRegExpold/SubMatches/Item.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: Item -parent: SubMatches -grand_parent: tbRegExp Package -permalink: /tB/Packages/Contributed/tbRegExp/SubMatches/Item -has_toc: false ---- - -# Item -{: .no_toc } - -Returns the string captured by one capture group, by its position or by its name. - -Syntax: *object*.**Item** ( *pItemIndex* ) - -*object* -: *required* An object expression that evaluates to a **SubMatches**. - -*pItemIndex* -: *required* A **Variant**. A number is the zero-based position of the group: the first group is 0 and the last is [**Count**](Count) - 1. A value that is not numeric is the name of a group. - -Returns a **String**. The property is read-only. - -## Remarks - -**Item** is the default member of the class, so `sm(0)` is the same as `sm.Item(0)`. - -*pItemIndex* is taken as a position when **IsNumeric** returns **True** for it, and as a name otherwise. - -A position below 0, or not below [**Count**](Count), raises run-time error `vbObjectError + 9` with the message *Requested item not found in SubMatches collection*. - -A name is the one given to a group in the pattern, written `(?...)`. **Item** returns the string of the group that has that name. Letter case counts: `sm("YEAR")` does not find a group named `year`. A name that no group has returns an empty string and raises no error. A name starts with a letter or an underscore and goes on with letters, digits and underscores. - -When several groups have the same name, **Item** returns the string of the last of them that took part in the match, as the last example shows. - -An empty result has three possible causes: a group that took no part in the match, a group that captured no characters, and a name that no group has. - -## Example - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(?\d{4})-(?\d{2})" - -Dim matches As MatchCollection -Set matches = re.Execute("2026-10") - -Dim sm As SubMatches -Set sm = matches(0).SubMatches -Debug.Print sm(0) ' 2026 -Debug.Print sm.Item("month") ' 10 -Debug.Print Len(sm("day")) ' 0 -``` - -This example asks for a position that the collection does not have. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(\d+)" - -Dim matches As MatchCollection -Set matches = re.Execute("a1") - -Dim sm As SubMatches -Set sm = matches(0).SubMatches - -On Error Resume Next -Debug.Print sm(1) -Debug.Print Err.Number & ": " & Err.Description ' -2147221495: Requested item not found in SubMatches collection -``` - -This example gives two groups the same name. In `ab` both groups took part, so the second one is returned. In `a` only the first group did. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(?a)(?b)?" - -Debug.Print re.Execute("ab")(0).SubMatches("n") ' b -Debug.Print re.Execute("a")(0).SubMatches("n") ' a -``` - -## See Also - -- [Count](Count) property -- the number of strings -- [NewEnum](NewEnum) method -- the enumerator for **For Each** -- [SubMatches](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/SubMatches/NewEnum.md b/docs/Reference/Contributed/tbRegExpold/SubMatches/NewEnum.md deleted file mode 100644 index 9e295865..00000000 --- a/docs/Reference/Contributed/tbRegExpold/SubMatches/NewEnum.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: NewEnum -parent: SubMatches -grand_parent: tbRegExp Package -permalink: /tB/Packages/Contributed/tbRegExp/SubMatches/NewEnum -has_toc: false ---- - -# NewEnum -{: .no_toc } - -Returns the enumerator that **For Each** uses to go through the strings of the collection. - -Syntax: *object*.**NewEnum** **As IUnknown** - -*object* -: *required* An object expression that evaluates to a **SubMatches**. - -Returns an **IUnknown** that refers to a new enumerator over the strings of the capture groups, which starts at the first one. - -## Remarks - -**NewEnum** is declared with the [**Enumerator**](../../../../Core/Attributes#enumerator) attribute, which makes it the member that [**For Each**](../../../../Core/For-Each-Next) calls to start an enumeration. [Custom Enumerators](../../../../../Features/Language/Custom-Enumerators) describes the rule. It is also declared [**Hidden**](../../../../Core/Attributes#hidden), so it does not appear in certain IntelliSense lists. - -The elements come in the order of their positions, from 0 to [**Count**](Count) - 1, and each is the string of one group. A group that took no part in the match gives an empty string. A **Variant** variable receives each element, as in the example. - -A program does not need to call **NewEnum**. **For Each** does it. - -## Example - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(\w+)@(\w+)\.com" - -Dim matches As MatchCollection -Set matches = re.Execute("ann@example.com") - -Dim sm As SubMatches -Set sm = matches(0).SubMatches - -Dim part As Variant -For Each part In sm - Debug.Print part -Next -' Output: -' ann -' example -``` - -## See Also - -- [Item](Item) property -- returns a string by position or by name -- [Count](Count) property -- the number of strings -- [For Each...Next](../../../../Core/For-Each-Next) statement -- uses the enumerator -- [SubMatches](./) class -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/SubMatches/index.md b/docs/Reference/Contributed/tbRegExpold/SubMatches/index.md deleted file mode 100644 index 95f29906..00000000 --- a/docs/Reference/Contributed/tbRegExpold/SubMatches/index.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: SubMatches -parent: tbRegExp Package -permalink: /tB/Packages/Contributed/tbRegExp/SubMatches/ -has_toc: false ---- - -# SubMatches class -{: .no_toc } - -Holds the strings that the capture groups of a pattern captured in one [**Match**](../Match/). - -The [**SubMatches**](../Match/SubMatches) property of a **Match** returns a **SubMatches** object. It holds one string for each capture group in the pattern, in the order of the groups. A pattern with no capture groups gives an object whose [**Count**](Count) is 0. A group that took no part in the match, such as one side of an alternation that did not match, still has its position, and its string is empty. - -The strings are read by position or by name. The position is zero-based, as in the VBScript object model that the package replaces. A group written `(?...)` has a name, and its string can be read by that name as well. [**Item**](Item) is the default member, so `sm(0)` is the same as `sm.Item(0)`. **For Each** goes through the strings in the order of their positions. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(?\d{4})-(?\d{2})-(?\d{2})" - -Dim matches As MatchCollection -Set matches = re.Execute("Released on 2026-10-11.") - -Dim sm As SubMatches -Set sm = matches(0).SubMatches -Debug.Print sm.Count ' 3 -Debug.Print sm(0) ' 2026 -Debug.Print sm("month") ' 10 -``` - -The class has no member that adds or removes a string: the package fills a **SubMatches** from the groups of the match it belongs to. - -## Properties - -- [Count](Count) -- returns the number of strings -- [Item](Item) -- returns one string by position or by group name (the default member) - -## Methods - -- [NewEnum](NewEnum) -- returns the enumerator that **For Each** uses (hidden) - -## See Also - -- [Match](../Match/) class -- one match; its **SubMatches** property returns this object -- [MatchCollection](../MatchCollection/) class -- the matches of a search -- [RegExp](../RegExp/) class -- runs the search -- [For Each...Next](../../../../Core/For-Each-Next) statement -- goes through the strings -- [tbRegExp package](../) -- overview diff --git a/docs/Reference/Contributed/tbRegExpold/index.md b/docs/Reference/Contributed/tbRegExpold/index.md deleted file mode 100644 index 231c2623..00000000 --- a/docs/Reference/Contributed/tbRegExpold/index.md +++ /dev/null @@ -1,143 +0,0 @@ ---- -title: tbRegExp Package -parent: Contributed Packages -has_toc: false -exclude_from_docs: - - StaticRegex -indexed_from: 1.8.0.0 -permalink: /tB/Packages/Contributed/tbRegExp/ ---- - -# tbRegExp Package -{: .no_toc } - -**Author:** GCuser99. **Version:** 1.8.0.0. **On GitHub:** [GCuser99/tbRegExp](https://github.com/GCuser99/tbRegExp), in the folder *Package*. - -The **tbRegExp** contributed package provides regular expressions for twinBASIC projects, as a replacement for the **RegExp** object of VBScript. It follows the same object model. A [**RegExp**](RegExp/) is given a pattern and options, its [**Execute**](RegExp/Execute) method returns a [**MatchCollection**](MatchCollection/) of [**Match**](Match/) objects, and each **Match** holds the text captured by the groups of the pattern as a [**SubMatches**](SubMatches/) object. Positions are zero-based, as in VBScript. - -The matching is done by the [vba-regex](https://github.com/sihlfall/vba-regex) engine of sihlfall, which the package includes as a private module, **StaticRegex**. - -Beyond VBScript's object, **RegExp** has the [**DotAll**](RegExp/DotAll) and [**Flags**](RegExp/Flags) options, the [**Split**](RegExp/Split) method, [**IsValid**](RegExp/IsValid), [**LastError**](RegExp/LastError) and [**LastErrorNumber**](RegExp/LastErrorNumber), which report an invalid pattern before it is used, and [**SearchAborted**](RegExp/SearchAborted), which reports a search that gave up. [**Replace**](RegExp/Replace) reads a `$` in the replacement string as VBScript does, and adds `$` for a named group. - -## Pattern syntax - -A pattern is written as for VBScript's **RegExp**. **tbRegExp** also accepts: - -- named groups, `(?...)`: [**SubMatches.Item**](SubMatches/Item) returns one by its name, and [**Replace**](RegExp/Replace) inserts one as `$`. A name starts with a letter or an underscore and goes on with letters, digits and underscores; a pattern with any other name is not valid; -- lookbehind, `(?<=...)` and `(?\d{4})-(?\d{2})" -Debug.Print re.Execute("2026-10")(0).SubMatches("month") ' 10 -Debug.Print re.Replace("2026-10", "$/$") ' 10/2026 - -re.Pattern = "(?<=\$)\d+" -Debug.Print re.Execute("costs $123")(0).Value ' 123 - -re.Pattern = "(?i:abc)DEF" -Debug.Print re.Test("ABCDEF") ' True -Debug.Print re.Test("ABCdef") ' False -``` - - -> [!NOTE] 1.8.0.0 -> A pattern that holds an atomic group `(?>...)` or a conditional is not valid: [**IsValid**](RegExp/IsValid) returns **False**. A named backreference `\k`, `\A`, `\Z` and `\p{...}` are not supported either, but they do not make the pattern invalid, because a backslash before a letter that names no escape stands for the letter itself, as in VBScript: `\k` matches the text `k`, and `\A` matches `A`. The pattern `\p{L}` is invalid for another reason: `{L}` does not start a quantifier (see below). - -The escapes that name a control character or a character code are read as in VBScript: - -- `\c` followed by a letter, in either case, is a control character: `\cJ` and `\CJ` are the line feed. A `\c` followed by anything else makes the pattern invalid; -- `\0` is the character with code 0, `\0` followed by octal digits, such as `\012`, is that code, and `\8` and `\9` are the digits themselves; -- `\1` to `\9` and longer numbers are backreferences when the pattern has that many groups. Otherwise the digits are read as an octal character code, so `\101` is `A` and `(a)\2` is `a` followed by the character with code 2; [**IsValid**](RegExp/IsValid) returns **True** for both; -- `\f`, `\n`, `\r` and `\t`, inside a character class as well, are the form feed, line feed, carriage return and tab. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "\101" -Debug.Print re.Test("A") ' True -re.Pattern = "\CJ" -Debug.Print re.Test(vbLf) ' True -re.Pattern = "\k" -Debug.Print re.IsValid ' True -Debug.Print re.Test("k") ' True -re.Pattern = "(a)\2" -Debug.Print re.Test("a" & ChrW(2)) ' True -``` - - -> [!NOTE] 1.8.0.0 -> A backreference to a group that has not yet taken part in the match, such as `\1(a)`, matches no characters, so the pattern finds `a` in the string `a`. VBScript's **RegExp** finds no match for such a pattern. - -`\w` matches only the ASCII letters and digits and `_`, as in VBScript, so `\w+` finds `caf` in `café`. - -A `{` has to start a quantifier: `{n}`, `{n,}`, `{n,m}` or `{,m}`, which means `{0,m}`. A pattern in which a `{` starts none, such as `a{` or `a{x}`, is not valid; write `\{` for the character. A `]` or `}` that closes nothing is an ordinary character, as in VBScript. - - -> [!NOTE] 1.8.0.0 -> VBScript's **RegExp** reads a `{` that starts no quantifier, and `{,m}`, as ordinary text: `a{x}` is a valid pattern there that finds the text `a{x}`, and `a{,2}` finds the text `a{,2}` and not `aa`. In **tbRegExp** the first is not valid, so a search with it raises the compile error, and `a{,2}` finds up to two `a`. - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(?<_part2>\d)" -Debug.Print re.IsValid ' True -re.Pattern = "(?<2part>\d)" -Debug.Print re.IsValid ' False - -re.Pattern = "a{x}" -Debug.Print re.IsValid ' False -re.Pattern = "a\{x}" -Debug.Print re.Execute("a{x}")(0).Value ' a{x} -``` - -## Searches that give up - - -A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, does not hang a search as it does in VBScript's **RegExp**. Every search has a budget of steps, which a program cannot change, and when it runs out the search stops and raises no error. Its result is then short or empty, and a program that has to tell it from a true result reads [**SearchAborted**](RegExp/SearchAborted) after the call. A long match is no problem: `a*` and `^.*$` match a string of 1,000,000 `a` whole. - - -```tb check_build check_run -Dim re As New RegExp -re.Pattern = "(a+)+b" -Debug.Print re.Test(String(30, "a")) ' False -Debug.Print re.SearchAborted ' True - -re.Pattern = "^.*$" -Debug.Print re.Test(String(100000, "a")) ' True -Debug.Print re.SearchAborted ' False -``` - -## Classes - -- [Match](Match/) -- holds one match that a RegExp found in a string: the matched text, where it starts, how many characters it has, and the text captured by its groups - - [FirstIndex](Match/FirstIndex) -- returns the zero-based position where the match starts - - [Length](Match/Length) -- returns the number of characters in the match - - [SubMatches](Match/SubMatches) -- returns the text captured by the groups of the pattern - - [Value](Match/Value) -- returns the matched text (default member) -- [MatchCollection](MatchCollection/) -- holds the Match objects that a regular expression finds in a string - - [Count](MatchCollection/Count) -- returns the number of matches - - [Item](MatchCollection/Item) -- returns one match by position (the default member) - - [NewEnum](MatchCollection/NewEnum) -- returns the enumerator that For Each uses (hidden) -- [RegExp](RegExp/) -- searches strings with a regular expression: finds the matches, tests for one, replaces them and splits a string at them - - [DotAll](RegExp/DotAll) -- returns or sets whether `.` matches a line feed - - [Execute](RegExp/Execute) -- returns the matches in a string - - [Flags](RegExp/Flags) -- returns or sets the four options as a string of letters - - [Global](RegExp/Global) -- returns or sets whether all matches are used - - [IgnoreCase](RegExp/IgnoreCase) -- returns or sets whether letter case is ignored - - [IsValid](RegExp/IsValid) -- returns whether the pattern compiles - - [LastError](RegExp/LastError) -- returns the message of the compile error - - [LastErrorNumber](RegExp/LastErrorNumber) -- returns the number of the compile error - - [MultiLine](RegExp/MultiLine) -- returns or sets whether `^` and `$` match at line boundaries - - [Pattern](RegExp/Pattern) -- returns or sets the regular expression - - [Replace](RegExp/Replace) -- replaces the matches in a string - - [SearchAborted](RegExp/SearchAborted) -- returns whether the last search gave up before it finished - - [Split](RegExp/Split) -- splits a string at the matches - - [Test](RegExp/Test) -- returns whether a string contains a match -- [SubMatches](SubMatches/) -- holds the strings that the capture groups of a pattern captured in one Match - - [Count](SubMatches/Count) -- returns the number of strings - - [Item](SubMatches/Item) -- returns one string by position or by group name (the default member) - - [NewEnum](SubMatches/NewEnum) -- returns the enumerator that For Each uses (hidden) - -## Test cases - -The notes and warnings of this section are checked by the test cases in [tbRegExp-tests.twinproj](tbRegExp-tests.twinproj), a twinBASIC project. It uses tbRegExp as a [linked package](../../../../Features/Packages/Linked#manual-management): put the package's `.twinpack` in `%APPDATA%\twinBASIC\packages`, then run the project, and each case prints whether it passes. From f4674f919a7aeb53885aa8a070e923d8a528bc08 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 14 Oct 2026 12:31:47 -0600 Subject: [PATCH 4/5] Update index.md --- docs/Reference/Contributed/tbRegExp/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Reference/Contributed/tbRegExp/index.md b/docs/Reference/Contributed/tbRegExp/index.md index 7149faeb..18fa0adb 100644 --- a/docs/Reference/Contributed/tbRegExp/index.md +++ b/docs/Reference/Contributed/tbRegExp/index.md @@ -44,6 +44,8 @@ Debug.Print re.Replace("2026-10", "$/$") ' 10/2026 ## Searches that give up +This is the one place where **tbRegExp** behaves differently from VBScript's **RegExp** rather than adding to it. + A pattern that backtracks without end, such as `(a+)+b` on a string of thirty `a`, does not hang a search as it does in VBScript's **RegExp**. Every search is given a budget of steps, and when it runs out the search raises an error rather than returning a result it never established. A long match is no problem: `a*` and `^.*$` match a string of 1,000,000 `a` whole. From 0d2b0b687c082f36cd82c1934bec30806267964e Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 14 Oct 2026 13:02:24 -0600 Subject: [PATCH 5/5] update by contributor --- .../tbRegExp/Sources/Expect.twin | 155 ++++++++++++++++++ .../tbRegExp/Sources/RunAll.twin | 32 ++-- .../tbRegExp/Sources/SearchAbortedTests.twin | 27 --- .../tbRegExp/Sources/SearchFinishesTests.twin | 39 +++++ .../tbRegExp/Sources/SearchGivesUpTests.twin | 110 ++++++++++--- test/package-tests/tbRegExp/expect.json | 50 +----- .../tbRegExp/tbRegExpTests.twinproj | Bin 0 -> 41540 bytes 7 files changed, 302 insertions(+), 111 deletions(-) create mode 100644 test/package-tests/tbRegExp/Sources/Expect.twin delete mode 100644 test/package-tests/tbRegExp/Sources/SearchAbortedTests.twin create mode 100644 test/package-tests/tbRegExp/Sources/SearchFinishesTests.twin create mode 100644 test/package-tests/tbRegExp/tbRegExpTests.twinproj diff --git a/test/package-tests/tbRegExp/Sources/Expect.twin b/test/package-tests/tbRegExp/Sources/Expect.twin new file mode 100644 index 00000000..59f5c2f4 --- /dev/null +++ b/test/package-tests/tbRegExp/Sources/Expect.twin @@ -0,0 +1,155 @@ +' Expect: the checks every test case of the twinBASIC documentation's package tests makes. +' Part of each test project; https://docs.twinbasic.com. MIT licence. +' +' A case is a [TestCase] Sub of a [TestFixture] Module. It starts with Expect.Begin, checks with +' Expect.AreEqual, Expect.IsTrue, Expect.IsFalse and Expect.Fail, and ends with Expect.Done. Each +' check states what is correct, so a case that fails names a defect of the package. A failed check +' does not stop the case: Expect.Done reports every failed check of the case at once. +' +' Run the project (Sub Main in the module RunAll) to run every case: each one prints PASS, FAIL or +' SKIP in the DEBUG CONSOLE. A test runner that runs one case at a time sees a failed case as a +' failed assertion, which the case's own On Error statement cannot hide. +Module Expect + + Private mChecks As Long + Private mFailures As Long + Private mText As String + Private mCollecting As Boolean + Private mReachedDone As Boolean + Private mPassed As Long + Private mFailed As Long + Private mSkipped As Long + + ' Starts a case: forgets the checks of the case before it. + Public Sub Begin() + mChecks = 0 + mFailures = 0 + mText = "" + End Sub + + ' Checks that actual is expected. A string equals only a string, Empty only Empty, Null only + ' Null and an object only the same object; numbers, dates and Booleans compare by value. + Public Sub AreEqual(ByVal Expected As Variant, ByVal Actual As Variant, ByVal What As String) + mChecks = mChecks + 1 + If Not Same(Expected, Actual) Then Record What & ": expected " & Show(Expected) & ", got " & Show(Actual) + End Sub + + Public Sub IsTrue(ByVal Condition As Boolean, ByVal What As String) + mChecks = mChecks + 1 + If Not Condition Then Record What & ": expected True, got False" + End Sub + + Public Sub IsFalse(ByVal Condition As Boolean, ByVal What As String) + mChecks = mChecks + 1 + If Condition Then Record What & ": expected False, got True" + End Sub + + ' A failed check with no values, for a place the case must not reach. + Public Sub Fail(ByVal What As String) + mChecks = mChecks + 1 + Record What + End Sub + + ' Ends a case: reports the failed checks, if any. + Public Sub Done() + Dim text As String + If mFailures > 0 Then text = mFailures & " of " & mChecks & " checks failed: " & mText + Begin + If mCollecting Then + mReachedDone = True + mText = text + ElseIf Len(text) > 0 Then + Assert.Strict.Fail text + End If + End Sub + + ' ---- used by RunAll + + Public Sub StartRun(ByVal Title As String) + mCollecting = True + mPassed = 0 + mFailed = 0 + mSkipped = 0 + Debug.Print Title + End Sub + + Public Sub StartCase() + Begin + mReachedDone = False + End Sub + + ' What the case's call left: Done's report, or the error that ended it before Done. + Public Sub EndCase(ByVal Name As String, ByVal ErrNumber As Long, ByVal ErrDescription As String) + Dim outcome As String + If mReachedDone Then + outcome = mText + ElseIf ErrNumber <> 0 Then + outcome = "run-time error " & ErrNumber & ": " & ErrDescription + Else + outcome = "the case ended before Expect.Done" + End If + If Len(outcome) = 0 Then + mPassed = mPassed + 1 + Debug.Print "PASS " & Name + Else + mFailed = mFailed + 1 + Debug.Print "FAIL " & Name & ": " & outcome + End If + Begin + End Sub + + Public Sub Skip(ByVal Name As String, ByVal Reason As String) + mSkipped = mSkipped + 1 + Debug.Print "SKIP " & Name & ": " & Reason + End Sub + + Public Sub EndRun() + Debug.Print mPassed & " passed, " & mFailed & " failed, " & mSkipped & " skipped" + mCollecting = False + End Sub + + ' ---- inside + + Private Sub Record(ByVal Text As String) + mFailures = mFailures + 1 + If Len(mText) > 0 Then mText = mText & "; " + mText = mText & Text + End Sub + + Private Function Same(ByVal A As Variant, ByVal B As Variant) As Boolean + If IsObject(A) Or IsObject(B) Then + If IsObject(A) And IsObject(B) Then Same = (A Is B) + ElseIf IsArray(A) Or IsArray(B) Then + Same = False + ElseIf IsNull(A) Or IsNull(B) Then + Same = IsNull(A) And IsNull(B) + ElseIf IsEmpty(A) Or IsEmpty(B) Then + Same = IsEmpty(A) And IsEmpty(B) + ElseIf VarType(A) = vbString Or VarType(B) = vbString Then + If VarType(A) = vbString And VarType(B) = vbString Then Same = (StrComp(A, B, vbBinaryCompare) = 0) + Else + Same = (A = B) + End If + End Function + + Private Function Show(ByVal V As Variant) As String + If IsObject(V) Then + If V Is Nothing Then Show = "Nothing" Else Show = "an object of type " & TypeName(V) + ElseIf IsArray(V) Then + Show = "an array of type " & TypeName(V) + ElseIf IsNull(V) Then + Show = "Null" + ElseIf IsEmpty(V) Then + Show = "Empty" + ElseIf VarType(V) = vbString Then + If Len(V) > 120 Then + Show = """" & Left$(V, 100) & """... (" & Len(V) & " characters)" + Else + Show = """" & V & """" + End If + Else + Show = CStr(V) + End If + End Function + +End Module diff --git a/test/package-tests/tbRegExp/Sources/RunAll.twin b/test/package-tests/tbRegExp/Sources/RunAll.twin index 8a724f12..4fe27c9e 100644 --- a/test/package-tests/tbRegExp/Sources/RunAll.twin +++ b/test/package-tests/tbRegExp/Sources/RunAll.twin @@ -1,11 +1,11 @@ ' Generated by scripts/package_tests.mjs from this project's [TestCase] Subs and expect.json: ' do not edit. Run Sub Main: each case prints PASS, FAIL or SKIP in the DEBUG CONSOLE. -' The cases are written for the package tbRegExp, version 1.8.0.0. Each one checks what is +' The cases are written for the package tbRegExp, version 1.9.0.0. Each one checks what is ' correct, so a case that fails names a defect of the package. Module RunAll Public Sub Main() - Expect.StartRun "tbRegExp 1.8.0.0: 31 test cases" + Expect.StartRun "tbRegExp 1.9.0.0: 32 test cases" On Error Resume Next Expect.StartCase Err.Clear @@ -101,28 +101,32 @@ Module RunAll Expect.EndCase "ReplaceTests.Replace_TildeItemIsCopied", Err.Number, Err.Description Expect.StartCase Err.Clear - SearchAbortedTests.SearchAborted_DigitsWithoutXFinishAt2000 - Expect.EndCase "SearchAbortedTests.SearchAborted_DigitsWithoutXFinishAt2000", Err.Number, Err.Description + SearchFinishesTests.Search_DigitsWithoutXFinishAt2000 + Expect.EndCase "SearchFinishesTests.Search_DigitsWithoutXFinishAt2000", Err.Number, Err.Description Expect.StartCase Err.Clear - SearchAbortedTests.SearchAborted_DigitsWithoutXFinishAt5000 - Expect.EndCase "SearchAbortedTests.SearchAborted_DigitsWithoutXFinishAt5000", Err.Number, Err.Description + SearchFinishesTests.Search_DigitsWithoutXFinishAt5000 + Expect.EndCase "SearchFinishesTests.Search_DigitsWithoutXFinishAt5000", Err.Number, Err.Description Expect.StartCase Err.Clear - SearchGivesUpTests.Test_CatastrophicPatternGivesUpWithoutError - Expect.EndCase "SearchGivesUpTests.Test_CatastrophicPatternGivesUpWithoutError", Err.Number, Err.Description + SearchGivesUpTests.Test_CatastrophicPatternRaises + Expect.EndCase "SearchGivesUpTests.Test_CatastrophicPatternRaises", Err.Number, Err.Description Expect.StartCase Err.Clear - SearchGivesUpTests.Execute_CatastrophicPatternKeepsMatchesFound - Expect.EndCase "SearchGivesUpTests.Execute_CatastrophicPatternKeepsMatchesFound", Err.Number, Err.Description + SearchGivesUpTests.Execute_CatastrophicPatternKeepsNothing + Expect.EndCase "SearchGivesUpTests.Execute_CatastrophicPatternKeepsNothing", Err.Number, Err.Description Expect.StartCase Err.Clear - SearchGivesUpTests.Replace_CatastrophicPatternReplacesMatchesFound - Expect.EndCase "SearchGivesUpTests.Replace_CatastrophicPatternReplacesMatchesFound", Err.Number, Err.Description + SearchGivesUpTests.Replace_CatastrophicPatternKeepsNothing + Expect.EndCase "SearchGivesUpTests.Replace_CatastrophicPatternKeepsNothing", Err.Number, Err.Description Expect.StartCase Err.Clear - SearchGivesUpTests.Split_CatastrophicPatternSplitsAtMatchesFound - Expect.EndCase "SearchGivesUpTests.Split_CatastrophicPatternSplitsAtMatchesFound", Err.Number, Err.Description + SearchGivesUpTests.Split_CatastrophicPatternKeepsNothing + Expect.EndCase "SearchGivesUpTests.Split_CatastrophicPatternKeepsNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + SearchGivesUpTests.SearchStepLimit_RaisesTheCeiling + Expect.EndCase "SearchGivesUpTests.SearchStepLimit_RaisesTheCeiling", Err.Number, Err.Description Expect.StartCase Err.Clear SearchGivesUpTests.Search_LongMatchOfAStarIsNotCut diff --git a/test/package-tests/tbRegExp/Sources/SearchAbortedTests.twin b/test/package-tests/tbRegExp/Sources/SearchAbortedTests.twin deleted file mode 100644 index b81b661a..00000000 --- a/test/package-tests/tbRegExp/Sources/SearchAbortedTests.twin +++ /dev/null @@ -1,27 +0,0 @@ -' tbRegExp: RegExp.SearchAborted for a pattern that is not catastrophic. A search that only has to -' try each start position finishes, however long the string; SearchAborted is for a search that -' backtracks without end (see SearchGivesUpTests). -[TestFixture] -Module SearchAbortedTests - - [TestCase] - Public Sub SearchAborted_DigitsWithoutXFinishAt2000() - Expect.Begin - Dim re As New RegExp - re.Pattern = "\d+x" - Expect.IsFalse re.Test(String$(2000, "1")), "Test of \d+x on 2,000 digits and no x" - Expect.IsFalse re.SearchAborted, "SearchAborted after Test of \d+x on 2,000 digits and no x" - Expect.Done - End Sub - - [TestCase] - Public Sub SearchAborted_DigitsWithoutXFinishAt5000() - Expect.Begin - Dim re As New RegExp - re.Pattern = "\d+x" - Expect.IsFalse re.Test(String$(5000, "1")), "Test of \d+x on 5,000 digits and no x" - Expect.IsFalse re.SearchAborted, "SearchAborted after Test of \d+x on 5,000 digits and no x" - Expect.Done - End Sub - -End Module diff --git a/test/package-tests/tbRegExp/Sources/SearchFinishesTests.twin b/test/package-tests/tbRegExp/Sources/SearchFinishesTests.twin new file mode 100644 index 00000000..5c3a5b47 --- /dev/null +++ b/test/package-tests/tbRegExp/Sources/SearchFinishesTests.twin @@ -0,0 +1,39 @@ +' tbRegExp: a quadratic search finishes. The step budget scales with the SQUARE of the input +' length, because ordinary backtracking does: '\d+x' over n digits with no x costs about 2.25*n^2 +' steps, since \d+ eats to the end from every start position and backtracks the whole way. A +' budget linear in n loses to that for large enough n whatever constant is chosen, so only the +' crossover moves. Only a pattern whose cost grows FASTER than n^2 is stopped - see +' SearchGivesUpTests. +' +' (Renamed from SearchAbortedTests: RegExp.SearchAborted no longer exists. Running out of budget +' raises, so there is no silently-returned value left for a flag to describe.) +[TestFixture] +Module SearchFinishesTests + + [TestCase] + Public Sub Search_DigitsWithoutXFinishAt2000() + Expect.Begin + Dim re As New RegExp + re.Pattern = "\d+x" + Err.Clear + Expect.IsFalse re.Test(String$(2000, "1")), "Test of \d+x on 2,000 digits and no x" + Expect.AreEqual 0, Err.Number, "Err.Number after Test of \d+x on 2,000 digits" + Expect.Done + End Sub + + ' The case the linear budget failed. It got 5000 * 5000 = 25,000,000 steps against the + ' ~56,000,000 this costs, and gave up. The quadratic budget allows 10 * 5000^2, capped at + ' 100,000,000, so it now completes - and the old 50,000,000 cap was itself below what this + ' needs, which is why raising the per-character constant alone could never have fixed it. + [TestCase] + Public Sub Search_DigitsWithoutXFinishAt5000() + Expect.Begin + Dim re As New RegExp + re.Pattern = "\d+x" + Err.Clear + Expect.IsFalse re.Test(String$(5000, "1")), "Test of \d+x on 5,000 digits and no x" + Expect.AreEqual 0, Err.Number, "Err.Number after Test of \d+x on 5,000 digits" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/tbRegExp/Sources/SearchGivesUpTests.twin b/test/package-tests/tbRegExp/Sources/SearchGivesUpTests.twin index a255ceac..b93550f5 100644 --- a/test/package-tests/tbRegExp/Sources/SearchGivesUpTests.twin +++ b/test/package-tests/tbRegExp/Sources/SearchGivesUpTests.twin @@ -1,57 +1,125 @@ -' tbRegExp: a search that backtracks without end, such as (a+)+b on thirty a, gives up when its -' step budget runs out instead of hanging, as VBScript's RegExp does. It raises nothing, returns -' what was found before, and RegExp.SearchAborted says so. A long match is not cut short. +' tbRegExp: a search whose cost grows faster than the square of the input - (a+)+b on thirty a - +' is stopped by its step budget instead of hanging, as VBScript's RegExp does. It RAISES. Running +' out of budget means the answer is not known, so nothing is returned and no partial result is +' kept: reporting "no match" would assert something the search never established. The error says +' what to try, and names SearchStepLimit when raising the limit could plausibly help. A long match +' is not cut short. [TestFixture] Module SearchGivesUpTests + ' The engine's error enum is private to the package, so this is spelled out here. + Private Const ERR_SEARCH_ABANDONED As Long = vbObjectError + 3016 + [TestCase] - Public Sub Test_CatastrophicPatternGivesUpWithoutError() + Public Sub Test_CatastrophicPatternRaises() Expect.Begin Dim re As New RegExp + Dim num As Long, desc As String re.Pattern = "(a+)+b" + On Error Resume Next + Err.Clear + re.Test String$(30, "a") + num = Err.Number: desc = Err.Description + On Error GoTo 0 Err.Clear - Expect.IsFalse re.SearchAborted, "SearchAborted of an object that has not searched" - Expect.IsFalse re.Test(String$(30, "a")), "Test of (a+)+b on thirty a" - Expect.AreEqual 0, Err.Number, "Err.Number after the search gave up" - Expect.IsTrue re.SearchAborted, "SearchAborted after Test of (a+)+b on thirty a" + Expect.AreEqual ERR_SEARCH_ABANDONED, num, "Err.Number of Test of (a+)+b on thirty a" + Expect.IsTrue InStr(desc, "abandoned") > 0, "the message says the search was abandoned" + ' At thirty characters the budget is the 1,000,000 floor, which already far exceeds the + ' 2,700 a merely quadratic pattern would need - so this one is worse than quadratic, no + ' limit could cover it, and the message says to simplify rather than naming SearchStepLimit. + Expect.IsTrue InStr(desc, "Simplify the pattern") > 0, "the message says to simplify the pattern" Expect.Done End Sub [TestCase] - Public Sub Execute_CatastrophicPatternKeepsMatchesFound() + Public Sub Execute_CatastrophicPatternKeepsNothing() Expect.Begin Dim re As New RegExp + Dim m As MatchCollection + Dim num As Long re.Global = True re.Pattern = "x|(a+)+b" - Dim m As MatchCollection + On Error Resume Next + Err.Clear Set m = re.Execute("xx" & String$(30, "a")) - Expect.AreEqual 2, m.Count, "matches of x|(a+)+b in xx and thirty a" - Expect.IsTrue re.SearchAborted, "SearchAborted after that Execute" + num = Err.Number + On Error GoTo 0 + Err.Clear + Expect.AreEqual ERR_SEARCH_ABANDONED, num, "Err.Number of Execute of x|(a+)+b in xx and thirty a" + ' The two x matches found before the budget ran out are discarded. Handing them back would + ' be a correct prefix presented as a complete answer, which is the silent wrong result this + ' whole design exists to avoid. + Expect.IsTrue m Is Nothing, "no partial MatchCollection is returned" Expect.Done End Sub [TestCase] - Public Sub Replace_CatastrophicPatternReplacesMatchesFound() + Public Sub Replace_CatastrophicPatternKeepsNothing() Expect.Begin Dim re As New RegExp + Dim s As String + Dim num As Long re.Global = True re.Pattern = "x|(a+)+b" - Expect.AreEqual "--" & String$(30, "a"), re.Replace("xx" & String$(30, "a"), "-"), "Replace of x|(a+)+b in xx and thirty a" - Expect.IsTrue re.SearchAborted, "SearchAborted after that Replace" + On Error Resume Next + Err.Clear + s = re.Replace("xx" & String$(30, "a"), "-") + num = Err.Number + On Error GoTo 0 + Err.Clear + Expect.AreEqual ERR_SEARCH_ABANDONED, num, "Err.Number of Replace of x|(a+)+b in xx and thirty a" + Expect.AreEqual "", s, "no partially replaced string is returned" Expect.Done End Sub [TestCase] - Public Sub Split_CatastrophicPatternSplitsAtMatchesFound() + Public Sub Split_CatastrophicPatternKeepsNothing() Expect.Begin Dim re As New RegExp + Dim c As Collection + Dim num As Long re.Global = True re.Pattern = "x|(?:a+)+b" - Dim c As Collection + On Error Resume Next + Err.Clear Set c = re.Split("xx" & String$(30, "a")) - Expect.AreEqual 3, c.Count, "pieces of xx and thirty a split at x|(?:a+)+b" - Expect.AreEqual String$(30, "a"), c(3), "last piece" - Expect.IsTrue re.SearchAborted, "SearchAborted after that Split" + num = Err.Number + On Error GoTo 0 + Err.Clear + Expect.AreEqual ERR_SEARCH_ABANDONED, num, "Err.Number of Split of xx and thirty a at x|(?:a+)+b" + Expect.IsTrue c Is Nothing, "no partial split is returned" + Expect.Done + End Sub + + ' SearchStepLimit is the recourse when a genuinely quadratic pattern exceeds the cap. Tested + ' here with an explicit low limit rather than by driving the automatic cap, which would mean + ' waiting out two ~25-second searches to prove the same mechanism. + [TestCase] + Public Sub SearchStepLimit_RaisesTheCeiling() + Expect.Begin + Dim re As New RegExp + Dim num As Long, desc As String + re.Pattern = "\d+x" + Expect.AreEqual 0, re.SearchStepLimit, "SearchStepLimit defaults to 0, meaning automatic" + + ' far too few for the ~2,250,000 steps this costs + re.SearchStepLimit = 100000 + On Error Resume Next + Err.Clear + re.Test String$(1000, "1") + num = Err.Number: desc = Err.Description + On Error GoTo 0 + Err.Clear + Expect.AreEqual ERR_SEARCH_ABANDONED, num, "Err.Number with SearchStepLimit set far too low" + ' Here raising the limit genuinely would help, so the message names the property and a + ' figure - the opposite of the (a+)+b case above. + Expect.IsTrue InStr(desc, "SearchStepLimit") > 0, "the message names the property to raise" + + ' raised high enough, the same search completes + re.SearchStepLimit = 3000000 + Err.Clear + Expect.IsFalse re.Test(String$(1000, "1")), "Test of \d+x on 1,000 digits once the limit is raised" + Expect.AreEqual 0, Err.Number, "Err.Number once the limit is raised" Expect.Done End Sub @@ -64,7 +132,6 @@ Module SearchGivesUpTests Set m = re.Execute(String$(1000000, "a")) Expect.AreEqual 1, m.Count, "matches of a* in 1,000,000 a" Expect.AreEqual 1000000, m(0).Length, "length of the match of a* in 1,000,000 a" - Expect.IsFalse re.SearchAborted, "SearchAborted after that Execute" Expect.Done End Sub @@ -77,7 +144,6 @@ Module SearchGivesUpTests Set m = re.Execute(String$(1000000, "a")) Expect.AreEqual 1, m.Count, "matches of ^.*$ in 1,000,000 a" Expect.AreEqual 1000000, m(0).Length, "length of the match of ^.*$ in 1,000,000 a" - Expect.IsFalse re.SearchAborted, "SearchAborted after that Execute" Expect.Done End Sub diff --git a/test/package-tests/tbRegExp/expect.json b/test/package-tests/tbRegExp/expect.json index d796abc1..118b60bc 100644 --- a/test/package-tests/tbRegExp/expect.json +++ b/test/package-tests/tbRegExp/expect.json @@ -1,50 +1,4 @@ { - "version": "1.8.0.0", - "cases": { - "IsValidTests.IsValid_LowCountAboveHigh": { - "fails": "1 of 1 checks failed: IsValid of the pattern a{2,1}, whose lower count is above its upper count: expected False, got True" - }, - "IsValidTests.LastErrorNumber_LowCountAboveHigh": { - "fails": "1 of 1 checks failed: LastErrorNumber of the pattern a{2,1} (invalid quantifier): expected -2147218496, got 0" - }, - "IsValidTests.Test_LowCountAboveHighRaisesCompileError": { - "fails": "2 of 2 checks failed: Err.Number of Test(\"aa\") with the pattern a{2,1}: expected -2147218496, got 3000; Err.Description of Test(\"aa\") with the pattern a{2,1}: expected \"Invalid quantifier: a malformed '{n,m}' count (e.g. '{2,1}', or a number too large).\", got \"The specified print monitor is unknown.\"" - }, - "IsValidTests.Execute_LowCountAboveHighRaisesCompileError": { - "fails": "1 of 1 checks failed: Err.Number of Execute(\"aa\") with the pattern a{2,1}: expected -2147218496, got 3000" - }, - "IsValidTests.Replace_LowCountAboveHighRaisesCompileError": { - "fails": "1 of 1 checks failed: Err.Number of Replace(\"aa\", \"x\") with the pattern a{2,1}: expected -2147218496, got 3000" - }, - "IsValidTests.Split_LowCountAboveHighRaisesCompileError": { - "fails": "1 of 1 checks failed: Err.Number of Split(\"aa\") with the pattern a{2,1}: expected -2147218496, got 3000" - }, - "IsValidTests.IsValid_InvalidPatternLeavesNoError": { - "fails": "1 of 2 checks failed: Err.Number after IsValid compiled the pattern (\\d+: expected 0, got -2147218495" - }, - "IsValidTests.LastError_InvalidPatternLeavesNoError": { - "fails": "1 of 2 checks failed: Err.Number after LastError compiled the pattern (\\d+: expected 0, got -2147218495" - }, - "IsValidTests.LastErrorNumber_InvalidPatternLeavesNoError": { - "fails": "1 of 2 checks failed: Err.Number after LastErrorNumber compiled the pattern (\\d+: expected 0, got -2147218495" - }, - "PatternSyntaxTests.Test_BackreferenceToLaterGroupMatchesNothing": { - "fails": "2 of 2 checks failed: Test of \\1(a) on a (VBScript finds no match): expected False, got True; Test of \\1(a) on aa (VBScript finds no match): expected False, got True" - }, - "PatternSyntaxTests.IsValid_BraceThatStartsNoQuantifier": { - "fails": "3 of 3 checks failed: IsValid of a{ (VBScript reads the brace as text): expected True, got False; IsValid of a{x} (VBScript reads the braces as text): expected True, got False; IsValid of \\p{L} (VBScript reads it as the text p{L}): expected True, got False" - }, - "PatternSyntaxTests.Test_BraceThatStartsNoQuantifierIsText": { - "fails": "2 of 2 checks failed: Err.Number of Test of a{x} on a{x}: expected 0, got -2147218496; Test of a{x} on a{x} (VBScript reads the braces as text): expected True, got False" - }, - "PatternSyntaxTests.Test_CommaAndMaximumOnlyIsText": { - "fails": "2 of 3 checks failed: first match of a{,2} in a{,2} (VBScript reads {,2} as text): expected \"a{,2}\", got \"a\"; Test of a{,2} on aa (VBScript reads {,2} as text): expected False, got True" - }, - "ReplaceTests.Replace_TildeItemIsCopied": { - "fails": "1 of 1 checks failed: Replace(\"abc\", \"x$~y\") for the pattern a (VBScript copies $~): expected \"x$~ybc\", got \"xybc\"" - }, - "SearchAbortedTests.SearchAborted_DigitsWithoutXFinishAt5000": { - "fails": "1 of 2 checks failed: SearchAborted after Test of \\d+x on 5,000 digits and no x: expected False, got True" - } - } + "version": "1.9.0.0", + "cases": {} } diff --git a/test/package-tests/tbRegExp/tbRegExpTests.twinproj b/test/package-tests/tbRegExp/tbRegExpTests.twinproj new file mode 100644 index 0000000000000000000000000000000000000000..04f9bfde5d687e1f6bf8e20e391e8f89ca544829 GIT binary patch literal 41540 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