Skip to content

Commit

Permalink
feat: regex construct (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdjastrzebski authored Mar 27, 2024
1 parent 4469146 commit 91adb02
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 24 deletions.
45 changes: 23 additions & 22 deletions src/__tests__/example-js-number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import {
endOfString,
oneOrMore,
optional,
regex,
startOfString,
zeroOrMore,
} from '..';

test('example: validate JavaScript number', () => {
const sign = anyOf('+-');
const exponent = [anyOf('eE'), optional(sign), oneOrMore(digit)];
const exponent = regex([anyOf('eE'), optional(sign), oneOrMore(digit)]);

const regex = buildRegExp([
const numberValidator = buildRegExp([
startOfString,
optional(sign),
choiceOf(
Expand All @@ -25,26 +26,26 @@ test('example: validate JavaScript number', () => {
endOfString,
]);

expect(regex).toMatchString('0');
expect(regex).toMatchString('-1');
expect(regex).toMatchString('+1');
expect(regex).toMatchString('1.0');
expect(regex).toMatchString('1.1234');
expect(regex).toMatchString('1.');
expect(regex).toMatchString('.1');
expect(regex).toMatchString('-.1234');
expect(regex).toMatchString('+.5');
expect(regex).toMatchString('1e21');
expect(regex).toMatchString('1e-21');
expect(regex).toMatchString('+1e+42');
expect(regex).toMatchString('-1e-42');
expect(numberValidator).toMatchString('0');
expect(numberValidator).toMatchString('-1');
expect(numberValidator).toMatchString('+1');
expect(numberValidator).toMatchString('1.0');
expect(numberValidator).toMatchString('1.1234');
expect(numberValidator).toMatchString('1.');
expect(numberValidator).toMatchString('.1');
expect(numberValidator).toMatchString('-.1234');
expect(numberValidator).toMatchString('+.5');
expect(numberValidator).toMatchString('1e21');
expect(numberValidator).toMatchString('1e-21');
expect(numberValidator).toMatchString('+1e+42');
expect(numberValidator).toMatchString('-1e-42');

expect(regex).not.toMatchString('');
expect(regex).not.toMatchString('a');
expect(regex).not.toMatchString('1a');
expect(regex).not.toMatchString('1.0.');
expect(regex).not.toMatchString('.1.1');
expect(regex).not.toMatchString('.');
expect(numberValidator).not.toMatchString('');
expect(numberValidator).not.toMatchString('a');
expect(numberValidator).not.toMatchString('1a');
expect(numberValidator).not.toMatchString('1.0.');
expect(numberValidator).not.toMatchString('.1.1');
expect(numberValidator).not.toMatchString('.');

expect(regex).toEqualRegex(/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/);
expect(numberValidator).toEqualRegex(/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/);
});
7 changes: 7 additions & 0 deletions src/constructs/__tests__/regex.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { regex } from '../..';

test('`regex` no-op pattern', () => {
expect(regex('a')).toEqualRegex(/a/);
expect(regex(['a', 'b'])).toEqualRegex(/ab/);
expect([regex('a'), regex(['b', 'c'])]).toEqualRegex(/abc/);
});
21 changes: 21 additions & 0 deletions src/constructs/regex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { encodeSequence } from '../encoder/encoder';
import type { EncodeResult } from '../encoder/types';
import { ensureArray } from '../utils/elements';
import type { RegexConstruct, RegexElement, RegexSequence } from '../types';

export interface Regex extends RegexConstruct {
type: 'sequence';
children: RegexElement[];
}

export function regex(sequence: RegexSequence): Regex {
return {
type: 'sequence',
children: ensureArray(sequence),
encode: encodeRegex,
};
}

function encodeRegex(this: Regex): EncodeResult {
return encodeSequence(this.children);
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ export { lookbehind } from './constructs/lookbehind';
export { negativeLookahead } from './constructs/negative-lookahead';
export { negativeLookbehind } from './constructs/negative-lookbehind';
export { oneOrMore, optional, zeroOrMore } from './constructs/quantifiers';
export { regex } from './constructs/regex';
export { repeat } from './constructs/repeat';
37 changes: 35 additions & 2 deletions website/docs/api/constructs.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ These functions and objects represent available regex constructs.

```ts
function choiceOf(
...alternatives: RegexSequence[]
...alternatives: RegexSequence[],
): ChoiceOf {
```
Expand All @@ -22,7 +22,9 @@ Example: `choiceOf("color", "colour")` matches either `color` or `colour` patter
### `capture()`
```ts
function capture(sequence: RegexSequence): Capture;
function capture(
sequence: RegexSequence,
): Capture;
```
Regex syntax: `(...)`.
Expand All @@ -35,3 +37,34 @@ TS Regex Builder does not have a construct for non-capturing groups. Such groups
:::
### `regex()`
```ts
function regex(
sequence: RegexSequence,
): Regex;
```
Regex syntax: the pattern remains unchanged when wrapped by this construct.
This construct is a no-op operator that groups array of `RegexElements` into a single element for composition purposes. This is particularly useful for defining smaller sequence patterns as separate variables.
Without `regex()`:
```ts
const exponent = [anyOf('eE'), optional(anyOf('+-')), oneOrMore(digit)];
const numberWithExponent = buildRegExp([
oneOrMore(digit),
...exponent, // Need to spread "exponent" as it's an array.
]);
```
With `regex()`:
```ts
const exponent = regex([anyOf('eE'), optional(anyOf('+-')), oneOrMore(digit)]);
const numberWithExponent = buildRegExp([
oneOrMore(digit),
exponent, // Easily compose "exponent" sequence as a single element.
]);
```

0 comments on commit 91adb02

Please sign in to comment.