using System.Diagnostics;
using System.Runtime.CompilerServices;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Environments;
using Jint.Runtime.Interpreter;
using Environment = Jint.Runtime.Environments.Environment;
namespace Jint.Native.Function;
[DebuggerDisplay("{ToString(),nq}")]
#pragma warning disable MA0049
public abstract partial class Function : ObjectInstance, ICallable
#pragma warning restore MA0049
{
protected PropertyDescriptor? _prototypeDescriptor;
protected internal PropertyDescriptor? _length;
internal PropertyDescriptor? _nameDescriptor;
internal Environment? _environment;
internal readonly JintFunctionDefinition? _functionDefinition;
internal readonly FunctionThisMode _thisMode;
internal JsValue _homeObject = Undefined;
internal ConstructorKind _constructorKind = ConstructorKind.Base;
internal Realm _realm;
internal PrivateEnvironment? _privateEnvironment;
private readonly IScriptOrModule? _scriptOrModule;
protected Function(
Engine engine,
Realm realm,
JsString? name)
: this(engine, realm, name, FunctionThisMode.Global)
{
}
internal Function(
Engine engine,
Realm realm,
JintFunctionDefinition function,
Environment env,
FunctionThisMode thisMode)
: this(
engine,
realm,
!string.IsNullOrWhiteSpace(function.Name) ? new JsString(function.Name!) : null,
thisMode)
{
_functionDefinition = function;
_environment = env;
}
internal Function(
Engine engine,
Realm realm,
JsString? name,
FunctionThisMode thisMode = FunctionThisMode.Global)
: base(engine, ObjectClass.Function)
{
if (name is not null)
{
_nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable);
}
_realm = realm;
_thisMode = thisMode;
_scriptOrModule = _engine.GetActiveScriptOrModule();
}
// for example RavenDB wants to inspect this
public IFunction? FunctionDeclaration => _functionDefinition?.Function;
internal override bool IsCallable => true;
JsValue ICallable.Call(JsValue thisObject, params JsCallArguments arguments) => Call(thisObject, arguments);
///
/// Executed when a function object is used as a function
///
protected internal abstract JsValue Call(JsValue thisObject, JsCallArguments 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;
}
}
internal sealed override IEnumerable GetInitialOwnStringPropertyKeys()
{
if (_length != null)
{
yield return CommonProperties.Length;
}
if (_nameDescriptor != null)
{
yield return CommonProperties.Name;
}
if (_prototypeDescriptor != null)
{
yield return CommonProperties.Prototype;
}
}
public override PropertyDescriptor GetOwnProperty(JsValue property)
{
if (CommonProperties.Prototype.Equals(property))
{
return _prototypeDescriptor ?? PropertyDescriptor.Undefined;
}
if (CommonProperties.Length.Equals(property))
{
return _length ?? PropertyDescriptor.Undefined;
}
if (CommonProperties.Name.Equals(property))
{
return _nameDescriptor ?? PropertyDescriptor.Undefined;
}
return base.GetOwnProperty(property);
}
protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
{
if (CommonProperties.Prototype.Equals(property))
{
_prototypeDescriptor = desc;
}
else if (CommonProperties.Length.Equals(property))
{
_length = desc;
}
else if (CommonProperties.Name.Equals(property))
{
_nameDescriptor = desc;
}
else
{
base.SetOwnProperty(property, desc);
}
}
public override void RemoveOwnProperty(JsValue property)
{
if (CommonProperties.Prototype.Equals(property))
{
_prototypeDescriptor = null;
}
if (CommonProperties.Length.Equals(property))
{
_length = null;
}
if (CommonProperties.Name.Equals(property))
{
_nameDescriptor = null;
}
base.RemoveOwnProperty(property);
}
///
/// https://tc39.es/ecma262/#sec-setfunctionname
///
internal void SetFunctionName(JsValue name, string? prefix = null, bool force = false)
{
if (!force && _nameDescriptor != null && UnwrapJsValue(_nameDescriptor) != JsString.Empty)
{
return;
}
if (name is JsSymbol symbol)
{
name = symbol._value.IsUndefined()
? JsString.Empty
: new JsString("[" + symbol._value + "]");
}
else if (name is PrivateName privateName)
{
name = "#" + privateName.Description;
}
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,
TState? state = default) 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)
{
if (constructor.Get(CommonProperties.Prototype) is not ObjectInstance proto)
{
var realm = GetFunctionRealm(constructor);
proto = intrinsicDefaultProto(realm.Intrinsics);
}
return proto;
}
///
/// https://tc39.es/ecma262/#sec-getfunctionrealm
///
internal Realm GetFunctionRealm(JsValue obj)
{
if (obj is Function functionInstance && functionInstance._realm is not null)
{
return functionInstance._realm;
}
if (obj is BindFunction bindFunctionInstance)
{
return GetFunctionRealm(bindFunctionInstance.BoundTargetFunction);
}
if (obj is JsProxy proxyInstance)
{
if (proxyInstance._handler is null)
{
Throw.TypeErrorNoEngine();
}
return GetFunctionRealm(proxyInstance._target);
}
return _engine.ExecutionContext.Realm;
}
///
/// https://tc39.es/ecma262/#sec-makemethod
///
internal void MakeMethod(ObjectInstance homeObject)
{
_homeObject = homeObject;
}
///
/// https://tc39.es/ecma262/#sec-ordinarycallbindthis
///
internal void OrdinaryCallBindThis(in ExecutionContext calleeContext, JsValue thisArgument)
{
if (_thisMode == FunctionThisMode.Lexical)
{
return;
}
var calleeRealm = _realm;
var localEnv = (FunctionEnvironment) calleeContext.LexicalEnvironment;
JsValue thisValue;
if (_thisMode == FunctionThisMode.Strict)
{
thisValue = thisArgument;
}
else
{
if (thisArgument is null || thisArgument.IsNullOrUndefined())
{
var globalEnv = calleeRealm.GlobalEnv;
thisValue = globalEnv.GlobalThisValue;
}
else
{
thisValue = TypeConverter.ToObject(calleeRealm, thisArgument);
}
}
localEnv.BindThisValue(thisValue);
}
///
/// https://tc39.es/ecma262/#sec-prepareforordinarycall
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref readonly ExecutionContext PrepareForOrdinaryCall(JsValue newTarget)
{
var callerContext = _engine.ExecutionContext;
var localEnv = JintEnvironment.NewFunctionEnvironment(_engine, this, newTarget);
var calleeRealm = _realm;
var calleeContext = new ExecutionContext(
_scriptOrModule,
lexicalEnvironment: localEnv,
variableEnvironment: localEnv,
_privateEnvironment,
calleeRealm,
generator: null,
function: 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.
_engine.EnterExecutionContext(calleeContext);
return ref _engine.ExecutionContext;
}
internal void MakeConstructor(bool writableProperty = true, ObjectInstance? prototype = null)
{
_constructorKind = ConstructorKind.Base;
if (prototype is null)
{
prototype = new ObjectInstanceWithConstructor(_engine, this)
{
_prototype = _realm.Intrinsics.Object.PrototypeObject
};
}
_prototypeDescriptor = new PropertyDescriptor(prototype, writableProperty, enumerable: false, configurable: false);
}
internal void SetFunctionLength(JsNumber length)
{
DefinePropertyOrThrow(CommonProperties.Length, new PropertyDescriptor(length, writable: false, enumerable: false, configurable: true));
}
// native syntax doesn't expect to have private identifier indicator
private static readonly char[] _functionNameTrimStartChars = ['#'];
public sealed override object ToObject()
{
return (JsCallDelegate) Call;
}
public override string ToString()
{
if (_functionDefinition?.Function is Node node && _engine.Options.Host.FunctionToStringHandler(this, node) is { } s)
{
return s;
}
var nameValue = _nameDescriptor != null ? UnwrapJsValue(_nameDescriptor) : JsString.Empty;
var name = "";
if (!nameValue.IsUndefined())
{
name = TypeConverter.ToString(nameValue);
}
name = name.TrimStart(_functionNameTrimStartChars);
return $"function {name}() {{ [native code] }}";
}
private sealed class ObjectInstanceWithConstructor : ObjectInstance
{
private PropertyDescriptor? _constructor;
public ObjectInstanceWithConstructor(Engine engine, ObjectInstance thisObj) : base(engine)
{
_constructor = new PropertyDescriptor(thisObj, PropertyFlag.NonEnumerable);
}
public override IEnumerable> GetOwnProperties()
{
if (_constructor != null)
{
yield return new KeyValuePair(CommonProperties.Constructor, _constructor);
}
foreach (var entry in base.GetOwnProperties())
{
yield return entry;
}
}
public override PropertyDescriptor GetOwnProperty(JsValue property)
{
if (CommonProperties.Constructor.Equals(property))
{
return _constructor ?? PropertyDescriptor.Undefined;
}
return base.GetOwnProperty(property);
}
protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
{
if (CommonProperties.Constructor.Equals(property))
{
_constructor = desc;
}
else
{
base.SetOwnProperty(property, desc);
}
}
public override void RemoveOwnProperty(JsValue property)
{
if (CommonProperties.Constructor.Equals(property))
{
_constructor = null;
}
else
{
base.RemoveOwnProperty(property);
}
}
}
}