├── .gitignore ├── LICENSE ├── README.md ├── fpm.toml ├── project ├── Makefile ├── fortran-regex.cbp ├── fortran-regex.depend ├── fortran-regex.layout ├── main.f90 ├── regex_module.mod ├── regex_test_1.mod ├── regex_test_2.mod └── regex_testdata.mod ├── src └── regex.f90 └── test ├── results.txt ├── test_1.f90 ├── test_2.f90 ├── test_2_data.txt ├── test_m_regex.f90 └── tests.f90 /.gitignore: -------------------------------------------------------------------------------- 1 | *.mod 2 | project/*.mod 3 | project/regex_module.mod 4 | project/regex_test_1.mod 5 | *.mod 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Federico Perini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### fortran-regex 2 | 3 | Fortran-regex is a Modern Fortran port of the [tiny-regex-c](https://github.com/kokke/tiny-regex-c) library for regular expressions. It is based on the original C implementation, but the API is modelled in Fortran style, which is similar to the intrinsic `index` function. 4 | 5 | ### API 6 | 7 | The main API is modelled around Fortran's `index` intrinsic function (which performs a simple search for a substring within a string): 8 | 9 | ```fortran 10 | ! Perform regex on a character string 11 | result = REGEX(string, pattern [, length] [, [back]]) 12 | ``` 13 | - If the pattern command is invalid, `result = -1`. 14 | - If no substrings with the given pattern are found, with a valid pattern `result = 0`. This is also returned if the string has zero length, and that is an acceptable answer for the input pattern. 15 | - Otherwise, if the pattern was found `result > 0` equal to the leftmost location inside the string where the pattern can be found. If the `back` keyword is provided and `.true.`, the location of the rightmost occurrence is returned instead. `length` returns the number of consecutive characters that match this pattern 16 | 17 | ### Arguments 18 | 19 | | Argument | Type | Intent | Description | 20 | | --- | --- | --- | --- | 21 | | `string` | `character(*)` | `in` | The input text | 22 | | `pattern` | `character(*)` | `in` | The regex command | 23 | | `length` | `integer` | `out` (optional) | Length of the matched pattern | 24 | | `back` | `logical` | `in` (optional) | If the BACK argument is present and `.true.`, the return value is the start of the last occurrence rather than the first. | 25 | 26 | 27 | 28 | ### Object-oriented interface 29 | 30 | One can also parse a regex pattern into a `type(regex_pattern)` structure, and use that instead of a string pattern. I have no idea why this should be useful, but at least it's given with a consistent interface 31 | 32 | ### Overview 33 | 34 | The original tiny-regex-c code has been significantly refactored, to: 35 | 36 | * Remove all references to `NULL` character string termination, and replace them with Fortran's string intrinsics (`len`, `len_trim`, etc.) 37 | * Remove all C escaped characters (`\n`, `\t`, etc), replace with Fortran syntax. 38 | * Even in presence of strings, use `pure` `elemental` functions wherever possible 39 | * It is a standalone module that has no external dependencies besides compiler modules. 40 | 41 | ### Example programs 42 | 43 | ```fortran 44 | ! Demonstrate use of regex 45 | program test_regex 46 | use regex_module 47 | implicit none 48 | 49 | integer :: idx,ln 50 | character(*), parameter :: text = 'table football' 51 | 52 | idx = REGEX(string=text,pattern='foo*',length=ln); 53 | 54 | ! Prints "foo" 55 | print *, text(idx:idx+ln-1) 56 | 57 | end program 58 | ``` 59 | 60 | 61 | ```fortran 62 | ! Demonstrate use of object-oriented interface 63 | program test_regex 64 | use regex_module 65 | implicit none 66 | 67 | integer :: idx,ln 68 | character(*), parameter :: text = 'table football' 69 | type(regex_pattern) :: re 70 | 71 | ! Parse pattern into a regex structure 72 | re = parse_pattern('foo*') 73 | 74 | idx = REGEX(string=text,pattern=re,length=ln); 75 | 76 | ! Prints "foo" 77 | print *, text(idx:idx+ln-1) 78 | 79 | end program 80 | ``` 81 | 82 | ### To do list 83 | 84 | - [X] Add a `BACK` optional keyword to return the last instance instead of the first. 85 | - [ ] Option to return ALL instances as an array, instead of the first/last one only. 86 | - [X] Replace fixed-size static storage with allocatable character strings (slower?) 87 | 88 | ### Reporting problems 89 | 90 | Please report any problems! It is appreciated. The original C library had hacks to account for the fact that several special characters are read in with escaped sequences, which partially collides with the escaped sequence options in regex. So, expect the current API to be still a bit rough around the edges. 91 | 92 | ### License 93 | 94 | fortran-regex is released under the MIT license. The code it's based upon is in the public domain. 95 | -------------------------------------------------------------------------------- /fpm.toml: -------------------------------------------------------------------------------- 1 | name = "fortran-regex" 2 | author = "Federico Perini" 3 | copyright = "Copyright (c) 2022-2023, Federico Perini" 4 | version = "1.1.2" 5 | license = "MIT" 6 | description = "Fortran regex library" 7 | homepage = "https://github.com/perazz/fortran-regex" 8 | keywords = ["regex","regular expressions","strings"] 9 | 10 | [build] 11 | auto-executables = false 12 | auto-examples = false 13 | auto-tests = true 14 | module-naming = "regex" 15 | 16 | [library] 17 | source-dir = "src" 18 | include-dir = "src" 19 | 20 | [install] 21 | library = true 22 | 23 | [[test]] 24 | name="regex_tests" 25 | source-dir="test" 26 | main="tests.f90" 27 | 28 | -------------------------------------------------------------------------------- /project/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This Makefile was generated by Code::Blocks IDE. 3 | # 4 | 5 | SRCS_f90d1 = \ 6 | regex.f90 7 | 8 | OBJS_f90d1 = \ 9 | regex.o 10 | 11 | SRC_DIR_f90d1 = ../src/ 12 | OBJS_DIR = obj/Debug/src/ 13 | EXE_DIR = bin/Debug/ 14 | 15 | EXE = fortran-regex 16 | FC = gcc-11 17 | LD = gcc-11 18 | IDIR = 19 | CFLAGS = -Wall -g -J$(OBJS_DIR) $(IDIR) 20 | LFLAGS = 21 | LIBS = 22 | 23 | VPATH = $(SRC_DIR_f90d1):$(OBJS_DIR) 24 | OBJS = $(addprefix $(OBJS_DIR), $(OBJS_f90d1)) 25 | 26 | all : $(EXE) 27 | 28 | $(EXE) : $(OBJS_f90d1) 29 | @mkdir -p $(EXE_DIR) 30 | $(LD) -o $(EXE_DIR)$(EXE) $(OBJS) $(LFLAGS) $(LIBS) 31 | 32 | $(OBJS_f90d1): 33 | @mkdir -p $(OBJS_DIR) 34 | $(FC) $(CFLAGS) -c $(SRC_DIR_f90d1)$(@:.o=.f90) -o $(OBJS_DIR)$@ 35 | 36 | clean : 37 | rm -f $(OBJS_DIR)*.* 38 | rm -f $(EXE_DIR)$(EXE) 39 | 40 | # File dependencies 41 | regex.o: \ 42 | regex.f90 43 | 44 | -------------------------------------------------------------------------------- /project/fortran-regex.cbp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 56 | 57 | -------------------------------------------------------------------------------- /project/fortran-regex.depend: -------------------------------------------------------------------------------- 1 | # depslib dependency file v1.0 2 | 1673295739 source:/Users/federico/code/fortran-regex/src/regex.f90 3 | 4 | 1673295130 source:/Users/federico/code/fortran-regex/test/test_1.f90 5 | 6 | 1671272275 source:/Users/federico/code/fortran-regex/test/testdata.f90 7 | 8 | 1671127283 source:i:\fortran-regex\test\testdata.f90 9 | 10 | 1671525497 source:i:\fortran-regex\src\regex.f90 11 | 12 | 1671525497 source:i:\fortran-regex\test\test_1.f90 13 | 14 | 1671311161 source:/Users/federico/code/fortran-regex/test/test_2.f90 15 | 16 | 1673295146 source:/Users/federico/code/fortran-regex/test/tests.f90 17 | 18 | 1673295146 source:/Users/federico/code/fortran-regex/test/test_m_regex.f90 19 | 20 | 1671525497 source:i:\fortran-regex\test\test_2.f90 21 | 22 | 1671525497 source:i:\fortran-regex\test\test_m_regex.f90 23 | 24 | -------------------------------------------------------------------------------- /project/fortran-regex.layout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /project/main.f90: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /project/regex_module.mod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perazz/fortran-regex/96ab33fe003862a28cec91ddd170ac0e86c26c87/project/regex_module.mod -------------------------------------------------------------------------------- /project/regex_test_1.mod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perazz/fortran-regex/96ab33fe003862a28cec91ddd170ac0e86c26c87/project/regex_test_1.mod -------------------------------------------------------------------------------- /project/regex_test_2.mod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perazz/fortran-regex/96ab33fe003862a28cec91ddd170ac0e86c26c87/project/regex_test_2.mod -------------------------------------------------------------------------------- /project/regex_testdata.mod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perazz/fortran-regex/96ab33fe003862a28cec91ddd170ac0e86c26c87/project/regex_testdata.mod -------------------------------------------------------------------------------- /src/regex.f90: -------------------------------------------------------------------------------- 1 | ! ************************************************************************************************* 2 | ! ____ ___________________ __ 3 | ! / __ \/ ____/ ____/ ____/ |/ / 4 | ! / /_/ / __/ / / __/ __/ | / 5 | ! / _, _/ /___/ /_/ / /___ / | 6 | ! /_/ |_/_____/\____/_____//_/|_| 7 | ! 8 | ! MIT License 9 | ! 10 | ! (C) Federico Perini, 2022 11 | ! A Fortran port of the tiny-regex library. 12 | ! 13 | ! https://github.com/kokke/tiny-regex-c 14 | ! Code inspired by Rob Pike's regex code described in: 15 | ! http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html 16 | ! 17 | ! ************************************************************************************************* 18 | module regex_module 19 | use iso_fortran_env, only: output_unit 20 | implicit none 21 | private 22 | 23 | public :: parse_pattern 24 | public :: check_pattern 25 | public :: regex 26 | 27 | ! Character kind 28 | integer, parameter, public :: RCK = selected_char_kind("ascii") 29 | 30 | logical, parameter, public :: RE_DOT_MATCHES_NEWLINE = .true. ! Define .false. if you DON'T want '.' to match '\r' + '\n' 31 | integer, parameter, public :: MAX_REGEXP_OBJECTS = 512 ! Max number of regex symbols in expression. 32 | integer, parameter, public :: MAX_CHAR_CLASS_LEN = 1024 ! Max length of character-class buffer in. 33 | 34 | ! Turn on verbosity for debugging 35 | logical, parameter :: DEBUG = .false. 36 | 37 | ! Supported patterns 38 | integer, parameter :: UNUSED = 0 39 | integer, parameter :: DOT = 1 ! '.' Dot, matches any character 40 | integer, parameter :: BEGIN_WITH = 2 ! '^' Start anchor, matches beginning of string 41 | integer, parameter :: END_WITH = 3 ! '$' End anchor, matches end of string 42 | integer, parameter :: QUESTIONMARK = 4 ! '?' Question, match zero or one (non-greedy) 43 | integer, parameter :: STAR = 5 ! '*' Asterisk, match zero or more (greedy) 44 | integer, parameter :: PLUS = 6 ! '+' Plus, match one or more (greedy) 45 | integer, parameter :: ATCHAR = 7 ! '[a-zA-Z]' Character ranges, the character set of the ranges { a-z | A-Z } 46 | integer, parameter :: AT_CHAR_CLASS = 8 ! '[abc]' Character class, match if one of {'a', 'b', 'c'} 47 | integer, parameter :: INV_CHAR_CLASS = 9 ! '[^abc]' Inverted class, match if NOT one of {'a', 'b', 'c'} -- NOTE: feature is currently broken! 48 | integer, parameter :: DIGIT = 10 ! '\d' Digits, [0-9] 49 | integer, parameter :: NOT_DIGIT = 11 ! '\D' Non-digits 50 | integer, parameter :: ALPHA = 12 ! '\w' Alphanumeric, [a-zA-Z0-9_] 51 | integer, parameter :: NOT_ALPHA = 13 ! '\W' Non-alphanumeric 52 | integer, parameter :: WHITESPACE = 14 ! '\s' Whitespace, \t \f \r \n \v and spaces 53 | integer, parameter :: NOT_WHITESPACE = 15 ! '\S' Non-whitespace 54 | 55 | character(kind=RCK,len=*), parameter :: types(*) = [ character(len=14) :: "UNUSED", "DOT", "BEGIN", "END", "QUESTIONMARK", & 56 | "STAR", "PLUS", "CHAR", "CHAR_CLASS", "INV_CHAR_CLASS", "DIGIT", "NOT_DIGIT", "ALPHA", "NOT_ALPHA", & 57 | "WHITESPACE", "NOT_WHITESPACE", "BRANCH" ] 58 | 59 | ! Characters 60 | character(kind=RCK,len=*), parameter :: lowercase="abcdefghijklmnopqrstuvwxyz" 61 | character(kind=RCK,len=*), parameter :: uppercase="ABCDEFGHIJKLMNOPQRSTUVWXYZ" 62 | 63 | character(kind=RCK), parameter, public :: UNDERSCORE = "_" 64 | character(kind=RCK), parameter, public :: SPACE = " " 65 | character(kind=RCK), parameter, public :: DASH = "-" 66 | character(kind=RCK), parameter, public :: CNULL = achar( 0,kind=RCK) ! \0 or null character 67 | character(kind=RCK), parameter, public :: NEWLINE = achar(10,kind=RCK) ! \n or line feed 68 | character(kind=RCK), parameter, public :: BACKSPCE = achar( 8,kind=RCK) ! \b or backspace character 69 | character(kind=RCK), parameter, public :: TAB = achar( 9,kind=RCK) ! \t or tabulation character 70 | 71 | ! Regex pattern element 72 | type, public :: regex_token 73 | 74 | integer :: type = UNUSED 75 | 76 | ! Single or multi-character pattern 77 | character(kind=RCK,len=:), allocatable :: ccl 78 | contains 79 | 80 | procedure :: print => print_pattern 81 | procedure :: destroy => pat_destroy 82 | procedure :: match => pat_match 83 | 84 | end type regex_token 85 | 86 | type, public :: regex_pattern 87 | 88 | integer :: n = 0 89 | 90 | type(regex_token), dimension(MAX_REGEXP_OBJECTS) :: pattern 91 | 92 | contains 93 | 94 | procedure :: new => new_from_pattern 95 | procedure :: write => write_pattern 96 | procedure :: nrules 97 | procedure :: destroy 98 | final :: finalize 99 | 100 | end type regex_pattern 101 | 102 | ! Public interface 103 | interface regex 104 | module procedure re_match 105 | module procedure re_match_noback 106 | module procedure re_match_nolength 107 | module procedure re_match_nolength_noback 108 | module procedure re_matchp 109 | module procedure re_matchp_noback 110 | module procedure re_matchp_nolength 111 | module procedure re_matchp_nolength_noback 112 | end interface regex 113 | 114 | ! Override default constructor for ifort bug 115 | interface regex_token 116 | module procedure pat_from_char 117 | end interface regex_token 118 | 119 | 120 | contains 121 | 122 | ! Construct a regex pattern from a single character 123 | elemental type(regex_token) function pat_from_char(type,ccl) result(this) 124 | integer, intent(in) :: type 125 | character(kind=RCK), intent(in) :: ccl 126 | call pat_destroy(this) 127 | this%type = type 128 | allocate(character(len=1,kind=RCK) :: this%ccl) 129 | this%ccl(1:1) = ccl 130 | end function pat_from_char 131 | 132 | ! Check that a pattern matches the expected result 133 | logical function check_pattern(string,pattern,expected) result(success) 134 | character(len=*,kind=RCK), intent(in) :: string 135 | character(len=*,kind=RCK), intent(in) :: pattern 136 | character(len=*,kind=RCK), intent(in) :: expected 137 | 138 | integer :: idx,length 139 | 140 | idx = regex(string,pattern,length) 141 | 142 | if (idx>0) then 143 | success = length==len(expected) 144 | if (success) success = string(idx:idx+length-1)==expected 145 | else 146 | success = len(expected)<=0 147 | end if 148 | 149 | if (DEBUG .and. .not.success) then 150 | print "('[regex] test FAILED: text=',a,' pattern=',a,' index=',i0,' len=',i0)", & 151 | string,pattern,idx,length 152 | stop 1 153 | endif 154 | 155 | end function check_pattern 156 | 157 | ! Clean up a pattern 158 | elemental subroutine pat_destroy(this) 159 | class(regex_token), intent(inout) :: this 160 | integer :: ierr 161 | this%type = UNUSED 162 | deallocate(this%ccl,stat=ierr) 163 | end subroutine pat_destroy 164 | 165 | ! Number of rules in the current pattern 166 | elemental integer function nrules(this) 167 | class(regex_pattern), intent(in) :: this 168 | integer :: i 169 | nrules = 0 170 | do i=1,MAX_REGEXP_OBJECTS 171 | if (this%pattern(i)%type==UNUSED) return 172 | nrules = nrules + 1 173 | end do 174 | end function nrules 175 | 176 | subroutine write_pattern(this,iunit) 177 | class(regex_pattern), intent(in) :: this 178 | integer, optional, intent(in) :: iunit 179 | 180 | integer :: i,u 181 | 182 | if (present(iunit)) then 183 | u = iunit 184 | else 185 | u = output_unit 186 | end if 187 | 188 | do i=1,this%nrules() 189 | write(u,'(a)') this%pattern(i)%print() 190 | end do 191 | 192 | end subroutine write_pattern 193 | 194 | elemental subroutine destroy(this) 195 | class(regex_pattern), intent(inout) :: this 196 | integer :: i 197 | do i=1,MAX_REGEXP_OBJECTS 198 | call this%pattern(i)%destroy() 199 | end do 200 | end subroutine destroy 201 | 202 | subroutine finalize(this) 203 | type(regex_pattern), intent(inout) :: this 204 | integer :: i 205 | do i=1,MAX_REGEXP_OBJECTS 206 | call this%pattern(i)%destroy() 207 | end do 208 | end subroutine finalize 209 | 210 | ! Check that a character matches a dot ("any character") pattern 211 | elemental logical function matchdot(c) 212 | character(kind=RCK), intent(in) :: c 213 | if (RE_DOT_MATCHES_NEWLINE) then 214 | matchdot = .true. 215 | else 216 | matchdot = c/=NEWLINE .and. c/=BACKSPCE 217 | end if 218 | end function matchdot 219 | 220 | elemental logical function ismetachar(c) 221 | character(kind=RCK), intent(in) :: c 222 | ismetachar = index("sSwWdD",c)>0 223 | end function ismetachar 224 | 225 | pure logical function matchmetachar(c, str) 226 | character(kind=RCK), intent(in) :: c 227 | character(kind=RCK,len=*), intent(in) :: str 228 | 229 | select case (str(1:1)) 230 | case ('d'); matchmetachar = isdigit(c) 231 | case ('D'); matchmetachar = .not.isdigit(c) 232 | case ('w'); matchmetachar = isalphanum(c) 233 | case ('W'); matchmetachar = .not.isalphanum(c) 234 | case ('s'); matchmetachar = isspace(c) 235 | case ('S'); matchmetachar = .not.isspace(c) 236 | case default; matchmetachar = c==str(1:1) 237 | end select 238 | end function matchmetachar 239 | 240 | elemental logical function isdigit(c) 241 | character(kind=RCK), intent(in) :: c 242 | isdigit = index("1234567890",c)>0 243 | end function isdigit 244 | 245 | elemental logical function isalpha(c) 246 | character(kind=RCK), intent(in) :: c 247 | isalpha = index(lowercase,c)>0 .or. index(uppercase,c)>0 248 | end function isalpha 249 | 250 | elemental logical function isalphanum(c) 251 | character(kind=RCK), intent(in) :: c 252 | isalphanum = isalpha(c) .or. isdigit(c) .or. c==UNDERSCORE 253 | end function isalphanum 254 | 255 | elemental logical function isspace(c) 256 | character(kind=RCK), intent(in) :: c 257 | isspace = c==SPACE 258 | end function isspace 259 | 260 | ! Match range of the tye 0-9 or 5-7 etc. 261 | elemental logical function matchrange(c,str) 262 | character(kind=RCK), intent(in) :: c 263 | character(kind=RCK,len=*), intent(in) :: str ! the range pattern 264 | 265 | matchrange = len(str)>=3; if (.not.matchrange) return 266 | matchrange = c /= DASH & 267 | .and. str(1:1) /= DASH & 268 | .and. str(2:2) == DASH & 269 | .and. iachar(c)>=iachar(str(1:1)) & 270 | .and. iachar(c)<=iachar(str(3:3)) ! Range (number/letters) is in increasing order 271 | 272 | end function matchrange 273 | 274 | logical function matchcharclass(c,str) result(match) 275 | character(kind=RCK), intent(in) :: c ! The current character 276 | character(kind=RCK,len=*), intent(in) :: str ! The charclass contents 277 | 278 | integer :: i 279 | 280 | match = .false. 281 | i = 0 282 | 283 | ! All characters in the charclass contents 284 | loop: do while (ilen(str) 315 | 316 | else 317 | match = .true. 318 | end if 319 | return 320 | end if 321 | 322 | end do loop 323 | 324 | if (DEBUG) print *, 'charclass: no match on i=',i,' str=',trim(str),' c=',c 325 | 326 | end function matchcharclass 327 | 328 | recursive logical function matchquestion(p, pattern, text, matchlength) 329 | type(regex_token), intent(in) :: p, pattern(:) 330 | character(len=*,kind=RCK), intent(in) :: text 331 | integer, intent(inout) :: matchlength 332 | 333 | matchquestion = .false. 334 | 335 | if (p%type == UNUSED) then 336 | matchquestion = .true. 337 | return 338 | elseif (matchpattern(pattern, text, matchlength)) then 339 | matchquestion = .true. 340 | return 341 | elseif (len(text)>0) then 342 | if (pat_match(p,text) .and. len(text)>1) then 343 | if (matchpattern(pattern,text(2:),matchlength)) then 344 | matchlength = matchlength+1 345 | matchquestion = .true. 346 | return 347 | endif 348 | end if 349 | end if 350 | 351 | end function matchquestion 352 | 353 | recursive logical function matchstar(p, pattern, text, it0, matchlength) 354 | type(regex_token), intent(in) :: p, pattern(:) 355 | character(len=*,kind=RCK), intent(in) :: text 356 | integer, intent(in) :: it0 ! starting point 357 | integer, intent(inout) :: matchlength 358 | 359 | integer :: prelen,it 360 | 361 | if (DEBUG) print *, 'match star, length=',matchlength,' it0=',it0,' lenm=',len(text) 362 | 363 | if (len(text)<=0) then 364 | matchstar = .false. 365 | return 366 | end if 367 | 368 | ! Save input variables 369 | prelen = matchlength 370 | it = it0 371 | 372 | do while (it>0 .and. it<=len(text)) 373 | if (.not.pat_match(p, text(it:))) exit 374 | it = it+1 375 | matchlength = matchlength+1 376 | end do 377 | 378 | do while (it>=it0) 379 | matchstar = matchpattern(pattern, text(it:), matchlength) 380 | it = it-1 381 | if (matchstar) return 382 | matchlength = matchlength-1 383 | end do 384 | 385 | matchlength = prelen 386 | matchstar = .false. 387 | 388 | end function matchstar 389 | 390 | recursive logical function matchplus(p, pattern, text, it0, matchlength) 391 | type(regex_token), intent(in) :: p, pattern(:) 392 | character(len=*,kind=RCK), intent(in) :: text 393 | integer, intent(in) :: it0 394 | integer, intent(inout) :: matchlength 395 | 396 | integer :: it 397 | 398 | if (DEBUG) print *, 'matching PLUS pattern' 399 | 400 | it = it0 401 | do while (it>0 .and. it<=len(text)) 402 | if (.not. pat_match(p, text(it:))) exit 403 | it = it+1 404 | matchlength = matchlength+1 405 | end do 406 | 407 | do while (it>it0) 408 | matchplus = matchpattern(pattern, text(it:), matchlength) 409 | it = it-1 410 | if (matchplus) return 411 | matchlength = matchlength-1 412 | end do 413 | 414 | matchplus = .false. 415 | 416 | end function matchplus 417 | 418 | ! Find matches of the given pattern in the string 419 | integer function re_match(string, pattern, length, back) result(index) 420 | character(*,kind=RCK), intent(in) :: pattern 421 | character(*,kind=RCK), intent(in) :: string 422 | integer, intent(out) :: length 423 | logical, intent(in) :: back 424 | type (regex_pattern) :: command 425 | 426 | command = parse_pattern(pattern) 427 | index = re_matchp(string,command,length,back) 428 | 429 | end function re_match 430 | 431 | ! Find matches of the given pattern in the string 432 | integer function re_match_noback(string, pattern, length) result(index) 433 | character(*,kind=RCK), intent(in) :: pattern 434 | character(*,kind=RCK), intent(in) :: string 435 | integer, intent(out) :: length 436 | type (regex_pattern) :: command 437 | 438 | command = parse_pattern(pattern) 439 | index = re_matchp(string,command,length,.false.) 440 | 441 | end function re_match_noback 442 | 443 | ! Find matches of the given pattern in the string 444 | integer function re_match_nolength(string, pattern, back) result(index) 445 | character(*,kind=RCK), intent(in) :: pattern 446 | character(*,kind=RCK), intent(in) :: string 447 | logical , intent(in) :: back 448 | 449 | type (regex_pattern) :: command 450 | integer :: length 451 | 452 | command = parse_pattern(pattern) 453 | index = re_matchp(string,command,length,back) 454 | 455 | end function re_match_nolength 456 | 457 | ! Find matches of the given pattern in the string 458 | integer function re_match_nolength_noback(string, pattern) result(index) 459 | character(*,kind=RCK), intent(in) :: pattern 460 | character(*,kind=RCK), intent(in) :: string 461 | 462 | type (regex_pattern) :: command 463 | integer :: length 464 | 465 | command = parse_pattern(pattern) 466 | index = re_matchp(string,command,length,.false.) 467 | 468 | end function re_match_nolength_noback 469 | 470 | type(regex_pattern) function parse_pattern(pattern) result(this) 471 | character(*,kind=RCK), intent(in) :: pattern 472 | 473 | call new_from_pattern(this,pattern) 474 | 475 | end function parse_pattern 476 | 477 | subroutine new_from_pattern(this,pattern) 478 | class(regex_pattern), intent(inout) :: this 479 | character(*,kind=RCK), intent(in) :: pattern 480 | 481 | ! Local variables 482 | character(len=MAX_CHAR_CLASS_LEN,kind=RCK) :: ccl_buf ! size of buffer for chars in all char-classes in the expression. */ 483 | integer :: loc,i,j,lenp,lenc 484 | character(kind=RCK) :: c 485 | 486 | ! Initialize class 487 | call this%destroy() 488 | ccl_buf = repeat(SPACE,MAX_CHAR_CLASS_LEN) 489 | 490 | if (DEBUG) print "('[regex] parsing pattern: <',a,'>')", trim(pattern) 491 | 492 | i = 1 ! index in pattern 493 | j = 1 ! index in re-compiled 494 | lenp = len_trim(pattern) 495 | 496 | ! Move along the pattern string 497 | to_the_moon: do while (i<=lenp) 498 | 499 | c = pattern(i:i) 500 | if (DEBUG) print "('[regex] at location ',i0,': <',a,'>')", i, c 501 | 502 | select case (c) 503 | 504 | ! Meta-characters are single-character patterns 505 | case ('^'); this%pattern(j) = regex_token(BEGIN_WITH,c) 506 | case ('$'); this%pattern(j) = regex_token(END_WITH,c) 507 | case ('.'); this%pattern(j) = regex_token(DOT,c) 508 | case ('*'); this%pattern(j) = regex_token(STAR,c) 509 | case ('+'); this%pattern(j) = regex_token(PLUS,c) 510 | case ('?'); this%pattern(j) = regex_token(QUESTIONMARK,c) 511 | 512 | ! Escaped character-classes (\s, \w, ...) 513 | case ('\'); 514 | 515 | ! Parse an escaped character class 516 | if (i=lenp) then 553 | call this%destroy() 554 | return 555 | end if 556 | 557 | else 558 | this%pattern(j)%type = AT_CHAR_CLASS 559 | end if 560 | 561 | ! Remove any escape characters 562 | loc = index(pattern(i+1:),']') 563 | lenc = loc-1 564 | if (loc>0) then 565 | ccl_buf = pattern(i+1:i+loc-1) 566 | i = i+loc 567 | if (DEBUG) print "('[regex] at end of multi-character pattern: ',a)", trim(ccl_buf) 568 | else 569 | ! Incomplete [] pattern 570 | call this%destroy() 571 | return 572 | end if 573 | 574 | ! If there is any escape character(s), just check that the next is nonempty 575 | loc = index(ccl_buf,'\') 576 | if (loc>0) then 577 | if (loc>=len(ccl_buf)) then 578 | ! stop 'incomplete escaped character inside [] pattern' 579 | call this%destroy() 580 | return 581 | end if 582 | if (ccl_buf(loc+1:loc+1)==SPACE) then 583 | ! stop 'empty escaped character inside [] pattern' 584 | call this%destroy() 585 | return 586 | end if 587 | end if 588 | 589 | ! Ensure there are no spaces 590 | 591 | allocate(character(len=lenc,kind=RCK) :: this%pattern(j)%ccl) 592 | this%pattern(j)%ccl = ccl_buf(:lenc) 593 | 594 | case default 595 | 596 | ! Single character 597 | this%pattern(j) = regex_token(ATCHAR,c) 598 | 599 | end select 600 | 601 | if (DEBUG) print "('[regex] added pattern ',i0,': ',a)",j,this%pattern(j)%print() 602 | 603 | ! A pattern was added: move to next 604 | i = i+1 605 | j = j+1 606 | if (j>MAX_REGEXP_OBJECTS) stop 'max regexp reached!' 607 | 608 | end do to_the_moon 609 | 610 | ! Save number of patterns 611 | this%n = j-1 612 | return 613 | 614 | end subroutine new_from_pattern 615 | 616 | function print_pattern(pattern) result(msg) 617 | class(regex_token), intent(in) :: pattern 618 | character(:,kind=RCK), allocatable :: msg 619 | 620 | character(len=MAX_CHAR_CLASS_LEN,kind=RCK) :: buffer 621 | integer :: lt 622 | 623 | write(buffer,1) trim(types(pattern%type+1)),trim(pattern%ccl) 624 | 625 | lt = len_trim(buffer) 626 | allocate(character(len=lt,kind=RCK) :: msg) 627 | if (lt>0) msg(1:lt) = buffer(1:lt) 628 | 629 | 1 format('type=',a,:,1x,'char=',a) 630 | 631 | end function print_pattern 632 | 633 | ! Match a single pattern at the g 634 | recursive logical function pat_match(p, c) result(match) 635 | class(regex_token), intent(in) :: p 636 | character(kind=RCK), intent(in) :: c 637 | 638 | select case (p%type) 639 | case (DOT); match = matchdot(c) 640 | case (AT_CHAR_CLASS); match = matchcharclass(c,p%ccl) 641 | case (INV_CHAR_CLASS); match = .not.matchcharclass(c,p%ccl) 642 | case (DIGIT); match = isdigit(c) 643 | case (NOT_DIGIT); match = .not.isdigit(c) 644 | case (ALPHA); match = isalphanum(c) 645 | case (NOT_ALPHA); match = .not.isalphanum(c) 646 | case (WHITESPACE); match = isspace(c) 647 | case (NOT_WHITESPACE); match = .not.isspace(c) 648 | case default; match = c==p%ccl(1:1) 649 | end select 650 | 651 | if (DEBUG) print "('[regex] current pattern=',a,' at char=',a,' match? ',l1)", p%print(),c,match 652 | 653 | end function pat_match 654 | 655 | integer function re_matchp_nolength(string, pattern, back) result(index) 656 | type(regex_pattern), intent(in) :: pattern 657 | character(len=*,kind=RCK), intent(in) :: string 658 | logical, intent(in) :: back 659 | integer :: matchlength 660 | index = re_matchp(string, pattern, matchlength, back) 661 | end function re_matchp_nolength 662 | 663 | integer function re_matchp_nolength_noback(string, pattern) result(index) 664 | type(regex_pattern), intent(in) :: pattern 665 | character(len=*,kind=RCK), intent(in) :: string 666 | integer :: matchlength 667 | index = re_matchp(string, pattern, matchlength, .false.) 668 | end function re_matchp_nolength_noback 669 | 670 | integer function re_matchp_noback(string, pattern, length) result(index) 671 | type(regex_pattern), intent(in) :: pattern 672 | character(len=*,kind=RCK), intent(in) :: string 673 | integer, intent(out) :: length 674 | index = re_matchp(string, pattern, length, .false.) 675 | end function re_matchp_noback 676 | 677 | 678 | integer function re_matchp(string, pattern, length, back) result(index) 679 | type(regex_pattern), intent(in) :: pattern 680 | character(len=*,kind=RCK), intent(in) :: string 681 | integer, intent(out) :: length 682 | logical, intent(in) :: back 683 | 684 | integer :: first,last,step 685 | 686 | if (pattern%n>0) then 687 | 688 | if (pattern%pattern(1)%type == BEGIN_WITH) then 689 | 690 | ! String must begin with this pattern 691 | length = 0 692 | index = merge(1,0,matchpattern(pattern%pattern(2:), string, length) .and. len(string)>0) 693 | 694 | else 695 | 696 | first = merge(1,len(string),.not.back) 697 | last = merge(1,len(string),back) 698 | step = sign(1,last-first) 699 | 700 | do index=first,last,step 701 | length = 0 702 | if (matchpattern(pattern%pattern,string(index:),length)) goto 1 703 | end do 704 | 705 | index = 0 706 | 707 | end if 708 | 709 | else 710 | 711 | ! On an empty/invalid pattern, return -1 712 | index = -1 713 | 714 | end if 715 | 716 | 1 if (DEBUG) then 717 | if (index==-1) then 718 | print "('[regex] end: empty/invalid regex pattern. ')" 719 | elseif (index==0) then 720 | print "('[regex] end: pattern not found. ')" 721 | else 722 | print "('[regex] end: pattern found at ',i0,': ',a)", index,string(index:) 723 | end if 724 | end if 725 | 726 | end function re_matchp 727 | 728 | 729 | ! Iterative matching 730 | recursive logical function matchpattern(pattern, text, matchlength) result(match) 731 | type(regex_token), intent(in) :: pattern(:) 732 | character(kind=RCK,len=*), intent(in) :: text 733 | integer, intent(inout) :: matchlength 734 | 735 | integer :: pre,ip,it 736 | 737 | pre = matchlength 738 | ip = 1 739 | it = 1 740 | 741 | iterate: do while (ip<=size(pattern)) 742 | 743 | if (pattern(ip)%type == UNUSED .or. pattern(ip+1)%type == QUESTIONMARK) then 744 | 745 | match = matchquestion(pattern(ip),pattern(ip+2:),text(it:),matchlength) 746 | return 747 | 748 | elseif (pattern(ip+1)%type == STAR) then 749 | 750 | match = matchstar(pattern(ip),pattern(ip+2:), text, it, matchlength) 751 | return 752 | 753 | elseif (pattern(ip+1)%type == PLUS) then 754 | 755 | match = matchplus(pattern(ip),pattern(ip+2:), text, it, matchlength) 756 | return 757 | 758 | elseif (pattern(ip)%type == END_WITH .and. pattern(ip+1)%type == UNUSED) then 759 | 760 | if (DEBUG .and. len(text(it:))>0) print *, '[regex] at end: remaining = ',text(it:),' len=',matchlength 761 | 762 | match = it>len(text) 763 | return 764 | 765 | end if 766 | 767 | if (it>len(text)) exit iterate 768 | 769 | matchlength = matchlength+1 770 | 771 | if (DEBUG) print "('[regex] matching ',i0,'-th pattern on chunk <',i0,':',i0,'>')", ip,it,len(text) 772 | if (.not. pat_match(pattern(ip), text(it:it))) exit iterate 773 | ip = ip+1 774 | it = it+1 775 | 776 | end do iterate 777 | 778 | matchlength = pre 779 | match = .false. 780 | return 781 | 782 | end function matchpattern 783 | 784 | 785 | 786 | 787 | end module regex_module 788 | -------------------------------------------------------------------------------- /test/results.txt: -------------------------------------------------------------------------------- 1 | { OK, "\\d", "5", (char*) 1 }, 2 | { OK, "\\w+", "hej", (char*) 3 }, 3 | { OK, "\\s", "\t \n", (char*) 1 }, 4 | { NOK, "\\S", "\t \n", (char*) 0 }, 5 | { OK, "[\\s]", "\t \n", (char*) 1 }, 6 | { NOK, "[\\S]", "\t \n", (char*) 0 }, 7 | { NOK, "\\D", "5", (char*) 0 }, 8 | { NOK, "\\W+", "hej", (char*) 0 }, 9 | { OK, "[0-9]+", "12345", (char*) 5 }, 10 | { OK, "\\D", "hej", (char*) 1 }, 11 | { NOK, "\\d", "hej", (char*) 0 }, 12 | { OK, "[^\\w]", "\\", (char*) 1 }, 13 | { OK, "[\\W]", "\\", (char*) 1 }, 14 | { NOK, "[\\w]", "\\", (char*) 0 }, 15 | { OK, "[^\\d]", "d", (char*) 1 }, 16 | { NOK, "[\\d]", "d", (char*) 0 }, 17 | { NOK, "[^\\D]", "d", (char*) 0 }, 18 | { OK, "[\\D]", "d", (char*) 1 }, 19 | { OK, "^.*\\\\.*$", "c:\\Tools", (char*) 8 }, 20 | { OK, "^[\\+-]*[\\d]+$", "+27", (char*) 3 }, 21 | { OK, "[abc]", "1c2", (char*) 1 }, 22 | { NOK, "[abc]", "1C2", (char*) 0 }, 23 | { OK, "[1-5]+", "0123456789", (char*) 5 }, 24 | { OK, "[.2]", "1C2", (char*) 1 }, 25 | { OK, "a*$", "Xaa", (char*) 2 }, 26 | { OK, "a*$", "Xaa", (char*) 2 }, 27 | { OK, "[a-h]+", "abcdefghxxx", (char*) 8 }, 28 | { NOK, "[a-h]+", "ABCDEFGH", (char*) 0 }, 29 | { OK, "[A-H]+", "ABCDEFGH", (char*) 8 }, 30 | { NOK, "[A-H]+", "abcdefgh", (char*) 0 }, 31 | { OK, "[^\\s]+", "abc def", (char*) 3 }, 32 | { OK, "[^fc]+", "abc def", (char*) 2 }, 33 | { OK, "[^d\\sf]+", "abc def", (char*) 3 }, 34 | { OK, "\n", "abc\ndef", (char*) 1 }, 35 | { OK, "b.\\s*\n", "aa\r\nbb\r\ncc\r\n\r\n",(char*) 4 }, 36 | { OK, ".*c", "abcabc", (char*) 6 }, 37 | { OK, ".+c", "abcabc", (char*) 6 }, 38 | { OK, "[b-z].*", "ab", (char*) 1 }, 39 | { OK, "b[k-z]*", "ab", (char*) 1 }, 40 | { NOK, "[0-9]", " - ", (char*) 0 }, 41 | { OK, "[^0-9]", " - ", (char*) 1 }, 42 | { OK, "0|", "0|", (char*) 2 }, 43 | { NOK, "\\d\\d:\\d\\d:\\d\\d", "0s:00:00", (char*) 0 }, 44 | { NOK, "\\d\\d:\\d\\d:\\d\\d", "000:00", (char*) 0 }, 45 | { NOK, "\\d\\d:\\d\\d:\\d\\d", "00:0000", (char*) 0 }, 46 | { NOK, "\\d\\d:\\d\\d:\\d\\d", "100:0:00", (char*) 0 }, 47 | { NOK, "\\d\\d:\\d\\d:\\d\\d", "00:100:00", (char*) 0 }, 48 | { NOK, "\\d\\d:\\d\\d:\\d\\d", "0:00:100", (char*) 0 }, 49 | { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "0:0:0", (char*) 5 }, 50 | { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "0:00:0", (char*) 6 }, 51 | { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "0:0:00", (char*) 5 }, 52 | { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "00:0:0", (char*) 6 }, 53 | { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "00:00:0", (char*) 7 }, 54 | { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "00:0:00", (char*) 6 }, 55 | { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "0:00:00", (char*) 6 }, 56 | { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "00:00:00", (char*) 7 }, 57 | { OK, "[Hh]ello [Ww]orld\\s*[!]?", "Hello world !", (char*) 12 }, 58 | { OK, "[Hh]ello [Ww]orld\\s*[!]?", "hello world !", (char*) 12 }, 59 | { OK, "[Hh]ello [Ww]orld\\s*[!]?", "Hello World !", (char*) 12 }, 60 | { OK, "[Hh]ello [Ww]orld\\s*[!]?", "Hello world! ", (char*) 11 }, 61 | { OK, "[Hh]ello [Ww]orld\\s*[!]?", "Hello world !", (char*) 13 }, 62 | { OK, "[Hh]ello [Ww]orld\\s*[!]?", "hello World !", (char*) 15 }, 63 | { OK, ".?bar", "real_bar", (char*) 4 }, 64 | { NOK, ".?bar", "real_foo", (char*) 0 }, 65 | { NOK, "X?Y", "Z", (char*) 0 }, 66 | { OK, "[a-z]+\nbreak", "blahblah\nbreak", (char*) 14 }, 67 | { OK, "[a-z\\s]+\nbreak", "bla bla \nbreak", (char*) 14 }, 68 | -------------------------------------------------------------------------------- /test/test_1.f90: -------------------------------------------------------------------------------- 1 | module regex_test_1 2 | use regex_module 3 | implicit none 4 | public 5 | 6 | ! Some tests from tiny-regex-c 7 | ! valid, pattern, string, length 8 | character(len=*,kind=RCK), parameter :: test1data(4,66) = reshape([ character(len=30) :: & 9 | "YES", "\d ", "5 ", "1 ", & 10 | "YES", "\w+ ", "hej ", "3 ", & 11 | "YES", "\s ", "\t \n ", "1 ", & 12 | "NO ", "\S ", "\t \n ", "0 ", & 13 | "YES", "[\s] ", "\t \n ", "1 ", & 14 | "NO ", "[\S] ", "\t \n ", "0 ", & 15 | "NO ", "\D ", "5 ", "0 ", & 16 | "NO ", "\W+ ", "hej ", "0 ", & 17 | "YES", "[0-9]+ ", "12345 ", "5 ", & 18 | "YES", "\D ", "hej ", "1 ", & 19 | "NO ", "\d ", "hej ", "0 ", & 20 | "YES", "[^\w] ", "\ ", "1 ", & 21 | "YES", "[\W] ", "\ ", "1 ", & 22 | "NO ", "[\w] ", "\ ", "0 ", & 23 | "YES", "[^\d] ", "d ", "1 ", & 24 | "NO ", "[\d] ", "d ", "0 ", & 25 | "NO ", "[^\D] ", "d ", "0 ", & 26 | "YES", "[\D] ", "d ", "1 ", & 27 | "YES", "^.*\\.*$ ", "c:\Tools ", "8 ", & 28 | "YES", "^[\+-]*[\d]+$ ", "+27 ", "3 ", & 29 | "YES", "[abc] ", "1c2 ", "1 ", & 30 | "NO ", "[abc] ", "1C2 ", "0 ", & 31 | "YES", "[1-5]+ ", "0123456789 ", "5 ", & 32 | "YES", "[.2] ", "1C2 ", "1 ", & 33 | "YES", "a*$ ", "Xaa ", "2 ", & 34 | "YES", "a*$ ", "Xaa ", "2 ", & 35 | "YES", "[a-h]+ ", "abcdefghxxx ", "8 ", & 36 | "NO ", "[a-h]+ ", "ABCDEFGH ", "0 ", & 37 | "YES", "[A-H]+ ", "ABCDEFGH ", "8 ", & 38 | "NO ", "[A-H]+ ", "abcdefgh ", "0 ", & 39 | "YES", "[^\s]+ ", "abc def ", "3 ", & 40 | "YES", "[^fc]+ ", "abc def ", "2 ", & 41 | "YES", "[^d\sf]+ ", "abc def ", "3 ", & 42 | "YES", "\n ", "abc\ndef ", "1 ", & 43 | "YES", ".*c ", "abcabc ", "6 ", & 44 | "YES", ".+c ", "abcabc ", "6 ", & 45 | "YES", "[b-z].* ", "ab ", "1 ", & 46 | "YES", "b[k-z]* ", "ab ", "1 ", & 47 | "NO ", "[0-9] ", "- ", "0 ", & 48 | "YES", "[^0-9] ", "- ", "1 ", & 49 | "YES", "0| ", "0| ", "2 ", & 50 | "NO ", "\d\d:\d\d:\d\d ", "0s:00:00 ", "0 ", & 51 | "NO ", "\d\d:\d\d:\d\d ", "000:00 ", "0 ", & 52 | "NO ", "\d\d:\d\d:\d\d ", "00:0000 ", "0 ", & 53 | "NO ", "\d\d:\d\d:\d\d ", "100:0:00 ", "0 ", & 54 | "NO ", "\d\d:\d\d:\d\d ", "00:100:00 ", "0 ", & 55 | "NO ", "\d\d:\d\d:\d\d ", "0:00:100 ", "0 ", & 56 | "YES", "\d\d?:\d\d?:\d\d? ", "0:0:0 ", "5 ", & 57 | "YES", "\d\d?:\d\d?:\d\d? ", "0:00:0 ", "6 ", & 58 | "YES", "\d\d?:\d\d?:\d\d? ", "0:0:00 ", "5 ", & 59 | "YES", "\d\d?:\d\d?:\d\d? ", "00:0:0 ", "6 ", & 60 | "YES", "\d\d?:\d\d?:\d\d? ", "00:00:0 ", "7 ", & 61 | "YES", "\d\d?:\d\d?:\d\d? ", "00:0:00 ", "6 ", & 62 | "YES", "\d\d?:\d\d?:\d\d? ", "0:00:00 ", "6 ", & 63 | "YES", "\d\d?:\d\d?:\d\d? ", "00:00:00 ", "7 ", & 64 | "YES", "[Hh]ello [Ww]orld\s*[!]? ", "Hello world ! ", "12", & 65 | "YES", "[Hh]ello [Ww]orld\s*[!]? ", "hello world ! ", "12", & 66 | "YES", "[Hh]ello [Ww]orld\s*[!]? ", "Hello World ! ", "12", & 67 | "YES", "[Hh]ello [Ww]orld\s*[!]? ", "Hello world! ", "11", & 68 | "YES", "[Hh]ello [Ww]orld\s*[!]? ", "Hello world ! ", "13", & 69 | "YES", "[Hh]ello [Ww]orld\s*[!]? ", "hello World ! ", "15", & 70 | "YES", ".?bar ", "real_bar ", "4 ", & 71 | "NO ", ".?bar ", "real_foo ", "0 ", & 72 | "NO ", "X?Y ", "Z ", "0 ", & 73 | "YES", "[a-z]+"//NEWLINE//"break ", "blahblah"//NEWLINE//"break ", "14", & 74 | "YES", "[a-z\s]+"//NEWLINE//"break ", "bla bla "//NEWLINE//"break ", "14"],[4,66]) 75 | 76 | ! These cases have C-specific characters and need be defined 77 | ! "YES", "b.\s*\n ", "aa\r\nbb\r\ncc\r\n\r\n ", "4 ", & 78 | 79 | contains 80 | 81 | logical function run_test1(valid,pattern,string,correct_len) result(success) 82 | logical, intent(in) :: valid 83 | character(*), intent(in) :: pattern 84 | character(*), intent(in) :: string 85 | integer, intent(in) :: correct_len 86 | 87 | integer :: idx,length 88 | type(regex_pattern) :: re 89 | 90 | print "('regex test: pattern=',a,' string=',a,'....')",trim(pattern),trim(string) 91 | 92 | idx = regex(string, pattern, length) 93 | 94 | success = valid .eqv. (idx>0) .and. (length == correct_len) 95 | 96 | if (.not.success) then 97 | write(*,*) 'pattern = ',trim(pattern),' string = ',trim(string),' idx = ',idx,' len = ',length 98 | write(*,*) 'should be found? ',valid,' with length = ',correct_len 99 | re = parse_pattern(pattern) 100 | print *, 'pattern breakdown: ' 101 | call re%write() 102 | endif 103 | 104 | end function run_test1 105 | 106 | subroutine get_test1(itest,valid,pattern,string,length) 107 | integer, intent(in) :: itest 108 | logical, intent(out) :: valid 109 | character(*), intent(out) :: pattern,string 110 | integer, intent(out) :: length 111 | 112 | character(len(test1data)) :: buffer 113 | 114 | if (.not.(itest>0 .and. itest<=size(test1data,2))) return 115 | 116 | valid = trim(test1data(1,itest))=='YES' 117 | pattern = test1data(2,itest) 118 | string = test1data(3,itest) 119 | buffer = test1data(4,itest); read(buffer,*) length 120 | 121 | end subroutine get_test1 122 | 123 | ! Read in results from test data in tiny-regex-c 124 | subroutine read_results(fileName) 125 | character(*), intent(in) :: fileName 126 | 127 | integer :: iunit,ierr,j,i,ntest 128 | character(len=30) :: openb,isok,pattern,text,chart,length,closeb,test_param(4,100) 129 | 130 | open(newunit=iunit,file=fileName,form='formatted',action='read',iostat=ierr) 131 | ntest = 0 132 | 133 | do while (.not.is_iostat_end(ierr)) 134 | 135 | read(iunit,*,iostat=ierr) openb,isok,pattern,text,chart,length,closeb 136 | 137 | if (ierr==0) then 138 | ntest = ntest + 1 139 | test_param(1,ntest) = merge('YES','NO ',trim(isok)=='OK') 140 | test_param(2,ntest) = pattern 141 | test_param(3,ntest) = text 142 | test_param(4,ntest) = length 143 | end if 144 | 145 | end do 146 | 147 | close(iunit) 148 | 149 | ! Print to output 150 | open(newunit=iunit,file='testdata.f90',form='formatted',action='write',iostat=ierr) 151 | 152 | write(iunit,1) ntest,len(test_param) 153 | write(iunit,2) ((adjustl(test_param(i,j)),i=1,4),j=1,ntest-1) 154 | write(iunit,3) ((adjustl(test_param(i,j)),i=1,4),j=ntest,ntest),4,ntest 155 | 156 | close(iunit) 157 | 158 | 1 format(4x,'character(len=*,kind=RCK), parameter :: testdata(4,',i0,') = reshape([ character(len=',i0,') :: &') 159 | 2 format(8x,'"',a3,'", "',a30,'", "',a30,'", "',a2,'", &') 160 | 3 format(8x,'"',a3,'", "',a30,'", "',a30,'", "',a2,'"],[',i0,',',i0,'])') 161 | 162 | end subroutine read_results 163 | 164 | end module regex_test_1 165 | -------------------------------------------------------------------------------- /test/test_2.f90: -------------------------------------------------------------------------------- 1 | ! A small performance test, to see how the library performs on a few MBs of text. 2 | module regex_test_2 3 | use iso_fortran_env 4 | use regex_module 5 | 6 | implicit none 7 | private 8 | 9 | public :: read_test2 10 | public :: run_test2 11 | 12 | character(len=*), parameter :: test2data = & 13 | "yfufcoubtwfoirqxficrlajyhmgfguiebmrcgtdbgdlfxhuoskhhdhqfyjjgpnownhyevasnwqtdwvtm" // & 14 | "nvyqyyjctuotmfgdwauyryisoigejkficydqhlgflnydgvdjsdjaymqrhghgfdcwosgmctwrkoochofg" // & 15 | "sfouwbqkwrhmfqxujtulibdaxxfqamvvoehxpyehvbsodvcdtsnjjarbfvcqmtgujkjnursmeiybmgcc" // & 16 | "llgnkvnayjbmpighptkptnbonnavhxwowvmmuksnyiorjxgekqakruiekpirpvyvrvivgfarlqfytxln" // & 17 | "dhafovngcxauseiwguwbukpwuipbvsqpxqwxfnuevkymtahaxntwcynmwqofoeeravddgwixjugfcyem" // & 18 | "bhrnxmqlnjbbdulmaqgmhogqituxileynevaujeshcbmkmomliyinvtfoclnjuppbxqcmngnferbbtxw" // & 19 | "oifuvylsbnlpjvlxnfohelupbbxabkanaxqqsnlojgaeaktxpgsexytvotsocvcpfmjqbxhtqpiewewe" // & 20 | "xjrqneydksycdkxdfiwboqlslhxpqyryoklwcjhgiphnaqwapevlrqffngqlwclekrfcsapqdinqxlta" // & 21 | "weqadfhgoufisnvnfcftlkhbnlxespvxapfpygighxpvsckhqgflxwlrvlshhahllyjftwebnqkaqugb" // & 22 | "vbkqqkeituywledgpjqslsaiphsvtiwikkphxygybcthicwpkjtjfuusehcjdvdrywmtijfegwgsstju" // & 23 | "enracngtmmevveadpwxrwpoifqnluhhocombaprwlvkphnmxheloescietwfdthfqbgrjdhwmrkxrkrx" // & 24 | "peiercjiburkijxehimjurfvyfgtyivmwweksaslptovyjfdgmrffnhcdslclqmlcieksdkvhsqargjy" // & 25 | "xjoeqerfbmcsdwxwogpmofyytueunrvrjwxnngtmofojgelucxlgxyrakqvwmmivonfaabbyafmxtxio" // & 26 | "cyptuyccpmpfgeapyymjmwjubkidqgdcbhlhrjgmrsxrdewrrqjdpqvseumvokfskikfhydxkgvntpgi" // & 27 | "bdrjnrrksomllfxuffnkitlyiplrntnngobpebbjhfvjtjiqlvwkksodbgjkheaiwxtbpsphkurphkum" // & 28 | "btweqwmepfplihuubbuimhcnmkxpwawixijhdtnrlmrunffgosnoxrrlfpfijqngltvxtwsfsupggbuw" // & 29 | "gxvmjxiucjbnmfakshrxymihosmbhpxonccoxetjwxyjbxpieywmklkbnusncglccjqbaxlmidoqbobr" // & 30 | "kaewbuynsygvcpijlbpbawinbqnkrvvdwhfxwdkjtfgipejhqvqiudkqqagmgbikafbcvdfscxtwncha" // & 31 | "lqpiewmirknouxmtjhrkirkxrhehvmgqeoarocpgxsobkedkbvvwcjwxnycubuggbjjtojlupeaohkgn" // & 32 | "jliqcujrqmusxbxfyypnulfnkksqmwhmvulwoqlgpovprdtwcxisowojddwkyreotaqqmmhfcmsvovhl" // & 33 | "akvjisvrlcxtsxbtphbjpftfafrtogvgldsimijtgsxiurnavjisjwqgfnftqjodclgpwxdokivuflmm" // & 34 | "eueuwjrkwgeconmjmfxffvaotocuytajdbuywjhvgatdmduedlpuhlptpqjmaygviljckiwcwcarfvix" // & 35 | "rhrutcdjsypoieksqpkhahybueyheogpwxowreusjdlunevxsjimamjmqmqkdleagjldxddxuspvifnt" // & 36 | "rsyxrtkxqcbrumhcixmqhmdeupkscriuwvgrulvenpcofqebsdpcxtosuoxsqnwupvtuaphrqymriyit" // & 37 | "aruhrybemaqlwgsfginbltvpqdqypsclgcsohxsflkbxphblfgtwalqpjolunayhsayiqnmflhtrddju" // & 38 | "pliihrfuaaidfihcgmvqjnjxawgwiwqmkjrqrnaeoqodmyafkwvdgofnydwumvdixkroutqlwamiaura" // & 39 | "mxlqntlnqxwodvfbbukufusjvupshlwmuqxsdsxivdanbjyopjmunucfeujdkefdbfxgwutbganghkls" // & 40 | "miiunliswicdulkxixgsrjdxjrxoavqfvcwguesddjwhoaoxnmxpvkggumnbptfjphpltmbfisjpfiks" // & 41 | "bpoaccqopjudmthvanrqbpdfpqaihdaeyxkrdcmlgajgdspvtplfwqfrlgifphgnpigqsrmqtdkqspxp" // & 42 | "uvtnscmdnsyhknjwnbgmrhlvdosmbgihdergufhdagjuulrqutsxtmbyocobgspprnhyvwvnnnxeecda" // & 43 | "bxqrchqcsppaqvdgtqtgtwnysgypwokytygjnjpvbwtprcltedcxdmsiepmqnlbdglrekcwmcpremtmc" // & 44 | "xuhpscnwqaskunvtcprebuajvfusdgmwdjfkklfdirpvgtljrgtfjbfsywkbwjusaixkfgbpedvywlut" // & 45 | "wcsbcsoldenljrwkaxuwladcdjahnehfqksobnirycoyyupwuvcwvuphehclmbshphkpcpyfhprwenet" // & 46 | "bdtxmpyttonmxsmjtmuoyflkbhwguqnmkcjjxxphtrteowlrtlfxwxrsoxmfcacnisyrorbvuqpaxwvp" // & 47 | "xiweucqokjtacadrdbgryktpnkcyftlnxlpslifmqwyvqngxtkkdocwauljmnrfjvuqomqwupwvtpqkc" // & 48 | "ldinghaqjqueikcvykvbwvsxmvfoidrmlbqarllpdivnbbiudskvvgkxvpxphgrnkhosimqktoysexlg" // & 49 | "tsobasacbttlhpvwjjjowofhxgdsuimibkwcioaraoawjcipbyaafquruailxcxltxoxjvraihlgeyum" // & 50 | "vebgktjbtjcfblujxaoybdadwiwwuxwirigeseqmleshnhikrtvxbjxrwvxhttbjlsvsyqrweojncdse" // & 51 | "itrvhyggxwuxrrhdvsdadxoglnajkyqkbtqkvnnnntkvangesryfaeetnaftpieggvbalfgvevjbddkm" // & 52 | "ppctcgxsrmcxmpnuvtykaimssidfnvggbwththeduhpisogktyhlwshadhvabtqoykmacedtgusmejvi" // & 53 | "ucrlipspbwaveibpompwxdxbwneqcmytrxnktbohtqfnmsvvovsffbbhfnrrsjsricsyikfnlqfftxbn" // & 54 | "uatbhovluljigwakvvofbofbgbunrmqmosilpiijmnhpccihhesjgetywgmvuiqhblrfvevqxthblhhf" // & 55 | "dbmowhixeqhymgwkdjykhqehemtcrgfvlaxtlgbsuspfidarknfsmqvujajpxalcumyefayhtrkwtcwg" // & 56 | "askygcsmgyesdrqehfkdsvcpxpvxmfycjjluwdbgwdtvimrdsuqntfenvklppnhxuwebhovcrpqggpdf" // & 57 | "ghypcgohpohokqybvjunweporoafekscabfegwwyrrknmukehnjneeaxfhdbyfcrdtqjrxvdlfmukdmx" // & 58 | "htcilmeccipvmnpgmdmoimvuekosxpclsnwomypwcwieqmtmxxapeedeuqgcdtlhxemohrerhkethxfd" // & 59 | "iwyrwsqxqvgtytkwcmuvogbvgrrramstxeddgwgoxtvkuiuhbrgsousyjkpyvdkqdyhvjqcepxatreoh" // & 60 | "vujujbeabktdpmnttrkrqfrrukkjcslvwqnpeqarhyytxbjcrxmarsiaaxickdyufrcgwoweuojrajlf" // & 61 | "rnpihjbllpqnuivvfnwvfixthxdyfuthogyxsmxyfctuobbbhxldvdrneqfiwvbfqctaxbtrkgixcjnv" // & 62 | "baohjgrrytxgerjhmoaqerdaysjdvwjkqluulxgwvxitbtuqwwpilaqflxjgwhecwrylodxwhxchfpxo" // & 63 | "pxfyvcjeyfxcnkuaplbhyckmbwhvbohbxxrrltytkhnttpsgsqkoijqxjwpvrdjhnjiysxqwqhmkhycb" // & 64 | "ckgoihnmgejtgfsbgxmibsgvqsymjwegpndvfkpsnaiqqbuprcihnmenjsbpjigiovoptofoddvfkbsl" // & 65 | "jnskuofnxkdlmsnijoxbkxtegrffiicsuyuswrwwtthxvqyxalknavkmvcpynxrauldioxaqhlawiitu" // & 66 | "ofunblmgyoiqvqxgufmiguokcaocpcvuctpxdwnixlcpfmhdjudqkdgxscnutwvjbtsvgpoxocudaykp" // & 67 | "suedtpaowmtnxerucqifyetwomfjwoxvfgedfabaqydlykduylgmvgwqiefqkpbdsnmggoupeycbakbj" // & 68 | "fhguxhkwfyglojjsoscxjgtmohkbpfjnngletolghmgyajnpdohkjddojdfgsjvxlgfulyieqlkcamax" // & 69 | "jtjxeubkkuwxswdaakqoolgnmwdmdmdvwkivrpwbuishevpvvljiwsifgjtjpolwpjvjahxsprxbvkfk" // & 70 | "dqmbmpyjflyvfuiqfyxwuukwmggkuudubsljlditnecynmlwjimxbtijfunonxsmttblhkpallcdgehs" // & 71 | "tmdxhqafwfjfoaeootjijuhlskruiccbndvuyqdajeeaanfetwabhhgqihkskvhsbceesrjqnajcabsn" // & 72 | "npgocpdfbadjxpaoiyfwtibjsumvtmawggroyuarelgicwqabcaqmmdjjpcqrkjfbvlarouruekusdvk" // & 73 | "lmstjpadtqlcolhuipssvxkgstopraulffioukmdtykfkeelkalscfgvmtaenbarcmljugmbnpvkdwkh" // & 74 | "utwasblyywofcsgdcbfqsyfsirqprghrmmyhwohbdpdpnrifkrdecwaaksswxwldaibrrmnvpurcwieb" // & 75 | "sskppdgqrjoyuuduosxgyyxnvclsjlenlpivcjojjokfusfqauxhkgqrkegmhtbxeoitqycgywkianki" // & 76 | "pxturfodwbysysxebkokvbnhjraasoxahvclwlcnmlqoprcuhagiojnaqrkfsldyyuymnlgejvfvwlkw" // & 77 | "udrswidtrifkomvtddstioqyedqimbmplvarnedwjgrxtriboqqjipsyjppmoomyonjnugvxerwdioci" // & 78 | "shryljawkrxcrcyijrnybgorckmavnsdbjxtrtdykqrhgccvowvadepqraccfusbppghpcldmvmgsrjj" // & 79 | "xkdxqqfhykkigpnfssxrcvyecgydiquhylkaxjlvcapyqlwlofcjidooudvpecrpbqlsttkonyrdfmys" // & 80 | "mdgkimvecsljfddprgilccqcisxgwjmtcchxaskksisrsqghevwenhrjcmgnesxsjmtaastbjjbcecpi" // & 81 | "fqphjfgxacluyjckrxeewwsqhokdnfdnkxgsludmfkwstphflpjbkxneajhcjduiqwngrppvpnktuvkb" // & 82 | "ycxlcoihvobjafkruvgtxsiccruaduipchrgiombahbvnobvpjgktuupplvmqkorpgcyrdgomwdaohca" // & 83 | "dagbnejnwfnuyduyyacocywdstjqcakvcnsrubuughqonaeunjvqhtyhepenlbxnfdcwhuxnqmyqhhus" // & 84 | "lgsumtrmweggogfumcummtrodtovsugesxwgfwfisuaejakrljqqsnopenrjtorvaitgromtutrcexqk" // & 85 | "ysclousbppseymovdvgidjriuiynmxibqnkfusktnalivcfmiabstjydogwhkcuctmvhrnimtoyxbvbo" // & 86 | "plobexcerwdveftccjmkndsbfmbfkvchleirubndjjmutymgenkgjncccmkdonjuwqtesqbavgmktxlf" // & 87 | "etohasfcydjytggkxpoldhivkwgmbrqvivnvqlttquklpnixqmbmielwhhmmbputwoeixbxtkvrvexwt" // & 88 | "cuqfnlmbvkwpvykxvnmfmkikdywbeaglfopssfhqhpeetienehvyfvgfhhmsxmiycoddyhqusmcgudke" // & 89 | "ivpejcphafsshtqlcabsusxmfttsctdukljrradtqpkipuanqwsorwgggassancplihgbvqrrrreempm" // & 90 | "ssihtvqfggpfixbxrmxvyjoyveaaollynbcviiwvmfiyyouqduklmslqxnmtnqkfmgikwfimmmjtwocv" // & 91 | "mgeedvhghfhoacxmvdtnmifqbpqfwwckvxrfpvxerobbgebvfnyidihgfpwnvdulqhjqqpehaqxulvhm" // & 92 | "deeyoxfwpffotsqneeuhndngrvqkeqmocscnfshmkrgmojwpdwbynelbfwnswdttwdefnsxmyvsqvafn" // & 93 | "vwoslnmxsqdfxescisibdctoniyfuqpylmgrgeruxhsovbbxhxyrevatiogymxsxhgwqvxtalvxexjvd" // & 94 | "omqojowyinfqnbfoahsrihhscgitlvyfoulisrtlobxhkmwcmgdwodrwkollnlcwswlqssoadeixcfof" // & 95 | "rwidlrkmrlmdqkiaufebnohujmqtxqujuohmhhxauqubylusrkfbqqyiomimyjfijfyaceigasqerskk" // & 96 | "poxixlxafkiidltpxuosyojntppfldygxnlxqltbnwlfqovypqvgsvvwdalhaaxtrxmmdmrejjyclbfu" // & 97 | "yrhqqdoycrlkdcuhqrwrlkumnlgufehmyplsyklmropvxmoahvojfwrnbqkqceqempenmwafvnyqvcpv" // & 98 | "ukteognyoarnjamcflcjnbbwcuqxeynyjexsscityfsqtcbvsjgxdwixkbyxggvrupfhvnnnudwjejpw" // & 99 | "eygnaykwwxncrhygvwhlxresqoxxwqpdjcvohprocrggvwermutckskslknpuvgnhfupxiilkfwgxfik" // & 100 | "raddxbhrvsxqeocyptwxuyuferpxlypvasdyvtywfeacvudunpcklybggtsrhogbnkhbdbpqatmlxkku" // & 101 | "hinmfvjchrmcmklxsrkldsqnvsilwrsrtxaeknobulicwmxvreumopckbdumbdwmkpprwdetbiceiqqo" // & 102 | "bggjwyuiwnbwagbtyvxqxpeaevvpismujvjrjstjvtmpytkvdhbvlywqdujwvnmteqvjgbpqdkqckrqp" // & 103 | "hdrppmnawdxgpkqcftdurrgfutcbqvoienobprtjlrxrfxmuycgltfyqghugxxqjtobvccevdhqoeakr" // & 104 | "gdfcnhtfmgpmlehobechbfvagabotqxehjlfyakllmdbrgwumipkbkpbuynxoujfshqkwvfrnlrmhnkl" // & 105 | "rxpbkgekjahjagcsbsmbprwglhgktyeyxrjuvkfnnpqakdawrccadheulapuvhidqhsxqqjmytqcxtno" // & 106 | "potspntsvuivckigpxcggooterhiqomlrhvxvsmejivfbcqssbmfghaugkpnmfatvgjmikuhjeoodpvg" // & 107 | "icpcdulgmvgjdbkerqlhmxfsuorcwkwqhbolwlikkkvfvrogcpfxkvjtbgaluchvlnuvcjodueamrcbi" // & 108 | "vfnjlchhqikbficomopvwcibvpnvxiqsukmxadxdnvrfdrfgnfgximbnrcqlgbjcpqlqffgvijerdngl" // & 109 | "hklekpkjfspwqaxtclujbwimmneuqvfwxxrrhyfhenmhkepfreyeysxbbiuctlrtsqdluwjjhdhvqmto" // & 110 | "fupqbddtkrpqmyhadlaurywimgdxlkcxjqcgmhdasjshtijrdlwprkhlmmccloxcayjdjprqptfsmymj" // & 111 | "bqoyfiexasakxpxdnmykwuxxhaomxyjwkwidtncvgesarsxfkldnxsshnsvbnmfmdybjnrqdpyjbdeas" // & 112 | "tnckjofhnghwshactsbjiurmwlnropmvqnvfnnupirfaomfuehidxlokfrjwdrraogadlvplfvywknng" // & 113 | "vvujdpqcgjncsvnxbncotedoobyuriaxrunkdgnroshyhakmrdoqrdkqlbhkjmdkqvlcrasbooliudcp" // & 114 | "knooleoxjtdybdiyclwotjhajhtjphrhcoqwxvjnvsgioshqilssygyadpaqhskeduvisngwjrbtvcos" // & 115 | "ddbirkqcssdinsdmcpgvaymstlymthewcfwndijbenyklnynevknbjrsrviqbvcilcdpmkclawqtaqle" // & 116 | "eppglwntdybdcwyssfhpeagrajhfbrmnexkkxtrkqgjiqemvyiaephndjwilkkcbmcvwgnkmgqixnmkx" // & 117 | "elawodybcakfryievwemliboywmxmboxhwmprmcqyeafxfcnihstmovqduwclcpamwvxrqoatbdhdjgu" // & 118 | "qyfpdrbkeefymupjaugepleawrgsthwpeltustmadoyxybukfyxusquvjcsheqnbuwthpwlfmyedfipf" // & 119 | "tflwqomxmmpswuyecnvfcmqegtskopbtxhmgrfuwihbptuochpynwahmmhtxvxvteuopeyoscakvltuu" // & 120 | "hoemthcqgdrcgkhtielstcuiqeugkyyoucmfuommscmspqvtqrwqpveyrmmvxewrxrluvvrrwkhjslom" // & 121 | "yhublmihxbeyrxmnhimtcefpwbkdunrjvnuonaqrtvaatffaxrpbesydugufqsspismlpdiotxprxqsc" // & 122 | "bpvlkbavsgwujredbynvmelbgcbwrxewqrduiwlowaimivcryyjocrtbxwmublkotcsptmjpojofafqu" // & 123 | "tsjlmyhqxumoptpvqjabgimbtkgsfomlyiimgmqfsfneluyhjtyprskemjnjvmmjkhejhqkcsjxhovpu" // & 124 | "srxotlomltksqfjdjhkgrxvhpinumvkbkaxowruvniolmhnkmuluruxksqkvccokujjtlxstuqnvwfna" // & 125 | "fbcercgwbjqujocqwrtnqoritefwahtpdvbedhchwukywpackdjyrosbeiqmfasnkcewsrwbcugbebkv" // & 126 | "aqxvampeohwdajogkghuqfsiubjlrqhiajjyqlmvohtwjcbmjotaokidrkguvwxalpioibagrbgegnbl" // & 127 | "fiikjmjpmsowculielymyqmatkxuklscmllxtoogqgxqdeckitmtkkxatfuwfhijfeyxejnqvyiqdvwg" // & 128 | "amlqflgdebfwletrbdalnlundtxcbbhsagyoglfburvobafstulbijhkuwmrnbwjdrtgvyleyxyhvuxi" // & 129 | "axnwdiaitpfxudjhoglntputujamxvcgofkqmtlfcbsltclmrslwujwfuusqhmmamkcclmwtliehmgcp" // & 130 | "cajqrgoxsbpooeoosumjupqaarjfrrbagcsynqbjcfvlddhwfklnkasnscxrbukcnknejbbwjwlbpghr" // & 131 | "smqcithlxixeumckduevlrncymcmgpfkvjkbuivlbvfrajgqdttplcsnqtkjdbiheqjkbslgmhxmsiet" // & 132 | "lafteebjjmqaitplheekiyrqqtonlbtcdifpvmkrljwatrbehnubqnccddbitlnpxicejoxljenllegn" // & 133 | "hbcelbonmalaviqwtjttmjjwpvpuloluialubohgigamrrckcplpiqnvowtdvgowvwnarvteccsemgie" // & 134 | "tnwcxwcvhevsdjhjerupakqhtoxmtbaqdkjvyalwjhxbpxxedtoqiupxqxfetmjufvpuilsvycbhtnir" // & 135 | "vkbmgeypymepyjbavdeuijvplitkemiuwhkorfhuscwiikhgfoqicwtakpbknownxnfixodwalpubpmv" // & 136 | "yrfdvuooqsuswyqcvxsnmpixulnvvymhqvcwgmjdosdphgoagmvoltrtvbpadoscdthhvyjfxjchjase" // & 137 | "eloaaqkrnqbofwrfcusebftiqfrgalhdcfsxtcdqquwjqjnkwdfhiwwqbsdpqecqqpnjuopcgervwyrk" // & 138 | "hwuhjfolyvfebrubiewrwtiovnvjhvouwoulgtmuybllfnvbjqefwbkqafqvklrlcchhjijcuqhkmavv" // & 139 | "njjghoegroppkcducgeqidfelrrfrtfuewmihnrwjargsvkgglugasxhrkdimlnhxvpglohcrvaihqio" // & 140 | "gkaimsfldvqxytbqkbqcitajcuojkdawfaltquainijkaexhbxulkhywjvoacqefngstbssvxkcxrljg" // & 141 | "icxdcmjommctpdmtrwjuslchqqbumypowrbjinvwasqyxpxndyxnnexshvyueselrmdvsvngstjbyypd" // & 142 | "qsmdjbfgpgtfnxxndndodosmhyhgoafhxsbkackjvbfxyavldqrufildbkcmsrawiqsxcttfqsclpfeh" // & 143 | "jrbpuefpsrbipoxxfxqcsjppjtgnyguritasthuporcupsdirutcquodtfcehvtwuiuceaywgwiontuu" // & 144 | "tlbokxqfxbddkiwdfptsjhtuenspxotirvprihpmfgtsmfhsklfbfvfjpvgrexaoxhbasdeksjdahgdx" // & 145 | "ooaexhdoqrifdqsaxtiwjttyopxkvmyrnetboggnktwsiuadjyppbxlqarleyawjykoffyjylbgodjga" // & 146 | "rjdrrwyaxwqpvgxxckxdwlsflctnlectehdlemdvqrkftqmktiusgelsclrfxigsuojuknodjfsicfnk" // & 147 | "hqiajcxnljxfqinbxurdsoexpsrrpxvjkcuxvglfajqqmjjbevsqwicetdyfmfthbmmdxtvuxyfqmmox" // & 148 | "acdtqcechdxkymqoayrndqccoqsmtqcdtoqxxkhtocboxnovjklhhbbjqgocfrccotmrwqrmelkvptra" // & 149 | "trqvyjwreaktmagussigwfsrckfvjigatdfxdrbjdeabcrafhrxwsjrfympwxdfcshauswcyyxfomygo" // & 150 | "tafxowkwfjcauiwbrwtfgkcaqwcugrggvsarraujhgudyxjaslobdsiyqyerylloqpfrwsbffxyrqsdh" // & 151 | "knongsljttjjtdpbyuamkppljsbrrvrfxfxxupmjpfeorvlgvistvcvupvioeprjkhmjqyrjthdrvenc" // & 152 | "fjafqibivfqgsolabciodpnfphpmwftsddodhcyrplvdftpkxjcjaitakjbcqknogwadysldddtqjsdk" // & 153 | "ejyxpnrqfyxwvaxiovqpkuiurofefiygqvvyqktowawcqurscmrnsfbpfywsaijjliiibccvtmnrimgv" // & 154 | "jrsaaoogxtvwbsdtivaiepvsopwsurhvsbxtxtiqiwlhmjeupylvbdhbprmepgesowfctwhntjvpywrg" // & 155 | "ehqfpvgnehwalvvuoxwwlesvkdpojymsndbmycrqhatbdcrsbysffnyapkjnbykdhefbtcvqswmsqfdm" // & 156 | "qhhwtayptfeqtfcslhdurbrrnqniurqcueysebgbpabtikmlejxcrofbwbuioplhjxsafyonsgvydbof" // & 157 | "cklutwjfucwvkmiwqaerfmpshstwnstvcurcfmgraiakitwhtnswfebjmfqlxyefbhranjynpyphqwio" // & 158 | "cfgwqkkbqgfkeexuutuqhrdknmibdhscatnwjowtcuopuaghhrkssfoydwhtqmisqbvwhhyuluynxofq" // & 159 | "ejbgutodxjcsrchkiplsemictwlkxeukjwswwdaiuvwxanqxewvqpmmeiqeftlaflbvswfjeyqkghnrq" // & 160 | "sqbqtmunnousvlhnynytyyytfwmunghhlvhehovqiholftmspvaesbyvlivvyjwehmauolfipsbsnrwm" // & 161 | "hgmutginiercduccjbgtktwswseiqccodcylutkpxbiijrcdbxntmfsuwljwxbgxpdwkkwhubgsvaaqc" // & 162 | "upjxmjvrvcflnwyoieeeinqydqruopaeuopkxyjlxbmmdbnhydytaejhobbesqnwvcbtttkmyxiuiobk" // & 163 | "juwgqmgpmekpmptxahrmxymwfhssdqddnrftreuyllhrlqklpayvrjeqrvqntubdglexugoydifgodbr" // & 164 | "syphcadjomuncatnotanlbodbvlthypjhqqklfgnahjoewdplecfkecbyoycwpagesgntlgndquapplh" // & 165 | "declnhlgpdcevxxxheaywmrroygxmmgquprihuopcfeiudurkgukgomemcsyaneidlsuifyeahvpbvxr" // & 166 | "ktskfclyhrxjbpsjhsggdpwiqeolhaodlhmdytxchbdapxntkqgsqsbadjlnwpfekdfmmjieeqfbotln" // & 167 | "esuoxbhrelojpwdkmsqhkakisemapyovbgxamcopfneebbksdlxqbvenqsnumqnusjtumhoijskupuij" // & 168 | "qjsqlfpcumoequwacxoxlivkeygtwedotyotntfnipwhxsfjeokdlsuddkhbwruvuicketdwbefejqiq" // & 169 | "xewwyfgrmlhjcyyrsbbwnlyuoxhjdlbgeledepuhdrqbftpwcmvcxdvurnehqxsbwloctdqbyxpjilsh" // & 170 | "ljkosjnbsfogvtlxclvwlltqkffcahlvhrrlkaolrkkxwxahcfjhxtqhdimurmxljhatidgqbgkoncds" // & 171 | "rllxdpvyiavmredudwpdqswuakvnbpuinstfmlkpgfoueixghxgkjhrqrqwltdytsmsoybyqlssjgnri" // & 172 | "uqbreckcyvtyygwyfdrsaobrvfbvshgcrdktlkxodjivguspxsugqqteempntdwhxrnqspojjoipckqn" // & 173 | "wylnsocqgewmumdgdessdtafsiiehfngimfexyrakhduggqvvuyqbvawqvvgpibnneegujppqhqkaltj" // & 174 | "kcprlsywtgqreargfmieuamiwlvqdoyiwjhsejuoujcglmxtturjdtrucorokedvymxqaaymwyhtingr" // & 175 | "tpdrjxlamqkjdpbjgidnkbajtaixuxrimnycphxmydcnbneolyqpjhpundrygwchryexdwnmbqyukcrq" // & 176 | "eilhfbaylgaxvlmusmjlnfpaonkpikaumskacrsejwkxhnoxikgcsvogcqmgbnvqqxxhxcerokujvmyh" // & 177 | "uxwumpnqvwvvpwrajbqggsqujsfndvjevfnhgxyfemmqsfcylrjoxvmxckgpxvabnvuvtpuaweuketvx" // & 178 | "fojrrpfpcsxntwxhajautukbljrysykowkutvtdydtdwqkurfuayxjijhjlwhgwsvnemojnanltkppuf" // & 179 | "prviwkvhnkwdvakmwojjbkysjjvbdinjnjkuoarmyfqwnrhtimkpewedfwkqlfpuccbedeujkgidwqep" // & 180 | "xgcbytusrpcrwfosfnaibuyqhwoygvbgxcpgbikuxwmqxcwxaautkwbqptmnbqsqxvyhasxawwbgwkmi" // & 181 | "wjjtidbplnuqnfrvccaktbhapiudjkhsokgcjyyexxxgsmcyluneyfmcvjkgqgiawquvvptmigafdksf" // & 182 | "fpewmpoaqnvijhpqpfnoprdxoufishqwkisfymditfsdnckuppdrqytqsojrmrxcsapmivsdlsiaxaxx" // & 183 | "gujxunoanbutwjhpgerlnuyjrkkcywtsjljikfkajrfgslgmlijmodxolutlttdrvobcwfitpnrulway" // & 184 | "xrbuuljlipvcjtulwlurhykbjdckpnmkxnwlamfnbwuvlrslacwudpvrjcbmrqbnvpfhchiflrtlxtdd" // & 185 | "tcjmijtxfhcfvctgntifiucedboqlbyaqmvncbfametnjxasnbxrekjvkopjerladadiaoofjpcnwkwc" // & 186 | "tbynwkpmgrfqrjoswqwbuxkjckxgffawyefdsqrwswwuysueuemtuetgiplmtshoalvemlmddasohwnl" // & 187 | "jwswdiymueojrhuojwilapidxsnptucqwxsifssjnfegpckovwutmwtltixmluufviqisfxnixiibqpi" // & 188 | "pxdmgpbgdeqwkrjqphpbqqtpsmpdgvitpdrjpmfitbfjribtqwnrksfqtjlhkjhllimgvxaxkjetumtq" // & 189 | "yajuultoroteuvbtodbjgbhokujlwixfnhpfhwtgkhcivbnidmpcjxgdipelagjdgabviwppjxphxeda" // & 190 | "invbdfjycxkqxwmkuroguirvfycwtmglpuqxomsudxkwkwoyxosebayutfqtnkhatbuevxkukiptutnk" // & 191 | "hbaekwcfabjshcayhptpsmgqkscmakiireanaxgjsgjlftkujkfihcsekbwhsrjgjcjpwriimkpbytct" // & 192 | "mlyyvntykjpkoxorwiqbajrknxneuappqafmeluqpncjiabrwylepmuxoxoslxauscekxamgiqnvjppk" // & 193 | "hxsutprpagcxxahvwdmfqiqajollbjwwwpkxhagmrksupgpsvjtvygmagsljydavhuviurncbclvxkyx" // & 194 | "yyhtjqlsiilaptfeqgukfiarrnnldeyhmfcqksbmhrplhdvawjalkicwoxcdbctbiyoujosjfbjjveim" // & 195 | "xhmgxvxsnorsrwldntbeadrujdcgaovvhisukmeimjwnwgjkmwsbcslbqlosmawvdfyjovgllovrwrmd" // & 196 | "kvtibyckdjcumvvbikqelylvdphjmunshwfjsargmcjwdakhljdshwgqtrrjgfmiwrguvenykqlyjdfj" // & 197 | "oluqqnuyhycqigippghqhvhxbnyswsflctqfpkumydwdlhbyxbovdbibwapgysfmmbwpfayfgexsepyq" // & 198 | "uqwwhnjkrdhgdflbuhdohsvanawoqbbeohkvlgelteyfrvtocimmxlmejcrebkfdmyrvtmjfggagodvy" // & 199 | "vinfysievjofhdrlglypnraisneygcmtgullqpbtaadvitkvvhbbfqjrjklrnttwawicnnhlhavaauun" // & 200 | "ipmlhsqcsaruougpeiewrywsvowjiriqyccukiubcumakjcowqiknwuhxtpwhgsrvafhbajnswyaftja" // & 201 | "pofuejvfgulymysyfdauymfjxjsvjjmogcfqgishknnjpahjwmeikgnocrypsmuvwkkrpatlvvvuprwl" // & 202 | "kdfodnoudgcqdpygapbrqmfeofcwkxpweqxfpdvctmbwkpmjdreanubjfmontjygsndtrydjyaadbfor" // & 203 | "prufwnblsdfqiyftdapgbirqaxtwkobwlugxgadvhrhqdcpiounvxmihxbcreicrquqitekqynntcpqj" // & 204 | "jnsjnfcffcovixuhetekhhlfymdxjiqkkaisgxmsxkbuqvykasmufcolejoxcxukxnlqpuhhkxwislgk" // & 205 | "mokrwxshchibwrwwsfwnhjhsllmsgcdmhsifxdntiqobhovqecpahgryqnihtalklocpiqmcdkygruek" // & 206 | "uavcamnmpjwfufhmqfmndcelroobxmgbjmugccaxfxfxmfyxreeyaypsrswibbqavfsffbcbocdcmmum" // & 207 | "ohbbnvekjipcuhonmqfudytimnxlsisygkjncoyfbicqlssclwdmgrdkuriphpbbdxixognlwoxeppjh" // & 208 | "nqdamcuunyfcnpotwucnqxgtyfqkdtlybxmgkjgacansakfitnpfjfdsftjtcrcsqvlditkslmufbymb" // & 209 | "pwockilxixfehbogeaqoiqutubiyvxjfynnsbcwtuqmmmtsjxtdmylofmaopqvypgqmibfuyqaikhfbf" // & 210 | "lsnqblnmcfsmaiujdarqusmqyhawtdimmarrvdfpdlslutsfugwmownhgfhokhbsdysuiowqhnfxofsh" // & 211 | "tvmmgmmsefkmiprpppdqutaxwdoycikdgxmecfsmpdwufawpcfuexlwsbwugdrakljhyjkkpgtkqrexq" // & 212 | "tdyyaihlhdgtdgdsmjlapnrbmfbwnntybmwvelrvyuvxowauluceywisbqjnkrlvnbdubnulexrljfon" // & 213 | "thwdhmkkknqvoqfuhpsumlnktuyfiayamfqgcscuvhpgxolhnuiiuxokgvysartbdacdcwnkxepskdjd" // & 214 | "vktnkgdxeqhhtbyusjirduafpfqmkcbqfehxwebxfrgxmumujrhmqhcnvcyfsinopfpbfcjariyiitva" // & 215 | "aghrbadkfmnyyghyvdeeakjomnchnvqgktphswlvgjwywthjpnoboblmcownuljahqvdxffgentnwelg" // & 216 | "bnbmkilgtnceptmqlemtbbodaapjybbwmpypgivufpjlbqakwhmksshbpqqkphucwcvbjfawovdurdjp" // & 217 | "uvehhcloxkfhhqytvhwvyukyadionhmucqkwehrcddjbnmrwtqgmhacelddqubgimvhbqtebaopxoexq" // & 218 | "gwpferiyrfbyfbmkkkxjhfowpupuxmctaxwechootysjkkgptwvsujtfjsdolsnuwmqqcobubrrfqcqo" // & 219 | "gqevqpoysigdqhsythpmntnyybeotuakinpnpkprxclfgqnreirnuqandkjicbvboatrfkgcmfsderes" // & 220 | "vpbbpqpfxbkocntcacxtynwdjsdrjhjhdbghjuxtspmeuqaolxgyfqduyuwgxipptmbphqumgsydtrgt" // & 221 | "pkgxflwqxlufigigylpqklgtyyxycsylbbxbtuamsmadxmakpwgcegbxplkcxcfyblselmpgigmaykvf" // & 222 | "fvnbfawglaoabudrwvmewegsdswhsitjebwsjadsoeuvacxdiymhiekikbmmsoapygmlarpyfxafqtls" // & 223 | "bparnxwiwjbuomgyqcavwukerrceedqtxwopqcwcuffmgavwidrosimiqtdqcrmuwcwgwdmoromkbcpo" // & 224 | "rbaqegdvxcffqoiskrgnwwysiyjmrtyhrnpamgftgmsrybmodcogpiyqhrycnwkfgseqrhdsxodfxhfe" // & 225 | "dxrahtyoojcjfkdqofkglllsaoxsqbnetgfolwjgmawwssalyjxbtbbbpdnnbcybaltngwcuqacgoqcg" // & 226 | "vscfthclfjqivlrbrgaorqdtdqkhckgbhkyvpqvbrrtaogoytibreasakrmsyxcbphcsweaqypqjgplb" // & 227 | "oybrsauwtypvdlqaghvfquriqfjxfmoumjauloqumpgyiaerllqhrueawiscmugivknxpdjlgnniwggn" // & 228 | "fvarkaxtfvnhkfdfrkhtokwswompavxvlqouyghhhwhhbmmtoxnbnewtybgyearxasnnwljwbphdbyes" // & 229 | "mwwxxienuqyksnuiwieuoyylkttcgguqtsvihmggtbimkxwhfajgoebigdxkujgrvdorccvfklbdpchp" // & 230 | "tqawkxhlwcvqlijlpuxhbwacwlfwydyyvuwnmrfktkbgvfttmhnjwlluketmblapugogmdvrkjenlcqk" // & 231 | "jdotgpinbrfefentdjxqabafjcsffyiltnxiklboleateefptqdgviyojtcbvqjnxkuglhcvcskmibrr" // & 232 | "pdyyclprjyvtgojghciixewdnoxrhyhojbrupkuvlikmmesbihvxalnarruupxqytnsjusvgmkhuwryb" // & 233 | "hmwkkasujtrfhcpyiflgqrgvscgvhncnucipnxffukgymmpnnnjivclvuyayteqgbdbgoklhamstpkbc" // & 234 | "ufjjjjdvkgsuisjnjhmrsqsujmiufqguspqaiqtqekqowhjwtyotibppemmwmrbgbtavykymuqnnvxrp" // & 235 | "bdplnylhpegmeuvbcpkufhhopfhsnxuhjnaxnkxnqfuhbyolfggllubykiqiurqjpvpmsprakwxxnsuu" // & 236 | "ohcluumrpuccckjymasbyukmjogabjnqiqjjahhvajrkvtoapwkgsupbmegnbyttrakvdiiilhxephyp" // & 237 | "erdhhlaotbxlrlbuldrdykrrbmtrkxluxjqgryvqumugjcfocfyttpujnddnhnnxkhrtlmsterlhxrfp" // & 238 | "ymaavwdnwtgotokwiyysrqcbixurbqonfglrfkanppqehqnyuuugdgutjkjequsmwxrnqascuxllxjuh" // & 239 | "eqwjvmmnjnaedrltvciewpaietmthgoebicwwauviiwofkwewlkijjlgvfturlfjdgbseunujjijpcol" // & 240 | "ikinyipqfkqqnsfwfsrwptfqiktdayenmokxapvtcxvcugwbcjbypanlroljcyuerrnvdswkmugtuosg" // & 241 | "kajhifodonjhauyvipikoqvjwchondqbmgvxpgogyxhsjokgtjwqwrplixsrrqgmcljrwakfclqxpopv" // & 242 | "yayomaksojdxmngntgtjtnyylcykoliqguabkmfqeyckynvlwuafcewujxuojaamihwsydwowvvmigxo" // & 243 | "vjsewnpjxuwybnvcavijhtxixqomvomdmslehlqmafixnkdybpmfmtnpglnhljqyfghsorsjwsegbuim" // & 244 | "qgssrknpohbrltfrwrmgutwbrshnpedkoevloirrwbcannrsbmkivmqbakjbmymxjphaevfqdlewrfqo" // & 245 | "iqyprmlipirdaytjayrlthfrrgchmwdqquimnirvnmrxusfrkwkwwrdwtvnfbniwbobsgyicycsdxvpk" // & 246 | "owraoxyghvaocxnlislytygyywwxeukeeiwlfeuhxfbluggpilyqwioymimyqfcuiivruikdbpdxyidr" // & 247 | "hsmhlrfjjofgmoftbtpaqpphwsxeaonlaqofhvvfyplmdmvumnsmgbxuljktxcgebcfgmwyfwryjdvxx" // & 248 | "kfolnmksqmhhtjukcwfknifunulgfnosnsswuqrvhdjlafkmncyisodraejgylmabngmwjwrhhovwsvo" // & 249 | "lfiuctptunfmfikpptfjlnekgqyvedjpcqohvwewxxehicftpenjhlpgobfyuqckdntercckxfcoosgs" // & 250 | "rgafwoxcodmhptpplaccxrhivytlldrbesncptjthmbctkboarhblnrqyvtgtptgwnypofrkuodceogf" // & 251 | "heuwdvafijgbrqdyrasruaecwwwpeebvehpwlccbjhydcsofxfxcogtuiswikxeplfpwbiftvundqoyb" // & 252 | "gxwytbsajgvjobeuuthjhkeysulifhlyoxurralhvdyjoibuksqttymmamrxrneaueewbbilmjvxdvac" // & 253 | "iqkmbxjmjxghyyfqksrvxtcrtvpyhyibatkyylxbexqdushbhtqpwajeymahaunnmssewfpxiedqoebn" // & 254 | "rianuvyhidnicmhebchgyggfcqiyijwvdlsqkkwcyxbvushedkpxuqndpdkowcwgtfxslwbhvfpnqfso" // & 255 | "pewcaqdlrjqhsfgbpwignxcujkpilvdovcubwgnggmcfmgekfsfufsskkxueuvpvanxflqfbquyptyep" // & 256 | "fyoupcvublxhowunympdqciuvhqugyiiwmijhjmjplsmlloliqsxmaasoffcqkmougqwgsvvjvlagfji" // & 257 | "sqirrxckvokxkrojddstudouwbdjgjsijycwurubhslitdmqvvnggeqpjytuvtgybchllpgedvrevkiq" // & 258 | "syxdpyavaftyjjjxkntofgbvqebdsocfflcwnrfrtpplorkyhgcbyosebvgewdlabadhyrpawmjipoww" // & 259 | "womxviixthhtlnfggdbmcqueuqlubffaigypnkoqbwlugwxynwjqqwhrswiuabeqjxnllpvtivaxfvnb" // & 260 | "dqqlyeekqjsnkvpywccgunftcvumflckymriguexplqiivpukjdfqqkjfdyiotblxawenobpvnsstmmj" // & 261 | "hfoxpvfojvjklodwxahcraixitxlyaagrjovmmlxcceigxsvximpsvxquewiswofekajxppqqygyyndl" // & 262 | "ljmjyscrjrwmtpwnwclqtuayiqjdbdhtuudmofditslfwdsncnsmgsvkskwlopioobclppnaxmctgfyi" // & 263 | "lhqhhuyabkkpjiujxetbmrltytfcmqhpltyniufmiothlkeriphrefnrqfonhbpmnuwhtwtavtycocld" // & 264 | "xiomfxllojwmtgcdydmafdyjahcmaipnawjdcdpmubmtdmtgmrwyovoubkrwfklsjvihowbtqftdcymr" // & 265 | "veiotsskhfdrpbsvdmplmcssrvivbdruvjbxiashjgbjdgwvliwtrowfgbnnfprdqyforeudkowsyuxj" // & 266 | "dmywkbwbfgshrbbgmmlxiluwmilkqdgescmmcejqymcnwdcqgwhmcagncqjruatilvbextcgunqihwik" // & 267 | "igplwmmlwwmfamfvibfkdhuxgdakxlxpwahemfowxoannwgkbpkiagjvhtampipjhqictjjsidduyjem" // & 268 | "qbhwtehkxinsefndkyfaxcjkhvtormslgjrxuomnamfkerpbgxdtwlgxquqbamyptqnjpatjojxmragv" // & 269 | "qquabmbgjasspditnbrrmxrvjkpmrwacydknrkanwponhlqbtdcgeggnmfbrqtvyproocyrcsdhcslol" // & 270 | "fqjuingrtjashimqvseocbqmfqiavmvodlltltvkcbypnddnqsqmqgjifvnsowgoeqspakgikuhmjlap" // & 271 | "qtfcagwwhaajtrmuudipdbkulshekttnlaltqlyievtwvifxhydyavhpxshmjgevsfkkphdyvqwcejno" // & 272 | "ounejdlinpomfvvfblxqqscnrmjtgwxtefmrsodnfqalrwgqmtirakxgxspkcvoekptqugxlkyvqckjn" // & 273 | "afoiodmnavioxhatmhrdcyxvlnojrkkpeelcfsfhntmdboogymiaxkkanvkpnmhgrritxjxuhqpkkdqc" // & 274 | "ellirvpopnstjaucaxoygeaimhlbuyhyejgohxpibkkdwxkviduprngsesqspxfknkjoskpqicosipok" // & 275 | "iuaprejuaguvemlsqkseyhpioilkrdlykjankktoydhhlobpnybbkfnopdfusybumgsskqnmubrjkcml" // & 276 | "olteqekwafxlxmnmjqbdhgajhrrbnkqmfujxdfutojtsubqkmfebdhytouvfypwexrenjbateshnrubd" // & 277 | "isekaonqandnvseclkmwfkrrxrkliiyvlxusemnthevpdkwlwmfyddotbmkaxjscnjbsrnlbuybfirlg" // & 278 | "xavbnfcihsxxtaxqnpuwcdaouhvqdrtulsrucatawtdethtxceyunmxwgentfvlxfvbvheosxvilcvpy" // & 279 | "muniafexmhbsfsklwdlrxulcjdhyotemepmpheeeltkitjqdrtntujrskilqxwulibnjhswwcfjxmwnb" // & 280 | "lvffueniyurmpqegtujmfdrfwpvusulsukqoyjbxibrajigqfqjcbyxiacjbjchyhicsegptroyuesjv" // & 281 | "odfrmqfmbjhtghnkglnrynkjfjeiujnjabyletcvcjhvmifhgqfhfuvntpcgshgatfenkldkppuxvhti" // & 282 | "vwnlwvfjajcjnsgsknujjpsfxyayjnvtetymnyuvrojgafjreyakekgwjisdnjhhsvpxylqxkwkrpdud" // & 283 | "jciypdrxrpyjaosdvywqiuijwewrkaexedfbortrsqdwvplsnhjbmopxdneomedqpllmnqefctbqjlkp" // & 284 | "jenllinaysxreixboynctlefrmjlkbklpedpgqlwkfbwrmjbabeqxgbfipykfoblasaldbapacsmvyhf" // & 285 | "fofojcafuuarajqkbelbbtiuiowesrcrbxuumplunrolaamyeqhjnnmihuejrawuisvkdwuahtecinbo" // & 286 | "cyeenfolaxihsbaoeupjhrnfbjtacrneegqyacrowwwtgwhlrdawdbocgwjkqxukjwvunmagtlbuxcjq" // & 287 | "oowotlfsxuxbfbpxejwqlqeyypkpykximelfxsnjrwivhxkqpvcabyodnreriidekqhkmllbrqreaieq" // & 288 | "lmfigqmmqioihmatoelhgykcixaoelydhfrjmdmqldkmdtdbrdgqhavpiasjrbfudwpnrovohsljaxrs" // & 289 | "pvvkuqemjgsdbhytqpfwnreurtvrxdshqetgkgdadcnqntvgkllcnprverlodqfxflbvuiepuvwnxjon" // & 290 | "gskakjjroiwaauoqvvpqucumnthcdletwdrgsreadpqvjyonaqcvxqcwfuxgnflamxyaglynhmfgauvs" // & 291 | "llvloaqasrindxsydciaqetekmutadwexqdekqmkplbhdbcblpkrxbyhnyjemidymjimyinvmtqytjpo" // & 292 | "ylnqcwvdfltdheursjegsikodbatpsojjkjgjfrnsbewjpeijpabocbwwkeuceaehfguagyqjfesldpe" // & 293 | "hnqktgwujytpymbnqurbkmhqxfgujoogkbgccqbyhnwphsdveunfjuacksbaqikplvywlldnmyqbponm" // & 294 | "stivotqsxlapxxoghprdqmjguaxqtscleflrmwobwphhrrfstyiyeyhgiqtuelbacdtcvwyescslmkjx" // & 295 | "lgvdhscxfsxgupccawohmmtjfbavbclohotijklpxjucgmnnocidfrimmjttbjagyoxbncdtqumsrgor" // & 296 | "mpolkfiycyycfplajabvpqsqrcwxbkuhiukwqnanbymancjtwmxdukyysbgbuorgovshfoxridafiljq" // & 297 | "sujiqdlargqvmdtrxgmpeyamyjhgldptbplmrnrhpxonkrfwradhkajmwlcxlectxvhaokdfsrsfegmp" // & 298 | "icujynmycqauvysfvalqiqkfexxcmxlnnyijoudleqhxgsvuplmoldgstskkxhwbtyroovstvgmlquaj" // & 299 | "iwmhrrkkillchbvlxiybyhqbprnfqqgcigpkviyxstigjxcfeedermgrdpsfnspenahffjujefpefkwc" // & 300 | "blaswperaivbkrdiabmydvavirdsvfgkqukuvhwcyvligivmylbefvsxsunhnayuowqmbtcgfhcyrujx" // & 301 | "yufunajkruooouxpqhvgecxxmscctgqluxtnfbncxudxigkdmoytaodgqwbcmdvgcyutohswivnhjjyu" // & 302 | "ehinmjihelwbakmwxpjsykcnymeruflyonwimrptuburlewfxbpplnixsyqkmubvcemglftmkrfnaoma" // & 303 | "gkfqqjnrxdqscytrtpwfsyqejsffgceoqlsysgxtaojutpvxikuxohleeaqefgftylbtifyaywdipgsk" // & 304 | "utlhtbdfvjirhsectanlcdgmhucjhdyakwjfuykeowubrwusdiyasgkhbuiojdwheefaahrjikwqxlkt" // & 305 | "vljpcoqeeoiqbslujtaxmbnnyihiwljlubnfgfbquiflhuqaxbidlfpadapckmxboeqsufjikdcocmis" // & 306 | "ywihfpvnodrypfocydvipnegslcowgcjijotahkclcqhappdbkhucnxhaqnwokxxtspwfakwlcveysvy" // & 307 | "hxksaqwpakysklwcibwdhlhybrjbrbakqccfbjueynecjucytiivxfhlgweovuacqsvdidudqbtotcfc" // & 308 | "tpmfookytapvmopdthjnkphdrrucniiaodvjinypadvimkgjngcoxysdwwrsaejembkixmxtbuykfesd" // & 309 | "gradldaotdhsimiooqpwuwqsucytoyocdrxpcebnedpjocpkgsxawpnpxqacmhjctsbutulklvkejwwd" // & 310 | "woopatetmohwkcebjtkxmvcslhomlxykttfivpooxamhwutyhocdkrehplaysmrutumooehdjtdhbpmh" // & 311 | "smevudefwiqjdksoplnljfgxayhcpyhlplvdhvgsebeqqyrwewyxrunqhrdfdchyfpgdubyfbtkrjpxa" // & 312 | "pnvcjbvihwlsbheoolenfjqiqfcncmgxrrfyfursivuuidonggxrrqqiafomghcbkklacebvrtlxhdqx" // & 313 | "vlrqsyfgrvluhkpncbmgdphhlewauqammglicjmbjwsmeosviopfdsirqjytnejkcvffrnymkmuvhpjx" // & 314 | "jlxhwfubluyijnxtlnexdoxhvlusbbyhmwumglhrrebhgatcrlcetxjhqqkdywshivdbwuxvgeuguhnd" // & 315 | "ygwohpidabivdxqnniahuvlthcmysiadlituipistlblnlosacnfrceqbhwtmbbuiucckhpqcoepjxsa" // & 316 | "huqfjwkjvyadjohrxduoddmeunsecuemmrebpddikjcxltahanlusbbnqbjxecaeusfaktkeoicnoikb" // & 317 | "esddmrvfvvrmgjemsegyycbjnhqeijncvrlgomdtavmvmudjdcupdjhredyyrjmwudorcetrngfpcmde" // & 318 | "crijthwtrywqlmyobyrabjwvamtesiotvhqvgvbocolwijbdbtjiqjlxgxgeepyequlnkhnbxevimfjp" // & 319 | "lchujlsuhmcshndoaeujubvymdqvxbjulkwyjjwtjrsfrciubpwnbeqqlvksndtubyqwusovmjsrbmis" // & 320 | "jjmoscrullhgptasxmclmtqegtujojndmhcwyjekbjfkvstukjyimlyjyyyxquvgpditpabyenenwcgs" // & 321 | "ywjsxabvtokoukogkgvekpmjaplgadtccksthmmiyegqnfkriuoeauqgcpkcvsdjnaurhmbhegycofhh" // & 322 | "fayhbtpinjxelwysibqcbseekooqkbohufxbsmbqdjlcvffibljixxehspeogsyigqpbnecbipnopwdb" // & 323 | "nnifaaybhrjulosujsbnoeebmjqllwqlijltjksxrnhdicsxwkjkxramkdqumeqqkarqnvwnafmfhjdj" // & 324 | "fuflvdsjlcmqgstvtfpullctphnxasjogxmybhofdxadmatrawjyufolyxvvrioyebesnanqchnvdoib" // & 325 | "nlnyqysrqekeevemriwqpxxkdpwysutbykypqpvoqqfbmktslxwhmmeutdqrldohcuxhoqahxxsbtpan" // & 326 | "hrqlrqpwhkdclvypvojegtsjrdjwkabipulonnkvfqtbqulpqpsajayxtwwavoluhcrwddwngjltsxrj" // & 327 | "nipydbcvevmokycxhpnudwdnlndxrsptrivtawdregnhhgdlonovqloguwjrmrpyurpxonemtrjbcvih" // & 328 | "wxlrwcujktsddptannbwvqxjqvtaduucaqxgwqfnlsanwbqckollmwjxwdmpjlsxhvnmlpoedtaasfvj" // & 329 | "bqooshykkpsbujtsuekkntuupkpxwdeuousdfrgewhfkbmbgmpfgbqhoxhvgmacgkueegcxanxfqumfs" // & 330 | "iubxppsuggyccbgribnxswxydpdfrfcntpbwnawdyitpcbnmrqlkomgihoyjsgpebjuogghmoddpipdb" // & 331 | "gvdbnqcfephxjfohpxgpnclgabgiwsawomkoghfwlqcrtywefmwwtqcxdiytqttquxbkxmlpqqxpiukk" // & 332 | "cnrhglqilupjamfvsjtegfqohonqxrkuywlfakuembhlprsrcdgigrdkawboyjhdenvrhnnwbhqbfisd" // & 333 | "oxljwkbvqpqpnnvhjlkcqyhjxntfsemwjgygdqybuhetljrkysqtkcppieivyikpjvjecspijlqcrjsy" // & 334 | "beiycluemnscfquaryuoaalhnwftopueffexrbgmpidehcgrylgdmhpjskbnbfvwijnatmsroykhqbyn" // & 335 | "bkgywndmfdsowpekeqemiglurfwavnitartbhgrjewnuuyjmouelojqttonqqybtwjauttmaowwvqdys" // & 336 | "aifjaadbtwjmnohxumlpwtqxwulbrscltcddshtdkcrvmhtljyphklhawiqdfdavajhusnphwigmgnsy" // & 337 | "jpweohgjfdlnnrgpvxhmifvwwfctthbeteteossibdjrurlyawfbqcqfoembafsqwubucuhjyhhwdygg" // & 338 | "dvixkkdadavdouqkrprkflhsfbsufyyqiskpegyfeiejdsytedvwhtryhbftgxpmhpmkstuvdooyvhgl" // & 339 | "xrdgcepoawbkadwlptdpmnqhysrlrpwbhamhjsuntaaougywgmapdvigpyoxpfvjnicfsixnfdbbfuaa" // & 340 | "lcykclndnjhfdcauyegkjvutlcxcxapfdvqssoedosuyrpeoedpbosirbvggbwpgjxepdsmywqhkyvie" // & 341 | "ufuvkkqmkngkoqwoapabduclwejwbidjqwefloglbtyibyloqnlhfnfvbjuslghswsvwoytluqxncbrd" // & 342 | "ttsphfvuwnkvsspcjxwwpnclwxnrjmbnmurcdxbfqgrkbsuptmgpciivhtpbujruynwhceqhffykkkbt" // & 343 | "hcimoqxfndpxngfbfoymjwnkxdedndhsurpqgcvajafoythjpwupivprvaxkbihwljxeqfajtrfrnlts" // & 344 | "ihouqkkipqstaqiwdmlwovvobctsstbgoqopsejqubcyeaduyitdjajwclginbshedrbohsfkvyjskpi" // & 345 | "bbqvomnoqfopusgrdxjyvhseeqofnabrpbekfssvniyfiosnwryeisxahnlypdukpswueywsarkiytcj" // & 346 | "dhciajvtcjyumpgmddxammrxtnnewecrtppqjrgiunlatfycfguuyuycrloswqxtruokqgecwxklimjx" // & 347 | "climjoabdgakkmqadkomremfewxxlcevqgsaihqvlrrcikxjnybdlclrborhybpgdsngliwvhpofcgrr" // & 348 | "liauabnuiawwejktaamowyrxswkcmkfqxddvyocbtieoebtotwxddcaevpcqbffuvxcehmnartgwqqgf" // & 349 | "lgsevrtghuvpfhxnmbkwlaejmlolfovsvnrcbcrtocjrkcmmbxrqqpjmennytftdsdstyfwajqhrsswq" // & 350 | "ganhuiafgssnkpaiacngbxnotgathtchdhjcyexlxjbsqeefjbukpfhsrmqynqoefkrxultjbbtxabue" // & 351 | "mgdgxehnumxwajyrcjkfkuisdsedjlbnfocxgcvvoxagwsrwcnbyyslefumhrapovohbeopkleifsqfq" // & 352 | "txveufxkcxpktgxuelluuukhocddgacameyviptqbhgeowtcdegxbfemjlbljuagqnnprlxwimfinkcv" // & 353 | "rpgydnhsusmbkmipkoeoxmiycqhrumgqmvooibxdioeffcblktyooypsyugryrcsldrobkbowvlblpfg" // & 354 | "ocyfigeyndnimqgoitgercodhsyymmigwmdptluytbbjceaybeheksxshjyqqnbaksjekthtfkovsnem" // & 355 | "nxriivnrqaalsctndqmkbbclaaexdqxhkvrypenjewcadfqavruhicuqqouwvaksnwpqdbaveeoqvwrs" // & 356 | "dsatuulotdmljrfhoucqssnkugtnkossjapffahpvlpdrcanwtsmsgncascsmvjmnmjfqnflfrwjyggb" // & 357 | "irqffiamuwftnmgutceumosatjfdpianadwucvlfywtydoefbqjljgihafuxselakthuppjgsqchedwf" // & 358 | "fjqwrbsmbseluogjaykpaqadocjucnyxqishjkfohfbeupphahvxmdldcsrvebfkfxbukiwvdwqrqmxj" // & 359 | "ksydkjvsvlxmljxyuvuygwmvryjboxissapfgyagbmejrwfebcqmmbbmujndypghpeqwuwngnalnddpp" // & 360 | "ojbpbimbpkclhykhqnqcnblqvjkgfjsyjsgwlalljcvkdxapcmjuoteckhgsscqcshhtvnnlfahqlnvy" // & 361 | "wvafimejpjyyjteefkwwhrbymxqgxtcyteiddqhtemjdxlavsluscdhvgnxxhuiejeuxwiqjpjpkprrc" // & 362 | "dtyechybcaynwyaefqnyxislgofuccexfjpgbciwcvmyhqwtolkqlgnpqxfisnriwqeookifnamsappk" // & 363 | "gvrfdqeqdaydkjlliebianntyccmekhlqvbxcwuiyovfwnlxnxsjtbhfehamwriokoeqkytwatitjupv" // & 364 | "jwpswruqvhssklcikugtnyvgqxdjyhneebvomutsjhqgkdbtdbweiaxhrpmikpnbitbkmdxpvtjjcewx" // & 365 | "rqcmlichqbboyuusqlwmbugfanssbgcrfncqxmhgyyvtmttsyvmyqrpeifstwmtnorqbgtbwoklfecun" // & 366 | "fkuiayltwksxpoirrtiobfalrkclsgnktppsmjsgqpwqijyqupgbraydssfxdviyobjmvifkiljiuqfw" // & 367 | "iuvksimfakytafddefsdvtocllfuijyrpdatutbarjsowegqdfiicjfsvpciudxbygykxocmiagowdpf" // & 368 | "ksvljdiohurgldsqgoilcjbqpxsffnyblweonpavgwmqspubbhxobvaeajpeppsubsardlvnktgjkfel" // & 369 | "beykixarsblpdxqxhlcrfxsyhsbhhogcsmmudcythtyikccgjvukcwydvthlvwhpiosfvbvdsedapgnk" // & 370 | "rpnpyunolgmxablnyuufteatimxdchpasgenptgfibhmsdogljgqlrnjkosspwfakpennvjncuworbxj" // & 371 | "jnnfifhvatovlchferqeciuskdkyhbyetapvyptuybnlpmirwqkcqalrgmktnqufkkewesrcgkjaisad" // & 372 | "mwtvecanbhnpbdnyiyhulyqxbdbxyfpephgmgkbechgckxjcscblakhhnapsitchdfibnlcrcbbtggab" // & 373 | "grujtxbfgcisuhvlfeifnnhrrebqcrqicftwegkplcmhhqprvrwarsuhlsoanvnfpaglkaugdvmowxrp" // & 374 | "nnmwgquxtqsvgaypmqsvlomweevhfjvrvqdgtmbkdmtxosbvxytkuwjwfettyndmngdbkwriapgddwkc" // & 375 | "ptgsiryednqhqynncdjpbsvrqqtgjbpciaomcgttprlmjidywmwukhnpqdforxxinukteleysseforui" // & 376 | "wautchslsxmkfduvooyxhmkojrnbcgyxkwuqinpsahfcjfutcmrqdwwihpylrbxijamumgaxanskltga" // & 377 | "qhblbwuelvsdhshyxjsjrewhdvummduxxvacoxivafbdrlpyncjposcvmvwoxcglkwqypjxqiivlmfof" // & 378 | "hdaqathrunmsiqqkyvtgcdpieurnlvcrmnsmulsuadirufqkwkdbhangmhfrikaxpmtmlucrqqpxbjip" // & 379 | "phjnntibdifbxkldfyjwwypnvxjpicbrodereumgtewlnrdmbhrfgcitilurlcsqfoygskolerxrvjon" // & 380 | "fnulkurtjmgggtalddqybremblovcqhynxlcyaiehhdffmjybqxpwxsianfnwrchunudgnijsqoqbske" // & 381 | "tsspffunpvwqxmqxknbmoifaocnmywesbswaeayvaxbwwpwwwkcnpvnyrhpobtexmvjcjfmwlsflxnmk" // & 382 | "ogjgqyuwvtmnhfyihudpltwpotqognmovbdtetcfgmucyieslsebmxhfwftqkemrcsafnqgdsdwjxofc" // & 383 | "aecvcmqmyyeayfodktlupcfwtepuxcfonbobyserfjcyfsscbivcnktvusqtnnshealhgeylyckwbums" // & 384 | "opihhwlccnxvivutpdjplgoblsnnxftekpyrsilralxncrrvdhxxpyavposlircrrxbiasgfdkgfylak" // & 385 | "rmjtprikqtnjyixsswhhskxrfjhagghvjqbbhkhfvknxewbprvvkvdlhyohijfgscakitdbjvtkyweie" // & 386 | "kwrtotbdxobskiydvxqjyawdfwcwwkihnmgjtlvhhrawqdaekreaodmqvhfxqlnhutuosppfvmsfjucw" // & 387 | "ifgvkhechsoudymonyjxdiqpmxpacwfylyfpswekrmpetahfcyykvvyxovgywwbwixteeojmvmufmnpy" // & 388 | "nlygrkldvlneidvmhyktcjcfetitqnbfxubtsfawmlbwahnvfjhscntdjuhcoswjhuriyiedmjgpyhol" // & 389 | "muoshhosplegknticrochkytwyvcqkmmoilwllbkqltgldkibsxfhwdtssqtdkjitlartnjlhxwkgooj" // & 390 | "aejxgawrnbmlngifnjviwryocingrjviuxmnedgpjmtxysrpraowayorkaauwicmhxtetgcwcqrrqldy" // & 391 | "xkcupdoolvoqumctqcvnuceigvdamrjkdphuglwqapngspnhssiejfhgrsrijwkxjasffekekahkjsyo" // & 392 | "dxrvlgapywwligqvyarbtdidoxmogglxtbdrcpdqyoctlilmwkicrppqushhlylgieyvktomayyjxnxb" // & 393 | "xeaphuxmwwpnugefolewakihsatbljemtwwdgovxbkqmgbuayxdeerbduafnflpsphmycqdpmuxakabg" // & 394 | "ebdfdjvghxnmeysdchrqjoqawjkibsciscdlgqiuqcdkanslefdnjtkxijdjwppgmrkeydaodpscrmhs" // & 395 | "tnorasarqrauwgphpfargsigkqsdwagpiqrwwhqcjapcpbqhcxqbpytvvbtcvhbsrddfqemrcwjwjvbl" // & 396 | "weackbyqukhtoftervtoqqnecxmttfqxwhutrbsleqflmufbqxhceorcithtrdbhcetxgtatuiokxlry" // & 397 | "wicjlubbwwdwrbytsnuihdqhoedwcoutjtudbhbvuufcibybmpuqtvqchoiwkkakytwlegkqavfwiryf" // & 398 | "ncupsolrpinqawkalpitrjutxnjqkjgqxamwkmhwyfwjuvpbgddsnrqienfdpesgntnnbxhqdbhnwllt" // & 399 | "fvrebymryxcbdwjgkkyemgjryxcdhfuncmpfiddvtbifoumgcytarpjseqqbuueimmrdbdrctntroygy" // & 400 | "xldcktvxpdldrfbqibajosmwmekqdlcalqelxtvgtidvrtuhhcwedgpppakehgmrnjdhbiavkqdtfglq" // & 401 | "kvawyuvlqbnakmnmwrufrkbkbsuuqagfypllkbcfjtvmpqjbhurubtpkfagnaoohwjrxysqxuaxcjuss" // & 402 | "bpqucnfrtyoosjubeyyyuoyxnwubfngschethujxiopbxehcuxbmsjhjvovjlsbndhoccgrxdqtvqbfy" // & 403 | "paxawdiyndxfhuevpjgjcgatgwlxxusdidiorbpewttfvntktwvgbuhbfxwrjfjdklpxcwkcbwwwefsd" // & 404 | "bvksujpcrkqsfaplvdativyjvuengcnuetluxjkxfhqnvhhlplfderostuoklkkjtqfcpinamdkstuoo" // & 405 | "aaemsgqwepsgmmmbtlhehtevboplthvnlhkvhhxryjgdqqjwjttdrittfaqpdhikknkjapshpbewwbds" // & 406 | "jnyvxhmjlvaqssixilesomgjdknsirdewqyrgqhjctupyusxmdvcgdikwsjmkmwhblgvjodjvdajtwhi" // & 407 | "jmmkgovsnopgewhcdudcybwxdpkppiubsvvrgosnptpmxywnuwpvobsdxofdgihypqrrjsmxadgprwye" // & 408 | "nrbdtbrrxvyvhcdbgpnvmotrvadrtygdotihxyeiidlcibcadhxedgmnngqnevvdjfepwygttgliernp" // & 409 | "bkxehtpjdmnyvsprogbucdttqxsyiybfduovhurufqmachigaikiwhrikprkufdykeqiagludpurhesi" // & 410 | "gemqfyadpesjvvyqqnvdckspjmestcjosuvrggarfpwyxjvdaloysbibdnbigmnkxfdamycwfadrjntx" // & 411 | "bcoofmkcdvmguehkhcwjpwkgaykbythsqxuucdojkasunqchqjxkhphsresdpudxxxfvoqecavtdxkra" // & 412 | "ndnexwcelmwlyketxbsfvbyvaypidfkhrbtmkusghxanbouerexuigpnsaqaewiljsbsfkoqsqbsxmwg" // & 413 | "xuflwnelcywopoeurogcliupipngtrrrniptqhpvkbhowfkdufgyeexapgwvgmdeibsegptngxuijhme" // & 414 | "xbmmbhelngkaxobydxoetrsmlavwhdqwqywilfueyrpukadlgnnruhhqberrshqjxttjjomgbakjfjfn" // & 415 | "uddtndboqmtlkrnhjpmrfdmqkfenitihxlafynqbucoykxeospditmmgjuyqrgoralywrgfvwfqyywts" // & 416 | "acwyokpxybrcxfteyfcjildbbxdhrsdbgvjutpynndcycusgwwjivtuivqbohpxiutvflwifkkpkmkyv" // & 417 | "isditdkrlxdhfcurmohnavryigvipnjldyheyctkdrbnwnmbxrwlgtjuxiecnrppiibwacwbnayfwsuq" // & 418 | "kmhtiejglqwmhxfhhulennwmyblvbliavhhxmhmugmhdchnmhcvtxxfadjintfcbhxtsrxkvyaeahmsv" // & 419 | "tilpjkwrgyeljwfsfgqogahlsnrgunxyrbxcmiybcpdpgxymvenhpelsqcjuuofwrmkqrjkqtuddnnoc" // & 420 | "trvvwgejixcjhhctopasnadbkeatwbtmfbnvnlgxgkwdgisygwnohslihuelwtwxbbhmowthxgqybtmf" // & 421 | "xdioepkbhlbicunhysxokxeyqnjgsbskohnenxbabxnqrjqgpygniutqpxfpaojpavtvgtyegkdxpcfy" // & 422 | "tetobywtwqcasxlwlqgbyigjolhgdvfswmvebhhvahmunav" 423 | 424 | contains 425 | 426 | ! Convert a C hex string to an integer 427 | integer(8) function c_strtol(string) 428 | use iso_c_binding 429 | character(kind=c_char, len=*), intent(in), target :: string 430 | interface 431 | integer(c_long) function strtol(str, endptr, base) bind(C,name="strtol") 432 | use iso_c_binding 433 | type(c_ptr), intent(in), value :: str ! address of a string 434 | type(c_ptr), intent(inout) :: endptr 435 | integer(c_int), intent(in), value :: base 436 | end function strtol 437 | end interface 438 | 439 | type(c_ptr) :: strend,str1 440 | 441 | strend = c_null_ptr 442 | str1 = c_loc(string) 443 | c_strtol = strtol(str1,strend,base=16_c_int) 444 | 445 | end function c_strtol 446 | 447 | ! Read in results from test data in tiny-regex-c, convert to an array 448 | subroutine read_test2(fileName) 449 | use iso_c_binding 450 | character(*), intent(in) :: fileName 451 | 452 | integer :: iunit,ierr,i,last 453 | character(len=4) :: line(16) 454 | character(kind=c_char,len=5) :: forc 455 | character(kind=RCK,len=:), allocatable :: whole 456 | 457 | 458 | open(newunit=iunit,file=fileName,form='formatted',action='read',iostat=ierr) 459 | 460 | allocate(character(len=4*1000000) :: whole) 461 | 462 | 463 | last = 0 464 | do while (.not.is_iostat_end(ierr)) 465 | read(iunit,2,iostat=ierr) line 466 | do i=1,16 467 | if (len_trim(line(i))<=0) exit 468 | forc = line(i)(1:4)//c_null_char 469 | last = last+1 470 | 471 | ! Now turn to chars 472 | whole(last:last) = achar(c_strtol(forc)) 473 | 474 | end do 475 | 476 | end do 477 | 478 | close(iunit) 479 | 480 | whole = whole(1:last) 481 | 482 | ! Print to output 483 | open(newunit=iunit,file='testdata.f90',form='formatted',action='write',iostat=ierr) 484 | 485 | write(iunit,1) 486 | 487 | last = 0 488 | do while (last0 7 | character(len=*,kind=RCK), parameter :: testMdata(3,91) = reshape([ character(len=30) :: & 8 | "Foo", "FooBar", "YES", & 9 | "Poo", "FooBar", "NO " , & 10 | "Bar", "FooBar", "YES", & 11 | "Par", "FooBar", "NO " , & 12 | "Foo", "Foo", "YES", & 13 | "Fo", "Foo", "YES", & 14 | "Foo", "Fo", "NO " , & 15 | "ooB", "FooBar", "YES", & 16 | "ooP", "FooBar", "NO " , & 17 | ".", "FooBar", "YES", & 18 | "P.", "FooBar", "NO " , & 19 | "^Foo", "FooBar", "YES", & 20 | "^Bar", "FooBar", "NO " , & 21 | "Foo$", "FooBar", "NO " , & 22 | "Bar$", "FooBar", "YES", & 23 | ".*o", "FooBar", "YES", & 24 | "o*o", "FooBar", "YES", & 25 | "P*o", "FooBar", "YES", & 26 | "Fo*o", "FooBar", "YES", & 27 | "Po*o", "FooBar", "NO " , & 28 | "F[po]o", "FooBar", "YES", & 29 | "F[op]o", "FooBar", "YES", & 30 | "F[qp]o", "FooBar", "NO " , & 31 | "F[^po]o", "FooBar", "NO " , & 32 | "F[^op]o", "FooBar", "NO " , & 33 | "F[^qp]o", "FooBar", "YES", & 34 | "F[po]*o", "FooBar", "YES", & 35 | "F[56]*o", "F5oBar", "YES", & 36 | "F[46]*o", "F5oBar", "NO " , & 37 | "F[46]*5", "F5oBar", "YES", & 38 | "F[46]*5o", "F5oBar", "YES", & 39 | "F[op]*o", "FooBar", "YES", & 40 | "F[qp]*o", "FooBar", "YES", & 41 | "P[qp]*o", "FooBar", "NO " , & 42 | "F[^po]*o", "FooBar", "YES", & 43 | "F[^op]*o", "FooBar", "YES", & 44 | "F[^qp]*o", "FooBar", "YES", & 45 | "P[^qp]*o", "FooBar", "NO " , & 46 | "[0-9][0-9]*$", "0123456789", "YES" , & 47 | "[0-9][0-9]*$", "A0123456789", "YES" , & 48 | "^[0-9][0-9]*$", "A0123456789", "NO ", & 49 | "^[0-9][0-9]*$", "", "NO ", & 50 | "^[0-9]$", "", "NO ", & 51 | "^[0-9]*$", "", "YES" , & 52 | "^$", "", "YES", & 53 | "^$", " ", "NO ", & 54 | "^[A-Z ][A-Z ]*$", "", "NO ", & 55 | "^[ ]*[A-Z][A-Z ]*$", " THIS IS ALL UPPERCASE", "YES", & 56 | "^[ ]*[a-z][a-z ]*$", " this is all lowercase", "YES", & 57 | "^[ ]*[A-Z][A-Z ]*$", " THIS IS not ALL UPPERCASE", "NO " , & 58 | "^[ ]*[a-z][a-z ]*$", " this is NOT all lowercase", "NO " , & 59 | "X[-+]Y", "X-Y", "YES", & 60 | "X[-+]Y", "X+Y", "YES", & 61 | "X[+-]Y", "X-Y", "YES", & 62 | "X[+-]Y", "X+Y", "YES", & 63 | "X[-+]Y", "Y-X", "NO ", & 64 | "X[-+]Y", "Y+X", "NO ", & 65 | "X[+-]Y", "Y-X", "NO ", & 66 | "X[+-]Y", "Y+X", "NO ", & 67 | "X"//TAB//"Y", "X"//TAB//"Y", "YES", & 68 | "X["//TAB//"ab]Y", "X"//TAB//"Y", "YES", & 69 | "X["//TAB//"ab]Y", "XtY", "NO ", & 70 | "X["//TAB//"ab]Y", "XaY", "YES", & 71 | "[0-9][0-9]*\.[0-9]*", "1.9", "YES", & 72 | "[0-9][0-9]*\.[0-9]*", "1.99", "YES", & 73 | "[0-9][0-9]*\.[0-9]*", "1.999", "YES", & 74 | "[0-9][0-9]*\.[0-9]*", "1.9999", "YES", & 75 | "[0-9][0-9]*\.[0-9]*", "1.99999", "YES", & 76 | "[0-9][0-9]*\.[0-9]*", "11.99999", "YES", & 77 | "[0-9][0-9]*\.[0-9]*", "111.99999", "YES", & 78 | "[0-9][0-9]*\.[0-9]*", "1111.99999", "YES", & 79 | "[0-9][0-9]*\.[0-9]*", "11111.99999", "YES", & 80 | "[0-9][0-9]*\.[0-9]*", "123456.99999", "YES", & 81 | "^[0-9][0-9]*\.[0-9]*", "1.9", "YES", & 82 | "^[0-9][0-9]*\.[0-9]*", "1.99", "YES", & 83 | "^[0-9][0-9]*\.[0-9]*", "1.999", "YES", & 84 | "^[0-9][0-9]*\.[0-9]*", "1.9999", "YES", & 85 | "^[0-9][0-9]*\.[0-9]*", "1.99999", "YES", & 86 | "^[0-9][0-9]*\.[0-9]*", "11.99999", "YES", & 87 | "^[0-9][0-9]*\.[0-9]*", "111.99999", "YES", & 88 | "^[0-9][0-9]*\.[0-9]*", "1111.99999", "YES", & 89 | "^[0-9][0-9]*\.[0-9]*", "11111.99999", "YES", & 90 | "^[0-9][0-9]*\.[0-9]*", "111111.99999", "YES", & 91 | "a[0-9][0-9]*\.[0-9]*", "a1.9", "YES", & 92 | "a[0-9][0-9]*\.", "a1.9", "YES", & 93 | "a[0-9][0-9]*", "a1.9", "YES", & 94 | "a", "a1.9", "YES", & 95 | "\\", "\", "YES", & 96 | "\.", "\", "NO " , & 97 | ".", "\", "YES", & 98 | "F[qpo", "FooBar", "NO "],[3,91]) 99 | 100 | ! These cases have C-specific characters and need be defined 101 | 102 | contains 103 | 104 | subroutine get_m_test(itest,valid,pattern,string) 105 | integer, intent(in) :: itest 106 | logical, intent(out) :: valid 107 | character(*), intent(out) :: pattern,string 108 | 109 | if (.not.(itest>0 .and. itest<=size(testMdata,2))) return 110 | 111 | valid = trim(testMdata(3,itest))=='YES' 112 | pattern = testMdata(1,itest) 113 | string = testMdata(2,itest) 114 | 115 | end subroutine get_m_test 116 | 117 | logical function run_m_test(valid,pattern,string) result(success) 118 | logical, intent(in) :: valid 119 | character(*), intent(in) :: pattern 120 | character(*), intent(in) :: string 121 | 122 | integer :: idx,length 123 | type(regex_pattern) :: re 124 | 125 | print "('regex test: pattern=',a,' string=',a,'....')",trim(pattern),trim(string) 126 | 127 | idx = regex(string, pattern, length) 128 | 129 | ! This test does not check the length of the match 130 | if (.not.valid) then 131 | success = idx<=0 132 | else 133 | success = idx>0 .or. (idx==0 .and. len(string)==0) 134 | end if 135 | 136 | if (.not.success) then 137 | write(*,*) 'FAILED: regex result: idx=',idx,' length=',length,' expected valid = ',valid 138 | re = parse_pattern(pattern) 139 | print *, ' ...pattern breakdown: ' 140 | call re%write() 141 | endif 142 | 143 | end function run_m_test 144 | 145 | 146 | end module regex_test_m_regex 147 | -------------------------------------------------------------------------------- /test/tests.f90: -------------------------------------------------------------------------------- 1 | program tests 2 | use regex_module 3 | use regex_test_1 4 | use regex_test_2 5 | use regex_test_m_regex 6 | use iso_fortran_env, only: output_unit 7 | implicit none 8 | 9 | integer :: nfailed = 0 10 | integer :: npassed = 0 11 | 12 | integer :: i,length 13 | logical :: valid 14 | character(len=30) :: pattern,str 15 | 16 | 17 | ! Test #1 18 | do i=1,size(test1data,2) 19 | call get_test1(i,valid,pattern,str,length) 20 | call add_test(run_test1(valid,pattern,trim(str),length)) 21 | end do 22 | 23 | ! Test m_regex 24 | do i=1,size(testMdata,2) 25 | call get_m_test(i,valid,pattern,str) 26 | call add_test(run_m_test(valid,trim(pattern),trim(str))) 27 | end do 28 | 29 | ! Test #3 30 | call add_test(test_invalid()) 31 | call add_test(test_main()) 32 | call add_test(test_bracket_space()) 33 | call add_test(test_end_anchor()) 34 | call add_test(test_end_anchor2()) 35 | call add_test(test_read_version()) 36 | 37 | ! Test #2 38 | call add_test(run_test2()) 39 | 40 | if (nfailed<=0) then 41 | print "(*(a,:,i0))", 'SUCCESS! all ',npassed,' tests passed.' 42 | stop 0 43 | else 44 | print "(*(a,:,i0))", 'ERROR: ',nfailed,' tests failed, ',npassed,' passed.' 45 | stop 1 46 | end if 47 | 48 | 49 | contains 50 | 51 | subroutine add_test(successful_test) 52 | logical, intent(in) :: successful_test 53 | if (successful_test) then 54 | npassed = npassed+1 55 | else 56 | nfailed = nfailed+1 57 | end if 58 | end subroutine add_test 59 | 60 | ! Test two bug patterns reported by @DavidKorczynski in https://github.com/kokke/tiny-regex-c/issues/44 61 | logical function test_invalid() result(success) 62 | 63 | type(regex_pattern) :: re 64 | 65 | ! Test 1: inverted set without a closing ']' 66 | re = parse_pattern("\\\x01[^\\\xff][^") 67 | success = re%n==0; if (.not.success) return 68 | 69 | ! Test 1: inverted set without a closing ']' 70 | re = parse_pattern("\\\x01[^\\\xff][\\") 71 | success = re%n==0; if (.not.success) return 72 | 73 | end function test_invalid 74 | 75 | logical function test_main() result(success) 76 | use regex_module 77 | implicit none 78 | 79 | character(*), parameter :: text = 'table football' 80 | 81 | success = check_pattern(text,'foo*',expected="foo") 82 | if (.not.success) return 83 | 84 | end function test_main 85 | 86 | logical function test_bracket_space() result(success) 87 | use regex_module 88 | implicit none 89 | 90 | character(*), parameter :: text = 'table football' 91 | 92 | success = check_pattern(text,'e[ ]f',expected="e f") 93 | if (.not.success) return 94 | 95 | success = check_pattern(text,'e[ ]+f',expected="e f") 96 | if (.not.success) return 97 | 98 | 99 | end function test_bracket_space 100 | 101 | logical function test_end_anchor() result(success) 102 | use regex_module 103 | implicit none 104 | 105 | character(*), parameter :: text = 'table football' 106 | 107 | success = check_pattern(text,'ll$',expected="ll") 108 | if (.not.success) return 109 | 110 | success = check_pattern(text,'l$',expected="l") 111 | if (.not.success) return 112 | 113 | end function test_end_anchor 114 | 115 | logical function test_end_anchor2() result(success) 116 | use regex_module 117 | implicit none 118 | 119 | character(*), parameter :: text = 'Avida Dollar$' 120 | 121 | success = check_pattern(text,'[A-Z][a-z]+$',expected="") 122 | if (.not.success) return 123 | 124 | end function test_end_anchor2 125 | 126 | logical function test_read_version() result(success) 127 | character(*), parameter :: & 128 | text = 'Intel(R) MPI Library 2021.8 for Linux*Copyright Intel Corporation.ifort version 2021.8.0' 129 | 130 | success = check_pattern(text,'\d+\.\d+\.\d+',expected="2021.8.0") 131 | 132 | end function test_read_version 133 | 134 | end program tests 135 | --------------------------------------------------------------------------------