From 5af1f2de88a94dbd376366a047b1b075ddb0f151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 6 Dec 2023 11:46:42 +0100 Subject: [PATCH 1/2] [WIP] feat: implement repeat --- src/__tests__/compiler.test.tsx | 4 +++- src/__tests__/repeat.test.tsx | 15 +++++++++++++ src/compiler.ts | 8 ++++++- src/{quantifiers.ts => quantifiers/index.ts} | 22 +++++++++++++++----- src/quantifiers/repeat.ts | 19 +++++++++++++++++ src/types.ts | 18 +++++++++++++++- 6 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 src/__tests__/repeat.test.tsx rename src/{quantifiers.ts => quantifiers/index.ts} (63%) create mode 100644 src/quantifiers/repeat.ts diff --git a/src/__tests__/compiler.test.tsx b/src/__tests__/compiler.test.tsx index 06e2316..dcdd8e4 100644 --- a/src/__tests__/compiler.test.tsx +++ b/src/__tests__/compiler.test.tsx @@ -1,5 +1,5 @@ import { buildPattern, buildRegex } from '../compiler'; -import { oneOrMore, optionally, one, zeroOrMore } from '../quantifiers'; +import { oneOrMore, optionally, one, zeroOrMore, repeat } from '../quantifiers'; test('basic quantifies', () => { expect(buildPattern('a')).toEqual('a'); @@ -12,6 +12,8 @@ test('basic quantifies', () => { expect(buildPattern('a', oneOrMore('bc'))).toEqual('a(?:bc)+'); expect(buildPattern('a', oneOrMore('bc'))).toEqual('a(?:bc)+'); + expect(buildPattern('a', repeat({ min: 1, max: 5 }, 'b'))).toEqual('ab{1,5}'); + expect(buildPattern('a', zeroOrMore('b'))).toEqual('ab*'); expect(buildPattern('a', zeroOrMore('bc'))).toEqual('a(?:bc)*'); expect(buildPattern('a', zeroOrMore('bc'))).toEqual('a(?:bc)*'); diff --git a/src/__tests__/repeat.test.tsx b/src/__tests__/repeat.test.tsx new file mode 100644 index 0000000..5c8776f --- /dev/null +++ b/src/__tests__/repeat.test.tsx @@ -0,0 +1,15 @@ +import { buildPattern } from '../compiler'; +import { repeat, zeroOrMore, oneOrMore } from '../quantifiers'; + +test('"repeat" quantifier', () => { + expect(buildPattern('a', repeat({ min: 1, max: 5 }, 'b'))).toEqual('ab{1,5}'); + expect(buildPattern('a', repeat({ min: 1 }, 'b'))).toEqual('ab{1,}'); + expect(buildPattern('a', repeat({ count: 1 }, 'b'))).toEqual('ab{1}'); + + expect(buildPattern('a', repeat({ count: 1 }, 'a', zeroOrMore('b')))).toEqual( + 'a(?:ab*){1}' + ); + expect( + buildPattern(repeat({ count: 5 }, 'text', ' ', oneOrMore('d'))) + ).toEqual('(?:text d+){5}'); +}); diff --git a/src/compiler.ts b/src/compiler.ts index ac3de1f..38ac703 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,5 +1,6 @@ import type { RegexElement } from './types'; import { compilers as quantifiers } from './quantifiers'; +import { repeat } from './quantifiers/repeat'; /** * Generate RegExp object for elements. @@ -32,11 +33,16 @@ function compileSingle(elements: RegexElement): string { return elements; } + const children = compileList(elements.children); + + if (elements.type === 'repeat') { + return repeat(children, elements.config); + } + const elementCompiler = quantifiers[elements.type]; if (!elementCompiler) { throw new Error(`Unknown elements type ${elements.type}`); } - const children = compileList(elements.children); return elementCompiler(children); } diff --git a/src/quantifiers.ts b/src/quantifiers/index.ts similarity index 63% rename from src/quantifiers.ts rename to src/quantifiers/index.ts index 4007990..efa651a 100644 --- a/src/quantifiers.ts +++ b/src/quantifiers/index.ts @@ -3,10 +3,11 @@ import type { OneOrMore, Optionally, RegexElement, + Repeat, ZeroOrMore, -} from './types'; -import type { CompilerMap } from './types-internal'; -import { wrapGroup } from './utils'; +} from '../types'; +import type { CompilerMap } from '../types-internal'; +import { wrapGroup } from '../utils'; export function oneOrMore(...children: RegexElement[]): OneOrMore { return { @@ -36,9 +37,20 @@ export function zeroOrMore(...children: RegexElement[]): ZeroOrMore { }; } +export function repeat( + config: Repeat['config'], + ...children: RegexElement[] +): Repeat { + return { + type: 'repeat', + children, + config, + }; +} + export const compilers = { one: (compiledChildren) => compiledChildren, oneOrMore: (compiledChildren) => `${wrapGroup(compiledChildren)}+`, - optionally: (compiledChildren: string) => `${wrapGroup(compiledChildren)}?`, - zeroOrMore: (compiledChildren: string) => `${wrapGroup(compiledChildren)}*`, + optionally: (compiledChildren) => `${wrapGroup(compiledChildren)}?`, + zeroOrMore: (compiledChildren) => `${wrapGroup(compiledChildren)}*`, } satisfies CompilerMap; diff --git a/src/quantifiers/repeat.ts b/src/quantifiers/repeat.ts new file mode 100644 index 0000000..7abd7f1 --- /dev/null +++ b/src/quantifiers/repeat.ts @@ -0,0 +1,19 @@ +import type { Repeat } from '../types'; +import { wrapGroup } from '../utils'; + +export function repeat( + compiledChildren: string, + config: Repeat['config'] +): string { + if ('count' in config && typeof config?.count === 'number') { + return `${wrapGroup(compiledChildren)}{${config?.count || ''}}`; + } + + if ('min' in config) { + return `${wrapGroup(compiledChildren)}{${config?.min || ''},${ + config?.max || '' + }}`; + } + + return `${wrapGroup(compiledChildren)}`; +} diff --git a/src/types.ts b/src/types.ts index dd6f023..206f674 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,11 @@ export type RegexElement = string | RegexQuantifier; -export type RegexQuantifier = One | OneOrMore | Optionally | ZeroOrMore; +export type RegexQuantifier = + | One + | OneOrMore + | Optionally + | ZeroOrMore + | Repeat; // Quantifiers export type One = { @@ -18,6 +23,17 @@ export type Optionally = { children: RegexElement[]; }; +export type Repeat = { + type: 'repeat'; + children: RegexElement[]; + config: + | { + min: number; + max?: number; + } + | { count: number }; +}; + export type ZeroOrMore = { type: 'zeroOrMore'; children: RegexElement[]; From 7773ccd06e13a5c0c8955138aad00f15f285b57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 6 Dec 2023 17:58:19 +0100 Subject: [PATCH 2/2] fix: apply review comments --- src/compiler.ts | 8 ++++---- src/quantifiers/index.ts | 3 ++- src/quantifiers/repeat.ts | 18 ++++++++---------- src/types.ts | 13 +++++++------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/compiler.ts b/src/compiler.ts index 38ac703..45fe05b 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,6 +1,6 @@ import type { RegexElement } from './types'; import { compilers as quantifiers } from './quantifiers'; -import { repeat } from './quantifiers/repeat'; +import { compileRepeat } from './quantifiers/repeat'; /** * Generate RegExp object for elements. @@ -33,10 +33,10 @@ function compileSingle(elements: RegexElement): string { return elements; } - const children = compileList(elements.children); + const compiledChildren = compileList(elements.children); if (elements.type === 'repeat') { - return repeat(children, elements.config); + return compileRepeat(elements.config, compiledChildren); } const elementCompiler = quantifiers[elements.type]; @@ -44,5 +44,5 @@ function compileSingle(elements: RegexElement): string { throw new Error(`Unknown elements type ${elements.type}`); } - return elementCompiler(children); + return elementCompiler(compiledChildren); } diff --git a/src/quantifiers/index.ts b/src/quantifiers/index.ts index efa651a..c6d170d 100644 --- a/src/quantifiers/index.ts +++ b/src/quantifiers/index.ts @@ -4,6 +4,7 @@ import type { Optionally, RegexElement, Repeat, + RepeatConfig, ZeroOrMore, } from '../types'; import type { CompilerMap } from '../types-internal'; @@ -38,7 +39,7 @@ export function zeroOrMore(...children: RegexElement[]): ZeroOrMore { } export function repeat( - config: Repeat['config'], + config: RepeatConfig, ...children: RegexElement[] ): Repeat { return { diff --git a/src/quantifiers/repeat.ts b/src/quantifiers/repeat.ts index 7abd7f1..4aa379a 100644 --- a/src/quantifiers/repeat.ts +++ b/src/quantifiers/repeat.ts @@ -1,18 +1,16 @@ -import type { Repeat } from '../types'; +import type { RepeatConfig } from '../types'; import { wrapGroup } from '../utils'; -export function repeat( - compiledChildren: string, - config: Repeat['config'] +export function compileRepeat( + config: RepeatConfig, + compiledChildren: string ): string { - if ('count' in config && typeof config?.count === 'number') { - return `${wrapGroup(compiledChildren)}{${config?.count || ''}}`; + if ('count' in config && typeof config.count === 'number') { + return `${wrapGroup(compiledChildren)}{${config.count}}`; } - if ('min' in config) { - return `${wrapGroup(compiledChildren)}{${config?.min || ''},${ - config?.max || '' - }}`; + if ('min' in config && typeof config.min === 'number') { + return `${wrapGroup(compiledChildren)}{${config.min},${config?.max ?? ''}}`; } return `${wrapGroup(compiledChildren)}`; diff --git a/src/types.ts b/src/types.ts index 206f674..b8ea674 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,15 +23,16 @@ export type Optionally = { children: RegexElement[]; }; +export type RepeatConfig = + | { min: number; max?: number } + | { + count: number; + }; + export type Repeat = { type: 'repeat'; children: RegexElement[]; - config: - | { - min: number; - max?: number; - } - | { count: number }; + config: RepeatConfig; }; export type ZeroOrMore = {