using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Esprima.Ast;
using Jint.Native.Object;
using Jint.Native.Proxy;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Environments;
using Jint.Runtime.Interpreter;
namespace Jint.Native.Function
{
public abstract class FunctionInstance : ObjectInstance, ICallable
{
protected PropertyDescriptor _prototypeDescriptor;
protected internal PropertyDescriptor _length;
private PropertyDescriptor _nameDescriptor;
protected internal EnvironmentRecord _environment;
internal readonly JintFunctionDefinition _functionDefinition;
internal readonly FunctionThisMode _thisMode;
internal JsValue _homeObject = Undefined;
internal ConstructorKind _constructorKind = ConstructorKind.Base;
internal Realm _realm;
private PrivateEnvironmentRecord _privateEnvironment;
protected FunctionInstance(
Engine engine,
Realm realm,
JsString name)
: this(engine, realm, name, FunctionThisMode.Global, ObjectClass.Function)
{
}
internal FunctionInstance(
Engine engine,
Realm realm,
JintFunctionDefinition function,
EnvironmentRecord scope,
FunctionThisMode thisMode)
: this(
engine,
realm,
!string.IsNullOrWhiteSpace(function.Name) ? new JsString(function.Name) : null,
thisMode)
{
_functionDefinition = function;
_environment = scope;
}
internal FunctionInstance(
Engine engine,
Realm realm,
JsString name,
FunctionThisMode thisMode = FunctionThisMode.Global,
ObjectClass objectClass = ObjectClass.Function)
: base(engine, objectClass)
{
if (name is not null)
{
_nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable);
}
_realm = realm;
_thisMode = thisMode;
}
// for example RavenDB wants to inspect this
public IFunction FunctionDeclaration => _functionDefinition.Function;
///
/// Executed when a function object is used as a function
///
///
///
///
public abstract JsValue Call(JsValue thisObject, JsValue[] arguments);
public bool Strict => _thisMode == FunctionThisMode.Strict;
internal override bool IsConstructor => this is IConstructor;
public override IEnumerable> GetOwnProperties()
{
if (_prototypeDescriptor != null)
{
yield return new KeyValuePair(CommonProperties.Prototype, _prototypeDescriptor);
}
if (_length != null)
{
yield return new KeyValuePair(CommonProperties.Length, _length);
}
if (_nameDescriptor != null)
{
yield return new KeyValuePair(CommonProperties.Name, GetOwnProperty(CommonProperties.Name));
}
foreach (var entry in base.GetOwnProperties())
{
yield return entry;
}
}
public override List GetOwnPropertyKeys(Types types)
{
var keys = base.GetOwnPropertyKeys(types);
// works around a problem where we don't use property for function names and classes should report it last
// as it's the last operation when creating a class constructor
if ((types & Types.String) != 0 && _nameDescriptor != null && this is ScriptFunctionInstance { _isClassConstructor: true })
{
keys.Add(CommonProperties.Name);
}
return keys;
}
internal override IEnumerable GetInitialOwnStringPropertyKeys()
{
if (_length != null)
{
yield return CommonProperties.Length;
}
// works around a problem where we don't use property for function names and classes should report it last
// as it's the last operation when creating a class constructor
if (_nameDescriptor != null && this is not ScriptFunctionInstance { _isClassConstructor: true })
{
yield return CommonProperties.Name;
}
if (_prototypeDescriptor != null)
{
yield return CommonProperties.Prototype;
}
}
public override PropertyDescriptor GetOwnProperty(JsValue property)
{
if (property == CommonProperties.Prototype)
{
return _prototypeDescriptor ?? PropertyDescriptor.Undefined;
}
if (property == CommonProperties.Length)
{
return _length ?? PropertyDescriptor.Undefined;
}
if (property == CommonProperties.Name)
{
return _nameDescriptor ?? PropertyDescriptor.Undefined;
}
return base.GetOwnProperty(property);
}
protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
{
if (property == CommonProperties.Prototype)
{
_prototypeDescriptor = desc;
}
else if (property == CommonProperties.Length)
{
_length = desc;
}
else if (property == CommonProperties.Name)
{
_nameDescriptor = desc;
}
else
{
base.SetOwnProperty(property, desc);
}
}
public override bool HasOwnProperty(JsValue property)
{
if (property == CommonProperties.Prototype)
{
return _prototypeDescriptor != null;
}
if (property == CommonProperties.Length)
{
return _length != null;
}
if (property == CommonProperties.Name)
{
return _nameDescriptor != null;
}
return base.HasOwnProperty(property);
}
public override void RemoveOwnProperty(JsValue property)
{
if (property == CommonProperties.Prototype)
{
_prototypeDescriptor = null;
}
if (property == CommonProperties.Length)
{
_length = null;
}
if (property == CommonProperties.Name)
{
_nameDescriptor = null;
}
base.RemoveOwnProperty(property);
}
internal void SetFunctionName(JsValue name, string prefix = null, bool force = false)
{
if (!force && _nameDescriptor != null && !UnwrapJsValue(_nameDescriptor).IsUndefined())
{
return;
}
if (name is JsSymbol symbol)
{
name = symbol._value.IsUndefined()
? JsString.Empty
: new JsString("[" + symbol._value + "]");
}
if (!string.IsNullOrWhiteSpace(prefix))
{
name = prefix + " " + name;
}
_nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable);
}
///
/// https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor
///
///
/// Uses separate builder to get correct type with state support to prevent allocations.
/// In spec intrinsicDefaultProto is string pointing to intrinsic, but we do a selector.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal T OrdinaryCreateFromConstructor(
JsValue constructor,
Func intrinsicDefaultProto,
Func objectCreator,
JsValue state = null) where T : ObjectInstance
{
var proto = GetPrototypeFromConstructor(constructor, intrinsicDefaultProto);
var obj = objectCreator(_engine, _realm, state);
obj._prototype = proto;
return obj;
}
///
/// https://tc39.es/ecma262/#sec-getprototypefromconstructor
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ObjectInstance GetPrototypeFromConstructor(JsValue constructor, Func intrinsicDefaultProto)
{
var proto = constructor.Get(CommonProperties.Prototype, constructor) as ObjectInstance;
if (proto is null)
{
var realm = GetFunctionRealm(constructor);
proto = intrinsicDefaultProto(realm.Intrinsics);
}
return proto;
}
///
/// https://tc39.es/ecma262/#sec-getfunctionrealm
///
internal Realm GetFunctionRealm(JsValue obj)
{
if (obj is FunctionInstance functionInstance && functionInstance._realm is not null)
{
return functionInstance._realm;
}
if (obj is BindFunctionInstance bindFunctionInstance)
{
return GetFunctionRealm(bindFunctionInstance.TargetFunction);
}
if (obj is ProxyInstance proxyInstance)
{
if (proxyInstance._handler is null)
{
ExceptionHelper.ThrowTypeErrorNoEngine();
}
return GetFunctionRealm(proxyInstance._target);
}
return _engine.ExecutionContext.Realm;
}
internal void MakeMethod(ObjectInstance homeObject)
{
_homeObject = homeObject;
}
///
/// https://tc39.es/ecma262/#sec-ordinarycallbindthis
///
internal void OrdinaryCallBindThis(ExecutionContext calleeContext, JsValue thisArgument)
{
var thisMode = _thisMode;
if (thisMode == FunctionThisMode.Lexical)
{
return;
}
var calleeRealm = _realm;
var localEnv = (FunctionEnvironmentRecord) calleeContext.LexicalEnvironment;
JsValue thisValue;
if (_thisMode == FunctionThisMode.Strict)
{
thisValue = thisArgument;
}
else
{
if (thisArgument.IsNullOrUndefined())
{
var globalEnv = calleeRealm.GlobalEnv;
thisValue = globalEnv.GlobalThisValue;
}
else
{
thisValue = TypeConverter.ToObject(calleeRealm, thisArgument);
}
}
localEnv.BindThisValue(thisValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Completion OrdinaryCallEvaluateBody(
EvaluationContext context,
JsValue[] arguments,
ExecutionContext calleeContext)
{
var argumentsInstance = _engine.FunctionDeclarationInstantiation(
functionInstance: this,
arguments);
var result = _functionDefinition.Execute(context);
argumentsInstance?.FunctionWasCalled();
return result;
}
///
/// https://tc39.es/ecma262/#sec-prepareforordinarycall
///
internal ExecutionContext PrepareForOrdinaryCall(JsValue newTarget)
{
var callerContext = _engine.ExecutionContext;
var localEnv = JintEnvironment.NewFunctionEnvironment(_engine, this, newTarget);
var calleeRealm = _realm;
var calleeContext = new ExecutionContext(
localEnv,
localEnv,
_privateEnvironment,
calleeRealm,
this);
// If callerContext is not already suspended, suspend callerContext.
// Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
// NOTE: Any exception objects produced after this point are associated with calleeRealm.
// Return calleeContext.
return _engine.EnterExecutionContext(calleeContext);
}
public override string ToString()
{
// TODO no way to extract SourceText from Esprima at the moment, just returning native code
var nameValue = _nameDescriptor != null ? UnwrapJsValue(_nameDescriptor) : JsString.Empty;
var name = "";
if (!nameValue.IsUndefined())
{
name = TypeConverter.ToString(nameValue);
}
return "function " + name + "() { [native code] }";
}
}
}