Skip to content

Commit 81b96f7

Browse files
Merge pull request #3 from lfbittencourt/feature/escaping
New feature: escaping
2 parents a36930c + d54648e commit 81b96f7

5 files changed

Lines changed: 102 additions & 16 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ Igroups can also be nested:
108108
(+a|(+b|c)) => ['a', 'ab', 'abc', 'ac', 'b', 'bc', 'c']
109109
```
110110

111+
### Escaping
112+
113+
You can escape special characters by using a backslash (`\`):
114+
115+
```
116+
\(a\|b\) => ['(a|b)']
117+
```
118+
111119
### Complex patterns
112120

113121
Finally, you can mix all of them into complex patterns:

src/parser.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,30 @@ import { EmbeddedActionsParser } from 'chevrotain';
33
import { And, LogicalChildren, Or } from './logical';
44

55
import tokens, {
6-
Text, LeftParenthesis, RightParenthesis, QuestionMark, Pipe, PlusSign,
6+
Backslash,
7+
escapableTokens,
8+
LeftParenthesis,
9+
Pipe,
10+
PlusSign,
11+
QuestionMark,
12+
RightParenthesis,
13+
Text,
714
} from './tokens';
815

916
/**
1017
* GRAMMAR
1118
*
1219
* element:
13-
* Text | optionalText | optionableGroup
20+
* optionableEscapedToken | optionableText | optionableGroup
1421
*
15-
* optionalText:
16-
* Text QuestionMark
22+
* optionableEscapedToken:
23+
* Backslash
24+
* (Backslash | LeftParenthesis | Pipe | PlusSign | QuestionMark | RightParenthesis)
25+
* (QuestionMark)?
26+
*
27+
* optionableText:
28+
* Text
29+
* (QuestionMark)?
1730
*
1831
* optionableGroup:
1932
* LeftParenthesis
@@ -32,14 +45,28 @@ export default class Parser extends EmbeddedActionsParser {
3245
this.performSelfAnalysis();
3346
}
3447

35-
private text = this.RULE('text', () => this.CONSUME(Text).image);
48+
private optionableEscapedToken = this.RULE('escapedToken', () => {
49+
this.CONSUME1(Backslash);
3650

37-
private optionalText = this.RULE('optionalText', () => {
38-
const text: string = this.SUBRULE(this.text);
51+
const token = this.OR(
52+
escapableTokens.map((escapableToken) => ({ ALT: () => this.CONSUME2(escapableToken) })),
53+
);
54+
55+
if (this.OPTION(() => this.CONSUME(QuestionMark))) {
56+
return new Or(token.image, '');
57+
}
3958

40-
this.CONSUME(QuestionMark);
59+
return token.image;
60+
});
61+
62+
private optionableText = this.RULE('optionalText', () => {
63+
const text: string = this.CONSUME(Text).image;
64+
65+
if (this.OPTION(() => this.CONSUME(QuestionMark))) {
66+
return new Or(text, text.slice(0, -1));
67+
}
4168

42-
return this.ACTION(() => new Or(text, text.slice(0, -1)));
69+
return text;
4370
});
4471

4572
private optionableGroup = this.RULE('optionableGroup', () => {
@@ -72,8 +99,8 @@ export default class Parser extends EmbeddedActionsParser {
7299
});
73100

74101
private element = this.RULE('element', () => this.OR([
75-
{ ALT: () => this.SUBRULE(this.optionalText) },
76-
{ ALT: () => this.SUBRULE(this.text) },
102+
{ ALT: () => this.SUBRULE(this.optionableEscapedToken) },
103+
{ ALT: () => this.SUBRULE(this.optionableText) },
77104
{ ALT: () => this.SUBRULE(this.optionableGroup) },
78105
]));
79106

src/tokens.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,15 @@ export const RightParenthesis = createToken({ name: 'RightParenthesis', pattern:
66
export const QuestionMark = createToken({ name: 'QuestionMark', pattern: '?' });
77
export const Pipe = createToken({ name: 'Pipe', pattern: '|' });
88
export const PlusSign = createToken({ name: 'PlusSign', pattern: '+' });
9+
export const Backslash = createToken({ name: 'Backslash', pattern: '\\' });
910

10-
export default [Text, LeftParenthesis, RightParenthesis, QuestionMark, Pipe, PlusSign];
11+
export const escapableTokens = [
12+
Backslash,
13+
LeftParenthesis,
14+
Pipe,
15+
PlusSign,
16+
QuestionMark,
17+
RightParenthesis,
18+
];
19+
20+
export default [...escapableTokens, Text];

tests/escaping.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { escapableTokens } from '../src/tokens';
2+
3+
import expand from '../src';
4+
5+
describe('escaping', () => {
6+
it('should escape escaped tokens', () => {
7+
const result = expand(escapableTokens.map((token) => `\\${token.PATTERN}`).join('')).sort();
8+
9+
expect(result).toEqual([escapableTokens.map((token) => token.PATTERN).join('')].sort());
10+
});
11+
12+
it('should escape escape parenthesis', () => {
13+
const result = expand('(\\(\\))').sort();
14+
15+
expect(result).toEqual(['()'].sort());
16+
});
17+
18+
it('should escape pipe', () => {
19+
const result = expand('(\\||a)').sort();
20+
21+
expect(result).toEqual(['|', 'a'].sort());
22+
});
23+
24+
it('should escape plus sign', () => {
25+
const result = expand('(+\\+|a)').sort();
26+
27+
expect(result).toEqual(['+', 'a', '+a'].sort());
28+
});
29+
30+
it('should escape question mark', () => {
31+
const result = expand('\\??').sort();
32+
33+
expect(result).toEqual(['?', ''].sort());
34+
});
35+
36+
it('should escape backslash', () => {
37+
const result = expand('\\\\').sort();
38+
39+
expect(result).toEqual(['\\'].sort());
40+
});
41+
});

tests/igroups.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import expand from '../src';
22

33
describe('inclusive groups', () => {
4-
it('should expand inclusive groups', () => {
4+
it('should expand igroups', () => {
55
const result = expand('(+a|b)').sort();
66

77
expect(result).toEqual(['a', 'b', 'ab'].sort());
88
});
99

10-
it('should expand optional inclusive groups', () => {
10+
it('should expand optional igroups', () => {
1111
const result = expand('(+a|b)?').sort();
1212

1313
expect(result).toEqual(['a', 'b', 'ab', ''].sort());
1414
});
1515

16-
it('should not expand single-option inclusive groups', () => {
16+
it('should not expand single-option igroups', () => {
1717
const result = expand('(+a)').sort();
1818

1919
expect(result).toEqual(['a'].sort());
2020
});
2121

22-
it('should expand nested inclusive groups', () => {
22+
it('should expand nested igroups', () => {
2323
const result = expand('(+a|(+b|c))').sort();
2424

2525
expect(result).toEqual(['a', 'ab', 'abc', 'ac', 'b', 'bc', 'c'].sort());

0 commit comments

Comments
 (0)