using System.Collections.Generic;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Environments;
namespace Jint.Native.Function
{
public abstract class FunctionInstance : ObjectInstance, ICallable
{
protected internal PropertyDescriptor _prototype;
protected PropertyDescriptor _length;
private JsValue _name;
private PropertyDescriptor _nameDescriptor;
protected readonly LexicalEnvironment _scope;
protected internal readonly string[] _formalParameters;
protected readonly bool _strict;
protected FunctionInstance(
Engine engine,
string name,
string[] parameters,
LexicalEnvironment scope,
bool strict,
string objectClass = "Function")
: this(engine, !string.IsNullOrWhiteSpace(name) ? new JsString(name) : null, parameters, scope, strict, objectClass)
{
}
internal FunctionInstance(
Engine engine,
JsString name,
string[] parameters,
LexicalEnvironment scope,
bool strict,
string objectClass = "Function")
: this(engine, name, strict, objectClass)
{
_formalParameters = parameters;
_scope = scope;
}
internal FunctionInstance(
Engine engine,
JsString name,
bool strict,
string objectClass = "Function")
: base(engine, objectClass)
{
_name = name;
_strict = strict;
}
///
/// Executed when a function object is used as a function
///
///
///
///
public abstract JsValue Call(JsValue thisObject, JsValue[] arguments);
public LexicalEnvironment Scope => _scope;
public string[] FormalParameters => _formalParameters;
public bool Strict => _strict;
public virtual bool HasInstance(JsValue v)
{
var vObj = v.TryCast();
if (ReferenceEquals(vObj, null))
{
return false;
}
var po = Get(KnownKeys.Prototype);
if (!po.IsObject())
{
ExceptionHelper.ThrowTypeError(_engine, $"Function has non-object prototype '{TypeConverter.ToString(po)}' in instanceof check");
}
var o = po.AsObject();
if (ReferenceEquals(o, null))
{
ExceptionHelper.ThrowTypeError(_engine);
}
while (true)
{
vObj = vObj.Prototype;
if (ReferenceEquals(vObj, null))
{
return false;
}
if (vObj == o)
{
return true;
}
}
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5.4
///
///
///
public override JsValue Get(in Key propertyName)
{
var v = base.Get(propertyName);
if (propertyName == KnownKeys.Caller
&& ((v.As()?._strict).GetValueOrDefault()))
{
ExceptionHelper.ThrowTypeError(_engine);
}
return v;
}
public override IEnumerable> GetOwnProperties()
{
if (_prototype != null)
{
yield return new KeyValuePair(KnownKeys.Prototype, _prototype);
}
if (_length != null)
{
yield return new KeyValuePair(KnownKeys.Length, _length);
}
if (!(_name is null))
{
yield return new KeyValuePair(KnownKeys.Name, GetOwnProperty(KnownKeys.Name));
}
foreach (var entry in base.GetOwnProperties())
{
yield return entry;
}
}
public override PropertyDescriptor GetOwnProperty(in Key propertyName)
{
if (propertyName == KnownKeys.Prototype)
{
return _prototype ?? PropertyDescriptor.Undefined;
}
if (propertyName == KnownKeys.Length)
{
return _length ?? PropertyDescriptor.Undefined;
}
if (propertyName == KnownKeys.Name)
{
return !(_name is null)
? _nameDescriptor ?? (_nameDescriptor = new PropertyDescriptor(_name, PropertyFlag.Configurable))
: PropertyDescriptor.Undefined;
}
return base.GetOwnProperty(propertyName);
}
protected internal override void SetOwnProperty(in Key propertyName, PropertyDescriptor desc)
{
if (propertyName == KnownKeys.Prototype)
{
_prototype = desc;
}
else if (propertyName == KnownKeys.Length)
{
_length = desc;
}
else if (propertyName == KnownKeys.Name)
{
_name = desc._value;
_nameDescriptor = desc;
}
else
{
base.SetOwnProperty(propertyName, desc);
}
}
public override bool HasOwnProperty(in Key propertyName)
{
if (propertyName == KnownKeys.Prototype)
{
return _prototype != null;
}
if (propertyName == KnownKeys.Length)
{
return _length != null;
}
if (propertyName == KnownKeys.Name)
{
return !(_name is null);
}
return base.HasOwnProperty(propertyName);
}
public override void RemoveOwnProperty(in Key propertyName)
{
if (propertyName == KnownKeys.Prototype)
{
_prototype = null;
}
if (propertyName == KnownKeys.Length)
{
_length = null;
}
if (propertyName == KnownKeys.Name)
{
_name = null;
_nameDescriptor = null;
}
base.RemoveOwnProperty(propertyName);
}
internal void SetFunctionName(in Key key, bool throwIfExists = false)
{
if (_name is null)
{
_name = key.Name;
}
else if (throwIfExists)
{
ExceptionHelper.ThrowError(_engine, "cannot set name");
}
}
}
}