Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:collection';
import 'package:analyzer/src/generated/ast.dart';
// ASCII character codes.
const _zero = 0x30;
const _nine = 0x39;
const _backslash = 0x5C;
const _openCurly = 0x7B;
const _closeCurly = 0x7D;
const _capitalA = 0x41;
const _capitalZ = 0x5A;
const _a = 0x61;
const _n = 0x6E;
const _r = 0x72;
const _f = 0x66;
const _b = 0x62;
const _t = 0x74;
const _u = 0x75;
const _v = 0x76;
const _x = 0x78;
const _z = 0x7A;
const _newline = 0xA;
const _carriageReturn = 0xD;
const _formFeed = 0xC;
const _backspace = 0x8;
const _tab = 0x9;
const _verticalTab = 0xB;
/// An iterator over the runes in the value of a [StringLiteral].
///
/// In addition to exposing the values of the runes themselves, this also
/// exposes the offset of the current rune in the Dart source file.
class StringLiteralIterator extends Iterator<int> {
int get current => _current;
int _current;
/// The offset of the beginning of [current] in the Dart source file that
/// contains the string literal.
///
/// Before iteration begins, this points to the character before the first
/// rune.
int get offset => _offset;
int _offset;
/// The offset of the next rune.
///
/// This isn't necessarily just `offset + 1`, since a single rune may be
/// represented by multiple characters in the source file, or a string literal
/// may be composed of several adjacent string literals.
int _nextOffset;
/// All [SimpleStringLiteral]s that compose the input literal.
///
/// If the input literal is itself a [SimpleStringLiteral], this just contains
/// that literal; otherwise, the literal is an [AdjacentStrings], and this
/// contains its component literals.
final _strings = new Queue<SimpleStringLiteral>();
/// Whether this is a raw string that begins with `r`.
///
/// This is necessary for knowing how to parse escape sequences.
bool _isRaw;
/// The iterator over the runes in the Dart source file.
///
/// When switching to a new string in [_strings], this is updated to point to
/// that string's component runes.
Iterator<int> _runes;
/// Whether this has finished iterating.
bool _done = false;
/// Creates a new [StringLiteralIterator] iterating over the contents of
/// [literal].
///
/// Throws an [ArgumentError] if [literal] contains interpolated strings.
StringLiteralIterator(StringLiteral literal) {
if (literal is StringInterpolation) {
throw new ArgumentError("Can't iterate over an interpolated string.");
} else if (literal is SimpleStringLiteral) {
_strings.add(literal);
} else {
assert(literal is AdjacentStrings);
for (var string in (literal as AdjacentStrings).strings) {
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
if (string is StringInterpolation) {
throw new ArgumentError("Can't iterate over an interpolated string.");
}
assert(string is SimpleStringLiteral);
_strings.add(string);
}
}
_offset = _strings.first.contentsOffset - 1;
}
bool moveNext() {
if (_done) return false;
// If we're at beginning of a [SimpleStringLiteral], move forward until
// there's actually text to consume.
while (_runes == null || _runes.current == null) {
if (_strings.isEmpty) {
// Move the offset past the end of the text.
_offset = _nextOffset;
_current = null;
return false;
}
var string = _strings.removeFirst();
var start = string.contentsOffset - string.offset;
// Compensate for the opening and closing quotes.
var end = start + string.literal.lexeme.length -
2 * (string.isMultiline ? 3 : 1) -
(string.isRaw ? 1 : 0);
var text = string.literal.lexeme.substring(start, end);
_nextOffset = string.contentsOffset;
_isRaw = string.isRaw;
_runes = text.runes.iterator;
_runes.moveNext();
}
_offset = _nextOffset;
_current = _nextRune();
if (_current != null) return true;
// If we encounter a parse failure, stop moving forward immediately.
_strings.clear();
return false;
}
/// Consume and return the next rune.
int _nextRune() {
if (_isRaw || _runes.current != _backslash) {
var rune = _runes.current;
_moveRunesNext();
return rune;
}
if (!_moveRunesNext()) return null;
return _parseEscapeSequence();
}
/// Parse an escape sequence in the underlying Dart text.
///
/// This assumes that a backslash has already been consumed. It leaves the
/// [_runes] cursor on the first character after the escape sequence.
int _parseEscapeSequence() {
switch (_runes.current) {
case _n:
_moveRunesNext();
return _newline;
case _r:
_moveRunesNext();
return _carriageReturn;
case _f:
_moveRunesNext();
return _formFeed;
case _b:
_moveRunesNext();
return _backspace;
case _t:
_moveRunesNext();
return _tab;
case _v:
_moveRunesNext();
return _verticalTab;
case _x:
if (!_moveRunesNext()) return null;
return _parseHex(2);
case _u:
if (!_moveRunesNext()) return null;
if (_runes.current != _openCurly) return _parseHex(4);
if (!_moveRunesNext()) return null;
var number = _parseHexSequence();
if (_runes.current != _closeCurly) return null;
if (!_moveRunesNext()) return null;
return number;
default:
var rune = _runes.current;
_moveRunesNext();
return rune;
}
}
/// Parse a variable-length sequence of hexadecimal digits and returns their
/// value as an [int].
///
/// This parses digits as they appear in a unicode escape sequence: one to six
/// hex digits.
int _parseHexSequence() {
var number = _parseHexDigit(_runes.current);
if (number == null) return null;
if (!_moveRunesNext()) return null;
for (var i = 0; i < 5; i++) {
var digit = _parseHexDigit(_runes.current);
if (digit == null) break;
number = number * 16 + digit;
if (!_moveRunesNext()) return null;
}
return number;
}
/// Parses [digits] hexadecimal digits and returns their value as an [int].
int _parseHex(int digits) {
var number = 0;
for (var i = 0; i < digits; i++) {
if (_runes.current == null) return null;
var digit = _parseHexDigit(_runes.current);
if (digit == null) return null;
number = number * 16 + digit;
_moveRunesNext();
}
return number;
}
/// Parses a single hexadecimal digit.
int _parseHexDigit(int rune) {
if (rune < _zero) return null;
if (rune <= _nine) return rune - _zero;
if (rune < _capitalA) return null;
if (rune <= _capitalZ) return 10 + rune - _capitalA;
if (rune < _a) return null;
if (rune <= _z) return 10 + rune - _a;
return null;
}
/// Move [_runes] to the next rune and update [_nextOffset].
bool _moveRunesNext() {
var result = _runes.moveNext();
_nextOffset++;
return result;
}
}