Skip to content

Commit e323eeb

Browse files
feat: add basic character classes (#13)
Co-authored-by: Maciej Jastrzebski <[email protected]>
1 parent 3dd6dfe commit e323eeb

File tree

12 files changed

+121
-49
lines changed

12 files changed

+121
-49
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ buck-out/
3030

3131
# generated by bob
3232
lib/
33+
34+
/.idea
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { any, digit, whitespace, word } from '../character-classes';
2+
import { buildPattern } from '../compiler';
3+
import { one } from '../quantifiers/base';
4+
5+
test('"whitespace" character class', () => {
6+
expect(buildPattern(whitespace)).toEqual(`\\s`);
7+
8+
expect(buildPattern(one('ab'), whitespace)).toEqual(`ab\\s`);
9+
10+
expect(buildPattern(one('ab'), whitespace, one('c'))).toEqual(`ab\\sc`);
11+
});
12+
13+
test('"digit" character class', () => {
14+
expect(buildPattern(digit)).toEqual(`\\d`);
15+
16+
expect(buildPattern(one('ab'), digit)).toEqual(`ab\\d`);
17+
18+
expect(buildPattern(one('ab'), digit, one('c'))).toEqual(`ab\\dc`);
19+
});
20+
21+
test('"word" character class', () => {
22+
expect(buildPattern(word)).toEqual(`\\w`);
23+
24+
expect(buildPattern(one('ab'), word)).toEqual(`ab\\w`);
25+
26+
expect(buildPattern(one('ab'), word, one('c'))).toEqual(`ab\\wc`);
27+
});
28+
29+
test('"any" character class', () => {
30+
expect(buildPattern(any)).toEqual(`.`);
31+
32+
expect(buildPattern(one('ab'), any)).toEqual(`ab.`);
33+
34+
expect(buildPattern(one('ab'), any, one('c'))).toEqual(`ab.c`);
35+
});

src/__tests__/compiler.test.tsx

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

45
test('basic quantifies', () => {
56
expect(buildPattern('a')).toEqual('a');

src/__tests__/quantifiers.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { one, oneOrMore, optionally, zeroOrMore } from '../quantifiers';
1+
import { one, oneOrMore, optionally, zeroOrMore } from '../quantifiers/base';
22
import { buildPattern, buildRegex } from '../compiler';
33

44
test('"oneOrMore" quantifier', () => {

src/__tests__/repeat.test.tsx

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

45
test('"repeat" quantifier', () => {
56
expect(buildPattern('a', repeat({ min: 1, max: 5 }, 'b'))).toEqual('ab{1,5}');

src/character-classes.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type {
2+
Any,
3+
CharacterClass,
4+
Digit,
5+
RegexElement,
6+
Whitespace,
7+
Word,
8+
} from './types';
9+
10+
export const whitespace: Whitespace = { type: 'whitespace' };
11+
export const digit: Digit = { type: 'digit' };
12+
export const word: Word = { type: 'word' };
13+
export const any: Any = { type: 'any' };
14+
15+
export const characterClasses = {
16+
whitespace: '\\s',
17+
digit: '\\d',
18+
word: '\\w',
19+
any: '.',
20+
} as const satisfies Record<string, string>;
21+
22+
export function isCharacterClass(
23+
element: Exclude<RegexElement, string>
24+
): element is CharacterClass {
25+
return element.type in characterClasses;
26+
}

src/compiler.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { RegexElement } from './types';
2-
import { compilers as quantifiers } from './quantifiers';
2+
import { characterClasses, isCharacterClass } from './character-classes';
3+
import { baseQuantifiers, isBaseQuantifier } from './quantifiers/base';
34
import { compileRepeat } from './quantifiers/repeat';
45

56
/**
@@ -28,21 +29,26 @@ function compileList(elements: RegexElement[]): string {
2829
return elements.map((c) => compileSingle(c)).join('');
2930
}
3031

31-
function compileSingle(elements: RegexElement): string {
32-
if (typeof elements === 'string') {
33-
return elements;
32+
function compileSingle(element: RegexElement): string {
33+
if (typeof element === 'string') {
34+
return element;
3435
}
3536

36-
const compiledChildren = compileList(elements.children);
37+
if (isCharacterClass(element)) {
38+
return characterClasses[element.type];
39+
}
40+
41+
const compiledChildren = compileList(element.children);
3742

38-
if (elements.type === 'repeat') {
39-
return compileRepeat(elements.config, compiledChildren);
43+
if (element.type === 'repeat') {
44+
return compileRepeat(element.config, compiledChildren);
4045
}
4146

42-
const elementCompiler = quantifiers[elements.type];
43-
if (!elementCompiler) {
44-
throw new Error(`Unknown elements type ${elements.type}`);
47+
if (isBaseQuantifier(element)) {
48+
const compiler = baseQuantifiers[element.type];
49+
return compiler(compiledChildren);
4550
}
4651

47-
return elementCompiler(compiledChildren);
52+
// @ts-expect-error User passed incorrect type
53+
throw new Error(`Unknown elements type ${element.type}`);
4854
}

src/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type * from './types';
22

3+
export { whitespace } from './character-classes';
34
export { buildRegex, buildPattern } from './compiler';
4-
export { oneOrMore, optionally } from './quantifiers';
5+
export { oneOrMore, optionally } from './quantifiers/base';

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

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ import type {
22
One,
33
OneOrMore,
44
Optionally,
5+
Quantifier,
56
RegexElement,
6-
Repeat,
7-
RepeatConfig,
87
ZeroOrMore,
98
} from '../types';
10-
import type { CompilerMap } from '../types-internal';
119
import { wrapGroup } from '../utils';
1210

1311
export function oneOrMore(...children: RegexElement[]): OneOrMore {
@@ -38,20 +36,15 @@ export function zeroOrMore(...children: RegexElement[]): ZeroOrMore {
3836
};
3937
}
4038

41-
export function repeat(
42-
config: RepeatConfig,
43-
...children: RegexElement[]
44-
): Repeat {
45-
return {
46-
type: 'repeat',
47-
children,
48-
config,
49-
};
50-
}
51-
52-
export const compilers = {
39+
export const baseQuantifiers = {
5340
one: (compiledChildren) => compiledChildren,
5441
oneOrMore: (compiledChildren) => `${wrapGroup(compiledChildren)}+`,
5542
optionally: (compiledChildren) => `${wrapGroup(compiledChildren)}?`,
5643
zeroOrMore: (compiledChildren) => `${wrapGroup(compiledChildren)}*`,
57-
} satisfies CompilerMap;
44+
} as const satisfies Record<string, (compiledChildren: string) => string>;
45+
46+
export function isBaseQuantifier(
47+
element: Exclude<RegexElement, string>
48+
): element is Quantifier {
49+
return element.type in baseQuantifiers;
50+
}

src/quantifiers/repeat.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1-
import type { RepeatConfig } from '../types';
1+
import type { RegexElement, Repeat, RepeatConfig } from '../types';
22
import { wrapGroup } from '../utils';
33

4+
export function repeat(
5+
config: RepeatConfig,
6+
...children: RegexElement[]
7+
): Repeat {
8+
return {
9+
type: 'repeat',
10+
children,
11+
config,
12+
};
13+
}
14+
415
export function compileRepeat(
516
config: RepeatConfig,
617
compiledChildren: string
718
): string {
8-
if ('count' in config && typeof config.count === 'number') {
19+
if ('count' in config) {
920
return `${wrapGroup(compiledChildren)}{${config.count}}`;
1021
}
1122

12-
if ('min' in config && typeof config.min === 'number') {
13-
return `${wrapGroup(compiledChildren)}{${config.min},${config?.max ?? ''}}`;
14-
}
15-
16-
return `${wrapGroup(compiledChildren)}`;
23+
return `${wrapGroup(compiledChildren)}{${config.min},${config?.max ?? ''}}`;
1724
}

0 commit comments

Comments
 (0)