text: // Copyright 2014 The Bazel Authors. All rights reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //    http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  #include "src/main/cpp/util/strings.h"
  
  #if defined(_WIN32) || defined(__CYGWIN__)
  #include <windows.h>
  #endif  // defined(_WIN32) || defined(__CYGWIN__)
  
  #include <stdarg.h>
  #include <stdio.h>
  #include <stdlib.h>
  
  #include <cassert>
  #include <memory>  // unique_ptr
  
  #include "src/main/cpp/util/exit_code.h"
  
  namespace blaze_util {
  
  using std::string;
  using std::unique_ptr;
  using std::vector;
  using std::wstring;
  
  static const char kSeparator[] = " \n\t\r";
  
  bool starts_with(const string &haystack, const string &needle) {
    return (haystack.length() >= needle.length()) &&
           (memcmp(haystack.c_str(), needle.c_str(), needle.length()) == 0);
  }
  
  template <typename char_type>
  static bool ends_with_impl(const std::basic_string<char_type> &haystack,
                             const std::basic_string<char_type> &needle) {
    return (haystack.length() >= needle.length()) &&
           std::equal(haystack.cend() - needle.length(), haystack.cend(),
                      needle.cbegin());
  }
  
  bool ends_with(const string &haystack, const string &needle) {
    return ends_with_impl(haystack, needle);
  }
  
  bool ends_with(const wstring &haystack, const wstring &needle) {
    return ends_with_impl(haystack, needle);
  }
  
  void JoinStrings(const vector<string> &pieces, const char delimeter,
                   string *output) {
    bool first = true;
    for (const auto &piece : pieces) {
      if (first) {
        *output = piece;
        first = false;
      } else {
        *output += delimeter + piece;
      }
    }
  }
  
  vector<string> Split(const string &contents, const char delimeter) {
    vector<string> result;
    SplitStringUsing(contents, delimeter, &result);
    return result;
  }
  
  void SplitStringUsing(const string &contents, const char delimeter,
                        vector<string> *result) {
    assert(result);
  
    size_t start = 0;
    while (start < contents.length() && contents[start] == delimeter) {
      ++start;
    }
  
    size_t newline = contents.find(delimeter, start);
    while (newline != string::npos) {
      result->push_back(string(contents, start, newline - start));
      start = newline;
      while (start < contents.length() && contents[start] == delimeter) {
        ++start;
      }
      newline = contents.find(delimeter, start);
    }
  
    // If there is a trailing line, add that.
    if (start != newline && start != contents.size()) {
      result->push_back(string(contents, start));
    }
  }
  
  size_t SplitQuotedStringUsing(const string &contents, const char delimeter,
                                std::vector<string> *output) {
    size_t len = contents.length();
    size_t start = 0;
    size_t quote = string::npos;  // quote position
    size_t num_segments = 0;
  
    for (size_t pos = 0; pos < len; ++pos) {
      if (start == pos && contents[start] == delimeter) {
        ++start;
      } else if (contents[pos] == '\\') {
        ++pos;
      } else if (quote != string::npos && contents[pos] == contents[quote]) {
        quote = string::npos;
      } else if (quote == string::npos &&
                 (contents[pos] == '"' || contents[pos] == '\'')) {
        quote = pos;
      } else if (quote == string::npos && contents[pos] == delimeter) {
        output->push_back(string(contents, start, pos - start));
        start = pos + 1;
        num_segments++;
      }
    }
  
    // A trailing element
    if (start < len) {
      output->push_back(string(contents, start));
      num_segments++;
    }
    return num_segments;
  }
  
  void Replace(const string &oldsub, const string &newsub, string *str) {
    size_t start = 0;
    // This is O(n^2) (the complexity of erase() is actually unspecified, but
    // usually linear).
    while ((start = str->find(oldsub, start)) != string::npos) {
      str->erase(start, oldsub.length());
      str->insert(start, newsub);
      start += newsub.length();
    }
  }
  
  void StripWhitespace(string *str) {
    int str_length = str->length();
  
    // Strip off leading whitespace.
    int first = 0;
    while (first < str_length && ascii_isspace(str->at(first))) {
      ++first;
    }
    // If entire string is white space.
    if (first == str_length) {
      str->clear();
      return;
    }
    if (first > 0) {
      str->erase(0, first);
      str_length -= first;
    }
  
    // Strip off trailing whitespace.
    int last = str_length - 1;
    while (last >= 0 && ascii_isspace(str->at(last))) {
      --last;
    }
    if (last != (str_length - 1) && last >= 0) {
      str->erase(last + 1, string::npos);
    }
  }
  
  static void GetNextToken(const string &str, const char &comment,
                           string::const_iterator *iter, vector<string> *words) {
    string output;
    auto last = *iter;
    char quote = '\0';
    // While not a delimiter.
    while (last != str.end() && (quote || strchr(kSeparator, *last) == nullptr)) {
      // Absorb escapes.
      if (*last == '\\') {
        ++last;
        if (last == str.end()) {
          break;
        }
        output += *last++;
        continue;
      }
  
      if (quote) {
        if (*last == quote) {
          // Absorb closing quote.
          quote = '\0';
          ++last;
        } else {
          output += *last++;
        }
      } else {
        if (*last == comment) {
          last = str.end();
          break;
        }
        if (*last == '\'' || *last == '"') {
          // Absorb opening quote.
          quote = *last++;
        } else {
          output += *last++;
        }
      }
    }
  
    if (!output.empty()) {
      words->push_back(output);
    }
  
    *iter = last;
  }
  
  void Tokenize(const string &str, const char &comment, vector<string> *words) {
    assert(words);
    words->clear();
  
    string::const_iterator i = str.begin();
    while (i != str.end()) {
      // Skip whitespace.
      while (i != str.end() && strchr(kSeparator, *i) != nullptr) {
        i++;
      }
      if (i != str.end() && *i == comment) {
        break;
      }
      GetNextToken(str, comment, &i, words);
    }
  }
  
  // Evaluate a format string and store the result in 'str'.
  void StringPrintf(string *str, const char *format, ...) {
    assert(str);
  
    // Determine the required buffer size. vsnpritnf won't account for the
    // terminating '\0'.
    va_list args;
    va_start(args, format);
    int output_size = vsnprintf(nullptr, 0, format, args);
    if (output_size < 0) {
      fprintf(stderr, "Fatal error formatting string: %d", output_size);
      exit(blaze_exit_code::INTERNAL_ERROR);
    }
    va_end(args);
  
    // Allocate a buffer and format the input.
    int buffer_size = output_size + sizeof '\0';
    char *buf = new char[buffer_size];
    va_start(args, format);
    int print_result = vsnprintf(buf, buffer_size, format, args);
    if (print_result < 0) {
      fprintf(stderr, "Fatal error formatting string: %d", print_result);
      exit(blaze_exit_code::INTERNAL_ERROR);
    }
    va_end(args);
  
    *str = buf;
    delete[] buf;
  }
  
  void ToLower(string *str) {
    assert(str);
    *str = AsLower(*str);
  }
  
  string AsLower(const string &str) {
    if (str.empty()) {
      return "";
    }
    unique_ptr<char[]> result(new char[str.size() + 1]);
    char *result_ptr = result.get();
    for (const auto &ch : str) {
      *result_ptr++ = tolower(ch);
    }
    result.get()[str.size()] = 0;
    return string(result.get());
  }
  
  template <typename U, typename V>
  static unique_ptr<V[]> UstringToVstring(
      const U *input, size_t (*convert)(V *output, const U *input, size_t len),
      const char *fmtStringU) {
    size_t size = convert(nullptr, input, 0) + 1;
    if (size == (size_t)-1) {
      fprintf(stderr, "UstringToVstring: invalid input \"");
      fprintf(stderr, fmtStringU, input);
      fprintf(stderr, "\"\n");
      exit(blaze_exit_code::INTERNAL_ERROR);
      return unique_ptr<V[]>(nullptr);  // formally return, though unreachable
    }
    unique_ptr<V[]> result(new V[size]);
    convert(result.get(), input, size);
    result.get()[size - 1] = 0;
    return std::move(result);
  }
  
  unique_ptr<char[]> WstringToCstring(const wchar_t *input) {
    return UstringToVstring<wchar_t, char>(input, wcstombs, "%ls");
  }
  
  std::string WstringToString(const std::wstring &input) {
    return string(WstringToCstring(input.c_str()).get());
  }
  
  unique_ptr<wchar_t[]> CstringToWstring(const char *input) {
    return UstringToVstring<char, wchar_t>(input, mbstowcs, "%s");
  }
  
  std::wstring CstringToWstring(const std::string &input) {
    return wstring(CstringToWstring(input.c_str()).get());
  }
  
  #if defined(_WIN32) || defined(__CYGWIN__)
  
  template <typename U, typename V>
  static bool UStrToVStr(const std::basic_string<U> &input,
                         std::basic_string<V> *output, const bool use_utf8,
                         int (*Convert)(const bool _utf8,
                                        const std::basic_string<U> &_in, V *_out,
                                        const size_t _size),
                         uint32_t *win32_error) {
    int buf_size = input.size() + 1;
    std::unique_ptr<V[]> buf(new V[buf_size]);
    // Attempt to convert, optimistically using the estimated output buffer size.
    int res = Convert(use_utf8, input, buf.get(), buf_size);
    if (res > 0) {
      *output = buf.get();
      return true;
    }
  
    DWORD err = GetLastError();
    if (err != ERROR_INSUFFICIENT_BUFFER) {
      if (win32_error) {
        *win32_error = static_cast<uint32_t>(err);
      }
      return false;
    }
  
    // The output buffer was too small. Get required buffer size.
    res = Convert(use_utf8, input, NULL, 0);
    if (res > 0) {
      buf_size = res;
      buf.reset(new V[buf_size]);
      res = Convert(use_utf8, input, buf.get(), buf_size);
      if (res > 0) {
        *output = buf.get();
        return true;
      }
    }
    if (win32_error) {
      *win32_error = static_cast<uint32_t>(GetLastError());
    }
    return false;
  }
  
  static int ConvertWcsToMbs(const bool use_utf8, const std::wstring &input,
                             char *output, const size_t output_size) {
    return WideCharToMultiByte(use_utf8 ? CP_UTF8 : CP_ACP, 0, input.c_str(), -1,
                               output, output_size, NULL, NULL);
  }
  
  static int ConvertMbsToWcs(const bool use_utf8, const std::string &input,
                             wchar_t *output, const size_t output_size) {
    return MultiByteToWideChar(use_utf8 ? CP_UTF8 : CP_ACP, 0, input.c_str(), -1,
                               output, output_size);
  }
  
  bool WcsToAcp(const std::wstring &input, std::string *output,
                uint32_t *win32_error) {
    return UStrToVStr(input, output, false, ConvertWcsToMbs, win32_error);
  }
  
  bool WcsToUtf8(const std::wstring &input, std::string *output,
                 uint32_t *win32_error) {
    return UStrToVStr(input, output, true, ConvertWcsToMbs, win32_error);
  }
  
  bool AcpToWcs(const std::string &input, std::wstring *output,
                uint32_t *win32_error) {
    return UStrToVStr(input, output, false, ConvertMbsToWcs, win32_error);
  }
  
  bool Utf8ToWcs(const std::string &input, std::wstring *output,
                 uint32_t *win32_error) {
    return UStrToVStr(input, output, true, ConvertMbsToWcs, win32_error);
  }
  
  #endif  // defined(_WIN32) || defined(__CYGWIN__)
  
  }  // namespace blaze_util
  