|
@@ -1,14 +1,15 @@
|
|
|
-using System;
|
|
|
-using System.Collections.Generic;
|
|
|
- using System.Runtime.CompilerServices;
|
|
|
- using Esprima.Ast;
|
|
|
+using System;
|
|
|
+using System.Runtime.CompilerServices;
|
|
|
+using Esprima.Ast;
|
|
|
using Jint.Collections;
|
|
|
using Jint.Native;
|
|
|
using Jint.Native.Argument;
|
|
|
+using Jint.Native.Array;
|
|
|
using Jint.Native.Function;
|
|
|
- using Jint.Runtime.Interpreter.Expressions;
|
|
|
+using Jint.Native.Iterator;
|
|
|
+using Jint.Runtime.Interpreter.Expressions;
|
|
|
|
|
|
- namespace Jint.Runtime.Environments
|
|
|
+namespace Jint.Runtime.Environments
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// Represents a declarative environment record
|
|
@@ -255,136 +256,211 @@ using Jint.Native.Function;
|
|
|
return keys;
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Optimized version for function calls.
|
|
|
- /// </summary>
|
|
|
internal void AddFunctionParameters(
|
|
|
FunctionInstance functionInstance,
|
|
|
JsValue[] arguments,
|
|
|
ArgumentsInstance argumentsInstance,
|
|
|
IFunction functionDeclaration)
|
|
|
{
|
|
|
- var parameters = functionInstance._formalParameters;
|
|
|
+ var parameters = functionDeclaration.Params;
|
|
|
+
|
|
|
bool empty = _dictionary == null && !_set;
|
|
|
- if (empty && parameters.Length == 1 && parameters[0].Length != BindingNameArguments.Length)
|
|
|
- {
|
|
|
- var jsValue = arguments.Length == 0 ? Undefined : arguments[0];
|
|
|
- jsValue = HandleAssignmentPatternIfNeeded(functionDeclaration, jsValue, 0);
|
|
|
- jsValue = HandleRestPatternIfNeeded(_engine, functionDeclaration, arguments, 0, jsValue);
|
|
|
- HandleObjectPatternIfNeeded(_engine, functionDeclaration, jsValue, 0);
|
|
|
|
|
|
- var binding = new Binding(jsValue, false, true);
|
|
|
- _set = true;
|
|
|
- _key = parameters[0];
|
|
|
- _value = binding;
|
|
|
- }
|
|
|
- else
|
|
|
+ if (ReferenceEquals(_argumentsBinding.Value, null)
|
|
|
+ && !(functionInstance is ArrowFunctionInstance))
|
|
|
{
|
|
|
- AddMultipleParameters(arguments, parameters, functionDeclaration);
|
|
|
+ _argumentsBinding = new Binding(argumentsInstance, canBeDeleted: false, mutable: true);
|
|
|
}
|
|
|
|
|
|
- if (ReferenceEquals(_argumentsBinding.Value, null))
|
|
|
+ for (var i = 0; i < parameters.Count; i++)
|
|
|
{
|
|
|
- _argumentsBinding = new Binding(argumentsInstance, canBeDeleted: false, mutable: true);
|
|
|
+ SetFunctionParameter(parameters[i], arguments, i, empty);
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
|
|
|
- private void AddMultipleParameters(JsValue[] arguments, string[] parameters, IFunction functionDeclaration)
|
|
|
+ private void SetFunctionParameter(
|
|
|
+ INode parameter,
|
|
|
+ JsValue[] arguments,
|
|
|
+ int index,
|
|
|
+ bool initiallyEmpty)
|
|
|
{
|
|
|
- bool empty = _dictionary == null && !_set;
|
|
|
- for (var i = 0; i < parameters.Length; i++)
|
|
|
+ var argument = arguments.Length > index ? arguments[index] : Undefined;
|
|
|
+
|
|
|
+ if (parameter is Identifier identifier)
|
|
|
+ {
|
|
|
+ SetItemSafely(identifier.Name, argument, initiallyEmpty);
|
|
|
+ }
|
|
|
+ else if (parameter is RestElement restElement)
|
|
|
{
|
|
|
- var argName = parameters[i];
|
|
|
- var jsValue = i + 1 > arguments.Length ? Undefined : arguments[i];
|
|
|
+ // index + 1 == parameters.count because rest is last
|
|
|
+ int restCount = arguments.Length - (index + 1) + 1;
|
|
|
+ uint count = restCount > 0 ? (uint) restCount : 0;
|
|
|
+
|
|
|
+ var rest = _engine.Array.ConstructFast(count);
|
|
|
+
|
|
|
+ uint targetIndex = 0;
|
|
|
+ for (var argIndex = index; argIndex < arguments.Length; ++argIndex)
|
|
|
+ {
|
|
|
+ rest.SetIndexValue(targetIndex++, arguments[argIndex], updateLength: false);
|
|
|
+ }
|
|
|
|
|
|
- jsValue = HandleAssignmentPatternIfNeeded(functionDeclaration, jsValue, i);
|
|
|
- if (i == parameters.Length - 1)
|
|
|
+ argument = rest;
|
|
|
+
|
|
|
+ if (restElement.Argument is Identifier restIdentifier)
|
|
|
+ {
|
|
|
+ SetItemSafely(restIdentifier.Name, argument, initiallyEmpty);
|
|
|
+ }
|
|
|
+ else if (restElement.Argument is BindingPattern bindingPattern)
|
|
|
+ {
|
|
|
+ SetFunctionParameter(bindingPattern, new [] { argument }, index, initiallyEmpty);
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- jsValue = HandleRestPatternIfNeeded(_engine, functionDeclaration, arguments, i, jsValue);
|
|
|
+ ExceptionHelper.ThrowSyntaxError(_engine, "Rest parameters can only be identifiers or arrays");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (parameter is ArrayPattern arrayPattern)
|
|
|
+ {
|
|
|
+ if (argument.IsNull())
|
|
|
+ {
|
|
|
+ ExceptionHelper.ThrowTypeError(_engine, "Destructed parameter is null");
|
|
|
}
|
|
|
- jsValue = HandleObjectPatternIfNeeded(_engine, functionDeclaration, jsValue, 0);
|
|
|
|
|
|
- if (empty || !TryGetValue(argName, out var existing))
|
|
|
+ ArrayInstance array = null;
|
|
|
+ var arrayContents = ArrayExt.Empty<JsValue>();
|
|
|
+ if (argument.IsArray())
|
|
|
{
|
|
|
- var binding = new Binding(jsValue, false, true);
|
|
|
- if (argName.Length == 9 && argName == BindingNameArguments)
|
|
|
- {
|
|
|
- _argumentsBinding = binding;
|
|
|
- }
|
|
|
- else
|
|
|
+ array = argument.AsArray();
|
|
|
+ }
|
|
|
+ else if (argument.IsObject() && argument.TryGetIterator(_engine, out var iterator))
|
|
|
+ {
|
|
|
+ array = _engine.Array.ConstructFast(0);
|
|
|
+ var protocol = new ArrayPatternProtocol(_engine, array, iterator, arrayPattern.Elements.Count);
|
|
|
+ protocol.Execute();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!ReferenceEquals(array, null))
|
|
|
+ {
|
|
|
+ arrayContents = new JsValue[array.Length];
|
|
|
+
|
|
|
+ for (uint contentsIndex = 0; contentsIndex < array.Length; contentsIndex++)
|
|
|
{
|
|
|
- SetItem(argName, binding);
|
|
|
+ arrayContents[contentsIndex] = array.Get(contentsIndex);
|
|
|
}
|
|
|
}
|
|
|
- else
|
|
|
+
|
|
|
+ for (uint arrayIndex = 0; arrayIndex < arrayPattern.Elements.Count; arrayIndex++)
|
|
|
+ {
|
|
|
+ SetFunctionParameter(arrayPattern.Elements[(int) arrayIndex], arrayContents, (int) arrayIndex, initiallyEmpty);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (parameter is ObjectPattern objectPattern)
|
|
|
+ {
|
|
|
+ if (argument.IsNullOrUndefined())
|
|
|
+ {
|
|
|
+ ExceptionHelper.ThrowTypeError(_engine, "Destructed parameter is null or undefined");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!argument.IsObject())
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var argumentObject = argument.AsObject();
|
|
|
+
|
|
|
+ var jsValues = _engine._jsValueArrayPool.RentArray(1);
|
|
|
+ foreach (var property in objectPattern.Properties)
|
|
|
{
|
|
|
- if (existing.Mutable)
|
|
|
+ if (property.Key is Identifier propertyIdentifier)
|
|
|
+ {
|
|
|
+ argument = argumentObject.Get(propertyIdentifier.Name);
|
|
|
+ }
|
|
|
+ else if (property.Key is Literal propertyLiteral)
|
|
|
{
|
|
|
- ref var b = ref GetExistingItem(argName);
|
|
|
- b.Value = jsValue;
|
|
|
+ argument = argumentObject.Get(propertyLiteral.Raw);
|
|
|
}
|
|
|
- else
|
|
|
+ else if (property.Key is CallExpression callExpression)
|
|
|
{
|
|
|
- ExceptionHelper.ThrowTypeError(_engine, "Can't update the value of an immutable binding.");
|
|
|
+ var jintCallExpression = JintExpression.Build(_engine, callExpression);
|
|
|
+ argument = argumentObject.Get(jintCallExpression.GetValue().AsString());
|
|
|
}
|
|
|
+
|
|
|
+ jsValues[0] = argument;
|
|
|
+ SetFunctionParameter(property.Value, jsValues, 0, initiallyEmpty);
|
|
|
}
|
|
|
+ _engine._jsValueArrayPool.ReturnArray(jsValues);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
- internal static JsValue HandleObjectPatternIfNeeded(Engine engine, IFunction functionDeclaration, JsValue jsValue, int index)
|
|
|
- {
|
|
|
- if (functionDeclaration.Params[index] is ObjectPattern op)
|
|
|
+ else if (parameter is AssignmentPattern assignmentPattern)
|
|
|
{
|
|
|
- if (jsValue.IsNullOrUndefined())
|
|
|
+ var idLeft = assignmentPattern.Left as Identifier;
|
|
|
+ if (idLeft != null
|
|
|
+ && assignmentPattern.Right is Identifier idRight
|
|
|
+ && idLeft.Name == idRight.Name)
|
|
|
{
|
|
|
- ExceptionHelper.ThrowTypeError(engine, "Cannot destructure 'undefined' or 'null'.");
|
|
|
+ ExceptionHelper.ThrowReferenceError(_engine, idRight.Name);
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- return jsValue;
|
|
|
- }
|
|
|
+ if (argument.IsUndefined())
|
|
|
+ {
|
|
|
+ JsValue RunInNewParameterEnvironment(JintExpression exp)
|
|
|
+ {
|
|
|
+ var oldEnv = _engine.ExecutionContext.LexicalEnvironment;
|
|
|
+ var paramVarEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, oldEnv);
|
|
|
|
|
|
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
- internal static JsValue HandleAssignmentPatternIfNeeded(IFunction functionDeclaration, JsValue jsValue, int index)
|
|
|
- {
|
|
|
- if (jsValue.IsUndefined()
|
|
|
- && index < functionDeclaration?.Params.Count
|
|
|
- && functionDeclaration.Params[index] is AssignmentPattern ap
|
|
|
- && ap.Right is Literal l)
|
|
|
- {
|
|
|
- return JintLiteralExpression.ConvertToJsValue(l);
|
|
|
- }
|
|
|
+ _engine.EnterExecutionContext(paramVarEnv, paramVarEnv, _engine.ExecutionContext.ThisBinding);;
|
|
|
+ var result = exp.GetValue();
|
|
|
+ _engine.LeaveExecutionContext();
|
|
|
|
|
|
- return jsValue;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ var expression = assignmentPattern.Right.As<Expression>();
|
|
|
+ var jintExpression = JintExpression.Build(_engine, expression);
|
|
|
+
|
|
|
+ argument = jintExpression is JintSequenceExpression
|
|
|
+ ? RunInNewParameterEnvironment(jintExpression)
|
|
|
+ : jintExpression.GetValue();
|
|
|
+
|
|
|
+ if (idLeft != null && assignmentPattern.Right.IsFunctionWithName())
|
|
|
+ {
|
|
|
+ ((FunctionInstance) argument).SetFunctionName(idLeft.Name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ SetFunctionParameter(assignmentPattern.Left, new []{ argument }, 0, initiallyEmpty);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
- private static JsValue HandleRestPatternIfNeeded(
|
|
|
- Engine engine,
|
|
|
- IFunction functionDeclaration,
|
|
|
- JsValue[] arguments,
|
|
|
- int index,
|
|
|
- JsValue defaultValue)
|
|
|
+ private void SetItemSafely(string name, JsValue argument, bool initiallyEmpty)
|
|
|
{
|
|
|
- if (index < functionDeclaration?.Params.Count
|
|
|
- && functionDeclaration.Params[index] is RestElement)
|
|
|
+ if (initiallyEmpty || !TryGetValue(name, out var existing))
|
|
|
{
|
|
|
- var count = (uint) (arguments.Length - functionDeclaration.Params.Count + 1);
|
|
|
- var rest = engine.Array.ConstructFast(count);
|
|
|
-
|
|
|
- uint targetIndex = 0;
|
|
|
- for (var i = index; i < arguments.Length; ++i)
|
|
|
+ var binding = new Binding(argument, false, true);
|
|
|
+ if (name.Length == 9 && name == BindingNameArguments)
|
|
|
+ {
|
|
|
+ _argumentsBinding = binding;
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- rest.SetIndexValue(targetIndex++, arguments[i], updateLength: false);
|
|
|
+ SetItem(name, binding);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (existing.Mutable)
|
|
|
+ {
|
|
|
+ ref var b = ref GetExistingItem(name);
|
|
|
+ b.Value = argument;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ExceptionHelper.ThrowTypeError(_engine, "Can't update the value of an immutable binding.");
|
|
|
}
|
|
|
- return rest;
|
|
|
}
|
|
|
-
|
|
|
- return defaultValue;
|
|
|
}
|
|
|
|
|
|
- internal void AddVariableDeclarations(ref Esprima.Ast.List<VariableDeclaration> variableDeclarations)
|
|
|
+ internal void AddVariableDeclarations(ref NodeList<VariableDeclaration> variableDeclarations)
|
|
|
{
|
|
|
var variableDeclarationsCount = variableDeclarations.Count;
|
|
|
for (var i = 0; i < variableDeclarationsCount; i++)
|
|
@@ -406,6 +482,21 @@ using Jint.Native.Function;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
+ internal static JsValue HandleAssignmentPatternIfNeeded(IFunction functionDeclaration, JsValue jsValue, int index)
|
|
|
+ {
|
|
|
+ // TODO remove this method, overwrite with above SetFunctionParameter logic
|
|
|
+ if (jsValue.IsUndefined()
|
|
|
+ && index < functionDeclaration?.Params.Count
|
|
|
+ && functionDeclaration.Params[index] is AssignmentPattern ap
|
|
|
+ && ap.Right is Literal l)
|
|
|
+ {
|
|
|
+ return JintLiteralExpression.ConvertToJsValue(l);
|
|
|
+ }
|
|
|
+
|
|
|
+ return jsValue;
|
|
|
+ }
|
|
|
|
|
|
internal override void FunctionWasCalled()
|
|
|
{
|
|
@@ -428,5 +519,40 @@ using Jint.Native.Function;
|
|
|
_argumentsBindingWasAccessed = null;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private sealed class ArrayPatternProtocol : IteratorProtocol
|
|
|
+ {
|
|
|
+ private readonly ArrayInstance _instance;
|
|
|
+ private readonly int _max;
|
|
|
+ private long _index = -1;
|
|
|
+
|
|
|
+ public ArrayPatternProtocol(
|
|
|
+ Engine engine,
|
|
|
+ ArrayInstance instance,
|
|
|
+ IIterator iterator,
|
|
|
+ int max) : base(engine, iterator, 0)
|
|
|
+ {
|
|
|
+ _instance = instance;
|
|
|
+ _max = max;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void ProcessItem(JsValue[] args, JsValue currentValue)
|
|
|
+ {
|
|
|
+ _index++;
|
|
|
+ var jsValue = ExtractValueFromIteratorInstance(currentValue);
|
|
|
+ _instance.SetIndexValue((uint) _index, jsValue, updateLength: false);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override bool ShouldContinue => _index < _max;
|
|
|
+
|
|
|
+ protected override void IterationEnd()
|
|
|
+ {
|
|
|
+ if (_index >= 0)
|
|
|
+ {
|
|
|
+ _instance.SetLength((uint) _index);
|
|
|
+ ReturnIterator();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
+}
|