using Jint.Collections;
using Jint.Native.Array;
using Jint.Native.Proxy;
using Jint.Native.Symbol;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
namespace Jint.Native.Object
{
public sealed class ObjectPrototype : Prototype
{
private readonly ObjectConstructor _constructor;
internal ObjectChangeFlags _objectChangeFlags;
internal ObjectPrototype(
Engine engine,
Realm realm,
ObjectConstructor constructor) : base(engine, realm)
{
_constructor = constructor;
}
protected override void Initialize()
{
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
const PropertyFlag lengthFlags = PropertyFlag.Configurable;
var properties = new PropertyDictionary(8, checkExistingKeys: false)
{
["constructor"] = new PropertyDescriptor(_constructor, propertyFlags),
["__proto__"] = new GetSetPropertyDescriptor(
new ClrFunctionInstance(Engine, "get __proto__", (thisObject, _) => TypeConverter.ToObject(_realm, thisObject).GetPrototypeOf() ?? Null, 0, lengthFlags),
new ClrFunctionInstance(Engine, "set __proto__", (thisObject, arguments) =>
{
TypeConverter.CheckObjectCoercible(_engine, thisObject);
var proto = arguments.At(0);
if (!proto.IsObject() && !proto.IsNull() || thisObject is not ObjectInstance objectInstance)
{
return Undefined;
}
if (!objectInstance.SetPrototypeOf(proto))
{
ExceptionHelper.ThrowTypeError(_realm, "Invalid prototype");
}
return Undefined;
}, 0, lengthFlags),
enumerable: false, configurable: true),
["toString"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toString", ToObjectString, 0, lengthFlags), propertyFlags),
["toLocaleString"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString, 0, lengthFlags), propertyFlags),
["valueOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "valueOf", ValueOf, 0, lengthFlags), propertyFlags),
["hasOwnProperty"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "hasOwnProperty", HasOwnProperty, 1, lengthFlags), propertyFlags),
["isPrototypeOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isPrototypeOf", IsPrototypeOf, 1, lengthFlags), propertyFlags),
["propertyIsEnumerable"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "propertyIsEnumerable", PropertyIsEnumerable, 1, lengthFlags), propertyFlags)
};
SetProperties(properties);
}
public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
{
TrackChanges(property);
return base.DefineOwnProperty(property, desc);
}
protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
{
TrackChanges(property);
base.SetOwnProperty(property, desc);
}
private void TrackChanges(JsValue property)
{
EnsureInitialized();
if (ArrayInstance.IsArrayIndex(property, out _))
{
_objectChangeFlags |= ObjectChangeFlags.ArrayIndex;
}
else
{
_objectChangeFlags |= property.IsSymbol() ? ObjectChangeFlags.Symbol : ObjectChangeFlags.Property;
}
}
private JsValue PropertyIsEnumerable(JsValue thisObject, JsValue[] arguments)
{
var p = TypeConverter.ToPropertyKey(arguments[0]);
var o = TypeConverter.ToObject(_realm, thisObject);
var desc = o.GetOwnProperty(p);
if (desc == PropertyDescriptor.Undefined)
{
return JsBoolean.False;
}
return desc.Enumerable;
}
private JsValue ValueOf(JsValue thisObject, JsValue[] arguments)
{
var o = TypeConverter.ToObject(_realm, thisObject);
return o;
}
private JsValue IsPrototypeOf(JsValue thisObject, JsValue[] arguments)
{
var arg = arguments[0];
if (!arg.IsObject())
{
return JsBoolean.False;
}
var v = arg.AsObject();
var o = TypeConverter.ToObject(_realm, thisObject);
while (true)
{
v = v.Prototype;
if (ReferenceEquals(v, null))
{
return JsBoolean.False;
}
if (ReferenceEquals(o, v))
{
return JsBoolean.True;
}
}
}
private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
{
var o = TypeConverter.ToObject(_realm, thisObject);
var func = o.Get("toString");
var callable = func as ICallable;
if (callable is null)
{
ExceptionHelper.ThrowTypeError(_realm, "Can only invoke functions");
}
return TypeConverter.ToJsString(callable.Call(thisObject, arguments));
}
///
/// https://tc39.es/ecma262/#sec-object.prototype.tostring
///
public JsValue ToObjectString(JsValue thisObject, JsValue[] arguments)
{
if (thisObject.IsUndefined())
{
return "[object Undefined]";
}
if (thisObject.IsNull())
{
return "[object Null]";
}
var o = TypeConverter.ToObject(_realm, thisObject);
var isArray = o.IsArray();
var tag = o.Get(GlobalSymbolRegistry.ToStringTag);
if (!tag.IsString())
{
if (isArray)
{
tag = "Array";
}
else if (o.IsCallable)
{
tag = "Function";
}
else
{
tag = (o is ProxyInstance ? ObjectClass.Object : o.Class).ToString();
}
}
return "[object " + tag + "]";
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.5
///
public JsValue HasOwnProperty(JsValue thisObject, JsValue[] arguments)
{
var p = TypeConverter.ToPropertyKey(arguments[0]);
var o = TypeConverter.ToObject(_realm, thisObject);
var desc = o.GetOwnProperty(p);
return desc != PropertyDescriptor.Undefined;
}
}
}