<commit_before>// "Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed."
// https://en.wikipedia.org/wiki/Coroutine

// In this implementation, coroutines are typically created via generator functions (function* with yield statements).
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*

// In this implementation, the generator function (possibly parameterized) is referred to as a coroutine factory, and the returned iterator is referred to as the coroutine.
// Technically yielding generator functions are not required - anything that implements the contract of Coroutine<T> can be run as a coroutine.

// The coroutine is started with the first call to next on the iterator, it is suspended with yield statements, and it is resumed with additional calls to next on the iterator.
// To create an object satisfying the Coroutine<T> contract with a generator function, it must not yield values, but rather only void via a plain "yield;" statement.
// Coroutines can call other coroutines via:
// 1. yield* someOtherCoroutine(); // If the called coroutine does not return a value
// 2. const result = yield* someOtherCoroutine(); // If the called coroutine returns a value

// Coroutines are run with the runCoroutine function, which takes a Coroutine<T>, a CoroutineScheduler<T>, and a success and error callback.
// A scheduler is responsible for scheduling the next step of a coroutine, either synchronously or asynchronously.

/**
 * A Coroutine<T> is the intersection of:
 * 1. An Iterator that yields void, returns a T, and is not passed values with calls to next.
 * 2. An IterableIterator of void (since it only yields void).
 */
type CoroutineBase<TStep, TReturn> = Iterator<TStep, TReturn, void> & IterableIterator<TStep>;
export type Coroutine<T> = CoroutineBase<void, T>;
export type AsyncCoroutine<T> = CoroutineBase<void | Promise<void>, T>;

// A CoroutineStep<T> represents a single step of a coroutine, and is an IteratorResult as returned from Coroutine<T>.next().
type CoroutineStep<T> = IteratorResult<void, T>;

// A CoroutineScheduler<T> is responsible for scheduling the call to Coroutine<T>.next and invokes the success or error callback after next is called.
export type CoroutineScheduler<T> = (coroutine: AsyncCoroutine<T>, onSuccess: (stepResult: CoroutineStep<T>) => void, onError: (stepError: any) => void) => void;

// The inline scheduler simply steps the coroutine synchronously. This is useful for running a coroutine synchronously, and also as a helper function for other schedulers.
export function inlineScheduler<T>(coroutine: AsyncCoroutine<T>, onSuccess: (stepResult: CoroutineStep<T>) => void, onError: (stepError: any) => void) {
    try {
        const step = coroutine.next();

        if (!!step.value) {
            if (step.done) {
                onSuccess(step);
            } else {
                step.value.then(
                    // TODO: Ideally just set step.value to undefined and reuse the same object
                    () => onSuccess({done: step.done, value: undefined}),
                    (error) => onError(error),
                );
            }
        } else {
            if (step.done) {
                onSuccess(step);
            } else {
                // const {done, value} = step;
                // onSuccess({done, value});
                onSuccess(step as IteratorYieldResult<void>); // TODO: WTF?
            }
        }
    } catch (error) {
        onError(error);
    }
}

// The yielding scheduler steps the coroutine synchronously until the specified time interval has elapsed, then yields control so other operations can be performed.
// A single instance of a yielding scheduler could be shared across multiple coroutines to yield when their collective work exceeds the threshold.
export function createYieldingScheduler<T>(yieldAfterMS = 25) {
    let start: number | undefined;
    return (coroutine: AsyncCoroutine<T>, onSuccess: (stepResult: CoroutineStep<T>) => void, onError: (stepError: any) => void) => {
        if (start === undefined) {
            // If start is undefined, this is the first step of a coroutine.
            start = performance.now();
        } else {
            // Otherwise check if the time interval has elapsed.
            const end = performance.now();
            if (end - start > yieldAfterMS) {
                // If so, record a new start time, and schedule the coroutine step to happen later, effectively yielding control of the execution context.
                start = end;
                setTimeout(() => {
                    inlineScheduler(coroutine, onSuccess, onError);
                }, 0);
                return;
            }
        }

        // If the coroutine step was not scheduled for the future, then do step it synchronously now.
        inlineScheduler(coroutine, onSuccess, onError);
    };
}

