"use strict"; const isGenerator = require("is-generator-function"); const lastItem = require("../util/last-item"); const Pause = Symbol("Pause"); const isPromise = require("./is-promise"); module.exports = { Pause: Pause, Internal: (generator, name) => { return { __yieldcoreInternal: true, generator: generator, name: name }; }, create: function createYieldCore(rootGenerator, { onYieldInstruction, onReturn, onBeforeStackDecrease, onAfterStackIncrease }) { let running = false; let finished = false; let stack = []; let currentFrame; let lastValue; // FIXME: Clear on every stack change function decreaseStack(returnValue) { if (onBeforeStackDecrease != null) { onBeforeStackDecrease(stack, returnValue); } stack.pop(); currentFrame = lastItem(stack); } function increaseStack(frame) { stack.push(frame); currentFrame = lastItem(stack); if (onAfterStackIncrease != null) { onAfterStackIncrease(stack); } } function insertInstruction(instruction) { increaseStack({ instruction: instruction, generator: instruction(), done: false, value: undefined, forceValue: undefined, name: undefined }); } function insertInternalInstruction(generator, name) { increaseStack({ instruction: "internal", generator: generator, done: false, value: undefined, forceValue: undefined, name: name // TODO: Rename this to `tag` or something instead? Doesn't have to be a name string }); } function finalizeCurrentFrame(returnValue) { currentFrame.done = true; currentFrame.value = returnValue; let lastFrame = currentFrame; decreaseStack(returnValue); if (onReturn != null && lastFrame.instruction !== "internal") { insertInternalInstruction(onReturn(returnValue, lastFrame, stack), "_onReturn"); } else { return returnValue; } } async function startLoop() { while (finished === false && running === true) { if (currentFrame.done === true) { // This is a previously completed frame; it doesn't need any further processing decreaseStack(currentFrame.value); } else if (currentFrame.forceValue !== undefined) { lastValue = finalizeCurrentFrame(currentFrame.forceValue); } else { // FIXME: Catch let result = currentFrame.generator.next(lastValue); let action; if (result.done === true) { // value == return value action = finalizeCurrentFrame(result.value); } else if (isGenerator(result.value)) { // TODO: Figure out better semantics for onYieldGenerator? // NOTE: Currently this hook *cannot* be a generator! // if (onYieldGenerator != null) { // onYieldGenerator(result.value, currentFrame, stack); // } insertInstruction(result.value); continue; // Proceed with the next cycle immediately } else if (isPromise(result.value)) { action = await result.value; } else if (result.value === Pause) { running = false; } else if (result.value?.__yieldcoreInternal === true) { insertInternalInstruction(result.value.generator, result.value.name); continue; // FIXME: Is this correct? } else { if (onYieldInstruction != null) { insertInternalInstruction(onYieldInstruction(result.value, currentFrame, stack), "_onYieldInstruction"); } else { // TODO: Is there ever a good reason not to have an onYieldInstruction handler? Should we just disallow that case? action = result.value; } } lastValue = action; if (stack.length === 0) { finished = true; } } } return lastValue; } // Initial frame insertInstruction(rootGenerator); return { resume: function () { if (running) { throw new Error(`Already running`); } else if (finished) { throw new Error(`Program has finished already!`); } else { running = true; return startLoop(); } } }; } };