blob: b4b2f8f757a1f3e51b8dc69948185652b834b036 [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 generator.@asyncGeneratorQueueLast === null;
}
@globalPrivate
function asyncGeneratorQueueEnqueue(generator, item)
{
"use strict";
@assert(item.@asyncGeneratorQueueItemNext === null && item.@asyncGeneratorQueueItemPrevious === null);
if (generator.@asyncGeneratorQueueFirst === null) {
@assert(generator.@asyncGeneratorQueueLast === null);
generator.@asyncGeneratorQueueFirst = item;
generator.@asyncGeneratorQueueLast = item;
} else {
item.@asyncGeneratorQueueItemPrevious = generator.@asyncGeneratorQueueLast;
generator.@asyncGeneratorQueueLast.@asyncGeneratorQueueItemNext = item;
generator.@asyncGeneratorQueueLast = item;
}
}
@globalPrivate
function asyncGeneratorQueueDequeue(generator)
{
"use strict";
if (generator.@asyncGeneratorQueueFirst === null)
return null;
const result = generator.@asyncGeneratorQueueFirst;
generator.@asyncGeneratorQueueFirst = result.@asyncGeneratorQueueItemNext;
if (generator.@asyncGeneratorQueueFirst === null)
generator.@asyncGeneratorQueueLast = null;
return result;
}
@globalPrivate
function asyncGeneratorDequeue(generator)
{
"use strict";
const queue = generator.@asyncGeneratorQueue;
@assert(!@asyncGeneratorQueueIsEmpty(generator), "Async genetator's Queue is an empty List.");
return @asyncGeneratorQueueDequeue(generator);
}
@globalPrivate
function isExecutionState(generator)
{
"use strict";
return (generator.@generatorState > 0 && generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonNone)
|| generator.@generatorState === @AsyncGeneratorStateExecuting
|| generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonAwait;
}
@globalPrivate
function isSuspendYieldState(generator)
{
"use strict";
return (generator.@generatorState > 0 && generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonYield)
|| generator.@generatorState === @AsyncGeneratorStateSuspendedYield;
}
@globalPrivate
function asyncGeneratorReject(generator, exception)
{
"use strict";
@assert(typeof 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 generator.@asyncGeneratorSuspendReason === "number", "Generator is not an AsyncGenerator instance.");
const { promiseCapability } = @asyncGeneratorDequeue(generator);
promiseCapability.@resolve.@call(@undefined, { done, value: value });
return @asyncGeneratorResumeNext(generator);
}
@globalPrivate
function asyncGeneratorYield(generator, value, resumeMode)
{
"use strict";
function asyncGeneratorYieldAwaited(result)
{
generator.@asyncGeneratorSuspendReason = @AsyncGeneratorSuspendReasonYield;
@asyncGeneratorResolve(generator, result, false);
}
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 = generator.@generatorState;
generator.@generatorState = @AsyncGeneratorStateExecuting;
generator.@asyncGeneratorSuspendReason = @AsyncGeneratorSuspendReasonNone;
try {
value = generator.@generatorNext.@call(generator.@generatorThis, generator, state, resumeValue, resumeMode, generator.@generatorFrame);
if (generator.@generatorState === @AsyncGeneratorStateExecuting)
generator.@generatorState = @AsyncGeneratorStateCompleted;
} catch (error) {
generator.@generatorState = @AsyncGeneratorStateCompleted;
generator.@asyncGeneratorSuspendReason = @AsyncGeneratorSuspendReasonNone;
return @asyncGeneratorReject(generator, error);
}
if (generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonAwait) {
const onFulfilled = function(result) { @doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeNormal); };
@awaitValue(generator, value, onFulfilled);
return @undefined;
}
if (generator.@asyncGeneratorSuspendReason === @AsyncGeneratorSuspendReasonYield)
return @asyncGeneratorYield(generator, value, resumeMode);
if (generator.@generatorState === @AsyncGeneratorStateCompleted) {
generator.@asyncGeneratorSuspendReason = @AsyncGeneratorSuspendReasonNone;
return @asyncGeneratorResolve(generator, value, true);
}
return @undefined;
}
@globalPrivate
function asyncGeneratorResumeNext(generator)
{
"use strict";
@assert(typeof generator.@asyncGeneratorSuspendReason === "number", "Generator is not an AsyncGenerator instance.");
let state = 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 = generator.@asyncGeneratorQueueFirst;
if (next.resumeMode !== @GeneratorResumeModeNormal) {
if (state === @AsyncGeneratorStateSuspendedStart) {
generator.@generatorState = @AsyncGeneratorStateCompleted;
state = @AsyncGeneratorStateCompleted;
}
if (state === @AsyncGeneratorStateCompleted) {
if (next.resumeMode === @GeneratorResumeModeReturn) {
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); });
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 generator.@asyncGeneratorSuspendReason !== 'number') {
promiseCapability.@reject.@call(@undefined, new @TypeError('|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);
}