Skip to content

Commit f8981c4

Browse files
committed
feat: more Set-like operations
1 parent 840cfdb commit f8981c4

File tree

4 files changed

+71
-8
lines changed

4 files changed

+71
-8
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@
33
Zero-dependency TypeScript library for regex utilities that go beyond string matching.
44
These are surprisingly hard to come by for any programming language.
55

6-
- [.and(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#and) - Compute intersection of two regex.
7-
- [.not(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#not) - Compute the complement of a regex.
8-
- [.isEquivalent(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#isEquivalent) - Check whether two regex match the same strings.
9-
- [.size()](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#size) - Count the number of strings that a regex matches.
10-
- [.enumerate()](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#enumerate) - Generate strings matching a regex.
11-
- [.derivative(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#derivative) - Compute a Brzozowski derivative of a regex.
6+
- Set-like operations:
7+
- [.and(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#and) - Compute intersection of two regex.
8+
- [.not()](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#not) - Compute the complement of a regex.
9+
- [.without(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#without) - Compute the difference of two regex.
10+
- Set-like predicates:
11+
- [.isEquivalent(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#isEquivalent) - Check whether two regex match the same strings.
12+
- [.isSubsetOf(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#isSubsetOf)
13+
- [.isSupersetOf(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#isSupersetOf)
14+
- [.isDisjointFrom(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#isDisjointFrom)
15+
- Miscellaneous:
16+
- [.size()](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#size) - Count the number of strings that a regex matches.
17+
- [.enumerate()](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#enumerate) - Generate strings matching a regex.
18+
- [.derivative(...)](https://gruhn.github.io/regex-utils/classes/RegexBuilder.html#derivative) - Compute a Brzozowski derivative of a regex.
1219

1320
## Installation
1421

src/dfa.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export function toStdRegex(inputRegex: RE.ExtRegex): RE.StdRegex {
206206
*
207207
* TODO: maybe expose `equal` as dedicated function.
208208
*/
209-
export function equivalent(regexA: RE.ExtRegex, regexB: RE.ExtRegex): boolean {
209+
export function isEquivalent(regexA: RE.ExtRegex, regexB: RE.ExtRegex): boolean {
210210
if (RE.equal(regexA, regexB)) { // First check hash based equality: cheap but weak.
211211
return true
212212
} else {

src/index.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { equivalent as isEquivalent, toStdRegex } from './dfa'
1+
import { isEquivalent, toStdRegex } from './dfa'
22
import * as RE from './regex'
33
import { parseRegExp } from './regex-parser'
44

@@ -337,6 +337,50 @@ class RegexBuilder {
337337
isEquivalent(re: RegexLike): boolean {
338338
return isEquivalent(this.regex, fromRegexLike(re))
339339
}
340+
341+
/**
342+
* Constructs the difference of the current regex and `re`.
343+
* That is, this returns a new regex which matches all strings that
344+
* the current regex matches EXCEPT everything that `re` matches.
345+
*
346+
* @example
347+
* ```typescript
348+
* RB(/^a*$/).without(/^a{5}$/) // /^(a{0,4}|a{6,})$/
349+
* ```
350+
*
351+
* @public
352+
*/
353+
without(re: RegexLike): RegexBuilder {
354+
return this.and(RB(re).not())
355+
}
356+
357+
/**
358+
* TODO
359+
*
360+
* @public
361+
*/
362+
isSubsetOf(re: RegexLike): boolean {
363+
return this.without(re).isEmpty()
364+
}
365+
366+
/**
367+
* TODO
368+
*
369+
* @public
370+
*/
371+
isSupersetOf(re: RegexLike): boolean {
372+
return RB(re).without(this).isEmpty()
373+
}
374+
375+
/**
376+
* TODO
377+
*
378+
* @public
379+
*/
380+
isDisjointFrom(re: RegexLike): boolean {
381+
return this.and(RB(re)).isEmpty()
382+
}
383+
340384
}
341385

342386
/**

test/index.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,20 @@ describe('isEquivalent', () => {
7373
])('%s is not equivalent to %s', (re1, re2) => {
7474
expect(RB(re1).isEquivalent(re2)).toBe(false)
7575
})
76+
7677
})
7778

79+
describe('without', () => {
80+
81+
test.each([
82+
[/^a*$/, /^a{3,10}$/, /^(a{,2}|a{11,})$/],
83+
])('%s without %s is %s', (re1, re2, expected) => {
84+
const actual = RB(re1).without(re2)
85+
expectSubsetOf(RB(expected), actual)
86+
expectSubsetOf(actual, RB(expected))
87+
})
88+
89+
})
7890

7991
test('A ∩ ¬A = ∅', () => {
8092
fc.assert(

0 commit comments

Comments
 (0)