blob: c9d9636ba5e7dc5d1f6e2b4784e0f487d5fbfd32 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of _js_helper;
// Helper method used by internal libraries.
regExpGetNative(JSSyntaxRegExp regexp) => regexp._nativeRegExp;
/**
* Returns a native version of the RegExp with the global flag set.
*
* The RegExp's `lastIndex` property is zero when it is returned.
*
* The returned regexp is shared, and its `lastIndex` property may be
* modified by other uses, so the returned regexp must be used immediately
* when it's returned, with no user-provided code run in between.
*/
regExpGetGlobalNative(JSSyntaxRegExp regexp) {
var regexp = regexp._nativeGlobalVersion;
JS("void", "#.lastIndex = 0", regexp);
return regexp;
}
class JSSyntaxRegExp implements RegExp {
final _nativeRegExp;
var _nativeGlobalRegExp;
var _nativeAnchoredRegExp;
JSSyntaxRegExp(String pattern,
{ bool multiLine: false,
bool caseSensitive: true })
: this._nativeRegExp =
makeNative(pattern, multiLine, caseSensitive, false);
get _nativeGlobalVersion {
if (_nativeGlobalRegExp != null) return _nativeGlobalRegExp;
return _nativeGlobalRegExp = makeNative(_pattern,
_isMultiLine,
_isCaseSensitive,
true);
}
get _nativeAnchoredVersion {
if (_nativeAnchoredRegExp != null) return _nativeAnchoredRegExp;
// An "anchored version" of a regexp is created by adding "|()" to the
// source. This means that the regexp always matches at the first position
// that it tries, and you can see if the original regexp matched, or it
// was the added zero-width match that matched, by looking at the last
// capture. If it is a String, the match participated, otherwise it didn't.
return _nativeAnchoredRegExp = makeNative("$_pattern|()",
_isMultiLine,
_isCaseSensitive,
true);
}
String get _pattern => JS("String", "#.source", _nativeRegExp);
bool get _isMultiLine => JS("bool", "#.multiline", _nativeRegExp);
bool get _isCaseSensitive => JS("bool", "!#.ignoreCase", _nativeRegExp);
static makeNative(
String pattern, bool multiLine, bool caseSensitive, bool global) {
checkString(pattern);
String m = multiLine ? 'm' : '';
String i = caseSensitive ? '' : 'i';
String g = global ? 'g' : '';
// We're using the JavaScript's try catch instead of the Dart one
// to avoid dragging in Dart runtime support just because of using
// RegExp.
var regexp = JS('',
'(function() {'
'try {'
'return new RegExp(#, # + # + #);'
'} catch (e) {'
'return e;'
'}'
'})()', pattern, m, i, g);
if (JS('bool', '# instanceof RegExp', regexp)) return regexp;
// The returned value is the JavaScript exception. Turn it into a
// Dart exception.
String errorMessage = JS('String', r'String(#)', regexp);
throw new FormatException(
"Illegal RegExp pattern: $pattern, $errorMessage");
}
Match firstMatch(String str) {
List<String> m =
JS('=List|Null', r'#.exec(#)', _nativeRegExp, checkString(str));
if (m == null) return null;
return new _MatchImplementation(this, m);
}
bool hasMatch(String str) {
return JS('bool', r'#.test(#)', _nativeRegExp, checkString(str));
}
String stringMatch(String str) {
var match = firstMatch(str);
if (match != null) return match.group(0);
return null;
}
Iterable<Match> allMatches(String str) {
checkString(str);
return new _AllMatchesIterable(this, str);
}
Match _execGlobal(String string, int start) {
Object regexp = _nativeGlobalVersion;
JS("void", "#.lastIndex = #", regexp, start);
List match = JS("=List|Null", "#.exec(#)", regexp, string);
if (match == null) return null;
return new _MatchImplementation(this, match);
}
Match _execAnchored(String string, int start) {
Object regexp = _nativeAnchoredVersion;
JS("void", "#.lastIndex = #", regexp, start);
List match = JS("=List|Null", "#.exec(#)", regexp, string);
if (match == null) return null;
// If the last capture group participated, the original regexp did not
// match at the start position.
if (match[match.length - 1] != null) return null;
match.length -= 1;
return new _MatchImplementation(this, match);
}
Match matchAsPrefix(String string, [int start = 0]) {
if (start < 0 || start > string.length) {
throw new RangeError.range(start, 0, string.length);
}
return _execAnchored(string, start);
}
String get pattern => _pattern;
bool get isMultiLine => _isMultiLine;
bool get isCaseSensitive => _isCaseSensitive;
}
class _MatchImplementation implements Match {
final Pattern pattern;
// Contains a JS RegExp match object.
// It is an Array of String values with extra "index" and "input" properties.
final List<String> _match;
_MatchImplementation(this.pattern, this._match) {
assert(JS("var", "#.input", _match) is String);
assert(JS("var", "#.index", _match) is int);
}
String get str => JS("String", "#.input", _match);
int get start => JS("int", "#.index", _match);
int get end => start + _match[0].length;
String group(int index) => _match[index];
String operator [](int index) => group(index);
int get groupCount => _match.length - 1;
List<String> groups(List<int> groups) {
List<String> out = [];
for (int i in groups) {
out.add(group(i));
}
return out;
}
}
class _AllMatchesIterable extends IterableBase<Match> {
final JSSyntaxRegExp _re;
final String _string;
const _AllMatchesIterable(this._re, this._string);
Iterator<Match> get iterator => new _AllMatchesIterator(_re, _string);
}
class _AllMatchesIterator implements Iterator<Match> {
final JSSyntaxRegExp _regExp;
String _string;
Match _current;
_AllMatchesIterator(this._regExp, this._string);
Match get current => _current;
bool moveNext() {
if (_string == null) return false;
int index = 0;
if (_current != null) {
index = _current.end;
if (_current.start == index) {
index++;
}
}
_current = _regExp._execGlobal(_string, index);
if (_current == null) {
_string = null; // Marks iteration as ended.
return false;
}
return true;
}
}
/** Find the first match of [regExp] in [string] at or after [start]. */
Match firstMatchAfter(JSSyntaxRegExp regExp, String string, int start) {
return regExp._execGlobal(string, start);
}