main
js 225 lines 6.22 KB
Raw
1 'use strict';
2
3 //------------------------------------------------------------------------------
4 // Requirements
5 //------------------------------------------------------------------------------
6
7 //------------------------------------------------------------------------------
8 // Helpers
9 //------------------------------------------------------------------------------
10
11 /**
12 * Checks whether or not a given segment is reachable.
13 * @param {CodePathSegment} segment A segment to check.
14 * @returns {boolean} `true` if the segment is reachable.
15 */
16 function isReachable(segment) {
17 return segment.reachable;
18 }
19
20 //------------------------------------------------------------------------------
21 // Public Interface
22 //------------------------------------------------------------------------------
23
24 /**
25 * A code path segment.
26 */
27 class CodePathSegment {
28 /**
29 * @param {string} id An identifier.
30 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
31 * This array includes unreachable segments.
32 * @param {boolean} reachable A flag which shows this is reachable.
33 */
34 constructor(id, allPrevSegments, reachable) {
35 /**
36 * The identifier of this code path.
37 * Rules use it to store additional information of each rule.
38 * @type {string}
39 */
40 this.id = id;
41
42 /**
43 * An array of the next segments.
44 * @type {CodePathSegment[]}
45 */
46 this.nextSegments = [];
47
48 /**
49 * An array of the previous segments.
50 * @type {CodePathSegment[]}
51 */
52 this.prevSegments = allPrevSegments.filter(isReachable);
53
54 /**
55 * An array of the next segments.
56 * This array includes unreachable segments.
57 * @type {CodePathSegment[]}
58 */
59 this.allNextSegments = [];
60
61 /**
62 * An array of the previous segments.
63 * This array includes unreachable segments.
64 * @type {CodePathSegment[]}
65 */
66 this.allPrevSegments = allPrevSegments;
67
68 /**
69 * A flag which shows this is reachable.
70 * @type {boolean}
71 */
72 this.reachable = reachable;
73
74 // Internal data.
75 Object.defineProperty(this, 'internal', {
76 value: {
77 used: false,
78 loopedPrevSegments: [],
79 },
80 });
81 }
82
83 /**
84 * Checks a given previous segment is coming from the end of a loop.
85 * @param {CodePathSegment} segment A previous segment to check.
86 * @returns {boolean} `true` if the segment is coming from the end of a loop.
87 */
88 isLoopedPrevSegment(segment) {
89 return this.internal.loopedPrevSegments.includes(segment);
90 }
91
92 /**
93 * Creates the root segment.
94 * @param {string} id An identifier.
95 * @returns {CodePathSegment} The created segment.
96 */
97 static newRoot(id) {
98 return new CodePathSegment(id, [], true);
99 }
100
101 /**
102 * Creates a segment that follows given segments.
103 * @param {string} id An identifier.
104 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
105 * @returns {CodePathSegment} The created segment.
106 */
107 static newNext(id, allPrevSegments) {
108 return new CodePathSegment(
109 id,
110 CodePathSegment.flattenUnusedSegments(allPrevSegments),
111 allPrevSegments.some(isReachable),
112 );
113 }
114
115 /**
116 * Creates an unreachable segment that follows given segments.
117 * @param {string} id An identifier.
118 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
119 * @returns {CodePathSegment} The created segment.
120 */
121 static newUnreachable(id, allPrevSegments) {
122 const segment = new CodePathSegment(
123 id,
124 CodePathSegment.flattenUnusedSegments(allPrevSegments),
125 false,
126 );
127
128 /*
129 * In `if (a) return a; foo();` case, the unreachable segment preceded by
130 * the return statement is not used but must not be remove.
131 */
132 CodePathSegment.markUsed(segment);
133
134 return segment;
135 }
136
137 /**
138 * Creates a segment that follows given segments.
139 * This factory method does not connect with `allPrevSegments`.
140 * But this inherits `reachable` flag.
141 * @param {string} id An identifier.
142 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
143 * @returns {CodePathSegment} The created segment.
144 */
145 static newDisconnected(id, allPrevSegments) {
146 return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
147 }
148
149 /**
150 * Makes a given segment being used.
151 *
152 * And this function registers the segment into the previous segments as a next.
153 * @param {CodePathSegment} segment A segment to mark.
154 * @returns {void}
155 */
156 static markUsed(segment) {
157 if (segment.internal.used) {
158 return;
159 }
160 segment.internal.used = true;
161
162 let i;
163
164 if (segment.reachable) {
165 for (i = 0; i < segment.allPrevSegments.length; ++i) {
166 const prevSegment = segment.allPrevSegments[i];
167
168 prevSegment.allNextSegments.push(segment);
169 prevSegment.nextSegments.push(segment);
170 }
171 } else {
172 for (i = 0; i < segment.allPrevSegments.length; ++i) {
173 segment.allPrevSegments[i].allNextSegments.push(segment);
174 }
175 }
176 }
177
178 /**
179 * Marks a previous segment as looped.
180 * @param {CodePathSegment} segment A segment.
181 * @param {CodePathSegment} prevSegment A previous segment to mark.
182 * @returns {void}
183 */
184 static markPrevSegmentAsLooped(segment, prevSegment) {
185 segment.internal.loopedPrevSegments.push(prevSegment);
186 }
187
188 /**
189 * Replaces unused segments with the previous segments of each unused segment.
190 * @param {CodePathSegment[]} segments An array of segments to replace.
191 * @returns {CodePathSegment[]} The replaced array.
192 */
193 static flattenUnusedSegments(segments) {
194 const done = Object.create(null);
195 const retv = [];
196
197 for (let i = 0; i < segments.length; ++i) {
198 const segment = segments[i];
199
200 // Ignores duplicated.
201 if (done[segment.id]) {
202 continue;
203 }
204
205 // Use previous segments if unused.
206 if (!segment.internal.used) {
207 for (let j = 0; j < segment.allPrevSegments.length; ++j) {
208 const prevSegment = segment.allPrevSegments[j];
209
210 if (!done[prevSegment.id]) {
211 done[prevSegment.id] = true;
212 retv.push(prevSegment);
213 }
214 }
215 } else {
216 done[segment.id] = true;
217 retv.push(segment);
218 }
219 }
220
221 return retv;
222 }
223 }
224
225 module.exports = CodePathSegment;