Skip to content

Commit 6fbf638

Browse files
committed
[RegExp] Fix escaping in character classes
1 parent 8eeb767 commit 6fbf638

4 files changed

Lines changed: 58 additions & 48 deletions

File tree

lib/src/regexp/classes.dart

Lines changed: 0 additions & 33 deletions
This file was deleted.

lib/src/regexp/escape.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import 'node.dart';
2+
3+
final escapeChars = <String, String>{
4+
't': '\t',
5+
'n': '\n',
6+
'r': '\r',
7+
'f': '\f',
8+
'e': '\x1b',
9+
};
10+
11+
final escapeClasses = <String, Node>{
12+
's': _spaceCharClass,
13+
'S': ComplementNode(_spaceCharClass),
14+
'd': _digitCharClass,
15+
'D': ComplementNode(_digitCharClass),
16+
'w': _wordCharClass,
17+
'W': ComplementNode(_wordCharClass),
18+
};
19+
20+
final _spaceCharClass = <Node>[
21+
LiteralNode(' '),
22+
LiteralNode('\t'),
23+
LiteralNode('\n'),
24+
LiteralNode('\r'),
25+
LiteralNode('\f'),
26+
LiteralNode('\v'),
27+
].reduce(AlternationNode.new);
28+
29+
final _digitCharClass = RangeNode('0', '9');
30+
31+
final _wordCharClass = <Node>[
32+
RangeNode('a', 'z'),
33+
RangeNode('A', 'Z'),
34+
RangeNode('0', '9'),
35+
LiteralNode('_'),
36+
].reduce(AlternationNode.new);

lib/src/regexp/parser.dart

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import 'package:petitparser/definition.dart';
22
import 'package:petitparser/expression.dart';
33
import 'package:petitparser/parser.dart';
44

5-
import 'classes.dart';
5+
import 'escape.dart';
66
import 'node.dart';
77

88
class RegexpParserDefinition extends GrammarDefinition<Node> {
99
Parser<Node> escape() => any()
1010
.skip(before: char(r'\'))
11-
.map((char) => escapeClasses[char] ?? LiteralNode(char));
11+
.map(
12+
(char) => escapeClasses.containsKey(char)
13+
? escapeClasses[char]!
14+
: LiteralNode(escapeChars[char] ?? char),
15+
);
1216
Parser<Node> dot() => char('.').map((_) => DotNode());
1317
Parser<Node> startAnchor() => char('^').map((_) => StartAnchorNode());
1418
Parser<Node> endAnchor() => char(r'$').map((_) => EndAnchorNode());
@@ -20,16 +24,19 @@ class RegexpParserDefinition extends GrammarDefinition<Node> {
2024
Parser<Node> charClassItems() => ref0(
2125
charClassItem,
2226
).plus().map((items) => items.reduce(AlternationNode.new));
23-
Parser<Node> charClassItem() => [
24-
ref0(charClassRange),
25-
ref0(escape),
26-
noneOf(']').map((char) => LiteralNode(char)),
27+
Parser<String> charClassChar() => [
28+
any().skip(before: char(r'\')).map((char) => escapeChars[char] ?? char),
29+
noneOf(']'),
2730
].toChoiceParser();
2831
Parser<Node> charClassRange() => seq3(
29-
any(),
32+
ref0(charClassChar),
3033
char('-'),
31-
any(),
34+
ref0(charClassChar),
3235
).map3((start, _, end) => RangeNode(start, end));
36+
Parser<Node> charClassItem() => [
37+
ref0(charClassRange),
38+
ref0(charClassChar).map(LiteralNode.new),
39+
].toChoiceParser();
3340

3441
Parser<int> integer() => digit().plusString().trim().map(int.parse);
3542
Parser<({int min, int? max})> range() =>

test/regexp_test.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'package:petitparser/reflection.dart';
22
import 'package:petitparser_examples/regexp.dart';
3-
import 'package:petitparser_examples/src/regexp/classes.dart';
43
import 'package:test/test.dart';
54

65
void expectedEqual(Node actual, Node expected) {
@@ -39,12 +38,12 @@ void main() {
3938
expectedEqual(Node.fromString(r'\e'), LiteralNode('\x1b'));
4039
});
4140
test('escape classes', () {
42-
expectedEqual(Node.fromString(r'\s'), spaceCharClass);
43-
expectedEqual(Node.fromString(r'\S'), ComplementNode(spaceCharClass));
44-
expectedEqual(Node.fromString(r'\d'), digitCharClass);
45-
expectedEqual(Node.fromString(r'\D'), ComplementNode(digitCharClass));
46-
expectedEqual(Node.fromString(r'\w'), wordCharClass);
47-
expectedEqual(Node.fromString(r'\W'), ComplementNode(wordCharClass));
41+
expect(Node.fromString(r'\s'), isA<AlternationNode>());
42+
expect(Node.fromString(r'\S'), isA<ComplementNode>());
43+
expect(Node.fromString(r'\d'), isA<RangeNode>());
44+
expect(Node.fromString(r'\D'), isA<ComplementNode>());
45+
expect(Node.fromString(r'\w'), isA<AlternationNode>());
46+
expect(Node.fromString(r'\W'), isA<ComplementNode>());
4847
});
4948
test('escape other', () {
5049
expectedEqual(Node.fromString(r'\.'), LiteralNode('.'));
@@ -80,6 +79,7 @@ void main() {
8079
AlternationNode(AlternationNode(la, lb), lc),
8180
);
8281
expectedEqual(Node.fromString(r'[a-c]'), RangeNode('a', 'c'));
82+
expectedEqual(Node.fromString(r'[\t-z]'), RangeNode('\t', 'z'));
8383
expectedEqual(Node.fromString(r'[^a]'), ComplementNode(la));
8484
expectedEqual(
8585
Node.fromString(r'[^ab]'),

0 commit comments

Comments
 (0)