1/** 2 * @fileoverview A class to operate forking. 3 * 4 * This is state of forking. 5 * This has a fork list and manages it. 6 * 7 * @author Toru Nagashima 8 */ 9 10"use strict"; 11 12//------------------------------------------------------------------------------ 13// Requirements 14//------------------------------------------------------------------------------ 15 16const assert = require("assert"), 17 CodePathSegment = require("./code-path-segment"); 18 19//------------------------------------------------------------------------------ 20// Helpers 21//------------------------------------------------------------------------------ 22 23/** 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. 27 */ 28function isReachable(segment) { 29 return segment.reachable; 30} 31 32/** 33 * Creates new segments from the specific range of `context.segmentsList`. 34 * 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. 43 */ 44function makeSegments(context, begin, end, create) { 45 const list = context.segmentsList; 46 47 const normalizedBegin = begin >= 0 ? begin : list.length + begin; 48 const normalizedEnd = end >= 0 ? end : list.length + end; 49 50 const segments = []; 51 52 for (let i = 0; i < context.count; ++i) { 53 const allPrevSegments = []; 54 55 for (let j = normalizedBegin; j <= normalizedEnd; ++j) { 56 allPrevSegments.push(list[j][i]); 57 } 58 59 segments.push(create(context.idGenerator.next(), allPrevSegments)); 60 } 61 62 return segments; 63} 64 65/** 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 69 * merges segments. 70 * @param {ForkContext} context An instance. 71 * @param {CodePathSegment[]} segments Segments to merge. 72 * @returns {CodePathSegment[]} The merged segments. 73 */ 74function mergeExtraSegments(context, segments) { 75 let currentSegments = segments; 76 77 while (currentSegments.length > context.count) { 78 const merged = []; 79 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]] 84 )); 85 } 86 currentSegments = merged; 87 } 88 return currentSegments; 89} 90 91//------------------------------------------------------------------------------ 92// Public Interface 93//------------------------------------------------------------------------------ 94 95/** 96 * A class to manage forking. 97 */ 98class ForkContext { 99 100 // eslint-disable-next-line jsdoc/require-description 101 /** 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. 105 */ 106 constructor(idGenerator, upper, count) { 107 this.idGenerator = idGenerator; 108 this.upper = upper; 109 this.count = count; 110 this.segmentsList = []; 111 } 112 113 /** 114 * The head segments. 115 * @type {CodePathSegment[]} 116 */ 117 get head() { 118 const list = this.segmentsList; 119 120 return list.length === 0 ? [] : list[list.length - 1]; 121 } 122 123 /** 124 * A flag which shows empty. 125 * @type {boolean} 126 */ 127 get empty() { 128 return this.segmentsList.length === 0; 129 } 130 131 /** 132 * A flag which shows reachable. 133 * @type {boolean} 134 */ 135 get reachable() { 136 const segments = this.head; 137 138 return segments.length > 0 && segments.some(isReachable); 139 } 140 141 /** 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. 146 */ 147 makeNext(begin, end) { 148 return makeSegments(this, begin, end, CodePathSegment.newNext); 149 } 150 151 /** 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. 157 */ 158 makeUnreachable(begin, end) { 159 return makeSegments(this, begin, end, CodePathSegment.newUnreachable); 160 } 161 162 /** 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. 169 */ 170 makeDisconnected(begin, end) { 171 return makeSegments(this, begin, end, CodePathSegment.newDisconnected); 172 } 173 174 /** 175 * Adds segments into this context. 176 * The added segments become the head. 177 * @param {CodePathSegment[]} segments Segments to add. 178 * @returns {void} 179 */ 180 add(segments) { 181 assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); 182 183 this.segmentsList.push(mergeExtraSegments(this, segments)); 184 } 185 186 /** 187 * Replaces the head segments with given segments. 188 * The current head segments are removed. 189 * @param {CodePathSegment[]} segments Segments to add. 190 * @returns {void} 191 */ 192 replaceHead(segments) { 193 assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); 194 195 this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments)); 196 } 197 198 /** 199 * Adds all segments of a given fork context into this context. 200 * @param {ForkContext} context A fork context to add. 201 * @returns {void} 202 */ 203 addAll(context) { 204 assert(context.count === this.count); 205 206 const source = context.segmentsList; 207 208 for (let i = 0; i < source.length; ++i) { 209 this.segmentsList.push(source[i]); 210 } 211 } 212 213 /** 214 * Clears all segments in this context. 215 * @returns {void} 216 */ 217 clear() { 218 this.segmentsList = []; 219 } 220 221 /** 222 * Creates the root fork context. 223 * @param {IdGenerator} idGenerator An identifier generator for segments. 224 * @returns {ForkContext} New fork context. 225 */ 226 static newRoot(idGenerator) { 227 const context = new ForkContext(idGenerator, null, 1); 228 229 context.add([CodePathSegment.newRoot(idGenerator.next())]); 230 231 return context; 232 } 233 234 /** 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. 239 */ 240 static newEmpty(parentContext, forkLeavingPath) { 241 return new ForkContext( 242 parentContext.idGenerator, 243 parentContext, 244 (forkLeavingPath ? 2 : 1) * parentContext.count 245 ); 246 } 247} 248 249module.exports = ForkContext; 250