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