From de129283a5ac18b34ec856f6ee94d04f3bbe658f Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sat, 9 Jan 2021 03:01:18 +0000 Subject: [PATCH] Add shorthand support to parsers --- src/parser.js | 21 ++++++++----- src/parser.test.js | 77 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/parser.js b/src/parser.js index 6059a3e..d37e274 100644 --- a/src/parser.js +++ b/src/parser.js @@ -12,6 +12,7 @@ export const parse = (quasis, expressions) => { let currentGroup = null; let lastMatch; let currentSequence = rootSequence; + let capture; for ( let quasiIndex = 0, stackIndex = 0; @@ -19,9 +20,9 @@ export const parse = (quasis, expressions) => { stackIndex++ ) { if (stackIndex % 2 !== 0) { - currentSequence.push({ - expression: expressions[stackIndex++ >> 1], - }); + const expression = expressions[stackIndex++ >> 1]; + currentSequence.push({ expression, capture }); + capture = undefined; } const quasi = quasis[stackIndex >> 1]; @@ -36,12 +37,18 @@ export const parse = (quasis, expressions) => { if (!currentSequence) syntaxError(char); } else if (char === '(') { sequenceStack.push(currentSequence); - currentSequence.push((currentGroup = { sequence: [] })); + currentSequence.push((currentGroup = { sequence: [], capture })); currentSequence = currentGroup.sequence; + capture = undefined; + } else if (char === ':' || char === '=' || char === '!') { + capture = char; + const nextChar = quasi[quasiIndex]; + if (quasi[quasiIndex] && quasi[quasiIndex] !== '(') syntaxError(char); } else if (char === '?' && !currentSequence.length && currentGroup) { - const nextChar = quasi[quasiIndex++]; - if (nextChar === ':' || nextChar === '=' || nextChar === '!') { - currentGroup.capture = nextChar; + capture = quasi[quasiIndex++]; + if (capture === ':' || capture === '=' || capture === '!') { + currentGroup.capture = capture; + capture = undefined; } else { syntaxError(char); } diff --git a/src/parser.test.js b/src/parser.test.js index 54e0abd..c8029ec 100644 --- a/src/parser.test.js +++ b/src/parser.test.js @@ -42,38 +42,85 @@ it('supports groups with quantifiers', () => { expect(ast).toHaveProperty('0.sequence.0.quantifier', undefined); }); -it('supports non-capturing groups', () => { - const ast = parseTag`(?: ${1})`; - expect(ast).toHaveProperty('length', 1); - expect(ast).toHaveProperty('0.capture', ':'); - expect(ast).toHaveProperty('0.sequence.length', 1); +describe('non-capturing syntax', () => { + it('supports regex-like syntax', () => { + const ast = parseTag`(?: ${1})`; + expect(ast).toHaveProperty('length', 1); + expect(ast).toHaveProperty('0.capture', ':'); + expect(ast).toHaveProperty('0.sequence.length', 1); + }); + + it('supports shorthand', () => { + let ast = parseTag`:${1}`; + expect(ast).toHaveProperty('length', 1); + expect(ast).toHaveProperty('0.capture', ':'); + expect(ast).toHaveProperty('0.expression', 1); + ast = parseTag`:(${1})`; + expect(ast).toHaveProperty('length', 1); + expect(ast).toHaveProperty('0.capture', ':'); + expect(ast).toHaveProperty('0.sequence.length', 1); + }); + + it('fails on invalid usage', () => { + expect(() => parseTag`${1} : ${2}`).toThrow(); + expect(() => parseTag`${1} :|${2}`).toThrow(); + }); }); -it('supports positive lookahead groups', () => { - const ast = parseTag`(?= ${1})`; - expect(ast).toHaveProperty('length', 1); - expect(ast).toHaveProperty('0.capture', '='); - expect(ast).toHaveProperty('0.sequence.length', 1); +describe('positive lookaheads syntax', () => { + it('supports regex-like syntax', () => { + const ast = parseTag`(?= ${1})`; + expect(ast).toHaveProperty('length', 1); + expect(ast).toHaveProperty('0.capture', '='); + expect(ast).toHaveProperty('0.sequence.length', 1); + }); + + it('supports shorthand', () => { + let ast = parseTag`=${1}`; + expect(ast).toHaveProperty('length', 1); + expect(ast).toHaveProperty('0.capture', '='); + expect(ast).toHaveProperty('0.expression', 1); + ast = parseTag`=(${1})`; + expect(ast).toHaveProperty('length', 1); + expect(ast).toHaveProperty('0.capture', '='); + expect(ast).toHaveProperty('0.sequence.length', 1); + }); }); -it('supports negative lookahead groups', () => { - const ast = parseTag`(?! ${1})`; - expect(ast).toHaveProperty('length', 1); - expect(ast).toHaveProperty('0.capture', '!'); - expect(ast).toHaveProperty('0.sequence.length', 1); +describe('negative lookaheads syntax', () => { + it('supports regex-like syntax', () => { + const ast = parseTag`(?! ${1})`; + expect(ast).toHaveProperty('length', 1); + expect(ast).toHaveProperty('0.capture', '!'); + expect(ast).toHaveProperty('0.sequence.length', 1); + }); + + it('supports shorthand', () => { + let ast = parseTag`!${1}`; + expect(ast).toHaveProperty('length', 1); + expect(ast).toHaveProperty('0.capture', '!'); + expect(ast).toHaveProperty('0.expression', 1); + ast = parseTag`!(${1})`; + expect(ast).toHaveProperty('length', 1); + expect(ast).toHaveProperty('0.capture', '!'); + expect(ast).toHaveProperty('0.sequence.length', 1); + }); }); it('supports groups with alternates', () => { expect(parseTag`(${1} | ${2}) ${3}`).toMatchInlineSnapshot(` Array [ Object { + "capture": undefined, "sequence": Array [ Object { + "capture": undefined, "expression": 1, }, ], }, Object { + "capture": undefined, "expression": 3, }, ] -- knotmirror 0.0.1