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;
  
  class StringMatch implements Match {
    const StringMatch(int this.start,
                      String this.input,
                      String this.pattern);
  
    int get end => start + pattern.length;
    String operator[](int g) => group(g);
    int get groupCount => 0;
  
    String group(int group_) {
      if (group_ != 0) {
        throw new RangeError.value(group_);
      }
      return pattern;
    }
  
    List<String> groups(List<int> groups_) {
      List<String> result = new List<String>();
      for (int g in groups_) {
        result.add(group(g));
      }
      return result;
    }
  
    final int start;
    final String input;
    final String pattern;
  }
  
  List<Match> allMatchesInStringUnchecked(String needle, String haystack) {
    // Copied from StringBase.allMatches in
    // /runtime/lib/string_base.dart
    List<Match> result = new List<Match>();
    int length = haystack.length;
    int patternLength = needle.length;
    int startIndex = 0;
    while (true) {
      int position = haystack.indexOf(needle, startIndex);
      if (position == -1) {
        break;
      }
      result.add(new StringMatch(position, haystack, needle));
      int endIndex = position + patternLength;
      if (endIndex == length) {
        break;
      } else if (position == endIndex) {
        ++startIndex;  // empty match, advance and restart
      } else {
        startIndex = endIndex;
      }
    }
    return result;
  }
  
  stringContainsUnchecked(receiver, other, startIndex) {
    if (other is String) {
      return receiver.indexOf(other, startIndex) != -1;
    } else if (other is JSSyntaxRegExp) {
      return other.hasMatch(receiver.substring(startIndex));
    } else {
      var substr = receiver.substring(startIndex);
      return other.allMatches(substr).isNotEmpty;
    }
  }
  
  stringReplaceJS(receiver, replacer, to) {
    // The JavaScript String.replace method recognizes replacement
    // patterns in the replacement string. Dart does not have that
    // behavior.
    to = JS('String', r'#.replace("$", "$$$$")', to);
    return JS('String', r'#.replace(#, #)', receiver, replacer, to);
  }
  
  const String ESCAPE_REGEXP = r'[[\]{}()*+?.\\^$|]';
  
  stringReplaceAllUnchecked(receiver, from, to) {
    checkString(to);
    if (from is String) {
      if (from == "") {
        if (receiver == "") {
          return to;
        } else {
          StringBuffer result = new StringBuffer();
          int length = receiver.length;
          result.write(to);
          for (int i = 0; i < length; i++) {
            result.write(receiver[i]);
            result.write(to);
          }
          return result.toString();
        }
      } else {
        var quoter = JS('', "new RegExp(#, 'g')", ESCAPE_REGEXP);
        var quoted = JS('String', r'#.replace(#, "\\$&")', from, quoter);
        var replacer = JS('', "new RegExp(#, 'g')", quoted);
        return stringReplaceJS(receiver, replacer, to);
      }
    } else if (from is JSSyntaxRegExp) {
      var re = regExpGetGlobalNative(from);
      return stringReplaceJS(receiver, re, to);
    } else {
      checkNull(from);
      // TODO(floitsch): implement generic String.replace (with patterns).
      throw "String.replaceAll(Pattern) UNIMPLEMENTED";
    }
  }
  
  String _matchString(Match match) => match[0];
  String _stringIdentity(String string) => string;
  
  stringReplaceAllFuncUnchecked(receiver, pattern, onMatch, onNonMatch) {
    if (pattern is! Pattern) {
      throw new ArgumentError("${pattern} is not a Pattern");
    }
    if (onMatch == null) onMatch = _matchString;
    if (onNonMatch == null) onNonMatch = _stringIdentity;
    if (pattern is String) {
      return stringReplaceAllStringFuncUnchecked(receiver, pattern,
                                                 onMatch, onNonMatch);
    }
    StringBuffer buffer = new StringBuffer();
    int startIndex = 0;
    for (Match match in pattern.allMatches(receiver)) {
      buffer.write(onNonMatch(receiver.substring(startIndex, match.start)));
      buffer.write(onMatch(match));
      startIndex = match.end;
    }
    buffer.write(onNonMatch(receiver.substring(startIndex)));
    return buffer.toString();
  }
  
  stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch) {
    // Pattern is the empty string.
    StringBuffer buffer = new StringBuffer();
    int length = receiver.length;
    int i = 0;
    buffer.write(onNonMatch(""));
    while (i < length) {
      buffer.write(onMatch(new StringMatch(i, receiver, "")));
      // Special case to avoid splitting a surrogate pair.
      int code = receiver.codeUnitAt(i);
      if ((code & ~0x3FF) == 0xD800 && length > i + 1) {
        // Leading surrogate;
        code = receiver.codeUnitAt(i + 1);
        if ((code & ~0x3FF) == 0xDC00) {
          // Matching trailing surrogate.
          buffer.write(onNonMatch(receiver.substring(i, i + 2)));
          i += 2;
          continue;
        }
      }
      buffer.write(onNonMatch(receiver[i]));
      i++;
    }
    buffer.write(onMatch(new StringMatch(i, receiver, "")));
    buffer.write(onNonMatch(""));
    return buffer.toString();
  }
  
  stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) {
    int patternLength = pattern.length;
    if (patternLength == 0) {
      return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch);
    }
    int length = receiver.length;
    StringBuffer buffer = new StringBuffer();
    int startIndex = 0;
    while (startIndex < length) {
      int position = receiver.indexOf(pattern, startIndex);
      if (position == -1) {
        break;
      }
      buffer.write(onNonMatch(receiver.substring(startIndex, position)));
      buffer.write(onMatch(new StringMatch(position, receiver, pattern)));
      startIndex = position + patternLength;
    }
    buffer.write(onNonMatch(receiver.substring(startIndex)));
    return buffer.toString();
  }
  
  
  stringReplaceFirstUnchecked(receiver, from, to) {
    if (from is String) {
      return stringReplaceJS(receiver, from, to);
    } else if (from is JSSyntaxRegExp) {
      var re = regExpGetNative(from);
      return stringReplaceJS(receiver, re, to);
    } else {
      checkNull(from);
      // TODO(floitsch): implement generic String.replace (with patterns).
      throw "String.replace(Pattern) UNIMPLEMENTED";
    }
  }
  
  stringJoinUnchecked(array, separator) {
    return JS('String', r'#.join(#)', array, separator);
  }
  