@@ -21,70 +21,131 @@ const assert = require("assert"),
2121//------------------------------------------------------------------------------
2222
2323/**
24- * Gets whether or not a given segment is reachable.
25- * @param {CodePathSegment } segment A segment to get .
24+ * Determines whether or not a given segment is reachable.
25+ * @param {CodePathSegment } segment The segment to check .
2626 * @returns {boolean } `true` if the segment is reachable.
2727 */
2828function isReachable ( segment ) {
2929 return segment . reachable ;
3030}
3131
3232/**
33- * Creates new segments from the specific range of `context.segmentsList`.
33+ * Creates a new segment for each fork in the given context and appends it
34+ * to the end of the specified range of segments. Ultimately, this ends up calling
35+ * `new CodePathSegment()` for each of the forks using the `create` argument
36+ * as a wrapper around special behavior.
37+ *
38+ * The `startIndex` and `endIndex` arguments specify a range of segments in
39+ * `context` that should become `allPrevSegments` for the newly created
40+ * `CodePathSegment` objects.
3441 *
3542 * 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+ * `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to
44+ * the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of
45+ * `b`, `d`, and `f`.
46+ * @param {ForkContext } context An instance from which the previous segments
47+ * will be obtained.
48+ * @param {number } startIndex The index of the first segment in the context
49+ * that should be specified as previous segments for the newly created segments.
50+ * @param {number } endIndex The index of the last segment in the context
51+ * that should be specified as previous segments for the newly created segments.
52+ * @param {Function } create A function that creates new `CodePathSegment`
53+ * instances in a particular way. See the `CodePathSegment.new*` methods.
54+ * @returns {Array<CodePathSegment> } An array of the newly-created segments.
4355 */
44- function makeSegments ( context , begin , end , create ) {
56+ function createSegments ( context , startIndex , endIndex , create ) {
57+
58+ /** @type {Array<Array<CodePathSegment>> } */
4559 const list = context . segmentsList ;
4660
47- const normalizedBegin = begin >= 0 ? begin : list . length + begin ;
48- const normalizedEnd = end >= 0 ? end : list . length + end ;
61+ /*
62+ * Both `startIndex` and `endIndex` work the same way: if the number is zero
63+ * or more, then the number is used as-is. If the number is negative,
64+ * then that number is added to the length of the segments list to
65+ * determine the index to use. That means -1 for either argument
66+ * is the last element, -2 is the second to last, and so on.
67+ *
68+ * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the
69+ * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function
70+ * will include items at indices 0, 1, and 2.
71+ *
72+ * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only
73+ * be using the last segment in `list`.
74+ */
75+ const normalizedBegin = startIndex >= 0 ? startIndex : list . length + startIndex ;
76+ const normalizedEnd = endIndex >= 0 ? endIndex : list . length + endIndex ;
4977
78+ /** @type {Array<CodePathSegment> } */
5079 const segments = [ ] ;
5180
5281 for ( let i = 0 ; i < context . count ; ++ i ) {
82+
83+ // this is passed into `new CodePathSegment` to add to code path.
5384 const allPrevSegments = [ ] ;
5485
5586 for ( let j = normalizedBegin ; j <= normalizedEnd ; ++ j ) {
5687 allPrevSegments . push ( list [ j ] [ i ] ) ;
5788 }
5889
90+ // note: `create` is just a wrapper that augments `new CodePathSegment`.
5991 segments . push ( create ( context . idGenerator . next ( ) , allPrevSegments ) ) ;
6092 }
6193
6294 return segments ;
6395}
6496
6597/**
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.
98+ * Inside of a `finally` block we end up with two parallel paths. If the code path
99+ * exits by a control statement (such as `break` or `continue`) from the `finally`
100+ * block, then we need to merge the remaining parallel paths back into one.
101+ * @param {ForkContext } context The fork context to work on.
102+ * @param {Array<CodePathSegment> } segments Segments to merge.
103+ * @returns {Array<CodePathSegment> } The merged segments.
73104 */
74105function mergeExtraSegments ( context , segments ) {
75106 let currentSegments = segments ;
76107
108+ /*
109+ * We need to ensure that the array returned from this function contains no more
110+ * than the number of segments that the context allows. `context.count` indicates
111+ * how many items should be in the returned array to ensure that the new segment
112+ * entries will line up with the already existing segment entries.
113+ */
77114 while ( currentSegments . length > context . count ) {
78115 const merged = [ ] ;
79116
80- for ( let i = 0 , length = currentSegments . length / 2 | 0 ; i < length ; ++ i ) {
117+ /*
118+ * Because `context.count` is a factor of 2 inside of a `finally` block,
119+ * we can divide the segment count by 2 to merge the paths together.
120+ * This loops through each segment in the list and creates a new `CodePathSegment`
121+ * that has the segment and the segment two slots away as previous segments.
122+ *
123+ * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such
124+ * that:
125+ *
126+ * When `i` is 0:
127+ * a->e
128+ * c->e
129+ *
130+ * When `i` is 1:
131+ * b->f
132+ * d->f
133+ */
134+ for ( let i = 0 , length = Math . floor ( currentSegments . length / 2 ) ; i < length ; ++ i ) {
81135 merged . push ( CodePathSegment . newNext (
82136 context . idGenerator . next ( ) ,
83137 [ currentSegments [ i ] , currentSegments [ i + length ] ]
84138 ) ) ;
85139 }
140+
141+ /*
142+ * Go through the loop condition one more time to see if we have the
143+ * number of segments for the context. If not, we'll keep merging paths
144+ * of the merged segments until we get there.
145+ */
86146 currentSegments = merged ;
87147 }
148+
88149 return currentSegments ;
89150}
90151
@@ -93,25 +154,55 @@ function mergeExtraSegments(context, segments) {
93154//------------------------------------------------------------------------------
94155
95156/**
96- * A class to manage forking .
157+ * Manages the forking of code paths .
97158 */
98159class ForkContext {
99160
100161 /**
162+ * Creates a new instance.
101163 * @param {IdGenerator } idGenerator An identifier generator for segments.
102- * @param {ForkContext|null } upper An upper fork context.
103- * @param {number } count A number of parallel segments.
164+ * @param {ForkContext|null } upper The preceding fork context.
165+ * @param {number } count The number of parallel segments in each element
166+ * of `segmentsList`.
104167 */
105168 constructor ( idGenerator , upper , count ) {
169+
170+ /**
171+ * The ID generator that will generate segment IDs for any new
172+ * segments that are created.
173+ * @type {IdGenerator }
174+ */
106175 this . idGenerator = idGenerator ;
176+
177+ /**
178+ * The preceding fork context.
179+ * @type {ForkContext|null }
180+ */
107181 this . upper = upper ;
182+
183+ /**
184+ * The number of elements in each element of `segmentsList`. In most
185+ * cases, this is 1 but can be 2 when there is a `finally` present,
186+ * which forks the code path outside of normal flow. In the case of nested
187+ * `finally` blocks, this can be a multiple of 2.
188+ * @type {number }
189+ */
108190 this . count = count ;
191+
192+ /**
193+ * The segments within this context. Each element in this array has
194+ * `count` elements that represent one step in each fork. For example,
195+ * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path
196+ * a->c->e and one path b->d->f, and `count` is 2 because each element
197+ * is an array with two elements.
198+ * @type {Array<Array<CodePathSegment>> }
199+ */
109200 this . segmentsList = [ ] ;
110201 }
111202
112203 /**
113- * The head segments.
114- * @type {CodePathSegment[] }
204+ * The segments that begin this fork context .
205+ * @type {Array< CodePathSegment> }
115206 */
116207 get head ( ) {
117208 const list = this . segmentsList ;
@@ -120,15 +211,15 @@ class ForkContext {
120211 }
121212
122213 /**
123- * A flag which shows empty .
214+ * Indicates if the context contains no segments .
124215 * @type {boolean }
125216 */
126217 get empty ( ) {
127218 return this . segmentsList . length === 0 ;
128219 }
129220
130221 /**
131- * A flag which shows reachable.
222+ * Indicates if there are any segments that are reachable.
132223 * @type {boolean }
133224 */
134225 get reachable ( ) {
@@ -138,75 +229,82 @@ class ForkContext {
138229 }
139230
140231 /**
141- * Creates new segments from this context.
142- * @param {number } begin The first index of previous segments.
143- * @param {number } end The last index of previous segments.
144- * @returns {CodePathSegment[] } New segments.
232+ * Creates new segments in this context and appends them to the end of the
233+ * already existing `CodePathSegment`s specified by `startIndex` and
234+ * `endIndex`.
235+ * @param {number } startIndex The index of the first segment in the context
236+ * that should be specified as previous segments for the newly created segments.
237+ * @param {number } endIndex The index of the last segment in the context
238+ * that should be specified as previous segments for the newly created segments.
239+ * @returns {Array<CodePathSegment> } An array of the newly created segments.
145240 */
146- makeNext ( begin , end ) {
147- return makeSegments ( this , begin , end , CodePathSegment . newNext ) ;
241+ makeNext ( startIndex , endIndex ) {
242+ return createSegments ( this , startIndex , endIndex , CodePathSegment . newNext ) ;
148243 }
149244
150245 /**
151- * Creates new segments from this context.
152- * The new segments is always unreachable.
153- * @param {number } begin The first index of previous segments.
154- * @param {number } end The last index of previous segments.
155- * @returns {CodePathSegment[] } New segments.
246+ * Creates new unreachable segments in this context and appends them to the end of the
247+ * already existing `CodePathSegment`s specified by `startIndex` and
248+ * `endIndex`.
249+ * @param {number } startIndex The index of the first segment in the context
250+ * that should be specified as previous segments for the newly created segments.
251+ * @param {number } endIndex The index of the last segment in the context
252+ * that should be specified as previous segments for the newly created segments.
253+ * @returns {Array<CodePathSegment> } An array of the newly created segments.
156254 */
157- makeUnreachable ( begin , end ) {
158- return makeSegments ( this , begin , end , CodePathSegment . newUnreachable ) ;
255+ makeUnreachable ( startIndex , endIndex ) {
256+ return createSegments ( this , startIndex , endIndex , CodePathSegment . newUnreachable ) ;
159257 }
160258
161259 /**
162- * Creates new segments from this context.
163- * The new segments don't have connections for previous segments.
164- * But these inherit the reachable flag from this context.
165- * @param {number } begin The first index of previous segments.
166- * @param {number } end The last index of previous segments.
167- * @returns {CodePathSegment[] } New segments.
260+ * Creates new segments in this context and does not append them to the end
261+ * of the already existing `CodePathSegment`s specified by `startIndex` and
262+ * `endIndex`. The `startIndex` and `endIndex` are only used to determine if
263+ * the new segments should be reachable. If any of the segments in this range
264+ * are reachable then the new segments are also reachable; otherwise, the new
265+ * segments are unreachable.
266+ * @param {number } startIndex The index of the first segment in the context
267+ * that should be considered for reachability.
268+ * @param {number } endIndex The index of the last segment in the context
269+ * that should be considered for reachability.
270+ * @returns {Array<CodePathSegment> } An array of the newly created segments.
168271 */
169- makeDisconnected ( begin , end ) {
170- return makeSegments ( this , begin , end , CodePathSegment . newDisconnected ) ;
272+ makeDisconnected ( startIndex , endIndex ) {
273+ return createSegments ( this , startIndex , endIndex , CodePathSegment . newDisconnected ) ;
171274 }
172275
173276 /**
174- * Adds segments into this context.
175- * The added segments become the head.
176- * @param {CodePathSegment[] } segments Segments to add.
277+ * Adds segments to the head of this context.
278+ * @param {Array<CodePathSegment> } segments The segments to add.
177279 * @returns {void }
178280 */
179281 add ( segments ) {
180282 assert ( segments . length >= this . count , `${ segments . length } >= ${ this . count } ` ) ;
181-
182283 this . segmentsList . push ( mergeExtraSegments ( this , segments ) ) ;
183284 }
184285
185286 /**
186- * Replaces the head segments with given segments.
287+ * Replaces the head segments with the given segments.
187288 * The current head segments are removed.
188- * @param {CodePathSegment[] } segments Segments to add .
289+ * @param {Array< CodePathSegment> } replacementHeadSegments The new head segments .
189290 * @returns {void }
190291 */
191- replaceHead ( segments ) {
192- assert ( segments . length >= this . count , `${ segments . length } >= ${ this . count } ` ) ;
193-
194- this . segmentsList . splice ( - 1 , 1 , mergeExtraSegments ( this , segments ) ) ;
292+ replaceHead ( replacementHeadSegments ) {
293+ assert (
294+ replacementHeadSegments . length >= this . count ,
295+ `${ replacementHeadSegments . length } >= ${ this . count } `
296+ ) ;
297+ this . segmentsList . splice ( - 1 , 1 , mergeExtraSegments ( this , replacementHeadSegments ) ) ;
195298 }
196299
197300 /**
198301 * Adds all segments of a given fork context into this context.
199- * @param {ForkContext } context A fork context to add.
302+ * @param {ForkContext } otherForkContext The fork context to add from .
200303 * @returns {void }
201304 */
202- addAll ( context ) {
203- assert ( context . count === this . count ) ;
204-
205- const source = context . segmentsList ;
206-
207- for ( let i = 0 ; i < source . length ; ++ i ) {
208- this . segmentsList . push ( source [ i ] ) ;
209- }
305+ addAll ( otherForkContext ) {
306+ assert ( otherForkContext . count === this . count ) ;
307+ this . segmentsList . push ( ...otherForkContext . segmentsList ) ;
210308 }
211309
212310 /**
@@ -218,7 +316,8 @@ class ForkContext {
218316 }
219317
220318 /**
221- * Creates the root fork context.
319+ * Creates a new root context, meaning that there are no parent
320+ * fork contexts.
222321 * @param {IdGenerator } idGenerator An identifier generator for segments.
223322 * @returns {ForkContext } New fork context.
224323 */
@@ -233,14 +332,16 @@ class ForkContext {
233332 /**
234333 * Creates an empty fork context preceded by a given context.
235334 * @param {ForkContext } parentContext The parent fork context.
236- * @param {boolean } forkLeavingPath A flag which shows inside of `finally` block.
335+ * @param {boolean } shouldForkLeavingPath Indicates that we are inside of
336+ * a `finally` block and should therefore fork the path that leaves
337+ * `finally`.
237338 * @returns {ForkContext } New fork context.
238339 */
239- static newEmpty ( parentContext , forkLeavingPath ) {
340+ static newEmpty ( parentContext , shouldForkLeavingPath ) {
240341 return new ForkContext (
241342 parentContext . idGenerator ,
242343 parentContext ,
243- ( forkLeavingPath ? 2 : 1 ) * parentContext . count
344+ ( shouldForkLeavingPath ? 2 : 1 ) * parentContext . count
244345 ) ;
245346 }
246347}
0 commit comments