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 |