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