• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 * @param {CodePathSegment} segment A segment to get.
26 * @returns {boolean} `true` if the segment is reachable.
27 */
28function isReachable(segment) {
29    return segment.reachable;
30}
31
32/**
33 * Creates new segments from the specific range of `context.segmentsList`.
34 *
35 * 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 */
44function makeSegments(context, begin, end, create) {
45    const list = context.segmentsList;
46
47    const normalizedBegin = begin >= 0 ? begin : list.length + begin;
48    const normalizedEnd = end >= 0 ? end : list.length + end;
49
50    const segments = [];
51
52    for (let i = 0; i < context.count; ++i) {
53        const allPrevSegments = [];
54
55        for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
56            allPrevSegments.push(list[j][i]);
57        }
58
59        segments.push(create(context.idGenerator.next(), allPrevSegments));
60    }
61
62    return segments;
63}
64
65/**
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.
73 */
74function mergeExtraSegments(context, segments) {
75    let currentSegments = segments;
76
77    while (currentSegments.length > context.count) {
78        const merged = [];
79
80        for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) {
81            merged.push(CodePathSegment.newNext(
82                context.idGenerator.next(),
83                [currentSegments[i], currentSegments[i + length]]
84            ));
85        }
86        currentSegments = merged;
87    }
88    return currentSegments;
89}
90
91//------------------------------------------------------------------------------
92// Public Interface
93//------------------------------------------------------------------------------
94
95/**
96 * A class to manage forking.
97 */
98class ForkContext {
99
100    // eslint-disable-next-line jsdoc/require-description
101    /**
102     * @param {IdGenerator} idGenerator An identifier generator for segments.
103     * @param {ForkContext|null} upper An upper fork context.
104     * @param {number} count A number of parallel segments.
105     */
106    constructor(idGenerator, upper, count) {
107        this.idGenerator = idGenerator;
108        this.upper = upper;
109        this.count = count;
110        this.segmentsList = [];
111    }
112
113    /**
114     * The head segments.
115     * @type {CodePathSegment[]}
116     */
117    get head() {
118        const list = this.segmentsList;
119
120        return list.length === 0 ? [] : list[list.length - 1];
121    }
122
123    /**
124     * A flag which shows empty.
125     * @type {boolean}
126     */
127    get empty() {
128        return this.segmentsList.length === 0;
129    }
130
131    /**
132     * A flag which shows reachable.
133     * @type {boolean}
134     */
135    get reachable() {
136        const segments = this.head;
137
138        return segments.length > 0 && segments.some(isReachable);
139    }
140
141    /**
142     * Creates new segments from this context.
143     * @param {number} begin The first index of previous segments.
144     * @param {number} end The last index of previous segments.
145     * @returns {CodePathSegment[]} New segments.
146     */
147    makeNext(begin, end) {
148        return makeSegments(this, begin, end, CodePathSegment.newNext);
149    }
150
151    /**
152     * Creates new segments from this context.
153     * The new segments is always unreachable.
154     * @param {number} begin The first index of previous segments.
155     * @param {number} end The last index of previous segments.
156     * @returns {CodePathSegment[]} New segments.
157     */
158    makeUnreachable(begin, end) {
159        return makeSegments(this, begin, end, CodePathSegment.newUnreachable);
160    }
161
162    /**
163     * Creates new segments from this context.
164     * The new segments don't have connections for previous segments.
165     * But these inherit the reachable flag from this context.
166     * @param {number} begin The first index of previous segments.
167     * @param {number} end The last index of previous segments.
168     * @returns {CodePathSegment[]} New segments.
169     */
170    makeDisconnected(begin, end) {
171        return makeSegments(this, begin, end, CodePathSegment.newDisconnected);
172    }
173
174    /**
175     * Adds segments into this context.
176     * The added segments become the head.
177     * @param {CodePathSegment[]} segments Segments to add.
178     * @returns {void}
179     */
180    add(segments) {
181        assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
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(segments.length >= this.count, `${segments.length} >= ${this.count}`);
194
195        this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments));
196    }
197
198    /**
199     * Adds all segments of a given fork context into this context.
200     * @param {ForkContext} context A fork context to add.
201     * @returns {void}
202     */
203    addAll(context) {
204        assert(context.count === this.count);
205
206        const source = context.segmentsList;
207
208        for (let i = 0; i < source.length; ++i) {
209            this.segmentsList.push(source[i]);
210        }
211    }
212
213    /**
214     * Clears all segments in this context.
215     * @returns {void}
216     */
217    clear() {
218        this.segmentsList = [];
219    }
220
221    /**
222     * Creates the root fork context.
223     * @param {IdGenerator} idGenerator An identifier generator for segments.
224     * @returns {ForkContext} New fork context.
225     */
226    static newRoot(idGenerator) {
227        const context = new ForkContext(idGenerator, null, 1);
228
229        context.add([CodePathSegment.newRoot(idGenerator.next())]);
230
231        return context;
232    }
233
234    /**
235     * Creates an empty fork context preceded by a given context.
236     * @param {ForkContext} parentContext The parent fork context.
237     * @param {boolean} forkLeavingPath A flag which shows inside of `finally` block.
238     * @returns {ForkContext} New fork context.
239     */
240    static newEmpty(parentContext, forkLeavingPath) {
241        return new ForkContext(
242            parentContext.idGenerator,
243            parentContext,
244            (forkLeavingPath ? 2 : 1) * parentContext.count
245        );
246    }
247}
248
249module.exports = ForkContext;
250