UNPKG

7.71 kBJavaScriptView Raw
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 *
26 * @param {CodePathSegment} segment - A segment to get.
27 * @returns {boolean} `true` if the segment is reachable.
28 */
29function 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 */
46function 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 */
77function 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 */
101class 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
260module.exports = ForkContext;