| 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 class JSSyntaxRegExp implements RegExp { | |
| 26 final String pattern; | |
| 27 final _nativeRegExp; | |
| 28 var _nativeGlobalRegExp; | |
| 29 var _nativeAnchoredRegExp; | |
| 30 | |
| 31 | |
| 32 JSSyntaxRegExp(String source, | |
| 33 { bool multiLine: false, | |
| 34 bool caseSensitive: true }) | |
| 35 : this.pattern = source, | |
| 36 this._nativeRegExp = | |
| 37 makeNative(source, multiLine, caseSensitive, false); | |
| 38 | |
| 39 get _nativeGlobalVersion { | |
| 40 if (_nativeGlobalRegExp != null) return _nativeGlobalRegExp; | |
| 41 return _nativeGlobalRegExp = makeNative(pattern, | |
| 42 _isMultiLine, | |
| 43 _isCaseSensitive, | |
| 44 true); | |
| 45 } | |
| 46 | |
| 47 get _nativeAnchoredVersion { | |
| 48 if (_nativeAnchoredRegExp != null) return _nativeAnchoredRegExp; | |
| 49 // An "anchored version" of a regexp is created by adding "|()" to the | |
| 50 // source. This means that the regexp always matches at the first position | |
| 51 // that it tries, and you can see if the original regexp matched, or it | |
| 52 // was the added zero-width match that matched, by looking at the last | |
| 53 // capture. If it is a String, the match participated, otherwise it didn't. | |
| 54 return _nativeAnchoredRegExp = makeNative("$pattern|()", | |
| 55 _isMultiLine, | |
| 56 _isCaseSensitive, | |
| 57 true); | |
| 58 } | |
| 59 | |
| 60 bool get _isMultiLine => JS("bool", "#.multiline", _nativeRegExp); | |
| 61 bool get _isCaseSensitive => JS("bool", "!#.ignoreCase", _nativeRegExp); | |
| 62 | |
| 63 static makeNative( | |
| 64 String source, bool multiLine, bool caseSensitive, bool global) { | |
| 65 checkString(source); | |
| 66 String m = multiLine ? 'm' : ''; | |
| 67 String i = caseSensitive ? '' : 'i'; | |
| 68 String g = global ? 'g' : ''; | |
| 69 // We're using the JavaScript's try catch instead of the Dart one | |
| 70 // to avoid dragging in Dart runtime support just because of using | |
| 71 // RegExp. | |
| 72 var regexp = JS('', | |
| 73 '(function() {' | |
| 74 'try {' | |
| 75 'return new RegExp(#, # + # + #);' | |
| 76 '} catch (e) {' | |
| 77 'return e;' | |
| 78 '}' | |
| 79 '})()', source, m, i, g); | |
| 80 if (JS('bool', '# instanceof RegExp', regexp)) return regexp; | |
| 81 // The returned value is the JavaScript exception. Turn it into a | |
| 82 // Dart exception. | |
| 83 String errorMessage = JS('String', r'String(#)', regexp); | |
| 84 throw new FormatException( | |
| 85 "Illegal RegExp pattern: $source, $errorMessage"); | |
| 86 } | |
| 87 | |
| 88 Match firstMatch(String string) { | |
| 89 List<String> m = JS('JSExtendableArray|Null', | |
| 90 r'#.exec(#)', | |
| 91 _nativeRegExp, | |
| 92 checkString(string)); | |
| 93 if (m == null) return null; | |
| 94 return new _MatchImplementation(this, m); | |
| 95 } | |
| 96 | |
| 97 bool hasMatch(String string) { | |
| 98 return JS('bool', r'#.test(#)', _nativeRegExp, checkString(string)); | |
| 99 } | |
| 100 | |
| 101 String stringMatch(String string) { | |
| 102 var match = firstMatch(string); | |
| 103 if (match != null) return match.group(0); | |
| 104 return null; | |
| 105 } | |
| 106 | |
| 107 Iterable<Match> allMatches(String string, [int start = 0]) { | |
| 108 checkString(string); | |
| 109 checkInt(start); | |
| 110 if (start < 0 || start > string.length) { | |
| 111 throw new RangeError.range(start, 0, string.length); | |
| 112 } | |
| 113 return new _AllMatchesIterable(this, string, start); | |
| 114 } | |
| 115 | |
| 116 Match _execGlobal(String string, int start) { | |
| 117 Object regexp = _nativeGlobalVersion; | |
| 118 JS("void", "#.lastIndex = #", regexp, start); | |
| 119 List match = JS("JSExtendableArray|Null", "#.exec(#)", regexp, string); | |
| 120 if (match == null) return null; | |
| 121 return new _MatchImplementation(this, match); | |
| 122 } | |
| 123 | |
| 124 Match _execAnchored(String string, int start) { | |
| 125 Object regexp = _nativeAnchoredVersion; | |
| 126 JS("void", "#.lastIndex = #", regexp, start); | |
| 127 List match = JS("JSExtendableArray|Null", "#.exec(#)", regexp, string); | |
| 128 if (match == null) return null; | |
| 129 // If the last capture group participated, the original regexp did not | |
| 130 // match at the start position. | |
| 131 if (match[match.length - 1] != null) return null; | |
| 132 match.length -= 1; | |
| 133 return new _MatchImplementation(this, match); | |
| 134 } | |
| 135 | |
| 136 Match matchAsPrefix(String string, [int start = 0]) { | |
| 137 if (start < 0 || start > string.length) { | |
| 138 throw new RangeError.range(start, 0, string.length); | |
| 139 } | |
| 140 return _execAnchored(string, start); | |
| 141 } | |
| 142 | |
| 143 bool get isMultiLine => _isMultiLine; | |
| 144 bool get isCaseSensitive => _isCaseSensitive; | |
| 145 } | |
| 146 | |
| 147 class _MatchImplementation implements Match { | |
| 148 final Pattern pattern; | |
| 149 // Contains a JS RegExp match object. | |
| 150 // It is an Array of String values with extra "index" and "input" properties. | |
| 151 final List<String> _match; | |
| 152 | |
| 153 _MatchImplementation(this.pattern, this._match) { | |
| 154 assert(JS("var", "#.input", _match) is String); | |
| 155 assert(JS("var", "#.index", _match) is int); | |
| 156 } | |
| 157 | |
| 158 String get input => JS("String", "#.input", _match); | |
| 159 int get start => JS("int", "#.index", _match); | |
| 160 int get end => start + _match[0].length; | |
| 161 | |
| 162 String group(int index) => _match[index]; | |
| 163 String operator [](int index) => group(index); | |
| 164 int get groupCount => _match.length - 1; | |
| 165 | |
| 166 List<String> groups(List<int> groups) { | |
| 167 List<String> out = []; | |
| 168 for (int i in groups) { | |
| 169 out.add(group(i)); | |
| 170 } | |
| 171 return out; | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 class _AllMatchesIterable extends IterableBase<Match> { | |
| 176 final JSSyntaxRegExp _re; | |
| 177 final String _string; | |
| 178 final int _start; | |
| 179 | |
| 180 _AllMatchesIterable(this._re, this._string, this._start); | |
| 181 | |
| 182 Iterator<Match> get iterator => new _AllMatchesIterator(_re, _string, _start); | |
| 183 } | |
| 184 | |
| 185 class _AllMatchesIterator implements Iterator<Match> { | |
| 186 final JSSyntaxRegExp _regExp; | |
| 187 String _string; | |
| 188 int _nextIndex; | |
| 189 Match _current; | |
| 190 | |
| 191 _AllMatchesIterator(this._regExp, this._string, this._nextIndex); | |
| 192 | |
| 193 Match get current => _current; | |
| 194 | |
| 195 bool moveNext() { | |
| 196 if (_string == null) return false; | |
| 197 if (_nextIndex <= _string.length) { | |
| 198 var match = _regExp._execGlobal(_string, _nextIndex); | |
| 199 if (match != null) { | |
| 200 _current = match; | |
| 201 int nextIndex = match.end; | |
| 202 if (match.start == nextIndex) { | |
| 203 nextIndex++; | |
| 204 } | |
| 205 _nextIndex = nextIndex; | |
| 206 return true; | |
| 207 } | |
| 208 } | |
| 209 _current = null; | |
| 210 _string = null; // Marks iteration as ended. | |
| 211 return false; | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 /** Find the first match of [regExp] in [string] at or after [start]. */ | |
| 216 Match firstMatchAfter(JSSyntaxRegExp regExp, String string, int start) { | |
| 217 return regExp._execGlobal(string, start); | |
| 218 } | |
| OLD | NEW |