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 |