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 shorthand', () => { 362 const node = match('node')`${/1/} :${/2/}+`; 363 it.each` 364 input | result | lastIndex 365 ${'12'} | ${['1']} | ${2} 366 ${'122'} | ${['1']} | ${3} 367 ${'13'} | ${undefined} | ${0} 368 ${'1'} | ${undefined} | ${0} 369 ${'_'} | ${undefined} | ${0} 370 `( 371 'should return $result when $input is passed', 372 ({ input, result, lastIndex }) => { 373 expectToParse(node, input, result, lastIndex); 374 } 375 ); 376}); 377 378describe('non-capturing group with plus matcher, then required matcher', () => { 379 const node = match('node')`(?: ${/1/}+) ${/2/}`; 380 it.each` 381 input | result | lastIndex 382 ${'12'} | ${['2']} | ${2} 383 ${'112'} | ${['2']} | ${3} 384 ${'1'} | ${undefined} | ${0} 385 ${'13'} | ${undefined} | ${0} 386 ${'2'} | ${undefined} | ${0} 387 ${'_'} | ${undefined} | ${0} 388 `( 389 'should return $result when $input is passed', 390 ({ input, result, lastIndex }) => { 391 expectToParse(node, input, result, lastIndex); 392 } 393 ); 394}); 395 396describe('non-capturing group with star group and required matcher, then required matcher', () => { 397 const node = match('node')`(?: ${/1/}* ${/2/}) ${/3/}`; 398 it.each` 399 input | result | lastIndex 400 ${'123'} | ${['3']} | ${3} 401 ${'1123'} | ${['3']} | ${4} 402 ${'23'} | ${['3']} | ${2} 403 ${'13'} | ${undefined} | ${0} 404 ${'2'} | ${undefined} | ${0} 405 ${'_'} | ${undefined} | ${0} 406 `( 407 'should return $result when $input is passed', 408 ({ input, result, lastIndex }) => { 409 expectToParse(node, input, result, lastIndex); 410 } 411 ); 412}); 413 414describe('non-capturing group with plus group and required matcher, then required matcher', () => { 415 const node = match('node')`(?: ${/1/}+ ${/2/}) ${/3/}`; 416 it.each` 417 input | result | lastIndex 418 ${'123'} | ${['3']} | ${3} 419 ${'1123'} | ${['3']} | ${4} 420 ${'23'} | ${undefined} | ${0} 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('non-capturing group with optional and required matcher, then required matcher', () => { 433 const node = match('node')`(?: ${/1/}? ${/2/}) ${/3/}`; 434 it.each` 435 input | result | lastIndex 436 ${'123'} | ${['3']} | ${3} 437 ${'23'} | ${['3']} | ${2} 438 ${'13'} | ${undefined} | ${0} 439 ${'2'} | ${undefined} | ${0} 440 ${'_'} | ${undefined} | ${0} 441 `( 442 'should return $result when $input is passed', 443 ({ input, result, lastIndex }) => { 444 expectToParse(node, input, result, lastIndex); 445 } 446 ); 447}); 448 449describe('positive lookahead group', () => { 450 const node = match('node')`(?= ${/1/}) ${/\d/}`; 451 it.each` 452 input | result | lastIndex 453 ${'1'} | ${['1']} | ${1} 454 ${'13'} | ${['1']} | ${1} 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 shorthand', () => { 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 group with plus matcher', () => { 482 const node = match('node')`(?= ${/1/}+) ${/\d/}`; 483 it.each` 484 input | result | lastIndex 485 ${'1'} | ${['1']} | ${1} 486 ${'11'} | ${['1']} | ${1} 487 ${'12'} | ${['1']} | ${1} 488 ${'22'} | ${undefined} | ${0} 489 ${'2'} | ${undefined} | ${0} 490 ${'_'} | ${undefined} | ${0} 491 `( 492 'should return $result when $input is passed', 493 ({ input, result, lastIndex }) => { 494 expectToParse(node, input, result, lastIndex); 495 } 496 ); 497}); 498 499describe('positive lookahead group with plus group and required matcher', () => { 500 const node = match('node')`(?= ${/1/}+ ${/2/}) ${/\d/}`; 501 it.each` 502 input | result | lastIndex 503 ${'12'} | ${['1']} | ${1} 504 ${'112'} | ${['1']} | ${1} 505 ${'1123'} | ${['1']} | ${1} 506 ${'2'} | ${undefined} | ${0} 507 ${'1'} | ${undefined} | ${0} 508 ${'2'} | ${undefined} | ${0} 509 ${'_'} | ${undefined} | ${0} 510 `( 511 'should return $result when $input is passed', 512 ({ input, result, lastIndex }) => { 513 expectToParse(node, input, result, lastIndex); 514 } 515 ); 516}); 517 518describe('negative lookahead group', () => { 519 const node = match('node')`(?! ${/1/}) ${/\d/}`; 520 it.each` 521 input | result | lastIndex 522 ${'2'} | ${['2']} | ${1} 523 ${'23'} | ${['2']} | ${1} 524 ${'1'} | ${undefined} | ${0} 525 ${'1'} | ${undefined} | ${0} 526 ${'_'} | ${undefined} | ${0} 527 `( 528 'should return $result when $input is passed', 529 ({ input, result, lastIndex }) => { 530 expectToParse(node, input, result, lastIndex); 531 } 532 ); 533}); 534 535describe('negative lookahead shorthand', () => { 536 const node = match('node')`!${/1/} ${/\d/}`; 537 it.each` 538 input | result | lastIndex 539 ${'2'} | ${['2']} | ${1} 540 ${'23'} | ${['2']} | ${1} 541 ${'1'} | ${undefined} | ${0} 542 ${'1'} | ${undefined} | ${0} 543 ${'_'} | ${undefined} | ${0} 544 `( 545 'should return $result when $input is passed', 546 ({ input, result, lastIndex }) => { 547 expectToParse(node, input, result, lastIndex); 548 } 549 ); 550}); 551 552describe('longer negative lookahead group', () => { 553 const node = match('node')`${/1/} (?! ${/2/} ${/3/}) ${/\d/} ${/\d/}`; 554 it.each` 555 input | result | lastIndex 556 ${'145'} | ${['1', '4', '5']} | ${3} 557 ${'124'} | ${['1', '2', '4']} | ${3} 558 ${'123'} | ${undefined} | ${0} 559 ${'2'} | ${undefined} | ${0} 560 ${'_'} | ${undefined} | ${0} 561 `( 562 'should return $result when $input is passed', 563 ({ input, result, lastIndex }) => { 564 expectToParse(node, input, result, lastIndex); 565 } 566 ); 567}); 568 569describe('negative lookahead group with plus matcher', () => { 570 const node = match('node')`(?! ${/1/}+) ${/\d/}`; 571 it.each` 572 input | result | lastIndex 573 ${'2'} | ${['2']} | ${1} 574 ${'21'} | ${['2']} | ${1} 575 ${'22'} | ${['2']} | ${1} 576 ${'11'} | ${undefined} | ${0} 577 ${'1'} | ${undefined} | ${0} 578 ${'_'} | ${undefined} | ${0} 579 `( 580 'should return $result when $input is passed', 581 ({ input, result, lastIndex }) => { 582 expectToParse(node, input, result, lastIndex); 583 } 584 ); 585}); 586 587describe('negative lookahead group with plus group and required matcher', () => { 588 const node = match('node')`(?! ${/1/}+ ${/2/}) ${/\d/}`; 589 it.each` 590 input | result | lastIndex 591 ${'21'} | ${['2']} | ${1} 592 ${'211'} | ${['2']} | ${1} 593 ${'113'} | ${['1']} | ${1} 594 ${'1'} | ${['1']} | ${1} 595 ${'112'} | ${undefined} | ${0} 596 ${'12'} | ${undefined} | ${0} 597 ${'_'} | ${undefined} | ${0} 598 `( 599 'should return $result when $input is passed', 600 ({ input, result, lastIndex }) => { 601 expectToParse(node, input, result, lastIndex); 602 } 603 ); 604}); 605 606describe('interpolation parsing', () => { 607 const node = match('node')` 608 ${/1/} 609 ${interpolation((x) => (x > 1 ? x : null))} 610 ${/3/} 611 `; 612 613 it('matches interpolations', () => { 614 const expected = ['1', 2, '3']; 615 expected.tag = 'node'; 616 expect(parse(node)`1${2}3`).toEqual(expected); 617 }); 618 619 it('does not match invalid inputs', () => { 620 expect(parse(node)`13`).toBe(undefined); 621 expect(parse(node)`13${2}`).toBe(undefined); 622 expect(parse(node)`${2}13`).toBe(undefined); 623 expect(parse(node)`1${1}3`).toBe(undefined); 624 }); 625}); 626 627describe('string matching', () => { 628 const node = match('node')` 629 ${'1'} 630 ${'2'} 631 `; 632 633 it('matches strings', () => { 634 const expected = ['1', '2']; 635 expected.tag = 'node'; 636 expect(parse(node)('12')).toEqual(expected); 637 expect(parse(node)('13')).toBe(undefined); 638 }); 639});