You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
4.4 KiB
JavaScript

"use strict";
const PromiseTry = require("es6-promise-try");
const isGenerator = require("is-generator-function");
const lastItem = require("../util/last-item");
const Pause = Symbol("Pause");
const isPromise = require("./is-promise");
async function asyncGeneratorContext(generatorFunction, customHook) {
let generator = generatorFunction();
let lastResult = { done: false };
let nextValue;
while (lastResult.done === false) {
lastResult = generator.next(nextValue);
if (typeof lastResult.value?.then === "function") {
nextValue = await lastResult.value;
} else {
nextValue = customHook(lastResult.value);
}
}
return nextValue;
}
module.exports = {
Pause: Pause,
Instruction: (instruction) => {
// FIXME: Unused?
return {
__yieldcoreInstruction: true,
instruction: instruction
};
},
Internal: (generator, name) => {
return {
__yieldcoreInternal: true,
generator: generator,
name: name
};
// FIXME: propagate NotEnoughInput, handle in custom handler as a retry
},
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);
// console.log("popping stack, new frame:", currentFrame);
}
function increaseStack(frame) {
stack.push(frame);
currentFrame = lastItem(stack);
// console.log("increasing stack, new frame:", currentFrame);
if (onAfterStackIncrease != null) {
onAfterStackIncrease(stack);
}
}
function insertInstruction(instruction) {
increaseStack({
instruction: instruction,
generator: instruction(),
done: false,
value: undefined,
name: undefined
});
}
function insertInternalInstruction(generator, name) {
increaseStack({
instruction: "internal",
generator: generator,
done: false,
value: undefined,
name: name // TODO: Rename this to `tag` or something instead? Doesn't have to be a name string
});
}
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 {
// FIXME: Catch
let result = currentFrame.generator.next(lastValue);
let action;
if (result.done === true) {
// value == return value
currentFrame.done = true;
currentFrame.value = result.value;
let lastFrame = currentFrame;
decreaseStack(result.value);
if (onReturn != null && lastFrame.instruction !== "internal") {
insertInternalInstruction(onReturn(result.value, lastFrame, stack), "_onReturn");
} else {
action = 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();
}
}
};
}
};