' Copyright (c) 2007-2024 Bruce A Henderson ' All rights reserved. ' ' Redistribution and use in source and binary forms, with or without ' modification, are permitted provided that the following conditions are met: ' * Redistributions of source code must retain the above copyright ' notice, this list of conditions and the following disclaimer. ' * Redistributions in binary form must reproduce the above copyright ' notice, this list of conditions and the following disclaimer in the ' documentation and/or other materials provided with the distribution. ' * Neither the name of Bruce A Henderson nor the ' names of its contributors may be used to endorse or promote products ' derived from this software without specific prior written permission. ' ' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY ' EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ' WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ' DISCLAIMED. IN NO EVENT SHALL Bruce A Henderson BE LIABLE FOR ANY ' DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ' (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ' LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ' ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ' (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ' SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ' SuperStrict Rem bbdoc: Regular Expressions End Rem Module Text.RegEx ModuleInfo "Version: 1.12" ModuleInfo "Author: PCRE - Philip Hazel" ModuleInfo "License: BSD" ModuleInfo "Copyright: PCRE - 1997-2021 University of Cambridge" ModuleInfo "Copyright: Wrapper - 2007-2024 Bruce A Henderson" ModuleInfo "History: 1.12" ModuleInfo "History: Updated to PCRE 10.43" ModuleInfo "History: Options are now configured per search. Default options are used if none provided." ModuleInfo "History: Added SetDefaultOptions method." ModuleInfo "History: Options can be ignored in preference for pattern provided options." ModuleInfo "History: 1.11" ModuleInfo "History: Updated to PCRE 10.39" ModuleInfo "History: 1.10" ModuleInfo "History: Updated to PCRE 10.31" ModuleInfo "History: 1.09" ModuleInfo "History: Fixed issue using wrong ovector offset." ModuleInfo "History: 1.08" ModuleInfo "History: Updated to PCRE 10.30" ModuleInfo "History: 1.07" ModuleInfo "History: Updated for 64-bit." ModuleInfo "History: 1.06" ModuleInfo "History: Updated to PCRE 10.00" ModuleInfo "History: Changed TRegExMatch to get data as required, rather than cache it." ModuleInfo "History: Added support for JIT compilation via new options." ModuleInfo "History: Added ByName methods for sub expression retrieval." ModuleInfo "History: 1.05" ModuleInfo "History: Updated to PCRE 8.34" ModuleInfo "History: Changed to use pcre16 functions, which is BlitzMax's native character size. (no more utf8 conversions)" ModuleInfo "History: Don't include the zero-termination character in sub expressions." ModuleInfo "History: 1.04" ModuleInfo "History: Updated to PCRE 8.0" ModuleInfo "History: Fixed offset problems when working with non-ascii text." ModuleInfo "History: Fixed Replace() where loop was overflowing." ModuleInfo "History: Added test_08 for utf-8." ModuleInfo "History: 1.03" ModuleInfo "History: Updated to PCRE 7.4" ModuleInfo "History: 1.02" ModuleInfo "History: Added grable's subquery/replace tweak." ModuleInfo "History: 1.01" ModuleInfo "History: Options now global." ModuleInfo "History: 1.00" ModuleInfo "History: Initial Release. (PCRE 7.0)" ModuleInfo "CC_OPTS: -DHAVE_CONFIG_H -DPCRE2_CODE_UNIT_WIDTH=16" ?macos ModuleInfo "CC_VOPT: osversion|-mmacosx-version-min=11.00" ? Import "common.bmx" Rem bbdoc: Performs #Find and #Replace / #ReplaceAll on strings using Perl Compatible Regular Expressions. End Rem Type TRegEx ' Configures the way in which Regular Expressions are handled. Global defaultOptions:TRegExOptions ' The options to use for this search. Field options:TRegExOptions ' The pattern to search for. Field searchPattern:String ' The replacement pattern string. 'Field replacePattern:String Field lastPattern:String Field lastTarget:String ' pointer to the target string (as a WString) Field TArg:Byte Ptr ' the length of the target string Field targLength:Size_T Field lastEndPos:Size_T ' pointer to the compiled expression Field pcre:Byte Ptr ' pointer to the offsets vector, owned by pcre2 Field offsets:Size_T Ptr ' number of offsets Field sizeOffsets:Int Field matchPtr:Byte Ptr Field compiled:Int = False Method Delete() If TArg Then MemFree(TArg) End If If matchPtr Then pcre2_match_data_free_16(matchPtr) End If If pcre MemFree(pcre) EndIf End Method Rem bbdoc: Creates a new #TRegEx object. about: @searchPattern is the regular expression with which to perform the search. End Rem Method New(searchPattern:String, options:TRegExOptions = Null) Self.searchPattern = searchPattern If options Then Self.options = options Else If Not defaultOptions Then defaultOptions = New TRegExOptions End If Self.options = defaultOptions End If End Method Rem bbdoc: Creates a new #TRegEx object. about: @searchPattern is the regular expression with which to perform the search. End Rem Function Create:TRegEx(searchPattern:String, options:TRegExOptions = Null) Return New TRegEx(searchPattern, options) End Function Rem bbdoc: Sets the default options for all new #TRegEx objects. about: This is useful if you want to set the options once and use them for all searches. End Rem Function SetDefaultOptions(options:TRegExOptions) defaultOptions = options End Function Rem bbdoc: Replaces all occurances of the search Pattern with @replaceWith on @target, from @startPos. returns: The newly replaced string. about: Doesn't affect the original @target contents. End Rem Method ReplaceAll:String(target:String, replaceWith:String, startPos:Size_T = 0) If Not options Then options = New TRegExOptions End If ' remember the current setting Local oldOpt:Int = options.replaceAllMatches ' enable global replace options.replaceAllMatches = True ' let Replace do all the work Local s:String = Replace(target, replaceWith, startPos) ' put back the "current" setting options.replaceAllMatches = oldOpt Return s End Method Rem bbdoc: Replaces the first occurance of the search Pattern with @replaceWith on @target, from @startPos. returns: The newly replaced string. about: To access a specific subquery during the replace, you can use the \n syntax in @replaceWith, where \0 refers to the whole match, and \1 refers to the first subquery/group, and so on. (see test_07 for an example).

Doesn't affect the original @target contents.

End Rem Method Replace:String(target:String, replaceWith:String, startPos:Size_T = 0) If Not options Then options = New TRegExOptions End If Local globalReplace:Int = options.replaceAllMatches ' the search pattern has changed ! ' recompile ' this only happens very occasionally... (probably ;-) 'If lastPattern <> searchPattern Or Not pcre Then If Not compiled Then init() End If Local retString:String ' this is a new target... If target <> lastTarget Then lastTarget = target If TArg Then MemFree(TArg) End If TArg = target.toWString() targLength = target.length End If ' initial search... Local result:Int = pcre2_match_16(pcre, TArg, targLength, startPos, getExecOpt(), matchPtr, Null) ' if there wasn't an error... process the match (even for no-match) While result >= 0 Or result = PCRE2_ERROR_NOMATCH sizeOffsets = pcre2_get_ovector_count_16(matchPtr) offsets = pcre2_get_ovector_pointer_16(matchPtr) Local replaceStr:String = replaceWith Local ofs:Size_T Ptr = offsets For Local i:Int = 0 Until result Local idx:Int = i * 2 replaceStr = replaceStr.Replace( "\" + i, lastTarget[ofs[idx]..ofs[idx+1]]) Next If result > 0 Then ' add text so far, and the replacement retString:+ lastTarget[startPos..offsets[0]] + replaceStr Else ' search finished. Fill to the end retString:+ lastTarget[startPos..targLength] Exit End If ' set start to the end of the last match position startPos = offsets[1] ' only doing the first replace? Then we can exit now... If Not globalReplace Then ' all done. Fill to the end retString:+ lastTarget[startPos..targLength] Exit End If ' find the next match result = pcre2_match_16(pcre, TArg, targLength, startPos, getExecOpt(), matchPtr, Null) Wend Return retString ' convert back to max string End Method Rem bbdoc: Performs a search on the given @target from @startPos, using the search Pattern. returns: A #TRegExMatch object or Null if no matches found. about: If @target is not set, the search will use the previous @target. You will want to set @target the first time this method is called.
If you call this method with no parameters it will start the search from the end of the last search, effectively iterating through the target string. End Rem Method Find:TRegExMatch(target:String = Null) If Not options Then options = New TRegExOptions End If ' the search pattern has changed ! ' recompile ' this only happens very occasionally... (probably ;-) 'If lastPattern <> searchPattern Or Not pcre Then If Not compiled Then init() End If Local startPos:Size_T ' no target specified, we are probably performing another search on the original target If Not target Then ' no lastTarget? Not allowed. If Not lastTarget Then Return Null End If startPos = lastEndPos Else ' this is a new target... If target <> lastTarget Then lastTarget = target If TArg Then MemFree(TArg) End If TArg = target.toWString() targLength = target.length End If End If Return DoFind(startPos) End Method Method DoFind:TRegExMatch(startPos:Size_T) Local result:Int = pcre2_match_16(pcre, TArg, targLength, startPos, getExecOpt(), matchPtr, Null) If result >= 0 Then sizeOffsets = pcre2_get_ovector_count_16(matchPtr) offsets = pcre2_get_ovector_pointer_16(matchPtr) Local match:TRegExMatch = New TRegExMatch(pcre, matchPtr) lastEndPos = offsets[1] Return match Else ' no point raising an exception when nothing found... we can just return a null object If result = PCRE2_ERROR_NOMATCH Then Return Null End If ' there was an error of some kind... throw it! Throw TRegExException.Raise(result) End If End Method Rem bbdoc: Performs a search on the given @target from @startPos, using the search Pattern. returns: A #TRegExMatch object or Null if no matches found. End Rem Method Find:TRegExMatch(target:String, startPos:Size_T) If Not options Then options = New TRegExOptions End If ' the search pattern has changed ! ' recompile ' this only happens very occasionally... (probably ;-) 'If lastPattern <> searchPattern Or Not pcre Then If Not compiled Then init() End If ' no target specified, we are probably performing another search on the original target If Not target Then ' no lastTarget? Not allowed. If Not lastTarget Then Return Null End If ' no startPos? then we'll start from the end of the last search point If startPos < 0 Then startPos = lastEndPos End If Else ' this is a new target... If target <> lastTarget Then lastTarget = target If TArg Then MemFree(TArg) End If TArg = target.toWString() targLength = target.length End If End If ' set the startPos to 0 if not already set If startPos < 0 Then startPos = 0 End If Return DoFind(startPos) End Method ' resets and recompiles the regular expression Method init() lastPattern = searchPattern Local pat:Short Ptr = lastPattern.ToWString() Local errorcode:Int Local erroffset:Size_T Local bptr:Byte Ptr = pcre2_compile_16(pat, Size_T(lastPattern.length), getCompileOpt(), Varptr errorcode, .. Varptr erroffset, Null) MemFree(pat) If bptr Then If pcre MemFree(pcre) EndIf pcre = bptr Else Local buffer:Short[256] pcre2_get_error_message_16(errorcode, buffer, 256) Throw TRegExException.Raise(-99, String.fromWString(buffer)) End If Local jitOptions:Int = getJITOpt() If jitOptions Then Local result:Int = pcre2_jit_compile_16(pcre, jitOptions) End If If matchPtr Then pcre2_match_data_free_16(matchPtr) End If matchPtr = pcre2_match_data_create_from_pattern_16(pcre, Null) compiled = True End Method ' possible options for compiling Method getCompileOpt:Int() Local opt:Int = PCRE2_UTF If options.onlyPatternOptions Then Return opt End If If Not options.caseSensitive Then opt:| PCRE2_CASELESS End If If options.dotMatchAll Then opt:| PCRE2_DOTALL End If If Not options.greedy Then opt:| PCRE2_UNGREEDY End If Select options.lineEndType Case 1 opt:| PCRE2_NEWLINE_CR Case 2 opt:| PCRE2_NEWLINE_LF Case 3 opt:| PCRE2_NEWLINE_CRLF Default opt:| PCRE2_NEWLINE_ANY End Select If Not options.matchEmpty Then opt:| PCRE2_NOTEMPTY End If If options.targetIsMultiline Then opt:| PCRE2_MULTILINE End If If options.dollarEndOnly Then opt:| PCRE2_DOLLAR_ENDONLY End If If options.extended Then opt:| PCRE2_EXTENDED End If Return opt End Method ' possible options for execution Method getExecOpt:Int() Local opt:Int If options.onlyPatternOptions Then Return opt End If Select options.lineEndType Case 1 opt:| PCRE2_NEWLINE_CR Case 2 opt:| PCRE2_NEWLINE_LF Case 3 opt:| PCRE2_NEWLINE_CRLF Default opt:| PCRE2_NEWLINE_ANY End Select If Not options.matchEmpty Then opt:| PCRE2_NOTEMPTY End If If Not options.stringIsLineBeginning Then opt:| PCRE2_NOTBOL End If If Not options.stringIsLineEnding Then opt:| PCRE2_NOTEOL End If Return opt End Method ' possible options for jit Method getJITOpt:Int() Local opt:Int If options.onlyPatternOptions Then Return opt End If If options.jitComplete Then opt :| PCRE2_JIT_COMPLETE End If If options.jitPartialSoft Then opt :| PCRE2_JIT_PARTIAL_SOFT End If If options.jitPartialHard Then opt :| PCRE2_JIT_PARTIAL_HARD End If End Method Rem bbdoc: Returns which optional features are available. End Rem Function Config:Int(what:Int, where_:Int Var) If what <> PCRE2_CONFIG_UNICODE_VERSION And what <> PCRE2_CONFIG_VERSION Then Return pcre2_config_16(what, Varptr where_) End If Return PCRE2_ERROR_BADOPTION End Function End Type Rem bbdoc: Used to extract the matched string when doing a search with regular expressions. End Rem Type TRegExMatch Private Field pcre:Byte Ptr Field matchPtr:Byte Ptr Field count:UInt Method New(pcre:Byte Ptr, matchPtr:Byte Ptr) Self.pcre = pcre Self.matchPtr = matchPtr Self.count = pcre2_get_ovector_count_16(matchPtr) End Method Public Rem bbdoc: Returns the number of subexpressions as a result of the search. End Rem Method SubCount:Int() Return count End Method Rem bbdoc: Returns the subexpression for @matchNumber. returns: The matched string, the subexpression string, or "" if @matchNumber is out of range. about: For expressions with no subpattern groups, this method can be used without a parameter to return the matched string. End Rem Method SubExp:String(matchNumber:Int = 0) Local _subExpr:String If matchNumber >= 0 And matchNumber < count Then Local sPtr:Short Ptr Local sLen:Size_T Local result:Int = pcre2_substring_get_bynumber_16(matchPtr, matchNumber, Varptr sPtr, Varptr sLen) If Not result Then _subExpr = String.FromShorts(sPtr, Int(sLen)) pcre2_substring_free_16(sPtr) End If End If Return _subExpr End Method Rem bbdoc: Returns the start position for subexpression @matchNumber. returns: The start position, or -1 if @matchNumber is out of range. about: For expressions with no subpattern groups, this method can be used without a parameter to return the start position of the matched string. End Rem Method SubStart:Int(matchNumber:Int = 0) If matchNumber >= 0 And matchNumber < count Then Local offsets:Size_T Ptr = pcre2_get_ovector_pointer_16(matchPtr) Return offsets[matchNumber] End If Return -1 End Method Rem bbdoc: Returns the end position for subexpression @matchNumber. returns: The end position, or -1 if @matchNumber is out of range. about: For expressions with no subpattern groups, this method can be used without a parameter to return the end position of the matched string. End Rem Method SubEnd:Int(matchNumber:Int = 0) If matchNumber >= 0 And matchNumber < count Then Local offsets:Size_T Ptr = pcre2_get_ovector_pointer_16(matchPtr) Return offsets[matchNumber + 1] - 1 End If Return -1 End Method Rem bbdoc: Returns the subexpression for the given @name. returns: The matched string, the subexpression string, or "" if @matchNumber is out of range. End Rem Method SubExp:String(name:String) Return SubExpByName(name) End Method Rem bbdoc: Returns the subexpression for the given @name. returns: The matched string, the subexpression string, or "" if @matchNumber is out of range. End Rem Method SubExpByName:String(name:String) Local _subExpr:String If name Then Local sPtr:Short Ptr Local sLen:Size_T Local n:Short Ptr = name.ToWString() Local result:Int = pcre2_substring_get_byname_16(matchPtr, n, Varptr sPtr, Varptr sLen) MemFree(n) If Not result Then _subExpr = String.FromShorts(sPtr, Int(sLen)) pcre2_substring_free_16(sPtr) End If End If Return _subExpr End Method Rem bbdoc: Returns the index of the subexpression for the given @name. End Rem Method SubIndex:Int(name:String) Return SubIndexByName(name) End Method Rem bbdoc: Returns the index of the subexpression for the given @name. End Rem Method SubIndexByName:Int(name:String) If name Then Local n:Short Ptr = name.ToWString() Local index:Int = pcre2_substring_number_from_name_16(pcre, n) MemFree(n) If index >= 0 Then Return index End If End If Return -1 End Method Rem bbdoc: Returns the start position of the subexpression for the given @name. End Rem Method SubStart:Int(name:String) Return SubStartByName(name) End Method Rem bbdoc: Returns the start position of the subexpression for the given @name. End Rem Method SubStartByName:Int(name:String) If name Then Local n:Short Ptr = name.ToWString() Local index:Int = pcre2_substring_number_from_name_16(pcre, n) MemFree(n) If index >= 0 Then Local offsets:Size_T Ptr = pcre2_get_ovector_pointer_16(matchPtr) Return offsets[index] End If End If Return -1 End Method Rem bbdoc: Returns the end position of the subexpression for the given @name. End Rem Method SubEnd:Int(name:String) Return SubEndByName(name) End Method Rem bbdoc: Returns the end position of the subexpression for the given @name. End Rem Method SubEndByName:Int(name:String) If name Then Local n:Short Ptr = name.ToWString() Local index:Int = pcre2_substring_number_from_name_16(pcre, n) MemFree(n) If index >= 0 Then Local offsets:Size_T Ptr = pcre2_get_ovector_pointer_16(matchPtr) Return offsets[index + 1] - 1 End If End If Return -1 End Method End Type Rem bbdoc: Specifies options used when performing searches. End Rem Type TRegExOptions Rem bbdoc: Ignore other options and use only the pattern's options, like case sensitivity, etc. End Rem Field onlyPatternOptions:Int = False Rem bbdoc: Whether matches are case sensitive. End Rem Field caseSensitive:Int = False Rem bbdoc: Allow dot (period) to match new lines as well as everything else. about: False indicates dot doesn't match new lines. End Rem Field dotMatchAll:Int = False Rem bbdoc: Greedy matches everything from the beginning of the first delimeter to the end of the last delimiter, and everything in between. about: End Rem Field greedy:Int = True Rem bbdoc: Determines how new lines are interpreted. about: End Rem Field lineEndType:Int = 0 Rem bbdoc: Allow patterns to match empty strings. End Rem Field matchEmpty:Int = True 'Rem 'bbdoc: Indicates whether all occurances of matches will be replaced. 'about: Only applicable during a Replace. 'End Rem Field replaceAllMatches:Int = False Rem bbdoc: Count the beginning of a string as the beginning of a line. End Rem Field stringIsLineBeginning:Int = True Rem bbdoc: Count the end of a string as the end of a line. End Rem Field stringIsLineEnding:Int = True Rem bbdoc: Matches internal new lines against ^ and $. about: Set to false to ignore internal new lines. End Rem Field targetIsMultiline:Int = True Rem bbdoc: Dollar ($) matches newline at end. about: Set to True for dollar to only match the end of the string, otherwise matches a newline before the end of the string. End Rem Field dollarEndOnly:Int = False Rem bbdoc: Ignore whitespace and # comments. about: When set to True, whitespace in the pattern (other than in a character class) and characters between a # outside a character class and the next newline are ignored.
An escaping backslash can be used to include a whitespace or # character as part of the pattern. End Rem Field extended:Int = False Rem bbdoc: Compile code for full matching. about: When set to True, the JIT compiler is enabled. End Rem Field jitComplete:Int = False Rem bbdoc: Compile code For soft partial matching. about: When set to True, the JIT compiler is enabled. End Rem Field jitPartialSoft:Int = False Rem bbdoc: Compile code for hard partial matching. about: When set to True, the JIT compiler is enabled. End Rem Field jitPartialHard:Int = False End Type Rem bbdoc: A Regular Expression exception. about: This can be thrown either during regular expression compilation (-99) or during a search (-1 to -23). End Rem Type TRegExException Rem bbdoc: The type of error thrown. about: -99 is a regular expression compile error. Read #message for details.
-1 to -23 is thrown during a search. Read #message for details. End Rem Field num:Int Rem bbdoc: The error text. End Rem Field message:String Function Raise:TRegExException(num:Int, message:String = Null) Local this:TRegExException = New TRegExException If message Then this.message = message Else this.message = this.getFromNum(num) End If this.num = num Return this End Function Rem bbdoc: Returns the exception as a String. End Rem Method toString:String() Return "( " + num + " ) " + message End Method Method getFromNum:String(err:Int) Select err Case -1 Return "No Match" Case -2 Return "PCRE2_ERROR_PARTIAL" Case -3 Return "PCRE2_ERROR_UTF8_ERR1" Case -4 Return "PCRE2_ERROR_UTF8_ERR2" Case -5 Return "PCRE2_ERROR_UTF8_ERR3" Case -6 Return "PCRE2_ERROR_UTF8_ERR4" Case -7 Return "PCRE2_ERROR_UTF8_ERR5" Case -8 Return "PCRE2_ERROR_UTF8_ERR6" Case -9 Return "PCRE2_ERROR_UTF8_ERR7" Case -10 Return "PCRE2_ERROR_UTF8_ERR8" Case -11 Return "PCRE2_ERROR_UTF8_ERR9" Case -12 Return "PCRE2_ERROR_UTF8_ERR10" Case -13 Return "PCRE2_ERROR_UTF8_ERR11" Case -14 Return "PCRE2_ERROR_UTF8_ERR12" Case -15 Return "PCRE2_ERROR_UTF8_ERR13" Case -16 Return "PCRE2_ERROR_UTF8_ERR14" Case -17 Return "PCRE2_ERROR_UTF8_ERR15" Case -18 Return "PCRE2_ERROR_UTF8_ERR16" Case -19 Return "PCRE2_ERROR_UTF8_ERR17" Case -20 Return "PCRE2_ERROR_UTF8_ERR18" Case -21 Return "PCRE2_ERROR_UTF8_ERR19" Case -22 Return "PCRE2_ERROR_UTF8_ERR20" Case -23 Return "PCRE2_ERROR_UTF8_ERR21" Case -24 Return "PCRE2_ERROR_UTF16_ERR1" Case -25 Return "PCRE2_ERROR_UTF16_ERR2" Case -26 Return "PCRE2_ERROR_UTF16_ERR3" Case -27 Return "PCRE2_ERROR_UTF32_ERR1" Case -28 Return "PCRE2_ERROR_UTF32_ERR2" Case -29 Return "PCRE2_ERROR_BADDATA" Case -30 Return "PCRE2_ERROR_BADLENGTH" Case -31 Return "PCRE2_ERROR_BADMAGIC" Case -32 Return "PCRE2_ERROR_BADMODE" Case -33 Return "PCRE2_ERROR_BADOFFSET" Case -34 Return "PCRE2_ERROR_BADOPTION" Case -35 Return "PCRE2_ERROR_BADREPLACEMENT" Case -36 Return "PCRE2_ERROR_BADUTFOFFSET" Case -37 Return "PCRE2_ERROR_CALLOUT" Case -38 Return "PCRE2_ERROR_DFA_BADRESTART" Case -39 Return "PCRE2_ERROR_DFA_RECURSE" Case -40 Return "PCRE2_ERROR_DFA_UCOND" Case -41 Return "PCRE2_ERROR_DFA_UFUNC" Case -42 Return "PCRE2_ERROR_DFA_UITEM" Case -43 Return "PCRE2_ERROR_DFA_WSSIZE" Case -44 Return "PCRE2_ERROR_INTERNAL" Case -45 Return "PCRE2_ERROR_JIT_BADOPTION" Case -46 Return "PCRE2_ERROR_JIT_STACKLIMIT" Case -47 Return "PCRE2_ERROR_MATCHLIMIT" Case -48 Return "PCRE2_ERROR_NOMEMORY" Case -49 Return "PCRE2_ERROR_NOSUBSTRING" Case -50 Return "PCRE2_ERROR_NOUNIQUESUBSTRING" Case -51 Return "PCRE2_ERROR_NULL" Case -52 Return "PCRE2_ERROR_RECURSELOOP" Case -53 Return "PCRE2_ERROR_RECURSIONLIMIT" Case -54 Return "PCRE2_ERROR_UNAVAILABLE" Case -55 Return "PCRE2_ERROR_UNSET" End Select End Method End Type