| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of _js_helper; | |
| 6 | |
| 7 // Helper method used by internal libraries. | |
| 8 regExpGetNative(JSSyntaxRegExp regexp) => regexp._nativeRegExp; | |
| 9 | |
| 10 /** | |
| 11 * Returns a native version of the RegExp with the global flag set. | |
| 12 * | |
| 13 * The RegExp's `lastIndex` property is zero when it is returned. | |
| 14 * | |
| 15 * The returned regexp is shared, and its `lastIndex` property may be | |
| 16 * modified by other uses, so the returned regexp must be used immediately | |
| 17 * when it's returned, with no user-provided code run in between. | |
| 18 */ | |
| 19 regExpGetGlobalNative(JSSyntaxRegExp regexp) { | |
| 20 var nativeRegexp = regexp._nativeGlobalVersion; | |
| 21 JS("void", "#.lastIndex = 0", nativeRegexp); | |
| 22 return nativeRegexp; | |
| 23 } | |
| 24 | |
| 25 /** | |
| 26 * Computes the number of captures in a regexp. | |
| 27 * | |
| 28 * This currently involves creating a new RegExp object with a different | |
| 29 * source and running it against the empty string (the last part is usually | |
| 30 * fast). | |
| 31 * | |
| 32 * The JSSyntaxRegExp could cache the result, and set the cache any time | |
| 33 * it finds a match. | |
| 34 */ | |
| 35 int regExpCaptureCount(JSSyntaxRegExp regexp) { | |
| 36 var nativeAnchoredRegExp = regexp._nativeAnchoredVersion; | |
| 37 var match = JS('JSExtendableArray', "#.exec('')", nativeAnchoredRegExp); | |
| 38 // The native-anchored regexp always have one capture more than the original, | |
| 39 // and always matches the empty string. | |
| 40 return match.length - 2; | |
| 41 } | |
| 42 | |
| 43 class JSSyntaxRegExp implements RegExp { | |
| 44 final String pattern; | |
| 45 final _nativeRegExp; | |
| 46 var _nativeGlobalRegExp; | |
| 47 var _nativeAnchoredRegExp; | |
| 48 | |
| 49 String toString() => "RegExp/$pattern/"; | |
| 50 | |
| 51 JSSyntaxRegExp(String source, | |
| 52 { bool multiLine: false, | |
| 53 bool caseSensitive: true }) | |
| 54 : this.pattern = source, | |
| 55 this._nativeRegExp = | |
| 56 makeNative(source, multiLine, caseSensitive, false); | |
| 57 | |
| 58 get _nativeGlobalVersion { | |
| 59 if (_nativeGlobalRegExp != null) return _nativeGlobalRegExp; | |
| 60 return _nativeGlobalRegExp = makeNative(pattern, | |
| 61 _isMultiLine, | |
| 62 _isCaseSensitive, | |
| 63 true); | |
| 64 } | |
| 65 | |
| 66 get _nativeAnchoredVersion { | |
| 67 if (_nativeAnchoredRegExp != null) return _nativeAnchoredRegExp; | |
| 68 // An "anchored version" of a regexp is created by adding "|()" to the | |
| 69 // source. This means that the regexp always matches at the first position | |
| 70 // that it tries, and you can see if the original regexp matched, or it | |
| 71 // was the added zero-width match that matched, by looking at the last | |
| 72 // capture. If it is a String, the match participated, otherwise it didn't. | |
| 73 return _nativeAnchoredRegExp = makeNative("$pattern|()", | |
| 74 _isMultiLine, | |
| 75 _isCaseSensitive, | |
| 76 true); | |
| 77 } | |
| 78 | |
| 79 bool get _isMultiLine => JS("bool", "#.multiline", _nativeRegExp); | |
| 80 bool get _isCaseSensitive => JS("bool", "!#.ignoreCase", _nativeRegExp); | |
| 81 | |
| 82 static makeNative( | |
| 83 String source, bool multiLine, bool caseSensitive, bool global) { | |
| 84 checkString(source); | |
| 85 String m = multiLine ? 'm' : ''; | |
| 86 String i = caseSensitive ? '' : 'i'; | |
| 87 String g = global ? 'g' : ''; | |
| 88 // We're using the JavaScript's try catch instead of the Dart one | |
| 89 // to avoid dragging in Dart runtime support just because of using | |
| 90 // RegExp. | |
| 91 var regexp = JS('', | |
| 92 '(function() {' | |
| 93 'try {' | |
| 94 'return new RegExp(#, # + # + #);' | |
| 95 '} catch (e) {' | |
| 96 'return e;' | |
| 97 '}' | |
| 98 '})()', source, m, i, g); | |
| 99 if (JS('bool', '# instanceof RegExp', regexp)) return regexp; | |
| 100 // The returned value is the JavaScript exception. Turn it into a | |
| 101 // Dart exception. | |
| 102 String errorMessage = JS('String', r'String(#)', regexp); | |
| 103 throw new FormatException( | |
| 104 "Illegal RegExp pattern ($errorMessage)", source); | |
| 105 } | |
| 106 | |
| 107 Match firstMatch(String string) { | |
| 108 List<String> m = JS('JSExtendableArray|Null', | |
| 109 r'#.exec(#)', | |
| 110 _nativeRegExp, | |
| 111 checkString(string)); | |
| 112 if (m == null) return null; | |
| 113 return new _MatchImplementation(this, m); | |
| 114 } | |
| 115 | |
| 116 bool hasMatch(String string) { | |
| 117 return JS('bool', r'#.test(#)', _nativeRegExp, checkString(string)); | |
| 118 } | |
| 119 | |
| 120 String stringMatch(String string) { | |
| 121 var match = firstMatch(string); | |
| 122 if (match != null) return match.group(0); | |
| 123 return null; | |
| 124 } | |
| 125 | |
| 126 Iterable<Match> allMatches(String string, [int start = 0]) { | |
| 127 checkString(string); | |
| 128 checkInt(start); | |
| 129 if (start < 0 || start > string.length) { | |
| 130 throw new RangeError.range(start, 0, string.length); | |
| 131 } | |
| 132 return new _AllMatchesIterable(this, string, start); | |
| 133 } | |
| 134 | |
| 135 Match _execGlobal(String string, int start) { | |
| 136 Object regexp = _nativeGlobalVersion; | |
| 137 JS("void", "#.lastIndex = #", regexp, start); | |
| 138 List match = JS("JSExtendableArray|Null", "#.exec(#)", regexp, string); | |
| 139 if (match == null) return null; | |
| 140 return new _MatchImplementation(this, match); | |
| 141 } | |
| 142 | |
| 143 Match _execAnchored(String string, int start) { | |
| 144 Object regexp = _nativeAnchoredVersion; | |
| 145 JS("void", "#.lastIndex = #", regexp, start); | |
| 146 List match = JS("JSExtendableArray|Null", "#.exec(#)", regexp, string); | |
| 147 if (match == null) return null; | |
| 148 // If the last capture group participated, the original regexp did not | |
| 149 // match at the start position. | |
| 150 if (match[match.length - 1] != null) return null; | |
| 151 match.length -= 1; | |
| 152 return new _MatchImplementation(this, match); | |
| 153 } | |
| 154 | |
| 155 Match matchAsPrefix(String string, [int start = 0]) { | |
| 156 if (start < 0 || start > string.length) { | |
| 157 throw new RangeError.range(start, 0, string.length); | |
| 158 } | |
| 159 return _execAnchored(string, start); | |
| 160 } | |
| 161 | |
| 162 bool get isMultiLine => _isMultiLine; | |
| 163 bool get isCaseSensitive => _isCaseSensitive; | |
| 164 } | |
| 165 | |
| 166 class _MatchImplementation implements Match { | |
| 167 final Pattern pattern; | |
| 168 // Contains a JS RegExp match object. | |
| 169 // It is an Array of String values with extra "index" and "input" properties. | |
| 170 final List<String> _match; | |
| 171 | |
| 172 _MatchImplementation(this.pattern, this._match) { | |
| 173 assert(JS("var", "#.input", _match) is String); | |
| 174 assert(JS("var", "#.index", _match) is int); | |
| 175 } | |
| 176 | |
| 177 String get input => JS("String", "#.input", _match); | |
| 178 int get start => JS("int", "#.index", _match); | |
| 179 int get end => start + _match[0].length; | |
| 180 | |
| 181 String group(int index) => _match[index]; | |
| 182 String operator [](int index) => group(index); | |
| 183 int get groupCount => _match.length - 1; | |
| 184 | |
| 185 List<String> groups(List<int> groups) { | |
| 186 List<String> out = []; | |
| 187 for (int i in groups) { | |
| 188 out.add(group(i)); | |
| 189 } | |
| 190 return out; | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 class _AllMatchesIterable extends IterableBase<Match> { | |
| 195 final JSSyntaxRegExp _re; | |
| 196 final String _string; | |
| 197 final int _start; | |
| 198 | |
| 199 _AllMatchesIterable(this._re, this._string, this._start); | |
| 200 | |
| 201 Iterator<Match> get iterator => new _AllMatchesIterator(_re, _string, _start); | |
| 202 } | |
| 203 | |
| 204 class _AllMatchesIterator implements Iterator<Match> { | |
| 205 final JSSyntaxRegExp _regExp; | |
| 206 String _string; | |
| 207 int _nextIndex; | |
| 208 Match _current; | |
| 209 | |
| 210 _AllMatchesIterator(this._regExp, this._string, this._nextIndex); | |
| 211 | |
| 212 Match get current => _current; | |
| 213 | |
| 214 bool moveNext() { | |
| 215 if (_string == null) return false; | |
| 216 if (_nextIndex <= _string.length) { | |
| 217 var match = _regExp._execGlobal(_string, _nextIndex); | |
| 218 if (match != null) { | |
| 219 _current = match; | |
| 220 int nextIndex = match.end; | |
| 221 if (match.start == nextIndex) { | |
| 222 nextIndex++; | |
| 223 } | |
| 224 _nextIndex = nextIndex; | |
| 225 return true; | |
| 226 } | |
| 227 } | |
| 228 _current = null; | |
| 229 _string = null; // Marks iteration as ended. | |
| 230 return false; | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 /** Find the first match of [regExp] in [string] at or after [start]. */ | |
| 235 Match firstMatchAfter(JSSyntaxRegExp regExp, String string, int start) { | |
| 236 return regExp._execGlobal(string, start); | |
| 237 } | |
| OLD | NEW |