1/*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "ecmascript/builtins/builtins_symbol.h"
17
18#include "ecmascript/ecma_string_table.h"
19#include "ecmascript/global_env.h"
20#include "ecmascript/js_primitive_ref.h"
21#include "ecmascript/symbol_table.h"
22
23namespace panda::ecmascript::builtins {
24// prototype
25// 19.4.3.1
26// constructor
27JSTaggedValue BuiltinsSymbol::SymbolConstructor(EcmaRuntimeCallInfo *argv)
28{
29    ASSERT(argv);
30    BUILTINS_API_TRACE(argv->GetThread(), Symbol, Constructor);
31    JSThread *thread = argv->GetThread();
32    [[maybe_unused]] EcmaHandleScope handleScope(thread);
33    // 1.If NewTarget is not undefined, throw a TypeError exception.
34    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
35    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
36    if (!newTarget->IsUndefined()) {
37        THROW_TYPE_ERROR_AND_RETURN(thread, "SymbolConstructor: NewTarget is not undefined",
38                                    JSTaggedValue::Exception());
39    }
40    // 2.If description is undefined, let descString be undefined.
41    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
42    if (key->IsUndefined()) {
43        JSHandle<JSSymbol> jsSymbol = factory->NewJSSymbol();
44        return jsSymbol.GetTaggedValue();
45    }
46    // 3.Else, let descString be ToString(description).
47    JSHandle<JSTaggedValue> descHandle = JSHandle<JSTaggedValue>::Cast(JSTaggedValue::ToString(thread, key));
48    // 4.ReturnIfAbrupt(descString).
49    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
50    // 5.Return a new unique Symbol value whose [[Description]] value is descString.
51    JSHandle<JSSymbol> jsSymbol = factory->NewPublicSymbol(descHandle);
52    return jsSymbol.GetTaggedValue();
53}
54
55// 19.4.3.2 Symbol.prototype.toString()
56JSTaggedValue BuiltinsSymbol::ToString(EcmaRuntimeCallInfo *argv)
57{
58    ASSERT(argv);
59    BUILTINS_API_TRACE(argv->GetThread(), Symbol, ToString);
60    JSThread *thread = argv->GetThread();
61    [[maybe_unused]] EcmaHandleScope handleScope(thread);
62    // Let s be the this value.
63    JSHandle<JSTaggedValue> valueHandle = GetThis(argv);
64    // 1.If value is a Symbol, return value.
65    if (valueHandle->IsSymbol()) {
66        // Return SymbolDescriptiveString(sym).
67        return SymbolDescriptiveString(thread, valueHandle.GetTaggedValue());
68    }
69
70    // 2.If value is an Object and value has a [[SymbolData]] internal slot, then
71    if (valueHandle->IsJSPrimitiveRef()) {
72        // Let sym be the value of s's [[SymbolData]] internal slot.
73        JSTaggedValue primitive = JSPrimitiveRef::Cast(valueHandle->GetTaggedObject())->GetValue();
74        if (primitive.IsSymbol()) {
75            return SymbolDescriptiveString(thread, primitive);
76        }
77    }
78
79    // 3.If s does not have a [[SymbolData]] internal slot, throw a TypeError exception.
80    THROW_TYPE_ERROR_AND_RETURN(thread, "ToString: no [[SymbolData]]", JSTaggedValue::Exception());
81}
82
83JSTaggedValue BuiltinsSymbol::SymbolDescriptiveString(JSThread *thread, JSTaggedValue sym)
84{
85    BUILTINS_API_TRACE(thread, Symbol, SymbolDescriptiveString);
86    // Assert: Type(sym) is Symbol.
87    ASSERT(sym.IsSymbol());
88    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
89
90    // Let desc be sym’s [[Description]] value.
91    auto symbolObject = reinterpret_cast<JSSymbol *>(sym.GetTaggedObject());
92    JSHandle<JSTaggedValue> descHandle(thread, symbolObject->GetDescription());
93
94    // If desc is undefined, let desc be the empty string.
95    JSHandle<SingleCharTable> singleCharTable(thread, thread->GetSingleCharTable());
96    auto constants = thread->GlobalConstants();
97    if (descHandle->IsUndefined()) {
98        JSHandle<EcmaString> leftHandle = JSHandle<EcmaString>::Cast(constants->GetHandledSymbolLeftParentheses());
99        JSHandle<EcmaString> rightHandle(thread, singleCharTable->GetStringFromSingleCharTable(')'));
100        JSHandle<EcmaString> str = factory->ConcatFromString(leftHandle, rightHandle);
101        return str.GetTaggedValue();
102    }
103    // Assert: Type(desc) is String.
104    ASSERT(descHandle->IsString());
105    // Return the result of concatenating the strings "Symbol(", desc, and ")".
106    JSHandle<EcmaString> leftHandle = JSHandle<EcmaString>::Cast(constants->GetHandledSymbolLeftParentheses());
107    JSHandle<EcmaString> rightHandle(thread, singleCharTable->GetStringFromSingleCharTable(')'));
108    JSHandle<EcmaString> stringLeft =
109        factory->ConcatFromString(leftHandle, JSTaggedValue::ToString(thread, descHandle));
110    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
111    JSHandle<EcmaString> str = factory->ConcatFromString(stringLeft, rightHandle);
112    return str.GetTaggedValue();
113}
114
115// 19.4.3.3
116JSTaggedValue BuiltinsSymbol::ValueOf(EcmaRuntimeCallInfo *argv)
117{
118    ASSERT(argv);
119    BUILTINS_API_TRACE(argv->GetThread(), Symbol, ValueOf);
120    JSThread *thread = argv->GetThread();
121    [[maybe_unused]] EcmaHandleScope handleScope(thread);
122    // Let s be the this value.
123    JSHandle<JSTaggedValue> valueHandle = GetThis(argv);
124    // 1.If value is a Symbol, return value.
125    if (valueHandle->IsSymbol()) {
126        return valueHandle.GetTaggedValue();
127    }
128
129    // 2.If value is an Object and value has a [[SymbolData]] internal slot, then
130    if (valueHandle->IsJSPrimitiveRef()) {
131        // Let sym be the value of s's [[SymbolData]] internal slot.
132        JSTaggedValue primitive = JSPrimitiveRef::Cast(valueHandle->GetTaggedObject())->GetValue();
133        if (primitive.IsSymbol()) {
134            return primitive;
135        }
136    }
137
138    // 3.If s does not have a [[SymbolData]] internal slot, throw a TypeError exception.
139    THROW_TYPE_ERROR_AND_RETURN(thread, "ValueOf: no [[SymbolData]]", JSTaggedValue::Exception());
140}
141
142// 19.4.2.1 Symbol.for (key)
143JSTaggedValue BuiltinsSymbol::For(EcmaRuntimeCallInfo *argv)
144{
145    ASSERT(argv);
146    BUILTINS_API_TRACE(argv->GetThread(), Symbol, For);
147    JSThread *thread = argv->GetThread();
148    [[maybe_unused]] EcmaHandleScope handleScope(thread);
149    // 1.Let stringKey be ToString(key).
150    JSHandle<JSTaggedValue> key = BuiltinsSymbol::GetCallArg(argv, 0);
151    JSHandle<JSTaggedValue> stringHandle = JSHandle<JSTaggedValue>::Cast(JSTaggedValue::ToString(thread, key));
152    // 2.ReturnIfAbrupt
153    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
154
155    // 3.For each element e of the GlobalSymbolRegistry List,
156    // If SameValue(e.[[key]], stringKey) is true, return e.[[symbol]].
157    // 4.Assert: GlobalSymbolRegistry does not currently contain an entry for stringKey.
158    // 5.Let newSymbol be a new unique Symbol value whose [[Description]] value is stringKey.
159    // 6.Append the record { [[key]]: stringKey, [[symbol]]: newSymbol } to the GlobalSymbolRegistry List.
160    // 7.Return newSymbol.
161    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
162    JSHandle<JSSymbol> symbol = factory->NewSymbolWithTable(stringHandle);
163    return symbol.GetTaggedValue();
164}
165
166// 19.4.2.5 Symbol.keyFor (sym)
167JSTaggedValue BuiltinsSymbol::KeyFor(EcmaRuntimeCallInfo *argv)
168{
169    ASSERT(argv);
170    BUILTINS_API_TRACE(argv->GetThread(), Symbol, KeyFor);
171    JSThread *thread = argv->GetThread();
172    [[maybe_unused]] EcmaHandleScope handleScope(thread);
173    // 1.If Type(sym) is not Symbol, throw a TypeError exception.
174    JSHandle<JSTaggedValue> sym = BuiltinsSymbol::GetCallArg(argv, 0);
175    if (!sym->IsSymbol()) {
176        // return typeError
177        THROW_TYPE_ERROR_AND_RETURN(thread, "KeyFor: sym is not Symbol", JSTaggedValue::Exception());
178    }
179    // 2.For each element e of the GlobalSymbolRegistry List,
180    // If SameValue(e.[[symbol]], sym) is true, return e.[[key]].
181    // 3.Assert: GlobalSymbolRegistry does not currently contain an entry for sym.
182    // 4.Return undefined.
183    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
184    auto *table = env->GetRegisterSymbols().GetObject<SymbolTable>();
185    JSTaggedValue key = table->FindSymbol(sym.GetTaggedValue());
186    if (key.IsUndefined()) {
187        return JSTaggedValue::Undefined();
188    }
189    return JSTaggedValue::ToString(thread, JSHandle<JSTaggedValue>(thread, key)).GetTaggedValue();
190}
191
192// 19.4.3.4 Symbol.prototype [ @@toPrimitive ] ( hint )
193JSTaggedValue BuiltinsSymbol::ToPrimitive(EcmaRuntimeCallInfo *argv)
194{
195    ASSERT(argv);
196    BUILTINS_API_TRACE(argv->GetThread(), Symbol, ToPrimitive);
197    JSThread *thread = argv->GetThread();
198    [[maybe_unused]] EcmaHandleScope handleScope(thread);
199    // Let s be the this value.
200    JSHandle<JSTaggedValue> sym = GetThis(argv);
201    // 1.If value is a Symbol, return value.
202    if (sym->IsSymbol()) {
203        return sym.GetTaggedValue();
204    }
205
206    // 2.If value is an Object and value has a [[SymbolData]] internal slot, then
207    if (sym->IsJSPrimitiveRef()) {
208        // Let sym be the value of s's [[SymbolData]] internal slot.
209        JSTaggedValue primitive = JSPrimitiveRef::Cast(sym->GetTaggedObject())->GetValue();
210        if (primitive.IsSymbol()) {
211            return primitive;
212        }
213    }
214
215    // 3.If s does not have a [[SymbolData]] internal slot, throw a TypeError exception.
216    THROW_TYPE_ERROR_AND_RETURN(thread, "ToPrimitive: s is not Object", JSTaggedValue::Exception());
217}
218JSTaggedValue BuiltinsSymbol::DescriptionGetter(EcmaRuntimeCallInfo *argv)
219{
220    ASSERT(argv);
221    BUILTINS_API_TRACE(argv->GetThread(), Symbol, DescriptionGetter);
222    // 1.Let s be the this value.
223    JSThread *thread = argv->GetThread();
224    [[maybe_unused]] EcmaHandleScope handleScope(thread);
225    JSHandle<JSTaggedValue> s = GetThis(argv);
226    // 2.Let sym be ? thisSymbolValue(s).
227    // 3.Return sym.[[Description]].
228    return ThisSymbolValue(thread, s);
229}
230
231JSTaggedValue BuiltinsSymbol::ThisSymbolValue(JSThread *thread, const JSHandle<JSTaggedValue> &value)
232{
233    BUILTINS_API_TRACE(thread, Symbol, ThisSymbolValue);
234    if (value->IsSymbol()) {
235        JSTaggedValue desValue = JSSymbol::Cast(value->GetTaggedObject())->GetDescription();
236        return desValue;
237    }
238
239    // If s does not have a [[SymbolData]] internal slot, throw a TypeError exception.
240    if (value->IsJSPrimitiveRef()) {
241        JSTaggedValue primitive = JSPrimitiveRef::Cast(value->GetTaggedObject())->GetValue();
242        if (primitive.IsSymbol()) {
243            // Return the value of s's [[SymbolData]] internal slot.
244            JSTaggedValue primitiveDesValue = JSSymbol::Cast(primitive.GetTaggedObject())->GetDescription();
245            return primitiveDesValue;
246        }
247    }
248    THROW_TYPE_ERROR_AND_RETURN(thread, "can not convert to Symbol", JSTaggedValue::Exception());
249}
250}  // namespace panda::ecmascript::builtins
251