Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: repeat quantifier #11

Merged
merged 2 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/__tests__/compiler.test.tsx
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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)*');
Expand Down
15 changes: 15 additions & 0 deletions src/__tests__/repeat.test.tsx
Original file line number Diff line number Diff line change
@@ -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}');
});
10 changes: 8 additions & 2 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 { compileRepeat } from './quantifiers/repeat';

/**
* Generate RegExp object for elements.
Expand Down Expand Up @@ -32,11 +33,16 @@ function compileSingle(elements: RegexElement): string {
return elements;
}

const compiledChildren = compileList(elements.children);

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

const elementCompiler = quantifiers[elements.type];
if (!elementCompiler) {
throw new Error(`Unknown elements type ${elements.type}`);
}

const children = compileList(elements.children);
return elementCompiler(children);
return elementCompiler(compiledChildren);
}
23 changes: 18 additions & 5 deletions src/quantifiers.ts → src/quantifiers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import type {
OneOrMore,
Optionally,
RegexElement,
Repeat,
RepeatConfig,
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 {
Expand Down Expand Up @@ -36,9 +38,20 @@ export function zeroOrMore(...children: RegexElement[]): ZeroOrMore {
};
}

export function repeat(
config: RepeatConfig,
...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;
17 changes: 17 additions & 0 deletions src/quantifiers/repeat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { RepeatConfig } from '../types';
import { wrapGroup } from '../utils';

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

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

return `${wrapGroup(compiledChildren)}`;
}
19 changes: 18 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -18,6 +23,18 @@ export type Optionally = {
children: RegexElement[];
};

export type RepeatConfig =
| { min: number; max?: number }
| {
count: number;
};

export type Repeat = {
type: 'repeat';
children: RegexElement[];
config: RepeatConfig;
};

export type ZeroOrMore = {
type: 'zeroOrMore';
children: RegexElement[];
Expand Down