using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Jint.Collections;
using Jint.Native;
using Jint.Native.Disposable;
namespace Jint.Runtime.Environments;
///
/// Represents a declarative environment record
/// https://tc39.es/ecma262/#sec-declarative-environment-records
///
internal class DeclarativeEnvironment : Environment
{
internal HybridDictionary? _dictionary;
internal readonly bool _catchEnvironment;
private DisposeCapability? _disposeCapability;
public DeclarativeEnvironment(Engine engine, bool catchEnvironment = false) : base(engine)
{
_catchEnvironment = catchEnvironment;
}
internal sealed override bool HasBinding(BindingName name) => HasBinding(name.Key);
internal sealed override bool HasBinding(Key name) => _dictionary is not null && _dictionary.ContainsKey(name);
internal override bool TryGetBinding(BindingName name, bool strict, [NotNullWhen(true)] out JsValue? value)
{
if (_dictionary?.TryGetValue(name.Key, out var binding) == true)
{
value = binding.Value;
return true;
}
value = null;
return false;
}
internal void CreateMutableBindingAndInitialize(Key name, bool canBeDeleted, JsValue value, DisposeHint hint)
{
_dictionary ??= new HybridDictionary();
_dictionary[name] = new Binding(value, canBeDeleted, mutable: true, strict: false);
if (hint != DisposeHint.Normal)
{
HandleDisposal(value, hint);
}
}
internal void CreateImmutableBindingAndInitialize(Key name, bool strict, JsValue value, DisposeHint hint)
{
_dictionary ??= new HybridDictionary();
_dictionary[name] = new Binding(value, canBeDeleted: false, mutable: false, strict);
if (hint != DisposeHint.Normal)
{
HandleDisposal(value, hint);
}
}
internal sealed override void CreateMutableBinding(Key name, bool canBeDeleted = false)
{
_dictionary ??= new HybridDictionary();
_dictionary.CreateMutableBinding(name, canBeDeleted);
}
internal sealed override void CreateImmutableBinding(Key name, bool strict = true)
{
_dictionary ??= new HybridDictionary();
_dictionary.CreateImmutableBinding(name, strict);
}
internal sealed override void InitializeBinding(Key name, JsValue value, DisposeHint hint)
{
_dictionary ??= new HybridDictionary();
_dictionary.SetOrUpdateValue(name, static (current, value) => current.ChangeValue(value), value);
if (hint != DisposeHint.Normal)
{
HandleDisposal(value, hint);
}
}
internal sealed override void SetMutableBinding(BindingName name, JsValue value, bool strict) => SetMutableBinding(name.Key, value, strict);
internal sealed override void SetMutableBinding(Key name, JsValue value, bool strict)
{
_dictionary ??= new HybridDictionary();
ref var binding = ref _dictionary.GetValueRefOrNullRef(name);
if (Unsafe.IsNullRef(ref binding))
{
if (strict)
{
Throw.ReferenceNameError(_engine.Realm, name);
}
_dictionary[name] = new Binding(value, canBeDeleted: true, mutable: true, strict: false);
return;
}
if (binding.Strict)
{
strict = true;
}
// Is it an uninitialized binding?
if (!binding.IsInitialized())
{
ThrowUninitializedBindingError(name);
}
if (binding.Mutable)
{
binding = binding.ChangeValue(value);
}
else
{
if (strict)
{
Throw.TypeError(_engine.Realm, "Assignment to constant variable.");
}
}
}
internal override JsValue GetBindingValue(Key name, bool strict)
{
if (_dictionary is not null && _dictionary.TryGetValue(name, out var binding) && binding.IsInitialized())
{
return binding.Value;
}
ThrowUninitializedBindingError(name);
return null!;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void ThrowUninitializedBindingError(Key name)
{
Throw.ReferenceError(_engine.Realm, $"Cannot access '{name}' before initialization");
}
internal sealed override bool DeleteBinding(Key name)
{
if (_dictionary is null || !_dictionary.TryGetValue(name, out var binding))
{
return true;
}
if (!binding.CanBeDeleted)
{
return false;
}
_dictionary.Remove(name);
return true;
}
internal override bool HasThisBinding() => false;
internal override bool HasSuperBinding() => false;
internal sealed override JsValue WithBaseObject() => Undefined;
internal sealed override bool HasBindings() => _dictionary?.Count > 0;
///
internal sealed override string[] GetAllBindingNames()
{
if (_dictionary is null)
{
return [];
}
var keys = new string[_dictionary.Count];
var n = 0;
foreach (var entry in _dictionary)
{
keys[n++] = entry.Key;
}
return keys;
}
internal override JsValue GetThisBinding() => Undefined;
internal sealed override Completion DisposeResources(Completion c) => _disposeCapability?.DisposeResources(c) ?? c;
public void Clear()
{
_dictionary = null;
}
internal void TransferTo(List names, DeclarativeEnvironment env)
{
var source = _dictionary!;
var target = env._dictionary ??= new HybridDictionary(names.Count, checkExistingKeys: true);
for (var j = 0; j < names.Count; j++)
{
var bn = names[j];
source.TryGetValue(bn, out var lastValue);
target[bn] = new Binding(lastValue.Value, canBeDeleted: false, mutable: true, strict: false);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void HandleDisposal(JsValue value, DisposeHint hint)
{
_disposeCapability ??= new DisposeCapability(_engine);
_disposeCapability.AddDisposableResource(value, hint);
}
}
internal static class DictionaryExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void CreateMutableBinding(this T dictionary, Key name, bool canBeDeleted = false) where T : IEngineDictionary
{
dictionary[name] = new Binding(null!, canBeDeleted, mutable: true, strict: false);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void CreateImmutableBinding(this T dictionary, Key name, bool strict = true) where T : IEngineDictionary
{
dictionary[name] = new Binding(null!, canBeDeleted: false, mutable: false, strict);
}
}