traceur
Version:
ES6 to ES5 compiler
179 lines (161 loc) • 5.9 kB
JavaScript
// Copyright 2012 Traceur Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {ParseTree} from './ParseTree.js';
import {STATE_MACHINE} from './ParseTreeType.js';
import {State} from '../../codegeneration/generator/State.js';
import {TryState} from '../../codegeneration/generator/TryState.js';
/**
* @param {TryState.Kind} kind
* @param {Object} enclosingMap map of state IDs to FinallyState.
* @param {Array.<TryState>} tryStates
*/
function addCatchOrFinallyStates(kind, enclosingMap, tryStates) {
for (let i = 0; i < tryStates.length; i++) {
let tryState = tryStates[i];
if (tryState.kind === kind) {
for (let j = 0; j < tryState.tryStates.length; j++) {
let id = tryState.tryStates[j];
enclosingMap[id] = tryState;
}
}
addCatchOrFinallyStates(kind, enclosingMap, tryState.nestedTrys);
}
}
/**
* @param {Array.<TryState>} tryStates
* @param {Array.<CatchState>} catches
*/
function addAllCatchStates(tryStates, catches) {
for (let i = 0; i < tryStates.length; i++) {
let tryState = tryStates[i];
if (tryState.kind === TryState.Kind.CATCH) {
catches.push(tryState);
}
addAllCatchStates(tryState.nestedTrys, catches);
}
}
/**
* A state machine tree is the result of transforming a set of statements that
* contain a yield, either directly or indirectly. StateMachine's break many of
* the design invariants in the compiler around parse trees. They are only valid
* only as temporary entities during the generator transform pass. They are not
* convertible (directly) to javascript code.
*
* State machine trees include a set of states identified by an integer id. A
* State represents some executable statements, plus some set of possible
* transitions to other states.
*
* The exceptionBlocks member stores a tree representing the dispatch portion of
* all try/catch/finally blocks from the original source code. The bodies of the
* try, catch and finally blocks are transformed to States and added to the main
* states list.
*
* States and StateMachineTrees are created by a bottom up traversal of the
* original source. When a control transfer statement (if, switch, while, for,
* try) contains a state machine, the nested statements are converted to
* StateMachines, then a new machine is created which knits together the states
* from the nested machines.
*
* States and StateMachineTrees are immutable.
*/
export class StateMachine extends ParseTree {
/**
* @param {number} startState
* @param {number} fallThroughState
* @param {Array.<State>} states
* @param {Array.<TryState>} exceptionBlocks
*/
constructor(startState, fallThroughState, states, exceptionBlocks) {
super(null);
this.startState = startState;
this.fallThroughState = fallThroughState;
this.states = states;
this.exceptionBlocks = exceptionBlocks;
}
/**
* @type {ParseTreeType}
*/
get type() {
return STATE_MACHINE;
}
transform(transformer) {
return transformer.transformStateMachine(this);
}
visit(visitor) {
visitor.visitStateMachine(this);
}
/**
* Returns all the state ids of states in the machine. Note that the
* fallThroughState is typically not a state in the machine.
* @return {Array.<number>}
*/
getAllStateIDs() {
let result = [];
for (let i = 0; i < this.states.length; i++) {
result.push(this.states[i].id);
}
return result;
}
/**
* Return a map from the states in the machine to their nearest enclosing
* finally.
* @return {Object} map of state IDs to FinallyState.
*/
getEnclosingFinallyMap() {
let enclosingMap = Object.create(null);
addCatchOrFinallyStates(TryState.Kind.FINALLY, enclosingMap,
this.exceptionBlocks);
return enclosingMap;
}
allCatchStates() {
let catches = [];
addAllCatchStates(this.exceptionBlocks, catches);
return catches;
}
replaceStateId(oldState, newState) {
return new StateMachine(
State.replaceStateId(this.startState, oldState, newState),
State.replaceStateId(this.fallThroughState, oldState, newState),
State.replaceAllStates(this.states, oldState, newState),
State.replaceAllStates(this.exceptionBlocks, oldState, newState));
}
replaceStartState(newState) {
return this.replaceStateId(this.startState, newState);
}
replaceFallThroughState(newState) {
return this.replaceStateId(this.fallThroughState, newState);
}
/**
* Returns a new state machine which will run this machine first, then run
* the next machine.
* @param {StateMachine} nextMachine
* @return {StateMachine}
*/
append(nextMachine) {
let states = [...this.states];
for (let i = 0; i < nextMachine.states.length; i++) {
let otherState = nextMachine.states[i];
states.push(
otherState.replaceState(nextMachine.startState, this.fallThroughState));
}
let exceptionBlocks = [...this.exceptionBlocks];
for (let i = 0; i < nextMachine.exceptionBlocks.length; i++) {
let tryState = nextMachine.exceptionBlocks[i];
exceptionBlocks.push(
tryState.replaceState(nextMachine.startState, this.fallThroughState));
}
return new StateMachine(this.startState, nextMachine.fallThroughState,
states, exceptionBlocks);
}
}