|
|
/** * @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 {
// eslint-disable-next-line jsdoc/require-description
/** * @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 segments 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;
|