async-abstraction/tape

View on GitHub
src/split.js

Summary

Maintainability
A
35 mins
Test Coverage
import assert from 'assert';

import exhaust from './exhaust.js';
import fromAsyncIterable from './fromAsyncIterable.js';

/**
 * Splits the input tape into a tape of tapes according to some set of
 * separators. See {@link _split} for the actual implementation.
 *
 * @param {Tape} tape - The input tape.
 * @param {Iterable} sep - An iterable of separators.
 * @returns {Tape} A tape of tapes.
 */
export default function split(tape, sep) {
    return fromAsyncIterable(_split(tape, sep));
}

export class Group {
    constructor(first, sep, tape) {
        this.first = first;
        this.sep = sep;
        this.tape = tape;
        this.state = 0;
    }

    async next() {
        if (this.state === 0) {
            this.state = 1;
            return {done: false, value: this.first};
        }

        if (this.state === 1) {
            const value = await this.tape.read();
            if (value !== this.tape.eof && !this.sep.has(value))
                return {done: false, value};
            this.sep = null;
            this.tape = null;
            this.state = 2;
            return {done: true};
        }

        return {done: true};
    }

    [Symbol.asyncIterator]() {
        return this;
    }
}

/**
 * Same as {@link split}, but returns an iterable rather than a tape.
 *
 * @private
 * @param {Tape} tape - The input tape.
 * @param {Iterable} sep - An iterable of separators.
 * @returns {AsyncIterableIterator} An iterable of tapes.
 */
export function _split(tape, sep) {
    return new Split(tape, sep);
}

class Split {
    constructor(tape, sep) {
        this.tape = tape;
        this.sep = new Set(sep);
        this.group = null;
        this.state = 0;
        assert(!this.sep.has(this.tape.eof));
    }

    async next() {
        if (this.state === 0) {
            if (this.group !== null) await exhaust(this.group);
            let token;
            do {
                // eslint-disable-next-line no-await-in-loop
                token = await this.tape.read();
            } while (this.sep.has(token));

            if (token === this.tape.eof) {
                this.tape = null;
                this.sep = null;
                this.group = null;
                this.state = 1;
                return {done: true};
            }

            assert(!this.sep.has(token));
            this.group = fromAsyncIterable(new Group(token, this.sep, this.tape));
            return {done: false, value: this.group};
        }

        return {done: true};
    }

    [Symbol.asyncIterator]() {
        return this;
    }
}