// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include 'src/builtins/builtins-string-gen.h'

namespace string {

extern enum TrimMode extends uint31 constexpr 'String::TrimMode' {
  kTrim,
  kTrimStart,
  kTrimEnd
}

@export
macro IsWhiteSpaceOrLineTerminator(charCode: char16|char8): bool {
  // 0x0020 - SPACE (Intentionally out of order to fast path a commmon case)
  if (charCode == 0x0020) {
    return true;
  }

  // Common Non-whitespace characters from (0x000E, 0x00A0)
  if (Unsigned(Convert<int32>(charCode) - 0x000E) < 0x0092) {
    return false;
  }

  // 0x0009 - HORIZONTAL TAB
  if (charCode < 0x0009) {
    return false;
  }
  // 0x000A - LINE FEED OR NEW LINE
  // 0x000B - VERTICAL TAB
  // 0x000C - FORMFEED
  // 0x000D - HORIZONTAL TAB
  if (charCode <= 0x000D) {
    return true;
  }

  // 0x00A0 - NO-BREAK SPACE
  if (charCode == 0x00A0) {
    return true;
  }

  // 0x1680 - Ogham Space Mark
  if (charCode == 0x1680) {
    return true;
  }

  // 0x2000 - EN QUAD
  if (charCode < 0x2000) {
    return false;
  }
  // 0x2001 - EM QUAD
  // 0x2002 - EN SPACE
  // 0x2003 - EM SPACE
  // 0x2004 - THREE-PER-EM SPACE
  // 0x2005 - FOUR-PER-EM SPACE
  // 0x2006 - SIX-PER-EM SPACE
  // 0x2007 - FIGURE SPACE
  // 0x2008 - PUNCTUATION SPACE
  // 0x2009 - THIN SPACE
  // 0x200A - HAIR SPACE
  if (charCode <= 0x200A) {
    return true;
  }

  // 0x2028 - LINE SEPARATOR
  if (charCode == 0x2028) {
    return true;
  }
  // 0x2029 - PARAGRAPH SEPARATOR
  if (charCode == 0x2029) {
    return true;
  }
  // 0x202F - NARROW NO-BREAK SPACE
  if (charCode == 0x202F) {
    return true;
  }
  // 0x205F - MEDIUM MATHEMATICAL SPACE
  if (charCode == 0x205F) {
    return true;
  }
  // 0xFEFF - BYTE ORDER MARK
  if (charCode == 0xFEFF) {
    return true;
  }
  // 0x3000 - IDEOGRAPHIC SPACE
  if (charCode == 0x3000) {
    return true;
  }

  return false;
}

transitioning macro StringTrimLoop<T: type>(implicit context: Context)(
    stringSlice: ConstSlice<T>, startIndex: intptr, endIndex: intptr,
    increment: intptr): intptr {
  let index = startIndex;
  while (true) {
    if (index == endIndex) {
      return index;
    }

    const char: T = *stringSlice.AtIndex(index);
    if (!IsWhiteSpaceOrLineTerminator(char)) {
      return index;
    }
    index = index + increment;
  }
  unreachable;
}

transitioning macro StringTrimBody<T: type>(implicit context: Context)(
    string: String, slice: ConstSlice<T>, variant: constexpr TrimMode): String {
  const stringLength: intptr = string.length_intptr;

  let startIndex: intptr = 0;
  let endIndex: intptr = stringLength - 1;
  if (variant == TrimMode::kTrim || variant == TrimMode::kTrimStart) {
    startIndex = StringTrimLoop(slice, startIndex, stringLength, 1);
    if (startIndex == stringLength) {
      return kEmptyString;
    }
  }

  if (variant == TrimMode::kTrim || variant == TrimMode::kTrimEnd) {
    endIndex = StringTrimLoop(slice, endIndex, -1, -1);
    if (endIndex == -1) {
      return kEmptyString;
    }
  }

  return SubString(string, Unsigned(startIndex), Unsigned(endIndex + 1));
}

transitioning macro StringTrim(implicit context: Context)(
    receiver: JSAny, _arguments: Arguments, methodName: constexpr string,
    variant: constexpr TrimMode): String {
  const receiverString: String = ToThisString(receiver, methodName);

  try {
    StringToSlice(receiverString) otherwise OneByte, TwoByte;
  } label OneByte(slice: ConstSlice<char8>) {
    return StringTrimBody(receiverString, slice, variant);
  } label TwoByte(slice: ConstSlice<char16>) {
    return StringTrimBody(receiverString, slice, variant);
  }
}

// ES6 #sec-string.prototype.trim
transitioning javascript builtin
StringPrototypeTrim(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): String {
  const methodName: constexpr string = 'String.prototype.trim';
  return StringTrim(receiver, arguments, methodName, TrimMode::kTrim);
}

// https://github.com/tc39/proposal-string-left-right-trim
transitioning javascript builtin
StringPrototypeTrimStart(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): String {
  const methodName: constexpr string = 'String.prototype.trimLeft';
  return StringTrim(receiver, arguments, methodName, TrimMode::kTrimStart);
}

// https://github.com/tc39/proposal-string-left-right-trim
transitioning javascript builtin
StringPrototypeTrimEnd(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): String {
  const methodName: constexpr string = 'String.prototype.trimRight';
  return StringTrim(receiver, arguments, methodName, TrimMode::kTrimEnd);
}
}