OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of dart._js_helper; | 5 part of dart._js_helper; |
6 | 6 |
| 7 int stringIndexOfStringUnchecked(receiver, other, startIndex) { |
| 8 return JS('int', '#.indexOf(#, #)', receiver, other, startIndex); |
| 9 } |
| 10 |
| 11 String substring1Unchecked(receiver, startIndex) { |
| 12 return JS('String', '#.substring(#)', receiver, startIndex); |
| 13 } |
| 14 |
| 15 String substring2Unchecked(receiver, startIndex, endIndex) { |
| 16 return JS('String', '#.substring(#, #)', receiver, startIndex, endIndex); |
| 17 } |
| 18 |
| 19 bool stringContainsStringUnchecked(receiver, other, startIndex) { |
| 20 return stringIndexOfStringUnchecked(receiver, other, startIndex) >= 0; |
| 21 } |
| 22 |
7 class StringMatch implements Match { | 23 class StringMatch implements Match { |
8 const StringMatch(int this.start, | 24 const StringMatch(int this.start, |
9 String this.input, | 25 String this.input, |
10 String this.pattern); | 26 String this.pattern); |
11 | 27 |
12 int get end => start + pattern.length; | 28 int get end => start + pattern.length; |
13 String operator[](int g) => group(g); | 29 String operator[](int g) => group(g); |
14 int get groupCount => 0; | 30 int get groupCount => 0; |
15 | 31 |
16 String group(int group_) { | 32 String group(int group_) { |
17 if (group_ != 0) { | 33 if (group_ != 0) { |
18 throw new RangeError.value(group_); | 34 throw new RangeError.value(group_); |
19 } | 35 } |
20 return pattern; | 36 return pattern; |
21 } | 37 } |
22 | 38 |
23 List<String> groups(List<int> groups_) { | 39 List<String> groups(List<int> groups_) { |
24 List<String> result = new List<String>(); | 40 List<String> result = new List<String>(); |
25 for (int g in groups_) { | 41 for (int g in groups_) { |
26 result.add(group(g)); | 42 result.add(group(g)); |
27 } | 43 } |
28 return result; | 44 return result; |
29 } | 45 } |
30 | 46 |
31 final int start; | 47 final int start; |
32 final String input; | 48 final String input; |
33 final String pattern; | 49 final String pattern; |
34 } | 50 } |
35 | 51 |
36 List<Match> allMatchesInStringUnchecked(String needle, String haystack, | 52 Iterable<Match> allMatchesInStringUnchecked(String pattern, String string, |
37 int startIndex) { | 53 int startIndex) { |
38 // Copied from StringBase.allMatches in | 54 return new _StringAllMatchesIterable(string, pattern, startIndex); |
39 // /runtime/lib/string_base.dart | |
40 List<Match> result = new List<Match>(); | |
41 int length = haystack.length; | |
42 int patternLength = needle.length; | |
43 while (true) { | |
44 int position = haystack.indexOf(needle, startIndex); | |
45 if (position == -1) { | |
46 break; | |
47 } | |
48 result.add(new StringMatch(position, haystack, needle)); | |
49 int endIndex = position + patternLength; | |
50 if (endIndex == length) { | |
51 break; | |
52 } else if (position == endIndex) { | |
53 ++startIndex; // empty match, advance and restart | |
54 } else { | |
55 startIndex = endIndex; | |
56 } | |
57 } | |
58 return result; | |
59 } | 55 } |
60 | 56 |
61 stringContainsUnchecked(receiver, other, startIndex) { | 57 class _StringAllMatchesIterable extends IterableBase<Match> { |
| 58 final String _input; |
| 59 final String _pattern; |
| 60 final int _index; |
| 61 |
| 62 _StringAllMatchesIterable(this._input, this._pattern, this._index); |
| 63 |
| 64 Iterator<Match> get iterator => |
| 65 new _StringAllMatchesIterator(_input, _pattern, _index); |
| 66 |
| 67 Match get first { |
| 68 int index = stringIndexOfStringUnchecked(_input, _pattern, _index); |
| 69 if (index >= 0) { |
| 70 return new StringMatch(index, _input, _pattern); |
| 71 } |
| 72 throw IterableElementError.noElement(); |
| 73 } |
| 74 } |
| 75 |
| 76 class _StringAllMatchesIterator implements Iterator<Match> { |
| 77 final String _input; |
| 78 final String _pattern; |
| 79 int _index; |
| 80 Match _current; |
| 81 |
| 82 _StringAllMatchesIterator(this._input, this._pattern, this._index); |
| 83 |
| 84 bool moveNext() { |
| 85 if (_index + _pattern.length > _input.length) { |
| 86 _current = null; |
| 87 return false; |
| 88 } |
| 89 var index = stringIndexOfStringUnchecked(_input, _pattern, _index); |
| 90 if (index < 0) { |
| 91 _index = _input.length + 1; |
| 92 _current = null; |
| 93 return false; |
| 94 } |
| 95 int end = index + _pattern.length; |
| 96 _current = new StringMatch(index, _input, _pattern); |
| 97 // Empty match, don't start at same location again. |
| 98 if (end == _index) end++; |
| 99 _index = end; |
| 100 return true; |
| 101 } |
| 102 |
| 103 Match get current => _current; |
| 104 } |
| 105 |
| 106 bool stringContainsUnchecked(String receiver, other, int startIndex) { |
62 if (other is String) { | 107 if (other is String) { |
63 return receiver.indexOf(other, startIndex) != -1; | 108 return stringContainsStringUnchecked(receiver, other, startIndex); |
64 } else if (other is JSSyntaxRegExp) { | 109 } else if (other is JSSyntaxRegExp) { |
65 return other.hasMatch(receiver.substring(startIndex)); | 110 return other.hasMatch(receiver.substring(startIndex)); |
66 } else { | 111 } else { |
67 var substr = receiver.substring(startIndex); | 112 var substr = receiver.substring(startIndex); |
68 return other.allMatches(substr).isNotEmpty; | 113 return other.allMatches(substr).isNotEmpty; |
69 } | 114 } |
70 } | 115 } |
71 | 116 |
72 stringReplaceJS(receiver, replacer, to) { | 117 String stringReplaceJS(receiver, replacer, replacement) { |
73 // The JavaScript String.replace method recognizes replacement | 118 // The JavaScript String.replace method recognizes replacement |
74 // patterns in the replacement string. Dart does not have that | 119 // patterns in the replacement string. Dart does not have that |
75 // behavior. | 120 // behavior. |
76 to = JS('String', r'#.replace(/\$/g, "$$$$")', to); | 121 replacement = JS('String', r'#.replace(/\$/g, "$$$$")', replacement); |
77 return JS('String', r'#.replace(#, #)', receiver, replacer, to); | 122 return JS('String', r'#.replace(#, #)', receiver, replacer, replacement); |
78 } | 123 } |
79 | 124 |
80 stringReplaceFirstRE(receiver, regexp, to, startIndex) { | 125 String stringReplaceFirstRE(String receiver, |
| 126 JSSyntaxRegExp regexp, String replacement, int startIndex) { |
81 var match = regexp._execGlobal(receiver, startIndex); | 127 var match = regexp._execGlobal(receiver, startIndex); |
82 if (match == null) return receiver; | 128 if (match == null) return receiver; |
83 var start = match.start; | 129 var start = match.start; |
84 var end = match.end; | 130 var end = match.end; |
85 return "${receiver.substring(0,start)}$to${receiver.substring(end)}"; | 131 return stringReplaceRangeUnchecked(receiver, start, end, replacement); |
86 } | 132 } |
87 | 133 |
88 const String ESCAPE_REGEXP = r'[[\]{}()*+?.\\^$|]'; | |
89 | 134 |
90 stringReplaceAllUnchecked(receiver, from, to) { | 135 /// Returns a string for a RegExp pattern that matches [string]. This is done by |
91 checkString(to); | 136 /// escaping all RegExp metacharacters. |
92 if (from is String) { | 137 String quoteStringForRegExp(string) { |
93 if (from == "") { | 138 return JS('String', r'#.replace(/[[\]{}()*+?.\\^$|]/g, "\\$&")', string); |
| 139 } |
| 140 |
| 141 String stringReplaceAllUnchecked( |
| 142 String receiver, Pattern pattern, String replacement) { |
| 143 checkString(replacement); |
| 144 if (pattern is String) { |
| 145 if (pattern == "") { |
94 if (receiver == "") { | 146 if (receiver == "") { |
95 return to; | 147 return replacement; |
96 } else { | 148 } else { |
97 StringBuffer result = new StringBuffer(); | 149 StringBuffer result = new StringBuffer(); |
98 int length = receiver.length; | 150 int length = receiver.length; |
99 result.write(to); | 151 result.write(replacement); |
100 for (int i = 0; i < length; i++) { | 152 for (int i = 0; i < length; i++) { |
101 result.write(receiver[i]); | 153 result.write(receiver[i]); |
102 result.write(to); | 154 result.write(replacement); |
103 } | 155 } |
104 return result.toString(); | 156 return result.toString(); |
105 } | 157 } |
106 } else { | 158 } else { |
107 var quoter = JS('', "new RegExp(#, 'g')", ESCAPE_REGEXP); | 159 var quoted = quoteStringForRegExp(pattern); |
108 var quoted = JS('String', r'#.replace(#, "\\$&")', from, quoter); | |
109 var replacer = JS('', "new RegExp(#, 'g')", quoted); | 160 var replacer = JS('', "new RegExp(#, 'g')", quoted); |
110 return stringReplaceJS(receiver, replacer, to); | 161 return stringReplaceJS(receiver, replacer, replacement); |
111 } | 162 } |
112 } else if (from is JSSyntaxRegExp) { | 163 } else if (pattern is JSSyntaxRegExp) { |
113 var re = regExpGetGlobalNative(from); | 164 var re = regExpGetGlobalNative(pattern); |
114 return stringReplaceJS(receiver, re, to); | 165 return stringReplaceJS(receiver, re, replacement); |
115 } else { | 166 } else { |
116 checkNull(from); | 167 checkNull(pattern); |
117 // TODO(floitsch): implement generic String.replace (with patterns). | 168 // TODO(floitsch): implement generic String.replace (with patterns). |
118 throw "String.replaceAll(Pattern) UNIMPLEMENTED"; | 169 throw "String.replaceAll(Pattern) UNIMPLEMENTED"; |
119 } | 170 } |
120 } | 171 } |
121 | 172 |
122 String _matchString(Match match) => match[0]; | 173 String _matchString(Match match) => match[0]; |
123 String _stringIdentity(String string) => string; | 174 String _stringIdentity(String string) => string; |
124 | 175 |
125 stringReplaceAllFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { | 176 String stringReplaceAllFuncUnchecked( |
126 if (pattern is! Pattern) { | 177 String receiver, |
127 throw new ArgumentError("${pattern} is not a Pattern"); | 178 Pattern pattern, |
128 } | 179 String onMatch(Match match), |
| 180 String onNonMatch(String nonMatch)) { |
129 if (onMatch == null) onMatch = _matchString; | 181 if (onMatch == null) onMatch = _matchString; |
130 if (onNonMatch == null) onNonMatch = _stringIdentity; | 182 if (onNonMatch == null) onNonMatch = _stringIdentity; |
131 if (pattern is String) { | 183 if (pattern is String) { |
132 return stringReplaceAllStringFuncUnchecked(receiver, pattern, | 184 return stringReplaceAllStringFuncUnchecked(receiver, pattern, |
133 onMatch, onNonMatch); | 185 onMatch, onNonMatch); |
134 } | 186 } |
| 187 // Placing the Pattern test here is indistingishable from placing it at the |
| 188 // top of the method but it saves an extra check on the `pattern is String` |
| 189 // path. |
| 190 if (pattern is! Pattern) { |
| 191 throw new ArgumentError.value(pattern, 'pattern', 'is not a Pattern'); |
| 192 } |
135 StringBuffer buffer = new StringBuffer(); | 193 StringBuffer buffer = new StringBuffer(); |
136 int startIndex = 0; | 194 int startIndex = 0; |
137 for (Match match in pattern.allMatches(receiver)) { | 195 for (Match match in pattern.allMatches(receiver)) { |
138 buffer.write(onNonMatch(receiver.substring(startIndex, match.start))); | 196 buffer.write(onNonMatch(receiver.substring(startIndex, match.start))); |
139 buffer.write(onMatch(match)); | 197 buffer.write(onMatch(match)); |
140 startIndex = match.end; | 198 startIndex = match.end; |
141 } | 199 } |
142 buffer.write(onNonMatch(receiver.substring(startIndex))); | 200 buffer.write(onNonMatch(receiver.substring(startIndex))); |
143 return buffer.toString(); | 201 return buffer.toString(); |
144 } | 202 } |
145 | 203 |
146 stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch) { | 204 String stringReplaceAllEmptyFuncUnchecked(String receiver, |
| 205 String onMatch(Match match), |
| 206 String onNonMatch(String nonMatch)) { |
147 // Pattern is the empty string. | 207 // Pattern is the empty string. |
148 StringBuffer buffer = new StringBuffer(); | 208 StringBuffer buffer = new StringBuffer(); |
149 int length = receiver.length; | 209 int length = receiver.length; |
150 int i = 0; | 210 int i = 0; |
151 buffer.write(onNonMatch("")); | 211 buffer.write(onNonMatch("")); |
152 while (i < length) { | 212 while (i < length) { |
153 buffer.write(onMatch(new StringMatch(i, receiver, ""))); | 213 buffer.write(onMatch(new StringMatch(i, receiver, ""))); |
154 // Special case to avoid splitting a surrogate pair. | 214 // Special case to avoid splitting a surrogate pair. |
155 int code = receiver.codeUnitAt(i); | 215 int code = receiver.codeUnitAt(i); |
156 if ((code & ~0x3FF) == 0xD800 && length > i + 1) { | 216 if ((code & ~0x3FF) == 0xD800 && length > i + 1) { |
157 // Leading surrogate; | 217 // Leading surrogate; |
158 code = receiver.codeUnitAt(i + 1); | 218 code = receiver.codeUnitAt(i + 1); |
159 if ((code & ~0x3FF) == 0xDC00) { | 219 if ((code & ~0x3FF) == 0xDC00) { |
160 // Matching trailing surrogate. | 220 // Matching trailing surrogate. |
161 buffer.write(onNonMatch(receiver.substring(i, i + 2))); | 221 buffer.write(onNonMatch(receiver.substring(i, i + 2))); |
162 i += 2; | 222 i += 2; |
163 continue; | 223 continue; |
164 } | 224 } |
165 } | 225 } |
166 buffer.write(onNonMatch(receiver[i])); | 226 buffer.write(onNonMatch(receiver[i])); |
167 i++; | 227 i++; |
168 } | 228 } |
169 buffer.write(onMatch(new StringMatch(i, receiver, ""))); | 229 buffer.write(onMatch(new StringMatch(i, receiver, ""))); |
170 buffer.write(onNonMatch("")); | 230 buffer.write(onNonMatch("")); |
171 return buffer.toString(); | 231 return buffer.toString(); |
172 } | 232 } |
173 | 233 |
174 stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { | 234 String stringReplaceAllStringFuncUnchecked( |
| 235 String receiver, |
| 236 String pattern, |
| 237 String onMatch(Match match), |
| 238 String onNonMatch(String nonMatch)) { |
175 int patternLength = pattern.length; | 239 int patternLength = pattern.length; |
176 if (patternLength == 0) { | 240 if (patternLength == 0) { |
177 return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch); | 241 return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch); |
178 } | 242 } |
179 int length = receiver.length; | 243 int length = receiver.length; |
180 StringBuffer buffer = new StringBuffer(); | 244 StringBuffer buffer = new StringBuffer(); |
181 int startIndex = 0; | 245 int startIndex = 0; |
182 while (startIndex < length) { | 246 while (startIndex < length) { |
183 int position = receiver.indexOf(pattern, startIndex); | 247 int position = stringIndexOfStringUnchecked(receiver, pattern, startIndex); |
184 if (position == -1) { | 248 if (position == -1) { |
185 break; | 249 break; |
186 } | 250 } |
187 buffer.write(onNonMatch(receiver.substring(startIndex, position))); | 251 buffer.write(onNonMatch(receiver.substring(startIndex, position))); |
188 buffer.write(onMatch(new StringMatch(position, receiver, pattern))); | 252 buffer.write(onMatch(new StringMatch(position, receiver, pattern))); |
189 startIndex = position + patternLength; | 253 startIndex = position + patternLength; |
190 } | 254 } |
191 buffer.write(onNonMatch(receiver.substring(startIndex))); | 255 buffer.write(onNonMatch(receiver.substring(startIndex))); |
192 return buffer.toString(); | 256 return buffer.toString(); |
193 } | 257 } |
194 | 258 |
195 | 259 |
196 stringReplaceFirstUnchecked(receiver, from, to, [int startIndex = 0]) { | 260 String stringReplaceFirstUnchecked( |
197 if (from is String) { | 261 String receiver, Pattern pattern, String replacement, int startIndex) { |
198 var index = receiver.indexOf(from, startIndex); | 262 if (pattern is String) { |
| 263 int index = stringIndexOfStringUnchecked(receiver, pattern, startIndex); |
199 if (index < 0) return receiver; | 264 if (index < 0) return receiver; |
200 return '${receiver.substring(0, index)}$to' | 265 int end = index + pattern.length; |
201 '${receiver.substring(index + from.length)}'; | 266 return stringReplaceRangeUnchecked(receiver, index, end, replacement); |
202 } else if (from is JSSyntaxRegExp) { | |
203 return startIndex == 0 ? | |
204 stringReplaceJS(receiver, regExpGetNative(from), to) : | |
205 stringReplaceFirstRE(receiver, from, to, startIndex); | |
206 } else { | |
207 checkNull(from); | |
208 // TODO(floitsch): implement generic String.replace (with patterns). | |
209 throw "String.replace(Pattern) UNIMPLEMENTED"; | |
210 } | 267 } |
| 268 if (pattern is JSSyntaxRegExp) { |
| 269 return startIndex == 0 |
| 270 ? stringReplaceJS(receiver, regExpGetNative(pattern), replacement) |
| 271 : stringReplaceFirstRE(receiver, pattern, replacement, startIndex); |
| 272 } |
| 273 checkNull(pattern); |
| 274 Iterator<Match> matches = pattern.allMatches(receiver, startIndex).iterator; |
| 275 if (!matches.moveNext()) return receiver; |
| 276 Match match = matches.current; |
| 277 return receiver.replaceRange(match.start, match.end, replacement); |
211 } | 278 } |
212 | 279 |
213 stringJoinUnchecked(array, separator) { | 280 String stringReplaceFirstMappedUnchecked( |
| 281 String receiver, |
| 282 Pattern pattern, |
| 283 String replace(Match current), |
| 284 int startIndex) { |
| 285 Iterator<Match> matches = pattern.allMatches(receiver, startIndex).iterator; |
| 286 if (!matches.moveNext()) return receiver; |
| 287 Match match = matches.current; |
| 288 String replacement = "${replace(match)}"; |
| 289 return receiver.replaceRange(match.start, match.end, replacement); |
| 290 } |
| 291 |
| 292 String stringJoinUnchecked(array, separator) { |
214 return JS('String', r'#.join(#)', array, separator); | 293 return JS('String', r'#.join(#)', array, separator); |
215 } | 294 } |
| 295 |
| 296 String stringReplaceRangeUnchecked(String receiver, |
| 297 int start, int end, String replacement) { |
| 298 var prefix = JS('String', '#.substring(0, #)', receiver, start); |
| 299 var suffix = JS('String', '#.substring(#)', receiver, end); |
| 300 return "$prefix$replacement$suffix"; |
| 301 } |
OLD | NEW |