| 1 | 'use strict'; |
| 2 | |
| 3 | //------------------------------------------------------------------------------ |
| 4 | // Requirements |
| 5 | //------------------------------------------------------------------------------ |
| 6 | |
| 7 | // eslint-disable-next-line |
| 8 | const assert = require('./assert'); |
| 9 | // eslint-disable-next-line |
| 10 | const CodePathSegment = require('./code-path-segment'); |
| 11 | |
| 12 | //------------------------------------------------------------------------------ |
| 13 | // Helpers |
| 14 | //------------------------------------------------------------------------------ |
| 15 | |
| 16 | /** |
| 17 | * Gets whether or not a given segment is reachable. |
| 18 | * @param {CodePathSegment} segment A segment to get. |
| 19 | * @returns {boolean} `true` if the segment is reachable. |
| 20 | */ |
| 21 | function isReachable(segment) { |
| 22 | return segment.reachable; |
| 23 | } |
| 24 | |
| 25 | /** |
| 26 | * Creates new segments from the specific range of `context.segmentsList`. |
| 27 | * |
| 28 | * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and |
| 29 | * `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`. |
| 30 | * This `h` is from `b`, `d`, and `f`. |
| 31 | * @param {ForkContext} context An instance. |
| 32 | * @param {number} begin The first index of the previous segments. |
| 33 | * @param {number} end The last index of the previous segments. |
| 34 | * @param {Function} create A factory function of new segments. |
| 35 | * @returns {CodePathSegment[]} New segments. |
| 36 | */ |
| 37 | function makeSegments(context, begin, end, create) { |
| 38 | const list = context.segmentsList; |
| 39 | |
| 40 | const normalizedBegin = begin >= 0 ? begin : list.length + begin; |
| 41 | const normalizedEnd = end >= 0 ? end : list.length + end; |
| 42 | |
| 43 | const segments = []; |
| 44 | |
| 45 | for (let i = 0; i < context.count; ++i) { |
| 46 | const allPrevSegments = []; |
| 47 | |
| 48 | for (let j = normalizedBegin; j <= normalizedEnd; ++j) { |
| 49 | allPrevSegments.push(list[j][i]); |
| 50 | } |
| 51 | |
| 52 | segments.push(create(context.idGenerator.next(), allPrevSegments)); |
| 53 | } |
| 54 | |
| 55 | return segments; |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * `segments` becomes doubly in a `finally` block. Then if a code path exits by a |
| 60 | * control statement (such as `break`, `continue`) from the `finally` block, the |
| 61 | * destination's segments may be half of the source segments. In that case, this |
| 62 | * merges segments. |
| 63 | * @param {ForkContext} context An instance. |
| 64 | * @param {CodePathSegment[]} segments Segments to merge. |
| 65 | * @returns {CodePathSegment[]} The merged segments. |
| 66 | */ |
| 67 | function mergeExtraSegments(context, segments) { |
| 68 | let currentSegments = segments; |
| 69 | |
| 70 | while (currentSegments.length > context.count) { |
| 71 | const merged = []; |
| 72 | |
| 73 | for ( |
| 74 | let i = 0, length = (currentSegments.length / 2) | 0; |
| 75 | i < length; |
| 76 | ++i |
| 77 | ) { |
| 78 | merged.push( |
| 79 | CodePathSegment.newNext(context.idGenerator.next(), [ |
| 80 | currentSegments[i], |
| 81 | currentSegments[i + length], |
| 82 | ]), |
| 83 | ); |
| 84 | } |
| 85 | currentSegments = merged; |
| 86 | } |
| 87 | return currentSegments; |
| 88 | } |
| 89 | |
| 90 | //------------------------------------------------------------------------------ |
| 91 | // Public Interface |
| 92 | //------------------------------------------------------------------------------ |
| 93 | |
| 94 | /** |
| 95 | * A class to manage forking. |
| 96 | */ |
| 97 | class ForkContext { |
| 98 | /** |
| 99 | * @param {IdGenerator} idGenerator An identifier generator for segments. |
| 100 | * @param {ForkContext|null} upper An upper fork context. |
| 101 | * @param {number} count A number of parallel segments. |
| 102 | */ |
| 103 | constructor(idGenerator, upper, count) { |
| 104 | this.idGenerator = idGenerator; |
| 105 | this.upper = upper; |
| 106 | this.count = count; |
| 107 | this.segmentsList = []; |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * The head segments. |
| 112 | * @type {CodePathSegment[]} |
| 113 | */ |
| 114 | get head() { |
| 115 | const list = this.segmentsList; |
| 116 | |
| 117 | return list.length === 0 ? [] : list[list.length - 1]; |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * A flag which shows empty. |
| 122 | * @type {boolean} |
| 123 | */ |
| 124 | get empty() { |
| 125 | return this.segmentsList.length === 0; |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * A flag which shows reachable. |
| 130 | * @type {boolean} |
| 131 | */ |
| 132 | get reachable() { |
| 133 | const segments = this.head; |
| 134 | |
| 135 | return segments.length > 0 && segments.some(isReachable); |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Creates new segments from this context. |
| 140 | * @param {number} begin The first index of previous segments. |
| 141 | * @param {number} end The last index of previous segments. |
| 142 | * @returns {CodePathSegment[]} New segments. |
| 143 | */ |
| 144 | makeNext(begin, end) { |
| 145 | return makeSegments(this, begin, end, CodePathSegment.newNext); |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Creates new segments from this context. |
| 150 | * The new segments is always unreachable. |
| 151 | * @param {number} begin The first index of previous segments. |
| 152 | * @param {number} end The last index of previous segments. |
| 153 | * @returns {CodePathSegment[]} New segments. |
| 154 | */ |
| 155 | makeUnreachable(begin, end) { |
| 156 | return makeSegments(this, begin, end, CodePathSegment.newUnreachable); |
| 157 | } |
| 158 | |
| 159 | /** |
| 160 | * Creates new segments from this context. |
| 161 | * The new segments don't have connections for previous segments. |
| 162 | * But these inherit the reachable flag from this context. |
| 163 | * @param {number} begin The first index of previous segments. |
| 164 | * @param {number} end The last index of previous segments. |
| 165 | * @returns {CodePathSegment[]} New segments. |
| 166 | */ |
| 167 | makeDisconnected(begin, end) { |
| 168 | return makeSegments(this, begin, end, CodePathSegment.newDisconnected); |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * Adds segments into this context. |
| 173 | * The added segments become the head. |
| 174 | * @param {CodePathSegment[]} segments Segments to add. |
| 175 | * @returns {void} |
| 176 | */ |
| 177 | add(segments) { |
| 178 | assert( |
| 179 | segments.length >= this.count, |
| 180 | `${segments.length} >= ${this.count}`, |
| 181 | ); |
| 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( |
| 194 | segments.length >= this.count, |
| 195 | `${segments.length} >= ${this.count}`, |
| 196 | ); |
| 197 | |
| 198 | this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments)); |
| 199 | } |
| 200 | |
| 201 | /** |
| 202 | * Adds all segments of a given fork context into this context. |
| 203 | * @param {ForkContext} context A fork context to add. |
| 204 | * @returns {void} |
| 205 | */ |
| 206 | addAll(context) { |
| 207 | assert(context.count === this.count); |
| 208 | |
| 209 | const source = context.segmentsList; |
| 210 | |
| 211 | for (let i = 0; i < source.length; ++i) { |
| 212 | this.segmentsList.push(source[i]); |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | /** |
| 217 | * Clears all segments in this context. |
| 218 | * @returns {void} |
| 219 | */ |
| 220 | clear() { |
| 221 | this.segmentsList = []; |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * Creates the root fork context. |
| 226 | * @param {IdGenerator} idGenerator An identifier generator for segments. |
| 227 | * @returns {ForkContext} New fork context. |
| 228 | */ |
| 229 | static newRoot(idGenerator) { |
| 230 | const context = new ForkContext(idGenerator, null, 1); |
| 231 | |
| 232 | context.add([CodePathSegment.newRoot(idGenerator.next())]); |
| 233 | |
| 234 | return context; |
| 235 | } |
| 236 | |
| 237 | /** |
| 238 | * Creates an empty fork context preceded by a given context. |
| 239 | * @param {ForkContext} parentContext The parent fork context. |
| 240 | * @param {boolean} forkLeavingPath A flag which shows inside of `finally` block. |
| 241 | * @returns {ForkContext} New fork context. |
| 242 | */ |
| 243 | static newEmpty(parentContext, forkLeavingPath) { |
| 244 | return new ForkContext( |
| 245 | parentContext.idGenerator, |
| 246 | parentContext, |
| 247 | (forkLeavingPath ? 2 : 1) * parentContext.count, |
| 248 | ); |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | module.exports = ForkContext; |