diff --git a/Jint.Tests/Runtime/AsyncDelegateTests.cs b/Jint.Tests/Runtime/AsyncDelegateTests.cs new file mode 100644 index 0000000000..ff6299d5c4 --- /dev/null +++ b/Jint.Tests/Runtime/AsyncDelegateTests.cs @@ -0,0 +1,502 @@ +using Jint.Runtime; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Jint.Tests.Runtime +{ + public class AsyncDelegateTests + { + public class TestMethods + { + public class TestClass + { + public string Name { get; set; } + } + + public class TestStruct + { + public string Name { get; set; } + } + + public static Dictionary ExpectedMethodResults = new Dictionary + { + { nameof(GetAsyncObject), nameof(GetAsyncObject) }, + { nameof(GetAsyncVoid), null }, + { nameof(GetSynchronousObject), nameof(GetSynchronousObject) }, + { nameof(GetSynchronousVoid), null }, + { nameof(GetAsyncDouble), 42d }, + { nameof(GetAsyncTestClass), new TestClass { Name = "MyTestClass" } }, + { nameof(GetAsyncTestStruct), new TestStruct { Name = "MyTestStruct" } }, + { nameof(GetAsyncDate), DateTime.Parse("1111-11-11 11:11:11").ToUniversalTime() }, + { nameof(GetAsyncEcho), "Echo..." } + }; + + private const int _delay = 5; + + public string MethodInvoked { get; private set; } + + public async Task GetAsyncDate() + { + await Task.Delay(_delay).ConfigureAwait(true); + MethodInvoked = nameof(GetAsyncDate); + return (DateTime)ExpectedMethodResults[MethodInvoked]; + } + + public async Task GetAsyncDouble() + { + await Task.Delay(_delay).ConfigureAwait(true); + MethodInvoked = nameof(GetAsyncDouble); + return (double)ExpectedMethodResults[MethodInvoked]; + } + + public async Task GetAsyncEcho(string echo = "Echo...") + { + await Task.Delay(_delay).ConfigureAwait(true); + MethodInvoked = nameof(GetAsyncEcho); + return (string)ExpectedMethodResults[MethodInvoked]; + } + + public async Task GetAsyncObject() + { + await Task.Delay(_delay).ConfigureAwait(true); + MethodInvoked = nameof(GetAsyncObject); + return (object)ExpectedMethodResults[MethodInvoked]; + } + + public async Task GetAsyncSameInt(int value) + { + await Task.Delay(_delay).ConfigureAwait(true); + return value; + } + + public async Task GetAsyncSameString(string value) + { + await Task.Delay(_delay).ConfigureAwait(true); + return value; + } + + public async Task GetAsyncTestClass() + { + await Task.Delay(_delay).ConfigureAwait(true); + MethodInvoked = nameof(GetAsyncTestClass); + return (TestClass)ExpectedMethodResults[MethodInvoked]; + } + + public async Task GetAsyncTestStruct() + { + await Task.Delay(_delay).ConfigureAwait(true); + MethodInvoked = nameof(GetAsyncTestStruct); + return (TestStruct)ExpectedMethodResults[MethodInvoked]; + } + + public async Task GetAsyncVoid() + { + await Task.Delay(_delay); + MethodInvoked = nameof(GetAsyncVoid); + } + + public object GetSynchronousObject() + { + Task.Delay(_delay).Wait(); + MethodInvoked = nameof(GetSynchronousObject); + return ExpectedMethodResults[MethodInvoked]; + } + + public void GetSynchronousVoid() + { + Task.Delay(_delay).Wait(); + MethodInvoked = nameof(GetSynchronousVoid); + } + } + + public static async Task ReturnAsync(object input) + { + await Task.Delay(20).ConfigureAwait(true); + return input; + } + + [Fact] + public async void ClrMethodAreAwaited() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + engine.SetValue("testMethods", testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"testMethods.{methodName}();"; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void ClrMethodAsFunctionArgumentIsAwaited() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + engine.SetValue("testMethods", testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $@" + function echo(arg) {{ + return arg; + }} + var result = echo(testMethods.{methodName}()); + return result; + "; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void ClrMethodAssignmentsAreAwaited() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + engine.SetValue("testMethods", testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"var result = testMethods.{methodName}(); return result;"; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void ClrMethodAssignToIdentifierIsAwaited() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + engine.SetValue("testMethods", testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $@" + var result = null; + result = testMethods.{methodName}(); + return result; + "; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void ClrMethodAssignToThisIsAwaited() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + engine.SetValue("testMethods", testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $@" + this.result = testMethods.{methodName}(); + return this.result; + "; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void ClrMethodHandlesImmediateExceptions() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + engine.SetValue("badMethod", new Func(() => throw new Exception("MyException"))); + + var script = "badMethod();"; + + await Assert.ThrowsAsync(async () => + { + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + }); + } + + [Fact] + public async void ClrMethodHandlesLateExceptions() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + engine.SetValue("badMethod", new Func(async () => + { + await Task.Delay(200); + throw new Exception("MyException"); + })); + + var script = "badMethod();"; + + await Assert.ThrowsAsync(async () => + { + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + }); + } + + [Fact] + public async void DelegateAsArrayIndexIsAwaited() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + SetupClrDelegates(engine, testMethods); + // Delegate calculates an array index + engine.SetValue(nameof(TestMethods.GetAsyncSameInt), new Func>(async s => await testMethods.GetAsyncSameInt(s))); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"var index = 10; var arr = []; arr[GetAsyncSameInt(index)] = {methodName}(); arr[index]"; + //var script = $"var index = 10; var arr = []; index2 = GetAsyncSameInt(index); arr[index2] = {methodName}(); arr[index]"; + + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void DelegateAsObjectPropertyNameIsAwaited() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + SetupClrDelegates(engine, testMethods); + // Delegate calculates an object property name + engine.SetValue(nameof(TestMethods.GetAsyncSameString), new Func>(async s => await testMethods.GetAsyncSameString(s))); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"var key = 'somekey'; var obj = {{}}; obj[GetAsyncSameString(key)] = {methodName}(); obj[key]"; + //var script = $"var key = 'somekey'; var obj = {{}}; key2 = GetAsyncSameString(key);obj[key2] = {methodName}(); obj[key]"; + + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void DelegatesAreAwaited() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + SetupClrDelegates(engine, testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"{methodName}()"; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void DelegatesAreAwaitedInsideDoWhile() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + SetupClrDelegates(engine, testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"var result = null; do {{ result = {methodName}(); }} while (false); result;"; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void DelegatesAreAwaitedInsideFor() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + SetupClrDelegates(engine, testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"for (var i = 0; i < 1; i++) {{ var result = {methodName}() }}; result;"; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void DelegatesAreAwaitedInsideFunction() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + SetupClrDelegates(engine, testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"function foo() {{ return {methodName}(); }} foo();"; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void DelegatesAreAwaitedInsideIfStatement() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + SetupClrDelegates(engine, testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"if (true) {{ {methodName}() }} else {{ {methodName}() }}"; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void DelegatesAreAwaitedInsideSwitch() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + SetupClrDelegates(engine, testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"var opt = 1; switch (opt) {{ case 1: var result = {methodName}(); break; default: result = null; break; }}; result;"; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + [Fact] + public async void DelegatesAreAwaitedInsideWhile() + { + var engine = new Engine(); + + var testMethods = new TestMethods(); + SetupClrDelegates(engine, testMethods); + + var methodNames = TestMethods.ExpectedMethodResults.Keys; + + foreach (var methodName in methodNames) + { + var script = $"var finish = false; var result = null; while (!finish) {{ finish = true; result = {methodName}(); }}; result;"; + var result = (await engine.ExecuteAsync(script)) + .GetCompletionValue().ToObject(); + + var expected = TestMethods.ExpectedMethodResults[methodName]; + Assert.Equal(expected, result); + Assert.Equal(methodName, testMethods.MethodInvoked); + } + } + + private static void SetupClrDelegates(Engine engine, TestMethods testMethods) + { + engine + .SetValue(nameof(TestMethods.GetAsyncDouble), new Func>(async () => await testMethods.GetAsyncDouble())) + .SetValue(nameof(TestMethods.GetAsyncObject), new Func>(async () => await testMethods.GetAsyncObject())) + .SetValue(nameof(TestMethods.GetAsyncVoid), new Func(async () => await testMethods.GetAsyncVoid())) + .SetValue(nameof(TestMethods.GetSynchronousObject), new Func(() => testMethods.GetSynchronousObject())) + .SetValue(nameof(TestMethods.GetSynchronousVoid), new Action(() => testMethods.GetSynchronousVoid())) + .SetValue(nameof(TestMethods.GetAsyncTestClass), new Func>(async () => await testMethods.GetAsyncTestClass())) + .SetValue(nameof(TestMethods.GetAsyncTestStruct), new Func>(async () => await testMethods.GetAsyncTestStruct())) + .SetValue(nameof(TestMethods.GetAsyncDate), new Func>(async () => await testMethods.GetAsyncDate())) + .SetValue(nameof(TestMethods.GetAsyncEcho), new Func>(async s => await testMethods.GetAsyncEcho(s))); + } + } +} \ No newline at end of file diff --git a/Jint.Tests/Runtime/AsyncTests.cs b/Jint.Tests/Runtime/AsyncTests.cs new file mode 100644 index 0000000000..ee059cf06c --- /dev/null +++ b/Jint.Tests/Runtime/AsyncTests.cs @@ -0,0 +1,203 @@ +using Jint.Native; +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Jint.Tests.Runtime +{ + /// + /// Setup new async tests here, and call echoAsync to return a result that matches the input, asynchroneosly. + /// Debug by setting a breakpoint in EchoAsync and run the test. + /// When the breakpoint hits, check the call stack and see where the Async execution path was swithed over to the synchroneos code path. + /// + public class AsyncTests + { + private static async Task EchoAsync(object input) + { + await Task.Delay(1).ConfigureAwait(true); + return input; + } + + private static async Task RunTest(string expression) + { + var engine = new Engine(); + engine.SetValue("echoAsync", new Func>(obj => EchoAsync(obj))); + return (await engine.ExecuteAsync(expression)).GetCompletionValue(); + } + + [Fact] + public async void GetPropertyIsAwaited() + { + var code = @" + var obj = {}; + Object.defineProperty(obj, 'value', { + get: function() { return echoAsync(42) }, + }); + return obj.value; + "; + + var result = await RunTest(code); + + Assert.Equal(42, result); + } + + [Fact] + public async void SpreadParametersAreAwaited() + { + var code = @" + function addSpread(...values) { + var result = 0; + for (var i=0; i Construct(arguments, null); + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Construct(arguments, null)); public void Configure() { diff --git a/Jint.Tests/Runtime/EngineTests.cs b/Jint.Tests/Runtime/EngineTests.cs index 1ba5d01d55..51446c3dc0 100644 --- a/Jint.Tests/Runtime/EngineTests.cs +++ b/Jint.Tests/Runtime/EngineTests.cs @@ -18,16 +18,14 @@ namespace Jint.Tests.Runtime public class EngineTests : IDisposable { private readonly Engine _engine; + private readonly Engine _asyncEngine; private int countBreak = 0; private StepMode stepMode; public EngineTests(ITestOutputHelper output) { - _engine = new Engine() - .SetValue("log", new Action( o => output.WriteLine(o.ToString()))) - .SetValue("assert", new Action(Assert.True)) - .SetValue("equal", new Action(Assert.Equal)) - ; + _engine = CreateEngine(output); + _asyncEngine = CreateEngine(output); } void IDisposable.Dispose() @@ -35,9 +33,18 @@ void IDisposable.Dispose() } - private void RunTest(string source) + private Engine CreateEngine(ITestOutputHelper output) + { + return new Engine() + .SetValue("log", new Action(o => output.WriteLine(o.ToString()))) + .SetValue("assert", new Action(Assert.True)) + .SetValue("equal", new Action(Assert.Equal)); + } + + private async void RunTest(string source) { _engine.Execute(source); + await _asyncEngine.ExecuteAsync(source); } private string GetEmbeddedFile(string filename) diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index c7a06d08f3..eb321e46e6 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -39,9 +39,10 @@ void IDisposable.Dispose() { } - private void RunTest(string source) + private async void RunTest(string source) { _engine.Execute(source); + //await _engine.ExecuteAsync(source); } [Fact] diff --git a/Jint/Directory.Build.props b/Jint/Directory.Build.props index 12d205ebef..78a298eafb 100644 --- a/Jint/Directory.Build.props +++ b/Jint/Directory.Build.props @@ -33,4 +33,9 @@ + + + + + diff --git a/Jint/Engine.Async.cs b/Jint/Engine.Async.cs new file mode 100644 index 0000000000..d8b72ecefa --- /dev/null +++ b/Jint/Engine.Async.cs @@ -0,0 +1,281 @@ +using Esprima; +using Esprima.Ast; +using Jint.Native; +using Jint.Native.Function; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.CallStack; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Environments; +using Jint.Runtime.Interpreter; +using Jint.Runtime.Interpreter.Expressions; +using Jint.Runtime.References; +using System.Threading.Tasks; + +namespace Jint +{ + public partial class Engine + { + internal async Task CallAsync(ICallable callable, JsValue thisObject, JsValue[] arguments, JintExpression expression) + { + if (callable is FunctionInstance functionInstance) + { + return await CallAsync(functionInstance, thisObject, arguments, expression); + } + + return await callable.CallAsync(thisObject, arguments); + } + + internal async Task CallAsync( + FunctionInstance functionInstance, + JsValue thisObject, + JsValue[] arguments, + JintExpression expression) + { + var callStackElement = new CallStackElement(functionInstance, expression); + var recursionDepth = CallStack.Push(callStackElement); + + if (recursionDepth > Options.MaxRecursionDepth) + { + // pop the current element as it was never reached + CallStack.Pop(); + ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack, callStackElement.ToString()); + } + + if (_isDebugMode) + { + DebugHandler.AddToDebugCallStack(functionInstance); + } + + var result = await functionInstance.CallAsync(thisObject, arguments); + + if (_isDebugMode) + { + DebugHandler.PopDebugCallStack(); + } + + CallStack.Pop(); + + return result; + } + + public Task ExecuteAsync(string source) + { + return ExecuteAsync(source, DefaultParserOptions); + } + + public Task ExecuteAsync(string source, ParserOptions parserOptions) + { + var parser = new JavaScriptParser(source, parserOptions); + return ExecuteAsync(parser.ParseScript()); + } + + public async Task ExecuteAsync(Script script) + { + ResetConstraints(); + ResetLastStatement(); + + using (new StrictModeScope(_isStrict || script.Strict)) + { + GlobalDeclarationInstantiation( + script, + GlobalEnvironment); + + var list = new JintStatementList(this, null, script.Body); + + Completion result; + try + { + result = await list.ExecuteAsync(); + } + catch + { + // unhandled exception + ResetCallStack(); + throw; + } + + if (result.Type == CompletionType.Throw) + { + var ex = new JavaScriptException(result.GetValueOrDefault()).SetCallstack(this, result.Location); + ResetCallStack(); + throw ex; + } + + _completionValue = result.GetValueOrDefault(); + } + + return this; + } + + /// + /// Invoke the current value as function. + /// + /// The name of the function to call. + /// The arguments of the function call. + /// The value returned by the function call. + public Task InvokeAsync(string propertyName, params object[] arguments) + { + return InvokeAsync(propertyName, null, arguments); + } + + /// + /// Invoke the current value as function. + /// + /// The name of the function to call. + /// The this value inside the function call. + /// The arguments of the function call. + /// The value returned by the function call. + public async Task InvokeAsync(string propertyName, object thisObj, object[] arguments) + { + var value = await GetValueAsync(propertyName); + + return await InvokeAsync(value, thisObj, arguments); + } + + /// + /// Invoke the current value as function. + /// + /// The function to call. + /// The arguments of the function call. + /// The value returned by the function call. + public Task InvokeAsync(JsValue value, params object[] arguments) + { + return InvokeAsync(value, null, arguments); + } + + /// + /// Invoke the current value as function. + /// + /// The function to call. + /// The this value inside the function call. + /// The arguments of the function call. + /// The value returned by the function call. + public async Task InvokeAsync(JsValue value, object thisObj, object[] arguments) + { + var callable = value as ICallable ?? ExceptionHelper.ThrowArgumentException("Can only invoke functions"); + + var items = _jsValueArrayPool.RentArray(arguments.Length); + for (int i = 0; i < arguments.Length; ++i) + { + items[i] = JsValue.FromObject(this, arguments[i]); + } + + var result = await callable.CallAsync(JsValue.FromObject(this, thisObj), items); + _jsValueArrayPool.ReturnArray(items); + + return result; + } + + /// + /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.7.1 + /// + public Task GetValueAsync(object value) + { + return GetValueAsync(value, false); + } + + internal Task GetValueAsync(object value, bool returnReferenceToPool) + { + if (value is JsValue jsValue) + { + return Task.FromResult(jsValue); + } + + if (!(value is Reference reference)) + { + return Task.FromResult(((Completion)value).Value); + } + + return GetValueAsync(reference, returnReferenceToPool); + } + + async internal Task GetValueAsync(Reference reference, bool returnReferenceToPool) + { + var baseValue = reference.GetBase(); + + if (baseValue._type == InternalTypes.Undefined) + { + if (_referenceResolver.TryUnresolvableReference(this, reference, out JsValue val)) + { + return val; + } + + ExceptionHelper.ThrowReferenceError(this, reference); + } + + if ((baseValue._type & InternalTypes.ObjectEnvironmentRecord) == 0 + && _referenceResolver.TryPropertyReference(this, reference, ref baseValue)) + { + return baseValue; + } + + if (reference.IsPropertyReference()) + { + var property = reference.GetReferencedName(); + if (returnReferenceToPool) + { + _referencePool.Return(reference); + } + + if (baseValue.IsObject()) + { + var o = TypeConverter.ToObject(this, baseValue); + var v = await o.GetAsync(property, reference.GetThisValue()); + return v; + } + else + { + // check if we are accessing a string, boxing operation can be costly to do index access + // we have good chance to have fast path with integer or string indexer + ObjectInstance o = null; + if ((property._type & (InternalTypes.String | InternalTypes.Integer)) != 0 + && baseValue is JsString s + && TryHandleStringValue(property, s, ref o, out var jsValue)) + { + return jsValue; + } + + if (o is null) + { + o = TypeConverter.ToObject(this, baseValue); + } + + var desc = o.GetProperty(property); + if (desc == PropertyDescriptor.Undefined) + { + return JsValue.Undefined; + } + + if (desc.IsDataDescriptor()) + { + return desc.Value; + } + + var getter = desc.Get; + if (getter.IsUndefined()) + { + return Undefined.Instance; + } + + var callable = (ICallable)getter.AsObject(); + return await callable.CallAsync(baseValue, Arguments.Empty); + } + } + + if (!(baseValue is EnvironmentRecord record)) + { + return ExceptionHelper.ThrowArgumentException(); + } + + var bindingValue = record.GetBindingValue(reference.GetReferencedName().ToString(), reference.IsStrictReference()); + + if (returnReferenceToPool) + { + _referencePool.Return(reference); + } + + return bindingValue; + } + } +} \ No newline at end of file diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 2603ed0651..a51e935a30 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -37,7 +37,7 @@ namespace Jint { - public class Engine + public partial class Engine { private static readonly ParserOptions DefaultParserOptions = new("") { @@ -659,6 +659,7 @@ public JsValue Invoke(JsValue value, object thisObj, object[] arguments) return result; } + /// /// Gets a named value from the Global scope. /// @@ -688,7 +689,7 @@ public JsValue GetValue(JsValue scope, JsValue property) _referencePool.Return(reference); return jsValue; } - + /// /// https://tc39.es/ecma262/#sec-resolvebinding /// diff --git a/Jint/Jint.csproj b/Jint/Jint.csproj index ad892d9d7d..60ac879711 100644 --- a/Jint/Jint.csproj +++ b/Jint/Jint.csproj @@ -10,4 +10,10 @@ + + + + + + diff --git a/Jint/Native/Array/ArrayConstructor.Async.cs b/Jint/Native/Array/ArrayConstructor.Async.cs new file mode 100644 index 0000000000..e85bdd9bb7 --- /dev/null +++ b/Jint/Native/Array/ArrayConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Array +{ + public sealed partial class ArrayConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Array/ArrayConstructor.cs b/Jint/Native/Array/ArrayConstructor.cs index a34cab0c6d..9c7fcb8699 100644 --- a/Jint/Native/Array/ArrayConstructor.cs +++ b/Jint/Native/Array/ArrayConstructor.cs @@ -12,7 +12,7 @@ namespace Jint.Native.Array { - public sealed class ArrayConstructor : FunctionInstance, IConstructor + public sealed partial class ArrayConstructor : FunctionInstance, IConstructor { private static readonly JsString _functionName = new JsString("Array"); diff --git a/Jint/Native/Boolean/BooleanConstructor.Async.cs b/Jint/Native/Boolean/BooleanConstructor.Async.cs new file mode 100644 index 0000000000..9ee8e77967 --- /dev/null +++ b/Jint/Native/Boolean/BooleanConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Boolean +{ + public sealed partial class BooleanConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Boolean/BooleanConstructor.cs b/Jint/Native/Boolean/BooleanConstructor.cs index b0c693f3b9..3c772ed16e 100644 --- a/Jint/Native/Boolean/BooleanConstructor.cs +++ b/Jint/Native/Boolean/BooleanConstructor.cs @@ -5,7 +5,7 @@ namespace Jint.Native.Boolean { - public sealed class BooleanConstructor : FunctionInstance, IConstructor + public sealed partial class BooleanConstructor : FunctionInstance, IConstructor { private static readonly JsString _functionName = new JsString("Boolean"); @@ -70,5 +70,6 @@ public BooleanInstance Construct(JsBoolean value) return instance; } + } } diff --git a/Jint/Native/Date/DateConstructor.Async.cs b/Jint/Native/Date/DateConstructor.Async.cs new file mode 100644 index 0000000000..d15c52df83 --- /dev/null +++ b/Jint/Native/Date/DateConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Date +{ + public sealed partial class DateConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Date/DateConstructor.cs b/Jint/Native/Date/DateConstructor.cs index fac35d00fe..e5d045da7d 100644 --- a/Jint/Native/Date/DateConstructor.cs +++ b/Jint/Native/Date/DateConstructor.cs @@ -9,7 +9,7 @@ namespace Jint.Native.Date { - public sealed class DateConstructor : FunctionInstance, IConstructor + public sealed partial class DateConstructor : FunctionInstance, IConstructor { internal static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); diff --git a/Jint/Native/Error/ErrorConstructor.Async.cs b/Jint/Native/Error/ErrorConstructor.Async.cs new file mode 100644 index 0000000000..9339e41cd1 --- /dev/null +++ b/Jint/Native/Error/ErrorConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Error +{ + public sealed partial class ErrorConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Error/ErrorConstructor.cs b/Jint/Native/Error/ErrorConstructor.cs index ae0dff2153..5be00bff62 100644 --- a/Jint/Native/Error/ErrorConstructor.cs +++ b/Jint/Native/Error/ErrorConstructor.cs @@ -5,7 +5,7 @@ namespace Jint.Native.Error { - public sealed class ErrorConstructor : FunctionInstance, IConstructor + public sealed partial class ErrorConstructor : FunctionInstance, IConstructor { private JsString _name; private static readonly JsString _functionName = new JsString("Error"); diff --git a/Jint/Native/Function/BindFunctionInstance.Async.cs b/Jint/Native/Function/BindFunctionInstance.Async.cs new file mode 100644 index 0000000000..08058d1550 --- /dev/null +++ b/Jint/Native/Function/BindFunctionInstance.Async.cs @@ -0,0 +1,24 @@ +using Jint.Native.Object; +using Jint.Runtime; +using System.Linq; +using System.Threading.Tasks; + +namespace Jint.Native.Function +{ + public sealed partial class BindFunctionInstance : FunctionInstance, IConstructor + { + public async override Task CallAsync(JsValue thisObject, JsValue[] arguments) + { + if (!(TargetFunction is FunctionInstance f)) + { + return ExceptionHelper.ThrowTypeError(Engine); + } + + var args = CreateArguments(arguments); + var value = await f.CallAsync(BoundThis, args); + _engine._jsValueArrayPool.ReturnArray(args); + + return value; + } + } +} \ No newline at end of file diff --git a/Jint/Native/Function/BindFunctionInstance.cs b/Jint/Native/Function/BindFunctionInstance.cs index c176097b9b..a30a95e8ce 100644 --- a/Jint/Native/Function/BindFunctionInstance.cs +++ b/Jint/Native/Function/BindFunctionInstance.cs @@ -3,7 +3,7 @@ namespace Jint.Native.Function { - public sealed class BindFunctionInstance : FunctionInstance, IConstructor + public sealed partial class BindFunctionInstance : FunctionInstance, IConstructor { public BindFunctionInstance(Engine engine) : base(engine, name: null, thisMode: FunctionThisMode.Strict) diff --git a/Jint/Native/Function/EvalFunctionInstance.Async.cs b/Jint/Native/Function/EvalFunctionInstance.Async.cs new file mode 100644 index 0000000000..ee48c046c8 --- /dev/null +++ b/Jint/Native/Function/EvalFunctionInstance.Async.cs @@ -0,0 +1,133 @@ +using Esprima; +using Esprima.Ast; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Environments; +using Jint.Runtime.Interpreter.Statements; +using System.Threading.Tasks; + +namespace Jint.Native.Function +{ + public sealed partial class EvalFunctionInstance : FunctionInstance + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) + { + return PerformEvalAsync(arguments, false); + } + + /// + /// https://tc39.es/ecma262/#sec-performeval + /// + public async Task PerformEvalAsync(JsValue[] arguments, bool direct) + { + if (!(arguments.At(0) is JsString x)) + { + return arguments.At(0); + } + + var inFunction = false; + var inMethod = false; + var inDerivedConstructor = false; + + if (direct) + { + var thisEnvRec = _engine.GetThisEnvironment(); + if (thisEnvRec is FunctionEnvironmentRecord functionEnvironmentRecord) + { + var F = functionEnvironmentRecord._functionObject; + inFunction = true; + inMethod = thisEnvRec.HasSuperBinding(); + + if (F._constructorKind == ConstructorKind.Derived) + { + inDerivedConstructor = true; + } + } + } + + var parser = new JavaScriptParser(x.ToString(), ParserOptions); + Script script; + try + { + script = parser.ParseScript(StrictModeScope.IsStrictModeCode); + } + catch (ParserException e) + { + return e.Description == Messages.InvalidLHSInAssignment + ? ExceptionHelper.ThrowReferenceError(_engine) + : ExceptionHelper.ThrowSyntaxError(_engine); + } + + var body = script.Body; + if (body.Count == 0) + { + return Undefined; + } + + if (!inFunction) + { + // if body Contains NewTarget, throw a SyntaxError exception. + } + if (!inMethod) + { + // if body Contains SuperProperty, throw a SyntaxError exception. + } + if (!inDerivedConstructor) + { + // if body Contains SuperCall, throw a SyntaxError exception. + } + + var strictEval = script.Strict || _engine._isStrict; + var ctx = _engine.ExecutionContext; + + using (new StrictModeScope(strictEval)) + { + LexicalEnvironment lexEnv; + LexicalEnvironment varEnv; + if (direct) + { + lexEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, ctx.LexicalEnvironment); + varEnv = ctx.VariableEnvironment; + } + else + { + lexEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, Engine.GlobalEnvironment); + varEnv = Engine.GlobalEnvironment; + } + + if (strictEval) + { + varEnv = lexEnv; + } + + // If ctx is not already suspended, suspend ctx. + + Engine.EnterExecutionContext(lexEnv, varEnv); + + try + { + Engine.EvalDeclarationInstantiation(script, varEnv, lexEnv, strictEval); + + var statement = new JintScript(_engine, script); + var result = await statement.ExecuteAsync(); + var value = result.GetValueOrDefault(); + + if (result.Type == CompletionType.Throw) + { + var ex = new JavaScriptException(value).SetCallstack(_engine, result.Location); + throw ex; + } + else + { + return value; + } + } + finally + { + Engine.LeaveExecutionContext(); + } + } + } + + } +} \ No newline at end of file diff --git a/Jint/Native/Function/EvalFunctionInstance.cs b/Jint/Native/Function/EvalFunctionInstance.cs index 1adfe8d222..e64b2c064e 100644 --- a/Jint/Native/Function/EvalFunctionInstance.cs +++ b/Jint/Native/Function/EvalFunctionInstance.cs @@ -7,7 +7,7 @@ namespace Jint.Native.Function { - public sealed class EvalFunctionInstance : FunctionInstance + public sealed partial class EvalFunctionInstance : FunctionInstance { private static readonly ParserOptions ParserOptions = new ParserOptions { AdaptRegexp = true, Tolerant = false }; private static readonly JsString _functionName = new JsString("eval"); diff --git a/Jint/Native/Function/FunctionConstructor.Async.cs b/Jint/Native/Function/FunctionConstructor.Async.cs new file mode 100644 index 0000000000..bba339e1e2 --- /dev/null +++ b/Jint/Native/Function/FunctionConstructor.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Native.Function +{ + public sealed partial class FunctionConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Function/FunctionConstructor.cs b/Jint/Native/Function/FunctionConstructor.cs index e84b3f375b..0af52a25b4 100644 --- a/Jint/Native/Function/FunctionConstructor.cs +++ b/Jint/Native/Function/FunctionConstructor.cs @@ -7,7 +7,7 @@ namespace Jint.Native.Function { - public sealed class FunctionConstructor : FunctionInstance, IConstructor + public sealed partial class FunctionConstructor : FunctionInstance, IConstructor { private static readonly ParserOptions ParserOptions = new ParserOptions { AdaptRegexp = true, Tolerant = false }; private static readonly JsString _functionName = new JsString("Function"); diff --git a/Jint/Native/Function/FunctionInstance.Async.cs b/Jint/Native/Function/FunctionInstance.Async.cs new file mode 100644 index 0000000000..90f2bf64f5 --- /dev/null +++ b/Jint/Native/Function/FunctionInstance.Async.cs @@ -0,0 +1,29 @@ +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Environments; +using System.Threading.Tasks; + +namespace Jint.Native.Function +{ + public abstract partial class FunctionInstance : ObjectInstance, ICallable + { + public abstract Task CallAsync(JsValue thisObject, JsValue[] arguments); + + protected async Task OrdinaryCallEvaluateBodyAsync( + JsValue[] arguments, + ExecutionContext calleeContext) + { + var argumentsInstance = _engine.FunctionDeclarationInstantiation( + functionInstance: this, + arguments, + calleeContext.LexicalEnvironment); + + var result = await _functionDefinition.ExecuteAsync(); + var value = result.GetValueOrDefault().Clone(); + + argumentsInstance?.FunctionWasCalled(); + + return new Completion(result.Type, value, result.Identifier, result.Location); + } + } +} \ No newline at end of file diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index e04a6308dd..27733b22e0 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -10,7 +10,7 @@ namespace Jint.Native.Function { - public abstract class FunctionInstance : ObjectInstance, ICallable + public abstract partial class FunctionInstance : ObjectInstance, ICallable { protected PropertyDescriptor _prototypeDescriptor; diff --git a/Jint/Native/Function/FunctionPrototype.Async.cs b/Jint/Native/Function/FunctionPrototype.Async.cs new file mode 100644 index 0000000000..ab0a071897 --- /dev/null +++ b/Jint/Native/Function/FunctionPrototype.Async.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; + +namespace Jint.Native.Function +{ + /// + /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4 + /// + public sealed partial class FunctionPrototype : FunctionInstance + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Function/FunctionPrototype.cs b/Jint/Native/Function/FunctionPrototype.cs index 76d0cfb4cb..ff5154038d 100644 --- a/Jint/Native/Function/FunctionPrototype.cs +++ b/Jint/Native/Function/FunctionPrototype.cs @@ -5,13 +5,14 @@ using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; +using System.Threading.Tasks; namespace Jint.Native.Function { /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4 /// - public sealed class FunctionPrototype : FunctionInstance + public sealed partial class FunctionPrototype : FunctionInstance { private FunctionPrototype(Engine engine) : base(engine, JsString.Empty) diff --git a/Jint/Native/Function/ScriptFunctionInstance.Async.cs b/Jint/Native/Function/ScriptFunctionInstance.Async.cs new file mode 100644 index 0000000000..91deb2ff25 --- /dev/null +++ b/Jint/Native/Function/ScriptFunctionInstance.Async.cs @@ -0,0 +1,47 @@ +using Jint.Runtime; +using System.Threading.Tasks; + +namespace Jint.Native.Function +{ + public sealed partial class ScriptFunctionInstance : FunctionInstance, IConstructor + { + public async override Task CallAsync(JsValue thisArgument, JsValue[] arguments) + { + if (_isClassConstructor) + { + ExceptionHelper.ThrowTypeError(_engine, $"Class constructor {_functionDefinition.Name} cannot be invoked without 'new'"); + } + + var calleeContext = PrepareForOrdinaryCall(Undefined); + + OrdinaryCallBindThis(calleeContext, thisArgument); + + // actual call + + var strict = _thisMode == FunctionThisMode.Strict || _engine._isStrict; + using (new StrictModeScope(strict, true)) + { + try + { + var result = await OrdinaryCallEvaluateBodyAsync(arguments, calleeContext); + + if (result.Type == CompletionType.Throw) + { + ExceptionHelper.ThrowJavaScriptException(_engine, result.Value, result); + } + + if (result.Type == CompletionType.Return) + { + return result.Value; + } + } + finally + { + _engine.LeaveExecutionContext(); + } + + return Undefined; + } + } + } +} \ No newline at end of file diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index f6ae1df495..4514a2b52a 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -9,7 +9,7 @@ namespace Jint.Native.Function { - public sealed class ScriptFunctionInstance : FunctionInstance, IConstructor + public sealed partial class ScriptFunctionInstance : FunctionInstance, IConstructor { private bool _isClassConstructor; diff --git a/Jint/Native/Function/ThrowTypeError.Async.cs b/Jint/Native/Function/ThrowTypeError.Async.cs new file mode 100644 index 0000000000..4d4650b1a1 --- /dev/null +++ b/Jint/Native/Function/ThrowTypeError.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Native.Function +{ + public sealed partial class ThrowTypeError : FunctionInstance + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Function/ThrowTypeError.cs b/Jint/Native/Function/ThrowTypeError.cs index bd22f43304..733a7bc784 100644 --- a/Jint/Native/Function/ThrowTypeError.cs +++ b/Jint/Native/Function/ThrowTypeError.cs @@ -3,7 +3,7 @@ namespace Jint.Native.Function { - public sealed class ThrowTypeError : FunctionInstance + public sealed partial class ThrowTypeError : FunctionInstance { private static readonly JsString _functionName = new JsString("throwTypeError"); diff --git a/Jint/Native/ICallable.Async.cs b/Jint/Native/ICallable.Async.cs new file mode 100644 index 0000000000..8623ffdd25 --- /dev/null +++ b/Jint/Native/ICallable.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Native +{ + public partial interface ICallable + { + Task CallAsync(JsValue thisObject, JsValue[] arguments); + } +} \ No newline at end of file diff --git a/Jint/Native/ICallable.cs b/Jint/Native/ICallable.cs index 7552bd0877..a8b3a2acaa 100644 --- a/Jint/Native/ICallable.cs +++ b/Jint/Native/ICallable.cs @@ -1,6 +1,6 @@ namespace Jint.Native { - public interface ICallable + public partial interface ICallable { JsValue Call(JsValue thisObject, JsValue[] arguments); } diff --git a/Jint/Native/Iterator/IteratorConstructor.Async.cs b/Jint/Native/Iterator/IteratorConstructor.Async.cs new file mode 100644 index 0000000000..5ffd83ecb2 --- /dev/null +++ b/Jint/Native/Iterator/IteratorConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Iterator +{ + public sealed partial class IteratorConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Iterator/IteratorConstructor.cs b/Jint/Native/Iterator/IteratorConstructor.cs index c89d1665f4..f02976faa5 100644 --- a/Jint/Native/Iterator/IteratorConstructor.cs +++ b/Jint/Native/Iterator/IteratorConstructor.cs @@ -7,7 +7,7 @@ namespace Jint.Native.Iterator { - public sealed class IteratorConstructor : FunctionInstance, IConstructor + public sealed partial class IteratorConstructor : FunctionInstance, IConstructor { private static readonly JsString _functionName = new JsString("iterator"); diff --git a/Jint/Native/JsValue.Async.cs b/Jint/Native/JsValue.Async.cs new file mode 100644 index 0000000000..61382c06db --- /dev/null +++ b/Jint/Native/JsValue.Async.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Jint.Native.Array; +using Jint.Native.Date; +using Jint.Native.Iterator; +using Jint.Native.Number; +using Jint.Native.Object; +using Jint.Native.RegExp; +using Jint.Native.Symbol; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; + +namespace Jint.Native +{ + [DebuggerTypeProxy(typeof(JsValueDebugView))] + public abstract partial class JsValue : IEquatable + { + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal async Task TryGetIteratorAsync(Engine engine) + { + var objectInstance = TypeConverter.ToObject(engine, this); + + if (!objectInstance.TryGetValue(GlobalSymbolRegistry.Iterator, out var value) + || !(value is ICallable callable)) + { + return null; + } + + var obj = await callable.CallAsync(this, Arguments.Empty) as ObjectInstance + ?? ExceptionHelper.ThrowTypeError(engine, "Result of the Symbol.iterator method is not an object"); + + if (obj is IIterator i) return i; + return new IteratorInstance.ObjectIterator(obj); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task GetAsync(JsValue property) + { + return GetAsync(property, this); + } + + /// + /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.3 + /// + public virtual Task GetAsync(JsValue property, JsValue receiver) + { + return Task.FromResult(Undefined); + } + } +} diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index ab4142b7b6..e4d78d7545 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -18,7 +18,7 @@ namespace Jint.Native { [DebuggerTypeProxy(typeof(JsValueDebugView))] - public abstract class JsValue : IEquatable + public abstract partial class JsValue : IEquatable { public static readonly JsValue Undefined = new JsUndefined(); public static readonly JsValue Null = new JsNull(); diff --git a/Jint/Native/Map/MapConstructor.Async.cs b/Jint/Native/Map/MapConstructor.Async.cs new file mode 100644 index 0000000000..de9bf52d10 --- /dev/null +++ b/Jint/Native/Map/MapConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Map +{ + public sealed partial class MapConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Map/MapConstructor.cs b/Jint/Native/Map/MapConstructor.cs index e732d89590..c500c6de07 100644 --- a/Jint/Native/Map/MapConstructor.cs +++ b/Jint/Native/Map/MapConstructor.cs @@ -9,7 +9,7 @@ namespace Jint.Native.Map { - public sealed class MapConstructor : FunctionInstance, IConstructor + public sealed partial class MapConstructor : FunctionInstance, IConstructor { private static readonly JsString _functionName = new JsString("Map"); diff --git a/Jint/Native/Number/NumberConstructor.Async.cs b/Jint/Native/Number/NumberConstructor.Async.cs new file mode 100644 index 0000000000..2bec224e30 --- /dev/null +++ b/Jint/Native/Number/NumberConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Number +{ + public sealed partial class NumberConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Number/NumberConstructor.cs b/Jint/Native/Number/NumberConstructor.cs index f81258dbd3..0b27305036 100644 --- a/Jint/Native/Number/NumberConstructor.cs +++ b/Jint/Native/Number/NumberConstructor.cs @@ -8,7 +8,7 @@ namespace Jint.Native.Number { - public sealed class NumberConstructor : FunctionInstance, IConstructor + public sealed partial class NumberConstructor : FunctionInstance, IConstructor { private static readonly JsString _functionName = new JsString("Number"); diff --git a/Jint/Native/Object/ObjectConstructor.Async.cs b/Jint/Native/Object/ObjectConstructor.Async.cs new file mode 100644 index 0000000000..e38f48e891 --- /dev/null +++ b/Jint/Native/Object/ObjectConstructor.Async.cs @@ -0,0 +1,18 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Object +{ + public sealed partial class ObjectConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + + private sealed partial class CreateDataPropertyOnObject : ICallable + { + public Task CallAsync(JsValue thisObject, JsValue[] arguments) + { + return Task.FromResult(Call(thisObject, arguments)); + } + } + } +} \ No newline at end of file diff --git a/Jint/Native/Object/ObjectConstructor.cs b/Jint/Native/Object/ObjectConstructor.cs index dd4baf9dac..865b904953 100644 --- a/Jint/Native/Object/ObjectConstructor.cs +++ b/Jint/Native/Object/ObjectConstructor.cs @@ -8,7 +8,7 @@ namespace Jint.Native.Object { - public sealed class ObjectConstructor : FunctionInstance, IConstructor + public sealed partial class ObjectConstructor : FunctionInstance, IConstructor { private static readonly JsString _name = new JsString("delegate"); @@ -479,7 +479,7 @@ private JsValue Values(JsValue thisObject, JsValue[] arguments) return o.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.Value); } - private sealed class CreateDataPropertyOnObject : ICallable + private sealed partial class CreateDataPropertyOnObject : ICallable { internal static readonly CreateDataPropertyOnObject Instance = new CreateDataPropertyOnObject(); diff --git a/Jint/Native/Object/ObjectInstance.Async.cs b/Jint/Native/Object/ObjectInstance.Async.cs new file mode 100644 index 0000000000..82934a18ec --- /dev/null +++ b/Jint/Native/Object/ObjectInstance.Async.cs @@ -0,0 +1,50 @@ +using Jint.Native.Function; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Jint.Native.Object +{ + public partial class ObjectInstance : JsValue, IEquatable + { + public override Task GetAsync(JsValue property, JsValue receiver) + { + var desc = GetProperty(property); + return UnwrapJsValueAsync(desc, receiver); + } + + internal async static Task UnwrapJsValueAsync(PropertyDescriptor desc, JsValue thisObject) + { + var value = (desc._flags & PropertyFlag.CustomJsValue) != 0 + ? desc.CustomValue + : desc._value; + + // IsDataDescriptor inlined + if ((desc._flags & (PropertyFlag.WritableSet | PropertyFlag.Writable)) != 0 + || !ReferenceEquals(value, null)) + { + return value ?? Undefined; + } + + return await UnwrapFromGetterAsync(desc, thisObject); + } + + /// + /// A rarer case. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private async static Task UnwrapFromGetterAsync(PropertyDescriptor desc, JsValue thisObject) + { + var getter = desc.Get ?? Undefined; + if (getter.IsUndefined()) + { + return Undefined; + } + + var functionInstance = (FunctionInstance)getter; + return await functionInstance._engine.CallAsync(functionInstance, thisObject, Arguments.Empty, expression: null); + } + } +} \ No newline at end of file diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index f15ac8e7af..28b6042b99 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Dynamic; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Jint.Collections; using Jint.Native.Array; using Jint.Native.Boolean; @@ -19,7 +20,7 @@ namespace Jint.Native.Object { - public class ObjectInstance : JsValue, IEquatable + public partial class ObjectInstance : JsValue, IEquatable { private bool _initialized; private readonly ObjectClass _class; diff --git a/Jint/Native/Proxy/ProxyConstructor.Async.cs b/Jint/Native/Proxy/ProxyConstructor.Async.cs new file mode 100644 index 0000000000..92565bad67 --- /dev/null +++ b/Jint/Native/Proxy/ProxyConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Proxy +{ + public sealed partial class ProxyConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} diff --git a/Jint/Native/Proxy/ProxyConstructor.cs b/Jint/Native/Proxy/ProxyConstructor.cs index b7f430b907..1549a8ee4b 100644 --- a/Jint/Native/Proxy/ProxyConstructor.cs +++ b/Jint/Native/Proxy/ProxyConstructor.cs @@ -7,7 +7,7 @@ namespace Jint.Native.Proxy { - public sealed class ProxyConstructor : FunctionInstance, IConstructor + public sealed partial class ProxyConstructor : FunctionInstance, IConstructor { private static readonly JsString _name = new JsString("Proxy"); private static readonly JsString PropertyProxy = new JsString("proxy"); diff --git a/Jint/Native/Proxy/ProxyInstance.Async.cs b/Jint/Native/Proxy/ProxyInstance.Async.cs new file mode 100644 index 0000000000..1f91901f63 --- /dev/null +++ b/Jint/Native/Proxy/ProxyInstance.Async.cs @@ -0,0 +1,25 @@ +using Jint.Native.Object; +using Jint.Runtime; +using System.Threading.Tasks; + +namespace Jint.Native.Proxy +{ + public partial class ProxyInstance : ObjectInstance, IConstructor, ICallable + { + public async Task CallAsync(JsValue thisObject, JsValue[] arguments) + { + var jsValues = new[] { _target, thisObject, _engine.Array.Construct(arguments) }; + if (TryCallHandler(TrapApply, jsValues, out var result)) + { + return result; + } + + if (!(_target is ICallable callable)) + { + return ExceptionHelper.ThrowTypeError(_engine, _target + " is not a function"); + } + + return await callable.CallAsync(thisObject, arguments); + } + } +} \ No newline at end of file diff --git a/Jint/Native/Proxy/ProxyInstance.cs b/Jint/Native/Proxy/ProxyInstance.cs index 8ac54175af..713e8f67f9 100644 --- a/Jint/Native/Proxy/ProxyInstance.cs +++ b/Jint/Native/Proxy/ProxyInstance.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; - using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; namespace Jint.Native.Proxy { - public class ProxyInstance : ObjectInstance, IConstructor, ICallable + public partial class ProxyInstance : ObjectInstance, IConstructor, ICallable { internal ObjectInstance _target; internal ObjectInstance _handler; diff --git a/Jint/Native/RegExp/RegExpConstructor.Async.cs b/Jint/Native/RegExp/RegExpConstructor.Async.cs new file mode 100644 index 0000000000..6fa28a3245 --- /dev/null +++ b/Jint/Native/RegExp/RegExpConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.RegExp +{ + public sealed partial class RegExpConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/RegExp/RegExpConstructor.cs b/Jint/Native/RegExp/RegExpConstructor.cs index f813e77bd2..2db50b11e4 100644 --- a/Jint/Native/RegExp/RegExpConstructor.cs +++ b/Jint/Native/RegExp/RegExpConstructor.cs @@ -11,7 +11,7 @@ namespace Jint.Native.RegExp { - public sealed class RegExpConstructor : FunctionInstance, IConstructor + public sealed partial class RegExpConstructor : FunctionInstance, IConstructor { private static readonly JsString _functionName = new JsString("RegExp"); @@ -170,7 +170,7 @@ private static void RegExpInitialize(RegExpInstance r) { r.SetOwnProperty(RegExpInstance.PropertyLastIndex, new PropertyDescriptor(0, PropertyFlag.OnlyWritable)); } - + public RegExpPrototype PrototypeObject { get; private set; } } } diff --git a/Jint/Native/Set/SetConstructor.Async.cs b/Jint/Native/Set/SetConstructor.Async.cs new file mode 100644 index 0000000000..452bd2fab3 --- /dev/null +++ b/Jint/Native/Set/SetConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Set +{ + public sealed partial class SetConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Set/SetConstructor.cs b/Jint/Native/Set/SetConstructor.cs index 7646659787..77b044aaeb 100644 --- a/Jint/Native/Set/SetConstructor.cs +++ b/Jint/Native/Set/SetConstructor.cs @@ -8,7 +8,7 @@ namespace Jint.Native.Set { - public sealed class SetConstructor : FunctionInstance, IConstructor + public sealed partial class SetConstructor : FunctionInstance, IConstructor { private static readonly JsString _functionName = new JsString("Set"); diff --git a/Jint/Native/String/StringConstructor.Async.cs b/Jint/Native/String/StringConstructor.Async.cs new file mode 100644 index 0000000000..ff9e8abce8 --- /dev/null +++ b/Jint/Native/String/StringConstructor.Async.cs @@ -0,0 +1,10 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.String +{ + public sealed partial class StringConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/String/StringConstructor.cs b/Jint/Native/String/StringConstructor.cs index b3209ba754..cb5de6f1b2 100644 --- a/Jint/Native/String/StringConstructor.cs +++ b/Jint/Native/String/StringConstructor.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Jint.Collections; using Jint.Native.Array; using Jint.Native.Function; @@ -11,7 +12,7 @@ namespace Jint.Native.String { - public sealed class StringConstructor : FunctionInstance, IConstructor + public sealed partial class StringConstructor : FunctionInstance, IConstructor { private static readonly JsString _functionName = new JsString("String"); diff --git a/Jint/Native/Symbol/SymbolConstructor.Async.cs b/Jint/Native/Symbol/SymbolConstructor.Async.cs new file mode 100644 index 0000000000..74fe4d648c --- /dev/null +++ b/Jint/Native/Symbol/SymbolConstructor.Async.cs @@ -0,0 +1,14 @@ +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Native.Symbol +{ + /// + /// 19.4 + /// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-symbol-objects + /// + public sealed partial class SymbolConstructor : FunctionInstance, IConstructor + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Native/Symbol/SymbolConstructor.cs b/Jint/Native/Symbol/SymbolConstructor.cs index 5bee40c519..41712eec5d 100644 --- a/Jint/Native/Symbol/SymbolConstructor.cs +++ b/Jint/Native/Symbol/SymbolConstructor.cs @@ -11,7 +11,7 @@ namespace Jint.Native.Symbol /// 19.4 /// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-symbol-objects /// - public sealed class SymbolConstructor : FunctionInstance, IConstructor + public sealed partial class SymbolConstructor : FunctionInstance, IConstructor { private static readonly JsString _functionName = new JsString("Symbol"); diff --git a/Jint/Runtime/Interop/AsyncHelpers.cs b/Jint/Runtime/Interop/AsyncHelpers.cs new file mode 100644 index 0000000000..7078f7d092 --- /dev/null +++ b/Jint/Runtime/Interop/AsyncHelpers.cs @@ -0,0 +1,33 @@ +using System.Linq; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interop +{ + public static class AsyncHelpers + { + // The idea of this helper was to avoid async constructs in javascript. So this one just auto awaits on any task type. + // I realize it might be better to just return the Task and handle the await in JS to give more control to the developer (cancellation and timeout) + // However, it would also be useful in many cases to just have this done automatically by the helper, to keep the JS code clean - so maybe it could be an optional feature w/timeout. + // I'm leaving it here, as this is what I use at the moment + + public async static Task AwaitWhenAsyncResult(this object callResult) + { + if (!(callResult is Task task)) return callResult; + + await task; + + // Return the result, if the task has one (must be a generic Task that is not of type VoidTaskResult) + return TaskHasResult(task) + ? (object)((dynamic)task)?.Result + : null; + } + + private static bool TaskHasResult(Task task) + { + // VoidTaskResult is an internal Microsoft class used as Task which correlates to the standard non generic Task + var taskType = task.GetType(); + return taskType.IsGenericType + && taskType.GenericTypeArguments[0].Name != "VoidTaskResult"; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interop/ClrFunctionInstance.Async.cs b/Jint/Runtime/Interop/ClrFunctionInstance.Async.cs new file mode 100644 index 0000000000..13e0662675 --- /dev/null +++ b/Jint/Runtime/Interop/ClrFunctionInstance.Async.cs @@ -0,0 +1,40 @@ +using Jint.Native; +using Jint.Native.Function; +using Jint.Runtime.Descriptors; +using System; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interop +{ + /// + /// Wraps a Clr method into a FunctionInstance + /// + public sealed partial class ClrFunctionInstance : FunctionInstance, IEquatable + { + internal readonly Func> _funcAsync; + + public ClrFunctionInstance( + Engine engine, + string name, + Func> funcAsync, + int length = 0, + PropertyFlag lengthFlags = PropertyFlag.AllForbidden) + : base(engine, !string.IsNullOrWhiteSpace(name) ? new JsString(name) : null) + { + _name = name; + _funcAsync = funcAsync; + + _prototype = engine.Function.PrototypeObject; + + _length = lengthFlags == PropertyFlag.AllForbidden + ? PropertyDescriptor.AllForbiddenDescriptor.ForNumber(length) + : new PropertyDescriptor(JsNumber.Create(length), lengthFlags); + } + + public async override Task CallAsync(JsValue thisObject, JsValue[] arguments) + { + if (_funcAsync != null) return await _funcAsync(thisObject, arguments); + else return await CallAsync(thisObject, arguments); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interop/ClrFunctionInstance.cs b/Jint/Runtime/Interop/ClrFunctionInstance.cs index 8bea12ebad..0f4dd3a1b9 100644 --- a/Jint/Runtime/Interop/ClrFunctionInstance.cs +++ b/Jint/Runtime/Interop/ClrFunctionInstance.cs @@ -8,7 +8,7 @@ namespace Jint.Runtime.Interop /// /// Wraps a Clr method into a FunctionInstance /// - public sealed class ClrFunctionInstance : FunctionInstance, IEquatable + public sealed partial class ClrFunctionInstance : FunctionInstance, IEquatable { private readonly string _name; internal readonly Func _func; diff --git a/Jint/Runtime/Interop/DelegateWrapper.Async.cs b/Jint/Runtime/Interop/DelegateWrapper.Async.cs new file mode 100644 index 0000000000..0887ceb679 --- /dev/null +++ b/Jint/Runtime/Interop/DelegateWrapper.Async.cs @@ -0,0 +1,108 @@ +using System; +using System.Globalization; +using System.Reflection; +using System.Threading.Tasks; +using Jint.Native; +using Jint.Native.Function; + +namespace Jint.Runtime.Interop +{ + /// + /// Represents a FunctionInstance wrapper around a CLR method. This is used by user to pass + /// custom methods to the engine. + /// + public sealed partial class DelegateWrapper : FunctionInstance + { + public async override Task CallAsync(JsValue thisObject, JsValue[] jsArguments) + { + var parameterInfos = _d.Method.GetParameters(); + +#if NETFRAMEWORK + if (parameterInfos.Length > 0 && parameterInfos[0].ParameterType == typeof(System.Runtime.CompilerServices.Closure)) + { + var reducedLength = parameterInfos.Length - 1; + var reducedParameterInfos = new ParameterInfo[reducedLength]; + Array.Copy(parameterInfos, 1, reducedParameterInfos, 0, reducedLength); + parameterInfos = reducedParameterInfos; + } +#endif + + int delegateArgumentsCount = parameterInfos.Length; + int delegateNonParamsArgumentsCount = _delegateContainsParamsArgument ? delegateArgumentsCount - 1 : delegateArgumentsCount; + + int jsArgumentsCount = jsArguments.Length; + int jsArgumentsWithoutParamsCount = Math.Min(jsArgumentsCount, delegateNonParamsArgumentsCount); + + var parameters = new object[delegateArgumentsCount]; + + // convert non params parameter to expected types + for (var i = 0; i < jsArgumentsWithoutParamsCount; i++) + { + var parameterType = parameterInfos[i].ParameterType; + + if (parameterType == typeof(JsValue)) + { + parameters[i] = jsArguments[i]; + } + else + { + parameters[i] = Engine.ClrTypeConverter.Convert( + jsArguments[i].ToObject(), + parameterType, + CultureInfo.InvariantCulture); + } + } + + // assign null to parameters not provided + for (var i = jsArgumentsWithoutParamsCount; i < delegateNonParamsArgumentsCount; i++) + { + if (parameterInfos[i].ParameterType.IsValueType) + { + parameters[i] = Activator.CreateInstance(parameterInfos[i].ParameterType); + } + else + { + parameters[i] = null; + } + } + + // assign params to array and converts each objet to expected type + if (_delegateContainsParamsArgument) + { + int paramsArgumentIndex = delegateArgumentsCount - 1; + int paramsCount = Math.Max(0, jsArgumentsCount - delegateNonParamsArgumentsCount); + + object[] paramsParameter = new object[paramsCount]; + var paramsParameterType = parameterInfos[paramsArgumentIndex].ParameterType.GetElementType(); + + for (var i = paramsArgumentIndex; i < jsArgumentsCount; i++) + { + var paramsIndex = i - paramsArgumentIndex; + + if (paramsParameterType == typeof(JsValue)) + { + paramsParameter[paramsIndex] = jsArguments[i]; + } + else + { + paramsParameter[paramsIndex] = Engine.ClrTypeConverter.Convert( + jsArguments[i].ToObject(), + paramsParameterType, + CultureInfo.InvariantCulture); + } + } + parameters[paramsArgumentIndex] = paramsParameter; + } + try + { + var result = await _d.DynamicInvoke(parameters).AwaitWhenAsyncResult(); + return FromObject(Engine, result); + } + catch (TargetInvocationException exception) + { + ExceptionHelper.ThrowMeaningfulException(_engine, exception); + throw; + } + } + } +} diff --git a/Jint/Runtime/Interop/DelegateWrapper.cs b/Jint/Runtime/Interop/DelegateWrapper.cs index 15a2b0d12f..a56d0b8aa2 100644 --- a/Jint/Runtime/Interop/DelegateWrapper.cs +++ b/Jint/Runtime/Interop/DelegateWrapper.cs @@ -10,7 +10,7 @@ namespace Jint.Runtime.Interop /// Represents a FunctionInstance wrapper around a CLR method. This is used by user to pass /// custom methods to the engine. /// - public sealed class DelegateWrapper : FunctionInstance + public sealed partial class DelegateWrapper : FunctionInstance { private static readonly JsString _name = new JsString("delegate"); private readonly Delegate _d; diff --git a/Jint/Runtime/Interop/GetterFunctionInstance.Async.cs b/Jint/Runtime/Interop/GetterFunctionInstance.Async.cs new file mode 100644 index 0000000000..8e3fe73e13 --- /dev/null +++ b/Jint/Runtime/Interop/GetterFunctionInstance.Async.cs @@ -0,0 +1,14 @@ +using Jint.Native; +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interop +{ + /// + /// Represents a FunctionInstance wrapping a Clr getter. + /// + public sealed partial class GetterFunctionInstance : FunctionInstance + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interop/GetterFunctionInstance.cs b/Jint/Runtime/Interop/GetterFunctionInstance.cs index 89bbc4e1b0..3fec012e5c 100644 --- a/Jint/Runtime/Interop/GetterFunctionInstance.cs +++ b/Jint/Runtime/Interop/GetterFunctionInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Jint.Native; using Jint.Native.Function; @@ -7,7 +8,7 @@ namespace Jint.Runtime.Interop /// /// Represents a FunctionInstance wrapping a Clr getter. /// - public sealed class GetterFunctionInstance: FunctionInstance + public sealed partial class GetterFunctionInstance: FunctionInstance { private static readonly JsString _name = new JsString("get"); private readonly Func _getter; diff --git a/Jint/Runtime/Interop/MethodInfoFunctionInstance.Async.cs b/Jint/Runtime/Interop/MethodInfoFunctionInstance.Async.cs new file mode 100644 index 0000000000..0fcb01fb1c --- /dev/null +++ b/Jint/Runtime/Interop/MethodInfoFunctionInstance.Async.cs @@ -0,0 +1,116 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; +using Jint.Native; +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interop +{ + internal sealed partial class MethodInfoFunctionInstance : FunctionInstance + { + public async override Task CallAsync(JsValue thisObject, JsValue[] jsArguments) + { + JsValue[] ArgumentProvider(MethodDescriptor method) + { + if (method.IsExtensionMethod) + { + var jsArgumentsTemp = new JsValue[1 + jsArguments.Length]; + jsArgumentsTemp[0] = thisObject; + Array.Copy(jsArguments, 0, jsArgumentsTemp, 1, jsArguments.Length); + jsArguments = jsArgumentsTemp; + } + return method.HasParams + ? ProcessParamsArrays(jsArguments, method) + : jsArguments; + } + + var converter = Engine.ClrTypeConverter; + + object[] parameters = null; + foreach (var tuple in TypeConverter.FindBestMatch(_engine, _methods, ArgumentProvider)) + { + var method = tuple.Item1; + var arguments = tuple.Item2; + var methodParameters = method.Parameters; + + if (parameters == null || parameters.Length != methodParameters.Length) + { + parameters = new object[methodParameters.Length]; + } + var argumentsMatch = true; + + for (var i = 0; i < parameters.Length; i++) + { + var methodParameter = methodParameters[i]; + var parameterType = methodParameter.ParameterType; + var argument = arguments.Length > i ? arguments[i] : null; + + if (typeof(JsValue).IsAssignableFrom(parameterType)) + { + parameters[i] = argument; + } + else if (argument is null) + { + // optional + parameters[i] = System.Type.Missing; + } + else if (parameterType == typeof(JsValue[]) && argument.IsArray()) + { + // Handle specific case of F(params JsValue[]) + + var arrayInstance = argument.AsArray(); + var len = TypeConverter.ToInt32(arrayInstance.Get(CommonProperties.Length, this)); + var result = new JsValue[len]; + for (uint k = 0; k < len; k++) + { + result[k] = arrayInstance.TryGetValue(k, out var value) ? value : Undefined; + } + parameters[i] = result; + } + else + { + if (!converter.TryConvert(argument.ToObject(), parameterType, CultureInfo.InvariantCulture, out parameters[i])) + { + argumentsMatch = false; + break; + } + + if (parameters[i] is LambdaExpression lambdaExpression) + { + parameters[i] = lambdaExpression.Compile(); + } + } + } + + if (!argumentsMatch) + { + continue; + } + + // todo: cache method info + + // Store and leave the current execution context while performing an async operation + var saveLexEnv = _engine.ExecutionContext.LexicalEnvironment; + var saveVarEnv = _engine.ExecutionContext.VariableEnvironment; + _engine.LeaveExecutionContext(); + try + { + var result = await method.Method.Invoke(thisObject.ToObject(), parameters).AwaitWhenAsyncResult(); + return FromObject(Engine, result); + } + catch (TargetInvocationException exception) + { + ExceptionHelper.ThrowMeaningfulException(_engine, exception); + } + finally + { + // Return to the original context when continuing + _engine.EnterExecutionContext(saveLexEnv, saveVarEnv); + } + } + return ExceptionHelper.ThrowTypeError(_engine, "No public methods with the specified arguments were found."); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs index fd1e9e13d5..16620efc00 100644 --- a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs +++ b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs @@ -7,12 +7,12 @@ namespace Jint.Runtime.Interop { - internal sealed class MethodInfoFunctionInstance : FunctionInstance + internal sealed partial class MethodInfoFunctionInstance : FunctionInstance { private static readonly JsString _name = new JsString("Function"); private readonly MethodDescriptor[] _methods; - public MethodInfoFunctionInstance(Engine engine, MethodDescriptor[] methods) + internal MethodInfoFunctionInstance(Engine engine, MethodDescriptor[] methods) : base(engine, _name) { _methods = methods; @@ -101,7 +101,9 @@ JsValue[] ArgumentProvider(MethodDescriptor method) // todo: cache method info try { - return FromObject(Engine, method.Method.Invoke(thisObject.ToObject(), parameters)); + var result = method.Method.Invoke(thisObject.ToObject(), parameters).AwaitWhenAsyncResult(); + if (!result.IsCompleted) ExceptionHelper.ThrowError(_engine, "Cannot not await call to async method from a synchroneous context. The current async invocation is possibly executing synchroneously due to a missing code implementation on the async execution path (check call stack)."); + return FromObject(Engine, result.Result); } catch (TargetInvocationException exception) { diff --git a/Jint/Runtime/Interop/NamespaceReference.Async.cs b/Jint/Runtime/Interop/NamespaceReference.Async.cs new file mode 100644 index 0000000000..8b20b53db7 --- /dev/null +++ b/Jint/Runtime/Interop/NamespaceReference.Async.cs @@ -0,0 +1,18 @@ +using Jint.Native; +using Jint.Native.Object; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interop +{ + /// + /// Any instance on this class represents a reference to a CLR namespace. + /// Accessing its properties will look for a class of the full name, or instantiate + /// a new as it assumes that the property is a deeper + /// level of the current namespace + /// + public partial class NamespaceReference : ObjectInstance, ICallable + { + public Task CallAsync(JsValue thisObject, JsValue[] arguments) + => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interop/NamespaceReference.cs b/Jint/Runtime/Interop/NamespaceReference.cs index 55262b7e43..0cbc2e35c0 100644 --- a/Jint/Runtime/Interop/NamespaceReference.cs +++ b/Jint/Runtime/Interop/NamespaceReference.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Reflection; +using System.Threading.Tasks; using Jint.Native; using Jint.Native.Object; using Jint.Runtime.Descriptors; @@ -14,7 +15,7 @@ namespace Jint.Runtime.Interop /// a new as it assumes that the property is a deeper /// level of the current namespace /// - public class NamespaceReference : ObjectInstance, ICallable + public partial class NamespaceReference : ObjectInstance, ICallable { private readonly string _path; diff --git a/Jint/Runtime/Interop/SetterFunctionInstance.Async.cs b/Jint/Runtime/Interop/SetterFunctionInstance.Async.cs new file mode 100644 index 0000000000..72a0376443 --- /dev/null +++ b/Jint/Runtime/Interop/SetterFunctionInstance.Async.cs @@ -0,0 +1,14 @@ +using Jint.Native; +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interop +{ + /// + /// Represents a FunctionInstance wrapping a Clr setter. + /// + public sealed partial class SetterFunctionInstance : FunctionInstance + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interop/SetterFunctionInstance.cs b/Jint/Runtime/Interop/SetterFunctionInstance.cs index 5f5961927d..01db698ac6 100644 --- a/Jint/Runtime/Interop/SetterFunctionInstance.cs +++ b/Jint/Runtime/Interop/SetterFunctionInstance.cs @@ -7,7 +7,7 @@ namespace Jint.Runtime.Interop /// /// Represents a FunctionInstance wrapping a Clr setter. /// - public sealed class SetterFunctionInstance : FunctionInstance + public sealed partial class SetterFunctionInstance : FunctionInstance { private static readonly JsString _name = new JsString("set"); private readonly Action _setter; diff --git a/Jint/Runtime/Interop/TypeReference.Async.cs b/Jint/Runtime/Interop/TypeReference.Async.cs new file mode 100644 index 0000000000..e1ea5368b5 --- /dev/null +++ b/Jint/Runtime/Interop/TypeReference.Async.cs @@ -0,0 +1,11 @@ +using Jint.Native; +using Jint.Native.Function; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interop +{ + public sealed partial class TypeReference : FunctionInstance, IConstructor, IObjectWrapper + { + public override Task CallAsync(JsValue thisObject, JsValue[] arguments) => Task.FromResult(Call(thisObject, arguments)); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index 27a8755a8b..00eb66632a 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -12,7 +12,7 @@ namespace Jint.Runtime.Interop { - public sealed class TypeReference : FunctionInstance, IConstructor, IObjectWrapper + public sealed partial class TypeReference : FunctionInstance, IConstructor, IObjectWrapper { private static readonly JsString _name = new JsString("typereference"); private static readonly ConcurrentDictionary _constructorCache = new(); diff --git a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.Async.cs new file mode 100644 index 0000000000..4128a24354 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class BindingPatternAssignmentExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs index 0890cef89e..007c035b62 100644 --- a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs @@ -10,7 +10,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class BindingPatternAssignmentExpression : JintExpression + internal sealed partial class BindingPatternAssignmentExpression : JintExpression { private readonly BindingPattern _pattern; private JintExpression _right; diff --git a/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.Async.cs new file mode 100644 index 0000000000..def204db73 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.Async.cs @@ -0,0 +1,58 @@ +using Jint.Native.Array; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintArrayExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + var a = _engine.Array.ConstructFast(_hasSpreads ? 0 : (uint)_expressions.Length); + var expressions = _expressions; + + uint arrayIndexCounter = 0; + for (uint i = 0; i < (uint)expressions.Length; i++) + { + var expr = expressions[i]; + if (expr == null) + { + arrayIndexCounter++; + continue; + } + + if (_hasSpreads && expr is JintSpreadExpression jse) + { + jse.GetValueAndCheckIterator(out var objectInstance, out var iterator); + // optimize for array + if (objectInstance is ArrayInstance ai) + { + var length = ai.GetLength(); + var newLength = arrayIndexCounter + length; + a.EnsureCapacity(newLength); + a.CopyValues(ai, sourceStartIndex: 0, targetStartIndex: arrayIndexCounter, length); + arrayIndexCounter += length; + a.SetLength(newLength); + } + else + { + var protocol = new ArraySpreadProtocol(_engine, a, iterator, arrayIndexCounter); + protocol.Execute(); + arrayIndexCounter += protocol._addedCount; + } + } + else + { + var value = await expr.GetValueAsync(); + a.SetIndexValue(arrayIndexCounter++, value, updateLength: false); + } + } + + if (_hasSpreads) + { + a.SetLength(arrayIndexCounter); + } + + return a; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs index 0576d91841..88dd665aa1 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs @@ -5,7 +5,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintArrayExpression : JintExpression + internal sealed partial class JintArrayExpression : JintExpression { private JintExpression[] _expressions; private bool _hasSpreads; @@ -83,7 +83,7 @@ protected override object EvaluateInternal() return a; } - + private sealed class ArraySpreadProtocol : IteratorProtocol { private readonly ArrayInstance _instance; diff --git a/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.Async.cs new file mode 100644 index 0000000000..1eed1f4174 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintArrowFunctionExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs index 54f1c7a711..3d3f7f7e06 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintArrowFunctionExpression : JintExpression + internal sealed partial class JintArrowFunctionExpression : JintExpression { private readonly JintFunctionDefinition _function; diff --git a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.Async.cs new file mode 100644 index 0000000000..85d45dd8dd --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.Async.cs @@ -0,0 +1,176 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Native.Function; +using Jint.Runtime.Environments; +using Jint.Runtime.References; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintAssignmentExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + var lref = await _left.EvaluateAsync() as Reference ?? ExceptionHelper.ThrowReferenceError(_engine); + + var rval = await _right.GetValueAsync(); + var lval = _engine.GetValue(lref, false); + + switch (_operator) + { + case AssignmentOperator.PlusAssign: + if (AreIntegerOperands(lval, rval)) + { + lval = (long)lval.AsInteger() + rval.AsInteger(); + } + else + { + var lprim = TypeConverter.ToPrimitive(lval); + var rprim = TypeConverter.ToPrimitive(rval); + + if (lprim.IsString() || rprim.IsString()) + { + if (!(lprim is JsString jsString)) + { + jsString = new JsString.ConcatenatedString(TypeConverter.ToString(lprim)); + } + + lval = jsString.Append(rprim); + } + else + { + lval = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim); + } + } + + break; + + case AssignmentOperator.MinusAssign: + lval = AreIntegerOperands(lval, rval) + ? JsNumber.Create(lval.AsInteger() - rval.AsInteger()) + : JsNumber.Create(TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval)); + break; + + case AssignmentOperator.TimesAssign: + if (AreIntegerOperands(lval, rval)) + { + lval = (long)lval.AsInteger() * rval.AsInteger(); + } + else if (lval.IsUndefined() || rval.IsUndefined()) + { + lval = Undefined.Instance; + } + else + { + lval = TypeConverter.ToNumber(lval) * TypeConverter.ToNumber(rval); + } + + break; + + case AssignmentOperator.DivideAssign: + lval = Divide(lval, rval); + break; + + case AssignmentOperator.ModuloAssign: + if (lval.IsUndefined() || rval.IsUndefined()) + { + lval = Undefined.Instance; + } + else + { + lval = TypeConverter.ToNumber(lval) % TypeConverter.ToNumber(rval); + } + + break; + + case AssignmentOperator.BitwiseAndAssign: + lval = TypeConverter.ToInt32(lval) & TypeConverter.ToInt32(rval); + break; + + case AssignmentOperator.BitwiseOrAssign: + lval = TypeConverter.ToInt32(lval) | TypeConverter.ToInt32(rval); + break; + + case AssignmentOperator.BitwiseXOrAssign: + lval = TypeConverter.ToInt32(lval) ^ TypeConverter.ToInt32(rval); + break; + + case AssignmentOperator.LeftShiftAssign: + lval = TypeConverter.ToInt32(lval) << (int)(TypeConverter.ToUint32(rval) & 0x1F); + break; + + case AssignmentOperator.RightShiftAssign: + lval = TypeConverter.ToInt32(lval) >> (int)(TypeConverter.ToUint32(rval) & 0x1F); + break; + + case AssignmentOperator.UnsignedRightShiftAssign: + lval = (uint)TypeConverter.ToInt32(lval) >> (int)(TypeConverter.ToUint32(rval) & 0x1F); + break; + + default: + return ExceptionHelper.ThrowNotImplementedException(); + } + + _engine.PutValue(lref, lval); + + _engine._referencePool.Return(lref); + return lval; + } + + internal sealed partial class SimpleAssignmentExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + JsValue rval = null; + if (_leftIdentifier != null) + { + rval = await AssignToIdentifierAsync(_engine, _leftIdentifier, _right, _evalOrArguments); + } + return rval ?? (await SetValueAsync()); + } + + private async Task SetValueAsync() + { + // slower version + var lref = await _left.EvaluateAsync() as Reference ?? ExceptionHelper.ThrowReferenceError(_engine); + lref.AssertValid(_engine); + + var rval = await _right.GetValueAsync(); + + _engine.PutValue(lref, rval); + _engine._referencePool.Return(lref); + return rval; + } + + internal async static Task AssignToIdentifierAsync(Engine engine, JintIdentifierExpression left, JintExpression right, bool hasEvalOrArguments) + { + var env = engine.ExecutionContext.LexicalEnvironment; + var strict = StrictModeScope.IsStrictModeCode; + if (LexicalEnvironment.TryGetIdentifierEnvironmentWithBindingValue( + env, + left._expressionName, + strict, + out var environmentRecord, + out _)) + { + if (strict && hasEvalOrArguments) + { + ExceptionHelper.ThrowSyntaxError(engine); + } + + var rval = (await right.GetValueAsync()).Clone(); + + if (right._expression.IsFunctionWithName()) + { + ((FunctionInstance)rval).SetFunctionName(left._expressionName.StringValue); + } + + environmentRecord.SetMutableBinding(left._expressionName, rval, strict); + return rval; + } + + return null; + } + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs index 52e348a6e3..234062081c 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs @@ -6,7 +6,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintAssignmentExpression : JintExpression + internal sealed partial class JintAssignmentExpression : JintExpression { private readonly JintExpression _left; private readonly JintExpression _right; @@ -142,7 +142,7 @@ protected override object EvaluateInternal() return lval; } - internal sealed class SimpleAssignmentExpression : JintExpression + internal sealed partial class SimpleAssignmentExpression : JintExpression { private JintExpression _left; private JintExpression _right; @@ -188,11 +188,7 @@ private JsValue SetValue() return rval; } - internal static JsValue AssignToIdentifier( - Engine engine, - JintIdentifierExpression left, - JintExpression right, - bool hasEvalOrArguments) + internal static JsValue AssignToIdentifier(Engine engine, JintIdentifierExpression left, JintExpression right, bool hasEvalOrArguments) { var env = engine.ExecutionContext.LexicalEnvironment; var strict = StrictModeScope.IsStrictModeCode; @@ -212,7 +208,7 @@ internal static JsValue AssignToIdentifier( if (right._expression.IsFunctionWithName()) { - ((FunctionInstance) rval).SetFunctionName(left._expressionName.StringValue); + ((FunctionInstance)rval).SetFunctionName(left._expressionName.StringValue); } environmentRecord.SetMutableBinding(left._expressionName, rval, strict); diff --git a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.Async.cs new file mode 100644 index 0000000000..f177eb1181 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.Async.cs @@ -0,0 +1,24 @@ +using Jint.Native; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal abstract partial class JintBinaryExpression : JintExpression + { + public async override Task GetValueAsync() + { + // need to notify correct node when taking shortcut + _engine._lastSyntaxNode = _expression; + + // we always create a JsValue + return (JsValue)await EvaluateInternalAsync(); + } + + protected async override Task EvaluateInternalAsync() + { + var left = await _left.GetValueAsync(); + var right = await _right.GetValueAsync(); + return EvaluateBinaryExpression(left, right); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs index 06219e7148..38e71301f2 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs @@ -7,7 +7,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal abstract class JintBinaryExpression : JintExpression + internal abstract partial class JintBinaryExpression : JintExpression { private readonly JintExpression _left; private readonly JintExpression _right; @@ -111,6 +111,15 @@ public override JsValue GetValue() return (JsValue) EvaluateInternal(); } + protected override object EvaluateInternal() + { + var left = _left.GetValue(); + var right = _right.GetValue(); + return EvaluateBinaryExpression(left, right); + } + + public abstract object EvaluateBinaryExpression(JsValue left, JsValue right); + public static bool StrictlyEqual(JsValue x, JsValue y) { var typeX = x._type & ~InternalTypes.InternalFlags; @@ -178,10 +187,8 @@ public StrictlyEqualBinaryExpression(Engine engine, BinaryExpression expression) { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); var equal = StrictlyEqual(left, right); return equal ? JsBoolean.True : JsBoolean.False; } @@ -193,10 +200,8 @@ public StrictlyNotEqualBinaryExpression(Engine engine, BinaryExpression expressi { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); return StrictlyEqual(left, right) ? JsBoolean.False : JsBoolean.True; @@ -209,10 +214,8 @@ public LessBinaryExpression(Engine engine, BinaryExpression expression) : base(e { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); var value = Compare(left, right); return value._type == InternalTypes.Undefined @@ -227,10 +230,8 @@ public GreaterBinaryExpression(Engine engine, BinaryExpression expression) : bas { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); var value = Compare(right, left, false); return value._type == InternalTypes.Undefined @@ -238,18 +239,15 @@ protected override object EvaluateInternal() : value; } } - + private sealed class PlusBinaryExpression : JintBinaryExpression { public PlusBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression) { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - if (AreIntegerOperands(left, right)) { return JsNumber.Create(left.AsInteger() + right.AsInteger()); @@ -258,21 +256,19 @@ protected override object EvaluateInternal() var lprim = TypeConverter.ToPrimitive(left); var rprim = TypeConverter.ToPrimitive(right); return lprim.IsString() || rprim.IsString() - ? (JsValue) JsString.Create(TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim)) + ? (JsValue)JsString.Create(TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim)) : JsNumber.Create(TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim)); } } + private sealed class MinusBinaryExpression : JintBinaryExpression { public MinusBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression) { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - return AreIntegerOperands(left, right) ? JsNumber.Create(left.AsInteger() - right.AsInteger()) : JsNumber.Create(TypeConverter.ToNumber(left) - TypeConverter.ToNumber(right)); @@ -285,14 +281,11 @@ public TimesBinaryExpression(Engine engine, BinaryExpression expression) : base( { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - if (AreIntegerOperands(left, right)) { - return JsNumber.Create((long) left.AsInteger() * right.AsInteger()); + return JsNumber.Create((long)left.AsInteger() * right.AsInteger()); } if (left.IsUndefined() || right.IsUndefined()) @@ -310,11 +303,8 @@ public DivideBinaryExpression(Engine engine, BinaryExpression expression) : base { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - return Divide(left, right); } } @@ -328,11 +318,8 @@ public EqualBinaryExpression(Engine engine, BinaryExpression expression, bool in _invert = invert; } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - return Equal(left, right) == !_invert ? JsBoolean.True : JsBoolean.False; @@ -348,16 +335,13 @@ public CompareBinaryExpression(Engine engine, BinaryExpression expression, bool _leftFirst = leftFirst; } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue leftValue, JsValue rightValue) { - var leftValue = _left.GetValue(); - var rightValue = _right.GetValue(); - var left = _leftFirst ? leftValue : rightValue; var right = _leftFirst ? rightValue : leftValue; var value = Compare(left, right, _leftFirst); - return value.IsUndefined() || ((JsBoolean) value)._value + return value.IsUndefined() || ((JsBoolean)value)._value ? JsBoolean.False : JsBoolean.True; } @@ -369,11 +353,8 @@ public InstanceOfBinaryExpression(Engine engine, BinaryExpression expression) : { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - if (!(right is FunctionInstance f)) { return ExceptionHelper.ThrowTypeError(_engine, "instanceof can only be used with a function object"); @@ -389,25 +370,20 @@ public ExponentiationBinaryExpression(Engine engine, BinaryExpression expression { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - return JsNumber.Create(Math.Pow(TypeConverter.ToNumber(left), TypeConverter.ToNumber(right))); } } + private sealed class InBinaryExpression : JintBinaryExpression { public InBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression) { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - if (!(right is ObjectInstance oi)) { return ExceptionHelper.ThrowTypeError(_engine, "in can only be used with an object"); @@ -423,11 +399,8 @@ public ModuloBinaryExpression(Engine engine, BinaryExpression expression) : base { } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - if (AreIntegerOperands(left, right)) { var leftInteger = left.AsInteger(); @@ -443,7 +416,8 @@ protected override object EvaluateInternal() return Undefined.Instance; } - return JsNumber.Create(TypeConverter.ToNumber(left) % TypeConverter.ToNumber(right)); } + return JsNumber.Create(TypeConverter.ToNumber(left) % TypeConverter.ToNumber(right)); + } } private sealed class BitwiseBinaryExpression : JintBinaryExpression @@ -455,16 +429,13 @@ public BitwiseBinaryExpression(Engine engine, BinaryExpression expression) : bas _operator = expression.Operator; } - protected override object EvaluateInternal() + public override object EvaluateBinaryExpression(JsValue left, JsValue right) { - var left = _left.GetValue(); - var right = _right.GetValue(); - if (AreIntegerOperands(left, right)) { int leftValue = left.AsInteger(); int rightValue = right.AsInteger(); - + switch (_operator) { case BinaryOperator.BitwiseAnd: @@ -479,20 +450,18 @@ protected override object EvaluateInternal() JsNumber.Create(leftValue ^ rightValue); case BinaryOperator.LeftShift: - return JsNumber.Create(leftValue << (int) ((uint) rightValue & 0x1F)); + return JsNumber.Create(leftValue << (int)((uint)rightValue & 0x1F)); case BinaryOperator.RightShift: - return JsNumber.Create(leftValue >> (int) ((uint) rightValue & 0x1F)); + return JsNumber.Create(leftValue >> (int)((uint)rightValue & 0x1F)); case BinaryOperator.UnsignedRightShift: - return JsNumber.Create((uint) leftValue >> (int) ((uint) rightValue & 0x1F)); + return JsNumber.Create((uint)leftValue >> (int)((uint)rightValue & 0x1F)); default: return ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(_operator), "unknown shift operator"); } - } - return EvaluateNonInteger(left, right); } diff --git a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.Async.cs new file mode 100644 index 0000000000..30da7f8251 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.Async.cs @@ -0,0 +1,117 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Native.Function; +using Jint.Runtime.Environments; +using Jint.Runtime.References; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintCallExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + return _calleeExpression is JintSuperExpression + ? await SuperCallAsync() + : await CallAsync(); + } + + private Task SuperCallAsync() + { + return Task.FromResult(SuperCall()); + } + + private async Task CallAsync() + { + var callee = _calleeExpression.Evaluate(); + var expression = (CallExpression)_expression; + + // todo: implement as in http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.4 + + var arguments = await ArgumentListEvaluationAsync(); + + var func = _engine.GetValue(callee, false); + var r = callee as Reference; + + if (func._type == InternalTypes.Undefined) + { + ExceptionHelper.ThrowTypeError(_engine, r == null ? "" : $"Object has no method '{r.GetReferencedName()}'"); + } + + if (!func.IsObject()) + { + if (!_engine._referenceResolver.TryGetCallable(_engine, callee, out func)) + { + ExceptionHelper.ThrowTypeError(_engine, + r == null ? "" : $"Property '{r.GetReferencedName()}' of object is not a function"); + } + } + + if (!(func is ICallable callable)) + { + var message = $"{r?.GetReferencedName() ?? ""} is not a function"; + return ExceptionHelper.ThrowTypeError(_engine, message); + } + + var thisObject = Undefined.Instance; + if (r != null) + { + var baseValue = r.GetBase(); + if ((baseValue._type & InternalTypes.ObjectEnvironmentRecord) == 0) + { + thisObject = r.GetThisValue(); + } + else + { + var env = (EnvironmentRecord)baseValue; + thisObject = env.ImplicitThisValue(); + } + + // is it a direct call to eval ? http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.1.1 + if (r.GetReferencedName() == CommonProperties.Eval && callable is EvalFunctionInstance instance) + { + var value = await instance.PerformEvalAsync(arguments, true); + _engine._referencePool.Return(r); + return value; + } + } + + var result = await _engine.CallAsync(callable, thisObject, arguments, _calleeExpression); + + if (!_cached && arguments.Length > 0) + { + _engine._jsValueArrayPool.ReturnArray(arguments); + } + + _engine._referencePool.Return(r); + return result; + } + + private async Task ArgumentListEvaluationAsync() + { + var cachedArguments = _cachedArguments; + var arguments = System.Array.Empty(); + if (_cached) + { + arguments = cachedArguments.CachedArguments; + } + else + { + if (cachedArguments.JintArguments.Length > 0) + { + if (_hasSpreads) + { + arguments = await BuildArgumentsWithSpreadsAsync(cachedArguments.JintArguments); + } + else + { + arguments = _engine._jsValueArrayPool.RentArray(cachedArguments.JintArguments.Length); + await BuildArgumentsAsync(cachedArguments.JintArguments, arguments); + } + } + } + + return arguments; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs index acf7e3093e..230d55be9b 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs @@ -7,7 +7,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintCallExpression : JintExpression + internal sealed partial class JintCallExpression : JintExpression { private CachedArgumentsHolder _cachedArguments; private bool _cached; @@ -143,7 +143,7 @@ private object Call() } else { - var env = (EnvironmentRecord) baseValue; + var env = (EnvironmentRecord)baseValue; thisObject = env.ImplicitThisValue(); } @@ -193,7 +193,6 @@ private JsValue[] ArgumentListEvaluation() return arguments; } - private class CachedArgumentsHolder { internal JintExpression[] JintArguments; diff --git a/Jint/Runtime/Interpreter/Expressions/JintClassExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintClassExpression.Async.cs new file mode 100644 index 0000000000..9b3200d11c --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintClassExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintClassExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs index 9330ebc1f6..d0dc04c4d5 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintClassExpression : JintExpression + internal sealed partial class JintClassExpression : JintExpression { public JintClassExpression(Engine engine, ClassExpression expression) : base(engine, expression) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintConditionalExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintConditionalExpression.Async.cs new file mode 100644 index 0000000000..d4c84716d2 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintConditionalExpression.Async.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintConditionalExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + return TypeConverter.ToBoolean(await _test.GetValueAsync()) + ? await _consequent.GetValueAsync() + : await _alternate.GetValueAsync(); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintConditionalExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintConditionalExpression.cs index e6520c079d..1b441bbdb3 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintConditionalExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintConditionalExpression.cs @@ -2,7 +2,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintConditionalExpression : JintExpression + internal sealed partial class JintConditionalExpression : JintExpression { private readonly JintExpression _test; private readonly JintExpression _consequent; diff --git a/Jint/Runtime/Interpreter/Expressions/JintConstantExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintConstantExpression.Async.cs new file mode 100644 index 0000000000..5abe0dfd24 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintConstantExpression.Async.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + /// + /// Constant JsValue returning expression. + /// + internal sealed partial class JintConstantExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintConstantExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintConstantExpression.cs index 4770c4e25d..84eeedb074 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintConstantExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintConstantExpression.cs @@ -6,7 +6,7 @@ namespace Jint.Runtime.Interpreter.Expressions /// /// Constant JsValue returning expression. /// - internal sealed class JintConstantExpression : JintExpression + internal sealed partial class JintConstantExpression : JintExpression { private readonly JsValue _value; diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.Async.cs new file mode 100644 index 0000000000..64afbaa67a --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.Async.cs @@ -0,0 +1,82 @@ +using System.Runtime.CompilerServices; +using Esprima.Ast; +using Jint.Native; +using Jint.Native.Array; +using Jint.Native.Iterator; +using Jint.Native.Number; +using Jint.Native.Symbol; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal abstract partial class JintExpression + { + public async virtual Task GetValueAsync() + { + return await _engine.GetValueAsync(await EvaluateAsync(), true); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task EvaluateAsync() + { + _engine._lastSyntaxNode = _expression; + if (!_initialized) + { + Initialize(); + _initialized = true; + } + return EvaluateInternalAsync(); + } + + protected abstract Task EvaluateInternalAsync(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected async static Task BuildArgumentsAsync(JintExpression[] jintExpressions, JsValue[] targetArray) + { + for (var i = 0; i < jintExpressions.Length; i++) + { + targetArray[i] = (await jintExpressions[i].GetValueAsync()).Clone(); + } + } + + protected async Task BuildArgumentsWithSpreadsAsync(JintExpression[] jintExpressions) + { + var args = new System.Collections.Generic.List(jintExpressions.Length); + for (var i = 0; i < jintExpressions.Length; i++) + { + var jintExpression = jintExpressions[i]; + if (jintExpression is JintSpreadExpression jse) + { + var iterationState = await jse.GetValueAndCheckIteratorAsync(); + var objectInstance = iterationState.instance; + var iterator = iterationState.iterator; + + // optimize for array unless someone has touched the iterator + if (objectInstance is ArrayInstance ai + && ReferenceEquals(ai.Get(GlobalSymbolRegistry.Iterator), _engine.Array.PrototypeObject._originalIteratorFunction)) + { + var length = ai.GetLength(); + for (uint j = 0; j < length; ++j) + { + if (ai.TryGetValue(j, out var value)) + { + args.Add(value); + } + } + } + else + { + var protocol = new ArraySpreadProtocol(_engine, args, iterator); + protocol.Execute(); + } + } + else + { + args.Add((await jintExpression.GetValueAsync()).Clone()); + } + } + + return args.ToArray(); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index e246675922..5db466236c 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -10,7 +10,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal abstract class JintExpression + internal abstract partial class JintExpression { // require sub-classes to set to false explicitly to skip virtual call protected bool _initialized = true; diff --git a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.Async.cs new file mode 100644 index 0000000000..9ab07f751a --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintFunctionExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs index d862f0ede7..5481ac05cb 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs @@ -4,7 +4,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintFunctionExpression : JintExpression + internal sealed partial class JintFunctionExpression : JintExpression { private readonly JintFunctionDefinition _function; diff --git a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.Async.cs new file mode 100644 index 0000000000..d4beeca706 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintIdentifierExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs index daa2d6c2c1..8c7d4cccda 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs @@ -4,7 +4,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintIdentifierExpression : JintExpression + internal sealed partial class JintIdentifierExpression : JintExpression { internal readonly EnvironmentRecord.BindingName _expressionName; private readonly JsValue _calculatedValue; diff --git a/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.Async.cs new file mode 100644 index 0000000000..0d8fd167b4 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal partial class JintLiteralExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs index 2e28b9a741..b861fa90d9 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs @@ -1,11 +1,12 @@ using System; +using System.Threading.Tasks; using Esprima; using Esprima.Ast; using Jint.Native; namespace Jint.Runtime.Interpreter.Expressions { - internal class JintLiteralExpression : JintExpression + internal partial class JintLiteralExpression : JintExpression { private JintLiteralExpression(Engine engine, Literal expression) : base(engine, expression) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.Async.cs new file mode 100644 index 0000000000..983aa385cb --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.Async.cs @@ -0,0 +1,25 @@ +using Jint.Native; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintLogicalAndExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + var left = await _left.GetValueAsync(); + + if (left is JsBoolean b && !b._value) + { + return b; + } + + if (!TypeConverter.ToBoolean(left)) + { + return left; + } + + return await _right.GetValueAsync(); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs index baafb25ed7..89b7f21882 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintLogicalAndExpression : JintExpression + internal sealed partial class JintLogicalAndExpression : JintExpression { private readonly JintExpression _left; private readonly JintExpression _right; diff --git a/Jint/Runtime/Interpreter/Expressions/JintLogicalOrExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintLogicalOrExpression.Async.cs new file mode 100644 index 0000000000..bf276d26e6 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintLogicalOrExpression.Async.cs @@ -0,0 +1,25 @@ +using Jint.Native; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintLogicalOrExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + var left = await _left.GetValueAsync(); + + if (left is JsBoolean b && b._value) + { + return b; + } + + if (TypeConverter.ToBoolean(left)) + { + return left; + } + + return await _right.GetValueAsync(); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintLogicalOrExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintLogicalOrExpression.cs index 4afa74f381..f171ec07a9 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintLogicalOrExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintLogicalOrExpression.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintLogicalOrExpression : JintExpression + internal sealed partial class JintLogicalOrExpression : JintExpression { private readonly JintExpression _left; private readonly JintExpression _right; diff --git a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.Async.cs new file mode 100644 index 0000000000..9320a5a738 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.Async.cs @@ -0,0 +1,74 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Runtime.Environments; +using Jint.Runtime.References; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + /// + /// http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.1 + /// + internal sealed partial class JintMemberExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + JsValue actualThis = null; + string baseReferenceName = null; + JsValue baseValue = null; + var isStrictModeCode = StrictModeScope.IsStrictModeCode; + + if (_objectExpression is JintIdentifierExpression identifierExpression) + { + baseReferenceName = identifierExpression._expressionName.Key.Name; + var strict = isStrictModeCode; + var env = _engine.ExecutionContext.LexicalEnvironment; + LexicalEnvironment.TryGetIdentifierEnvironmentWithBindingValue( + env, + identifierExpression._expressionName, + strict, + out _, + out baseValue); + } + else if (_objectExpression is JintThisExpression thisExpression) + { + baseValue = await thisExpression.GetValueAsync(); + } + else if (_objectExpression is JintSuperExpression) + { + var env = (FunctionEnvironmentRecord)_engine.GetThisEnvironment(); + actualThis = env.GetThisBinding(); + baseValue = env.GetSuperBase(); + } + + if (baseValue is null) + { + // fast checks failed + var baseReference = await _objectExpression.EvaluateAsync(); + if (baseReference is Reference reference) + { + baseReferenceName = reference.GetReferencedName().ToString(); + baseValue = await _engine.GetValueAsync(reference, false); + _engine._referencePool.Return(reference); + } + else + { + baseValue = await _engine.GetValueAsync(baseReference, false); + } + } + + var property = _determinedProperty ?? await _propertyExpression.GetValueAsync(); + if (baseValue.IsNullOrUndefined()) + { + TypeConverter.CheckObjectCoercible(_engine, baseValue, _memberExpression.Property, _determinedProperty?.ToString() ?? baseReferenceName); + } + + // only convert if necessary + var propertyKey = property.IsInteger() && baseValue.IsIntegerIndexedArray + ? property + : TypeConverter.ToPropertyKey(property); + + return _engine._referencePool.Rent(baseValue, propertyKey, isStrictModeCode, thisValue: actualThis); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs index d5c57efed3..0928c508d5 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs @@ -8,7 +8,7 @@ namespace Jint.Runtime.Interpreter.Expressions /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.1 /// - internal sealed class JintMemberExpression : JintExpression + internal sealed partial class JintMemberExpression : JintExpression { private MemberExpression _memberExpression; private JintExpression _objectExpression; diff --git a/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.Async.cs new file mode 100644 index 0000000000..759ec1b0be --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintMetaPropertyExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs index 911d6a2644..dd306d2617 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMetaPropertyExpression.cs @@ -1,8 +1,9 @@ using Esprima.Ast; +using System.Threading.Tasks; namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintMetaPropertyExpression : JintExpression + internal sealed partial class JintMetaPropertyExpression : JintExpression { private readonly bool _newTarget; diff --git a/Jint/Runtime/Interpreter/Expressions/JintNewExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.Async.cs new file mode 100644 index 0000000000..bb68e34988 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.Async.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading.Tasks; +using Jint.Native; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintNewExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + // todo: optimize by defining a common abstract class or interface + var jsValue = _calleeExpression.GetValue(); + + JsValue[] arguments; + if (_jintArguments.Length == 0) + { + arguments = Array.Empty(); + } + else if (_hasSpreads) + { + arguments = BuildArgumentsWithSpreads(_jintArguments); + } + else + { + arguments = _engine._jsValueArrayPool.RentArray(_jintArguments.Length); + await BuildArgumentsAsync(_jintArguments, arguments); + } + + if (!jsValue.IsConstructor) + { + ExceptionHelper.ThrowTypeError(_engine, _calleeExpression.SourceText + " is not a constructor"); + } + + // construct the new instance using the Function's constructor method + var instance = _engine.Construct((IConstructor)jsValue, arguments, jsValue, _calleeExpression); + + _engine._jsValueArrayPool.ReturnArray(arguments); + + return instance; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs index 6bfaa49425..532bedeed4 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs @@ -4,7 +4,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintNewExpression : JintExpression + internal sealed partial class JintNewExpression : JintExpression { private JintExpression _calleeExpression; private JintExpression[] _jintArguments = Array.Empty(); diff --git a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.Async.cs new file mode 100644 index 0000000000..7ca8f11faf --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.Async.cs @@ -0,0 +1,44 @@ +using Jint.Collections; +using Jint.Runtime.Descriptors; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + /// + /// http://www.ecma-international.org/ecma-262/#sec-object-initializer + /// + internal sealed partial class JintObjectExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + return _canBuildFast + ? await BuildObjectFastAsync() + : BuildObjectNormal(); + } + + + + /// + /// Version that can safely build plain object with only normal init/data fields fast. + /// + private async Task BuildObjectFastAsync() + { + var obj = _engine.Object.Construct(0); + if (_properties.Length == 0) + { + return obj; + } + + var properties = new PropertyDictionary(_properties.Length, checkExistingKeys: true); + for (var i = 0; i < _properties.Length; i++) + { + var objectProperty = _properties[i]; + var valueExpression = _valueExpressions[i]; + var propValue = (await valueExpression.GetValueAsync()).Clone(); + properties[objectProperty!._key] = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable); + } + obj.SetProperties(properties); + return obj; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs index 2f34fe7f72..60da55397b 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs @@ -12,7 +12,7 @@ namespace Jint.Runtime.Interpreter.Expressions /// /// https://tc39.es/ecma262/#sec-object-initializer /// - internal sealed class JintObjectExpression : JintExpression + internal sealed partial class JintObjectExpression : JintExpression { private JintExpression[] _valueExpressions = System.Array.Empty(); private ObjectProperty?[] _properties = System.Array.Empty(); diff --git a/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.Async.cs new file mode 100644 index 0000000000..8a77814fc9 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.Async.cs @@ -0,0 +1,21 @@ +using Jint.Native; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintSequenceExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + var result = Undefined.Instance; + var expressions = _expressions; + for (var i = 0; i < (uint)expressions.Length; i++) + { + var expression = expressions[i]; + result = await expression.GetValueAsync(); + } + + return result; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs index 9b5d021d90..cdbd1df3b8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintSequenceExpression.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintSequenceExpression : JintExpression + internal sealed partial class JintSequenceExpression : JintExpression { private JintExpression[] _expressions; @@ -14,7 +14,7 @@ public JintSequenceExpression(Engine engine, SequenceExpression expression) : ba protected override void Initialize() { - var expression = (SequenceExpression) _expression; + var expression = (SequenceExpression)_expression; _expressions = new JintExpression[expression.Expressions.Count]; for (var i = 0; i < expression.Expressions.Count; i++) { @@ -26,7 +26,7 @@ protected override object EvaluateInternal() { var result = Undefined.Instance; var expressions = _expressions; - for (var i = 0; i < (uint) expressions.Length; i++) + for (var i = 0; i < (uint)expressions.Length; i++) { var expression = expressions[i]; result = expression.GetValue(); diff --git a/Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.Async.cs new file mode 100644 index 0000000000..775d1ad792 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.Async.cs @@ -0,0 +1,39 @@ +using Jint.Native; +using Jint.Native.Iterator; +using System; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintSpreadExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + var iterationState = await GetValueAndCheckIteratorAsync(); + return iterationState.instance; + } + + public async override Task GetValueAsync() + { + // need to notify correct node when taking shortcut + _engine._lastSyntaxNode = _expression; + + var iterationState = await GetValueAndCheckIteratorAsync(); + return iterationState.instance; + } + + internal async Task<(JsValue instance, IIterator iterator)> GetValueAndCheckIteratorAsync() + { + var instance = await _argument.GetValueAsync(); + var iterator = await instance.TryGetIteratorAsync(_engine); + + if (instance is null || iterator is null) + { + iterator = null; + ExceptionHelper.ThrowTypeError(_engine, _argumentName + " is not iterable"); + } + + return (instance: instance, iterator: iterator); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.cs index b784d3203c..3f90edfe95 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.cs @@ -4,7 +4,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintSpreadExpression : JintExpression + internal sealed partial class JintSpreadExpression : JintExpression { private readonly JintExpression _argument; private readonly string _argumentName; diff --git a/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.Async.cs new file mode 100644 index 0000000000..2fd4f89453 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintSuperExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.cs index 8c481a0c13..57f97eecf7 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintSuperExpression : JintExpression + internal sealed partial class JintSuperExpression : JintExpression { public JintSuperExpression(Engine engine, Super expression) : base(engine, expression) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.Async.cs new file mode 100644 index 0000000000..7a016aa60c --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.Async.cs @@ -0,0 +1,31 @@ +using Jint.Native; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintTaggedTemplateExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + var tagger = _engine.GetValue(await _tagIdentifier.GetValueAsync()) as ICallable + ?? ExceptionHelper.ThrowTypeError(_engine, "Argument must be callable"); + + var expressions = _quasi._expressions; + + var args = _engine._jsValueArrayPool.RentArray((expressions.Length + 1)); + + var template = GetTemplateObject(); + args[0] = template; + + for (int i = 0; i < expressions.Length; ++i) + { + args[i + 1] = await expressions[i].GetValueAsync(); + } + + var result = await tagger.CallAsync(JsValue.Undefined, args); + _engine._jsValueArrayPool.ReturnArray(args); + + return result; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs index 33932eb690..0fc3f5e491 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintTaggedTemplateExpression.cs @@ -5,7 +5,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintTaggedTemplateExpression : JintExpression + internal sealed partial class JintTaggedTemplateExpression : JintExpression { internal static readonly JsString PropertyRaw = new JsString("raw"); diff --git a/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.Async.cs new file mode 100644 index 0000000000..6ff4924554 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintTemplateLiteralExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs index 3917c5c0a8..6087ac2377 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs @@ -4,7 +4,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintTemplateLiteralExpression : JintExpression + internal sealed partial class JintTemplateLiteralExpression : JintExpression { internal readonly TemplateLiteral _templateLiteralExpression; internal JintExpression[] _expressions; diff --git a/Jint/Runtime/Interpreter/Expressions/JintThisExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintThisExpression.Async.cs new file mode 100644 index 0000000000..fb7235b8d5 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintThisExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintThisExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintThisExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintThisExpression.cs index ea2e6d5026..fdc5e73093 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintThisExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintThisExpression.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintThisExpression : JintExpression + internal sealed partial class JintThisExpression : JintExpression { public JintThisExpression(Engine engine, ThisExpression expression) : base(engine, expression) { diff --git a/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.Async.cs new file mode 100644 index 0000000000..f499db5e78 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.Async.cs @@ -0,0 +1,125 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Runtime.Environments; +using Jint.Runtime.References; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintUnaryExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + switch (_operator) + { + case UnaryOperator.Plus: + var plusValue = await _argument.GetValueAsync(); + return plusValue.IsInteger() && plusValue.AsInteger() != 0 + ? plusValue + : JsNumber.Create(TypeConverter.ToNumber(plusValue)); + + case UnaryOperator.Minus: + return EvaluateMinus(await _argument.GetValueAsync()); + + case UnaryOperator.BitwiseNot: + return JsNumber.Create(~TypeConverter.ToInt32(await _argument.GetValueAsync())); + + case UnaryOperator.LogicalNot: + return !TypeConverter.ToBoolean(await _argument.GetValueAsync()) ? JsBoolean.True : JsBoolean.False; + + case UnaryOperator.Delete: + var r = await _argument.EvaluateAsync() as Reference; + if (r == null) + { + return JsBoolean.True; + } + + if (r.IsUnresolvableReference()) + { + if (r.IsStrictReference()) + { + ExceptionHelper.ThrowSyntaxError(_engine); + } + + _engine._referencePool.Return(r); + return JsBoolean.True; + } + + if (r.IsPropertyReference()) + { + if (r.IsSuperReference()) + { + ExceptionHelper.ThrowReferenceError(_engine, r); + } + + var o = TypeConverter.ToObject(_engine, r.GetBase()); + var deleteStatus = o.Delete(r.GetReferencedName()); + if (!deleteStatus && r.IsStrictReference()) + { + ExceptionHelper.ThrowTypeError(_engine); + } + + _engine._referencePool.Return(r); + return deleteStatus ? JsBoolean.True : JsBoolean.False; + } + + if (r.IsStrictReference()) + { + ExceptionHelper.ThrowSyntaxError(_engine); + } + + var bindings = r.GetBase().TryCast(); + var property = r.GetReferencedName(); + _engine._referencePool.Return(r); + + return bindings.DeleteBinding(property.ToString()) ? JsBoolean.True : JsBoolean.False; + + case UnaryOperator.Void: + await _argument.GetValueAsync(); + return Undefined.Instance; + + case UnaryOperator.TypeOf: + var value = await _argument.EvaluateAsync(); + r = value as Reference; + if (r != null) + { + if (r.IsUnresolvableReference()) + { + _engine._referencePool.Return(r); + return JsString.UndefinedString; + } + } + + var v = await _argument.GetValueAsync(); + + if (v.IsUndefined()) + { + return JsString.UndefinedString; + } + + if (v.IsNull()) + { + return JsString.ObjectString; + } + + switch (v.Type) + { + case Types.Boolean: return JsString.BooleanString; + case Types.Number: return JsString.NumberString; + case Types.String: return JsString.StringString; + case Types.Symbol: return JsString.SymbolString; + } + + if (v.IsCallable) + { + return JsString.FunctionString; + } + + return JsString.ObjectString; + + default: + return ExceptionHelper.ThrowArgumentException(); + } + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs index c35f71ae6b..694496dbd8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs @@ -5,7 +5,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintUnaryExpression : JintExpression + internal sealed partial class JintUnaryExpression : JintExpression { private readonly JintExpression _argument; private readonly UnaryOperator _operator; diff --git a/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.Async.cs new file mode 100644 index 0000000000..545726aaaa --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.Async.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class JintUpdateExpression : JintExpression + { + protected override Task EvaluateInternalAsync() => Task.FromResult(EvaluateInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs index 1d2467454e..10e4988d74 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs @@ -5,7 +5,7 @@ namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class JintUpdateExpression : JintExpression + internal sealed partial class JintUpdateExpression : JintExpression { private JintExpression _argument; private int _change; diff --git a/Jint/Runtime/Interpreter/Expressions/NullishCoalescingExpression.Async.cs b/Jint/Runtime/Interpreter/Expressions/NullishCoalescingExpression.Async.cs new file mode 100644 index 0000000000..18092d98cb --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/NullishCoalescingExpression.Async.cs @@ -0,0 +1,26 @@ +using Jint.Native; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed partial class NullishCoalescingExpression : JintExpression + { + protected async override Task EvaluateInternalAsync() + { + return await EvaluateConstantOrExpressionAsync(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private async Task EvaluateConstantOrExpressionAsync() + { + var left = await _left.GetValueAsync(); + + return !left.IsNullOrUndefined() + ? left + : _constant ?? await _right.GetValueAsync(); + } + + + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/NullishCoalescingExpression.cs b/Jint/Runtime/Interpreter/Expressions/NullishCoalescingExpression.cs index cb96560a7b..e73b8d0617 100644 --- a/Jint/Runtime/Interpreter/Expressions/NullishCoalescingExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/NullishCoalescingExpression.cs @@ -1,10 +1,10 @@ -using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native; +using System.Runtime.CompilerServices; namespace Jint.Runtime.Interpreter.Expressions { - internal sealed class NullishCoalescingExpression : JintExpression + internal sealed partial class NullishCoalescingExpression : JintExpression { private readonly JintExpression _left; private readonly JintExpression _right; diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.Async.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.Async.cs new file mode 100644 index 0000000000..4dfe5eb454 --- /dev/null +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.Async.cs @@ -0,0 +1,27 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Runtime.Interpreter.Expressions; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter +{ + /// + /// Works as memento for function execution. Optimization to cache things that don't change. + /// + internal sealed partial class JintFunctionDefinition + { + internal async Task ExecuteAsync() + { + if (Function.Expression) + { + _bodyExpression ??= JintExpression.Build(_engine, (Expression)Function.Body); + var jsValue = await _bodyExpression?.GetValueAsync() ?? Undefined.Instance; + return new Completion(CompletionType.Return, jsValue, null, Function.Body.Location); + } + + var blockStatement = (BlockStatement)Function.Body; + _bodyStatementList ??= new JintStatementList(_engine, blockStatement, blockStatement.Body); + return await _bodyStatementList.ExecuteAsync(); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index f294f044bf..b4f09bc161 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -10,7 +10,7 @@ namespace Jint.Runtime.Interpreter /// /// Works as memento for function execution. Optimization to cache things that don't change. /// - internal sealed class JintFunctionDefinition + internal sealed partial class JintFunctionDefinition { private readonly Engine _engine; diff --git a/Jint/Runtime/Interpreter/JintStatementList.Async.cs b/Jint/Runtime/Interpreter/JintStatementList.Async.cs new file mode 100644 index 0000000000..e70a7972ac --- /dev/null +++ b/Jint/Runtime/Interpreter/JintStatementList.Async.cs @@ -0,0 +1,72 @@ +using Jint.Native; +using Jint.Runtime.Interpreter.Statements; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter +{ + internal partial class JintStatementList + { + internal async Task ExecuteAsync() + { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + + if (_statement != null) + { + _engine._lastSyntaxNode = _statement; + _engine.RunBeforeExecuteStatementChecks(_statement); + } + + JintStatement s = null; + var c = new Completion(CompletionType.Normal, null, null, _engine._lastSyntaxNode?.Location ?? default); + Completion sl = c; + + // The value of a StatementList is the value of the last value-producing item in the StatementList + JsValue lastValue = null; + try + { + foreach (var pair in _jintStatements) + { + s = pair.Statement; + c = pair.Value ?? await s.ExecuteAsync(); + if (c.Type != CompletionType.Normal) + { + return new Completion( + c.Type, + c.Value ?? sl.Value, + c.Identifier, + c.Location); + } + sl = c; + lastValue = c.Value ?? lastValue; + } + } + catch (JavaScriptException v) + { + var location = v.Location == default ? s.Location : v.Location; + var completion = new Completion(CompletionType.Throw, v.Error, null, location); + return completion; + } + catch (TypeErrorException e) + { + var error = _engine.TypeError.Construct(new JsValue[] + { + e.Message + }); + return new Completion(CompletionType.Throw, error, null, s.Location); + } + catch (RangeErrorException e) + { + var error = _engine.RangeError.Construct(new JsValue[] + { + e.Message + }); + c = new Completion(CompletionType.Throw, error, null, s.Location); + } + return new Completion(c.Type, lastValue ?? JsValue.Undefined, c.Identifier, c.Location); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index 5f8f679b43..ff8a7083a9 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -6,7 +6,7 @@ namespace Jint.Runtime.Interpreter { - internal class JintStatementList + internal partial class JintStatementList { private class Pair { diff --git a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.Async.cs new file mode 100644 index 0000000000..4aa36aee9f --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.Async.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Esprima.Ast; +using Jint.Runtime.Environments; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed partial class JintBlockStatement : JintStatement + { + protected async override Task ExecuteInternalAsync() + { + LexicalEnvironment oldEnv = null; + if (_lexicalDeclarations != null) + { + oldEnv = _engine.ExecutionContext.LexicalEnvironment; + var blockEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, _engine.ExecutionContext.LexicalEnvironment); + JintStatementList.BlockDeclarationInstantiation(blockEnv, _lexicalDeclarations); + _engine.UpdateLexicalEnvironment(blockEnv); + } + + var blockValue = await _statementList.ExecuteAsync(); + + if (oldEnv != null) + { + _engine.UpdateLexicalEnvironment(oldEnv); + } + + return blockValue; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs index ccd4084ae2..17de01ba3f 100644 --- a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs @@ -4,7 +4,7 @@ namespace Jint.Runtime.Interpreter.Statements { - internal sealed class JintBlockStatement : JintStatement + internal sealed partial class JintBlockStatement : JintStatement { private JintStatementList _statementList; private List _lexicalDeclarations; diff --git a/Jint/Runtime/Interpreter/Statements/JintBreakStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintBreakStatement.Async.cs new file mode 100644 index 0000000000..155ab70888 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintBreakStatement.Async.cs @@ -0,0 +1,13 @@ +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + /// + /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.8 + /// + internal sealed partial class JintBreakStatement : JintStatement + { + protected override Task ExecuteInternalAsync() => Task.FromResult(ExecuteInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintBreakStatement.cs b/Jint/Runtime/Interpreter/Statements/JintBreakStatement.cs index 476cc1cd44..8e28fa1c2f 100644 --- a/Jint/Runtime/Interpreter/Statements/JintBreakStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintBreakStatement.cs @@ -5,7 +5,7 @@ namespace Jint.Runtime.Interpreter.Statements /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.8 /// - internal sealed class JintBreakStatement : JintStatement + internal sealed partial class JintBreakStatement : JintStatement { private readonly string _label; diff --git a/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.Async.cs new file mode 100644 index 0000000000..c83dff1900 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.Async.cs @@ -0,0 +1,12 @@ +#nullable enable + +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed partial class JintClassDeclarationStatement : JintStatement + { + protected override Task ExecuteInternalAsync() => Task.FromResult(ExecuteInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs index 9c4969e9da..2b29e6787d 100644 --- a/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs @@ -5,7 +5,7 @@ namespace Jint.Runtime.Interpreter.Statements { - internal sealed class JintClassDeclarationStatement : JintStatement + internal sealed partial class JintClassDeclarationStatement : JintStatement { private readonly ClassDefinition _classDefinition; diff --git a/Jint/Runtime/Interpreter/Statements/JintContinueStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintContinueStatement.Async.cs new file mode 100644 index 0000000000..aab7b81951 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintContinueStatement.Async.cs @@ -0,0 +1,13 @@ +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + /// + /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.7 + /// + internal sealed partial class JintContinueStatement : JintStatement + { + protected override Task ExecuteInternalAsync() => Task.FromResult(ExecuteInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintContinueStatement.cs b/Jint/Runtime/Interpreter/Statements/JintContinueStatement.cs index e852c19bc9..87b1fef44f 100644 --- a/Jint/Runtime/Interpreter/Statements/JintContinueStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintContinueStatement.cs @@ -5,7 +5,7 @@ namespace Jint.Runtime.Interpreter.Statements /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.7 /// - internal sealed class JintContinueStatement : JintStatement + internal sealed partial class JintContinueStatement : JintStatement { private readonly string _labelName; diff --git a/Jint/Runtime/Interpreter/Statements/JintDebuggerStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintDebuggerStatement.Async.cs new file mode 100644 index 0000000000..c9841b242f --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintDebuggerStatement.Async.cs @@ -0,0 +1,10 @@ +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed partial class JintDebuggerStatement : JintStatement + { + protected override Task ExecuteInternalAsync() => Task.FromResult(ExecuteInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintDebuggerStatement.cs b/Jint/Runtime/Interpreter/Statements/JintDebuggerStatement.cs index db5071b5d6..0ffe926184 100644 --- a/Jint/Runtime/Interpreter/Statements/JintDebuggerStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintDebuggerStatement.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Statements { - internal sealed class JintDebuggerStatement : JintStatement + internal sealed partial class JintDebuggerStatement : JintStatement { public JintDebuggerStatement(Engine engine, DebuggerStatement statement) : base(engine, statement) { diff --git a/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.Async.cs new file mode 100644 index 0000000000..843e1bd5cc --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.Async.cs @@ -0,0 +1,44 @@ +using Esprima.Ast; +using Jint.Native; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + /// + /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.1 + /// + internal sealed partial class JintDoWhileStatement : JintStatement + { + protected async override Task ExecuteInternalAsync() + { + JsValue v = Undefined.Instance; + bool iterating; + + do + { + var completion = await _body.ExecuteAsync(); + if (!ReferenceEquals(completion.Value, null)) + { + v = completion.Value; + } + + if (completion.Type != CompletionType.Continue || completion.Identifier != _labelSetName) + { + if (completion.Type == CompletionType.Break && (completion.Identifier == null || completion.Identifier == _labelSetName)) + { + return new Completion(CompletionType.Normal, v, null, Location); + } + + if (completion.Type != CompletionType.Normal) + { + return completion; + } + } + + iterating = TypeConverter.ToBoolean(await _test.GetValueAsync()); + } while (iterating); + + return new Completion(CompletionType.Normal, v, null, Location); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs b/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs index c7caba496c..32fe895504 100644 --- a/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintDoWhileStatement.cs @@ -7,7 +7,7 @@ namespace Jint.Runtime.Interpreter.Statements /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.1 /// - internal sealed class JintDoWhileStatement : JintStatement + internal sealed partial class JintDoWhileStatement : JintStatement { private readonly JintStatement _body; private readonly string _labelSetName; @@ -51,5 +51,5 @@ protected override Completion ExecuteInternal() return new Completion(CompletionType.Normal, v, null, Location); } - } + } } \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintEmptyStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintEmptyStatement.Async.cs new file mode 100644 index 0000000000..877e1b2118 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintEmptyStatement.Async.cs @@ -0,0 +1,10 @@ +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed partial class JintEmptyStatement : JintStatement + { + protected override Task ExecuteInternalAsync() => Task.FromResult(ExecuteInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintEmptyStatement.cs b/Jint/Runtime/Interpreter/Statements/JintEmptyStatement.cs index 23719dca43..07cc360db7 100644 --- a/Jint/Runtime/Interpreter/Statements/JintEmptyStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintEmptyStatement.cs @@ -2,7 +2,7 @@ namespace Jint.Runtime.Interpreter.Statements { - internal sealed class JintEmptyStatement : JintStatement + internal sealed partial class JintEmptyStatement : JintStatement { public JintEmptyStatement(Engine engine, EmptyStatement statement) : base(engine, statement) { diff --git a/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.Async.cs new file mode 100644 index 0000000000..29b5546c1c --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.Async.cs @@ -0,0 +1,13 @@ +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed partial class JintExpressionStatement : JintStatement + { + protected async override Task ExecuteInternalAsync() { + var value = await _expression.GetValueAsync(); + return new Completion(CompletionType.Normal, value, null, Location); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs index 228139c23a..2e541674f9 100644 --- a/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintExpressionStatement.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Statements { - internal sealed class JintExpressionStatement : JintStatement + internal sealed partial class JintExpressionStatement : JintStatement { private readonly JintExpression _expression; diff --git a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.Async.cs new file mode 100644 index 0000000000..c528a534bf --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.Async.cs @@ -0,0 +1,175 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Native.Iterator; +using Jint.Runtime.Environments; +using Jint.Runtime.Interpreter.Expressions; +using Jint.Runtime.References; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + /// + /// https://tc39.es/ecma262/#sec-for-in-and-for-of-statements + /// + internal sealed partial class JintForInForOfStatement : JintStatement + { + protected async override Task ExecuteInternalAsync() + { + if (!HeadEvaluation(out var keyResult)) + { + return new Completion(CompletionType.Normal, JsValue.Undefined, null, Location); + } + + return await BodyEvaluationAsync(_expr, _body, keyResult, IterationKind.Enumerate, _lhsKind); + } + + /// + /// https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset + /// + private async Task BodyEvaluationAsync( + JintExpression lhs, + JintStatement stmt, + IIterator iteratorRecord, + IterationKind iterationKind, + LhsKind lhsKind, + IteratorKind iteratorKind = IteratorKind.Sync) + { + var oldEnv = _engine.ExecutionContext.LexicalEnvironment; + var v = Undefined.Instance; + var destructuring = _destructuring; + string lhsName = null; + + var completionType = CompletionType.Normal; + var close = false; + + try + { + while (true) + { + LexicalEnvironment iterationEnv = null; + if (!iteratorRecord.TryIteratorStep(out var nextResult)) + { + close = true; + return new Completion(CompletionType.Normal, v, null, Location); + } + + if (iteratorKind == IteratorKind.Async) + { + // nextResult = await nextResult; + } + + var nextValue = nextResult.Get(CommonProperties.Value); + close = true; + + Reference lhsRef = null; + if (lhsKind != LhsKind.LexicalBinding) + { + if (!destructuring) + { + lhsRef = (Reference)lhs.Evaluate(); + } + } + else + { + iterationEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, oldEnv); + if (_tdzNames != null) + { + BindingInstantiation(iterationEnv); + } + _engine.UpdateLexicalEnvironment(iterationEnv); + + if (!destructuring) + { + lhsName ??= ((Identifier)((VariableDeclaration)_leftNode).Declarations[0].Id).Name; + lhsRef = _engine.ResolveBinding(lhsName); + } + } + + if (!destructuring) + { + // If lhsRef is an abrupt completion, then + // Let status be lhsRef. + + if (lhsKind == LhsKind.LexicalBinding) + { + lhsRef.InitializeReferencedBinding(nextValue); + } + else + { + _engine.PutValue(lhsRef, nextValue); + } + } + else + { + BindingPatternAssignmentExpression.ProcessPatterns( + _engine, + _assignmentPattern, + nextValue, + iterationEnv, + checkObjectPatternPropertyReference: _lhsKind != LhsKind.VarBinding); + + if (lhsKind == LhsKind.Assignment) + { + // DestructuringAssignmentEvaluation of assignmentPattern using nextValue as the argument. + } + else if (lhsKind == LhsKind.VarBinding) + { + // BindingInitialization for lhs passing nextValue and undefined as the arguments. + } + else + { + // BindingInitialization for lhs passing nextValue and iterationEnv as arguments + } + } + + var result = await stmt.ExecuteAsync(); + _engine.UpdateLexicalEnvironment(oldEnv); + + if (!ReferenceEquals(result.Value, null)) + { + v = result.Value; + } + + if (result.Type == CompletionType.Break && (result.Identifier == null || result.Identifier == _statement?.LabelSet?.Name)) + { + completionType = CompletionType.Normal; + return new Completion(CompletionType.Normal, v, null, Location); + } + + if (result.Type != CompletionType.Continue || (result.Identifier != null && result.Identifier != _statement?.LabelSet?.Name)) + { + completionType = result.Type; + if (result.Type != CompletionType.Normal) + { + return result; + } + } + } + } + catch + { + completionType = CompletionType.Throw; + throw; + } + finally + { + if (close) + { + try + { + iteratorRecord.Close(completionType); + } + catch + { + // if we already have and exception, use it + if (completionType != CompletionType.Throw) + { + throw; + } + } + } + _engine.UpdateLexicalEnvironment(oldEnv); + } + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs index e93ae890f3..68d3d6bd2d 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs @@ -13,7 +13,7 @@ namespace Jint.Runtime.Interpreter.Statements /// /// https://tc39.es/ecma262/#sec-for-in-and-for-of-statements /// - internal sealed class JintForInForOfStatement : JintStatement + internal sealed partial class JintForInForOfStatement : JintStatement { private readonly Node _leftNode; private readonly Statement _forBody; diff --git a/Jint/Runtime/Interpreter/Statements/JintForStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintForStatement.Async.cs new file mode 100644 index 0000000000..c7167d0a59 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintForStatement.Async.cs @@ -0,0 +1,108 @@ +using Esprima.Ast; +using Jint.Native; +using Jint.Runtime.Environments; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + /// + /// https://tc39.es/ecma262/#sec-forbodyevaluation + /// + internal sealed partial class JintForStatement : JintStatement + { + protected async override Task ExecuteInternalAsync() + { + LexicalEnvironment oldEnv = null; + LexicalEnvironment loopEnv = null; + if (_boundNames != null) + { + oldEnv = _engine.ExecutionContext.LexicalEnvironment; + loopEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, oldEnv); + var loopEnvRec = loopEnv._record; + var kind = _initStatement._statement.Kind; + for (var i = 0; i < _boundNames.Count; i++) + { + var name = _boundNames[i]; + if (kind == VariableDeclarationKind.Const) + { + loopEnvRec.CreateImmutableBinding(name, true); + } + else + { + loopEnvRec.CreateMutableBinding(name, false); + } + } + + _engine.UpdateLexicalEnvironment(loopEnv); + } + + try + { + if (_initExpression != null) + { + await _initExpression?.GetValueAsync(); + } + else + { + await _initStatement?.ExecuteAsync(); + } + + return await ForBodyEvaluationAsync(); + } + finally + { + if (oldEnv != null) + { + _engine.UpdateLexicalEnvironment(oldEnv); + } + } + } + + private async Task ForBodyEvaluationAsync() + { + var v = Undefined.Instance; + + if (_shouldCreatePerIterationEnvironment) + { + CreatePerIterationEnvironment(); + } + + while (true) + { + if (_test != null) + { + if (!TypeConverter.ToBoolean(await _test.GetValueAsync())) + { + return new Completion(CompletionType.Normal, v, null, Location); + } + } + + var result = await _body.ExecuteAsync(); + if (!ReferenceEquals(result.Value, null)) + { + v = result.Value; + } + + if (result.Type == CompletionType.Break && (result.Identifier == null || result.Identifier == _statement?.LabelSet?.Name)) + { + return new Completion(CompletionType.Normal, result.Value, null, Location); + } + + if (result.Type != CompletionType.Continue || (result.Identifier != null && result.Identifier != _statement?.LabelSet?.Name)) + { + if (result.Type != CompletionType.Normal) + { + return result; + } + } + + if (_shouldCreatePerIterationEnvironment) + { + CreatePerIterationEnvironment(); + } + + _increment?.GetValue(); + } + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs index 078fb380e0..cb3585f859 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs @@ -1,22 +1,22 @@ -using System.Collections.Generic; using Esprima.Ast; using Jint.Native; using Jint.Runtime.Environments; using Jint.Runtime.Interpreter.Expressions; +using System.Collections.Generic; namespace Jint.Runtime.Interpreter.Statements { /// /// https://tc39.es/ecma262/#sec-forbodyevaluation /// - internal sealed class JintForStatement : JintStatement + internal sealed partial class JintForStatement : JintStatement { private JintVariableDeclaration _initStatement; private JintExpression _initExpression; - + private JintExpression _test; private JintExpression _increment; - + private JintStatement _body; private List _boundNames; @@ -35,7 +35,7 @@ protected override void Initialize() { if (_statement.Init.Type == Nodes.VariableDeclaration) { - var d = (VariableDeclaration) _statement.Init; + var d = (VariableDeclaration)_statement.Init; if (d.Kind != VariableDeclarationKind.Var) { _boundNames = new List(); @@ -46,7 +46,7 @@ protected override void Initialize() } else { - _initExpression = JintExpression.Build(_engine, (Expression) _statement.Init); + _initExpression = JintExpression.Build(_engine, (Expression)_statement.Init); } } @@ -165,13 +165,13 @@ private void CreatePerIterationEnvironment() { return; } - + var lastIterationEnv = _engine.ExecutionContext.LexicalEnvironment; var lastIterationEnvRec = lastIterationEnv._record; var outer = lastIterationEnv._outer; var thisIterationEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, outer); - var thisIterationEnvRec = (DeclarativeEnvironmentRecord) thisIterationEnv._record; - + var thisIterationEnvRec = (DeclarativeEnvironmentRecord)thisIterationEnv._record; + for (var j = 0; j < _boundNames.Count; j++) { var bn = _boundNames[j]; diff --git a/Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.Async.cs new file mode 100644 index 0000000000..b7bf4caca9 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.Async.cs @@ -0,0 +1,10 @@ +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed partial class JintFunctionDeclarationStatement : JintStatement + { + protected override Task ExecuteInternalAsync() => Task.FromResult(ExecuteInternal()); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.cs b/Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.cs index df6ae7f421..0163e73d73 100644 --- a/Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.cs @@ -1,8 +1,9 @@ using Esprima.Ast; +using System.Threading.Tasks; namespace Jint.Runtime.Interpreter.Statements { - internal sealed class JintFunctionDeclarationStatement : JintStatement + internal sealed partial class JintFunctionDeclarationStatement : JintStatement { public JintFunctionDeclarationStatement(Engine engine, FunctionDeclaration statement) : base(engine, statement) { diff --git a/Jint/Runtime/Interpreter/Statements/JintIfStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintIfStatement.Async.cs new file mode 100644 index 0000000000..6c5adc7b22 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintIfStatement.Async.cs @@ -0,0 +1,27 @@ +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed partial class JintIfStatement : JintStatement + { + protected async override Task ExecuteInternalAsync() + { + Completion result; + if (TypeConverter.ToBoolean(await _test.GetValueAsync())) + { + result = await _statementConsequent.ExecuteAsync(); + } + else if (_alternate != null) + { + result = await _alternate.ExecuteAsync(); + } + else + { + return new Completion(CompletionType.Normal, null, null, Location); + } + + return result; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs index ec8cc53153..d090ab137f 100644 --- a/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs @@ -3,7 +3,7 @@ namespace Jint.Runtime.Interpreter.Statements { - internal sealed class JintIfStatement : JintStatement + internal sealed partial class JintIfStatement : JintStatement { private readonly JintStatement _statementConsequent; private readonly JintExpression _test; diff --git a/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.Async.cs new file mode 100644 index 0000000000..bedb216fb4 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.Async.cs @@ -0,0 +1,23 @@ +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed partial class JintLabeledStatement : JintStatement + { + protected async override Task ExecuteInternalAsync() + { + // TODO: Esprima added Statement.Label, maybe not necessary as this line is finding the + // containing label and could keep a table per program with all the labels + // labeledStatement.Body.LabelSet = labeledStatement.Label; + var result = await _body.ExecuteAsync(); + if (result.Type == CompletionType.Break && result.Identifier == _labelName) + { + var value = result.Value; + return new Completion(CompletionType.Normal, value, null, Location); + } + + return result; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.cs b/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.cs index 8652fcb39f..98064e539d 100644 --- a/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintLabeledStatement.cs @@ -2,7 +2,7 @@ namespace Jint.Runtime.Interpreter.Statements { - internal sealed class JintLabeledStatement : JintStatement + internal sealed partial class JintLabeledStatement : JintStatement { private readonly JintStatement _body; private readonly string _labelName; diff --git a/Jint/Runtime/Interpreter/Statements/JintReturnStatement.Async.cs b/Jint/Runtime/Interpreter/Statements/JintReturnStatement.Async.cs new file mode 100644 index 0000000000..bb4860b297 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintReturnStatement.Async.cs @@ -0,0 +1,20 @@ +using Esprima.Ast; +using Jint.Native; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + /// + /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.9 + /// + internal sealed partial class JintReturnStatement : JintStatement + { + protected async override Task ExecuteInternalAsync() + { + var jsValue = _argument != null + ? await _argument?.GetValueAsync() ?? Undefined.Instance + : null; + return new Completion(CompletionType.Return, jsValue, null, Location); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintReturnStatement.cs b/Jint/Runtime/Interpreter/Statements/JintReturnStatement.cs index 0d617e82b8..6c031931b9 100644 --- a/Jint/Runtime/Interpreter/Statements/JintReturnStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintReturnStatement.cs @@ -7,7 +7,7 @@ namespace Jint.Runtime.Interpreter.Statements /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.9 /// - internal sealed class JintReturnStatement : JintStatement + internal sealed partial class JintReturnStatement : JintStatement { private readonly JintExpression _argument; diff --git a/Jint/Runtime/Interpreter/Statements/JintScript.Async.cs b/Jint/Runtime/Interpreter/Statements/JintScript.Async.cs new file mode 100644 index 0000000000..98c22a9f41 --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintScript.Async.cs @@ -0,0 +1,10 @@ +using Esprima.Ast; +using System.Threading.Tasks; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed partial class JintScript : JintStatement