blob: ff1538d026ca38f5d9dcb612d82a5e587fcc33f9 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"
namespace Js
{
JavascriptMap::JavascriptMap(DynamicType* type)
: DynamicObject(type)
{
}
JavascriptMap* JavascriptMap::New(ScriptContext* scriptContext)
{
JavascriptMap* map = scriptContext->GetLibrary()->CreateMap();
map->map = RecyclerNew(scriptContext->GetRecycler(), MapDataMap, scriptContext->GetRecycler());
return map;
}
bool JavascriptMap::Is(Var aValue)
{
return JavascriptOperators::GetTypeId(aValue) == TypeIds_Map;
}
JavascriptMap* JavascriptMap::FromVar(Var aValue)
{
AssertMsg(Is(aValue), "Ensure var is actually a 'JavascriptMap'");
return static_cast<JavascriptMap *>(RecyclableObject::FromVar(aValue));
}
JavascriptMap::MapDataList::Iterator JavascriptMap::GetIterator()
{
return list.GetIterator();
}
Var JavascriptMap::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
JavascriptLibrary* library = scriptContext->GetLibrary();
AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Map"));
Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0];
bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget);
Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr);
CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(MapCount);
JavascriptMap* mapObject = nullptr;
if (callInfo.Flags & CallFlags_New)
{
mapObject = library->CreateMap();
}
else
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map"), _u("Map"));
}
Assert(mapObject != nullptr);
Var iterable = (args.Info.Count > 1) ? args[1] : library->GetUndefined();
RecyclableObject* iter = nullptr;
RecyclableObject* adder = nullptr;
if (JavascriptConversion::CheckObjectCoercible(iterable, scriptContext))
{
iter = JavascriptOperators::GetIterator(iterable, scriptContext);
Var adderVar = JavascriptOperators::GetProperty(mapObject, PropertyIds::set, scriptContext);
if (!JavascriptConversion::IsCallable(adderVar))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction);
}
adder = RecyclableObject::FromVar(adderVar);
}
if (mapObject->map != nullptr)
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_ObjectIsAlreadyInitialized, _u("Map"), _u("Map"));
}
mapObject->map = RecyclerNew(scriptContext->GetRecycler(), MapDataMap, scriptContext->GetRecycler());
if (iter != nullptr)
{
Var nextItem;
Var undefined = library->GetUndefined();
while (JavascriptOperators::IteratorStepAndValue(iter, scriptContext, &nextItem))
{
if (!JavascriptOperators::IsObject(nextItem))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject);
}
RecyclableObject* obj = RecyclableObject::FromVar(nextItem);
Var key, value;
if (!JavascriptOperators::GetItem(obj, 0u, &key, scriptContext))
{
key = undefined;
}
if (!JavascriptOperators::GetItem(obj, 1u, &value, scriptContext))
{
value = undefined;
}
// CONSIDER: if adder is the default built-in, fast path it and skip the JS call?
adder->GetEntryPoint()(adder, CallInfo(CallFlags_Value, 3), mapObject, key, value);
}
}
return isCtorSuperCall ?
JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), mapObject, nullptr, scriptContext) :
mapObject;
}
Var JavascriptMap::EntryClear(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.clear"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
map->Clear();
return scriptContext->GetLibrary()->GetUndefined();
}
Var JavascriptMap::EntryDelete(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.delete"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
bool didDelete = map->Delete(key);
return scriptContext->GetLibrary()->CreateBoolean(didDelete);
}
Var JavascriptMap::EntryForEach(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Map.prototype.forEach"));
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.forEach"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
if (args.Info.Count < 2 || !JavascriptConversion::IsCallable(args[1]))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Map.prototype.forEach"));
}
RecyclableObject* callBackFn = RecyclableObject::FromVar(args[1]);
Var thisArg = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined();
auto iterator = map->GetIterator();
while (iterator.Next())
{
Var key = iterator.Current().Key();
Var value = iterator.Current().Value();
callBackFn->GetEntryPoint()(callBackFn, CallInfo(CallFlags_Value, 4), thisArg, value, key, map);
}
return scriptContext->GetLibrary()->GetUndefined();
}
Var JavascriptMap::EntryGet(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.get"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
Var value = nullptr;
if (map->Get(key, &value))
{
return value;
}
return scriptContext->GetLibrary()->GetUndefined();
}
Var JavascriptMap::EntryHas(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.has"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
bool hasValue = map->Has(key);
return scriptContext->GetLibrary()->CreateBoolean(hasValue);
}
Var JavascriptMap::EntrySet(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.set"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
Var key = (args.Info.Count > 1) ? args[1] : scriptContext->GetLibrary()->GetUndefined();
Var value = (args.Info.Count > 2) ? args[2] : scriptContext->GetLibrary()->GetUndefined();
if (JavascriptNumber::Is(key) && JavascriptNumber::IsNegZero(JavascriptNumber::GetValue(key)))
{
// Normalize -0 to +0
key = JavascriptNumber::New(0.0, scriptContext);
}
map->Set(key, value);
return map;
}
Var JavascriptMap::EntrySizeGetter(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.size"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
int size = map->Size();
return JavascriptNumber::ToVar(size, scriptContext);
}
Var JavascriptMap::EntryEntries(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.entries"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::KeyAndValue);
}
Var JavascriptMap::EntryKeys(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.keys"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::Key);
}
Var JavascriptMap::EntryValues(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (!JavascriptMap::Is(args[0]))
{
JavascriptError::ThrowTypeErrorVar(scriptContext, JSERR_NeedObjectOfType, _u("Map.prototype.values"), _u("Map"));
}
JavascriptMap* map = JavascriptMap::FromVar(args[0]);
return scriptContext->GetLibrary()->CreateMapIterator(map, JavascriptMapIteratorKind::Value);
}
void JavascriptMap::Clear()
{
list.Clear();
map->Clear();
}
bool JavascriptMap::Delete(Var key)
{
if (map->ContainsKey(key))
{
MapDataNode* node = map->Item(key);
list.Remove(node);
return map->Remove(key);
}
return false;
}
bool JavascriptMap::Get(Var key, Var* value)
{
if (map->ContainsKey(key))
{
MapDataNode* node = map->Item(key);
*value = node->data.Value();
return true;
}
return false;
}
bool JavascriptMap::Has(Var key)
{
return map->ContainsKey(key);
}
void JavascriptMap::Set(Var key, Var value)
{
if (map->ContainsKey(key))
{
MapDataNode* node = map->Item(key);
node->data = MapDataKeyValuePair(key, value);
}
else
{
MapDataKeyValuePair pair(key, value);
MapDataNode* node = list.Append(pair, GetScriptContext()->GetRecycler());
map->Add(key, node);
}
}
int JavascriptMap::Size()
{
return map->Count();
}
BOOL JavascriptMap::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
{
stringBuilder->AppendCppLiteral(_u("Map"));
return TRUE;
}
Var JavascriptMap::EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...)
{
ARGUMENTS(args, callInfo);
Assert(args.Info.Count > 0);
return args[0];
}
}