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