| /** |
| * @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; |