Mirror: The magical sticky regex-based parser generator 🧙

Add shorthand support to parsers

Changed files
+76 -22
src
+14 -7
src/parser.js
··· 12 12 let currentGroup = null; 13 13 let lastMatch; 14 14 let currentSequence = rootSequence; 15 + let capture; 15 16 16 17 for ( 17 18 let quasiIndex = 0, stackIndex = 0; ··· 19 20 stackIndex++ 20 21 ) { 21 22 if (stackIndex % 2 !== 0) { 22 - currentSequence.push({ 23 - expression: expressions[stackIndex++ >> 1], 24 - }); 23 + const expression = expressions[stackIndex++ >> 1]; 24 + currentSequence.push({ expression, capture }); 25 + capture = undefined; 25 26 } 26 27 27 28 const quasi = quasis[stackIndex >> 1]; ··· 36 37 if (!currentSequence) syntaxError(char); 37 38 } else if (char === '(') { 38 39 sequenceStack.push(currentSequence); 39 - currentSequence.push((currentGroup = { sequence: [] })); 40 + currentSequence.push((currentGroup = { sequence: [], capture })); 40 41 currentSequence = currentGroup.sequence; 42 + capture = undefined; 43 + } else if (char === ':' || char === '=' || char === '!') { 44 + capture = char; 45 + const nextChar = quasi[quasiIndex]; 46 + if (quasi[quasiIndex] && quasi[quasiIndex] !== '(') syntaxError(char); 41 47 } else if (char === '?' && !currentSequence.length && currentGroup) { 42 - const nextChar = quasi[quasiIndex++]; 43 - if (nextChar === ':' || nextChar === '=' || nextChar === '!') { 44 - currentGroup.capture = nextChar; 48 + capture = quasi[quasiIndex++]; 49 + if (capture === ':' || capture === '=' || capture === '!') { 50 + currentGroup.capture = capture; 51 + capture = undefined; 45 52 } else { 46 53 syntaxError(char); 47 54 }
+62 -15
src/parser.test.js
··· 42 42 expect(ast).toHaveProperty('0.sequence.0.quantifier', undefined); 43 43 }); 44 44 45 - it('supports non-capturing groups', () => { 46 - const ast = parseTag`(?: ${1})`; 47 - expect(ast).toHaveProperty('length', 1); 48 - expect(ast).toHaveProperty('0.capture', ':'); 49 - expect(ast).toHaveProperty('0.sequence.length', 1); 45 + describe('non-capturing syntax', () => { 46 + it('supports regex-like syntax', () => { 47 + const ast = parseTag`(?: ${1})`; 48 + expect(ast).toHaveProperty('length', 1); 49 + expect(ast).toHaveProperty('0.capture', ':'); 50 + expect(ast).toHaveProperty('0.sequence.length', 1); 51 + }); 52 + 53 + it('supports shorthand', () => { 54 + let ast = parseTag`:${1}`; 55 + expect(ast).toHaveProperty('length', 1); 56 + expect(ast).toHaveProperty('0.capture', ':'); 57 + expect(ast).toHaveProperty('0.expression', 1); 58 + ast = parseTag`:(${1})`; 59 + expect(ast).toHaveProperty('length', 1); 60 + expect(ast).toHaveProperty('0.capture', ':'); 61 + expect(ast).toHaveProperty('0.sequence.length', 1); 62 + }); 63 + 64 + it('fails on invalid usage', () => { 65 + expect(() => parseTag`${1} : ${2}`).toThrow(); 66 + expect(() => parseTag`${1} :|${2}`).toThrow(); 67 + }); 50 68 }); 51 69 52 - it('supports positive lookahead groups', () => { 53 - const ast = parseTag`(?= ${1})`; 54 - expect(ast).toHaveProperty('length', 1); 55 - expect(ast).toHaveProperty('0.capture', '='); 56 - expect(ast).toHaveProperty('0.sequence.length', 1); 70 + describe('positive lookaheads syntax', () => { 71 + it('supports regex-like syntax', () => { 72 + const ast = parseTag`(?= ${1})`; 73 + expect(ast).toHaveProperty('length', 1); 74 + expect(ast).toHaveProperty('0.capture', '='); 75 + expect(ast).toHaveProperty('0.sequence.length', 1); 76 + }); 77 + 78 + it('supports shorthand', () => { 79 + let ast = parseTag`=${1}`; 80 + expect(ast).toHaveProperty('length', 1); 81 + expect(ast).toHaveProperty('0.capture', '='); 82 + expect(ast).toHaveProperty('0.expression', 1); 83 + ast = parseTag`=(${1})`; 84 + expect(ast).toHaveProperty('length', 1); 85 + expect(ast).toHaveProperty('0.capture', '='); 86 + expect(ast).toHaveProperty('0.sequence.length', 1); 87 + }); 57 88 }); 58 89 59 - it('supports negative lookahead groups', () => { 60 - const ast = parseTag`(?! ${1})`; 61 - expect(ast).toHaveProperty('length', 1); 62 - expect(ast).toHaveProperty('0.capture', '!'); 63 - expect(ast).toHaveProperty('0.sequence.length', 1); 90 + describe('negative lookaheads syntax', () => { 91 + it('supports regex-like syntax', () => { 92 + const ast = parseTag`(?! ${1})`; 93 + expect(ast).toHaveProperty('length', 1); 94 + expect(ast).toHaveProperty('0.capture', '!'); 95 + expect(ast).toHaveProperty('0.sequence.length', 1); 96 + }); 97 + 98 + it('supports shorthand', () => { 99 + let ast = parseTag`!${1}`; 100 + expect(ast).toHaveProperty('length', 1); 101 + expect(ast).toHaveProperty('0.capture', '!'); 102 + expect(ast).toHaveProperty('0.expression', 1); 103 + ast = parseTag`!(${1})`; 104 + expect(ast).toHaveProperty('length', 1); 105 + expect(ast).toHaveProperty('0.capture', '!'); 106 + expect(ast).toHaveProperty('0.sequence.length', 1); 107 + }); 64 108 }); 65 109 66 110 it('supports groups with alternates', () => { 67 111 expect(parseTag`(${1} | ${2}) ${3}`).toMatchInlineSnapshot(` 68 112 Array [ 69 113 Object { 114 + "capture": undefined, 70 115 "sequence": Array [ 71 116 Object { 117 + "capture": undefined, 72 118 "expression": 1, 73 119 }, 74 120 ], 75 121 }, 76 122 Object { 123 + "capture": undefined, 77 124 "expression": 3, 78 125 }, 79 126 ]