250 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @fileoverview A class to operate forking.
 | |
|  *
 | |
|  * This is state of forking.
 | |
|  * This has a fork list and manages it.
 | |
|  *
 | |
|  * @author Toru Nagashima
 | |
|  */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Requirements
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| const assert = require("assert"),
 | |
|     CodePathSegment = require("./code-path-segment");
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Helpers
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * Gets whether or not a given segment is reachable.
 | |
|  * @param {CodePathSegment} segment A segment to get.
 | |
|  * @returns {boolean} `true` if the segment is reachable.
 | |
|  */
 | |
| function isReachable(segment) {
 | |
|     return segment.reachable;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates new segments from the specific range of `context.segmentsList`.
 | |
|  *
 | |
|  * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
 | |
|  * `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`.
 | |
|  * This `h` is from `b`, `d`, and `f`.
 | |
|  * @param {ForkContext} context An instance.
 | |
|  * @param {number} begin The first index of the previous segments.
 | |
|  * @param {number} end The last index of the previous segments.
 | |
|  * @param {Function} create A factory function of new segments.
 | |
|  * @returns {CodePathSegment[]} New segments.
 | |
|  */
 | |
| function makeSegments(context, begin, end, create) {
 | |
|     const list = context.segmentsList;
 | |
| 
 | |
|     const normalizedBegin = begin >= 0 ? begin : list.length + begin;
 | |
|     const normalizedEnd = end >= 0 ? end : list.length + end;
 | |
| 
 | |
|     const segments = [];
 | |
| 
 | |
|     for (let i = 0; i < context.count; ++i) {
 | |
|         const allPrevSegments = [];
 | |
| 
 | |
|         for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
 | |
|             allPrevSegments.push(list[j][i]);
 | |
|         }
 | |
| 
 | |
|         segments.push(create(context.idGenerator.next(), allPrevSegments));
 | |
|     }
 | |
| 
 | |
|     return segments;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * `segments` becomes doubly in a `finally` block. Then if a code path exits by a
 | |
|  * control statement (such as `break`, `continue`) from the `finally` block, the
 | |
|  * destination's segments may be half of the source segments. In that case, this
 | |
|  * merges segments.
 | |
|  * @param {ForkContext} context An instance.
 | |
|  * @param {CodePathSegment[]} segments Segments to merge.
 | |
|  * @returns {CodePathSegment[]} The merged segments.
 | |
|  */
 | |
| function mergeExtraSegments(context, segments) {
 | |
|     let currentSegments = segments;
 | |
| 
 | |
|     while (currentSegments.length > context.count) {
 | |
|         const merged = [];
 | |
| 
 | |
|         for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) {
 | |
|             merged.push(CodePathSegment.newNext(
 | |
|                 context.idGenerator.next(),
 | |
|                 [currentSegments[i], currentSegments[i + length]]
 | |
|             ));
 | |
|         }
 | |
|         currentSegments = merged;
 | |
|     }
 | |
|     return currentSegments;
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Public Interface
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| /**
 | |
|  * A class to manage forking.
 | |
|  */
 | |
| class ForkContext {
 | |
| 
 | |
|     // eslint-disable-next-line jsdoc/require-description
 | |
|     /**
 | |
|      * @param {IdGenerator} idGenerator An identifier generator for segments.
 | |
|      * @param {ForkContext|null} upper An upper fork context.
 | |
|      * @param {number} count A number of parallel segments.
 | |
|      */
 | |
|     constructor(idGenerator, upper, count) {
 | |
|         this.idGenerator = idGenerator;
 | |
|         this.upper = upper;
 | |
|         this.count = count;
 | |
|         this.segmentsList = [];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * The head segments.
 | |
|      * @type {CodePathSegment[]}
 | |
|      */
 | |
|     get head() {
 | |
|         const list = this.segmentsList;
 | |
| 
 | |
|         return list.length === 0 ? [] : list[list.length - 1];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * A flag which shows empty.
 | |
|      * @type {boolean}
 | |
|      */
 | |
|     get empty() {
 | |
|         return this.segmentsList.length === 0;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * A flag which shows reachable.
 | |
|      * @type {boolean}
 | |
|      */
 | |
|     get reachable() {
 | |
|         const segments = this.head;
 | |
| 
 | |
|         return segments.length > 0 && segments.some(isReachable);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates new segments from this context.
 | |
|      * @param {number} begin The first index of previous segments.
 | |
|      * @param {number} end The last index of previous segments.
 | |
|      * @returns {CodePathSegment[]} New segments.
 | |
|      */
 | |
|     makeNext(begin, end) {
 | |
|         return makeSegments(this, begin, end, CodePathSegment.newNext);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates new segments from this context.
 | |
|      * The new segments is always unreachable.
 | |
|      * @param {number} begin The first index of previous segments.
 | |
|      * @param {number} end The last index of previous segments.
 | |
|      * @returns {CodePathSegment[]} New segments.
 | |
|      */
 | |
|     makeUnreachable(begin, end) {
 | |
|         return makeSegments(this, begin, end, CodePathSegment.newUnreachable);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates new segments from this context.
 | |
|      * The new segments don't have connections for previous segments.
 | |
|      * But these inherit the reachable flag from this context.
 | |
|      * @param {number} begin The first index of previous segments.
 | |
|      * @param {number} end The last index of previous segments.
 | |
|      * @returns {CodePathSegment[]} New segments.
 | |
|      */
 | |
|     makeDisconnected(begin, end) {
 | |
|         return makeSegments(this, begin, end, CodePathSegment.newDisconnected);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Adds segments into this context.
 | |
|      * The added segments become the head.
 | |
|      * @param {CodePathSegment[]} segments Segments to add.
 | |
|      * @returns {void}
 | |
|      */
 | |
|     add(segments) {
 | |
|         assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
 | |
| 
 | |
|         this.segmentsList.push(mergeExtraSegments(this, segments));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Replaces the head segments with given segments.
 | |
|      * The current head segments are removed.
 | |
|      * @param {CodePathSegment[]} segments Segments to add.
 | |
|      * @returns {void}
 | |
|      */
 | |
|     replaceHead(segments) {
 | |
|         assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
 | |
| 
 | |
|         this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Adds all segments of a given fork context into this context.
 | |
|      * @param {ForkContext} context A fork context to add.
 | |
|      * @returns {void}
 | |
|      */
 | |
|     addAll(context) {
 | |
|         assert(context.count === this.count);
 | |
| 
 | |
|         const source = context.segmentsList;
 | |
| 
 | |
|         for (let i = 0; i < source.length; ++i) {
 | |
|             this.segmentsList.push(source[i]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Clears all secments in this context.
 | |
|      * @returns {void}
 | |
|      */
 | |
|     clear() {
 | |
|         this.segmentsList = [];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates the root fork context.
 | |
|      * @param {IdGenerator} idGenerator An identifier generator for segments.
 | |
|      * @returns {ForkContext} New fork context.
 | |
|      */
 | |
|     static newRoot(idGenerator) {
 | |
|         const context = new ForkContext(idGenerator, null, 1);
 | |
| 
 | |
|         context.add([CodePathSegment.newRoot(idGenerator.next())]);
 | |
| 
 | |
|         return context;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates an empty fork context preceded by a given context.
 | |
|      * @param {ForkContext} parentContext The parent fork context.
 | |
|      * @param {boolean} forkLeavingPath A flag which shows inside of `finally` block.
 | |
|      * @returns {ForkContext} New fork context.
 | |
|      */
 | |
|     static newEmpty(parentContext, forkLeavingPath) {
 | |
|         return new ForkContext(
 | |
|             parentContext.idGenerator,
 | |
|             parentContext,
 | |
|             (forkLeavingPath ? 2 : 1) * parentContext.count
 | |
|         );
 | |
|     }
 | |
| }
 | |
| 
 | |
| module.exports = ForkContext;
 | 
