2 * @fileoverview A class to operate forking.
4 * This is state of forking.
5 * This has a fork list and manages it.
7 * @author Toru Nagashima
12 //------------------------------------------------------------------------------
14 //------------------------------------------------------------------------------
16 const assert = require("assert"),
17 CodePathSegment = require("./code-path-segment");
19 //------------------------------------------------------------------------------
21 //------------------------------------------------------------------------------
24 * Gets whether or not a given segment is reachable.
25 * @param {CodePathSegment} segment A segment to get.
26 * @returns {boolean} `true` if the segment is reachable.
28 function isReachable(segment) {
29 return segment.reachable;
33 * Creates new segments from the specific range of `context.segmentsList`.
35 * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
36 * `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`.
37 * This `h` is from `b`, `d`, and `f`.
38 * @param {ForkContext} context An instance.
39 * @param {number} begin The first index of the previous segments.
40 * @param {number} end The last index of the previous segments.
41 * @param {Function} create A factory function of new segments.
42 * @returns {CodePathSegment[]} New segments.
44 function makeSegments(context, begin, end, create) {
45 const list = context.segmentsList;
47 const normalizedBegin = begin >= 0 ? begin : list.length + begin;
48 const normalizedEnd = end >= 0 ? end : list.length + end;
52 for (let i = 0; i < context.count; ++i) {
53 const allPrevSegments = [];
55 for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
56 allPrevSegments.push(list[j][i]);
59 segments.push(create(context.idGenerator.next(), allPrevSegments));
66 * `segments` becomes doubly in a `finally` block. Then if a code path exits by a
67 * control statement (such as `break`, `continue`) from the `finally` block, the
68 * destination's segments may be half of the source segments. In that case, this
70 * @param {ForkContext} context An instance.
71 * @param {CodePathSegment[]} segments Segments to merge.
72 * @returns {CodePathSegment[]} The merged segments.
74 function mergeExtraSegments(context, segments) {
75 let currentSegments = segments;
77 while (currentSegments.length > context.count) {
80 for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) {
81 merged.push(CodePathSegment.newNext(
82 context.idGenerator.next(),
83 [currentSegments[i], currentSegments[i + length]]
86 currentSegments = merged;
88 return currentSegments;
91 //------------------------------------------------------------------------------
93 //------------------------------------------------------------------------------
96 * A class to manage forking.
100 // eslint-disable-next-line jsdoc/require-description
102 * @param {IdGenerator} idGenerator An identifier generator for segments.
103 * @param {ForkContext|null} upper An upper fork context.
104 * @param {number} count A number of parallel segments.
106 constructor(idGenerator, upper, count) {
107 this.idGenerator = idGenerator;
110 this.segmentsList = [];
115 * @type {CodePathSegment[]}
118 const list = this.segmentsList;
120 return list.length === 0 ? [] : list[list.length - 1];
124 * A flag which shows empty.
128 return this.segmentsList.length === 0;
132 * A flag which shows reachable.
136 const segments = this.head;
138 return segments.length > 0 && segments.some(isReachable);
142 * Creates new segments from this context.
143 * @param {number} begin The first index of previous segments.
144 * @param {number} end The last index of previous segments.
145 * @returns {CodePathSegment[]} New segments.
147 makeNext(begin, end) {
148 return makeSegments(this, begin, end, CodePathSegment.newNext);
152 * Creates new segments from this context.
153 * The new segments is always unreachable.
154 * @param {number} begin The first index of previous segments.
155 * @param {number} end The last index of previous segments.
156 * @returns {CodePathSegment[]} New segments.
158 makeUnreachable(begin, end) {
159 return makeSegments(this, begin, end, CodePathSegment.newUnreachable);
163 * Creates new segments from this context.
164 * The new segments don't have connections for previous segments.
165 * But these inherit the reachable flag from this context.
166 * @param {number} begin The first index of previous segments.
167 * @param {number} end The last index of previous segments.
168 * @returns {CodePathSegment[]} New segments.
170 makeDisconnected(begin, end) {
171 return makeSegments(this, begin, end, CodePathSegment.newDisconnected);
175 * Adds segments into this context.
176 * The added segments become the head.
177 * @param {CodePathSegment[]} segments Segments to add.
181 assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
183 this.segmentsList.push(mergeExtraSegments(this, segments));
187 * Replaces the head segments with given segments.
188 * The current head segments are removed.
189 * @param {CodePathSegment[]} segments Segments to add.
192 replaceHead(segments) {
193 assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
195 this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments));
199 * Adds all segments of a given fork context into this context.
200 * @param {ForkContext} context A fork context to add.
204 assert(context.count === this.count);
206 const source = context.segmentsList;
208 for (let i = 0; i < source.length; ++i) {
209 this.segmentsList.push(source[i]);
214 * Clears all secments in this context.
218 this.segmentsList = [];
222 * Creates the root fork context.
223 * @param {IdGenerator} idGenerator An identifier generator for segments.
224 * @returns {ForkContext} New fork context.
226 static newRoot(idGenerator) {
227 const context = new ForkContext(idGenerator, null, 1);
229 context.add([CodePathSegment.newRoot(idGenerator.next())]);
235 * Creates an empty fork context preceded by a given context.
236 * @param {ForkContext} parentContext The parent fork context.
237 * @param {boolean} forkLeavingPath A flag which shows inside of `finally` block.
238 * @returns {ForkContext} New fork context.
240 static newEmpty(parentContext, forkLeavingPath) {
241 return new ForkContext(
242 parentContext.idGenerator,
244 (forkLeavingPath ? 2 : 1) * parentContext.count
249 module.exports = ForkContext;