content: // 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 nativeRegexp = regexp._nativeGlobalVersion;
    JS("void", "#.lastIndex = 0", nativeRegexp);
    return nativeRegexp;
  }
  
  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('JSExtendableArray|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("JSExtendableArray|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("JSExtendableArray|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 input => 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);
  }
  