|  | /** | 
						
						
							|  |  * @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 | 
						
						
							|  | //------------------------------------------------------------------------------ | 
						
						
							|  |  | 
						
						
							|  | /** | 
						
						
							|  |  * Determines whether or not a given segment is reachable. | 
						
						
							|  |  * @param {CodePathSegment} segment The segment to check. | 
						
						
							|  |  * @returns {boolean} `true` if the segment is reachable. | 
						
						
							|  |  */ | 
						
						
							|  | function isReachable(segment) { | 
						
						
							|  |     return segment.reachable; | 
						
						
							|  | } | 
						
						
							|  | 
 | 
						
						
							|  | /** | 
						
						
							|  |  * Creates a new segment for each fork in the given context and appends it | 
						
						
							|  |  * to the end of the specified range of segments. Ultimately, this ends up calling | 
						
						
							|  |  * `new CodePathSegment()` for each of the forks using the `create` argument | 
						
						
							|  |  * as a wrapper around special behavior. | 
						
						
							|  |  * | 
						
						
							|  |  * The `startIndex` and `endIndex` arguments specify a range of segments in | 
						
						
							|  |  * `context` that should become `allPrevSegments` for the newly created | 
						
						
							|  |  * `CodePathSegment` objects. | 
						
						
							|  |  * | 
						
						
							|  |  * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and | 
						
						
							|  |  * `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to | 
						
						
							|  |  * the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of | 
						
						
							|  |  * `b`, `d`, and `f`. | 
						
						
							|  |  * @param {ForkContext} context An instance from which the previous segments | 
						
						
							|  |  *      will be obtained. | 
						
						
							|  |  * @param {number} startIndex The index of the first segment in the context | 
						
						
							|  |  *      that should be specified as previous segments for the newly created segments. | 
						
						
							|  |  * @param {number} endIndex The index of the last segment in the context | 
						
						
							|  |  *      that should be specified as previous segments for the newly created segments. | 
						
						
							|  |  * @param {Function} create A function that creates new `CodePathSegment` | 
						
						
							|  |  *      instances in a particular way. See the `CodePathSegment.new*` methods. | 
						
						
							|  |  * @returns {Array<CodePathSegment>} An array of the newly-created segments. | 
						
						
							|  |  */ | 
						
						
							|  | function createSegments(context, startIndex, endIndex, create) { | 
						
						
							|  | 
 | 
						
						
							|  |     /** @type {Array<Array<CodePathSegment>>} */ | 
						
						
							|  |     const list = context.segmentsList; | 
						
						
							|  | 
 | 
						
						
							|  |     /* | 
						
						
							|  |      * Both `startIndex` and `endIndex` work the same way: if the number is zero | 
						
						
							|  |      * or more, then the number is used as-is. If the number is negative, | 
						
						
							|  |      * then that number is added to the length of the segments list to | 
						
						
							|  |      * determine the index to use. That means -1 for either argument | 
						
						
							|  |      * is the last element, -2 is the second to last, and so on. | 
						
						
							|  |      * | 
						
						
							|  |      * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the | 
						
						
							|  |      * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function | 
						
						
							|  |      * will include items at indices 0, 1, and 2. | 
						
						
							|  |      * | 
						
						
							|  |      * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only | 
						
						
							|  |      * be using the last segment in `list`. | 
						
						
							|  |      */ | 
						
						
							|  |     const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex; | 
						
						
							|  |     const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex; | 
						
						
							|  | 
 | 
						
						
							|  |     /** @type {Array<CodePathSegment>} */ | 
						
						
							|  |     const segments = []; | 
						
						
							|  | 
 | 
						
						
							|  |     for (let i = 0; i < context.count; ++i) { | 
						
						
							|  | 
 | 
						
						
							|  |         // this is passed into `new CodePathSegment` to add to code path. | 
						
						
							|  |         const allPrevSegments = []; | 
						
						
							|  | 
 | 
						
						
							|  |         for (let j = normalizedBegin; j <= normalizedEnd; ++j) { | 
						
						
							|  |             allPrevSegments.push(list[j][i]); | 
						
						
							|  |         } | 
						
						
							|  | 
 | 
						
						
							|  |         // note: `create` is just a wrapper that augments `new CodePathSegment`. | 
						
						
							|  |         segments.push(create(context.idGenerator.next(), allPrevSegments)); | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     return segments; | 
						
						
							|  | } | 
						
						
							|  | 
 | 
						
						
							|  | /** | 
						
						
							|  |  * Inside of a `finally` block we end up with two parallel paths. If the code path | 
						
						
							|  |  * exits by a control statement (such as `break` or `continue`) from the `finally` | 
						
						
							|  |  * block, then we need to merge the remaining parallel paths back into one. | 
						
						
							|  |  * @param {ForkContext} context The fork context to work on. | 
						
						
							|  |  * @param {Array<CodePathSegment>} segments Segments to merge. | 
						
						
							|  |  * @returns {Array<CodePathSegment>} The merged segments. | 
						
						
							|  |  */ | 
						
						
							|  | function mergeExtraSegments(context, segments) { | 
						
						
							|  |     let currentSegments = segments; | 
						
						
							|  | 
 | 
						
						
							|  |     /* | 
						
						
							|  |      * We need to ensure that the array returned from this function contains no more | 
						
						
							|  |      * than the number of segments that the context allows. `context.count` indicates | 
						
						
							|  |      * how many items should be in the returned array to ensure that the new segment | 
						
						
							|  |      * entries will line up with the already existing segment entries. | 
						
						
							|  |      */ | 
						
						
							|  |     while (currentSegments.length > context.count) { | 
						
						
							|  |         const merged = []; | 
						
						
							|  | 
 | 
						
						
							|  |         /* | 
						
						
							|  |          * Because `context.count` is a factor of 2 inside of a `finally` block, | 
						
						
							|  |          * we can divide the segment count by 2 to merge the paths together. | 
						
						
							|  |          * This loops through each segment in the list and creates a new `CodePathSegment` | 
						
						
							|  |          * that has the segment and the segment two slots away as previous segments. | 
						
						
							|  |          * | 
						
						
							|  |          * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such | 
						
						
							|  |          * that: | 
						
						
							|  |          * | 
						
						
							|  |          * When `i` is 0: | 
						
						
							|  |          * a->e | 
						
						
							|  |          * c->e | 
						
						
							|  |          * | 
						
						
							|  |          * When `i` is 1: | 
						
						
							|  |          * b->f | 
						
						
							|  |          * d->f | 
						
						
							|  |          */ | 
						
						
							|  |         for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) { | 
						
						
							|  |             merged.push(CodePathSegment.newNext( | 
						
						
							|  |                 context.idGenerator.next(), | 
						
						
							|  |                 [currentSegments[i], currentSegments[i + length]] | 
						
						
							|  |             )); | 
						
						
							|  |         } | 
						
						
							|  | 
 | 
						
						
							|  |         /* | 
						
						
							|  |          * Go through the loop condition one more time to see if we have the | 
						
						
							|  |          * number of segments for the context. If not, we'll keep merging paths | 
						
						
							|  |          * of the merged segments until we get there. | 
						
						
							|  |          */ | 
						
						
							|  |         currentSegments = merged; | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     return currentSegments; | 
						
						
							|  | } | 
						
						
							|  | 
 | 
						
						
							|  | //------------------------------------------------------------------------------ | 
						
						
							|  | // Public Interface | 
						
						
							|  | //------------------------------------------------------------------------------ | 
						
						
							|  |  | 
						
						
							|  | /** | 
						
						
							|  |  * Manages the forking of code paths. | 
						
						
							|  |  */ | 
						
						
							|  | class ForkContext { | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Creates a new instance. | 
						
						
							|  |      * @param {IdGenerator} idGenerator An identifier generator for segments. | 
						
						
							|  |      * @param {ForkContext|null} upper The preceding fork context. | 
						
						
							|  |      * @param {number} count The number of parallel segments in each element | 
						
						
							|  |      *      of `segmentsList`. | 
						
						
							|  |      */ | 
						
						
							|  |     constructor(idGenerator, upper, count) { | 
						
						
							|  | 
 | 
						
						
							|  |         /** | 
						
						
							|  |          * The ID generator that will generate segment IDs for any new | 
						
						
							|  |          * segments that are created. | 
						
						
							|  |          * @type {IdGenerator} | 
						
						
							|  |          */ | 
						
						
							|  |         this.idGenerator = idGenerator; | 
						
						
							|  | 
 | 
						
						
							|  |         /** | 
						
						
							|  |          * The preceding fork context. | 
						
						
							|  |          * @type {ForkContext|null} | 
						
						
							|  |          */ | 
						
						
							|  |         this.upper = upper; | 
						
						
							|  | 
 | 
						
						
							|  |         /** | 
						
						
							|  |          * The number of elements in each element of `segmentsList`. In most | 
						
						
							|  |          * cases, this is 1 but can be 2 when there is a `finally` present, | 
						
						
							|  |          * which forks the code path outside of normal flow. In the case of nested | 
						
						
							|  |          * `finally` blocks, this can be a multiple of 2. | 
						
						
							|  |          * @type {number} | 
						
						
							|  |          */ | 
						
						
							|  |         this.count = count; | 
						
						
							|  | 
 | 
						
						
							|  |         /** | 
						
						
							|  |          * The segments within this context. Each element in this array has | 
						
						
							|  |          * `count` elements that represent one step in each fork. For example, | 
						
						
							|  |          * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path | 
						
						
							|  |          * a->c->e and one path b->d->f, and `count` is 2 because each element | 
						
						
							|  |          * is an array with two elements. | 
						
						
							|  |          * @type {Array<Array<CodePathSegment>>} | 
						
						
							|  |          */ | 
						
						
							|  |         this.segmentsList = []; | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * The segments that begin this fork context. | 
						
						
							|  |      * @type {Array<CodePathSegment>} | 
						
						
							|  |      */ | 
						
						
							|  |     get head() { | 
						
						
							|  |         const list = this.segmentsList; | 
						
						
							|  | 
 | 
						
						
							|  |         return list.length === 0 ? [] : list[list.length - 1]; | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Indicates if the context contains no segments. | 
						
						
							|  |      * @type {boolean} | 
						
						
							|  |      */ | 
						
						
							|  |     get empty() { | 
						
						
							|  |         return this.segmentsList.length === 0; | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Indicates if there are any segments that are reachable. | 
						
						
							|  |      * @type {boolean} | 
						
						
							|  |      */ | 
						
						
							|  |     get reachable() { | 
						
						
							|  |         const segments = this.head; | 
						
						
							|  | 
 | 
						
						
							|  |         return segments.length > 0 && segments.some(isReachable); | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Creates new segments in this context and appends them to the end of the | 
						
						
							|  |      * already existing `CodePathSegment`s specified by `startIndex` and | 
						
						
							|  |      * `endIndex`. | 
						
						
							|  |      * @param {number} startIndex The index of the first segment in the context | 
						
						
							|  |      *      that should be specified as previous segments for the newly created segments. | 
						
						
							|  |      * @param {number} endIndex The index of the last segment in the context | 
						
						
							|  |      *      that should be specified as previous segments for the newly created segments. | 
						
						
							|  |      * @returns {Array<CodePathSegment>} An array of the newly created segments. | 
						
						
							|  |      */ | 
						
						
							|  |     makeNext(startIndex, endIndex) { | 
						
						
							|  |         return createSegments(this, startIndex, endIndex, CodePathSegment.newNext); | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Creates new unreachable segments in this context and appends them to the end of the | 
						
						
							|  |      * already existing `CodePathSegment`s specified by `startIndex` and | 
						
						
							|  |      * `endIndex`. | 
						
						
							|  |      * @param {number} startIndex The index of the first segment in the context | 
						
						
							|  |      *      that should be specified as previous segments for the newly created segments. | 
						
						
							|  |      * @param {number} endIndex The index of the last segment in the context | 
						
						
							|  |      *      that should be specified as previous segments for the newly created segments. | 
						
						
							|  |      * @returns {Array<CodePathSegment>} An array of the newly created segments. | 
						
						
							|  |      */ | 
						
						
							|  |     makeUnreachable(startIndex, endIndex) { | 
						
						
							|  |         return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable); | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Creates new segments in this context and does not append them to the end | 
						
						
							|  |      *  of the already existing `CodePathSegment`s specified by `startIndex` and | 
						
						
							|  |      * `endIndex`. The `startIndex` and `endIndex` are only used to determine if | 
						
						
							|  |      * the new segments should be reachable. If any of the segments in this range | 
						
						
							|  |      * are reachable then the new segments are also reachable; otherwise, the new | 
						
						
							|  |      * segments are unreachable. | 
						
						
							|  |      * @param {number} startIndex The index of the first segment in the context | 
						
						
							|  |      *      that should be considered for reachability. | 
						
						
							|  |      * @param {number} endIndex The index of the last segment in the context | 
						
						
							|  |      *      that should be considered for reachability. | 
						
						
							|  |      * @returns {Array<CodePathSegment>} An array of the newly created segments. | 
						
						
							|  |      */ | 
						
						
							|  |     makeDisconnected(startIndex, endIndex) { | 
						
						
							|  |         return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected); | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Adds segments to the head of this context. | 
						
						
							|  |      * @param {Array<CodePathSegment>} segments The 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 the given segments. | 
						
						
							|  |      * The current head segments are removed. | 
						
						
							|  |      * @param {Array<CodePathSegment>} replacementHeadSegments The new head segments. | 
						
						
							|  |      * @returns {void} | 
						
						
							|  |      */ | 
						
						
							|  |     replaceHead(replacementHeadSegments) { | 
						
						
							|  |         assert( | 
						
						
							|  |             replacementHeadSegments.length >= this.count, | 
						
						
							|  |             `${replacementHeadSegments.length} >= ${this.count}` | 
						
						
							|  |         ); | 
						
						
							|  |         this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments)); | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Adds all segments of a given fork context into this context. | 
						
						
							|  |      * @param {ForkContext} otherForkContext The fork context to add from. | 
						
						
							|  |      * @returns {void} | 
						
						
							|  |      */ | 
						
						
							|  |     addAll(otherForkContext) { | 
						
						
							|  |         assert(otherForkContext.count === this.count); | 
						
						
							|  |         this.segmentsList.push(...otherForkContext.segmentsList); | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Clears all segments in this context. | 
						
						
							|  |      * @returns {void} | 
						
						
							|  |      */ | 
						
						
							|  |     clear() { | 
						
						
							|  |         this.segmentsList = []; | 
						
						
							|  |     } | 
						
						
							|  | 
 | 
						
						
							|  |     /** | 
						
						
							|  |      * Creates a new root context, meaning that there are no parent | 
						
						
							|  |      * fork contexts. | 
						
						
							|  |      * @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} shouldForkLeavingPath Indicates that we are inside of | 
						
						
							|  |      *      a `finally` block and should therefore fork the path that leaves | 
						
						
							|  |      *      `finally`. | 
						
						
							|  |      * @returns {ForkContext} New fork context. | 
						
						
							|  |      */ | 
						
						
							|  |     static newEmpty(parentContext, shouldForkLeavingPath) { | 
						
						
							|  |         return new ForkContext( | 
						
						
							|  |             parentContext.idGenerator, | 
						
						
							|  |             parentContext, | 
						
						
							|  |             (shouldForkLeavingPath ? 2 : 1) * parentContext.count | 
						
						
							|  |         ); | 
						
						
							|  |     } | 
						
						
							|  | } | 
						
						
							|  | 
 | 
						
						
							|  | module.exports = ForkContext;
 |