| // 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 { |
| |
| namespace runtime { |
| extern transitioning runtime ToString(Context, JSAny): String; |
| } |
| |
| @export |
| transitioning macro ToStringImpl(context: Context, o: JSAny): String { |
| let result: JSAny = o; |
| while (true) { |
| typeswitch (result) { |
| case (num: Number): { |
| return NumberToString(num); |
| } |
| case (str: String): { |
| return str; |
| } |
| case (oddball: Oddball): { |
| return oddball.to_string; |
| } |
| case (JSReceiver): { |
| result = NonPrimitiveToPrimitive_String(context, result); |
| continue; |
| } |
| case (Symbol): { |
| ThrowTypeError(MessageTemplate::kSymbolToString); |
| } |
| case (JSAny): { |
| return runtime::ToString(context, o); |
| } |
| } |
| } |
| unreachable; |
| } |
| |
| transitioning builtin ToString(context: Context, o: JSAny): String { |
| return ToStringImpl(context, o); |
| } |
| |
| extern macro StringBuiltinsAssembler::SubString( |
| String, uintptr, uintptr): String; |
| |
| // ES6 #sec-string.prototype.tostring |
| transitioning javascript builtin |
| StringPrototypeToString( |
| js-implicit context: NativeContext, receiver: JSAny)(): JSAny { |
| return ToThisValue( |
| receiver, PrimitiveType::kString, 'String.prototype.toString'); |
| } |
| |
| // ES6 #sec-string.prototype.valueof |
| transitioning javascript builtin |
| StringPrototypeValueOf( |
| js-implicit context: NativeContext, receiver: JSAny)(): JSAny { |
| return ToThisValue( |
| receiver, PrimitiveType::kString, 'String.prototype.valueOf'); |
| } |
| |
| extern macro StringBuiltinsAssembler::LoadSurrogatePairAt( |
| String, intptr, intptr, constexpr UnicodeEncoding): int32; |
| extern macro StringBuiltinsAssembler::StringFromSingleUTF16EncodedCodePoint( |
| int32): String; |
| |
| // This function assumes StringPrimitiveWithNoCustomIteration is true. |
| transitioning builtin StringToList(implicit context: Context)(string: String): |
| JSArray { |
| const kind = ElementsKind::PACKED_ELEMENTS; |
| const stringLength: intptr = string.length_intptr; |
| |
| const nativeContext = LoadNativeContext(context); |
| const map: Map = LoadJSArrayElementsMap(kind, nativeContext); |
| const array: JSArray = AllocateJSArray( |
| kind, map, stringLength, SmiTag(stringLength), |
| AllocationFlag::kAllowLargeObjectAllocation); |
| const elements = UnsafeCast<FixedArray>(array.elements); |
| const encoding = UnicodeEncoding::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: JSAny, position: JSAny, |
| methodName: constexpr string): never labels |
| IfInBounds(String, uintptr, uintptr), IfOutOfBounds { |
| // 1. Let O be ? RequireObjectCoercible(this value). |
| // 2. Let S be ? ToString(O). |
| const string: String = ToThisString(receiver, methodName); |
| |
| // 3. Let position be ? ToInteger(pos). |
| const indexNumber: Number = ToInteger_Inline(position); |
| |
| // Convert the {position} to a uintptr and check that it's in bounds of |
| // the {string}. |
| typeswitch (indexNumber) { |
| case (indexSmi: Smi): { |
| const length: uintptr = string.length_uintptr; |
| const index: uintptr = Unsigned(Convert<intptr>(indexSmi)); |
| // Max string length fits Smi range, so we can do an unsigned bounds |
| // check. |
| const kMaxStringLengthFitsSmi: constexpr bool = |
| kStringMaxLengthUintptr < kSmiMaxValue; |
| static_assert(kMaxStringLengthFitsSmi); |
| if (index >= length) goto IfOutOfBounds; |
| goto IfInBounds(string, index, length); |
| } |
| case (indexHeapNumber: HeapNumber): { |
| assert(IsNumberNormalized(indexHeapNumber)); |
| // Valid string indices fit into Smi range, so HeapNumber index is |
| // definitely an out of bounds case. |
| goto IfOutOfBounds; |
| } |
| } |
| } |
| |
| // ES6 #sec-string.prototype.charat |
| transitioning javascript builtin StringPrototypeCharAt( |
| js-implicit context: NativeContext, |
| receiver: JSAny)(position: JSAny): JSAny { |
| try { |
| GenerateStringAt(receiver, position, 'String.prototype.charAt') |
| otherwise IfInBounds, IfOutOfBounds; |
| } label IfInBounds(string: String, index: uintptr, _length: uintptr) { |
| 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: NativeContext, |
| receiver: JSAny)(position: JSAny): JSAny { |
| try { |
| GenerateStringAt(receiver, position, 'String.prototype.charCodeAt') |
| otherwise IfInBounds, IfOutOfBounds; |
| } label IfInBounds(string: String, index: uintptr, _length: uintptr) { |
| 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: NativeContext, |
| receiver: JSAny)(position: JSAny): JSAny { |
| try { |
| GenerateStringAt(receiver, position, 'String.prototype.codePointAt') |
| otherwise IfInBounds, IfOutOfBounds; |
| } label IfInBounds(string: String, index: uintptr, length: uintptr) { |
| // This is always a call to a builtin from Javascript, so we need to |
| // produce UTF32. |
| const code: int32 = LoadSurrogatePairAt( |
| string, Signed(length), Signed(index), UnicodeEncoding::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: NativeContext, receiver: JSAny)(...arguments): JSAny { |
| // 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(arguments[i]); |
| string = string + temp; |
| } |
| return string; |
| } |
| |
| extern transitioning runtime |
| SymbolDescriptiveString(implicit context: Context)(Symbol): String; |
| |
| // ES #sec-string-constructor |
| // https://tc39.github.io/ecma262/#sec-string-constructor |
| transitioning javascript builtin StringConstructor( |
| js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny, |
| target: JSFunction)(...arguments): JSAny { |
| const length: intptr = Convert<intptr>(arguments.length); |
| let s: String; |
| // 1. If no arguments were passed to this function invocation, let s be "". |
| if (length == 0) { |
| s = EmptyStringConstant(); |
| } else { |
| // 2. Else, |
| // 2. a. If NewTarget is undefined and Type(value) is Symbol, return |
| // SymbolDescriptiveString(value). |
| if (newTarget == Undefined) { |
| typeswitch (arguments[0]) { |
| case (value: Symbol): { |
| return SymbolDescriptiveString(value); |
| } |
| case (JSAny): { |
| } |
| } |
| } |
| // 2. b. Let s be ? ToString(value). |
| s = ToString_Inline(arguments[0]); |
| } |
| // 3. If NewTarget is undefined, return s. |
| if (newTarget == Undefined) { |
| return s; |
| } |
| // 4. Return ! StringCreate(s, ? GetPrototypeFromConstructor(NewTarget, |
| // "%String.prototype%")). |
| const map = GetDerivedMap(target, UnsafeCast<JSReceiver>(newTarget)); |
| const obj = |
| UnsafeCast<JSPrimitiveWrapper>(AllocateFastOrSlowJSObjectFromMap(map)); |
| obj.value = s; |
| return obj; |
| } |
| |
| transitioning builtin StringAddConvertLeft(implicit context: Context)( |
| left: JSAny, right: String): String { |
| return ToStringImpl(context, ToPrimitiveDefault(left)) + right; |
| } |
| |
| transitioning builtin StringAddConvertRight(implicit context: Context)( |
| left: String, right: JSAny): String { |
| return left + ToStringImpl(context, ToPrimitiveDefault(right)); |
| } |
| |
| builtin StringCharAt(implicit context: Context)( |
| receiver: String, position: uintptr): String { |
| // Load the character code at the {position} from the {receiver}. |
| const code: int32 = StringCharCodeAt(receiver, position); |
| // And return the single character string with only that {code} |
| return StringFromSingleCharCode(code); |
| } |
| } |