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;
|