Skip to content

Commit 576f566

Browse files
feat: anyOf component (#21)
1 parent e64701b commit 576f566

File tree

13 files changed

+106
-51
lines changed

13 files changed

+106
-51
lines changed

src/character-classes.ts

-26
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { buildPattern as p } from '../../compiler';
2+
import { oneOrMore } from '../../quantifiers/base';
3+
import { anyOf } from '../any-of';
4+
5+
test('"anyOf" base cases', () => {
6+
expect(p(anyOf(''))).toBe('');
7+
expect(p(anyOf('a'))).toBe('a');
8+
expect(p(anyOf('abc'))).toBe('[abc]');
9+
});
10+
11+
test('"anyOf" in context', () => {
12+
expect(p('x', anyOf('a'), 'x')).toBe('xax');
13+
expect(p('x', anyOf('abc'), 'x')).toBe('x[abc]x');
14+
expect(p('x', oneOrMore(anyOf('abc')), 'x')).toBe('x(?:[abc])+x');
15+
});
16+
17+
test('"anyOf" escapes special characters', () => {
18+
expect(p(anyOf('abc-+.'))).toBe('[-abc\\+\\.]');
19+
});
20+
21+
test('"anyOf" moves hyphen to the first position', () => {
22+
expect(p(anyOf('a-bc'))).toBe('[-abc]');
23+
});

src/__tests__/characterClasses.test.tsx src/character-classes/__tests__/base.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { any, digit, whitespace, word } from '../character-classes';
2-
import { buildPattern } from '../compiler';
3-
import { one } from '../quantifiers/base';
1+
import { any, digit, whitespace, word } from '../base';
2+
import { buildPattern } from '../../compiler';
3+
import { one } from '../../quantifiers/base';
44

55
test('"whitespace" character class', () => {
66
expect(buildPattern(whitespace)).toEqual(`\\s`);

src/character-classes/any-of.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { CharacterClass } from '../types';
2+
import { escapeText } from '../utils';
3+
4+
export function anyOf(characters: string): CharacterClass {
5+
return {
6+
type: 'characterClass',
7+
characters: characters.split('').map(escapeText),
8+
};
9+
}

src/character-classes/base.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { CharacterClass } from '../types';
2+
3+
export const whitespace: CharacterClass = {
4+
type: 'characterClass',
5+
characters: ['\\s'],
6+
};
7+
8+
export const digit: CharacterClass = {
9+
type: 'characterClass',
10+
characters: ['\\d'],
11+
};
12+
13+
export const word: CharacterClass = {
14+
type: 'characterClass',
15+
characters: ['\\w'],
16+
};
17+
18+
export const any: CharacterClass = {
19+
type: 'characterClass',
20+
characters: ['.'],
21+
};

src/character-classes/compiler.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { CharacterClass } from '../types';
2+
3+
export function compileCharacterClass({ characters }: CharacterClass): string {
4+
if (characters.length === 0) {
5+
return '';
6+
}
7+
8+
if (characters.length === 1) {
9+
return characters[0]!;
10+
}
11+
12+
return `[${escapeHyphen(characters).join('')}]`;
13+
}
14+
15+
// If passed characters includes hyphen (`-`) it need to be moved to
16+
// first (or last) place in order to treat it as hyphen character and not a range.
17+
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Character_classes#types
18+
function escapeHyphen(characters: string[]) {
19+
if (characters.includes('-')) {
20+
return ['-', ...characters.filter((c) => c !== '-')];
21+
}
22+
23+
return characters;
24+
}

src/compiler.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { RegexElement } from './types';
2-
import { characterClasses, isCharacterClass } from './character-classes';
32
import { compileChoiceOf } from './components/choiceOf';
3+
import { compileCharacterClass } from './character-classes/compiler';
44
import { baseQuantifiers, isBaseQuantifier } from './quantifiers/base';
55
import { compileRepeat } from './quantifiers/repeat';
66
import { escapeText } from './utils';
@@ -36,8 +36,8 @@ function compileSingle(element: RegexElement): string {
3636
return escapeText(element);
3737
}
3838

39-
if (isCharacterClass(element)) {
40-
return characterClasses[element.type];
39+
if (element.type === 'characterClass') {
40+
return compileCharacterClass(element);
4141
}
4242

4343
if (element.type === 'choiceOf') {

src/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
export type * from './types';
22

3-
export { any, digit, whitespace, word } from './character-classes';
43
export { buildRegex, buildPattern } from './compiler';
4+
5+
export { any, digit, whitespace, word } from './character-classes/base';
6+
export { anyOf } from './character-classes/any-of';
57
export { one, oneOrMore, optionally, zeroOrMore } from './quantifiers/base';
68
export { repeat } from './quantifiers/repeat';
79
export { choiceOf } from './components/choiceOf';

src/index.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type * from './types';
2+
3+
export { whitespace } from './character-classes/base';
4+
export { buildRegex, buildPattern } from './compiler';
5+
export { oneOrMore, optionally } from './quantifiers/base';

src/__tests__/quantifiers.test.tsx src/quantifiers/__tests__/base.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { one, oneOrMore, optionally, zeroOrMore } from '../quantifiers/base';
2-
import { buildPattern, buildRegex } from '../compiler';
1+
import { one, oneOrMore, optionally, zeroOrMore } from '../base';
2+
import { buildPattern, buildRegex } from '../../compiler';
33

44
test('"oneOrMore" quantifier', () => {
55
expect(buildPattern(oneOrMore('a'))).toEqual('a+');

src/__tests__/repeat.test.tsx src/quantifiers/__tests__/repeat.test.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { buildPattern } from '../compiler';
2-
import { zeroOrMore, oneOrMore } from '../quantifiers/base';
3-
import { repeat } from '../quantifiers/repeat';
1+
import { buildPattern } from '../../compiler';
2+
import { zeroOrMore, oneOrMore } from '../base';
3+
import { repeat } from '../repeat';
44

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

src/quantifiers/base.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,23 @@ import type {
88
} from '../types';
99
import { wrapGroup } from '../utils';
1010

11-
export function oneOrMore(...children: RegexElement[]): OneOrMore {
11+
export function one(...children: RegexElement[]): One {
1212
return {
13-
type: 'oneOrMore',
13+
type: 'one',
1414
children,
1515
};
1616
}
1717

18-
export function optionally(...children: RegexElement[]): Optionally {
18+
export function oneOrMore(...children: RegexElement[]): OneOrMore {
1919
return {
20-
type: 'optionally',
20+
type: 'oneOrMore',
2121
children,
2222
};
2323
}
2424

25-
export function one(...children: RegexElement[]): One {
25+
export function optionally(...children: RegexElement[]): Optionally {
2626
return {
27-
type: 'one',
27+
type: 'optionally',
2828
children,
2929
};
3030
}

src/types.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
export type RegexElement = string | ChoiceOf | CharacterClass | Quantifier;
22

3-
export type CharacterClass = Whitespace | Digit | Word | Any;
4-
53
export type Quantifier = One | OneOrMore | Optionally | ZeroOrMore | Repeat;
64

7-
// Character classes
8-
export type Whitespace = { type: 'whitespace' };
9-
export type Digit = { type: 'digit' };
10-
export type Word = { type: 'word' };
11-
export type Any = { type: 'any' };
5+
export type CharacterClass = {
6+
type: 'characterClass';
7+
characters: string[];
8+
};
129

1310
// Components
1411
export type ChoiceOf = {

0 commit comments

Comments
 (0)