// Runs the specified coroutine with the specified scheduler. The success or error callback will be invoked when the coroutine finishes.
export function runCoroutine<T>(coroutine: AsyncCoroutine<T>, scheduler: CoroutineScheduler<T>, onSuccess: (result: T) => void, onError: (error: any) => void, abortSignal?: AbortSignal) {
    function resume() {
        if (!abortSignal || !abortSignal.aborted) {
            scheduler(coroutine,
                (stepResult: CoroutineStep<T>) => {
                    if (stepResult.done) {
                        // If the coroutine is done, report success.<commit_msg>Update comment<commit_after>// "Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed."
// https://en.wikipedia.org/wiki/Coroutine

// In this implementation, coroutines are typically created via generator functions (function* with yield statements).
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*

// In this implementation, the generator function (possibly parameterized) is referred to as a coroutine factory, and the returned iterator is referred to as the coroutine.
// Technically yielding generator functions are not required - anything that implements the contract of Coroutine<T> can be run as a coroutine.

// The coroutine is started with the first call to next on the iterator, it is suspended with yield statements, and it is resumed with additional calls to next on the iterator.
// To create an object satisfying the Coroutine<T> contract with a generator function, it must not yield values, but rather only void via a plain "yield;" statement.
// Coroutines can call other coroutines via:
// 1. yield* someOtherCoroutine(); // If the called coroutine does not return a value
// 2. const result = yield* someOtherCoroutine(); // If the called coroutine returns a value

// Coroutines are run with the runCoroutine function, which takes a Coroutine<T>, a CoroutineScheduler<T>, and a success and error callback.
// A scheduler is responsible for scheduling the next step of a coroutine, either synchronously or asynchronously.

/**
 * A Coroutine<T> is the intersection of:
 * 1. An Iterator that yields void, returns a T, and is not passed values with calls to next.
 * 2. An IterableIterator of void (since it only yields void).
 */
type CoroutineBase<TStep, TReturn> = Iterator<TStep, TReturn, void> & IterableIterator<TStep>;
export type Coroutine<T> = CoroutineBase<void, T>;
export type AsyncCoroutine<T> = CoroutineBase<void | Promise<void>, T>;

// A CoroutineStep<T> represents a single step of a coroutine, and is an IteratorResult as returned from Coroutine<T>.next().
type CoroutineStep<T> = IteratorResult<void, T>;

// A CoroutineScheduler<T> is responsible for scheduling the call to Coroutine<T>.next and invokes the success or error callback after next is called.
export type CoroutineScheduler<T> = (coroutine: AsyncCoroutine<T>, onSuccess: (stepResult: CoroutineStep<T>) => void, onError: (stepError: any) => void) => void;

// The inline scheduler simply steps the coroutine synchronously. This is useful for running a coroutine synchronously, and also as a helper function for other schedulers.
export function inlineScheduler<T>(coroutine: AsyncCoroutine<T>, onSuccess: (stepResult: CoroutineStep<T>) => void, onError: (stepError: any) => void) {
    try {
        const step = coroutine.next();

        if (!!step.value) {
            if (step.done) {
                onSuccess(step);
            } else {
                step.value.then(
                    // TODO: Ideally just set step.value to undefined and reuse the same object
                    () => onSuccess({done: step.done, value: undefined}),
                    (error) => onError(error),
                );
            }
        } else {
            if (step.done) {
                onSuccess(step);
            } else {
                // const {done, value} = step;
                // onSuccess({done, value});
                onSuccess(step as IteratorYieldResult<void>); // TODO: Why is this typing wonky?
            }
        }
    } catch (error) {
        onError(error);
    }
}

// The yielding scheduler steps the coroutine synchronously until the specified time interval has elapsed, then yields control so other operations can be performed.
// A single instance of a yielding scheduler could be shared across multiple coroutines to yield when their collective work exceeds the threshold.
export function createYieldingScheduler<T>(yieldAfterMS = 25) {
    let start: number | undefined;
    return (coroutine: AsyncCoroutine<T>, onSuccess: (stepResult: CoroutineStep<T>) => void, onError: (stepError: any) => void) => {
        if (start === undefined) {
            // If start is undefined, this is the first step of a coroutine.
            start = performance.now();
        } else {
            // Otherwise check if the time interval has elapsed.
            const end = performance.now();
            if (end - start > yieldAfterMS) {
                // If so, record a new start time, and schedule the coroutine step to happen later, effectively yielding control of the execution context.
                start = end;
                setTimeout(() => {
                    inlineScheduler(coroutine, onSuccess, onError);
                }, 0);
                return;
            }
        }

        // If the coroutine step was not scheduled for the future, then do step it synchronously now.
        inlineScheduler(coroutine, onSuccess, onError);
    };
}

// function createObservableScheduler<T>(observable: Observable<any>) {
//     const coroutines = new Array<AsyncCoroutine<T>>();
//     const onSuccesses = new Array<(stepResult: CoroutineStep<T>) => void>();
//     const onErrors = new Array<(stepError: any) => void>();

//     const observer = observable.add(() => {
//         const count = coroutines.length;
//         for (let i = 0; i < count; i++) {
//             inlineScheduler(coroutines.pop()!, onSuccesses.pop()!, onErrors.pop()!);
//         }
//     });

//     const scheduler = (coroutine: AsyncCoroutine<T>, onSuccess: (stepResult: CoroutineStep<T>) => void, onError: (stepError: any) => void) => {
//         coroutines.push(coroutine);
//         onSuccesses.push(onSuccess);
//         onErrors.push(onError);
//     };

//     return Object.assign(scheduler, {
//         dispose: () => observable.remove(observer),
//     });
// }

// Runs the specified coroutine with the specified scheduler. The success or error callback will be invoked when the coroutine finishes.
export function runCoroutine<T>(coroutine: AsyncCoroutine<T>, scheduler: CoroutineScheduler<T>, onSuccess: (result: T) => void, onError: (error: any) => void, abortSignal?: AbortSignal) {
    function resume() {
        if (!abortSignal || !abortSignal.aborted) {
            scheduler(coroutine,
                (stepResult: CoroutineStep<T>) => {
                    if (stepResult.done) {
                        // If the coroutine is done, report success.
{'fraction_non_alphanumeric': 0.09726568934277714, 'fraction_numerical': 0.001235381321034426, 'mean_word_length': 2.8585954877661264, 'pattern_counts': {'":': 0, '<': 79, '<?xml version=': 0, '>': 115, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5357104', 'n_tokens_mistral': 3185, 'n_tokens_neox': 3204, 'n_words': 1248}