├── .luacov ├── .luacheckrc ├── .lua-format ├── rockspecs ├── regex-scm-1.rockspec ├── regex-0.1.0-1.rockspec ├── regex-0.1.1-1.rockspec └── regex-0.2.0-1.rockspec ├── .github └── workflows │ └── test.yml ├── README.md ├── regex.lua └── test └── regex_test.lua /.luacov: -------------------------------------------------------------------------------- 1 | codefromstrings = false 2 | runreport = true 3 | deletestats = true 4 | modules = { 5 | regex = 'regex.lua', 6 | } 7 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = 'max' 2 | include_files = { 3 | 'regex.lua', 4 | 'test/*_test.lua', 5 | } 6 | ignore = { 7 | 'assert', 8 | -- unused argument 9 | '212', 10 | } 11 | 12 | -------------------------------------------------------------------------------- /.lua-format: -------------------------------------------------------------------------------- 1 | break_after_table_lb: true 2 | break_before_table_rb: false 3 | break_before_functioncall_rp: true 4 | break_before_functiondef_rp: true 5 | chop_down_table: true 6 | extra_sep_at_table_end: true 7 | keep_simple_control_block_one_line: false 8 | keep_simple_function_one_line: false 9 | column_table_limit: 1 10 | -------------------------------------------------------------------------------- /rockspecs/regex-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "regex" 2 | version = "scm-1" 3 | source = { 4 | url = "git+https://github.com/mah0x211/lua-regex.git", 5 | } 6 | description = { 7 | summary = "simple regular expression module for lua", 8 | homepage = "https://github.com/mah0x211/lua-regex", 9 | license = "MIT/X11", 10 | maintainer = "Masatoshi Fukunaga", 11 | } 12 | dependencies = { 13 | "lua >= 5.1", 14 | "pcre2", 15 | "metamodule >= 0.5.0", 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | regex = "regex.lua", 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /rockspecs/regex-0.1.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "regex" 2 | version = "0.1.0-1" 3 | source = { 4 | url = "git+https://github.com/mah0x211/lua-regex.git", 5 | tag = "v0.1.0" 6 | } 7 | description = { 8 | summary = "simple regular expression module for lua", 9 | homepage = "https://github.com/mah0x211/lua-regex", 10 | license = "MIT/X11", 11 | maintainer = "Masatoshi Fukunaga" 12 | } 13 | dependencies = { 14 | "lua >= 5.1", 15 | "pcre2 >= 0.1.0", 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | regex = "regex.lua" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rockspecs/regex-0.1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "regex" 2 | version = "0.1.1-1" 3 | source = { 4 | url = "git+https://github.com/mah0x211/lua-regex.git", 5 | tag = "v0.1.1", 6 | } 7 | description = { 8 | summary = "simple regular expression module for lua", 9 | homepage = "https://github.com/mah0x211/lua-regex", 10 | license = "MIT/X11", 11 | maintainer = "Masatoshi Fukunaga", 12 | } 13 | dependencies = { 14 | "lua >= 5.1", 15 | "pcre2 >= 0.1.0", 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | regex = "regex.lua", 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /rockspecs/regex-0.2.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "regex" 2 | version = "0.2.0-1" 3 | source = { 4 | url = "git+https://github.com/mah0x211/lua-regex.git", 5 | tag = "v0.2.0", 6 | } 7 | description = { 8 | summary = "simple regular expression module for lua", 9 | homepage = "https://github.com/mah0x211/lua-regex", 10 | license = "MIT/X11", 11 | maintainer = "Masatoshi Fukunaga", 12 | } 13 | dependencies = { 14 | "lua >= 5.1", 15 | "pcre2 >= 0.1.0", 16 | "metamodule >= 0.5.0", 17 | } 18 | build = { 19 | type = "builtin", 20 | modules = { 21 | regex = "regex.lua", 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | - 'LICENSE' 8 | 9 | 10 | jobs: 11 | luacheck: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - 15 | name: Checkout 16 | uses: actions/checkout@v2 17 | - 18 | name: Setup Lua 19 | uses: leafo/gh-actions-lua@v9 20 | - 21 | name: Setup Luarocks 22 | uses: leafo/gh-actions-luarocks@v4 23 | - 24 | name: Install Tools 25 | run: luarocks install luacheck 26 | - 27 | name: Run luacheck 28 | run: | 29 | luacheck . 30 | 31 | test: 32 | runs-on: ubuntu-latest 33 | strategy: 34 | matrix: 35 | lua-version: 36 | - "5.1" 37 | - "5.2" 38 | - "5.3" 39 | - "5.4" 40 | steps: 41 | - 42 | name: Checkout 43 | uses: actions/checkout@v2 44 | with: 45 | submodules: 'true' 46 | - 47 | name: Setup Lua ${{ matrix.lua-version }} 48 | uses: leafo/gh-actions-lua@v9 49 | with: 50 | luaVersion: ${{ matrix.lua-version }} 51 | - 52 | name: Setup Luarocks 53 | uses: leafo/gh-actions-luarocks@v4 54 | - 55 | name: Install Tools 56 | run: | 57 | luarocks install testcase 58 | luarocks install luacov 59 | - 60 | name: Install 61 | run: | 62 | luarocks make 63 | - 64 | name: Run Test 65 | run: | 66 | testcase --coverage ./test/ 67 | - 68 | name: Upload coverage to Codecov 69 | uses: codecov/codecov-action@v4.0.1 70 | with: 71 | token: ${{ secrets.CODECOV_TOKEN }} 72 | flags: unittests 73 | 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-regex 2 | 3 | [![test](https://github.com/mah0x211/lua-regex/actions/workflows/test.yml/badge.svg)](https://github.com/mah0x211/lua-regex/actions/workflows/test.yml) 4 | [![codecov](https://codecov.io/gh/mah0x211/lua-regex/branch/master/graph/badge.svg)](https://codecov.io/gh/mah0x211/lua-regex) 5 | 6 | 7 | simple regular expression module for lua. 8 | 9 | 10 | ## Installation 11 | 12 | ```sh 13 | luarocks install regex 14 | ``` 15 | 16 | 17 | *** 18 | 19 | 20 | ## re, err = regex.new( pattern [, flgs] ) 21 | 22 | creates a new regex object. 23 | 24 | **Parameters** 25 | 26 | - `pattern:string`: string containing expression to be compiled. 27 | - `flgs:string`: regular expression flags that can be combined with any of the following characters. 28 | - `i`: Do caseless matching. 29 | - `s`: `.` matches anything including NL. 30 | - `m`: `^` and `$` match newlines within data. 31 | - `u`: Treat pattern and subjects as UTF strings. 32 | - `U`: Do not check the pattern for `UTF` valid. 33 | - `x`: Ignore white space and `#` comments. 34 | - `o`: compile-once mode that caching a compiled regex. 35 | - `g`: global match. 36 | - `j`: enable JIT compilation. 37 | 38 | **Returns** 39 | 40 | - `re:table`: regex object. 41 | - `err:string`: error message. 42 | 43 | **Example** 44 | 45 | ```lua 46 | local regex = require('regex') 47 | local re, err = regex.new('a(b+)(c+)', 'i') 48 | if re then 49 | local arr, err = re:match('ABBBCCC') 50 | if arr then 51 | print(arr[1]) -- 'ABBBCCC' 52 | print(arr[2]) -- 'BBB' 53 | print(arr[3]) -- 'CCC' 54 | else 55 | print(err) 56 | end 57 | else 58 | print(err) 59 | end 60 | ``` 61 | 62 | 63 | 64 | ## arr, err = regex:match( sbj [, offset] ) 65 | 66 | matches a compiled regular expression against a given subject string. It returns matched substrings. 67 | 68 | **Parameters** 69 | 70 | - `sbj:string`: the subject string. 71 | - `offset:number`: offset in the subject at which to start matching. 72 | 73 | **Returns** 74 | 75 | - `arr:table`: array of matched substrings. 76 | - `err:string`: error message. 77 | 78 | 79 | ## arr, err = regex:matches( sbj [, offset] ) 80 | 81 | almost same as `match` method but it returns all matched substrings **except capture strings**. 82 | 83 | **Parameters** 84 | 85 | - `sbj:string`: the subject string. 86 | - `offset:number`: offset in the subject at which to start matching. 87 | 88 | **Returns** 89 | 90 | - `arr:table`: array of matched substrings. 91 | - `err:string`: error message. 92 | 93 | 94 | ## arr, err = regex:indexof( sbj [, offset] ) 95 | 96 | almost same as `match` method but it returns offsets of matched substrings. 97 | 98 | **Parameters** 99 | 100 | - `sbj:string`: the subject string. 101 | - `offset:number`: offset in the subject at which to start matching. 102 | 103 | **Returns** 104 | 105 | - `arr:table`: array of offsets of matched substrings. 1st index is the start offset of matched substring, and 2nd index is the end offset of matched substring, and 3rd index is the start offset of 1st capture string, and 4th index is the end offset of 1st capture string, and so on. 106 | - `err:string`: error message. 107 | 108 | 109 | ## arr, err = regex:indexesof( sbj [, offset] ) 110 | 111 | almost same as `match` method but it returns all offsets of matched substrings **except capture strings**. 112 | 113 | **Parameters** 114 | 115 | - `sbj:string`: the subject string. 116 | - `offset:number`: offset in the subject at which to start matching. 117 | 118 | **Returns** 119 | 120 | - `arr:table`: array of offsets of matched substrings. 1st index is the start offset of matched substring, and 2nd index is the end offset of matched substring, and so on. 121 | - `err:string`: error message. 122 | 123 | 124 | ## ok, err = regex:test( sbj [, offset] ) 125 | 126 | returns true if there is a matched. 127 | 128 | **Parameters** 129 | 130 | - `sbj:string`: the subject string. 131 | - `offset:number`: offset in the subject at which to start matching. 132 | 133 | **Returns** 134 | 135 | - `ok:boolean`: true on matched. 136 | - `err:string`: error message. 137 | 138 | 139 | 140 | ## Static Methods 141 | 142 | 143 | ## arr, err = regex.match( sbj, pattern [, flgs [, offset]] ) 144 | 145 | same as the following code: 146 | 147 | ```lua 148 | local re, err = regex.new( pattern, flgs ) 149 | if re then 150 | return re:match( sbj, offset ) 151 | end 152 | return nil, err 153 | ``` 154 | 155 | 156 | ## arr, err = regex.matches( sbj, pattern [, flgs [, offset]] ) 157 | 158 | same as the following code: 159 | 160 | ```lua 161 | local re, err = regex.new( pattern, flgs ) 162 | if re then 163 | return re:matches( sbj, offset ) 164 | end 165 | return nil, err 166 | ``` 167 | 168 | 169 | ## arr, err = regex.indexof( sbj, pattern [, flgs [, offset]] ) 170 | 171 | same as the following code: 172 | 173 | ```lua 174 | local re, err = regex.new( pattern, flgs ) 175 | if re then 176 | return re:indexof( sbj, offset ) 177 | end 178 | return nil, err 179 | ``` 180 | 181 | 182 | ## arr, err = regex.indexesof( sbj, pattern [, flgs [, offset]] ) 183 | 184 | same as the following code: 185 | 186 | ```lua 187 | local re, err = regex.new( pattern, flgs ) 188 | if re then 189 | return re:indexesof( sbj, offset ) 190 | end 191 | return nil, err 192 | ``` 193 | 194 | 195 | ## ok, err = regex.test( sbj, pattern [, flgs [, offset]] ) 196 | 197 | same as the following code: 198 | 199 | ```lua 200 | local re, err = regex.new( pattern, flgs ) 201 | if re then 202 | return re:test( sbj, offset ) 203 | end 204 | return nil, err 205 | ``` 206 | 207 | -------------------------------------------------------------------------------- /regex.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (C) Masatoshi Fukunaga 3 | -- 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 5 | -- of this software and associated documentation files (the "Software"), to deal 6 | -- in the Software without restriction, including without limitation the rights 7 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | -- copies of the Software, and to permit persons to whom the Software is 9 | -- furnished to do so, subject to the following conditions: 10 | -- 11 | -- The above copyright notice and this permission notice shall be included in 12 | -- all copies or substantial portions of the Software. 13 | -- 14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | -- THE SOFTWARE. 21 | -- 22 | -- regex.lua 23 | -- lua-regex 24 | -- Created by Masatoshi Teruya on 17/06/01. 25 | -- 26 | --- file scope variables 27 | local unpack = unpack or table.unpack 28 | local sub = string.sub 29 | local type = type 30 | local pcre2 = require('pcre2') 31 | --- constants 32 | local CFLG2OPT_LUT = { 33 | i = pcre2.CASELESS, -- : Do caseless matching. 34 | s = pcre2.DOTALL, -- : `.` matches anything including NL. 35 | m = pcre2.MULTILINE, -- : `^` and `$` match newlines within data. 36 | u = pcre2.UTF, -- : Treat pattern and subjects as UTF strings. 37 | x = pcre2.EXTENDED, -- : Ignore white space and `#` comments 38 | } 39 | --- static variables 40 | local RECACHE = setmetatable({}, { 41 | __mode = 'v', 42 | }) 43 | 44 | --- flags2opts 45 | --- @param flags string 46 | --- @return table opts 47 | --- @return boolean global 48 | --- @return boolean cache 49 | --- @return boolean jit 50 | local function flags2opts(flags) 51 | local opts = {} 52 | local global = false 53 | local cache = false 54 | local jit = false 55 | local nopt = 0 56 | 57 | for i = 1, #flags do 58 | local flg = sub(flags, i, i) 59 | 60 | if flg == 'j' then 61 | -- jit compile flag 62 | jit = true 63 | elseif flg == 'g' then 64 | -- global flag 65 | global = true 66 | elseif flg == 'o' then 67 | -- compile-once mode flag 68 | cache = true 69 | elseif flg == 'U' then 70 | -- do not check the pattern for UTF valid. 71 | -- only relevant if UTF option is set. 72 | opts[nopt + 1] = pcre2.UTF 73 | opts[nopt + 2] = pcre2.NO_UTF_CHECK 74 | nopt = nopt + 2 75 | else 76 | local opt = CFLG2OPT_LUT[flg] 77 | if not opt then 78 | -- invalid flag 79 | error(('unknown flag %q'):format(flg)) 80 | end 81 | 82 | -- add option 83 | nopt = nopt + 1 84 | opts[nopt] = opt 85 | end 86 | end 87 | 88 | return opts, global, cache, jit 89 | end 90 | 91 | --- @class regex 92 | --- @field p pcre2 93 | --- @field global boolean 94 | --- @field lastidx integer 95 | local Regex = {} 96 | 97 | --- init 98 | --- @param pattern string 99 | --- @param flags string? 100 | --- @return regex? regex 101 | --- @return any err 102 | function Regex:init(pattern, flags) 103 | assert(type(pattern) == 'string', 'pattern must be string') 104 | assert(flags == nil or type(flags) == 'string', 105 | 'flags must be string or nil') 106 | if not flags then 107 | flags = '' 108 | end 109 | 110 | -- parse flags 111 | local cache_key = pattern .. '@' .. flags 112 | local opts, global, cache, jit = flags2opts(flags) 113 | -- check the cache table 114 | if cache then 115 | -- return the cached object if exists 116 | local re = RECACHE[cache_key] 117 | if re then 118 | return re 119 | end 120 | end 121 | 122 | -- compile pattern 123 | local p, err = pcre2.new(pattern, unpack(opts)) 124 | if not p then 125 | return nil, err 126 | elseif jit then 127 | -- jit compile 128 | local ok 129 | ok, err = p:jit_compile() 130 | if not ok then 131 | return nil, err 132 | end 133 | end 134 | self.p = p 135 | self.global = global 136 | self.lastidx = 0 137 | 138 | -- save into cache table 139 | if cache then 140 | RECACHE[cache_key] = self 141 | end 142 | 143 | return self 144 | end 145 | 146 | --- matches 147 | --- @param sbj string 148 | --- @param offset integer 149 | --- @return string[]? arr 150 | --- @return any err 151 | function Regex:matches(sbj, offset) 152 | local head, tail, err = self.p:match_nocap(sbj, offset) 153 | 154 | if head then 155 | local arr = {} 156 | local idx = 1 157 | 158 | while head do 159 | arr[idx] = sub(sbj, head, tail) 160 | idx = idx + 1 161 | head, tail, err = self.p:match_nocap(sbj, tail) 162 | end 163 | if err then 164 | return nil, err 165 | end 166 | 167 | return arr 168 | end 169 | 170 | return nil, err 171 | end 172 | 173 | --- match 174 | --- @param sbj string 175 | --- @param offset integer? 176 | --- @return string[]? arr 177 | --- @return any err 178 | function Regex:match(sbj, offset) 179 | local heads, tails, err = self.p:match(sbj, offset or self.lastidx) 180 | 181 | if heads then 182 | -- found 183 | local arr = {} 184 | for i = 1, #heads do 185 | arr[i] = sub(sbj, heads[i], tails[i]) 186 | end 187 | 188 | -- updaet a last-index if global option is enabled 189 | if self.global == true then 190 | self.lastidx = tails[1] 191 | end 192 | return arr 193 | elseif err then 194 | return nil, err 195 | elseif self.global then 196 | -- reset a last-index to 0 if global option is enabled 197 | self.lastidx = 0 198 | end 199 | end 200 | 201 | --- indexesof 202 | --- @param sbj string 203 | --- @param offset? integer 204 | --- @return integer[]? arr 205 | --- @return any err 206 | function Regex:indexesof(sbj, offset) 207 | local head, tail, err = self.p:match_nocap(sbj, offset) 208 | 209 | if head then 210 | local arr = {} 211 | local idx = 1 212 | 213 | while head do 214 | arr[idx], arr[idx + 1] = head, tail 215 | idx = idx + 2 216 | head, tail, err = self.p:match_nocap(sbj, tail) 217 | end 218 | 219 | if err then 220 | return nil, err 221 | end 222 | 223 | return arr 224 | elseif err then 225 | return nil, err 226 | end 227 | end 228 | 229 | --- indexof 230 | --- @param sbj string 231 | --- @param offset? integer 232 | --- @return integer[]? arr 233 | --- @return any err 234 | function Regex:indexof(sbj, offset) 235 | local heads, tails, err = self.p:match(sbj, offset or self.lastidx) 236 | 237 | if heads then 238 | local arr = {} 239 | local idx = 1 240 | 241 | for i = 1, #heads do 242 | arr[idx], arr[idx + 1] = heads[i], tails[i] 243 | idx = idx + 2 244 | end 245 | 246 | -- updaet a last-index if global option is enabled 247 | if self.global == true then 248 | self.lastidx = tails[1] 249 | end 250 | 251 | return arr 252 | elseif err then 253 | return nil, err 254 | elseif self.global then 255 | -- reset a last-index to 0 if global option is enabled 256 | self.lastidx = 0 257 | end 258 | end 259 | 260 | --- test 261 | --- @param sbj string 262 | --- @param offset integer? 263 | --- @return boolean ok 264 | --- @return any err 265 | function Regex:test(sbj, offset) 266 | local head, tail, err = self.p:match_nocap(sbj, offset or self.lastidx) 267 | 268 | -- found 269 | if head then 270 | -- updaet a last-index if global option is enabled 271 | if self.global == true then 272 | self.lastidx = tail 273 | end 274 | return true 275 | elseif self.global then 276 | -- reset a last-index to 0 if global option is enabled 277 | self.lastidx = 0 278 | end 279 | 280 | return false, err 281 | end 282 | 283 | Regex = require('metamodule').new(Regex) 284 | 285 | --- matches 286 | --- @param sbj string 287 | --- @param pattern string 288 | --- @param flags? string 289 | --- @param offset? integer 290 | --- @return string[]? arr 291 | --- @return any err 292 | local function matches(sbj, pattern, flags, offset) 293 | local re, err = Regex(pattern, flags) 294 | if re then 295 | return re:matches(sbj, offset) 296 | end 297 | return nil, err 298 | end 299 | 300 | --- match 301 | --- @param sbj string 302 | --- @param pattern string 303 | --- @param flags? string 304 | --- @param offset? integer 305 | --- @return string[]? arr 306 | --- @return any err 307 | local function match(sbj, pattern, flags, offset) 308 | local re, err = Regex(pattern, flags) 309 | if re then 310 | return re:match(sbj, offset) 311 | end 312 | return nil, err 313 | end 314 | 315 | --- indexesof 316 | --- @param sbj string 317 | --- @param pattern string 318 | --- @param flags? string 319 | --- @param offset? integer 320 | --- @return integer[]? arr 321 | --- @return any err 322 | local function indexesof(sbj, pattern, flags, offset) 323 | local re, err = Regex(pattern, flags) 324 | if re then 325 | return re:indexesof(sbj, offset) 326 | end 327 | return nil, err 328 | end 329 | 330 | --- indexof 331 | --- @param sbj string 332 | --- @param pattern string 333 | --- @param flags? string 334 | --- @param offset? integer 335 | --- @return integer[]? arr 336 | --- @return any err 337 | local function indexof(sbj, pattern, flags, offset) 338 | local re, err = Regex(pattern, flags) 339 | if re then 340 | return re:indexof(sbj, offset) 341 | end 342 | return nil, err 343 | end 344 | 345 | --- test 346 | --- @param sbj string 347 | --- @param pattern string 348 | --- @param flags? string 349 | --- @param offset? integer 350 | --- @return boolean ok 351 | --- @return any err 352 | local function test(sbj, pattern, flags, offset) 353 | local re, err = Regex(pattern, flags) 354 | if re then 355 | return re:test(sbj, offset) 356 | end 357 | return false, err 358 | end 359 | 360 | return { 361 | new = Regex, 362 | matches = matches, 363 | match = match, 364 | indexesof = indexesof, 365 | indexof = indexof, 366 | test = test, 367 | } 368 | -------------------------------------------------------------------------------- /test/regex_test.lua: -------------------------------------------------------------------------------- 1 | require('luacov') 2 | local testcase = require('testcase') 3 | local assert = require('assert') 4 | local regex = require('regex') 5 | 6 | function testcase.new() 7 | -- test that create a new regex object 8 | local re, err = regex.new('abc', 'ismxgojU') 9 | assert.is_nil(err) 10 | assert.match(re, '^regex: ', false) 11 | 12 | -- test that return error if failed to compile pattern 13 | re, err = regex.new('abc(') 14 | assert.is_nil(re) 15 | assert.match(err, 'compilation failed') 16 | 17 | -- test that throws error if pattern is not string 18 | err = assert.throws(regex.new, 123) 19 | assert.match(err, 'pattern must be string') 20 | 21 | -- test that throws error if flags is not string 22 | err = assert.throws(regex.new, 'abc', 123) 23 | assert.match(err, 'flags must be string or nil') 24 | 25 | -- test that throws error if unknown flag is provided 26 | err = assert.throws(regex.new, 'abc', 'v') 27 | assert.match(err, 'unknown flag "v"') 28 | end 29 | 30 | function testcase.matches_method() 31 | local re = assert(regex.new('[a-z]+([08]\\d*)')) 32 | local sbj = 'abcd0123efg4567hijk890' 33 | 34 | -- test that return matches in string array 35 | local arr, err = re:matches(sbj) 36 | assert.is_nil(err) 37 | assert.equal(arr, { 38 | 'abcd0123', 39 | 'hijk890', 40 | }) 41 | 42 | -- test that exec matches with offset 43 | arr, err = re:matches(sbj, 6) 44 | assert.is_nil(err) 45 | assert.equal(arr, { 46 | 'hijk890', 47 | }) 48 | 49 | -- test that return nil and error if invalid offset 50 | arr, err = re:matches(sbj, -1) 51 | assert.match(err, 'offset') 52 | assert.is_nil(arr) 53 | 54 | -- test that throws error if subject is not string 55 | err = assert.throws(re.matches, re, 123) 56 | assert.match(err, 'string expected') 57 | 58 | -- test that throws error if offset is not integer 59 | err = assert.throws(re.matches, re, sbj, 1.23) 60 | assert.match(err, 'integer expected') 61 | end 62 | 63 | function testcase.matches() 64 | local sbj = 'abcd0123efg4567hijk890' 65 | local pattern = '[a-z]+([08]\\d*)' 66 | 67 | -- test that return matches in string array 68 | local arr, err = regex.matches(sbj, pattern, nil, 6) 69 | assert.is_nil(err) 70 | assert.equal(arr, { 71 | 'hijk890', 72 | }) 73 | 74 | -- test that return nil and error if invalid pattern 75 | arr, err = regex.matches(sbj, 'abc(', nil, 1) 76 | assert.match(err, 'compilation failed') 77 | assert.is_nil(arr) 78 | 79 | -- test that return nil and error if invalid offset 80 | arr, err = regex.matches(sbj, pattern, nil, -1) 81 | assert.match(err, 'offset') 82 | assert.is_nil(arr) 83 | 84 | -- test that throws error if subject is not string 85 | err = assert.throws(regex.matches, 123, pattern) 86 | assert.match(err, 'string expected') 87 | 88 | -- test that throws error if offset is not integer 89 | err = assert.throws(regex.matches, sbj, pattern, nil, 1.23) 90 | assert.match(err, 'integer expected') 91 | end 92 | 93 | function testcase.match_method() 94 | local re = assert(regex.new('[a-z]+([08]\\d*)')) 95 | local sbj = 'abcd0123efg4567hijk890' 96 | 97 | -- test that return first match in string array 98 | local arr, err = re:match(sbj) 99 | assert.is_nil(err) 100 | assert.equal(arr, { 101 | 'abcd0123', 102 | '0123', 103 | }) 104 | 105 | -- test that always return first matches if global flag is not set 106 | arr, err = re:match(sbj) 107 | assert.is_nil(err) 108 | assert.equal(arr, { 109 | 'abcd0123', 110 | '0123', 111 | }) 112 | 113 | -- test that exec matches with offset 114 | arr, err = re:match(sbj, 6) 115 | assert.is_nil(err) 116 | assert.equal(arr, { 117 | 'hijk890', 118 | '890', 119 | }) 120 | 121 | -- test that return next matches if global flag is set 122 | re = assert(regex.new('[a-z]+([08]\\d*)', 'g')) 123 | for i, exp in ipairs({ 124 | { 125 | 'abcd0123', 126 | '0123', 127 | }, 128 | { 129 | 'hijk890', 130 | '890', 131 | }, 132 | {}, 133 | }) do 134 | arr, err = re:match(sbj) 135 | if i == 3 then 136 | assert.is_nil(arr) 137 | assert.is_nil(err) 138 | else 139 | assert.is_nil(err) 140 | assert.equal(arr, exp) 141 | end 142 | end 143 | 144 | -- test that return nil and error if invalid offset 145 | arr, err = re:match(sbj, -1) 146 | assert.match(err, 'offset') 147 | assert.is_nil(arr) 148 | 149 | -- test that throws error if subject is not string 150 | err = assert.throws(re.match, re, 123) 151 | assert.match(err, 'string expected') 152 | 153 | -- test that throws error if offset is not integer 154 | err = assert.throws(re.match, re, sbj, 1.23) 155 | assert.match(err, 'integer expected') 156 | end 157 | 158 | function testcase.match() 159 | local sbj = 'abcd0123efg4567hijk890' 160 | local pattern = '[a-z]+([08]\\d*)' 161 | 162 | -- test that return first match in string array 163 | local arr, err = regex.match(sbj, pattern, nil, 6) 164 | assert.is_nil(err) 165 | assert.equal(arr, { 166 | 'hijk890', 167 | '890', 168 | }) 169 | 170 | -- test that return nil and error if invalid pattern 171 | arr, err = regex.match(sbj, 'abc(', nil, 1) 172 | assert.match(err, 'compilation failed') 173 | assert.is_nil(arr) 174 | 175 | -- test that return nil and error if invalid offset 176 | arr, err = regex.match(sbj, pattern, nil, -1) 177 | assert.match(err, 'offset') 178 | assert.is_nil(arr) 179 | 180 | -- test that throws error if subject is not string 181 | err = assert.throws(regex.match, 123, pattern) 182 | assert.match(err, 'string expected') 183 | 184 | -- test that throws error if offset is not integer 185 | err = assert.throws(regex.match, sbj, pattern, nil, 1.23) 186 | assert.match(err, 'integer expected') 187 | end 188 | 189 | function testcase.indexesof_method() 190 | local re = assert(regex.new('[a-z]+([08]\\d*)')) 191 | local sbj = 'abcd0123efg4567hijk890' 192 | 193 | -- test that return indexes of matches in integer array 194 | local arr, err = re:indexesof(sbj) 195 | assert.is_nil(err) 196 | assert.equal(arr, { 197 | -- 1st match 'abcd0123' 198 | 1, 199 | 8, 200 | -- 2nd match 'hijk890' and capture '890' 201 | 16, 202 | 22, 203 | }) 204 | 205 | -- test that return nil and error if invalid offset 206 | arr, err = re:indexesof(sbj, -1) 207 | assert.match(err, 'offset') 208 | assert.is_nil(arr) 209 | 210 | -- test that throws error if subject is not string 211 | err = assert.throws(re.indexesof, re, 123) 212 | assert.match(err, 'string expected') 213 | 214 | -- test that throws error if offset is not integer 215 | err = assert.throws(re.indexesof, re, sbj, 1.23) 216 | assert.match(err, 'integer expected') 217 | end 218 | 219 | function testcase.indexesof() 220 | local sbj = 'abcd0123efg4567hijk890' 221 | local pattern = '[a-z]+([08]\\d*)' 222 | 223 | -- test that return indexes of matches in integer array 224 | local arr, err = regex.indexesof(sbj, pattern) 225 | assert.is_nil(err) 226 | assert.equal(arr, { 227 | -- 1st match 'abcd0123' 228 | 1, 229 | 8, 230 | -- 2nd match 'hijk890' and capture '890' 231 | 16, 232 | 22, 233 | }) 234 | 235 | -- test that return nil and error if invalid pattern 236 | arr, err = regex.indexesof(sbj, 'abc(', nil, 1) 237 | assert.match(err, 'compilation failed') 238 | assert.is_nil(arr) 239 | 240 | -- test that return nil and error if invalid offset 241 | arr, err = regex.indexesof(sbj, pattern, nil, -1) 242 | assert.match(err, 'offset') 243 | assert.is_nil(arr) 244 | 245 | -- test that throws error if subject is not string 246 | err = assert.throws(regex.indexesof, 123, pattern) 247 | assert.match(err, 'string expected') 248 | 249 | -- test that throws error if offset is not integer 250 | err = assert.throws(regex.indexesof, sbj, pattern, nil, 1.23) 251 | assert.match(err, 'integer expected') 252 | end 253 | 254 | function testcase.indexof_method() 255 | local re = assert(regex.new('[a-z]+([08]\\d*)')) 256 | local sbj = 'abcd0123efg4567hijk890' 257 | 258 | -- test that return first match in integer array 259 | local arr, err = re:indexof(sbj) 260 | assert.is_nil(err) 261 | assert.equal(arr, { 262 | -- 1st match 'abcd0123' 263 | 1, 264 | 8, 265 | 5, 266 | -- capture '0123' 267 | 8, 268 | }) 269 | 270 | -- test that always return first matches if global flag is not set 271 | arr, err = re:indexof(sbj) 272 | assert.is_nil(err) 273 | assert.equal(arr, { 274 | 1, 275 | 8, 276 | 5, 277 | 8, 278 | }) 279 | 280 | -- test that exec matches with offset 281 | arr, err = re:indexof(sbj, 6) 282 | assert.is_nil(err) 283 | assert.equal(arr, { 284 | -- 1st match 'hijk890', 285 | 16, 286 | 22, 287 | -- capture '890' 288 | 20, 289 | 22, 290 | }) 291 | 292 | -- test that return next matches if global flag is set 293 | re = assert(regex.new('[a-z]+([08]\\d*)', 'g')) 294 | for i, exp in ipairs({ 295 | { 296 | -- 1st match 'abcd0123' 297 | 1, 298 | 8, 299 | -- capture '0123' 300 | 5, 301 | 8, 302 | }, 303 | { 304 | -- 2nd match 'hijk890' 305 | 16, 306 | 22, 307 | -- capture '890' 308 | 20, 309 | 22, 310 | }, 311 | {}, 312 | }) do 313 | arr, err = re:indexof(sbj) 314 | if i == 3 then 315 | assert.is_nil(arr) 316 | assert.is_nil(err) 317 | else 318 | assert.is_nil(err) 319 | assert.equal(arr, exp) 320 | end 321 | end 322 | 323 | -- test that return nil and error if invalid offset 324 | arr, err = re:indexof(sbj, -1) 325 | assert.match(err, 'offset') 326 | assert.is_nil(arr) 327 | 328 | -- test that throws error if subject is not string 329 | err = assert.throws(re.indexof, re, 123) 330 | assert.match(err, 'string expected') 331 | 332 | -- test that throws error if offset is not integer 333 | err = assert.throws(re.indexof, re, sbj, 1.23) 334 | assert.match(err, 'integer expected') 335 | end 336 | 337 | function testcase.indexof() 338 | local sbj = 'abcd0123efg4567hijk890' 339 | local pattern = '[a-z]+([08]\\d*)' 340 | 341 | -- test that return first match in integer array 342 | local arr, err = regex.indexof(sbj, pattern, nil, 6) 343 | assert.is_nil(err) 344 | assert.equal(arr, { 345 | -- 1st match 'hijk890', 346 | 16, 347 | 22, 348 | -- capture '890' 349 | 20, 350 | 22, 351 | }) 352 | 353 | -- test that return nil and error if invalid pattern 354 | arr, err = regex.indexof(sbj, 'abc(', nil, 1) 355 | assert.match(err, 'compilation failed') 356 | assert.is_nil(arr) 357 | 358 | -- test that return nil and error if invalid offset 359 | arr, err = regex.indexof(sbj, pattern, nil, -1) 360 | assert.match(err, 'offset') 361 | assert.is_nil(arr) 362 | 363 | -- test that throws error if subject is not string 364 | err = assert.throws(regex.indexof, 123, pattern) 365 | assert.match(err, 'string expected') 366 | 367 | -- test that throws error if offset is not integer 368 | err = assert.throws(regex.indexof, sbj, pattern, nil, 1.23) 369 | assert.match(err, 'integer expected') 370 | end 371 | 372 | function testcase.test_method() 373 | local re = assert(regex.new('[a-z]+([08]\\d*)')) 374 | local sbj = 'abcd0123efg4567hijk890' 375 | 376 | -- test that return true if matches found 377 | local ok, err = re:test(sbj) 378 | assert.is_nil(err) 379 | assert.is_true(ok) 380 | 381 | -- test that return false if matches not found 382 | ok, err = re:test('abc') 383 | assert.is_nil(err) 384 | assert.is_false(ok) 385 | 386 | -- test that return false and error if invalid offset 387 | ok, err = re:test(sbj, -1) 388 | assert.match(err, 'offset') 389 | assert.is_false(ok) 390 | 391 | -- test that throws error if subject is not string 392 | err = assert.throws(re.test, re, 123) 393 | assert.match(err, 'string expected') 394 | 395 | -- test that throws error if offset is not integer 396 | err = assert.throws(re.test, re, sbj, 1.23) 397 | assert.match(err, 'integer expected') 398 | end 399 | 400 | function testcase.test() 401 | local sbj = 'abcd0123efg4567hijk890' 402 | local pattern = '[a-z]+([08]\\d*)' 403 | 404 | -- test that return true if matches found 405 | local ok, err = regex.test(sbj, pattern) 406 | assert.is_true(ok) 407 | assert.is_nil(err) 408 | 409 | -- test that return false if matches not found 410 | ok, err = regex.test(sbj, 'abc(') 411 | assert.is_false(ok) 412 | assert.match(err, 'compilation failed') 413 | 414 | -- test that throws error if subject is not string 415 | err = assert.throws(regex.test, 123, pattern) 416 | assert.match(err, 'string expected') 417 | 418 | -- test that throws error if offset is not integer 419 | err = assert.throws(regex.test, sbj, pattern, nil, 1.23) 420 | assert.match(err, 'integer expected') 421 | end 422 | 423 | --------------------------------------------------------------------------------