Mirror: The magical sticky regex-based parser generator 馃
1import { parse, match, interpolation } from './core'; 2 3const expectToParse = (node, input, result, lastIndex = 0) => { 4 const state = { quasis: [input], expressions: [], x: 0, y: 0 }; 5 if (result) result.tag = 'node'; 6 expect(node(state)).toEqual(result); 7 8 // NOTE: After parsing we expect the current index to exactly match the 9 // sum amount of matched characters 10 if (result === undefined) { 11 expect(state.y).toBe(0); 12 } else { 13 const index = lastIndex || result.reduce((acc, x) => acc + x.length, 0); 14 expect(state.y).toBe(index); 15 } 16}; 17 18describe('required matcher', () => { 19 const node = match('node')`${/1/}`; 20 it.each` 21 input | result 22 ${'1'} | ${['1']} 23 ${''} | ${undefined} 24 `('should return $result when $input is passed', ({ input, result }) => { 25 expectToParse(node, input, result); 26 }); 27}); 28 29describe('optional matcher', () => { 30 const node = match('node')`${/1/}?`; 31 it.each` 32 input | result 33 ${'1'} | ${['1']} 34 ${'_'} | ${[]} 35 ${''} | ${[]} 36 `('should return $result when $input is passed', ({ input, result }) => { 37 expectToParse(node, input, result); 38 }); 39}); 40 41describe('star matcher', () => { 42 const node = match('node')`${/1/}*`; 43 it.each` 44 input | result 45 ${'1'} | ${['1']} 46 ${'11'} | ${['1', '1']} 47 ${'111'} | ${['1', '1', '1']} 48 ${'_'} | ${[]} 49 ${''} | ${[]} 50 `('should return $result when "$input" is passed', ({ input, result }) => { 51 expectToParse(node, input, result); 52 }); 53}); 54 55describe('plus matcher', () => { 56 const node = match('node')`${/1/}+`; 57 it.each` 58 input | result 59 ${'1'} | ${['1']} 60 ${'11'} | ${['1', '1']} 61 ${'111'} | ${['1', '1', '1']} 62 ${'_'} | ${undefined} 63 ${''} | ${undefined} 64 `('should return $result when "$input" is passed', ({ input, result }) => { 65 expectToParse(node, input, result); 66 }); 67}); 68 69describe('optional then required matcher', () => { 70 const node = match('node')`${/1/}? ${/2/}`; 71 it.each` 72 input | result 73 ${'12'} | ${['1', '2']} 74 ${'2'} | ${['2']} 75 ${''} | ${undefined} 76 `('should return $result when $input is passed', ({ input, result }) => { 77 expectToParse(node, input, result); 78 }); 79}); 80 81describe('star then required matcher', () => { 82 const node = match('node')`${/1/}* ${/2/}`; 83 it.each` 84 input | result 85 ${'12'} | ${['1', '2']} 86 ${'112'} | ${['1', '1', '2']} 87 ${'2'} | ${['2']} 88 ${''} | ${undefined} 89 `('should return $result when $input is passed', ({ input, result }) => { 90 expectToParse(node, input, result); 91 }); 92}); 93 94describe('plus then required matcher', () => { 95 const node = match('node')`${/1/}+ ${/2/}`; 96 it.each` 97 input | result 98 ${'12'} | ${['1', '2']} 99 ${'112'} | ${['1', '1', '2']} 100 ${'2'} | ${undefined} 101 ${''} | ${undefined} 102 `('should return $result when $input is passed', ({ input, result }) => { 103 expectToParse(node, input, result); 104 }); 105}); 106 107describe('optional group then required matcher', () => { 108 const node = match('node')`(${/1/} ${/2/})? ${/3/}`; 109 it.each` 110 input | result 111 ${'123'} | ${['1', '2', '3']} 112 ${'3'} | ${['3']} 113 ${'_'} | ${undefined} 114 `('should return $result when $input is passed', ({ input, result }) => { 115 expectToParse(node, input, result); 116 }); 117}); 118 119describe('star group then required matcher', () => { 120 const node = match('node')`(${/1/} ${/2/})* ${/3/}`; 121 it.each` 122 input | result 123 ${'123'} | ${['1', '2', '3']} 124 ${'12123'} | ${['1', '2', '1', '2', '3']} 125 ${'3'} | ${['3']} 126 ${'13'} | ${undefined} 127 ${'_'} | ${undefined} 128 `('should return $result when $input is passed', ({ input, result }) => { 129 expectToParse(node, input, result); 130 }); 131}); 132 133describe('plus group then required matcher', () => { 134 const node = match('node')`(${/1/} ${/2/})+ ${/3/}`; 135 it.each` 136 input | result 137 ${'123'} | ${['1', '2', '3']} 138 ${'12123'} | ${['1', '2', '1', '2', '3']} 139 ${'3'} | ${undefined} 140 ${'13'} | ${undefined} 141 ${'_'} | ${undefined} 142 `('should return $result when $input is passed', ({ input, result }) => { 143 expectToParse(node, input, result); 144 }); 145}); 146 147describe('optional group with nested optional matcher, then required matcher', () => { 148 const node = match('node')`(${/1/}? ${/2/})? ${/3/}`; 149 it.each` 150 input | result 151 ${'123'} | ${['1', '2', '3']} 152 ${'23'} | ${['2', '3']} 153 ${'3'} | ${['3']} 154 ${'13'} | ${undefined} 155 ${'_'} | ${undefined} 156 `('should return $result when $input is passed', ({ input, result }) => { 157 expectToParse(node, input, result); 158 }); 159}); 160 161describe('star group with nested optional matcher, then required matcher', () => { 162 const node = match('node')`(${/1/}? ${/2/})* ${/3/}`; 163 it.each` 164 input | result 165 ${'123'} | ${['1', '2', '3']} 166 ${'23'} | ${['2', '3']} 167 ${'223'} | ${['2', '2', '3']} 168 ${'2123'} | ${['2', '1', '2', '3']} 169 ${'3'} | ${['3']} 170 ${'13'} | ${undefined} 171 ${'_'} | ${undefined} 172 `('should return $result when $input is passed', ({ input, result }) => { 173 expectToParse(node, input, result); 174 }); 175}); 176 177describe('plus group with nested optional matcher, then required matcher', () => { 178 const node = match('node')`(${/1/}? ${/2/})+ ${/3/}`; 179 it.each` 180 input | result 181 ${'123'} | ${['1', '2', '3']} 182 ${'23'} | ${['2', '3']} 183 ${'223'} | ${['2', '2', '3']} 184 ${'2123'} | ${['2', '1', '2', '3']} 185 ${'3'} | ${undefined} 186 ${'13'} | ${undefined} 187 ${'_'} | ${undefined} 188 `('should return $result when $input is passed', ({ input, result }) => { 189 expectToParse(node, input, result); 190 }); 191}); 192 193describe('plus group with nested plus matcher, then required matcher', () => { 194 const node = match('node')`(${/1/}+ ${/2/})+ ${/3/}`; 195 it.each` 196 input | result 197 ${'123'} | ${['1', '2', '3']} 198 ${'1123'} | ${['1', '1', '2', '3']} 199 ${'12123'} | ${['1', '2', '1', '2', '3']} 200 ${'121123'} | ${['1', '2', '1', '1', '2', '3']} 201 ${'3'} | ${undefined} 202 ${'23'} | ${undefined} 203 ${'13'} | ${undefined} 204 ${'_'} | ${undefined} 205 `('should return $result when $input is passed', ({ input, result }) => { 206 expectToParse(node, input, result); 207 }); 208}); 209 210describe('plus group with nested required and plus matcher, then required matcher', () => { 211 const node = match('node')`(${/1/} ${/2/}+)+ ${/3/}`; 212 it.each` 213 input | result 214 ${'123'} | ${['1', '2', '3']} 215 ${'1223'} | ${['1', '2', '2', '3']} 216 ${'122123'} | ${['1', '2', '2', '1', '2', '3']} 217 ${'13'} | ${undefined} 218 ${'_'} | ${undefined} 219 `('should return $result when $input is passed', ({ input, result }) => { 220 expectToParse(node, input, result); 221 }); 222}); 223 224describe('nested plus group with nested required and plus matcher, then required matcher or alternate', () => { 225 const node = match('node')`(${/1/} ${/2/}+)+ ${/3/} | ${/1/}`; 226 it.each` 227 input | result 228 ${'123'} | ${['1', '2', '3']} 229 ${'1223'} | ${['1', '2', '2', '3']} 230 ${'122123'} | ${['1', '2', '2', '1', '2', '3']} 231 ${'1'} | ${['1']} 232 ${'13'} | ${['1']} 233 ${'_'} | ${undefined} 234 `('should return $result when $input is passed', ({ input, result }) => { 235 expectToParse(node, input, result); 236 }); 237}); 238 239describe('nested plus group with nested required and plus matcher, then alternate', () => { 240 const node = match('node')`(${/1/} ${/2/}+)+ (${/3/} | ${/4/})`; 241 it.each` 242 input | result 243 ${'123'} | ${['1', '2', '3']} 244 ${'124'} | ${['1', '2', '4']} 245 ${'1223'} | ${['1', '2', '2', '3']} 246 ${'1224'} | ${['1', '2', '2', '4']} 247 ${'1'} | ${undefined} 248 ${'13'} | ${undefined} 249 ${'_'} | ${undefined} 250 `('should return $result when $input is passed', ({ input, result }) => { 251 expectToParse(node, input, result); 252 }); 253}); 254 255describe('regular alternate', () => { 256 const node = match('node')`${/1/} | ${/2/} | ${/3/} | ${/4/}`; 257 it.each` 258 input | result 259 ${'1'} | ${['1']} 260 ${'2'} | ${['2']} 261 ${'3'} | ${['3']} 262 ${'4'} | ${['4']} 263 ${'_'} | ${undefined} 264 `('should return $result when $input is passed', ({ input, result }) => { 265 expectToParse(node, input, result); 266 }); 267}); 268 269describe('nested alternate in nested alternate in alternate', () => { 270 const node = match('node')`((${/1/} | ${/2/}) | ${/3/}) | ${/4/}`; 271 it.each` 272 input | result 273 ${'1'} | ${['1']} 274 ${'2'} | ${['2']} 275 ${'3'} | ${['3']} 276 ${'4'} | ${['4']} 277 ${'_'} | ${undefined} 278 `('should return $result when $input is passed', ({ input, result }) => { 279 expectToParse(node, input, result); 280 }); 281}); 282 283describe('alternate after required matcher', () => { 284 const node = match('node')`${/1/} (${/2/} | ${/3/})`; 285 it.each` 286 input | result 287 ${'12'} | ${['1', '2']} 288 ${'13'} | ${['1', '3']} 289 ${'14'} | ${undefined} 290 ${'3'} | ${undefined} 291 ${'_'} | ${undefined} 292 `('should return $result when $input is passed', ({ input, result }) => { 293 expectToParse(node, input, result); 294 }); 295}); 296 297describe('alternate with star group and required matcher after required matcher', () => { 298 const node = match('node')`${/1/} (${/2/}* ${/3/} | ${/4/})`; 299 it.each` 300 input | result 301 ${'123'} | ${['1', '2', '3']} 302 ${'1223'} | ${['1', '2', '2', '3']} 303 ${'13'} | ${['1', '3']} 304 ${'14'} | ${['1', '4']} 305 ${'12'} | ${undefined} 306 ${'15'} | ${undefined} 307 ${'_'} | ${undefined} 308 `('should return $result when $input is passed', ({ input, result }) => { 309 expectToParse(node, input, result); 310 }); 311}); 312 313describe('alternate with plus group and required matcher after required matcher', () => { 314 const node = match('node')`${/1/} (${/2/}+ ${/3/} | ${/4/})`; 315 it.each` 316 input | result 317 ${'123'} | ${['1', '2', '3']} 318 ${'1223'} | ${['1', '2', '2', '3']} 319 ${'14'} | ${['1', '4']} 320 ${'13'} | ${undefined} 321 ${'12'} | ${undefined} 322 ${'15'} | ${undefined} 323 ${'_'} | ${undefined} 324 `('should return $result when $input is passed', ({ input, result }) => { 325 expectToParse(node, input, result); 326 }); 327}); 328 329describe('alternate with optional and required matcher after required matcher', () => { 330 const node = match('node')`${/1/} (${/2/}? ${/3/} | ${/4/})`; 331 it.each` 332 input | result 333 ${'123'} | ${['1', '2', '3']} 334 ${'13'} | ${['1', '3']} 335 ${'14'} | ${['1', '4']} 336 ${'12'} | ${undefined} 337 ${'15'} | ${undefined} 338 ${'_'} | ${undefined} 339 `('should return $result when $input is passed', ({ input, result }) => { 340 expectToParse(node, input, result); 341 }); 342}); 343 344describe('non-capturing group', () => { 345 const node = match('node')`${/1/} (?: ${/2/}+)`; 346 it.each` 347 input | result | lastIndex 348 ${'12'} | ${['1']} | ${2} 349 ${'122'} | ${['1']} | ${3} 350 ${'13'} | ${undefined} | ${0} 351 ${'1'} | ${undefined} | ${0} 352 ${'_'} | ${undefined} | ${0} 353 `( 354 'should return $result when $input is passed', 355 ({ input, result, lastIndex }) => { 356 expectToParse(node, input, result, lastIndex); 357 } 358 ); 359}); 360 361describe('non-capturing group with plus matcher, then required matcher', () => { 362 const node = match('node')`(?: ${/1/}+) ${/2/}`; 363 it.each` 364 input | result | lastIndex 365 ${'12'} | ${['2']} | ${2} 366 ${'112'} | ${['2']} | ${3} 367 ${'1'} | ${undefined} | ${0} 368 ${'13'} | ${undefined} | ${0} 369 ${'2'} | ${undefined} | ${0} 370 ${'_'} | ${undefined} | ${0} 371 `( 372 'should return $result when $input is passed', 373 ({ input, result, lastIndex }) => { 374 expectToParse(node, input, result, lastIndex); 375 } 376 ); 377}); 378 379describe('non-capturing group with star group and required matcher, then required matcher', () => { 380 const node = match('node')`(?: ${/1/}* ${/2/}) ${/3/}`; 381 it.each` 382 input | result | lastIndex 383 ${'123'} | ${['3']} | ${3} 384 ${'1123'} | ${['3']} | ${4} 385 ${'23'} | ${['3']} | ${2} 386 ${'13'} | ${undefined} | ${0} 387 ${'2'} | ${undefined} | ${0} 388 ${'_'} | ${undefined} | ${0} 389 `( 390 'should return $result when $input is passed', 391 ({ input, result, lastIndex }) => { 392 expectToParse(node, input, result, lastIndex); 393 } 394 ); 395}); 396 397describe('non-capturing group with plus group and required matcher, then required matcher', () => { 398 const node = match('node')`(?: ${/1/}+ ${/2/}) ${/3/}`; 399 it.each` 400 input | result | lastIndex 401 ${'123'} | ${['3']} | ${3} 402 ${'1123'} | ${['3']} | ${4} 403 ${'23'} | ${undefined} | ${0} 404 ${'13'} | ${undefined} | ${0} 405 ${'2'} | ${undefined} | ${0} 406 ${'_'} | ${undefined} | ${0} 407 `( 408 'should return $result when $input is passed', 409 ({ input, result, lastIndex }) => { 410 expectToParse(node, input, result, lastIndex); 411 } 412 ); 413}); 414 415describe('non-capturing group with optional and required matcher, then required matcher', () => { 416 const node = match('node')`(?: ${/1/}? ${/2/}) ${/3/}`; 417 it.each` 418 input | result | lastIndex 419 ${'123'} | ${['3']} | ${3} 420 ${'23'} | ${['3']} | ${2} 421 ${'13'} | ${undefined} | ${0} 422 ${'2'} | ${undefined} | ${0} 423 ${'_'} | ${undefined} | ${0} 424 `( 425 'should return $result when $input is passed', 426 ({ input, result, lastIndex }) => { 427 expectToParse(node, input, result, lastIndex); 428 } 429 ); 430}); 431 432describe('positive lookahead group', () => { 433 const node = match('node')`(?= ${/1/}) ${/\d/}`; 434 it.each` 435 input | result | lastIndex 436 ${'1'} | ${['1']} | ${1} 437 ${'13'} | ${['1']} | ${1} 438 ${'2'} | ${undefined} | ${0} 439 ${'_'} | ${undefined} | ${0} 440 `( 441 'should return $result when $input is passed', 442 ({ input, result, lastIndex }) => { 443 expectToParse(node, input, result, lastIndex); 444 } 445 ); 446}); 447 448describe('positive lookahead group with plus matcher', () => { 449 const node = match('node')`(?= ${/1/}+) ${/\d/}`; 450 it.each` 451 input | result | lastIndex 452 ${'1'} | ${['1']} | ${1} 453 ${'11'} | ${['1']} | ${1} 454 ${'12'} | ${['1']} | ${1} 455 ${'22'} | ${undefined} | ${0} 456 ${'2'} | ${undefined} | ${0} 457 ${'_'} | ${undefined} | ${0} 458 `( 459 'should return $result when $input is passed', 460 ({ input, result, lastIndex }) => { 461 expectToParse(node, input, result, lastIndex); 462 } 463 ); 464}); 465 466describe('positive lookahead group with plus group and required matcher', () => { 467 const node = match('node')`(?= ${/1/}+ ${/2/}) ${/\d/}`; 468 it.each` 469 input | result | lastIndex 470 ${'12'} | ${['1']} | ${1} 471 ${'112'} | ${['1']} | ${1} 472 ${'1123'} | ${['1']} | ${1} 473 ${'2'} | ${undefined} | ${0} 474 ${'1'} | ${undefined} | ${0} 475 ${'2'} | ${undefined} | ${0} 476 ${'_'} | ${undefined} | ${0} 477 `( 478 'should return $result when $input is passed', 479 ({ input, result, lastIndex }) => { 480 expectToParse(node, input, result, lastIndex); 481 } 482 ); 483}); 484 485describe('negative lookahead group', () => { 486 const node = match('node')`(?! ${/1/}) ${/\d/}`; 487 it.each` 488 input | result | lastIndex 489 ${'2'} | ${['2']} | ${1} 490 ${'23'} | ${['2']} | ${1} 491 ${'1'} | ${undefined} | ${0} 492 ${'1'} | ${undefined} | ${0} 493 ${'_'} | ${undefined} | ${0} 494 `( 495 'should return $result when $input is passed', 496 ({ input, result, lastIndex }) => { 497 expectToParse(node, input, result, lastIndex); 498 } 499 ); 500}); 501 502describe('longer negative lookahead group', () => { 503 const node = match('node')`${/1/} (?! ${/2/} ${/3/}) ${/\d/} ${/\d/}`; 504 it.each` 505 input | result | lastIndex 506 ${'145'} | ${['1', '4', '5']} | ${3} 507 ${'124'} | ${['1', '2', '4']} | ${3} 508 ${'123'} | ${undefined} | ${0} 509 ${'2'} | ${undefined} | ${0} 510 ${'_'} | ${undefined} | ${0} 511 `( 512 'should return $result when $input is passed', 513 ({ input, result, lastIndex }) => { 514 expectToParse(node, input, result, lastIndex); 515 } 516 ); 517}); 518 519describe('negative lookahead group with plus matcher', () => { 520 const node = match('node')`(?! ${/1/}+) ${/\d/}`; 521 it.each` 522 input | result | lastIndex 523 ${'2'} | ${['2']} | ${1} 524 ${'21'} | ${['2']} | ${1} 525 ${'22'} | ${['2']} | ${1} 526 ${'11'} | ${undefined} | ${0} 527 ${'1'} | ${undefined} | ${0} 528 ${'_'} | ${undefined} | ${0} 529 `( 530 'should return $result when $input is passed', 531 ({ input, result, lastIndex }) => { 532 expectToParse(node, input, result, lastIndex); 533 } 534 ); 535}); 536 537describe('negative lookahead group with plus group and required matcher', () => { 538 const node = match('node')`(?! ${/1/}+ ${/2/}) ${/\d/}`; 539 it.each` 540 input | result | lastIndex 541 ${'21'} | ${['2']} | ${1} 542 ${'211'} | ${['2']} | ${1} 543 ${'113'} | ${['1']} | ${1} 544 ${'1'} | ${['1']} | ${1} 545 ${'112'} | ${undefined} | ${0} 546 ${'12'} | ${undefined} | ${0} 547 ${'_'} | ${undefined} | ${0} 548 `( 549 'should return $result when $input is passed', 550 ({ input, result, lastIndex }) => { 551 expectToParse(node, input, result, lastIndex); 552 } 553 ); 554}); 555 556describe('interpolation parsing', () => { 557 const node = match('node')` 558 ${/1/} 559 ${interpolation((x) => x > 1 && x)} 560 ${/3/} 561 `; 562 563 it('matches interpolations', () => { 564 const expected = ['1', 2, '3']; 565 expected.tag = 'node'; 566 expect(parse(node)`1${2}3`).toEqual(expected); 567 }); 568 569 it('does not match invalid inputs', () => { 570 expect(parse(node)`13`).toBe(undefined); 571 expect(parse(node)`13${2}`).toBe(undefined); 572 expect(parse(node)`${2}13`).toBe(undefined); 573 expect(parse(node)`1${1}3`).toBe(undefined); 574 }); 575});