Skip to content

Commit

Permalink
feat: add basic character classes (#13)
Browse files Browse the repository at this point in the history
Co-authored-by: Maciej Jastrzebski <[email protected]>
  • Loading branch information
jaworek and mdjastrzebski authored Dec 6, 2023
1 parent 3dd6dfe commit e323eeb
Show file tree
Hide file tree
Showing 12 changed files with 121 additions and 49 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ buck-out/

# generated by bob
lib/

/.idea
35 changes: 35 additions & 0 deletions src/__tests__/characterClasses.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { any, digit, whitespace, word } from '../character-classes';
import { buildPattern } from '../compiler';
import { one } from '../quantifiers/base';

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

expect(buildPattern(one('ab'), whitespace)).toEqual(`ab\\s`);

expect(buildPattern(one('ab'), whitespace, one('c'))).toEqual(`ab\\sc`);
});

test('"digit" character class', () => {
expect(buildPattern(digit)).toEqual(`\\d`);

expect(buildPattern(one('ab'), digit)).toEqual(`ab\\d`);

expect(buildPattern(one('ab'), digit, one('c'))).toEqual(`ab\\dc`);
});

test('"word" character class', () => {
expect(buildPattern(word)).toEqual(`\\w`);

expect(buildPattern(one('ab'), word)).toEqual(`ab\\w`);

expect(buildPattern(one('ab'), word, one('c'))).toEqual(`ab\\wc`);
});

test('"any" character class', () => {
expect(buildPattern(any)).toEqual(`.`);

expect(buildPattern(one('ab'), any)).toEqual(`ab.`);

expect(buildPattern(one('ab'), any, one('c'))).toEqual(`ab.c`);
});
3 changes: 2 additions & 1 deletion src/__tests__/compiler.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { buildPattern, buildRegex } from '../compiler';
import { oneOrMore, optionally, one, zeroOrMore, repeat } from '../quantifiers';
import { oneOrMore, optionally, one, zeroOrMore } from '../quantifiers/base';
import { repeat } from '../quantifiers/repeat';

