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