main
js 252 lines 7 KB
Raw
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;