| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 | /** * @fileoverview A class to operate forking. * * This is state of forking. * This has a fork list and manages it. * * @author Toru Nagashima */"use strict";//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------const assert = require("assert"),    CodePathSegment = require("./code-path-segment");//------------------------------------------------------------------------------// Helpers//------------------------------------------------------------------------------/** * Gets whether or not a given segment is reachable. * * @param {CodePathSegment} segment - A segment to get. * @returns {boolean} `true` if the segment is reachable. */function isReachable(segment) {    return segment.reachable;}/** * Creates new segments from the specific range of `context.segmentsList`. * * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and * `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`. * This `h` is from `b`, `d`, and `f`. * * @param {ForkContext} context - An instance. * @param {number} begin - The first index of the previous segments. * @param {number} end - The last index of the previous segments. * @param {Function} create - A factory function of new segments. * @returns {CodePathSegment[]} New segments. */function makeSegments(context, begin, end, create) {    const list = context.segmentsList;    const normalizedBegin = begin >= 0 ? begin : list.length + begin;    const normalizedEnd = end >= 0 ? end : list.length + end;    const segments = [];    for (let i = 0; i < context.count; ++i) {        const allPrevSegments = [];        for (let j = normalizedBegin; j <= normalizedEnd; ++j) {            allPrevSegments.push(list[j][i]);        }        segments.push(create(context.idGenerator.next(), allPrevSegments));    }    return segments;}/** * `segments` becomes doubly in a `finally` block. Then if a code path exits by a * control statement (such as `break`, `continue`) from the `finally` block, the * destination's segments may be half of the source segments. In that case, this * merges segments. * * @param {ForkContext} context - An instance. * @param {CodePathSegment[]} segments - Segments to merge. * @returns {CodePathSegment[]} The merged segments. */function mergeExtraSegments(context, segments) {    let currentSegments = segments;    while (currentSegments.length > context.count) {        const merged = [];        for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) {            merged.push(CodePathSegment.newNext(                context.idGenerator.next(),                [currentSegments[i], currentSegments[i + length]]            ));        }        currentSegments = merged;    }    return currentSegments;}//------------------------------------------------------------------------------// Public Interface//------------------------------------------------------------------------------/** * A class to manage forking. */class ForkContext {    /**     * @param {IdGenerator} idGenerator - An identifier generator for segments.     * @param {ForkContext|null} upper - An upper fork context.     * @param {number} count - A number of parallel segments.     */    constructor(idGenerator, upper, count) {        this.idGenerator = idGenerator;        this.upper = upper;        this.count = count;        this.segmentsList = [];    }    /**     * The head segments.     * @type {CodePathSegment[]}     */    get head() {        const list = this.segmentsList;        return list.length === 0 ? [] : list[list.length - 1];    }    /**     * A flag which shows empty.     * @type {boolean}     */    get empty() {        return this.segmentsList.length === 0;    }    /**     * A flag which shows reachable.     * @type {boolean}     */    get reachable() {        const segments = this.head;        return segments.length > 0 && segments.some(isReachable);    }    /**     * Creates new segments from this context.     *     * @param {number} begin - The first index of previous segments.     * @param {number} end - The last index of previous segments.     * @returns {CodePathSegment[]} New segments.     */    makeNext(begin, end) {        return makeSegments(this, begin, end, CodePathSegment.newNext);    }    /**     * Creates new segments from this context.     * The new segments is always unreachable.     *     * @param {number} begin - The first index of previous segments.     * @param {number} end - The last index of previous segments.     * @returns {CodePathSegment[]} New segments.     */    makeUnreachable(begin, end) {        return makeSegments(this, begin, end, CodePathSegment.newUnreachable);    }    /**     * Creates new segments from this context.     * The new segments don't have connections for previous segments.     * But these inherit the reachable flag from this context.     *     * @param {number} begin - The first index of previous segments.     * @param {number} end - The last index of previous segments.     * @returns {CodePathSegment[]} New segments.     */    makeDisconnected(begin, end) {        return makeSegments(this, begin, end, CodePathSegment.newDisconnected);    }    /**     * Adds segments into this context.     * The added segments become the head.     *     * @param {CodePathSegment[]} segments - Segments to add.     * @returns {void}     */    add(segments) {        assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);        this.segmentsList.push(mergeExtraSegments(this, segments));    }    /**     * Replaces the head segments with given segments.     * The current head segments are removed.     *     * @param {CodePathSegment[]} segments - Segments to add.     * @returns {void}     */    replaceHead(segments) {        assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);        this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments));    }    /**     * Adds all segments of a given fork context into this context.     *     * @param {ForkContext} context - A fork context to add.     * @returns {void}     */    addAll(context) {        assert(context.count === this.count);        const source = context.segmentsList;        for (let i = 0; i < source.length; ++i) {            this.segmentsList.push(source[i]);        }    }    /**     * Clears all secments in this context.     *     * @returns {void}     */    clear() {        this.segmentsList = [];    }    /**     * Creates the root fork context.     *     * @param {IdGenerator} idGenerator - An identifier generator for segments.     * @returns {ForkContext} New fork context.     */    static newRoot(idGenerator) {        const context = new ForkContext(idGenerator, null, 1);        context.add([CodePathSegment.newRoot(idGenerator.next())]);        return context;    }    /**     * Creates an empty fork context preceded by a given context.     *     * @param {ForkContext} parentContext - The parent fork context.     * @param {boolean} forkLeavingPath - A flag which shows inside of `finally` block.     * @returns {ForkContext} New fork context.     */    static newEmpty(parentContext, forkLeavingPath) {        return new ForkContext(            parentContext.idGenerator,            parentContext,            (forkLeavingPath ? 2 : 1) * parentContext.count        );    }}module.exports = ForkContext;
 |