// 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 {
  // ES6 #sec-string.prototype.tostring
  transitioning javascript builtin
  StringPrototypeToString(js-implicit context: Context)(receiver: Object):
      Object {
    return ToThisValue(receiver, kString, 'String.prototype.toString');
  }

  // ES6 #sec-string.prototype.valueof
  transitioning javascript builtin
  StringPrototypeValueOf(js-implicit context: Context)(receiver: Object):
      Object {
    return ToThisValue(receiver, kString, 'String.prototype.valueOf');
  }

  extern macro StringBuiltinsAssembler::LoadSurrogatePairAt(
      String, intptr, intptr, constexpr UnicodeEncoding): int32;
  extern macro StringFromSingleUTF16EncodedCodePoint(int32): String;

  // This function assumes StringPrimitiveWithNoCustomIteration is true.
  transitioning builtin StringToList(implicit context: Context)(string: String):
      JSArray {
    const kind = PACKED_ELEMENTS;
    const stringLength: intptr = string.length_intptr;

    const map: Map = LoadJSArrayElementsMap(kind, LoadNativeContext(context));
    const array: JSArray = AllocateJSArray(
        kind, map, stringLength, SmiTag(stringLength),
        kAllowLargeObjectAllocation);
    const elements = UnsafeCast<FixedArray>(array.elements);
    const encoding = UTF16;
    let arrayLength: Smi = 0;
    let i: intptr = 0;
    while (i < stringLength) {
      const ch: int32 = LoadSurrogatePairAt(string, stringLength, i, encoding);
      const value: String = StringFromSingleUTF16EncodedCodePoint(ch);
      elements[arrayLength] = value;
      // Increment and continue the loop.
      i = i + value.length_intptr;
      arrayLength++;
    }
    assert(arrayLength >= 0);
    assert(SmiTag(stringLength) >= arrayLength);
    array.length = arrayLength;

    return array;
  }

  transitioning macro GenerateStringAt(implicit context: Context)(
      receiver: Object, position: Object,
      methodName: constexpr string): never labels
  IfInBounds(String, intptr, intptr), IfOutOfBounds {
    // Check that {receiver} is coercible to Object and convert it to a String.
    const string: String = ToThisString(receiver, methodName);
    // Convert the {position} to a Smi and check that it's in bounds of
    // the {string}.
    const indexNumber: Number =
        ToInteger_Inline(context, position, kTruncateMinusZero);
    if (TaggedIsNotSmi(indexNumber)) goto IfOutOfBounds;
    const index: intptr = SmiUntag(UnsafeCast<Smi>(indexNumber));
    const length: intptr = string.length_intptr;
    if (Convert<uintptr>(index) >= Convert<uintptr>(length)) goto IfOutOfBounds;
    goto IfInBounds(string, index, length);
  }

  // ES6 #sec-string.prototype.charat
  transitioning javascript builtin StringPrototypeCharAt(
      js-implicit context: Context,
      receiver: Object)(position: Object): Object {
    try {
      GenerateStringAt(receiver, position, 'String.prototype.charAt')
          otherwise IfInBounds, IfOutOfBounds;
    }
    label IfInBounds(string: String, index: intptr, _length: intptr) {
      const code: int32 = StringCharCodeAt(string, index);
      return StringFromSingleCharCode(code);
    }
    label IfOutOfBounds {
      return kEmptyString;
    }
  }

  // ES6 #sec-string.prototype.charcodeat
  transitioning javascript builtin StringPrototypeCharCodeAt(
      js-implicit context: Context,
      receiver: Object)(position: Object): Object {
    try {
      GenerateStringAt(receiver, position, 'String.prototype.charCodeAt')
          otherwise IfInBounds, IfOutOfBounds;
    }
    label IfInBounds(string: String, index: intptr, _length: intptr) {
      const code: int32 = StringCharCodeAt(string, index);
      return Convert<Smi>(code);
    }
    label IfOutOfBounds {
      return kNaN;
    }
  }

  // ES6 #sec-string.prototype.codepointat
  transitioning javascript builtin StringPrototypeCodePointAt(
      js-implicit context: Context,
      receiver: Object)(position: Object): Object {
    try {
      GenerateStringAt(receiver, position, 'String.prototype.codePointAt')
          otherwise IfInBounds, IfOutOfBounds;
    }
    label IfInBounds(string: String, index: intptr, length: intptr) {
      // This is always a call to a builtin from Javascript, so we need to
      // produce UTF32.
      const code: int32 = LoadSurrogatePairAt(string, length, index, UTF32);
      return Convert<Smi>(code);
    }
    label IfOutOfBounds {
      return Undefined;
    }
  }

  // ES6 String.prototype.concat(...args)
  // ES6 #sec-string.prototype.concat
  transitioning javascript builtin StringPrototypeConcat(
      js-implicit context: Context, receiver: Object)(...arguments): Object {
    // Check that {receiver} is coercible to Object and convert it to a String.
    let string: String = ToThisString(receiver, 'String.prototype.concat');

    // Concatenate all the arguments passed to this builtin.
    const length: intptr = Convert<intptr>(arguments.length);
    for (let i: intptr = 0; i < length; i++) {
      const temp: String = ToString_Inline(context, arguments[i]);
      string = string + temp;
    }
    return string;
  }
}