blob: d9afd4245cfad461e1c4761ee8770cac1a65eba8 [file] [log] [blame]
/*
* Copyright (C) 2017 Oleksandr Skachkov <gskachkov@gmail.com>.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@globalPrivate
function asyncGeneratorQueueIsEmpty(generator)
{
"use strict";
return @getByIdDirectPrivate(generator, "asyncGeneratorQueueLast") === null;
}
@globalPrivate
function asyncGeneratorQueueEnqueue(generator, item)
{
"use strict";
@assert(@getByIdDirectPrivate(item, "asyncGeneratorQueueItemNext") === null && @getByIdDirectPrivate(item, "asyncGeneratorQueueItemPrevious") === null);
if (@getByIdDirectPrivate(generator, "asyncGeneratorQueueFirst") === null) {
@assert(@getByIdDirectPrivate(generator, "asyncGeneratorQueueLast") === null);
@putByIdDirectPrivate(generator, "asyncGeneratorQueueFirst", item);
@putByIdDirectPrivate(generator, "asyncGeneratorQueueLast", item);
} else {
var last = @getByIdDirectPrivate(generator, "asyncGeneratorQueueLast");
@putByIdDirectPrivate(item, "asyncGeneratorQueueItemPrevious", last);
@putByIdDirectPrivate(last, "asyncGeneratorQueueItemNext", item);
@putByIdDirectPrivate(generator, "asyncGeneratorQueueLast", item);
}
}
@globalPrivate
function asyncGeneratorQueueDequeue(generator)
{
"use strict";
const result = @getByIdDirectPrivate(generator, "asyncGeneratorQueueFirst");
if (result === null)
return null;
var updatedFirst = @getByIdDirectPrivate(result, "asyncGeneratorQueueItemNext");
@putByIdDirectPrivate(generator, "asyncGeneratorQueueFirst", updatedFirst);
if (updatedFirst === null)
@putByIdDirectPrivate(generator, "asyncGeneratorQueueLast", null);
return result;
}
@globalPrivate
function asyncGeneratorDequeue(generator)
{
"use strict";
const queue = @getByIdDirectPrivate(generator, "asyncGeneratorQueue");
@assert(!@asyncGeneratorQueueIsEmpty(generator), "Async genetator's Queue is an empty List.");
return @asyncGeneratorQueueDequeue(generator);
}
@globalPrivate
function isExecutionState(generator)
{
"use strict";
var state = @getByIdDirectPrivate(generator, "generatorState");
var reason = @getByIdDirectPrivate(generator, "asyncGeneratorSuspendReason");
return (state > 0 && reason === @AsyncGeneratorSuspendReasonNone)
|| state === @AsyncGeneratorStateExecuting
|| reason === @AsyncGeneratorSuspendReasonAwait;
}
@globalPrivate
function isSuspendYieldState(generator)
{
"use strict";
var state = @getByIdDirectPrivate(generator, "generatorState");
return (state > 0 && @getByIdDirectPrivate(generator, "asyncGeneratorSuspendReason") === @AsyncGeneratorSuspendReasonYield)
|| state === @AsyncGeneratorStateSuspendedYield;
}
@globalPrivate
function asyncGeneratorReject(generator, exception)
{
"use strict";
@assert(typeof @getByIdDirectPrivate(generator, "asyncGeneratorSuspendReason") === "number", "Generator is not an AsyncGenerator instance.");
const { promiseCapability } = @asyncGeneratorDequeue(generator);
promiseCapability.@reject.@call(@undefined, exception);
return @asyncGeneratorResumeNext(generator);
}
@globalPrivate
function asyncGeneratorResolve(generator, value, done)
{
"use strict";
@assert(typeof @getByIdDirectPrivate(generator, "asyncGeneratorSuspendReason") === "number", "Generator is not an AsyncGenerator instance.");
const { promiseCapability } = @asyncGeneratorDequeue(generator);
promiseCapability.@resolve.@call(@undefined, { value, done });
return @asyncGeneratorResumeNext(generator);
}
@globalPrivate
function asyncGeneratorYield(generator, value, resumeMode)
{
"use strict";
function asyncGeneratorYieldAwaited(result)
{
@putByIdDirectPrivate(generator, "asyncGeneratorSuspendReason", @AsyncGeneratorSuspendReasonYield);
@asyncGeneratorResolve(generator, result, false);
}
@putByIdDirectPrivate(generator, "asyncGeneratorSuspendReason", @AsyncGeneratorSuspendReasonAwait);
@awaitValue(generator, value, asyncGeneratorYieldAwaited);
return @undefined;
}
@globalPrivate
function awaitValue(generator, value, onFullfiled)
{
"use strict";
const wrappedValue = @newPromiseCapability(@Promise);
const onRejected = function (result) { @doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeThrow); };
wrappedValue.@resolve.@call(@undefined, value);
wrappedValue.@promise.@then(onFullfiled, onRejected);
return wrappedValue;
}
@globalPrivate
function doAsyncGeneratorBodyCall(generator, resumeValue, resumeMode)
{
"use strict";
let value = @undefined;
let state = @getByIdDirectPrivate(generator, "generatorState");
@putByIdDirectPrivate(generator, "generatorState", @AsyncGeneratorStateExecuting);
@putByIdDirectPrivate(generator, "asyncGeneratorSuspendReason", @AsyncGeneratorSuspendReasonNone);
try {
value = @getByIdDirectPrivate(generator, "generatorNext").@call(@getByIdDirectPrivate(generator, "generatorThis"), generator, state, resumeValue, resumeMode, @getByIdDirectPrivate(generator, "generatorFrame"));
if (@getByIdDirectPrivate(generator, "generatorState") === @AsyncGeneratorStateExecuting)
@putByIdDirectPrivate(generator, "generatorState", @AsyncGeneratorStateCompleted);
} catch (error) {
@putByIdDirectPrivate(generator, "generatorState", @AsyncGeneratorStateCompleted);
@putByIdDirectPrivate(generator, "asyncGeneratorSuspendReason", @AsyncGeneratorSuspendReasonNone);
return @asyncGeneratorReject(generator, error);
}
if (@getByIdDirectPrivate(generator, "asyncGeneratorSuspendReason") === @AsyncGeneratorSuspendReasonAwait) {
const onFulfilled = function(result) { @doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeNormal); };
@awaitValue(generator, value, onFulfilled);
return @undefined;
}
if (@getByIdDirectPrivate(generator, "asyncGeneratorSuspendReason") === @AsyncGeneratorSuspendReasonYield)
return @asyncGeneratorYield(generator, value, resumeMode);
if (@getByIdDirectPrivate(generator, "generatorState") === @AsyncGeneratorStateCompleted) {
@putByIdDirectPrivate(generator, "asyncGeneratorSuspendReason", @AsyncGeneratorSuspendReasonNone);
return @asyncGeneratorResolve(generator, value, true);
}
return @undefined;
}
@globalPrivate
function asyncGeneratorResumeNext(generator)
{
"use strict";
@assert(typeof @getByIdDirectPrivate(generator, "asyncGeneratorSuspendReason") === "number", "Generator is not an AsyncGenerator instance.");
let state = @getByIdDirectPrivate(generator, "generatorState");
@assert(state !== @AsyncGeneratorStateExecuting, "Async generator should not be in executing state");
if (state === @AsyncGeneratorStateAwaitingReturn)
return @undefined;
if (@asyncGeneratorQueueIsEmpty(generator))
return @undefined;
const next = @getByIdDirectPrivate(generator, "asyncGeneratorQueueFirst");
if (next.resumeMode !== @GeneratorResumeModeNormal) {
if (state === @AsyncGeneratorStateSuspendedStart) {
@putByIdDirectPrivate(generator, "generatorState", @AsyncGeneratorStateCompleted);
state = @AsyncGeneratorStateCompleted;
}
if (state === @AsyncGeneratorStateCompleted) {
if (next.resumeMode === @GeneratorResumeModeReturn) {
@putByIdDirectPrivate(generator, "generatorState", @AsyncGeneratorStateAwaitingReturn);
const promiseCapability = @newPromiseCapability(@Promise);
promiseCapability.@resolve.@call(@undefined, next.value);
const throwawayCapabilityPromise = promiseCapability.@promise.@then(
function (result) { generator.@generatorState = @AsyncGeneratorStateCompleted; @asyncGeneratorResolve(generator, result, true); },
function (error) { generator.@generatorState = @AsyncGeneratorStateCompleted; @asyncGeneratorReject(generator, error); });
@putByIdDirectPrivate(throwawayCapabilityPromise, "promiseIsHandled", true);
return @undefined;
}
@assert(next.resumeMode === @GeneratorResumeModeThrow, "Async generator has wrong mode");
return @asyncGeneratorReject(generator, next.value);;
}
} else if (state === @AsyncGeneratorStateCompleted)
return @asyncGeneratorResolve(generator, @undefined, true);
@assert(state === @AsyncGeneratorStateSuspendedStart || @isSuspendYieldState(generator), "Async generator has wrong state");
@doAsyncGeneratorBodyCall(generator, next.value, next.resumeMode);
return @undefined;
}
@globalPrivate
function asyncGeneratorEnqueue(generator, value, resumeMode)
{
"use strict";
const promiseCapability = @newPromiseCapability(@Promise);
if (!@isObject(generator) || typeof @getByIdDirectPrivate(generator, "asyncGeneratorSuspendReason") !== 'number') {
promiseCapability.@reject.@call(@undefined, @makeTypeError('|this| should be an async generator'));
return promiseCapability.@promise;
}
@asyncGeneratorQueueEnqueue(generator, {resumeMode, value, promiseCapability, @asyncGeneratorQueueItemNext: null, @asyncGeneratorQueueItemPrevious: null});
if (!@isExecutionState(generator))
@asyncGeneratorResumeNext(generator);
return promiseCapability.@promise;
}
function next(value)
{
"use strict";
return @asyncGeneratorEnqueue(this, value, @GeneratorResumeModeNormal);
}
function return(value)
{
"use strict";
return @asyncGeneratorEnqueue(this, value, @GeneratorResumeModeReturn);
}
function throw(exception)
{
"use strict";
return @asyncGeneratorEnqueue(this, exception, @GeneratorResumeModeThrow);
}