test('basic quantifies', () => {
expect(buildPattern('a')).toEqual('a');
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/quantifiers.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { one, oneOrMore, optionally, zeroOrMore } from '../quantifiers';
import { one, oneOrMore, optionally, zeroOrMore } from '../quantifiers/base';
import { buildPattern, buildRegex } from '../compiler';

test('"oneOrMore" quantifier', () => {
Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/repeat.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { buildPattern } from '../compiler';
import { repeat, zeroOrMore, oneOrMore } from '../quantifiers';
import { zeroOrMore, oneOrMore } from '../quantifiers/base';
import { repeat } from '../quantifiers/repeat';

test('"repeat" quantifier', () => {
expect(buildPattern('a', repeat({ min: 1, max: 5 }, 'b'))).toEqual('ab{1,5}');
Expand Down
26 changes: 26 additions & 0 deletions src/character-classes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type {
Any,
CharacterClass,
Digit,
RegexElement,
Whitespace,
Word,
} from './types';

export const whitespace: Whitespace = { type: 'whitespace' };
export const digit: Digit = { type: 'digit' };
export const word: Word = { type: 'word' };
export const any: Any = { type: 'any' };

export const characterClasses = {
whitespace: '\\s',
digit: '\\d',
word: '\\w',
any: '.',
} as const satisfies Record<string, string>;

export function isCharacterClass(
element: Exclude<RegexElement, string>
): element is CharacterClass {
return element.type in characterClasses;
}
28 changes: 17 additions & 11 deletions src/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { RegexElement } from './types';
import { compilers as quantifiers } from './quantifiers';
import { characterClasses, isCharacterClass } from './character-classes';
import { baseQuantifiers, isBaseQuantifier } from './quantifiers/base';
import { compileRepeat } from './quantifiers/repeat';

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

function compileSingle(elements: RegexElement): string {
if (typeof elements === 'string') {
return elements;
function compileSingle(element: RegexElement): string {
if (typeof element === 'string') {
return element;
}

const compiledChildren = compileList(elements.children);
if (isCharacterClass(element)) {
return characterClasses[element.type];
}

const compiledChildren = compileList(element.children);

if (elements.type === 'repeat') {
return compileRepeat(elements.config, compiledChildren);
if (element.type === 'repeat') {
return compileRepeat(element.config, compiledChildren);
}

const elementCompiler = quantifiers[elements.type];
if (!elementCompiler) {
throw new Error(`Unknown elements type ${elements.type}`);
if (isBaseQuantifier(element)) {
const compiler = baseQuantifiers[element.type];
return compiler(compiledChildren);
}

return elementCompiler(compiledChildren);
// @ts-expect-error User passed incorrect type
throw new Error(`Unknown elements type ${element.type}`);
}
3 changes: 2 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type * from './types';

export { whitespace } from './character-classes';
export { buildRegex, buildPattern } from './compiler';
export { oneOrMore, optionally } from './quantifiers';
export { oneOrMore, optionally } from './quantifiers/base';
25 changes: 9 additions & 16 deletions src/quantifiers/index.ts → src/quantifiers/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import type {
One,
OneOrMore,
Optionally,
Quantifier,
RegexElement,
Repeat,
RepeatConfig,
ZeroOrMore,
} from '../types';
import type { CompilerMap } from '../types-internal';
import { wrapGroup } from '../utils';

export function oneOrMore(...children: RegexElement[]): OneOrMore {
Expand Down Expand Up @@ -38,20 +36,15 @@ export function zeroOrMore(...children: RegexElement[]): ZeroOrMore {
};
}

export function repeat(
config: RepeatConfig,
...children: RegexElement[]
): Repeat {
return {
type: 'repeat',
children,
config,
};
}

export const compilers = {
export const baseQuantifiers = {
one: (compiledChildren) => compiledChildren,
oneOrMore: (compiledChildren) => `${wrapGroup(compiledChildren)}+`,
optionally: (compiledChildren) => `${wrapGroup(compiledChildren)}?`,
zeroOrMore: (compiledChildren) => `${wrapGroup(compiledChildren)}*`,
} satisfies CompilerMap;
} as const satisfies Record<string, (compiledChildren: string) => string>;

export function isBaseQuantifier(
element: Exclude<RegexElement, string>
): element is Quantifier {
return element.type in baseQuantifiers;
}
21 changes: 14 additions & 7 deletions src/quantifiers/repeat.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import type { RepeatConfig } from '../types';
import type { RegexElement, Repeat, RepeatConfig } from '../types';
import { wrapGroup } from '../utils';

export function repeat(
config: RepeatConfig,
...children: RegexElement[]
): Repeat {
return {
type: 'repeat',
children,
config,
};
}

export function compileRepeat(
config: RepeatConfig,
compiledChildren: string
): string {
if ('count' in config && typeof config.count === 'number') {
if ('count' in config) {
return `${wrapGroup(compiledChildren)}{${config.count}}`;
}

if ('min' in config && typeof config.min === 'number') {
return `${wrapGroup(compiledChildren)}{${config.min},${config?.max ?? ''}}`;
}

return `${wrapGroup(compiledChildren)}`;
return `${wrapGroup(compiledChildren)}{${config.min},${config?.max ?? ''}}`;
}
3 changes: 0 additions & 3 deletions src/types-internal.ts

This file was deleted.

19 changes: 11 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
export type RegexElement = string | RegexQuantifier;

export type RegexQuantifier =
| One
| OneOrMore
| Optionally
| ZeroOrMore
| Repeat;
export type RegexElement = string | CharacterClass | Quantifier;

export type CharacterClass = Whitespace | Digit | Word | Any;

export type Quantifier = One | OneOrMore | Optionally | ZeroOrMore | Repeat;

// Character classes
export type Whitespace = { type: 'whitespace' };
export type Digit = { type: 'digit' };
export type Word = { type: 'word' };
export type Any = { type: 'any' };

// Quantifiers
export type One = {
Expand Down

0 comments on commit e323eeb

Please sign in to comment.