Skip to content

Commit eb111c2

Browse files
authored
feat: repeat quantifier (#11)
1 parent 9503643 commit eb111c2

File tree

6 files changed

+79
-9
lines changed

6 files changed

+79
-9
lines changed

src/__tests__/compiler.test.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { buildPattern, buildRegex } from '../compiler';
2-
import { oneOrMore, optionally, one, zeroOrMore } from '../quantifiers';
2+
import { oneOrMore, optionally, one, zeroOrMore, repeat } from '../quantifiers';
33

44
test('basic quantifies', () => {
55
expect(buildPattern('a')).toEqual('a');
@@ -12,6 +12,8 @@ test('basic quantifies', () => {
1212
expect(buildPattern('a', oneOrMore('bc'))).toEqual('a(?:bc)+');
1313
expect(buildPattern('a', oneOrMore('bc'))).toEqual('a(?:bc)+');
1414

15+
expect(buildPattern('a', repeat({ min: 1, max: 5 }, 'b'))).toEqual('ab{1,5}');
16+
1517
expect(buildPattern('a', zeroOrMore('b'))).toEqual('ab*');
1618
expect(buildPattern('a', zeroOrMore('bc'))).toEqual('a(?:bc)*');
1719
expect(buildPattern('a', zeroOrMore('bc'))).toEqual('a(?:bc)*');

src/__tests__/repeat.test.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { buildPattern } from '../compiler';
2+
import { repeat, zeroOrMore, oneOrMore } from '../quantifiers';
3+
4+
test('"repeat" quantifier', () => {
5+
expect(buildPattern('a', repeat({ min: 1, max: 5 }, 'b'))).toEqual('ab{1,5}');
6+
expect(buildPattern('a', repeat({ min: 1 }, 'b'))).toEqual('ab{1,}');
7+
expect(buildPattern('a', repeat({ count: 1 }, 'b'))).toEqual('ab{1}');
8+
9+
expect(buildPattern('a', repeat({ count: 1 }, 'a', zeroOrMore('b')))).toEqual(
10+
'a(?:ab*){1}'
11+
);
12+
expect(
13+
buildPattern(repeat({ count: 5 }, 'text', ' ', oneOrMore('d')))
14+
).toEqual('(?:text d+){5}');
15+
});

src/compiler.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { RegexElement } from './types';
22
import { compilers as quantifiers } from './quantifiers';
3+
import { compileRepeat } from './quantifiers/repeat';
34

45
/**
56
* Generate RegExp object for elements.
@@ -32,11 +33,16 @@ function compileSingle(elements: RegexElement): string {
3233
return elements;
3334
}
3435

36+
const compiledChildren = compileList(elements.children);
37+
38+
if (elements.type === 'repeat') {
39+
return compileRepeat(elements.config, compiledChildren);
40+
}
41+
3542
const elementCompiler = quantifiers[elements.type];
3643
if (!elementCompiler) {
3744
throw new Error(`Unknown elements type ${elements.type}`);
3845
}
3946

40-
const children = compileList(elements.children);
41-
return elementCompiler(children);
47+
return elementCompiler(compiledChildren);
4248
}

src/quantifiers.ts renamed to src/quantifiers/index.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import type {
33
OneOrMore,
44
Optionally,
55
RegexElement,
6+
Repeat,
7+
RepeatConfig,
68
ZeroOrMore,
7-
} from './types';
8-
import type { CompilerMap } from './types-internal';
9-
import { wrapGroup } from './utils';
9+
} from '../types';
10+
import type { CompilerMap } from '../types-internal';
11+
import { wrapGroup } from '../utils';
1012

1113
export function oneOrMore(...children: RegexElement[]): OneOrMore {
1214
return {
@@ -36,9 +38,20 @@ export function zeroOrMore(...children: RegexElement[]): ZeroOrMore {
3638
};
3739
}
3840

41+
export function repeat(
42+
config: RepeatConfig,
43+
...children: RegexElement[]
44+
): Repeat {
45+
return {
46+
type: 'repeat',
47+
children,
48+
config,
49+
};
50+
}
51+
3952
export const compilers = {
4053
one: (compiledChildren) => compiledChildren,
4154
oneOrMore: (compiledChildren) => `${wrapGroup(compiledChildren)}+`,
42-
optionally: (compiledChildren: string) => `${wrapGroup(compiledChildren)}?`,
43-
zeroOrMore: (compiledChildren: string) => `${wrapGroup(compiledChildren)}*`,
55+
optionally: (compiledChildren) => `${wrapGroup(compiledChildren)}?`,
56+
zeroOrMore: (compiledChildren) => `${wrapGroup(compiledChildren)}*`,
4457
} satisfies CompilerMap;

src/quantifiers/repeat.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { RepeatConfig } from '../types';
2+
import { wrapGroup } from '../utils';
3+
4+
export function compileRepeat(
5+
config: RepeatConfig,
6+
compiledChildren: string
7+
): string {
8+
if ('count' in config && typeof config.count === 'number') {
9+
return `${wrapGroup(compiledChildren)}{${config.count}}`;
10+
}
11+
12+
if ('min' in config && typeof config.min === 'number') {
13+
return `${wrapGroup(compiledChildren)}{${config.min},${config?.max ?? ''}}`;
14+
}
15+
16+
return `${wrapGroup(compiledChildren)}`;
17+
}

src/types.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
export type RegexElement = string | RegexQuantifier;
22

3-
export type RegexQuantifier = One | OneOrMore | Optionally | ZeroOrMore;
3+
export type RegexQuantifier =
4+
| One
5+
| OneOrMore
6+
| Optionally
7+
| ZeroOrMore
8+
| Repeat;
49

510
// Quantifiers
611
export type One = {
@@ -18,6 +23,18 @@ export type Optionally = {
1823
children: RegexElement[];
1924
};
2025

26+
export type RepeatConfig =
27+
| { min: number; max?: number }
28+
| {
29+
count: number;
30+
};
31+
32+
export type Repeat = {
33+
type: 'repeat';
34+
children: RegexElement[];
35+
config: RepeatConfig;
36+
};
37+
2138
export type ZeroOrMore = {
2239
type: 'zeroOrMore';
2340
children: RegexElement[];

0 commit comments

Comments
 (0)