<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@*" data-semver="1.3.13" src="https://code.angularjs.org/1.3.13/angular.js"></script>
    <script data-require="ace@*" data-semver="1.1.8" src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.8/ace.js"></script>
    <link rel="stylesheet" href="hellow-w.css" />
    <script src="em-test-portal.js"></script>
    <script src="em-bootstrap.js"></script>
    <script src="hello-w.js"></script>
  </head>

  <body>
    <div class="container" ng-controller="helloW">
      <div>
        <input type="text" ng-model="object" />
        <label>{{sayHi()}}</label>
      </div>
      <label ng-if="!json.hi">{{json.hi}}</label>
      <label ng-if="json.hi == '123'">recieved: {{json.hi}}</label>
      
      <button ng-click="callAjax()">send ajax call</button>
    </div>
  </body>

</html>
var pageData = {
  name: "Young",
  say: "hello"
};

var app = angular.module("app", []);
app.config(['$httpProvider', function($httpProvider) {
        $httpProvider.defaults.useXDomain = true;
        delete $httpProvider.defaults.headers.common['X-Requested-With'];
    }
]);
app.controller("helloW", ["$scope", "pageData", "$http",
  function($scope, pageData, $http) {
    $scope.object = "";
    $scope.sayHi = function() {
      return pageData.name + ":" + pageData.say;
    };
    $http.get("https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular-animate.js").success(function(data, status, headers, config) {
      $scope.json = data;
    });
    $scope.callAjax = function(){
      $http.get('ajax-call.json').success(function(data, status, headers, config) {
      $scope.json = data;
    });
    }
  }
]);

emBootstrap("app", pageData);
.test-report {
  position: absolute;
  left: 0;
  bottom: 0;
  margin: 0 0 20px 0;
  padding: 0;
}
This a angularJs test tool, so front end developer can intercept json objects sent by server side.
Manipulate the values to debug and see the affects on pages.
It supports Ajax interceptions as well.

The following libraries are used:
Angularjs
Material-AngularJs
jslint
jsBeatify
<html>

  <head>
    <link data-require="jasmine@*" data-semver="2.2.1" rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.css" />
    <script data-require="jasmine@*" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.js"></script>
    <script data-require="jasmine@*" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine-html.js"></script>
    <script data-require="jasmine@*" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/boot.js"></script>
    <script data-require="angular.js@*" data-semver="1.4.0-beta.6" src="https://code.angularjs.org/1.4.0-beta.6/angular.js"></script>
    <script data-require="angular-mocks@1.4.0-beta.4" data-semver="1.4.0-beta.4" src="https://code.angularjs.org/1.4.0-beta.4/angular-mocks.js"></script>
    <script src="hello-w.js"></script>
    <script src="hello-w.spec.js"></script>
  </head>

  <body></body>

</html>
describe("hello-w", function() {
  var $scope;
  beforeEach(module("app"));
  beforeEach(inject(function(_$controller_, _$rootScope_) {

    $scope = _$rootScope_;
    helloWController = _$controller_("helloW", {$scope:_$rootScope_});
  }))
  it("should say hi to object when calling greeting()", function() {
   $scope.object = "Young";
    expect($scope.sayHi()).toBe("Hello Young");

 
  });
 

});
console.log(inject)
.em-test-portal .em-page-test-configuration {
  width: 100%;
  top: 0;
  left: 0;
  box-sizing: border-box;
  margin: 10px auto;
  padding: 20px 50px;
  background-color: #fff;
  margin: 0;
}

.em-test-portal-head {
  text-align: center;
  background: -webkit-linear-gradient(top, #1e5799 0%,#2989d8 50%,#207cca 51%,#7db9e8 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.em-cloak {
  display: none;
}

.test-inject-object {
  min-width: 200px;
  width: 30%;
  display: block;
}

.saved-test-data-list {
  min-width: 200px;
  width: 30%;
}

.test-data-section {
  position: relative;
  width: 100%;
}

#dataEditor{
  width: 100%;
  height: 200px;
}
.test-data {
  margin: 0 0;
  display: block;
  width: 100%;
  height: 200px;
  box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.14), 0 4px 5px 0 rgba(0, 0, 0, 0.098), 0 1px 10px 0 rgba(0, 0, 0, 0.084);
}

.test-data-beautify {
  margin: 10px 0px!important;
  position: relative;
  left: 100%;
  transform: translateX(-100%);
}

.test-data-error {
  position: relative;
  display: block;
  background-color: #FF4081;
  color: #fff;
  padding:20px;
}

.ajax-call-switch {
  margin-left: 0;
}

.test-data-apply-button {
  display: inline-block;
}

.save-test-data-as {
  min-width: 200px;
  width: 40%;
}

.ajax-filter-list {
  min-width: 200px;
  width: 80%;
}

.ajax-filter-list .header {
  color: rgba(255, 255, 255, 0.87);
  background-color: rgb(63, 81, 181);
  padding: 5px 16px 5px 5px !important;
  margin: 0px;
}

.ajax-filter-list .ajax-filter {
  padding: 0;
  min-height: 25px;
}

.ajax-filter-list .ajax-filter *:not(button) {
  width: 100%;
  padding: 0;
}

.ajax-filter-list md-input-container {
  padding: 2px 2px 2px 2px !important;
}

.ajax-filter-text {
  padding-left: 20px !important;
  font: 20px bolder;
}

.ajax-filter-list .md-icon-button {
  margin: 0 !important;
}

.em-test-portal-ajax {
  position: fixed;
  width: 70%;
  padding: 5px 15px;
  -webkit-box-shadow: 0px 0px 18px 1px rgba(50, 50, 50, 0.75);
  -moz-box-shadow: 0px 0px 18px 1px rgba(50, 50, 50, 0.75);
  box-shadow: 0px 0px 18px 1px rgba(50, 50, 50, 0.75);
  background-color: #fff;
  z-index: 1000;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -60%);
}

.em-test-portal-ajax .em-test-configuration {
  width: 100%;
  min-width: 300px;
  margin: 10px auto;
  display: block;
}

.em-open-configure {
  position: fixed;
  top: 10px;
  left: 90%;
  z-index: 9999999;
}

.em-open-configure button {
  animation-name: configure-icon;
  animation-duration: 4s;
  animation-iteration-count: infinite;
  animation-play-state: paused;
}

.em-open-configure button:hover {
  animation-play-state: running;
}

.em-open-configure button.em-open-configure-expanding {
  animation-name: configure-expand;
  animation-play-state: running;
  animation-duration: 0.2s;
  animation-iteration-count: 1;
}

.em-open-configure button.em-open-configure-collapse {
  animation-name: configure-collapse;
  animation-play-state: running;
  animation-duration: 0.2s;
  animation-iteration-count: 1;
}

@keyframes configure-expand {
  from {
    transform: scale(1);
  }
  to {
    transform: scale(50);
  }
}

@keyframes configure-collapse {
  from {
    transform: scale(50);
  }
  to {
    transform: scale(1);
  }
}

@keyframes configure-icon {
  0% {
    background-color: rgb(255, 0, 0);
  }
  25% {
    background-color: rgb(255, 153, 204);
  }
  50% {
    background-color: rgb(102, 0, 255);
  }
  75% {
    background-color: rgb(0, 255, 255);
  }
  100% {
    background-color: rgb(255, 0, 0);
  }
}
(function(w) {
  var EmTest = function() {
    this.startTestPortal = function(moduleName, pageData, i18n, pageSettings) {
      var injectableData = {
        pageData: pageData,
        i18n: i18n,
        pageSettings: pageSettings
      };
      loadResouces(function() {
        angular.injector(["ng"]).invoke(function($rootScope, $compile, $http, $document) {
          $http.get('em-test-portal.html')
            .success(function(data, status, headers, config) {
              loadTestPortalModule(moduleName, injectableData, angular.element(data));


            });
        });
      });
    };

    this.isPortalOn = function() {
      return document.location.hash === "#/em-test";
    };
  };

  function loadTestPortalModule(moduleName, injectableData, element) {

    var pageElements = document.querySelectorAll("body > *");
    var emTestPortal = angular.module("emTestPortal", ["ngMaterial"]);
    var emTestModule = angular.module("emTest", [moduleName]);
    emTestPortal.value("testModule", emTestModule);
    emTestPortal.value("pageNodes", pageElements);
    emTestPortal.value("injectableData", injectableData);
    emTestPortal.filter("savedData", function() {
      return function(input, key) {
        return input.replace(key, "");
      }
    });
    emTestPortal.controller("testPortalController", function($scope, testModule, pageNodes, $timeout) {
      var scope = $scope;
      var vm = this;
      vm.isShowPageConfigure = true;
      vm.isSimulateAjax = false;
      vm.errorMsg = "";
      vm.bootstrap = function(injectableData) {
        vm.showHidePageConfigure();
        testModule.value("pageData", injectableData.pageData);
        testModule.value("i18n", injectableData.i18n);
        testModule.value("pageSettings", injectableData.pageSettings);
        testModule.run(function($rootScope) {
          $rootScope.i18n = injectableData.i18n;
          $rootScope.pageSettings = injectableData.pageSettings;
        });
        var pageNode = createNewNode();
        angular.bootstrap(pageNode, ["emTest"]);
        document.querySelector(".em-test-page").appendChild(pageNode);
      };
      vm.showHidePageConfigure = function($event) {
        var element = angular.element(document.querySelector(".em-open-configure button"));
        if (vm.isShowPageConfigure) {
          vm.isShowPageConfigure = false;
          element.addClass("em-open-configure-collapse");

          $timeout(function() {
            element.removeClass("em-open-configure-collapse");
          }, 1000);
        } else {
          element.addClass("em-open-configure-expanding");
          $timeout(function() {
            element.removeClass("em-open-configure-expanding");
            vm.isShowPageConfigure = true;
          }, 100);
        }
      }
      vm.moveConfigureIcon = function($event) {
        var element = angular.element($event.currentTarget);
        var body = angular.element(document).find("body");
        var handle = $timeout(function() {

          body.bind("mousemove", function($event) {
            angular.element(element).css({
              left: $event.clientX - 20 + "px",
              top: $event.clientY - 20 + "px"
            });
            body.bind("mouseup", function() {
              body.unbind("mousemove");
              body.unbind("mouseup");
              element.unbind("click");
              $timeout(function() {
                element.bind("click", function() {
                  vm.showHidePageConfigure();
                });
              }, 500);
            });
          });
        }, 150);
        body.bind("mouseup", function() {
          $timeout.cancel(handle);
        });

      };
      var pageWrapper = angular.element('<div class="em-test-page-content"></div>');
      pageWrapper.append(pageNodes);
      var pageNodeClone = angular.element(pageWrapper).clone();

      (function init() {
        angular.element(pageNodes).remove();
        scope.$watch('portal.isSimulateAjax', function() {
          scope.$broadcast("simulateAjax", vm.isSimulateAjax);
        });
      }());



      function createNewNode() {
        angular.element(document.querySelector(".em-test-page-content")).remove();
        var pageNode = angular.element(pageNodeClone).clone()[0];
        return pageNode;
      }
    })
    emTestPortal.controller("pageTestController", function($scope, injectableData, $timeout) {
      var editor = null;
      var scope = $scope;
      scope.injectObjectName = "pageData";
      scope.savedTestDataNames = [];
      scope.injectableData = injectableData;
      scope.injectableDataClone = angular.copy(injectableData, {});
      scope.saveTestDataAs = scope.injectObjectName + ":";
      scope.isDataUpdated = false;
      scope.savedTestDataName = null;
      scope.rollbackChanges = function() {
        editor.getSession().setValue(formatedData(scope.injectableDataClone[scope.injectObjectName]));
      };
      scope.showInjectObjectName = function() {
        var data = injectableData[scope.injectObjectName];
        if (editor)
          editor.getSession().setValue(formatedData(data));
        scope.savedTestDataName = "";
        scope.saveTestDataAs = scope.injectObjectName + ":";
      };

      scope.loadTestData = function(testDataName) {
        if (typeof(Storage) !== "undefined" && testDataName) {
          var data = localStorage.getItem(testDataName);
          editor.getSession().setValue(formatedData(data));
          scope.saveTestDataAs = testDataName.replace("$em-test$", "");
        }
      };
      scope.beautifyData = function() {
        editor.getSession().setValue(formatedData(editor.getSession().getValue()));
      };
      scope.saveTestData = function() {
        if (typeof(Storage) !== "undefined") {
          if (/^(\w+):\s*\w/.test(scope.saveTestDataAs)) {
            localStorage.setItem("$em-test$" + scope.saveTestDataAs, editor.getSession().getValue());
          } else {
            if (scope.savedTestDataName) {
              localStorage.removeItem(scope.savedTestDataName);
            }
          }
          refreshTestDataNames();
        }
      };
      scope.addAjaxFilter = function() {
        if (typeof(Storage) !== "undefined" && scope.newAjaxFilter) {
          localStorage.setItem("$em-ajax-filter$" + scope.newAjaxFilter, scope.newAjaxFilter);
          scope.newAjaxFilter = "";
          refreshAjaxFilters();
        }
      };
      scope.removeAjaxFilter = function(filter) {
        if (typeof(Storage) !== "undefined") {
          localStorage.removeItem(filter);
          refreshAjaxFilters();
        }
      };

      (function init() {

        $timeout(function() {
          editor = ace.edit("dataEditor");
          editor.setTheme("ace/theme/solarized_dark");
          editor.getSession().setMode("ace/mode/json");
          editor.setOptions({
            minLines: 15,
            maxLines: 35,
            enableBasicAutocompletion: true
          });
          editor.getSession().setValue(formatedData(scope.injectableDataClone[scope.injectObjectName]));
          editor.getSession().on('change', function() {
            var newValue = editor.getSession().getValue();
            try {
              if (isJson(newValue)) {
                injectableData[scope.injectObjectName] = angular.fromJson(newValue);
              } else {
                injectableData[scope.injectObjectName] = newValue;
              }
              scope.isDataUpdated = formatedData(scope.injectableDataClone[scope.injectObjectName]) !== formatedData(newValue);
            } catch (e) {
              scope.isDataUpdated = true;
            }
            scope.$apply();
          });
        }, 20);
        scope.showInjectObjectName();

        scope.$watch('injectObjectName', function(newValue, oldValue) {
          try {
            scope.isDataUpdated = formatedData(scope.injectableDataClone[scope.injectObjectName]) !== formatedData(scope.injectObjectVaule);
          } catch (e) {
            scope.isDataUpdated = true;
          }
        });
        refreshTestDataNames();
        refreshAjaxFilters();
      }());

      function refreshTestDataNames() {
        scope.savedTestDataNames = [];
        if (typeof(Storage) !== "undefined") {
          for (var key in localStorage) {
            if (key.indexOf("$em-test$") === 0) {
              scope.savedTestDataNames.push(key);
            }
          }
        }
      }

      function refreshAjaxFilters() {
        scope.savedAjaxFilters = [];
        if (typeof(Storage) !== "undefined") {
          for (var key in localStorage) {
            if (key.indexOf("$em-ajax-filter$") === 0) {
              scope.savedAjaxFilters.push(key);
            }
          }
        }
        if (scope.savedAjaxFilters.length === 0) {
          scope.savedAjaxFilters.push(".*")
          localStorage.setItem("$em-ajax-filter$.*", ".*");
        }
      }

    });

    emTestPortal.controller("ajaxTestController", function($scope, testModule) {
      var __injectAjaxData = null;
      var scope = $scope;
      scope.isSimulateAjax = false;
      scope.isShowAjaxConfigure = false;
      scope.ajaxQueue = [];
      scope.injectData = null;
      scope.requestUrl = null;
      scope.responseStatus = 200;
      scope.savedTestDataNames = [];
      scope.loadSavedTestDataNames = function() {
        if (typeof(Storage) !== "undefined") {
          for (var key in localStorage) {
            if (key.indexOf("$em-ajax-test$" + scope.requestUrl + "$") === 0) {
              scope.savedTestDataNames.push(key);
            }
          }
        }
      };
      scope.loadTestData = function(testDataName) {
        if (typeof(Storage) !== "undefined" && testDataName) {
          scope.injectData = localStorage.getItem(testDataName);
          scope.saveTestDataAs = testDataName.replace("$em-ajax-test$" + scope.requestUrl + "$", "");
        }
      };
      scope.ajaxCallback = function(injectData, responseStatus, saveTestDataAs, savedTestDataName) {

        var data = null;
        try {
          data = angular.fromJson(injectData);
        } catch (e) {
          data = injectData;
        }
        /*
        if (typeof(Storage) !== "undefined") {
          if (saveTestDataAs) {
            localStorage.setItem("$em-ajax-test$" + scope.requestUrl + "$" + saveTestDataAs, injectData);
          } else {
            if (savedTestDataName) {
              localStorage.removeItem(savedTestDataName);
            }
          }
        }
        */
        if (data)
          __injectAjaxData(responseStatus, data, new Date().toString(), responseStatus == 200 ? "OK" : "BAD");
        else
          __injectAjaxData();
      };
      (function init() {
        scope.$on("simulateAjax", function($scope, isSimulateAjax) {
          scope.isSimulateAjax = isSimulateAjax;
        });
        scope.$watchCollection('ajaxQueue', function() {
          processAjaxCall();
        });
        scope.$watch('injectData', function(newValue, oldValue) {
          scope.errorMsg = "";
          if (isJson(newValue)) {
            var warnings = jslint(newValue).warnings;
            if (warnings.length) {
              scope.errorMsg = warnings[0].message + " line:" + warnings[0].line + ", column:" + warnings[0].column;
            }
          }
          scope.isDataUpdated = true;
        });
        testModule.config(function($provide) {
          $provide.decorator('$httpBackend', function($delegate) {
            var proxy = function(method, url, data, callback, headers) {
              var interceptor = function() {
                var self = this;
                scope.ajaxQueue.push({
                  context: self,
                  method: method,
                  url: url,
                  headers: headers,
                  requestData: data,
                  callback: callback,
                  responseData: arguments
                });
                scope.$apply();
              };
              return $delegate.call(this, method, url, data, interceptor, headers);
            };
            for (var key in $delegate) {
              proxy[key] = $delegate[key];
            }
            return proxy;
          });
        })

        var isProcessing = false;

        function processAjaxCall() {
          var ajax = null;
          if (!isProcessing) {
            isProcessing = true;
            if (scope.isSimulateAjax) {
              ajax = scope.ajaxQueue.shift();
              scope.isShowAjaxConfigure = true;

              scope.injectData = formatedData(ajax.responseData[1]);

              scope.responseStatus = ajax.responseData[0];
              scope.requestUrl = ajax.url;
              scope.loadSavedTestDataNames();

              __injectAjaxData = function() {
                if (arguments.length === 0)
                  ajax.callback.apply(ajax.context, ajax.responseData);
                else
                  ajax.callback.apply(ajax.context, arguments);
                scope.isShowAjaxConfigure = false;
                if (scope.ajaxQueue.length) {
                  processAjaxCall();
                } else {
                  isProcessing = false;
                }
              };
            } else {
              while (scope.ajaxQueue.length) {
                ajax = scope.ajaxQueue.shift();
                ajax.callback.apply(ajax.context, ajax.responseData);
              }
              isProcessing = false;
            }
          }
        }



      }());

    });

    angular.bootstrap(element[0], ["emTestPortal"]);
    document.querySelector("body").appendChild(element[0]);


  }

  function isJson(data) {
    return /^\s*{|\[/.test(data);

  }

  function isHtml(data) {
    return /^\s*</.test(data);
  }

  function formatedData(data) {
    var fData = null;
    try {
      if (angular.isObject(data)) {
        fData = js_beautify(angular.toJson(data));
      } else if (isJson(data)) {
        fData = js_beautify(data);
      } else if (isHtml(data)) {
        fData = html_beautify(data);
      } else {
        fData = data;
      }
    } catch (e) {
      throw e;
    }
    return fData;
  }

  function loadResouces(callback) {
    var script = document.createElement("script");
    script.onload = function() {
      script = document.createElement("script");
      script.onload = function() {
        script = document.createElement("script");
        script.onload = function() {
          script = document.createElement("script");
          script.onload = function() {
            script = document.createElement("script");
            //script.onload = function() {
            callback && callback();
            //}
            //script.setAttribute("src", "jslint.js");
            //document.querySelector("head").appendChild(script);
          }
          script.setAttribute("src", "https://ajax.googleapis.com/ajax/libs/angular_material/0.9.4/angular-material.min.js");
          document.querySelector("head").appendChild(script);
        }
        script.setAttribute("src", "https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-aria.min.js");
        document.querySelector("head").appendChild(script);
      }
      script.setAttribute("src", "https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.min.js");
      document.querySelector("head").appendChild(script);
    }
    script.setAttribute("src", "beautify.js");
    document.querySelector("head").appendChild(script);
  }

  w.emTest = new EmTest();

}(window));
function emBootstrap(moduleName, pageData, i18n, pageSettings, pageApi) {
  angular.element(document).ready(function() {

    if (emTest && true || emTest.isPortalOn()) {

      emTest.startTestPortal(moduleName, pageData, i18n, pageSettings, pageApi);
    } else {
      var module = angular.module(moduleName);
      module.value("pageData", pageData);
      module.value("pageSettings", pageSettings);
      module.value("i18n", i18n);
      module.value("pageApi", pageApi)
      module.run(['$rootScope', function($rootScope) {
        $rootScope.i18n = i18n;
        $rootScope.pageSettings = pageSettings;
        $rootScope.pageApi = pageApi;
      }]);
      angular.bootstrap(document, [moduleName]);
    }
  });

}
<div class="em-test-portal" ng-controller="testPortalController as portal">
  <link rel="stylesheet" href="em-test-portal.css" />
  <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/0.9.4/angular-material.min.css">
  <div class="em-page-test-configuration" ng-show="portal.isShowPageConfigure" ng-controller="pageTestController">
    <h1 class="em-test-portal-head">EM Test Portal v1.1</h1>
    <section layout="row" layout-align="start end">
      <md-select class="test-inject-object md-primary" ng-model="injectObjectName" placeholder="select a page object to inject" ng-change="showInjectObjectName(injectObjectName)">
        <md-option value="pageData">pageData</md-option>
        <md-option value="i18n">i18n</md-option>
        <md-option value="pageSettings">pageSettings</md-option>
      </md-select>
      <md-button ng-show="isDataUpdated" ng-click="rollbackChanges()" class="md-icon-button md-accent">
        <md-icon md-svg-src="setting-back.svg"></md-icon>
      </md-button>
    </section>
    <md-select class="saved-test-data-list" ng-model="savedTestDataName" placeholder="Select saved test data" ng-change='loadTestData(savedTestDataName)'>
      <md-option ng-repeat="name in savedTestDataNames | filter:injectObjectName" value="{{name}}">{{name| savedData: "$em-test$"}}</md-option>
    </md-select>
    
    <section class="test-data-section">
      <div id="dataEditor"></div>
      <md-button class="test-data-beautify md-primary" ng-click="beautifyData()">Beautify Data</md-button>
      <label ng-show="errorMsg" class="test-data-error">
        {{errorMsg}}
      </label>    
    </section>
    <section layout="column" layout-align="center start">
      <md-switch class="ajax-call-switch" ng-model="portal.isSimulateAjax">Simulate Ajax Call</md-switch>
      <md-list class="ajax-filter-list">
        <div class="header">Ajax call filter (regx)</div>
        <md-list-item class="ajax-filter" ng-repeat="filter in savedAjaxFilters">
          <div layout="row" layout-align="start center">
            <div class="md-list-item-text ajax-filter-text">{{filter | savedData: "$em-ajax-filter$"}}</div>
            <md-button class="md-icon-button md-accent" ng-click="removeAjaxFilter(filter)">
              <md-icon md-svg-src="clear.svg"></md-icon>
            </md-button>
          </div>
          <md-divider></md-divider>
        </md-list-item>
        <md-list-item class="ajax-filter">
          <div layout="row" layout-align="start center">
            <md-input-container md-no-float="">
              <input class="ajax-filter-input" ng-model="newAjaxFilter" placeholder="Ajax path to include">
            </md-input-container>
            <md-button class="md-icon-button md-primary" ng-click="addAjaxFilter()">
              <md-icon md-svg-src="add.svg"></md-icon>
            </md-button>
          </div>
        </md-list-item>
      </md-list>
    </section>
    <section layout="column">
      <md-input-container>
        <label>Save test data as</label>
        <input class="save-test-data-as" ng-model="saveTestDataAs" type="text" />
      </md-input-container>
      <div>
        <md-button class="md-raised md-primary test-data-apply-button" ng-click="saveTestData();portal.bootstrap(injectableData);">Submit</md-button>
      </div>
    </section>
  </div>
  <div class="em-test-portal-ajax" ng-controller="ajaxTestController" ng-show="isShowAjaxConfigure" >
    <div class="em-test-configuration">
      <h3 class="em-test-portal-head">EM Ajax Test Portal v0.1</h3>
      <div>
        <label>Request Url:</label>{{requestUrl}}</div>
      <div>Response Status
        <label>
          <input type="radio" ng-model="responseStatus" value="200">200
        </label>
        <label>
          <input type="radio" ng-model="responseStatus" ng-value="404">404
        </label>
      </div>
      <select class="saved-test-data-list" ng-model="savedTestDataName" ng-change='loadTestData(savedTestDataName)'>
        <option value="">select saved test data</option>
        <option ng-repeat="name in savedTestDataNames" value="{{name}}">{{name| savedData:requestUrl}}</option>
      </select>
      <section class="test-data-section">
      <textarea class="test-data" ng-model="injectData"></textarea> 
      <md-button class="test-data-beautify md-primary" ng-click="beautifyData()">Beautify Data</md-button>
      <label ng-show="errorMsg" class="test-data-error">
        {{errorMsg}}
      </label>    
    </section>
      <div>
        <input type="checkbox" ng-model="isSimulateAjax" />Simulate Ajax Call
      </div>
      <div>
        <button ng-click="ajaxCallback(injectData, responseStatus, saveTestDataAs, savedTestDataName)">Submit</button>
        <input ng-model="saveTestDataAs" placeholder="give a name to save test data" class="save-test-data-as" type="text" />
      </div>
    </div>
  </div>
  <div class="em-open-configure" ng-mousedown="portal.moveConfigureIcon($event)" ng-click="portal.showHidePageConfigure()"  ng-hide="portal.isShowPageConfigure">
    <md-button class="md-fab md-mini md-accent" >
      <md-icon md-svg-src="edit.svg"></md-icon>
    </md-button>
  </div>
  <div class="em-test-page" ng-hide="portal.isShowPageConfigure"></div>
</div>
{"hi": "123"}

/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
/*

  The MIT License (MIT)

  Copyright (c) 2007-2013 Einar Lielmanis and contributors.

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation files
  (the "Software"), to deal in the Software without restriction,
  including without limitation the rights to use, copy, modify, merge,
  publish, distribute, sublicense, and/or sell copies of the Software,
  and to permit persons to whom the Software is furnished to do so,
  subject to the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.

 JS Beautifier
---------------


  Written by Einar Lielmanis, <einar@jsbeautifier.org>
      http://jsbeautifier.org/

  Originally converted to javascript by Vital, <vital76@gmail.com>
  "End braces on own line" added by Chris J. Shull, <chrisjshull@gmail.com>
  Parsing improvements for brace-less statements by Liam Newman <bitwiseman@gmail.com>


  Usage:
    js_beautify(js_source_text);
    js_beautify(js_source_text, options);

  The options are:
    indent_size (default 4)          - indentation size,
    indent_char (default space)      - character to indent with,
    preserve_newlines (default true) - whether existing line breaks should be preserved,
    max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk,

    jslint_happy (default false) - if true, then jslint-stricter mode is enforced.

            jslint_happy        !jslint_happy
            ---------------------------------
            function ()         function()

            switch () {         switch() {
            case 1:               case 1:
              break;                break;
            }                   }

    space_after_anon_function (default false) - should the space before an anonymous function's parens be added, "function()" vs "function ()",
          NOTE: This option is overriden by jslint_happy (i.e. if jslint_happy is true, space_after_anon_function is true by design)

    brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "none"
            put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are.

    space_before_conditional (default true) - should the space before conditional statement be added, "if(true)" vs "if (true)",

    unescape_strings (default false) - should printable characters in strings encoded in \xNN notation be unescaped, "example" vs "\x65\x78\x61\x6d\x70\x6c\x65"

    wrap_line_length (default unlimited) - lines should wrap at next opportunity after this number of characters.
          NOTE: This is not a hard limit. Lines will continue until a point where a newline would
                be preserved if it were present.

    end_with_newline (default false)  - end output with a newline


    e.g

    js_beautify(js_source_text, {
      'indent_size': 1,
      'indent_char': '\t'
    });

*/

(function (w) {

    var acorn = {};
    (function (exports) {
        // This section of code is taken from acorn.
        //
        // Acorn was written by Marijn Haverbeke and released under an MIT
        // license. The Unicode regexps (for identifiers and whitespace) were
        // taken from [Esprima](http://esprima.org) by Ariya Hidayat.
        //
        // Git repositories for Acorn are available at
        //
        //     http://marijnhaverbeke.nl/git/acorn
        //     https://github.com/marijnh/acorn.git

        // ## Character categories

        // Big ugly regular expressions that match characters in the
        // whitespace, identifier, and identifier-start categories. These
        // are only applied when a character is found to actually have a
        // code point above 128.

        var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
        var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
        var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f";
        var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
        var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");

        // Whether a single character denotes a newline.

        var newline = exports.newline = /[\n\r\u2028\u2029]/;

        // Matches a whole line break (where CRLF is considered a single
        // line break). Used to count lines.

        var lineBreak = /\r\n|[\n\r\u2028\u2029]/g;

        // Test whether a given character code starts an identifier.

        var isIdentifierStart = exports.isIdentifierStart = function (code) {
            if (code < 65) return code === 36;
            if (code < 91) return true;
            if (code < 97) return code === 95;
            if (code < 123) return true;
            return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
        };

        // Test whether a given character is part of an identifier.

        var isIdentifierChar = exports.isIdentifierChar = function (code) {
            if (code < 48) return code === 36;
            if (code < 58) return true;
            if (code < 65) return false;
            if (code < 91) return true;
            if (code < 97) return code === 95;
            if (code < 123) return true;
            return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
        };
    })(acorn);

    function in_array(what, arr) {
        for (var i = 0; i < arr.length; i += 1) {
            if (arr[i] === what) {
                return true;
            }
        }
        return false;
    }

    function trim(s) {
        return s.replace(/^\s+|\s+$/g, '');
    }

    function ltrim(s) {
        return s.replace(/^\s+/g, '');
    }

    function rtrim(s) {
        return s.replace(/\s+$/g, '');
    }

    function js_beautify(js_source_text, options) {
        "use strict";
        var beautifier = new Beautifier(js_source_text, options);
        return beautifier.beautify();
    }

    var MODE = {
        BlockStatement: 'BlockStatement', // 'BLOCK'
        Statement: 'Statement', // 'STATEMENT'
        ObjectLiteral: 'ObjectLiteral', // 'OBJECT',
        ArrayLiteral: 'ArrayLiteral', //'[EXPRESSION]',
        ForInitializer: 'ForInitializer', //'(FOR-EXPRESSION)',
        Conditional: 'Conditional', //'(COND-EXPRESSION)',
        Expression: 'Expression' //'(EXPRESSION)'
    };

    function Beautifier(js_source_text, options) {
        "use strict";
        var output
        var tokens = [], token_pos;
        var Tokenizer;
        var current_token;
        var last_type, last_last_text, indent_string;
        var flags, previous_flags, flag_store;
        var prefix;

        var handlers, opt;
        var baseIndentString = '';

        handlers = {
            'TK_START_EXPR': handle_start_expr,
            'TK_END_EXPR': handle_end_expr,
            'TK_START_BLOCK': handle_start_block,
            'TK_END_BLOCK': handle_end_block,
            'TK_WORD': handle_word,
            'TK_RESERVED': handle_word,
            'TK_SEMICOLON': handle_semicolon,
            'TK_STRING': handle_string,
            'TK_EQUALS': handle_equals,
            'TK_OPERATOR': handle_operator,
            'TK_COMMA': handle_comma,
            'TK_BLOCK_COMMENT': handle_block_comment,
            'TK_INLINE_COMMENT': handle_inline_comment,
            'TK_COMMENT': handle_comment,
            'TK_DOT': handle_dot,
            'TK_UNKNOWN': handle_unknown,
            'TK_EOF': handle_eof
        };

        function create_flags(flags_base, mode) {
            var next_indent_level = 0;
            if (flags_base) {
                next_indent_level = flags_base.indentation_level;
                if (!output.just_added_newline() &&
                    flags_base.line_indent_level > next_indent_level) {
                    next_indent_level = flags_base.line_indent_level;
                }
            }

            var next_flags = {
                mode: mode,
                parent: flags_base,
                last_text: flags_base ? flags_base.last_text : '', // last token text
                last_word: flags_base ? flags_base.last_word : '', // last 'TK_WORD' passed
                declaration_statement: false,
                declaration_assignment: false,
                multiline_frame: false,
                if_block: false,
                else_block: false,
                do_block: false,
                do_while: false,
                in_case_statement: false, // switch(..){ INSIDE HERE }
                in_case: false, // we're on the exact line with "case 0:"
                case_body: false, // the indented case-action block
                indentation_level: next_indent_level,
                line_indent_level: flags_base ? flags_base.line_indent_level : next_indent_level,
                start_line_index: output.get_line_number(),
                ternary_depth: 0
            };
            return next_flags;
        }

        // Some interpreters have unexpected results with foo = baz || bar;
        options = options ? options : {};
        opt = {};

        // compatibility
        if (options.braces_on_own_line !== undefined) { //graceful handling of deprecated option
            opt.brace_style = options.braces_on_own_line ? "expand" : "collapse";
        }
        opt.brace_style = options.brace_style ? options.brace_style : (opt.brace_style ? opt.brace_style : "collapse");

        // graceful handling of deprecated option
        if (opt.brace_style === "expand-strict") {
            opt.brace_style = "expand";
        }


        opt.indent_size = options.indent_size ? parseInt(options.indent_size, 10) : 4;
        opt.indent_char = options.indent_char ? options.indent_char : ' ';
        opt.preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
        opt.break_chained_methods = (options.break_chained_methods === undefined) ? false : options.break_chained_methods;
        opt.max_preserve_newlines = (options.max_preserve_newlines === undefined) ? 0 : parseInt(options.max_preserve_newlines, 10);
        opt.space_in_paren = (options.space_in_paren === undefined) ? false : options.space_in_paren;
        opt.space_in_empty_paren = (options.space_in_empty_paren === undefined) ? false : options.space_in_empty_paren;
        opt.jslint_happy = (options.jslint_happy === undefined) ? false : options.jslint_happy;
        opt.space_after_anon_function = (options.space_after_anon_function === undefined) ? false : options.space_after_anon_function;
        opt.keep_array_indentation = (options.keep_array_indentation === undefined) ? false : options.keep_array_indentation;
        opt.space_before_conditional = (options.space_before_conditional === undefined) ? true : options.space_before_conditional;
        opt.unescape_strings = (options.unescape_strings === undefined) ? false : options.unescape_strings;
        opt.wrap_line_length = (options.wrap_line_length === undefined) ? 0 : parseInt(options.wrap_line_length, 10);
        opt.e4x = (options.e4x === undefined) ? false : options.e4x;
        opt.end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
        opt.comma_first = (options.comma_first === undefined) ? false : options.comma_first;


        // force opt.space_after_anon_function to true if opt.jslint_happy
        if (opt.jslint_happy) {
            opt.space_after_anon_function = true;
        }

        if (options.indent_with_tabs) {
            opt.indent_char = '\t';
            opt.indent_size = 1;
        }

        //----------------------------------
        indent_string = '';
        while (opt.indent_size > 0) {
            indent_string += opt.indent_char;
            opt.indent_size -= 1;
        }

        var preindent_index = 0;
        if (js_source_text && js_source_text.length) {
            while ((js_source_text.charAt(preindent_index) === ' ' ||
                    js_source_text.charAt(preindent_index) === '\t')) {
                baseIndentString += js_source_text.charAt(preindent_index);
                preindent_index += 1;
            }
            js_source_text = js_source_text.substring(preindent_index);
        }

        last_type = 'TK_START_BLOCK'; // last token type
        last_last_text = ''; // pre-last token text
        output = new Output(indent_string, baseIndentString);


        // Stack of parsing/formatting states, including MODE.
        // We tokenize, parse, and output in an almost purely a forward-only stream of token input
        // and formatted output.  This makes the beautifier less accurate than full parsers
        // but also far more tolerant of syntax errors.
        //
        // For example, the default mode is MODE.BlockStatement. If we see a '{' we push a new frame of type
        // MODE.BlockStatement on the the stack, even though it could be object literal.  If we later
        // encounter a ":", we'll switch to to MODE.ObjectLiteral.  If we then see a ";",
        // most full parsers would die, but the beautifier gracefully falls back to
        // MODE.BlockStatement and continues on.
        flag_store = [];
        set_mode(MODE.BlockStatement);

        this.beautify = function () {

            /*jshint onevar:true */
            var local_token, sweet_code;
            Tokenizer = new tokenizer(js_source_text, opt, indent_string);
            tokens = Tokenizer.tokenize();
            token_pos = 0;

            while (local_token = get_token()) {
                for (var i = 0; i < local_token.comments_before.length; i++) {
                    // The cleanest handling of inline comments is to treat them as though they aren't there.
                    // Just continue formatting and the behavior should be logical.
                    // Also ignore unknown tokens.  Again, this should result in better behavior.
                    handle_token(local_token.comments_before[i]);
                }
                handle_token(local_token);

                last_last_text = flags.last_text;
                last_type = local_token.type;
                flags.last_text = local_token.text;

                token_pos += 1;
            }

            sweet_code = output.get_code();
            if (opt.end_with_newline) {
                sweet_code += '\n';
            }

            return sweet_code;
        };

        function handle_token(local_token) {
            var newlines = local_token.newlines;
            var keep_whitespace = opt.keep_array_indentation && is_array(flags.mode);

            if (keep_whitespace) {
                for (i = 0; i < newlines; i += 1) {
                    print_newline(i > 0);
                }
            } else {
                if (opt.max_preserve_newlines && newlines > opt.max_preserve_newlines) {
                    newlines = opt.max_preserve_newlines;
                }

                if (opt.preserve_newlines) {
                    if (local_token.newlines > 1) {
                        print_newline();
                        for (var i = 1; i < newlines; i += 1) {
                            print_newline(true);
                        }
                    }
                }
            }

            current_token = local_token;
            handlers[current_token.type]();
        }

        // we could use just string.split, but
        // IE doesn't like returning empty strings
        function split_newlines(s) {
            //return s.split(/\x0d\x0a|\x0a/);

            s = s.replace(/\x0d/g, '');
            var out = [],
                idx = s.indexOf("\n");
            while (idx !== -1) {
                out.push(s.substring(0, idx));
                s = s.substring(idx + 1);
                idx = s.indexOf("\n");
            }
            if (s.length) {
                out.push(s);
            }
            return out;
        }

        function allow_wrap_or_preserved_newline(force_linewrap) {
            force_linewrap = (force_linewrap === undefined) ? false : force_linewrap;

            // Never wrap the first token on a line
            if (output.just_added_newline()) {
                return
            }

            if ((opt.preserve_newlines && current_token.wanted_newline) || force_linewrap) {
                print_newline(false, true);
            } else if (opt.wrap_line_length) {
                var proposed_line_length = output.current_line.get_character_count() + current_token.text.length +
                    (output.space_before_token ? 1 : 0);
                if (proposed_line_length >= opt.wrap_line_length) {
                    print_newline(false, true);
                }
            }
        }

        function print_newline(force_newline, preserve_statement_flags) {
            if (!preserve_statement_flags) {
                if (flags.last_text !== ';' && flags.last_text !== ',' && flags.last_text !== '=' && last_type !== 'TK_OPERATOR') {
                    while (flags.mode === MODE.Statement && !flags.if_block && !flags.do_block) {
                        restore_mode();
                    }
                }
            }

            if (output.add_new_line(force_newline)) {
                flags.multiline_frame = true;
            }
        }

        function print_token_line_indentation() {
            if (output.just_added_newline()) {
                if (opt.keep_array_indentation && is_array(flags.mode) && current_token.wanted_newline) {
                    output.current_line.push(current_token.whitespace_before);
                    output.space_before_token = false;
                } else if (output.set_indent(flags.indentation_level)) {
                    flags.line_indent_level = flags.indentation_level;
                }
            }
        }

        function print_token(printable_token) {
            if (opt.comma_first && last_type === 'TK_COMMA'
                && output.just_added_newline()) {
                if (output.previous_line.last() === ',') {
                    output.previous_line.pop();
                    print_token_line_indentation();
                    output.add_token(',');
                    output.space_before_token = true;
                }
            }

            printable_token = printable_token || current_token.text;
            print_token_line_indentation();
            output.add_token(printable_token);
        }

        function indent() {
            flags.indentation_level += 1;
        }

        function deindent() {
            if (flags.indentation_level > 0 &&
                ((!flags.parent) || flags.indentation_level > flags.parent.indentation_level))
                flags.indentation_level -= 1;
        }

        function set_mode(mode) {
            if (flags) {
                flag_store.push(flags);
                previous_flags = flags;
            } else {
                previous_flags = create_flags(null, mode);
            }

            flags = create_flags(previous_flags, mode);
        }

        function is_array(mode) {
            return mode === MODE.ArrayLiteral;
        }

        function is_expression(mode) {
            return in_array(mode, [MODE.Expression, MODE.ForInitializer, MODE.Conditional]);
        }

        function restore_mode() {
            if (flag_store.length > 0) {
                previous_flags = flags;
                flags = flag_store.pop();
                if (previous_flags.mode === MODE.Statement) {
                    output.remove_redundant_indentation(previous_flags);
                }
            }
        }

        function start_of_object_property() {
            return flags.parent.mode === MODE.ObjectLiteral && flags.mode === MODE.Statement && (
                (flags.last_text === ':' && flags.ternary_depth === 0) || (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['get', 'set'])));
        }

        function start_of_statement() {
            if (
                    (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['var', 'let', 'const']) && current_token.type === 'TK_WORD') ||
                    (last_type === 'TK_RESERVED' && flags.last_text === 'do') ||
                    (last_type === 'TK_RESERVED' && flags.last_text === 'return' && !current_token.wanted_newline) ||
                    (last_type === 'TK_RESERVED' && flags.last_text === 'else' && !(current_token.type === 'TK_RESERVED' && current_token.text === 'if')) ||
                    (last_type === 'TK_END_EXPR' && (previous_flags.mode === MODE.ForInitializer || previous_flags.mode === MODE.Conditional)) ||
                    (last_type === 'TK_WORD' && flags.mode === MODE.BlockStatement
                        && !flags.in_case
                        && !(current_token.text === '--' || current_token.text === '++')
                        && current_token.type !== 'TK_WORD' && current_token.type !== 'TK_RESERVED') ||
                    (flags.mode === MODE.ObjectLiteral && (
                        (flags.last_text === ':' && flags.ternary_depth === 0) || (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['get', 'set']))))
                ) {

                set_mode(MODE.Statement);
                indent();

                if (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['var', 'let', 'const']) && current_token.type === 'TK_WORD') {
                    flags.declaration_statement = true;
                }

                // Issue #276:
                // If starting a new statement with [if, for, while, do], push to a new line.
                // if (a) if (b) if(c) d(); else e(); else f();
                if (!start_of_object_property()) {
                    allow_wrap_or_preserved_newline(
                        current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['do', 'for', 'if', 'while']));
                }

                return true;
            }
            return false;
        }

        function all_lines_start_with(lines, c) {
            for (var i = 0; i < lines.length; i++) {
                var line = trim(lines[i]);
                if (line.charAt(0) !== c) {
                    return false;
                }
            }
            return true;
        }

        function each_line_matches_indent(lines, indent) {
            var i = 0,
                len = lines.length,
                line;
            for (; i < len; i++) {
                line = lines[i];
                // allow empty lines to pass through
                if (line && line.indexOf(indent) !== 0) {
                    return false;
                }
            }
            return true;
        }

        function is_special_word(word) {
            return in_array(word, ['case', 'return', 'do', 'if', 'throw', 'else']);
        }

        function get_token(offset) {
            var index = token_pos + (offset || 0);
            return (index < 0 || index >= tokens.length) ? null : tokens[index];
        }

        function handle_start_expr() {
            if (start_of_statement()) {
                // The conditional starts the statement if appropriate.
            }

            var next_mode = MODE.Expression;
            if (current_token.text === '[') {

                if (last_type === 'TK_WORD' || flags.last_text === ')') {
                    // this is array index specifier, break immediately
                    // a[x], fn()[x]
                    if (last_type === 'TK_RESERVED' && in_array(flags.last_text, Tokenizer.line_starters)) {
                        output.space_before_token = true;
                    }
                    set_mode(next_mode);
                    print_token();
                    indent();
                    if (opt.space_in_paren) {
                        output.space_before_token = true;
                    }
                    return;
                }

                next_mode = MODE.ArrayLiteral;
                if (is_array(flags.mode)) {
                    if (flags.last_text === '[' ||
                        (flags.last_text === ',' && (last_last_text === ']' || last_last_text === '}'))) {
                        // ], [ goes to new line
                        // }, [ goes to new line
                        if (!opt.keep_array_indentation) {
                            print_newline();
                        }
                    }
                }

            } else {
                if (last_type === 'TK_RESERVED' && flags.last_text === 'for') {
                    next_mode = MODE.ForInitializer;
                } else if (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['if', 'while'])) {
                    next_mode = MODE.Conditional;
                } else {
                    // next_mode = MODE.Expression;
                }
            }

            if (flags.last_text === ';' || last_type === 'TK_START_BLOCK') {
                print_newline();
            } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || flags.last_text === '.') {
                // TODO: Consider whether forcing this is required.  Review failing tests when removed.
                allow_wrap_or_preserved_newline(current_token.wanted_newline);
                // do nothing on (( and )( and ][ and ]( and .(
            } else if (!(last_type === 'TK_RESERVED' && current_token.text === '(') && last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
                output.space_before_token = true;
            } else if ((last_type === 'TK_RESERVED' && (flags.last_word === 'function' || flags.last_word === 'typeof')) ||
                (flags.last_text === '*' && last_last_text === 'function')) {
                // function() vs function ()
                if (opt.space_after_anon_function) {
                    output.space_before_token = true;
                }
            } else if (last_type === 'TK_RESERVED' && (in_array(flags.last_text, Tokenizer.line_starters) || flags.last_text === 'catch')) {
                if (opt.space_before_conditional) {
                    output.space_before_token = true;
                }
            }

            // Support of this kind of newline preservation.
            // a = (b &&
            //     (c || d));
            if (current_token.text === '(') {
                if (last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
                    if (!start_of_object_property()) {
                        allow_wrap_or_preserved_newline();
                    }
                }
            }

            set_mode(next_mode);
            print_token();
            if (opt.space_in_paren) {
                output.space_before_token = true;
            }

            // In all cases, if we newline while inside an expression it should be indented.
            indent();
        }

        function handle_end_expr() {
            // statements inside expressions are not valid syntax, but...
            // statements must all be closed when their container closes
            while (flags.mode === MODE.Statement) {
                restore_mode();
            }

            if (flags.multiline_frame) {
                allow_wrap_or_preserved_newline(current_token.text === ']' && is_array(flags.mode) && !opt.keep_array_indentation);
            }

            if (opt.space_in_paren) {
                if (last_type === 'TK_START_EXPR' && !opt.space_in_empty_paren) {
                    // () [] no inner space in empty parens like these, ever, ref #320
                    output.trim();
                    output.space_before_token = false;
                } else {
                    output.space_before_token = true;
                }
            }
            if (current_token.text === ']' && opt.keep_array_indentation) {
                print_token();
                restore_mode();
            } else {
                restore_mode();
                print_token();
            }
            output.remove_redundant_indentation(previous_flags);

            // do {} while () // no statement required after
            if (flags.do_while && previous_flags.mode === MODE.Conditional) {
                previous_flags.mode = MODE.Expression;
                flags.do_block = false;
                flags.do_while = false;

            }
        }

        function handle_start_block() {
            // Check if this is should be treated as a ObjectLiteral
            var next_token = get_token(1)
            var second_token = get_token(2)
            if (second_token && (
                    (second_token.text === ':' && in_array(next_token.type, ['TK_STRING', 'TK_WORD', 'TK_RESERVED']))
                    || (in_array(next_token.text, ['get', 'set']) && in_array(second_token.type, ['TK_WORD', 'TK_RESERVED']))
                )) {
                // We don't support TypeScript,but we didn't break it for a very long time.
                // We'll try to keep not breaking it.
                if (!in_array(last_last_text, ['class', 'interface'])) {
                    set_mode(MODE.ObjectLiteral);
                } else {
                    set_mode(MODE.BlockStatement);
                }
            } else {
                set_mode(MODE.BlockStatement);
            }

            var empty_braces = !next_token.comments_before.length && next_token.text === '}';
            var empty_anonymous_function = empty_braces && flags.last_word === 'function' &&
                last_type === 'TK_END_EXPR';

            if (opt.brace_style === "expand" ||
                (opt.brace_style === "none" && current_token.wanted_newline)) {
                if (last_type !== 'TK_OPERATOR' &&
                    (empty_anonymous_function ||
                        last_type === 'TK_EQUALS' ||
                        (last_type === 'TK_RESERVED' && is_special_word(flags.last_text) && flags.last_text !== 'else'))) {
                    output.space_before_token = true;
                } else {
                    print_newline(false, true);
                }
            } else { // collapse
                if (last_type !== 'TK_OPERATOR' && last_type !== 'TK_START_EXPR') {
                    if (last_type === 'TK_START_BLOCK') {
                        print_newline();
                    } else {
                        output.space_before_token = true;
                    }
                } else {
                    // if TK_OPERATOR or TK_START_EXPR
                    if (is_array(previous_flags.mode) && flags.last_text === ',') {
                        if (last_last_text === '}') {
                            // }, { in array context
                            output.space_before_token = true;
                        } else {
                            print_newline(); // [a, b, c, {
                        }
                    }
                }
            }
            print_token();
            indent();
        }

        function handle_end_block() {
            // statements must all be closed when their container closes
            while (flags.mode === MODE.Statement) {
                restore_mode();
            }
            var empty_braces = last_type === 'TK_START_BLOCK';

            if (opt.brace_style === "expand") {
                if (!empty_braces) {
                    print_newline();
                }
            } else {
                // skip {}
                if (!empty_braces) {
                    if (is_array(flags.mode) && opt.keep_array_indentation) {
                        // we REALLY need a newline here, but newliner would skip that
                        opt.keep_array_indentation = false;
                        print_newline();
                        opt.keep_array_indentation = true;

                    } else {
                        print_newline();
                    }
                }
            }
            restore_mode();
            print_token();
        }

        function handle_word() {
            if (current_token.type === 'TK_RESERVED' && flags.mode !== MODE.ObjectLiteral &&
                in_array(current_token.text, ['set', 'get'])) {
                current_token.type = 'TK_WORD';
            }

            if (current_token.type === 'TK_RESERVED' && flags.mode === MODE.ObjectLiteral) {
                var next_token = get_token(1);
                if (next_token.text == ':') {
                    current_token.type = 'TK_WORD';
                }
            }

            if (start_of_statement()) {
                // The conditional starts the statement if appropriate.
            } else if (current_token.wanted_newline && !is_expression(flags.mode) &&
                (last_type !== 'TK_OPERATOR' || (flags.last_text === '--' || flags.last_text === '++')) &&
                last_type !== 'TK_EQUALS' &&
                (opt.preserve_newlines || !(last_type === 'TK_RESERVED' && in_array(flags.last_text, ['var', 'let', 'const', 'set', 'get'])))) {

                print_newline();
            }

            if (flags.do_block && !flags.do_while) {
                if (current_token.type === 'TK_RESERVED' && current_token.text === 'while') {
                    // do {} ## while ()
                    output.space_before_token = true;
                    print_token();
                    output.space_before_token = true;
                    flags.do_while = true;
                    return;
                } else {
                    // do {} should always have while as the next word.
                    // if we don't see the expected while, recover
                    print_newline();
                    flags.do_block = false;
                }
            }

            // if may be followed by else, or not
            // Bare/inline ifs are tricky
            // Need to unwind the modes correctly: if (a) if (b) c(); else d(); else e();
            if (flags.if_block) {
                if (!flags.else_block && (current_token.type === 'TK_RESERVED' && current_token.text === 'else')) {
                    flags.else_block = true;
                } else {
                    while (flags.mode === MODE.Statement) {
                        restore_mode();
                    }
                    flags.if_block = false;
                    flags.else_block = false;
                }
            }

            if (current_token.type === 'TK_RESERVED' && (current_token.text === 'case' || (current_token.text === 'default' && flags.in_case_statement))) {
                print_newline();
                if (flags.case_body || opt.jslint_happy) {
                    // switch cases following one another
                    deindent();
                    flags.case_body = false;
                }
                print_token();
                flags.in_case = true;
                flags.in_case_statement = true;
                return;
            }

            if (current_token.type === 'TK_RESERVED' && current_token.text === 'function') {
                if (in_array(flags.last_text, ['}', ';']) || (output.just_added_newline() && !in_array(flags.last_text, ['[', '{', ':', '=', ',']))) {
                    // make sure there is a nice clean space of at least one blank line
                    // before a new function definition
                    if (!output.just_added_blankline() && !current_token.comments_before.length) {
                        print_newline();
                        print_newline(true);
                    }
                }
                if (last_type === 'TK_RESERVED' || last_type === 'TK_WORD') {
                    if (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['get', 'set', 'new', 'return', 'export'])) {
                        output.space_before_token = true;
                    } else if (last_type === 'TK_RESERVED' && flags.last_text === 'default' && last_last_text === 'export') {
                        output.space_before_token = true;
                    } else {
                        print_newline();
                    }
                } else if (last_type === 'TK_OPERATOR' || flags.last_text === '=') {
                    // foo = function
                    output.space_before_token = true;
                } else if (!flags.multiline_frame && (is_expression(flags.mode) || is_array(flags.mode))) {
                    // (function
                } else {
                    print_newline();
                }
            }

            if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
                if (!start_of_object_property()) {
                    allow_wrap_or_preserved_newline();
                }
            }

            if (current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['function', 'get', 'set'])) {
                print_token();
                flags.last_word = current_token.text;
                return;
            }

            prefix = 'NONE';

            if (last_type === 'TK_END_BLOCK') {
                if (!(current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['else', 'catch', 'finally']))) {
                    prefix = 'NEWLINE';
                } else {
                    if (opt.brace_style === "expand" ||
                        opt.brace_style === "end-expand" ||
                        (opt.brace_style === "none" && current_token.wanted_newline)) {
                        prefix = 'NEWLINE';
                    } else {
                        prefix = 'SPACE';
                        output.space_before_token = true;
                    }
                }
            } else if (last_type === 'TK_SEMICOLON' && flags.mode === MODE.BlockStatement) {
                // TODO: Should this be for STATEMENT as well?
                prefix = 'NEWLINE';
            } else if (last_type === 'TK_SEMICOLON' && is_expression(flags.mode)) {
                prefix = 'SPACE';
            } else if (last_type === 'TK_STRING') {
                prefix = 'NEWLINE';
            } else if (last_type === 'TK_RESERVED' || last_type === 'TK_WORD' ||
                (flags.last_text === '*' && last_last_text === 'function')) {
                prefix = 'SPACE';
            } else if (last_type === 'TK_START_BLOCK') {
                prefix = 'NEWLINE';
            } else if (last_type === 'TK_END_EXPR') {
                output.space_before_token = true;
                prefix = 'NEWLINE';
            }

            if (current_token.type === 'TK_RESERVED' && in_array(current_token.text, Tokenizer.line_starters) && flags.last_text !== ')') {
                if (flags.last_text === 'else' || flags.last_text === 'export') {
                    prefix = 'SPACE';
                } else {
                    prefix = 'NEWLINE';
                }

            }

            if (current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['else', 'catch', 'finally'])) {
                if (last_type !== 'TK_END_BLOCK' ||
                    opt.brace_style === "expand" ||
                    opt.brace_style === "end-expand" ||
                    (opt.brace_style === "none" && current_token.wanted_newline)) {
                    print_newline();
                } else {
                    output.trim(true);
                    var line = output.current_line;
                    // If we trimmed and there's something other than a close block before us
                    // put a newline back in.  Handles '} // comment' scenario.
                    if (line.last() !== '}') {
                        print_newline();
                    }
                    output.space_before_token = true;
                }
            } else if (prefix === 'NEWLINE') {
                if (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) {
                    // no newline between 'return nnn'
                    output.space_before_token = true;
                } else if (last_type !== 'TK_END_EXPR') {
                    if ((last_type !== 'TK_START_EXPR' || !(current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['var', 'let', 'const']))) && flags.last_text !== ':') {
                        // no need to force newline on 'var': for (var x = 0...)
                        if (current_token.type === 'TK_RESERVED' && current_token.text === 'if' && flags.last_text === 'else') {
                            // no newline for } else if {
                            output.space_before_token = true;
                        } else {
                            print_newline();
                        }
                    }
                } else if (current_token.type === 'TK_RESERVED' && in_array(current_token.text, Tokenizer.line_starters) && flags.last_text !== ')') {
                    print_newline();
                }
            } else if (flags.multiline_frame && is_array(flags.mode) && flags.last_text === ',' && last_last_text === '}') {
                print_newline(); // }, in lists get a newline treatment
            } else if (prefix === 'SPACE') {
                output.space_before_token = true;
            }
            print_token();
            flags.last_word = current_token.text;

            if (current_token.type === 'TK_RESERVED' && current_token.text === 'do') {
                flags.do_block = true;
            }

            if (current_token.type === 'TK_RESERVED' && current_token.text === 'if') {
                flags.if_block = true;
            }
        }

        function handle_semicolon() {
            if (start_of_statement()) {
                // The conditional starts the statement if appropriate.
                // Semicolon can be the start (and end) of a statement
                output.space_before_token = false;
            }
            while (flags.mode === MODE.Statement && !flags.if_block && !flags.do_block) {
                restore_mode();
            }
            print_token();
        }

        function handle_string() {
            if (start_of_statement()) {
                // The conditional starts the statement if appropriate.
                // One difference - strings want at least a space before
                output.space_before_token = true;
            } else if (last_type === 'TK_RESERVED' || last_type === 'TK_WORD') {
                output.space_before_token = true;
            } else if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
                if (!start_of_object_property()) {
                    allow_wrap_or_preserved_newline();
                }
            } else {
                print_newline();
            }
            print_token();
        }

        function handle_equals() {
            if (start_of_statement()) {
                // The conditional starts the statement if appropriate.
            }

            if (flags.declaration_statement) {
                // just got an '=' in a var-line, different formatting/line-breaking, etc will now be done
                flags.declaration_assignment = true;
            }
            output.space_before_token = true;
            print_token();
            output.space_before_token = true;
        }

        function handle_comma() {
            if (flags.declaration_statement) {
                if (is_expression(flags.parent.mode)) {
                    // do not break on comma, for(var a = 1, b = 2)
                    flags.declaration_assignment = false;
                }

                print_token();

                if (flags.declaration_assignment) {
                    flags.declaration_assignment = false;
                    print_newline(false, true);
                } else {
                    output.space_before_token = true;
                    // for comma-first, we want to allow a newline before the comma
                    // to turn into a newline after the comma, which we will fixup later
                    if (opt.comma_first) {
                        allow_wrap_or_preserved_newline();
                    }
                }
                return;
            }

            print_token();
            if (flags.mode === MODE.ObjectLiteral ||
                (flags.mode === MODE.Statement && flags.parent.mode === MODE.ObjectLiteral)) {
                if (flags.mode === MODE.Statement) {
                    restore_mode();
                }
                print_newline();
            } else {
                // EXPR or DO_BLOCK
                output.space_before_token = true;
                // for comma-first, we want to allow a newline before the comma
                // to turn into a newline after the comma, which we will fixup later
                if (opt.comma_first) {
                    allow_wrap_or_preserved_newline();
                }
            }

        }

        function handle_operator() {
            if (start_of_statement()) {
                // The conditional starts the statement if appropriate.
            }

            if (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) {
                // "return" had a special handling in TK_WORD. Now we need to return the favor
                output.space_before_token = true;
                print_token();
                return;
            }

            // hack for actionscript's import .*;
            if (current_token.text === '*' && last_type === 'TK_DOT') {
                print_token();
                return;
            }

            if (current_token.text === ':' && flags.in_case) {
                flags.case_body = true;
                indent();
                print_token();
                print_newline();
                flags.in_case = false;
                return;
            }

            if (current_token.text === '::') {
                // no spaces around exotic namespacing syntax operator
                print_token();
                return;
            }

            // Allow line wrapping between operators
            if (last_type === 'TK_OPERATOR') {
                allow_wrap_or_preserved_newline();
            }

            var space_before = true;
            var space_after = true;

            if (in_array(current_token.text, ['--', '++', '!', '~']) || (in_array(current_token.text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || in_array(flags.last_text, Tokenizer.line_starters) || flags.last_text === ','))) {
                // unary operators (and binary +/- pretending to be unary) special cases

                space_before = false;
                space_after = false;

                // http://www.ecma-international.org/ecma-262/5.1/#sec-7.9.1
                // if there is a newline between -- or ++ and anything else we should preserve it.
                if (current_token.wanted_newline && (current_token.text === '--' || current_token.text === '++')) {
                    print_newline(false, true);
                }

                if (flags.last_text === ';' && is_expression(flags.mode)) {
                    // for (;; ++i)
                    //        ^^^
                    space_before = true;
                }

                if (last_type === 'TK_RESERVED') {
                    space_before = true;
                } else if (last_type === 'TK_END_EXPR') {
                    space_before = !(flags.last_text === ']' && (current_token.text === '--' || current_token.text === '++'));
                } else if (last_type === 'TK_OPERATOR') {
                    // a++ + ++b;
                    // a - -b
                    space_before = in_array(current_token.text, ['--', '-', '++', '+']) && in_array(flags.last_text, ['--', '-', '++', '+']);
                    // + and - are not unary when preceeded by -- or ++ operator
                    // a-- + b
                    // a * +b
                    // a - -b
                    if (in_array(current_token.text, ['+', '-']) && in_array(flags.last_text, ['--', '++'])) {
                        space_after = true;
                    }
                }

                if ((flags.mode === MODE.BlockStatement || flags.mode === MODE.Statement) && (flags.last_text === '{' || flags.last_text === ';')) {
                    // { foo; --i }
                    // foo(); --bar;
                    print_newline();
                }
            } else if (current_token.text === ':') {
                if (flags.ternary_depth === 0) {
                    // Colon is invalid javascript outside of ternary and object, but do our best to guess what was meant.
                    space_before = false;
                } else {
                    flags.ternary_depth -= 1;
                }
            } else if (current_token.text === '?') {
                flags.ternary_depth += 1;
            } else if (current_token.text === '*' && last_type === 'TK_RESERVED' && flags.last_text === 'function') {
                space_before = false;
                space_after = false;
            }
            output.space_before_token = output.space_before_token || space_before;
            print_token();
            output.space_before_token = space_after;
        }

        function handle_block_comment() {
            var lines = split_newlines(current_token.text);
            var j; // iterator for this case
            var javadoc = false;
            var starless = false;
            var lastIndent = current_token.whitespace_before;
            var lastIndentLength = lastIndent.length;

            // block comment starts with a new line
            print_newline(false, true);
            if (lines.length > 1) {
                if (all_lines_start_with(lines.slice(1), '*')) {
                    javadoc = true;
                }
                else if (each_line_matches_indent(lines.slice(1), lastIndent)) {
                    starless = true;
                }
            }

            // first line always indented
            print_token(lines[0]);
            for (j = 1; j < lines.length; j++) {
                print_newline(false, true);
                if (javadoc) {
                    // javadoc: reformat and re-indent
                    print_token(' ' + ltrim(lines[j]));
                } else if (starless && lines[j].length > lastIndentLength) {
                    // starless: re-indent non-empty content, avoiding trim
                    print_token(lines[j].substring(lastIndentLength));
                } else {
                    // normal comments output raw
                    output.add_token(lines[j]);
                }
            }

            // for comments of more than one line, make sure there's a new line after
            print_newline(false, true);
        }

        function handle_inline_comment() {
            output.space_before_token = true;
            print_token();
            output.space_before_token = true;
        }

        function handle_comment() {
            if (current_token.wanted_newline) {
                print_newline(false, true);
            } else {
                output.trim(true);
            }

            output.space_before_token = true;
            print_token();
            print_newline(false, true);
        }

        function handle_dot() {
            if (start_of_statement()) {
                // The conditional starts the statement if appropriate.
            }

            if (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) {
                output.space_before_token = true;
            } else {
                // allow preserved newlines before dots in general
                // force newlines on dots after close paren when break_chained - for bar().baz()
                allow_wrap_or_preserved_newline(flags.last_text === ')' && opt.break_chained_methods);
            }

            print_token();
        }

        function handle_unknown() {
            print_token();

            if (current_token.text[current_token.text.length - 1] === '\n') {
                print_newline();
            }
        }

        function handle_eof() {
            // Unwind any open statements
            while (flags.mode === MODE.Statement) {
                restore_mode();
            }
        }
    }


    function OutputLine(parent) {
        var _character_count = 0;
        // use indent_count as a marker for lines that have preserved indentation
        var _indent_count = -1;

        var _items = [];
        var _empty = true;

        this.set_indent = function (level) {
            _character_count = parent.baseIndentLength + level * parent.indent_length
            _indent_count = level;
        }

        this.get_character_count = function () {
            return _character_count;
        }

        this.is_empty = function () {
            return _empty;
        }

        this.last = function () {
            if (!this._empty) {
                return _items[_items.length - 1];
            } else {
                return null;
            }
        }

        this.push = function (input) {
            _items.push(input);
            _character_count += input.length;
            _empty = false;
        }

        this.pop = function () {
            var item = null;
            if (!_empty) {
                item = _items.pop();
                _character_count -= item.length;
                _empty = _items.length === 0;
            }
            return item;
        }

        this.remove_indent = function () {
            if (_indent_count > 0) {
                _indent_count -= 1;
                _character_count -= parent.indent_length
            }
        }

        this.trim = function () {
            while (this.last() === ' ') {
                var item = _items.pop();
                _character_count -= 1;
            }
            _empty = _items.length === 0;
        }

        this.toString = function () {
            var result = '';
            if (!this._empty) {
                if (_indent_count >= 0) {
                    result = parent.indent_cache[_indent_count];
                }
                result += _items.join('')
            }
            return result;
        }
    }

    function Output(indent_string, baseIndentString) {
        baseIndentString = baseIndentString || '';
        this.indent_cache = [baseIndentString];
        this.baseIndentLength = baseIndentString.length;
        this.indent_length = indent_string.length;

        var lines = [];
        this.baseIndentString = baseIndentString;
        this.indent_string = indent_string;
        this.previous_line = null;
        this.current_line = null;
        this.space_before_token = false;

        this.get_line_number = function () {
            return lines.length;
        }

        // Using object instead of string to allow for later expansion of info about each line
        this.add_new_line = function (force_newline) {
            if (this.get_line_number() === 1 && this.just_added_newline()) {
                return false; // no newline on start of file
            }

            if (force_newline || !this.just_added_newline()) {
                this.previous_line = this.current_line;
                this.current_line = new OutputLine(this);
                lines.push(this.current_line);
                return true;
            }

            return false;
        }

        // initialize
        this.add_new_line(true);

        this.get_code = function () {
            var sweet_code = lines.join('\n').replace(/[\r\n\t ]+$/, '');
            return sweet_code;
        }

        this.set_indent = function (level) {
            // Never indent your first output indent at the start of the file
            if (lines.length > 1) {
                while (level >= this.indent_cache.length) {
                    this.indent_cache.push(this.indent_cache[this.indent_cache.length - 1] + this.indent_string);
                }

                this.current_line.set_indent(level);
                return true;
            }
            this.current_line.set_indent(0);
            return false;
        }

        this.add_token = function (printable_token) {
            this.add_space_before_token();
            this.current_line.push(printable_token);
        }

        this.add_space_before_token = function () {
            if (this.space_before_token && !this.just_added_newline()) {
                this.current_line.push(' ');
            }
            this.space_before_token = false;
        }

        this.remove_redundant_indentation = function (frame) {
            // This implementation is effective but has some issues:
            //     - can cause line wrap to happen too soon due to indent removal
            //           after wrap points are calculated
            // These issues are minor compared to ugly indentation.

            if (frame.multiline_frame ||
                frame.mode === MODE.ForInitializer ||
                frame.mode === MODE.Conditional) {
                return;
            }

            // remove one indent from each line inside this section
            var index = frame.start_line_index;
            var line;

            var output_length = lines.length;
            while (index < output_length) {
                lines[index].remove_indent();
                index++;
            }
        }

        this.trim = function (eat_newlines) {
            eat_newlines = (eat_newlines === undefined) ? false : eat_newlines;

            this.current_line.trim(indent_string, baseIndentString);

            while (eat_newlines && lines.length > 1 &&
                this.current_line.is_empty()) {
                lines.pop();
                this.current_line = lines[lines.length - 1]
                this.current_line.trim();
            }

            this.previous_line = lines.length > 1 ? lines[lines.length - 2] : null;
        }

        this.just_added_newline = function () {
            return this.current_line.is_empty();
        }

        this.just_added_blankline = function () {
            if (this.just_added_newline()) {
                if (lines.length === 1) {
                    return true; // start of the file and newline = blank
                }

                var line = lines[lines.length - 2];
                return line.is_empty();
            }
            return false;
        }
    }


    var Token = function (type, text, newlines, whitespace_before, mode, parent) {
        this.type = type;
        this.text = text;
        this.comments_before = [];
        this.newlines = newlines || 0;
        this.wanted_newline = newlines > 0;
        this.whitespace_before = whitespace_before || '';
        this.parent = null;
    }

    function tokenizer(input, opts, indent_string) {

        var whitespace = "\n\r\t ".split('');
        var digit = /[0-9]/;

        var punct = ('+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! ~ , : ? ^ ^= |= :: =>'
                + ' <%= <% %> <?= <? ?>').split(' '); // try to be a good boy and try not to break the markup language identifiers

        // words which should always start on new line.
        this.line_starters = 'continue,try,throw,return,var,let,const,if,switch,case,default,for,while,break,function,import,export'.split(',');
        var reserved_words = this.line_starters.concat(['do', 'in', 'else', 'get', 'set', 'new', 'catch', 'finally', 'typeof', 'yield']);

        var n_newlines, whitespace_before_token, in_html_comment, tokens, parser_pos;
        var input_length;

        this.tokenize = function () {
            // cache the source's length.
            input_length = input.length
            parser_pos = 0;
            in_html_comment = false
            tokens = [];

            var next, last;
            var token_values;
            var open = null;
            var open_stack = [];
            var comments = [];

            while (!(last && last.type === 'TK_EOF')) {
                token_values = tokenize_next();
                next = new Token(token_values[1], token_values[0], n_newlines, whitespace_before_token);
                while (next.type === 'TK_INLINE_COMMENT' || next.type === 'TK_COMMENT' ||
                    next.type === 'TK_BLOCK_COMMENT' || next.type === 'TK_UNKNOWN') {
                    comments.push(next);
                    token_values = tokenize_next();
                    next = new Token(token_values[1], token_values[0], n_newlines, whitespace_before_token);
                }

                if (comments.length) {
                    next.comments_before = comments;
                    comments = [];
                }

                if (next.type === 'TK_START_BLOCK' || next.type === 'TK_START_EXPR') {
                    next.parent = last;
                    open = next;
                    open_stack.push(next);
                } else if ((next.type === 'TK_END_BLOCK' || next.type === 'TK_END_EXPR') &&
                    (open && (
                        (next.text === ']' && open.text === '[') ||
                        (next.text === ')' && open.text === '(') ||
                        (next.text === '}' && open.text === '}')))) {
                    next.parent = open.parent;
                    open = open_stack.pop();
                }

                tokens.push(next);
                last = next;
            }

            return tokens;
        }

        function tokenize_next() {
            var i, resulting_string;
            var whitespace_on_this_line = [];

            n_newlines = 0;
            whitespace_before_token = '';

            if (parser_pos >= input_length) {
                return ['', 'TK_EOF'];
            }

            var last_token;
            if (tokens.length) {
                last_token = tokens[tokens.length - 1];
            } else {
                // For the sake of tokenizing we can pretend that there was on open brace to start
                last_token = new Token('TK_START_BLOCK', '{');
            }


            var c = input.charAt(parser_pos);
            parser_pos += 1;

            while (in_array(c, whitespace)) {

                if (c === '\n') {
                    n_newlines += 1;
                    whitespace_on_this_line = [];
                } else if (n_newlines) {
                    if (c === indent_string) {
                        whitespace_on_this_line.push(indent_string);
                    } else if (c !== '\r') {
                        whitespace_on_this_line.push(' ');
                    }
                }

                if (parser_pos >= input_length) {
                    return ['', 'TK_EOF'];
                }

                c = input.charAt(parser_pos);
                parser_pos += 1;
            }

            if (whitespace_on_this_line.length) {
                whitespace_before_token = whitespace_on_this_line.join('');
            }

            if (digit.test(c)) {
                var allow_decimal = true;
                var allow_e = true;
                var local_digit = digit;

                if (c === '0' && parser_pos < input_length && /[Xx]/.test(input.charAt(parser_pos))) {
                    // switch to hex number, no decimal or e, just hex digits
                    allow_decimal = false;
                    allow_e = false;
                    c += input.charAt(parser_pos);
                    parser_pos += 1;
                    local_digit = /[0123456789abcdefABCDEF]/
                } else {
                    // we know this first loop will run.  It keeps the logic simpler.
                    c = '';
                    parser_pos -= 1
                }

                // Add the digits
                while (parser_pos < input_length && local_digit.test(input.charAt(parser_pos))) {
                    c += input.charAt(parser_pos);
                    parser_pos += 1;

                    if (allow_decimal && parser_pos < input_length && input.charAt(parser_pos) === '.') {
                        c += input.charAt(parser_pos);
                        parser_pos += 1;
                        allow_decimal = false;
                    }

                    if (allow_e && parser_pos < input_length && /[Ee]/.test(input.charAt(parser_pos))) {
                        c += input.charAt(parser_pos);
                        parser_pos += 1;

                        if (parser_pos < input_length && /[+-]/.test(input.charAt(parser_pos))) {
                            c += input.charAt(parser_pos);
                            parser_pos += 1;
                        }

                        allow_e = false;
                        allow_decimal = false;
                    }
                }

                return [c, 'TK_WORD'];
            }

            if (acorn.isIdentifierStart(input.charCodeAt(parser_pos - 1))) {
                if (parser_pos < input_length) {
                    while (acorn.isIdentifierChar(input.charCodeAt(parser_pos))) {
                        c += input.charAt(parser_pos);
                        parser_pos += 1;
                        if (parser_pos === input_length) {
                            break;
                        }
                    }
                }

                if (!(last_token.type === 'TK_DOT' ||
                        (last_token.type === 'TK_RESERVED' && in_array(last_token.text, ['set', 'get'])))
                    && in_array(c, reserved_words)) {
                    if (c === 'in') { // hack for 'in' operator
                        return [c, 'TK_OPERATOR'];
                    }
                    return [c, 'TK_RESERVED'];
                }

                return [c, 'TK_WORD'];
            }

            if (c === '(' || c === '[') {
                return [c, 'TK_START_EXPR'];
            }

            if (c === ')' || c === ']') {
                return [c, 'TK_END_EXPR'];
            }

            if (c === '{') {
                return [c, 'TK_START_BLOCK'];
            }

            if (c === '}') {
                return [c, 'TK_END_BLOCK'];
            }

            if (c === ';') {
                return [c, 'TK_SEMICOLON'];
            }

            if (c === '/') {
                var comment = '';
                // peek for comment /* ... */
                var inline_comment = true;
                if (input.charAt(parser_pos) === '*') {
                    parser_pos += 1;
                    if (parser_pos < input_length) {
                        while (parser_pos < input_length && !(input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/')) {
                            c = input.charAt(parser_pos);
                            comment += c;
                            if (c === "\n" || c === "\r") {
                                inline_comment = false;
                            }
                            parser_pos += 1;
                            if (parser_pos >= input_length) {
                                break;
                            }
                        }
                    }
                    parser_pos += 2;
                    if (inline_comment && n_newlines === 0) {
                        return ['/*' + comment + '*/', 'TK_INLINE_COMMENT'];
                    } else {
                        return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT'];
                    }
                }
                // peek for comment // ...
                if (input.charAt(parser_pos) === '/') {
                    comment = c;
                    while (input.charAt(parser_pos) !== '\r' && input.charAt(parser_pos) !== '\n') {
                        comment += input.charAt(parser_pos);
                        parser_pos += 1;
                        if (parser_pos >= input_length) {
                            break;
                        }
                    }
                    return [comment, 'TK_COMMENT'];
                }

            }

            if (c === '`' || c === "'" || c === '"' || // string
                (
                    (c === '/') || // regexp
                    (opts.e4x && c === "<" && input.slice(parser_pos - 1).match(/^<([-a-zA-Z:0-9_.]+|{[^{}]*}|!\[CDATA\[[\s\S]*?\]\])\s*([-a-zA-Z:0-9_.]+=('[^']*'|"[^"]*"|{[^{}]*})\s*)*\/?\s*>/)) // xml
                ) && ( // regex and xml can only appear in specific locations during parsing
                    (last_token.type === 'TK_RESERVED' && in_array(last_token.text, ['return', 'case', 'throw', 'else', 'do', 'typeof', 'yield'])) ||
                    (last_token.type === 'TK_END_EXPR' && last_token.text === ')' &&
                        last_token.parent && last_token.parent.type === 'TK_RESERVED' && in_array(last_token.parent.text, ['if', 'while', 'for'])) ||
                    (in_array(last_token.type, ['TK_COMMENT', 'TK_START_EXPR', 'TK_START_BLOCK',
                        'TK_END_BLOCK', 'TK_OPERATOR', 'TK_EQUALS', 'TK_EOF', 'TK_SEMICOLON', 'TK_COMMA'
            ]))
                )) {

                var sep = c,
                    esc = false,
                    has_char_escapes = false;

                resulting_string = c;

                if (sep === '/') {
                    //
                    // handle regexp
                    //
                    var in_char_class = false;
                    while (parser_pos < input_length &&
                            ((esc || in_char_class || input.charAt(parser_pos) !== sep) &&
                            !acorn.newline.test(input.charAt(parser_pos)))) {
                        resulting_string += input.charAt(parser_pos);
                        if (!esc) {
                            esc = input.charAt(parser_pos) === '\\';
                            if (input.charAt(parser_pos) === '[') {
                                in_char_class = true;
                            } else if (input.charAt(parser_pos) === ']') {
                                in_char_class = false;
                            }
                        } else {
                            esc = false;
                        }
                        parser_pos += 1;
                    }
                } else if (opts.e4x && sep === '<') {
                    //
                    // handle e4x xml literals
                    //
                    var xmlRegExp = /<(\/?)([-a-zA-Z:0-9_.]+|{[^{}]*}|!\[CDATA\[[\s\S]*?\]\])\s*([-a-zA-Z:0-9_.]+=('[^']*'|"[^"]*"|{[^{}]*})\s*)*(\/?)\s*>/g;
                    var xmlStr = input.slice(parser_pos - 1);
                    var match = xmlRegExp.exec(xmlStr);
                    if (match && match.index === 0) {
                        var rootTag = match[2];
                        var depth = 0;
                        while (match) {
                            var isEndTag = !!match[1];
                            var tagName = match[2];
                            var isSingletonTag = (!!match[match.length - 1]) || (tagName.slice(0, 8) === "![CDATA[");
                            if (tagName === rootTag && !isSingletonTag) {
                                if (isEndTag) {
                                    --depth;
                                } else {
                                    ++depth;
                                }
                            }
                            if (depth <= 0) {
                                break;
                            }
                            match = xmlRegExp.exec(xmlStr);
                        }
                        var xmlLength = match ? match.index + match[0].length : xmlStr.length;
                        parser_pos += xmlLength - 1;
                        return [xmlStr.slice(0, xmlLength), "TK_STRING"];
                    }
                } else {
                    //
                    // handle string
                    //
                    // Template strings can travers lines without escape characters.
                    // Other strings cannot
                    while (parser_pos < input_length &&
                            (esc || (input.charAt(parser_pos) !== sep &&
                            (sep === '`' || !acorn.newline.test(input.charAt(parser_pos)))))) {
                        resulting_string += input.charAt(parser_pos);
                        if (esc) {
                            if (input.charAt(parser_pos) === 'x' || input.charAt(parser_pos) === 'u') {
                                has_char_escapes = true;
                            }
                            esc = false;
                        } else {
                            esc = input.charAt(parser_pos) === '\\';
                        }
                        parser_pos += 1;
                    }

                }

                if (has_char_escapes && opts.unescape_strings) {
                    resulting_string = unescape_string(resulting_string);
                }

                if (parser_pos < input_length && input.charAt(parser_pos) === sep) {
                    resulting_string += sep;
                    parser_pos += 1;

                    if (sep === '/') {
                        // regexps may have modifiers /regexp/MOD , so fetch those, too
                        // Only [gim] are valid, but if the user puts in garbage, do what we can to take it.
                        while (parser_pos < input_length && acorn.isIdentifierStart(input.charCodeAt(parser_pos))) {
                            resulting_string += input.charAt(parser_pos);
                            parser_pos += 1;
                        }
                    }
                }
                return [resulting_string, 'TK_STRING'];
            }

            if (c === '#') {

                if (tokens.length === 0 && input.charAt(parser_pos) === '!') {
                    // shebang
                    resulting_string = c;
                    while (parser_pos < input_length && c !== '\n') {
                        c = input.charAt(parser_pos);
                        resulting_string += c;
                        parser_pos += 1;
                    }
                    return [trim(resulting_string) + '\n', 'TK_UNKNOWN'];
                }



                // Spidermonkey-specific sharp variables for circular references
                // https://developer.mozilla.org/En/Sharp_variables_in_JavaScript
                // http://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935
                var sharp = '#';
                if (parser_pos < input_length && digit.test(input.charAt(parser_pos))) {
                    do {
                        c = input.charAt(parser_pos);
                        sharp += c;
                        parser_pos += 1;
                    } while (parser_pos < input_length && c !== '#' && c !== '=');
                    if (c === '#') {
                        //
                    } else if (input.charAt(parser_pos) === '[' && input.charAt(parser_pos + 1) === ']') {
                        sharp += '[]';
                        parser_pos += 2;
                    } else if (input.charAt(parser_pos) === '{' && input.charAt(parser_pos + 1) === '}') {
                        sharp += '{}';
                        parser_pos += 2;
                    }
                    return [sharp, 'TK_WORD'];
                }
            }

            if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '<!--') {
                parser_pos += 3;
                c = '<!--';
                while (input.charAt(parser_pos) !== '\n' && parser_pos < input_length) {
                    c += input.charAt(parser_pos);
                    parser_pos++;
                }
                in_html_comment = true;
                return [c, 'TK_COMMENT'];
            }

            if (c === '-' && in_html_comment && input.substring(parser_pos - 1, parser_pos + 2) === '-->') {
                in_html_comment = false;
                parser_pos += 2;
                return ['-->', 'TK_COMMENT'];
            }

            if (c === '.') {
                return [c, 'TK_DOT'];
            }

            if (in_array(c, punct)) {
                while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) {
                    c += input.charAt(parser_pos);
                    parser_pos += 1;
                    if (parser_pos >= input_length) {
                        break;
                    }
                }

                if (c === ',') {
                    return [c, 'TK_COMMA'];
                } else if (c === '=') {
                    return [c, 'TK_EQUALS'];
                } else {
                    return [c, 'TK_OPERATOR'];
                }
            }

            return [c, 'TK_UNKNOWN'];
        }


        function unescape_string(s) {
            var esc = false,
                out = '',
                pos = 0,
                s_hex = '',
                escaped = 0,
                c;

            while (esc || pos < s.length) {

                c = s.charAt(pos);
                pos++;

                if (esc) {
                    esc = false;
                    if (c === 'x') {
                        // simple hex-escape \x24
                        s_hex = s.substr(pos, 2);
                        pos += 2;
                    } else if (c === 'u') {
                        // unicode-escape, \u2134
                        s_hex = s.substr(pos, 4);
                        pos += 4;
                    } else {
                        // some common escape, e.g \n
                        out += '\\' + c;
                        continue;
                    }
                    if (!s_hex.match(/^[0123456789abcdefABCDEF]+$/)) {
                        // some weird escaping, bail out,
                        // leaving whole string intact
                        return s;
                    }

                    escaped = parseInt(s_hex, 16);

                    if (escaped >= 0x00 && escaped < 0x20) {
                        // leave 0x00...0x1f escaped
                        if (c === 'x') {
                            out += '\\x' + s_hex;
                        } else {
                            out += '\\u' + s_hex;
                        }
                        continue;
                    } else if (escaped === 0x22 || escaped === 0x27 || escaped === 0x5c) {
                        // single-quote, apostrophe, backslash - escape these
                        out += '\\' + String.fromCharCode(escaped);
                    } else if (c === 'x' && escaped > 0x7e && escaped <= 0xff) {
                        // we bail out on \x7f..\xff,
                        // leaving whole string escaped,
                        // as it's probably completely binary
                        return s;
                    } else {
                        out += String.fromCharCode(escaped);
                    }
                } else if (c === '\\') {
                    esc = true;
                } else {
                    out += c;
                }
            }
            return out;
        }

    }

w.js_beautify = js_beautify;
    if (typeof define === "function" && define.amd) {
        // Add support for AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- )
        define([], function () {
            return { js_beautify: js_beautify };
        });
    } else if (typeof exports !== "undefined") {
        // Add support for CommonJS. Just put this file somewhere on your require.paths
        // and you will be able to `var js_beautify = require("beautify").js_beautify`.
        exports.js_beautify = js_beautify;
    } else if (typeof window !== "undefined") {
        // If we're running a web page and don't have either of the above, add our one global
        window.js_beautify = js_beautify;
    } else if (typeof global !== "undefined") {
        // If we don't even have window, try global.
        global.js_beautify = js_beautify;
    }


}(window));

/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
/*

  The MIT License (MIT)

  Copyright (c) 2007-2013 Einar Lielmanis and contributors.

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation files
  (the "Software"), to deal in the Software without restriction,
  including without limitation the rights to use, copy, modify, merge,
  publish, distribute, sublicense, and/or sell copies of the Software,
  and to permit persons to whom the Software is furnished to do so,
  subject to the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.


 Style HTML
---------------

  Written by Nochum Sossonko, (nsossonko@hotmail.com)

  Based on code initially developed by: Einar Lielmanis, <einar@jsbeautifier.org>
    http://jsbeautifier.org/

  Usage:
    style_html(html_source);

    style_html(html_source, options);

  The options are:
    indent_inner_html (default false)  — indent <head> and <body> sections,
    indent_size (default 4)          — indentation size,
    indent_char (default space)      — character to indent with,
    wrap_line_length (default 250)            -  maximum amount of characters per line (0 = disable)
    brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "none"
            put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are.
    unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
    indent_scripts (default normal)  - "keep"|"separate"|"normal"
    preserve_newlines (default true) - whether existing line breaks before elements should be preserved
                                        Only works before elements, not inside tags or for text.
    max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk
    indent_handlebars (default false) - format and indent {{#foo}} and {{/foo}}
    end_with_newline (false)          - end with a newline
    extra_liners (default [head,body,/html]) -List of tags that should have an extra newline before them.

    e.g.

    style_html(html_source, {
      'indent_inner_html': false,
      'indent_size': 2,
      'indent_char': ' ',
      'wrap_line_length': 78,
      'brace_style': 'expand',
      'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'],
      'preserve_newlines': true,
      'max_preserve_newlines': 5,
      'indent_handlebars': false,
      'extra_liners': ['/html']
    });
*/

(function(window) {

    function trim(s) {
        return s.replace(/^\s+|\s+$/g, '');
    }

    function ltrim(s) {
        return s.replace(/^\s+/g, '');
    }

    function rtrim(s) {
        return s.replace(/\s+$/g,'');
    }

    function style_html(html_source, options, js_beautify, css_beautify) {
        //Wrapper function to invoke all the necessary constructors and deal with the output.

        var multi_parser,
            indent_inner_html,
            indent_size,
            indent_character,
            wrap_line_length,
            brace_style,
            unformatted,
            preserve_newlines,
            max_preserve_newlines,
            indent_handlebars,
            wrap_attributes,
            wrap_attributes_indent_size,
            end_with_newline,
            extra_liners;

        options = options || {};

        // backwards compatibility to 1.3.4
        if ((options.wrap_line_length === undefined || parseInt(options.wrap_line_length, 10) === 0) &&
                (options.max_char !== undefined && parseInt(options.max_char, 10) !== 0)) {
            options.wrap_line_length = options.max_char;
        }

        indent_inner_html = (options.indent_inner_html === undefined) ? false : options.indent_inner_html;
        indent_size = (options.indent_size === undefined) ? 4 : parseInt(options.indent_size, 10);
        indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char;
        brace_style = (options.brace_style === undefined) ? 'collapse' : options.brace_style;
        wrap_line_length =  parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10);
        unformatted = options.unformatted || ['a', 'span', 'img', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
        preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
        max_preserve_newlines = preserve_newlines ?
            (isNaN(parseInt(options.max_preserve_newlines, 10)) ? 32786 : parseInt(options.max_preserve_newlines, 10))
            : 0;
        indent_handlebars = (options.indent_handlebars === undefined) ? false : options.indent_handlebars;
        wrap_attributes = (options.wrap_attributes === undefined) ? 'auto' : options.wrap_attributes;
        wrap_attributes_indent_size = (options.wrap_attributes_indent_size === undefined) ? indent_size : parseInt(options.wrap_attributes_indent_size, 10) || indent_size;
        end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
        extra_liners = Array.isArray(options.extra_liners) ?
            options.extra_liners.concat() : (typeof options.extra_liners === 'string') ?
            options.extra_liners.split(',') : 'head,body,/html'.split(',');

        if(options.indent_with_tabs){
            indent_character = '\t';
            indent_size = 1;
        }

        function Parser() {

            this.pos = 0; //Parser position
            this.token = '';
            this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
            this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
                parent: 'parent1',
                parentcount: 1,
                parent1: ''
            };
            this.tag_type = '';
            this.token_text = this.last_token = this.last_text = this.token_type = '';
            this.newlines = 0;
            this.indent_content = indent_inner_html;

            this.Utils = { //Uilities made available to the various functions
                whitespace: "\n\r\t ".split(''),
                single_token: 'br,input,link,meta,source,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML
                extra_liners: extra_liners, //for tags that need a line of whitespace before them
                in_array: function(what, arr) {
                    for (var i = 0; i < arr.length; i++) {
                        if (what === arr[i]) {
                            return true;
                        }
                    }
                    return false;
                }
            };

            // Return true if the given text is composed entirely of whitespace.
            this.is_whitespace = function(text) {
                for (var n = 0; n < text.length; text++) {
                    if (!this.Utils.in_array(text.charAt(n), this.Utils.whitespace)) {
                        return false;
                    }
                }
                return true;
            };

            this.traverse_whitespace = function() {
                var input_char = '';

                input_char = this.input.charAt(this.pos);
                if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
                    this.newlines = 0;
                    while (this.Utils.in_array(input_char, this.Utils.whitespace)) {
                        if (preserve_newlines && input_char === '\n' && this.newlines <= max_preserve_newlines) {
                            this.newlines += 1;
                        }

                        this.pos++;
                        input_char = this.input.charAt(this.pos);
                    }
                    return true;
                }
                return false;
            };

            // Append a space to the given content (string array) or, if we are
            // at the wrap_line_length, append a newline/indentation.
            this.space_or_wrap = function(content) {
                if (this.line_char_count >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached
                    this.print_newline(false, content);
                    this.print_indentation(content);
                } else {
                    this.line_char_count++;
                    content.push(' ');
                }
            };

            this.get_content = function() { //function to capture regular content between tags
                var input_char = '',
                    content = [],
                    space = false; //if a space is needed

                while (this.input.charAt(this.pos) !== '<') {
                    if (this.pos >= this.input.length) {
                        return content.length ? content.join('') : ['', 'TK_EOF'];
                    }

                    if (this.traverse_whitespace()) {
                        this.space_or_wrap(content);
                        continue;
                    }

                    if (indent_handlebars) {
                        // Handlebars parsing is complicated.
                        // {{#foo}} and {{/foo}} are formatted tags.
                        // {{something}} should get treated as content, except:
                        // {{else}} specifically behaves like {{#if}} and {{/if}}
                        var peek3 = this.input.substr(this.pos, 3);
                        if (peek3 === '{{#' || peek3 === '{{/') {
                            // These are tags and not content.
                            break;
                        } else if (peek3 === '{{!') {
                            return [this.get_tag(), 'TK_TAG_HANDLEBARS_COMMENT'];
                        } else if (this.input.substr(this.pos, 2) === '{{') {
                            if (this.get_tag(true) === '{{else}}') {
                                break;
                            }
                        }
                    }

                    input_char = this.input.charAt(this.pos);
                    this.pos++;
                    this.line_char_count++;
                    content.push(input_char); //letter at-a-time (or string) inserted to an array
                }
                return content.length ? content.join('') : '';
            };

            this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify
                if (this.pos === this.input.length) {
                    return ['', 'TK_EOF'];
                }
                var input_char = '';
                var content = '';
                var reg_match = new RegExp('</' + name + '\\s*>', 'igm');
                reg_match.lastIndex = this.pos;
                var reg_array = reg_match.exec(this.input);
                var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script
                if (this.pos < end_script) { //get everything in between the script tags
                    content = this.input.substring(this.pos, end_script);
                    this.pos = end_script;
                }
                return content;
            };

            this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object
                if (this.tags[tag + 'count']) { //check for the existence of this tag type
                    this.tags[tag + 'count']++;
                    this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
                } else { //otherwise initialize this tag type
                    this.tags[tag + 'count'] = 1;
                    this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
                }
                this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
                this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
            };

            this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer
                if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
                    var temp_parent = this.tags.parent; //check to see if it's a closable tag.
                    while (temp_parent) { //till we reach '' (the initial value);
                        if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
                            break;
                        }
                        temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
                    }
                    if (temp_parent) { //if we caught something
                        this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
                        this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
                    }
                    delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
                    delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
                    if (this.tags[tag + 'count'] === 1) {
                        delete this.tags[tag + 'count'];
                    } else {
                        this.tags[tag + 'count']--;
                    }
                }
            };

            this.indent_to_tag = function(tag) {
                // Match the indentation level to the last use of this tag, but don't remove it.
                if (!this.tags[tag + 'count']) {
                    return;
                }
                var temp_parent = this.tags.parent;
                while (temp_parent) {
                    if (tag + this.tags[tag + 'count'] === temp_parent) {
                        break;
                    }
                    temp_parent = this.tags[temp_parent + 'parent'];
                }
                if (temp_parent) {
                    this.indent_level = this.tags[tag + this.tags[tag + 'count']];
                }
            };

            this.get_tag = function(peek) { //function to get a full tag and parse its type
                var input_char = '',
                    content = [],
                    comment = '',
                    space = false,
                    first_attr = true,
                    tag_start, tag_end,
                    tag_start_char,
                    orig_pos = this.pos,
                    orig_line_char_count = this.line_char_count;

                peek = peek !== undefined ? peek : false;

                do {
                    if (this.pos >= this.input.length) {
                        if (peek) {
                            this.pos = orig_pos;
                            this.line_char_count = orig_line_char_count;
                        }
                        return content.length ? content.join('') : ['', 'TK_EOF'];
                    }

                    input_char = this.input.charAt(this.pos);
                    this.pos++;

                    if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
                        space = true;
                        continue;
                    }

                    if (input_char === "'" || input_char === '"') {
                        input_char += this.get_unformatted(input_char);
                        space = true;

                    }

                    if (input_char === '=') { //no space before =
                        space = false;
                    }

                    if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) {
                        //no space after = or before >
                        this.space_or_wrap(content);
                        space = false;
                        if (!first_attr && wrap_attributes === 'force' &&  input_char !== '/') {
                            this.print_newline(true, content);
                            this.print_indentation(content);
                            for (var count = 0; count < wrap_attributes_indent_size; count++) {
                                content.push(indent_character);
                            }
                        }
                        for (var i = 0; i < content.length; i++) {
                          if (content[i] === ' ') {
                            first_attr = false;
                            break;
                          }
                        }
                    }

                    if (indent_handlebars && tag_start_char === '<') {
                        // When inside an angle-bracket tag, put spaces around
                        // handlebars not inside of strings.
                        if ((input_char + this.input.charAt(this.pos)) === '{{') {
                            input_char += this.get_unformatted('}}');
                            if (content.length && content[content.length - 1] !== ' ' && content[content.length - 1] !== '<') {
                                input_char = ' ' + input_char;
                            }
                            space = true;
                        }
                    }

                    if (input_char === '<' && !tag_start_char) {
                        tag_start = this.pos - 1;
                        tag_start_char = '<';
                    }

                    if (indent_handlebars && !tag_start_char) {
                        if (content.length >= 2 && content[content.length - 1] === '{' && content[content.length - 2] === '{') {
                            if (input_char === '#' || input_char === '/' || input_char === '!') {
                                tag_start = this.pos - 3;
                            } else {
                                tag_start = this.pos - 2;
                            }
                            tag_start_char = '{';
                        }
                    }

                    this.line_char_count++;
                    content.push(input_char); //inserts character at-a-time (or string)

                    if (content[1] && content[1] === '!') { //if we're in a comment, do something special
                        // We treat all comments as literals, even more than preformatted tags
                        // we just look for the appropriate close tag
                        content = [this.get_comment(tag_start)];
                        break;
                    }

                    if (indent_handlebars && content[1] && content[1] === '{' && content[2] && content[2] === '!') { //if we're in a comment, do something special
                        // We treat all comments as literals, even more than preformatted tags
                        // we just look for the appropriate close tag
                        content = [this.get_comment(tag_start)];
                        break;
                    }

                    if (indent_handlebars && tag_start_char === '{' && content.length > 2 && content[content.length - 2] === '}' && content[content.length - 1] === '}') {
                        break;
                    }
                } while (input_char !== '>');

                var tag_complete = content.join('');
                var tag_index;
                var tag_offset;

                if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
                    tag_index = tag_complete.indexOf(' ');
                } else if (tag_complete[0] === '{') {
                    tag_index = tag_complete.indexOf('}');
                } else { //otherwise go with the tag ending
                    tag_index = tag_complete.indexOf('>');
                }
                if (tag_complete[0] === '<' || !indent_handlebars) {
                    tag_offset = 1;
                } else {
                    tag_offset = tag_complete[2] === '#' ? 3 : 2;
                }
                var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase();
                if (tag_complete.charAt(tag_complete.length - 2) === '/' ||
                    this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
                    if (!peek) {
                        this.tag_type = 'SINGLE';
                    }
                } else if (indent_handlebars && tag_complete[0] === '{' && tag_check === 'else') {
                    if (!peek) {
                        this.indent_to_tag('if');
                        this.tag_type = 'HANDLEBARS_ELSE';
                        this.indent_content = true;
                        this.traverse_whitespace();
                    }
                } else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
                    comment = this.get_unformatted('</' + tag_check + '>', tag_complete); //...delegate to get_unformatted function
                    content.push(comment);
                    tag_end = this.pos - 1;
                    this.tag_type = 'SINGLE';
                } else if (tag_check === 'script' &&
                    (tag_complete.search('type') === -1 ||
                    (tag_complete.search('type') > -1 &&
                    tag_complete.search(/\b(text|application)\/(x-)?(javascript|ecmascript|jscript|livescript)/) > -1))) {
                    if (!peek) {
                        this.record_tag(tag_check);
                        this.tag_type = 'SCRIPT';
                    }
                } else if (tag_check === 'style' &&
                    (tag_complete.search('type') === -1 ||
                    (tag_complete.search('type') > -1 && tag_complete.search('text/css') > -1))) {
                    if (!peek) {
                        this.record_tag(tag_check);
                        this.tag_type = 'STYLE';
                    }
                } else if (tag_check.charAt(0) === '!') { //peek for <! comment
                    // for comments content is already correct.
                    if (!peek) {
                        this.tag_type = 'SINGLE';
                        this.traverse_whitespace();
                    }
                } else if (!peek) {
                    if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
                        this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
                        this.tag_type = 'END';
                    } else { //otherwise it's a start-tag
                        this.record_tag(tag_check); //push it on the tag stack
                        if (tag_check.toLowerCase() !== 'html') {
                            this.indent_content = true;
                        }
                        this.tag_type = 'START';
                    }

                    // Allow preserving of newlines after a start or end tag
                    if (this.traverse_whitespace()) {
                        this.space_or_wrap(content);
                    }

                    if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
                        this.print_newline(false, this.output);
                        if (this.output.length && this.output[this.output.length - 2] !== '\n') {
                            this.print_newline(true, this.output);
                        }
                    }
                }

                if (peek) {
                    this.pos = orig_pos;
                    this.line_char_count = orig_line_char_count;
                }

                return content.join(''); //returns fully formatted tag
            };

            this.get_comment = function(start_pos) { //function to return comment content in its entirety
                // this is will have very poor perf, but will work for now.
                var comment = '',
                    delimiter = '>',
                    matched = false;

                this.pos = start_pos;
                input_char = this.input.charAt(this.pos);
                this.pos++;

                while (this.pos <= this.input.length) {
                    comment += input_char;

                    // only need to check for the delimiter if the last chars match
                    if (comment[comment.length - 1] === delimiter[delimiter.length - 1] &&
                        comment.indexOf(delimiter) !== -1) {
                        break;
                    }

                    // only need to search for custom delimiter for the first few characters
                    if (!matched && comment.length < 10) {
                        if (comment.indexOf('<![if') === 0) { //peek for <![if conditional comment
                            delimiter = '<![endif]>';
                            matched = true;
                        } else if (comment.indexOf('<![cdata[') === 0) { //if it's a <[cdata[ comment...
                            delimiter = ']]>';
                            matched = true;
                        } else if (comment.indexOf('<![') === 0) { // some other ![ comment? ...
                            delimiter = ']>';
                            matched = true;
                        } else if (comment.indexOf('<!--') === 0) { // <!-- comment ...
                            delimiter = '-->';
                            matched = true;
                        } else if (comment.indexOf('{{!') === 0) { // {{! handlebars comment
                            delimiter = '}}';
                            matched = true;
                        }
                    }

                    input_char = this.input.charAt(this.pos);
                    this.pos++;
                }

                return comment;
            };

            this.get_unformatted = function(delimiter, orig_tag) { //function to return unformatted content in its entirety

                if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
                    return '';
                }
                var input_char = '';
                var content = '';
                var min_index = 0;
                var space = true;
                do {

                    if (this.pos >= this.input.length) {
                        return content;
                    }

                    input_char = this.input.charAt(this.pos);
                    this.pos++;

                    if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
                        if (!space) {
                            this.line_char_count--;
                            continue;
                        }
                        if (input_char === '\n' || input_char === '\r') {
                            content += '\n';
                            /*  Don't change tab indention for unformatted blocks.  If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array'
                for (var i=0; i<this.indent_level; i++) {
                  content += this.indent_string;
                }
                space = false; //...and make sure other indentation is erased
                */
                            this.line_char_count = 0;
                            continue;
                        }
                    }
                    content += input_char;
                    this.line_char_count++;
                    space = true;

                    if (indent_handlebars && input_char === '{' && content.length && content[content.length - 2] === '{') {
                        // Handlebars expressions in strings should also be unformatted.
                        content += this.get_unformatted('}}');
                        // These expressions are opaque.  Ignore delimiters found in them.
                        min_index = content.length;
                    }
                } while (content.toLowerCase().indexOf(delimiter, min_index) === -1);
                return content;
            };

            this.get_token = function() { //initial handler for token-retrieval
                var token;

                if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript
                    var type = this.last_token.substr(7);
                    token = this.get_contents_to(type);
                    if (typeof token !== 'string') {
                        return token;
                    }
                    return [token, 'TK_' + type];
                }
                if (this.current_mode === 'CONTENT') {
                    token = this.get_content();
                    if (typeof token !== 'string') {
                        return token;
                    } else {
                        return [token, 'TK_CONTENT'];
                    }
                }

                if (this.current_mode === 'TAG') {
                    token = this.get_tag();
                    if (typeof token !== 'string') {
                        return token;
                    } else {
                        var tag_name_type = 'TK_TAG_' + this.tag_type;
                        return [token, tag_name_type];
                    }
                }
            };

            this.get_full_indent = function(level) {
                level = this.indent_level + level || 0;
                if (level < 1) {
                    return '';
                }

                return Array(level + 1).join(this.indent_string);
            };

            this.is_unformatted = function(tag_check, unformatted) {
                //is this an HTML5 block-level link?
                if (!this.Utils.in_array(tag_check, unformatted)) {
                    return false;
                }

                if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)) {
                    return true;
                }

                //at this point we have an  tag; is its first child something we want to remain
                //unformatted?
                var next_tag = this.get_tag(true /* peek. */ );

                // test next_tag to see if it is just html tag (no external content)
                var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/);

                // if next_tag comes back but is not an isolated tag, then
                // let's treat the 'a' tag as having content
                // and respect the unformatted option
                if (!tag || this.Utils.in_array(tag, unformatted)) {
                    return true;
                } else {
                    return false;
                }
            };

            this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions

                this.input = js_source || ''; //gets the input for the Parser
                this.output = [];
                this.indent_character = indent_character;
                this.indent_string = '';
                this.indent_size = indent_size;
                this.brace_style = brace_style;
                this.indent_level = 0;
                this.wrap_line_length = wrap_line_length;
                this.line_char_count = 0; //count to see if wrap_line_length was exceeded

                for (var i = 0; i < this.indent_size; i++) {
                    this.indent_string += this.indent_character;
                }

                this.print_newline = function(force, arr) {
                    this.line_char_count = 0;
                    if (!arr || !arr.length) {
                        return;
                    }
                    if (force || (arr[arr.length - 1] !== '\n')) { //we might want the extra line
                        if ((arr[arr.length - 1] !== '\n')) {
                            arr[arr.length - 1] = rtrim(arr[arr.length - 1]);
                        }
                        arr.push('\n');
                    }
                };

                this.print_indentation = function(arr) {
                    for (var i = 0; i < this.indent_level; i++) {
                        arr.push(this.indent_string);
                        this.line_char_count += this.indent_string.length;
                    }
                };

                this.print_token = function(text) {
                    // Avoid printing initial whitespace.
                    if (this.is_whitespace(text) && !this.output.length) {
                        return;
                    }
                    if (text || text !== '') {
                        if (this.output.length && this.output[this.output.length - 1] === '\n') {
                            this.print_indentation(this.output);
                            text = ltrim(text);
                        }
                    }
                    this.print_token_raw(text);
                };

                this.print_token_raw = function(text) {
                    // If we are going to print newlines, truncate trailing
                    // whitespace, as the newlines will represent the space.
                    if (this.newlines > 0) {
                        text = rtrim(text);
                    }

                    if (text && text !== '') {
                        if (text.length > 1 && text[text.length - 1] === '\n') {
                            // unformatted tags can grab newlines as their last character
                            this.output.push(text.slice(0, -1));
                            this.print_newline(false, this.output);
                        } else {
                            this.output.push(text);
                        }
                    }

                    for (var n = 0; n < this.newlines; n++) {
                        this.print_newline(n > 0, this.output);
                    }
                    this.newlines = 0;
                };

                this.indent = function() {
                    this.indent_level++;
                };

                this.unindent = function() {
                    if (this.indent_level > 0) {
                        this.indent_level--;
                    }
                };
            };
            return this;
        }

        /*_____________________--------------------_____________________*/

        multi_parser = new Parser(); //wrapping functions Parser
        multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values

        while (true) {
            var t = multi_parser.get_token();
            multi_parser.token_text = t[0];
            multi_parser.token_type = t[1];

            if (multi_parser.token_type === 'TK_EOF') {
                break;
            }

            switch (multi_parser.token_type) {
                case 'TK_TAG_START':
                    multi_parser.print_newline(false, multi_parser.output);
                    multi_parser.print_token(multi_parser.token_text);
                    if (multi_parser.indent_content) {
                        multi_parser.indent();
                        multi_parser.indent_content = false;
                    }
                    multi_parser.current_mode = 'CONTENT';
                    break;
                case 'TK_TAG_STYLE':
                case 'TK_TAG_SCRIPT':
                    multi_parser.print_newline(false, multi_parser.output);
                    multi_parser.print_token(multi_parser.token_text);
                    multi_parser.current_mode = 'CONTENT';
                    break;
                case 'TK_TAG_END':
                    //Print new line only if the tag has no content and has child
                    if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
                        var tag_name = multi_parser.token_text.match(/\w+/)[0];
                        var tag_extracted_from_last_output = null;
                        if (multi_parser.output.length) {
                            tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/);
                        }
                        if (tag_extracted_from_last_output === null ||
                            (tag_extracted_from_last_output[1] !== tag_name && !multi_parser.Utils.in_array(tag_extracted_from_last_output[1], unformatted))) {
                            multi_parser.print_newline(false, multi_parser.output);
                        }
                    }
                    multi_parser.print_token(multi_parser.token_text);
                    multi_parser.current_mode = 'CONTENT';
                    break;
                case 'TK_TAG_SINGLE':
                    // Don't add a newline before elements that should remain unformatted.
                    var tag_check = multi_parser.token_text.match(/^\s*<([a-z-]+)/i);
                    if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) {
                        multi_parser.print_newline(false, multi_parser.output);
                    }
                    multi_parser.print_token(multi_parser.token_text);
                    multi_parser.current_mode = 'CONTENT';
                    break;
                case 'TK_TAG_HANDLEBARS_ELSE':
                    multi_parser.print_token(multi_parser.token_text);
                    if (multi_parser.indent_content) {
                        multi_parser.indent();
                        multi_parser.indent_content = false;
                    }
                    multi_parser.current_mode = 'CONTENT';
                    break;
                case 'TK_TAG_HANDLEBARS_COMMENT':
                    multi_parser.print_token(multi_parser.token_text);
                    multi_parser.current_mode = 'TAG';
                    break;
                case 'TK_CONTENT':
                    multi_parser.print_token(multi_parser.token_text);
                    multi_parser.current_mode = 'TAG';
                    break;
                case 'TK_STYLE':
                case 'TK_SCRIPT':
                    if (multi_parser.token_text !== '') {
                        multi_parser.print_newline(false, multi_parser.output);
                        var text = multi_parser.token_text,
                            _beautifier,
                            script_indent_level = 1;
                        if (multi_parser.token_type === 'TK_SCRIPT') {
                            _beautifier = typeof js_beautify === 'function' && js_beautify;
                        } else if (multi_parser.token_type === 'TK_STYLE') {
                            _beautifier = typeof css_beautify === 'function' && css_beautify;
                        }

                        if (options.indent_scripts === "keep") {
                            script_indent_level = 0;
                        } else if (options.indent_scripts === "separate") {
                            script_indent_level = -multi_parser.indent_level;
                        }

                        var indentation = multi_parser.get_full_indent(script_indent_level);
                        if (_beautifier) {
                            // call the Beautifier if avaliable
                            text = _beautifier(text.replace(/^\s*/, indentation), options);
                        } else {
                            // simply indent the string otherwise
                            var white = text.match(/^\s*/)[0];
                            var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
                            var reindent = multi_parser.get_full_indent(script_indent_level - _level);
                            text = text.replace(/^\s*/, indentation)
                                .replace(/\r\n|\r|\n/g, '\n' + reindent)
                                .replace(/\s+$/, '');
                        }
                        if (text) {
                            multi_parser.print_token_raw(text);
                            multi_parser.print_newline(true, multi_parser.output);
                        }
                    }
                    multi_parser.current_mode = 'TAG';
                    break;
                default:
                    // We should not be getting here but we don't want to drop input on the floor
                    // Just output the text and move on
                    if (multi_parser.token_text !== '') {
                        multi_parser.print_token(multi_parser.token_text);
                    }
                    break;
            }
            multi_parser.last_token = multi_parser.token_type;
            multi_parser.last_text = multi_parser.token_text;
        }
        var sweet_code = multi_parser.output.join('').replace(/[\r\n\t ]+$/, '');
        if (end_with_newline) {
            sweet_code += '\n';
        }
        return sweet_code;
    }

    if (typeof define === "function" && define.amd) {
        // Add support for AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- )
        define(["require", "./beautify", "./beautify-css"], function(requireamd) {
            var js_beautify =  requireamd("./beautify");
            var css_beautify =  requireamd("./beautify-css");

            return {
              html_beautify: function(html_source, options) {
                return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify);
              }
            };
        });
    } else if (typeof exports !== "undefined") {
        // Add support for CommonJS. Just put this file somewhere on your require.paths
        // and you will be able to `var html_beautify = require("beautify").html_beautify`.
        var js_beautify = require('./beautify.js');
        var css_beautify = require('./beautify-css.js');

        exports.html_beautify = function(html_source, options) {
            return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify);
        };
    } else if (typeof window !== "undefined") {
        // If we're running a web page and don't have either of the above, add our one global
        window.html_beautify = function(html_source, options) {
            return style_html(html_source, options, window.js_beautify, window.css_beautify);
        };
    } else if (typeof global !== "undefined") {
        // If we don't even have window, try global.
        global.html_beautify = function(html_source, options) {
            return style_html(html_source, options, global.js_beautify, global.css_beautify);
        };
    }

}(window));
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fit="" preserveAspectRatio="xMidYMid meet" style="pointer-events: none; display: block;">
    <path d="M0 0h24v24H0z" fill="none"/>
    <path d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z"/>
</svg>
<svg height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg">
    <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
    <path d="M0 0h24v24H0z" fill="none"/>
</svg>
<svg height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg">
    <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
    <path d="M0 0h24v24H0z" fill="none"/>
</svg>
<svg height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg">
  	<path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/>
    <path d="M0 0h24v24H0z" fill="none"/>
</svg>
// jslint.js
// 2015-06-11
// Copyright (c) 2015 Douglas Crockford  (www.JSLint.com)

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// The Software shall be used for Good, not Evil.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// jslint is a function that takes 3 arguments:

//      source          A text to analyze, a string or an array of strings.
//      option_object   An object whose keys correspond to option names.
//      global_array    An array of strings containing global variables that
//                      the file is allowed readonly access.

// jslint returns an object containing its results. The object contains a lot
// of valuable information. It can be used to generate reports. The object
// contains:

//      edition: the version of JSLint that did the analysis.
//      functions: an array of objects that represent all of the functions
//              declared in the file.
//      global: an object representing the global object. Its .context property
//              is an object containing a property for each global variable.
//      id: "(JSLint)"
//      imports: an array of strings representing each of the imports.
//      json: true if the file is a JSON text.
//      lines: an array of strings, the source.
//      module: true if an import or export statement was used.
//      ok: true if no warnings were generated. This is what you want.
//      option: the option argument.
//      property: a property object.
//      stop: true if JSLint was unable to finish. You don't want this.
//      tokens: an array of objects representing the tokens in the file.
//      tree: the token objects arranged in a tree.
//      warnings: an array of warning objects. A warning object can contain:
//          name: 'JSLintError'
//          column: A column number in the file.
//          line: A line number in the file.
//          code: A warning code string.
//          message: The warning message string.
//          a: Exhibit A.
//          b: Exhibit B.
//          c: Exhibit C.
//          d: Exhibit D.

// jslint works in several phases. In any of these phases, errors might be
// found. Sometimes JSLint is able to recover from an error and continue
// parsing. In some cases, it cannot and will stop early. If that should happen,
// repair your code and try again.

// Phases:

//      1. If the source is a single string, split it into an array of strings.
//      2. Turn the source into an array of tokens.
//      3. Furcate the tokens into a parse tree.
//      4. Walk the tree, traversing all of the nodes of the tree. It is a
//          recursive traversal. Each node may be processed on the way down
//          (preaction) and on the way up (postaction).
//      5. Check the whitespace between the tokens.

// jslint can also examine JSON text. It decides that a file is JSON text if
// the first token is '[' or '{'. Processing of JSON text is much simpler than
// the processing of JavaScript programs. Only the first three phases are
// required.

// WARNING: JSLint will hurt your feelings.

/*property
    a, and, arity, b, bad_assignment_a, bad_character_number_a, bad_get,
    bad_module_name_a, bad_option_a, bad_property_a, bad_set, bitwise, block,
    body, browser, c, calls, catch, charAt, charCodeAt, closer, closure, code,
    column, concat, constant, context, couch, create, d, dead, devel,
    directive, disrupt, dot, duplicate_a, edition, ellipsis, else, empty_block,
    es6, eval, every, expected_a_at_b_c, expected_a_b, expected_a_b_from_c_d,
    expected_a_before_b, expected_digits_after_a, expected_four_digits,
    expected_identifier_a, expected_line_break_a_b, expected_regexp_factor_a,
    expected_space_a_b, expected_string_a, expected_type_string_a, expression,
    extra, flag, for, forEach, free, from, fud, fudge, function,
    function_in_loop, functions, g, global, i, id, identifier, import, imports,
    inc, indexOf, infix_in, init, initial, isArray, isNaN, join, json, keys,
    label, label_a, lbp, led, length, level, line, lines, live, loop, m,
    margin, match, maxerr, maxlen, message, misplaced_a, misplaced_directive_a,
    module, naked_block, name, names, nested_comment, new, node, not_label_a,
    nud, ok, open, option, out_of_scope_a, parameters, pop, property, push,
    qmark, quote, redefinition_a_b, replace, reserved_a, role, search,
    signature, slash_equal, slice, sort, split, statement, stop, stopping,
    strict, subscript_a, switch, test, this, thru, toString, todo_comment,
    tokens, too_long, too_many, tree, type, u, unclosed_comment, unclosed_mega,
    unclosed_string, undeclared_a, unexpected_a, unexpected_a_after_b,
    unexpected_at_top_level_a, unexpected_char_a, unexpected_comment,
    unexpected_directive_a, unexpected_expression_a, unexpected_label_a,
    unexpected_parens, unexpected_space_a_b, unexpected_statement_a,
    unexpected_trailing_space, unexpected_typeof_a, uninitialized_a,
    unreachable_a, unregistered_property_a, unsafe, unused_a, use_spaces, used,
    value, var_loop, var_switch, variable, warning, warnings,
    weird_condition_a, weird_expression_a, weird_loop, weird_relation_a, white,
    wrap_immediate, wrap_regexp, wrapped, writable, y
*/

var jslint = (function JSLint() {
    'use strict';

    function empty() {

// The empty function produces a new empty object that inherits nothing. This is
// much better than {} because confusions around accidental method names like
// 'constructor' are completely avoided.

        return Object.create(null);
    }

    function populate(object, array, value) {

// Augment an object by taking property names from an array of strings.

        array.forEach(function (name) {
            object[name] = value;
        });
    }

    var allowed_option = {

// These are the options that are recognized in the option object or that may
// appear in a /*jslint*/ directive. Most options will have a boolean value,
// usually true. Some options will also predefine some number of global
// variables.

        bitwise: true,
        browser: [
            'Audio', 'clearInterval', 'clearTimeout', 'document', 'event',
            'FormData', 'frames', 'history', 'Image', 'localStorage',
            'location', 'name', 'navigator', 'Option', 'parent', 'screen',
            'sessionStorage', 'setInterval', 'setTimeout', 'Storage',
            'XMLHttpRequest'
        ],
        couch: [
            'emit', 'getRow', 'isArray', 'log', 'provides', 'registerType',
            'require', 'send', 'start', 'sum', 'toJSON'
        ],
        devel: [
            'alert', 'confirm', 'console', 'Debug', 'opera', 'prompt', 'WSH'
        ],
        es6: [
            'ArrayBuffer', 'DataView', 'Float32Array', 'Float64Array',
            'Generator', 'GeneratorFunction', 'Int8Array', 'Int16Array',
            'Int32Array', 'Intl', 'Map', 'Promise', 'Proxy', 'Reflect',
            'Set', 'Symbol', 'System', 'Uint8Array', 'Uint8ClampedArray',
            'Uint16Array', 'Uint32Array', 'WeakMap', 'WeakSet'
        ],
        eval: true,
        for: true,
        fudge: true,
        maxerr: 10000,
        maxlen: 10000,
        node: [
            'Buffer', 'clearImmediate', 'clearInterval', 'clearTimeout',
            'console', 'exports', 'global', 'module', 'process',
            'require', 'setImmediate', 'setInterval', 'setTimeout',
            '__dirname', '__filename'
        ],
        this: true,
        white: true
    };

    var spaceop = {

// This is the set of infix operators that require a space on each side.

        '!=': true,
        '!==': true,
        '%': true,
        '%=': true,
        '^': true,
        '^=': true,
        '&': true,
        '&=': true,
        '&&': true,
        '*': true,
        '*=': true,
        '-=': true,
        '+=': true,
        '=': true,
        '=>': true,
        '==': true,
        '===': true,
        '|': true,
        '|=': true,
        '||': true,
        '<': true,
        '<=': true,
        '<<': true,
        '<<=': true,
        '>': true,
        '>=': true,
        '>>': true,
        '>>=': true,
        '>>>': true,
        '>>>=': true
    };

    var bitwiseop = {

// These are the bitwise operators.

        '~': true,
        '^': true,
        '^=': true,
        '&': true,
        '&=': true,
        '|': true,
        '|=': true,
        '<<': true,
        '<<=': true,
        '>>': true,
        '>>=': true,
        '>>>': true,
        '>>>=': true
    };

    var opener = {

// The open and close pairs.

        '(': ')',       // paren
        '[': ']',       // bracket
        '{': '}',       // brace
        '${': '}'       // mega
    };

    var relationop = {

// The relational operators.

        '!=': true,
        '!==': true,
        '==': true,
        '===': true,
        '<': true,
        '<=': true,
        '>': true,
        '>=': true
    };

    var standard = [

// These are the globals that are provided by the ES5 language standard.

        'Array', 'Boolean', 'Date', 'decodeURI', 'decodeURIComponent',
        'encodeURI', 'encodeURIComponent', 'Error', 'EvalError', 'Function',
        'isFinite', 'isNaN', 'JSON', 'Math', 'Number', 'Object', 'parseInt',
        'parseFloat', 'RangeError', 'ReferenceError', 'RegExp', 'String',
        'SyntaxError', 'TypeError', 'URIError'
    ];

    var bundle = {

// The bundle contains the raw text messages that are generated by jslint. It
// seems that they are all error messages and warnings. There are no "Atta
// boy!" or "You are so awesome!" messages. There is no positive reinforcement
// or encouragement. This relentless negativity can undermine self-esteem and
// wound the inner child. But if you accept it as sound advice rather than as
// personal criticism, it can make your programs better.

        and: "The '&&' subexpression should be wrapped in parens.",
        bad_assignment_a: "Bad assignment to '{a}'.",
        bad_character_number_a: "Bad character code: '{a}'",
        bad_get: "A get function takes no parameters.",
        bad_module_name_a: "Bad module name '{a}'.",
        bad_option_a: "Bad option '{a}'.",
        bad_property_a: "Bad property name '{a}'.",
        bad_set: "A set function takes one parameter.",
        duplicate_a: "Duplicate '{a}'.",
        empty_block: "Empty block.",
        es6: "Unexpected ES6 feature.",
        expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.",
        expected_a_b: "Expected '{a}' and instead saw '{b}'.",
        expected_a_b_from_c_d: "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
        expected_a_before_b: "Expected '{a}' before '{b}'.",
        expected_digits_after_a: "Expected digits after '{a}'.",
        expected_four_digits: "Expected four digits after '\\u'.",
        expected_identifier_a: "Expected an identifier and instead saw '{a}'.",
        expected_line_break_a_b: "Expected a line break between '{a}' and '{b}'.",
        expected_regexp_factor_a: "Expected a regexp factor and instead saw '{a}'.",
        expected_space_a_b: "Expected one space between '{a}' and '{b}'.",
        expected_string_a: "Expected a string and instead saw '{a}'.",
        expected_type_string_a: "Expected a type string and instead saw '{a}'.",
        function_in_loop: "Don't make functions within a loop.",
        infix_in: "Unexpected 'in'. Compare with undefined, or use the hasOwnProperty method instead.",
        isNaN: "Use the isNaN function to compare with NaN.",
        label_a: "'{a}' is a statement label.",
        misplaced_a: "Place '{a}' at the outermost level.",
        misplaced_directive_a: "Place the '/*{a}*/' directive before the first statement.",
        naked_block: "Naked block.",
        nested_comment: "Nested comment.",
        not_label_a: "'{a}' is not a label.",
        out_of_scope_a: "'{a}' is out of scope.",
        redefinition_a_b: "Redefinition of '{a}' from line {b}.",
        reserved_a: "Reserved name '{a}'.",
        slash_equal: "A regular expression literal can be confused with '/='.",
        stopping: "Stopping.",
        subscript_a: "['{a}'] is better written in dot notation.",
        todo_comment: "Unexpected TODO comment.",
        too_long: "Line too long.",
        too_many: "Too many warnings.",
        unclosed_comment: "Unclosed comment.",
        unclosed_mega: "Unclosed mega literal.",
        unclosed_string: "Unclosed string.",
        undeclared_a: "Undeclared '{a}'.",
        unexpected_a: "Unexpected '{a}'.",
        unexpected_a_after_b: "Unexpected '{a}' after '{b}'.",
        unexpected_at_top_level_a: "Unexpected '{a}' at top level.",
        unexpected_char_a: "Unexpected character '{a}'.",
        unexpected_comment: "Unexpected comment.",
        unexpected_directive_a: "When using modules, don't use directive '/*{a}'.",
        unexpected_expression_a: "Unexpected expression '{a}' in statement position.",
        unexpected_label_a: "Unexpected label '{a}'.",
        unexpected_parens: "Don't wrap function literals in parens.",
        unexpected_space_a_b: "Unexpected space between '{a}' and '{b}'.",
        unexpected_statement_a: "Unexpected statement '{a}' in expression position.",
        unexpected_trailing_space: "Unexpected trailing space.",
        unexpected_typeof_a: "Unexpected 'typeof'. Use '===' to compare directly with {a}.",
        uninitialized_a: "Uninitialized '{a}'.",
        unreachable_a: "Unreachable '{a}'.",
        unregistered_property_a: "Unregistered property name '{a}'.",
        unsafe: "Unsafe character '{a}'.",
        unused_a: "Unused '{a}'.",
        use_spaces: "Use spaces, not tabs.",
        var_loop: "Don't declare variables in a loop.",
        var_switch: "Don't declare variables in a switch.",
        weird_condition_a: "Weird condition '{a}'.",
        weird_expression_a: "Weird expression '{a}'.",
        weird_loop: "Weird loop.",
        weird_relation_a: "Weird relation '{a}'.",
        wrap_immediate: "Wrap an immediate function invocation in " +
                "parentheses to assist the reader in understanding that the " +
                "expression is the result of a function, and not the " +
                "function itself.",
        wrap_regexp: "Wrap this regexp in parens to avoid confusion."
    };

// Regular expression literals:

// supplant {variables}
    var rx_supplant = /\{([^{}]*)\}/g,
// carriage return, carriage return linefeed, or linefeed
        rx_crlf = /\n|\r\n?/,
// unsafe characters that are silently deleted by one or more browsers
        rx_unsafe = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,
// identifier
        rx_identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,
        rx_bad_property = /^_|\$|Sync$|_$/,
// star slash
        rx_star_slash = /\*\//,
// slash star
        rx_slash_star = /\/\*/,
// slash star or ending slash
        rx_slash_star_or_slash = /\/\*|\/$/,
// uncompleted work comment
        rx_todo = /\b(?:todo|TO\s?DO|HACK)\b/,
// tab
        rx_tab = /\t/g,
// directive
        rx_directive = /^(jslint|property|global)\s*(.*)$/,
        rx_directive_part = /^([a-zA-Z$_][a-zA-Z0-9$_]*)\s*(?::\s*(true|false|[0-9]+)\s*)?(?:,\s*)?(.*)$/,
// token (sorry it is so long)
        rx_token = /^((\s+)|([a-zA-Z_$][a-zA-Z0-9_$]*)|[(){}\[\]\?,:;'"~`]|=(?:==?|>)?|\.+|\/[*\/]?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|[\^%]=?|&[&=]?|\|[\|=]?|>{1,3}=?|<<?=?|!={0,2}|(0|[1-9][0-9]*))(.*)$/,
        rx_digits = /^([0-9]+)(.*)$/,
        rx_hexs = /^([0-9a-fA-F]+)(.*)$/,
        rx_octals = /^([0-7]+)(.*)$/,
        rx_bits = /^([01]+)(.*)$/,
// mega
        rx_mega = /`|\$\{/,
// indentation
        rx_colons = /^(.*)\?([:.]*)$/,
        rx_dot = /\.$/,
// JSON number
        rx_JSON_number = /^-?\d+(?:\.\d*)?(?:e[\-+]?\d+)?$/i;

    function is_letter(string) {
        return (string >= 'a' && string <= 'z\uffff') ||
                (string >= 'A' && string <= 'Z\uffff');
    }

    function supplant(string, object) {
        return string.replace(rx_supplant, function (found, filling) {
            var replacement = object[filling];
            return replacement !== undefined
                ? replacement
                : found;
        });
    }

    var anon = "anonymous", // The guessed name for anonymous functions.
        blockage,           // The current block.
        block_stack,        // The stack of blocks.
        declared_globals,   // The object containing the global declarations.
        directive_mode,     // true if directives are still allowed.
        early_stop,         // true if JSLint cannot finish.
        export_mode,        // true if an export statement was seen.
        fudge,              // true if the natural numbers start with 1.
        functionage,        // The current function.
        functions,          // The array containing all of the functions.
        global,             // The global object, the outermost context.
        imports,            // The array collecting all import-from strings.
        json_mode,          // true if parsing JSON.
        lines,              // The array containing source lines.
        module_mode,        // true if import or export was used.
        next_token,         // The next token to be examined in the parse.
        option,             // The options parameter.
        property,           // The object containing the tallied property names.
        mega_mode,          // true if currently parsing a megastring literal.
        stack,              // The stack of functions.
        syntax,             // The object containing the parser.
        token,              // The current token being examined in the parse.
        token_nr,           // The number of the next token.
        tokens,             // The array of tokens.
        tenure,             // The predefined property registry.
        tree,               // The abstract parse tree.
        var_mode,           // true if using var, false if using let.
        warnings;           // The array collecting all generated warnings.

// Error reportage functions:

    function artifact(the_token) {

// Return a string representing an artifact.

        if (the_token === undefined) {
            the_token = next_token;
        }
        return the_token.id === '(string)' || the_token.id === '(number)'
            ? String(the_token.value)
            : the_token.id;
    }

    function artifact_line(the_token) {

// Return the fudged line number of an artifact.

        if (the_token === undefined) {
            the_token = next_token;
        }
        return the_token.line + fudge;
    }

    function artifact_column(the_token) {

// Return the fudged column number of an artifact.

        if (the_token === undefined) {
            the_token = next_token;
        }
        return the_token.from + fudge;
    }

    function warn_at(code, line, column, a, b, c, d) {

// Report an error at some line and column of the program. The warning object
// resembles an exception.

        var warning = {         // ~~
            name: 'JSLintError',
            column: column,
            line: line,
            code: code
        };
        if (a !== undefined) {
            warning.a = a;
        }
        if (b !== undefined) {
            warning.b = b;
        }
        if (c !== undefined) {
            warning.c = c;
        }
        if (d !== undefined) {
            warning.d = d;
        }
        warning.message = supplant(bundle[code] || code, warning);
        warnings.push(warning);
        return typeof option.maxerr === 'number' && warnings.length === option.maxerr
            ? stop_at('too_many', line, column)
            : warning;
    }

    function stop_at(code, line, column, a, b, c, d) {

// Same as warn_at, except that it stops the analysis.

        warn_at(code, line, column, a, b, c, d);
        throw warn_at('stopping', line, column);
    }

    function warn(code, the_token, a, b, c, d) {

// Same as warn_at, except the warning will be associated with a specific token.
// If there is already a warning on this token, suppress the new one. It is
// likely that the first warning will be the most meaningful.

        if (the_token === undefined) {
            the_token = next_token;
        }
        if (the_token.warning === undefined) {
            the_token.warning = warn_at(
                code,
                the_token.line,
                the_token.from,
                a || artifact(the_token),
                b,
                c,
                d
            );
            return the_token.warning;
        }
    }

    function stop(code, the_token, a, b, c, d) {

// Similar to warn and stop_at. If the token already had a warning, that
// warning will be replaced with this new one. It is likely that the stopping
// warning will be the most meaningful.

        if (the_token === undefined) {
            the_token = next_token;
        }
        the_token.warning = undefined;
        warn(code, the_token, a, b, c, d);
        the_token.warning = undefined;
        throw warn('stopping', the_token, a, b, c, d);
    }

// Tokenize:

    function tokenize(source) {

// tokenize takes a source and produces from it an array of token objects.
// JavaScript is notoriously difficult to tokenize because of the horrible
// interactions between automatic semicolon insertion, regular expression
// literals, and now megastring literals. JSLint benefits from eliminating
// automatic semicolon insertion and nested megastring literals, which allows
// full tokenization to precede parsing.

// If the source is not an array, then it is split into lines at the
// carriage return/linefeed.

        lines = Array.isArray(source)
            ? source
            : source.split(rx_crlf);
        tokens = [];

        var char,                   // a popular character
            column = 0,             // the column number of the next character
            from,                   // the starting column number of the token
            line = -1,              // the line number of the next character
            previous = global,      // the previous token including comments
            prior = global,         // the previous token excluding comments
            mega_from,              // the starting column of megastring
            mega_line,              // the starting line of megastring
            snippet,                // a piece of string
            source_line;            // the current line source string

        function next_line() {

// Put the next line of source in source_line. If the line contains tabs,
// replace them with spaces and give a warning. Also warn if the line contains
// unsafe characters or is too damn long.

            var at;
            column = 0;
            line += 1;
            source_line = lines[line];
            if (source_line !== undefined) {
                at = source_line.search(rx_tab);
                if (at >= 0) {
                    if (!option.white) {
                        warn_at('use_spaces', line, at + 1);
                    }
                    source_line = source_line.replace(rx_tab, ' ');
                }
                at = source_line.search(rx_unsafe);
                if (at >= 0) {
                    warn_at(
                        'unsafe',
                        line,
                        column + at,
                        'U+' + source_line.charCodeAt(at).toString(16)
                    );
                }
                if (option.maxlen && option.maxlen < source_line.length) {
                    warn_at('too_long', line, source_line.length);
                } else if (!option.white && source_line.slice(-1) === ' ') {
                    warn_at(
                        'unexpected_trailing_space',
                        line,
                        source_line.length - 1
                    );
                }
            }
            return source_line;
        }

// Most tokens, including the identifiers, operators, and punctuators, can be
// found with a regular expression. Regular expressions cannot correctly match
// regular expression literals, so we will match those the hard way. String
// literals and number literals can be matched by regular expressions, but they
// don't provide good warnings. The functions snip, next_char, prev_char,
// some_digits, and escape help in the parsing of literals.

        function snip() {

// Remove the last character from snippet.

            snippet = snippet.slice(0, -1);
        }

        function next_char(match) {

// Get the next character from the source line. Remove it from the source_line,
// and append it to the snippet. Optionally check that the previous character
// matched an expected value.

            if (match !== undefined && char !== match) {
                return stop_at('expected_a_b', line, column, match, char);
            }
            if (source_line) {
                char = source_line.charAt(0);
                source_line = source_line.slice(1);
                snippet += char;
            } else {
                char = '';
                snippet += ' ';
            }
            column += 1;
            return char;
        }

        function back_char() {

// Back up one character by moving a character from the end of the snippet to
// the front of the source_line.

            if (snippet) {
                char = snippet.slice(-1);
                source_line = char + source_line;
                column -= 1;
                snip();
            } else {
                char = '';
            }
            return char;
        }

        function some_digits(rx, quiet) {
            var result = source_line.match(rx);
            if (result) {
                char = result[1];
                column += char.length;
                source_line = result[2];
                snippet += char;
            } else {
                char = '';
                if (!quiet) {
                    warn_at(
                        'expected_digits_after_a',
                        line,
                        column,
                        snippet
                    );
                }
            }
            return char.length;
        }

        function escape(extra) {
            switch (next_char('\\')) {
            case '\\':
            case '\'':
            case '"':
            case '/':
            case ':':
            case '|':
            case 'b':
            case 'f':
            case 'n':
            case 'r':
            case 't':
            case ' ':
                break;
            case 'u':
                if (next_char('u') === '{') {
                    if (some_digits(rx_hexs) > 5) {
                        warn_at('too_many_digits', line, column - 1);
                    }
                    if (!option.es6) {
                        warn_at('es6', line, column);
                    }
                    if (next_char() !== '}') {
                        stop_at('expected_a_before_b', line, column, '}', char);
                    }
                    return;
                }
                back_char();
                if (some_digits(rx_hexs, true) < 4) {
                    warn_at('expected_four_digits', line, column - 1);
                }
                break;
            case '':
                return stop_at('unclosed_string', line, column);
            default:
                if (extra && extra.indexOf(char) < 0) {
                    warn_at('unexpected_a_after_b', line, column, char, '\\');
                }
            }
            next_char();
        }

        function make(id, value, identifier) {

// Make the token object and append it to the tokens list.

            var the_token = {
                id: id,
                identifier: !!identifier,
                from: from,
                thru: column,
                line: line
            };
            tokens.push(the_token);

// Directives must appear before the first statement.

            if (id !== '(comment)') {
                directive_mode = false;
            }

// If the token is to have a value, give it one.

            if (value !== undefined) {
                the_token.value = value;
            }

// If this token is an identifier that touches a preceding number, or
// a '/', comment, or regular expression literal that touches a preceding
// comment or regular expression literal, then give a missing space warning.
// This warning is not suppressed by option.white.

            if (
                previous.line === line &&
                previous.thru === from &&
                (
                    (id === '(comment)' || id === '(regexp)' || id === '/') &&
                    (
                        previous.id === '(comment)' ||
                        previous.id === '(regexp)'
                    )
                )
            ) {
                warn(
                    'expected_space_a_b',
                    the_token,
                    artifact(previous),
                    artifact(the_token)
                );
            }
            if (previous.id === '.' && id === '(number)') {
                warn('expected_a_before_b', previous, '0', '.');
            }
            if (prior.id === '.' && the_token.identifier) {
                the_token.dot = true;
            }

// The previous token is used to detect adjacency problems.

            previous = the_token;

// The prior token is a previous token that was not a comment. The prior token
// is used to disambiguate '/', which can mean division or regular expression
// literal.

            if (previous.id !== '(comment)') {
                prior = previous;
            }
            return the_token;
        }

        function directive(the_comment, body) {

// JSLint recognizes three directives that can be encoded in comments. This
// function processes one item, and calls itself recursively to process the
// next one.

            var result = body.match(rx_directive_part);
            if (result) {
                var allowed,
                    name = result[1],
                    value = result[2];
                switch (the_comment.directive) {
                case 'jslint':
                    allowed = allowed_option[name];
                    switch (typeof allowed) {
                    case 'boolean':
                        switch (value) {
                        case 'true':
                        case '':
                        case undefined:
                            option[name] = true;
                            break;
                        case 'false':
                            option[name] = false;
                            break;
                        default:
                            warn('bad_option_a', the_comment, name + ':' + value);
                        }
                        break;
                    case 'number':
                        if (isFinite(+value)) {
                            option[name] = +value;
                        } else {
                            warn('bad_option_a', the_comment, name + ':' + value);
                        }
                        break;
                    case 'object':
                        option[name] = true;
                        populate(declared_globals, allowed, false);
                        break;
                    default:
                        warn('bad_option_a', the_comment, name);
                    }
                    break;
                case 'property':
                    if (tenure === undefined) {
                        tenure = empty();
                    }
                    tenure[name] = true;
                    break;
                case 'global':
                    if (value) {
                        warn('bad_option_a', the_comment, name + ':' + value);
                    }
                    declared_globals[name] = false;
                    module_mode = the_comment;
                    break;
                }
                return directive(the_comment, result[3]);
            }
            if (body) {
                return stop('bad_directive_a', the_comment, body);
            }
        }

        function comment(snippet) {

// Make a comment object. Comments are not allowed in JSON text. Comments can
// include directives and notices of incompletion.

            var the_comment = make('(comment)', snippet);
            if (json_mode) {
                warn('unexpected_comment', the_comment);
            }
            if (Array.isArray(snippet)) {
                snippet = snippet.join(' ');
            }
            if (!option.devel && rx_todo.test(snippet)) {
                warn('todo_comment', the_comment);
            }
            var result = snippet.match(rx_directive);
            if (result) {
                if (!directive_mode) {
                    warn_at('misplaced_directive_a', line, from, result[1]);
                } else {
                    the_comment.directive = result[1];
                    directive(the_comment, result[2]);
                }
            }
            return the_comment;
        }

        function regexp() {

// Parse a regular expression literal.

            var result,
                u_mode = false,
                value;

            function quantifier() {

// Match an optional quantifier.

                switch (char) {
                case '?':
                case '*':
                case '+':
                    next_char();
                    break;
                case '{':
                    if (some_digits(rx_digits, true) === 0) {
                        warn_at('expected_a', line, column, '0');
                    }
                    if (next_char() === ',') {
                        some_digits(rx_digits, true);
                        next_char();
                    }
                    next_char('}');
                    break;
                default:
                    return;
                }
                if (char === '?') {
                    next_char('?');
                }
            }

            function subklass() {

// Match a character in a character class.

                switch (char) {
                case '\\':
                    escape();
                    return true;
                case '[':
                case ']':
                case '/':
                case '^':
                case '-':
                case '|':
                case '':
                    return false;
                case '`':
                    if (mega_mode) {
                        warn_at('unexpected_a', line, column, '`');
                    }
                    next_char();
                    return true;
                case ' ':
                    warn_at('expected_a_before_b', line, column, '\\', ' ');
                    next_char();
                    return true;
                default:
                    next_char();
                    return true;
                }
            }

            function range() {

// Match a range of subclasses.

                if (subklass()) {
                    if (char === '-') {
                        next_char('-');
                        if (!subklass()) {
                            return stop_at('unexpected_a', line, column - 1, '-');
                        }
                    }
                    return range();
                }
            }

            function klass() {

// Match a class.

                next_char('[');
                if (char === '^') {
                    next_char('^');
                }
                range();
                next_char(']');
            }

            function choice() {

                function group() {

// Match a group that starts with left paren.

                    next_char('(');
                    if (char === '?') {
                        next_char('?');
                        switch (char) {
                        case ':':
                        case '=':
                        case '!':
                            next_char();
                            break;
                        default:
                            next_char(':');
                        }
                    } else if (char === ':') {
                        warn_at('expected_a_before_b', line, column, '?', ':');
                    }
                    choice();
                    next_char(')');
                }

                function factor() {
                    switch (char) {
                    case '[':
                        klass();
                        return true;
                    case '\\':
                        escape('BbDdSsWw^${}[]().|*+?');
                        return true;
                    case '(':
                        group();
                        return true;
                    case '/':
                    case '|':
                    case ']':
                    case ')':
                    case '}':
                    case '{':
                    case '?':
                    case '+':
                    case '*':
                    case '':
                        return false;
                    case '`':
                        if (mega_mode) {
                            warn_at('unexpected_a', line, column, '`');
                        }
                        break;
                    case ' ':
                        warn_at('expected_a_before_b', line, column, '\\', ' ');
                        break;
                    }
                    next_char();
                    return true;
                }

                function sequence(follow) {
                    if (factor()) {
                        quantifier();
                        return sequence(true);
                    }
                    if (!follow) {
                        warn_at('expected_regexp_factor_a', line, column, char);
                    }
                }

// Match a choice (a sequence that can be followed by | and another choice).

                sequence();
                if (char === '|') {
                    next_char('|');
                    return choice();
                }
            }

// Scan the regexp literal. Give a warning if the first character is = because
// /= looks like a division assignment operator.

            snippet = '';
            next_char();
            if (char === '=') {
                warn_at('expected_a_before_b', line, column, '\\', '=');
            }
            choice();

// Make sure there is a closing slash.

            snip();
            value = snippet;
            next_char('/');

// Process dangling flag letters.

            var allowed = {
                    g: true,
                    i: true,
                    m: true,
                    u: u_mode,
                    y: option.es6
                },
                flag = empty();
            (function make_flag() {
                if (is_letter(char)) {
                    if (allowed[char] !== true) {
                        warn_at('unexpected_a', line, column, char);
                    }
                    allowed[char] = false;
                    flag[char] = true;
                    next_char();
                    return make_flag();
                }
            }());
            if (u_mode && !flag.u) {
                warn_at('expected_a_before_b', line, column, 'u', char);
            }
            back_char();
            if (char === '/' || char === '*') {
                return stop_at('unexpected_a', line, from, char);
            }
            result = make('(regexp)', char);
            result.flag = flag;
            result.value = value;
            return result;
        }

        function string(quote) {

// Make a string token.

            var the_token;
            snippet = '';
            next_char();

            return (function next() {
                switch (char) {
                case quote:
                    snip();
                    the_token = make('(string)', snippet);
                    the_token.quote = quote;
                    return the_token;
                case '\\':
                    escape();
                    break;
                case '':
                    return stop_at('unclosed_string', line, column);
                case '`':
                    if (mega_mode) {
                        warn_at('unexpected_a', line, column, '`');
                    }
                    next_char('`');
                    break;
                default:
                    next_char();
                }
                return next();
            }());
        }

        function frack() {
            if (char === '.') {
                some_digits(rx_digits);
                next_char();
            }
            if (char === 'E' || char === 'e') {
                next_char();
                if (char !== '+' && char !== '-') {
                    back_char();
                }
                some_digits(rx_digits);
                next_char();
            }
        }

        function number() {
            if (snippet === '0') {
                switch (next_char()) {
                case '.':
                    frack();
                    break;
                case 'b':
                    some_digits(rx_bits);
                    next_char();
                    break;
                case 'o':
                    some_digits(rx_octals);
                    next_char();
                    break;
                case 'x':
                    some_digits(rx_hexs);
                    next_char();
                    break;
                }
            } else {
                next_char();
                frack();
            }

// If the next character after a number is a digit or letter, then something
// unexpected is going on.

            if (
                (char >= '0' && char <= '9') ||
                (char >= 'a' && char <= 'z') ||
                (char >= 'A' && char <= 'Z')
            ) {
                return stop_at(
                    'unexpected_a_after_b',
                    line,
                    column - 1,
                    snippet.slice(-1),
                    snippet.slice(0, -1)
                );
            }
            back_char();
            return make('(number)', snippet);
        }

        function lex() {
            var array,
                i,
                j,
                last,
                result,
                the_token;
            if (!source_line) {
                source_line = next_line();
                from = 0;
                return source_line === undefined
                    ? mega_mode
                        ? stop_at('unclosed_mega', mega_line, mega_from)
                        : make('(end)')
                    : lex();
            }
            from = column;
            result = source_line.match(rx_token);

// result[1] token
// result[2] whitespace
// result[3] identifier
// result[4] number
// result[5] rest

            if (!result) {
                return stop_at('unexpected_char_a', line, column, source_line.charAt(0));
            }

            snippet = result[1];
            column += snippet.length;
            source_line = result[5];

// Whitespace was matched. Call lex again to get more.

            if (result[2]) {
                return lex();
            }

// The token is an identifier.

            if (result[3]) {
                return make(snippet, undefined, true);
            }

// The token is a number.

            if (result[4]) {
                return number(snippet);
            }

// The token is something miscellaneous.

            switch (snippet) {

// The token is a single quote string.

            case '\'':
                if (json_mode) {
                    warn_at('unexpected_a', line, column, '\'');
                }
                return string('\'');

// The token is a double quote string.

            case '"':
                return string('"');

// The token is a megastring. We don't allow any kind if mega nesting.

            case '`':
                if (mega_mode) {
                    return stop_at('expected_a_b', line, column, '}', '`');
                }
                snippet = '';
                mega_from = from;
                mega_line = line;
                mega_mode = true;

// Parsing a mega literal is tricky. First make a ` token.

                make('`');
                from += 1;

// Then loop, building up a string, possibly from many lines, until seeing
// the end of file, a closing `, or a ${ indicting an expression within the
// string.

                (function part() {
                    var at = source_line.search(rx_mega);

// If neither ` nor ${ is seen, then the whole line joins the snippet.

                    if (at < 0) {
                        snippet += source_line + '\n';
                        return next_line() === undefined
                            ? stop_at('unclosed_mega', mega_line, mega_from)
                            : part();
                    }

// if either ` or ${ was found, then the preceding joins the snippet to become
// a string token.

                    snippet += source_line.slice(0, at);
                    column += at;
                    source_line = source_line.slice(at);
                    make('(string)', snippet).quote = '`';
                    snippet = '';

// If ${, then make tokens that will become part of an expression until
// a } token is made.

                    if (source_line.charAt(0) === '$') {
                        column += 2;
                        make('${');
                        source_line = source_line.slice(2);
                        (function expr() {
                            var id = lex().id;
                            if (id === '{') {
                                return stop_at(
                                    'expected_a_b',
                                    line,
                                    column,
                                    '}',
                                    '{'
                                );
                            }
                            if (id !== '}') {
                                return expr();
                            }
                        }());
                        return part();
                    }
                }());
                source_line = source_line.slice(1);
                column += 1;
                mega_mode = false;
                return make('`');

// The token is a // comment.

            case '//':
                snippet = source_line;
                source_line = '';
                the_token = comment(snippet);
                if (mega_mode) {
                    warn('unexpected_comment', the_token, '`');
                }
                return the_token;

// The token is a /* comment.

            case '/*':
                array = [];
                if (source_line.charAt(0) === '/') {
                    warn_at('unexpected_a', line, column + i, '/');
                }
                (function next() {
                    if (source_line > '') {
                        i = source_line.search(rx_star_slash);
                        if (i >= 0) {
                            return;
                        }
                        j = source_line.search(rx_slash_star);
                        if (j >= 0) {
                            warn_at('nested_comment', line, column + j);
                        }
                    }
                    array.push(source_line);
                    source_line = next_line();
                    if (source_line === undefined) {
                        return stop_at('unclosed_comment', line, column);
                    }
                    return next();
                }());
                snippet = source_line.slice(0, i);
                j = snippet.search(rx_slash_star_or_slash);
                if (j >= 0) {
                    warn_at('nested_comment', line, column + j);
                }
                array.push(snippet);
                column += i + 2;
                source_line = source_line.slice(i + 2);
                return comment(array);

// The token is a slash.

            case '/':

// The / can be a division operator or the beginning of a regular expression
// literal. It is not possible to know which without doing a complete parse.
// We want to complete the tokenization before we begin to parse, so we will
// estimate. This estimator can fail in some cases. For example, it cannot
// know if '}' is ending a block or ending an object literal, so it can
// behave incorrectly in that case; it is not meaningful to divide an
// object, so it is likely that we can get away with it. We avoided the worst
// cases by eliminating automatic semicolon insertion.

                if (prior.identifier) {
                    if (!prior.dot) {
                        switch (prior.id) {
                        case 'return':
                            return regexp();
                        case '(begin)':
                        case 'case':
                        case 'delete':
                        case 'in':
                        case 'instanceof':
                        case 'new':
                        case 'typeof':
                        case 'void':
                        case 'yield':
                            the_token = regexp();
                            return stop('unexpected_a', the_token);
                        }
                    }
                } else {
                    last = prior.id.charAt(prior.id.length - 1);
                    if ('(,=:?['.indexOf(last) >= 0) {
                        return regexp();
                    }
                    if ('!&|{};~+-*%/^<>'.indexOf(last) >= 0) {
                        the_token = regexp();
                        warn('wrap_regexp', the_token);
                        return the_token;
                    }
                }
                if (source_line.charAt(0) === '/') {
                    column += 1;
                    source_line = source_line.slice(1);
                    snippet = '/=';
                    warn_at('unexpected_a', line, column, '/=');
                }
                break;
            }
            return make(snippet);
        }

// This is the only loop in JSLint. It will turn into a recursive call to lex
// when ES6 has been finished and widely deployed and adopted.

        while (true) {
            if (lex().id === '(end)') {
                break;
            }
        }
    }

// Parsing:

// Parsing weaves the tokens into an abstract syntax tree. During that process,
// a token may be given any of these properties:

//      arity       string
//      label       identifier
//      name        identifier
//      expression  expressions
//      block       statements
//      else        statements (else, default, catch)

// Specialized tokens may have additional properties.

    function survey(name) {
        var id = name.id;

// Tally the property name. If it is a string, only tally strings that conform
// to the identifier rules.

        if (id === '(string)') {
            id = name.value;
            if (!rx_identifier.test(id)) {
                return id;
            }
        } else if (!name.identifier) {
            return stop('expected_identifier_a', name);
        }

// If we have seen this name before, increment its count.

        if (typeof property[id] === 'number') {
            property[id] += 1;

// If this is the first time seeing this property name, and if there is a
// tenure list, then it must be on the list. Otherwise, it must conform to
// the rules for good property names.

        } else {
            if (tenure !== undefined) {
                if (tenure[id] !== true) {
                    warn('unregistered_property_a', name);
                }
            } else {
                if (rx_bad_property.test(id)) {
                    warn('bad_property_a', name);
                }
            }
            property[id] = 1;
        }
        return id;
    }

    function dispense() {

// Deliver the next token, skipping the comments.

        var cadet = tokens[token_nr];
        token_nr += 1;
        return cadet.id === '(comment)'
            ? dispense()
            : cadet;
    }

    function lookahead() {

// Look ahead one token without advancing.

        var old_token_nr = token_nr,
            cadet = dispense(true);
        token_nr = old_token_nr;
        return cadet;
    }

    function advance(id, match) {

// Produce the next token.

// Attempt to give helpful names to anonymous functions.

        if (token.identifier && token.id !== 'function') {
            anon = token.id;
        } else if (token.id === '(string)' && rx_identifier.test(token.value)) {
            anon = token.value;
        }

// Attempt to match next_token with an expected id.

        if (id !== undefined && next_token.id !== id) {
            return match === undefined
                ? stop('expected_a_b', next_token, id, artifact())
                : stop(
                    'expected_a_b_from_c_d',
                    next_token,
                    id,
                    artifact(match),
                    artifact_line(match),
                    artifact(next_token)
                );
        }

// Promote the tokens, skipping comments.

        token = next_token;
        next_token = dispense();
        if (next_token.id === '(end)') {
            token_nr -= 1;
        }
    }

// Parsing of JSON is simple:

    function json_value() {

        function json_object() {
            var brace = next_token,
                object = empty();
            advance('{');
            if (next_token.id !== '}') {
                (function next() {
                    advance('(string)');
                    if (object[token.value] !== undefined) {
                        warn('duplicate_a', token);
                    } else if (token.value === '__proto__') {
                        warn('bad_property_name_a', token);
                    } else {
                        object[token.value] = token;
                    }
                    advance(':');
                    json_value();
                    if (next_token.id === ',') {
                        advance(',');
                        return next();
                    }
                }());
            }
            advance('}', brace);
        }

        function json_array() {
            var bracket = next_token;
            advance('[');
            if (next_token.id !== ']') {
                (function next() {
                    json_value();
                    if (next_token.id === ',') {
                        advance(',');
                        return next();
                    }
                }());
            }
            advance(']', bracket);
        }

        switch (next_token.id) {
        case '{':
            json_object();
            break;
        case '[':
            json_array();
            break;
        case 'true':
        case 'false':
        case 'null':
            advance();
            break;
        case '(number)':
            if (!rx_JSON_number.test(next_token.value)) {
                warn('unexpected_a');
            }
            advance();
            break;
        case '(string)':
            advance();
            break;
        case '-':
            advance('-');
            advance('(number)');
            break;
        default:
            stop('unexpected_a');
        }
    }

// Now we parse JavaScript.

    function enroll(name, role, readonly) {

// Enroll a name into the current function context. The role can be exception,
// label, parameter, or variable. We look for variable redefinition because it
// causes confusion.

        var id = name.id;

// Reserved words may not be enrolled.

        if (syntax[id] !== undefined && id !== 'ignore') {
            warn('reserved_a', name);
        } else {

// Has the name been enrolled in this context?

            var earlier = functionage.context[id];
            if (earlier) {
                warn(
                    'redefinition_a_b',
                    name,
                    name.id,
                    earlier.line + fudge
                );

// Has the name been enrolled in an outer context?

            } else {
                stack.forEach(function (value) {
                    var item = value.context[id];
                    if (item !== undefined) {
                        earlier = item;
                    }
                });
                if (earlier) {
                    if (id === 'ignore') {
                        if (earlier.role === 'variable') {
                            warn('unexpected_a', name);
                        }
                    } else {
                        if (
                            (role !== 'exception' || earlier.role !== 'exception') &&
                            role !== 'parameter' &&
                            role !== 'function'
                        ) {
                            warn(
                                'redefinition_a_b',
                                name,
                                name.id,
                                earlier.line + fudge
                            );
                        }
                    }
                }

// Enroll it.

                functionage.context[id] = name;
                name.dead = true;
                name.function = functionage;
                name.init = false;
                name.role = role;
                name.used = 0;
                name.writable = !readonly;
            }
        }
    }

    function expression(rbp, initial) {

// This is the heart of the Pratt parser. I retained Pratt's nomenclature.
// They are elements of the parsing method called Top Down Operator Precedence.

// nud     Null denotation
// led     Left denotation
// lbp     Left binding power
// rbp     Right binding power

// It processes a nud (variable, constant, prefix operator). It will then
// process leds (infix operators) until the bind powers cause it to stop. It
// returns the expression's parse tree.

        var left, the_symbol;

// Statements will have already advanced, so advance now only if the token is
// not the first of a statement,

        if (!initial) {
            advance();
        }
        the_symbol = syntax[token.id];
        if (the_symbol !== undefined && the_symbol.nud !== undefined) {
            left = the_symbol.nud();
        } else if (token.identifier) {
            left = token;
            left.arity = 'variable';
        } else {
            return stop('unexpected_a', token);
        }
        (function right() {
            the_symbol = syntax[next_token.id];
            if (
                the_symbol !== undefined &&
                the_symbol.led !== undefined &&
                rbp < the_symbol.lbp
            ) {
                advance();
                left = the_symbol.led(left);
                return right();
            }
        }());
        return left;
    }

    function condition() {

// Parse the condition part of a do, if, while.

        var the_paren = next_token,
            the_value;
        the_paren.free = true;
        advance('(');
        the_value = expression(0);
        advance(')');
        if (the_value.wrapped === true) {
            warn('unexpected_a', the_paren);
        }
        switch (the_value.id) {
        case '~':
        case '&':
        case '|':
        case '^':
        case '<<':
        case '>>':
        case '>>>':
        case '+':
        case '-':
        case '*':
        case '/':
        case '%':
        case 'typeof':
        case '(number)':
        case '(string)':
            warn('unexpected_a', the_value);
            break;
        }
        return the_value;
    }

    function semicolon() {

// Try to match a semicolon.

        if (next_token.id === ';') {
            advance(';');
        } else {
            warn_at(
                'expected_a_b',
                token.line,
                token.thru,
                ';',
                artifact(next_token)
            );
        }
        anon = 'anonymous';
    }

    function statement() {

// Parse a statement. Any statement may have a label, but only four statements
// have use for one. A statement can be one of the standard statements, or
// an assignment expression, or an invocation expression.

        var first,
            the_label,
            the_statement,
            the_symbol;
        advance();
        if (token.identifier && next_token.id === ':') {
            the_label = token;
            if (the_label.id === 'ignore') {
                warn('unexpected_a', the_label);
            }
            advance(':');
            switch (next_token.id) {
            case 'do':
            case 'for':
            case 'switch':
            case 'while':
                enroll(the_label, 'label', true);
                the_label.init = true;
                the_statement = statement();
                the_statement.label = the_label;
                the_statement.statement = true;
                return the_statement;
            default:
                advance();
                warn('unexpected_label_a', the_label);
            }
        }

// Parse the statement.

        first = token;
        first.statement = true;
        the_symbol = syntax[first.id];
        if (the_symbol !== undefined && the_symbol.fud !== undefined) {
            the_symbol.disrupt = false;
            the_symbol.statement = true;
            return the_symbol.fud();
        }

// It is an expression statement.

        the_statement = expression(0, true);
        if (
            the_statement.wrapped &&
            (
                the_statement.id !== '(' ||
                the_statement.expression[0].id !== 'function'
            )
        ) {
            warn('unexpected_a', first);
        }
        semicolon();
        return the_statement;
    }

    function statements() {

// Parse a list of statements. Give a warning if an unreachable statement
// follows a disruptive statement.

        var array = [];
        (function next(disrupt) {
            var a_statement;
            switch (next_token.id) {
            case '}':
            case 'case':
            case 'default':
            case 'else':
            case '(end)':
                break;
            default:
                a_statement = statement();
                array.push(a_statement);
                if (disrupt) {
                    warn('unreachable_a', a_statement);
                }
                return next(a_statement.disrupt);
            }
        }(false));
        return array;
    }

    function not_top_level(thing) {

// Some features should not be at the outermost level.

        if (functionage === global) {
            warn('unexpected_at_top_level_a', thing);
        }
    }

    function top_level_only(the_thing) {

// Some features must be at the most outermost level.

        if (blockage !== global) {
            warn('misplaced_a', the_thing);
        }
    }

    function block(special) {

// Parse a block, a sequence of statements wrapped in braces.
//  special 'body'      The block is a function body.
//          'ignore'    No warning on an empty block.
//          'naked'     No advance.
//          undefined   Not special.

        var stmts, the_block;
        if (special !== 'naked') {
            advance('{');
        }
        the_block = token;
        the_block.arity = 'statement';
        the_block.body = special === 'body';

// All top level function bodies should include the 'use strict' pragma unless
// the whole file is strict.

        if (the_block.body && stack.length <= 1 && !global.strict) {
            if (
                next_token.id === '(string)' ||
                next_token.value === 'use strict'
            ) {
                next_token.statement = true;
                functionage.strict = true;
                advance('(string)');
                advance(';');
            } else {
                warn(
                    'expected_a_before_b',
                    next_token,
                    next_token.id === '`'
                        ? '\''
                        : 'use strict',
                    artifact(next_token)
                );
            }
        }
        stmts = statements();
        the_block.block = stmts;
        if (stmts.length === 0) {
            if (!option.devel && special !== 'ignore') {
                warn('empty_block', the_block);
            }
            the_block.disrupt = false;
        } else {
            the_block.disrupt = stmts[stmts.length - 1].disrupt;
        }
        advance('}');
        return the_block;
    }

    function mutation_check(the_thing) {

// The only expressions that may be assigned to are
//      e.b
//      e[b]
//      v

        if (
            the_thing.id !== '.' &&
            (the_thing.id !== '[' || the_thing.arity !== 'binary') &&
            the_thing.arity !== 'variable'
        ) {
            warn('bad_assignment_a', the_thing);
            return false;
        }
        return true;
    }

    function left_check(left, right) {

// Warn if the left is not one of these:
//      e.b
//      e[b]
//      e()
//      identifier

        var id = left.id;
        if (
            !left.identifier &&
            (
                left.arity !== 'binary' ||
                (id !== '.' && id !== '(' && id !== '[')
            )
        ) {
            warn('unexpected_a', right);
            return false;
        }
        return true;
    }

// These functions are used to specify the grammar of our language:

    function symbol(id, bp) {

// Make a symbol if it does not already exist in the language's syntax.

        var the_symbol = syntax[id];
        if (the_symbol === undefined) {
            the_symbol = empty();
            the_symbol.id = id;
            the_symbol.lbp = bp || 0;
            syntax[id] = the_symbol;
        }
        return the_symbol;
    }

    function assignment(id) {

// Make an assignment operator. The one true assignment is different because
// its left side, when it is a variable, is not treated as an expression.
// That case is special because that is when a variable gets initialized. The
// other assignment operators can modify, but they cannot initialize.

        var the_symbol = symbol(id, 20);
        the_symbol.led = function (left) {
            var the_token = token,
                right;
            the_token.arity = 'assignment';
            right = expression(20 - 1);
            if (id === '=' && left.arity === 'variable') {
                the_token.names = left;
                the_token.expression = right;
            } else {
                the_token.expression = [left, right];
            }
            switch (right.arity) {
            case 'assignment':
            case 'pre':
            case 'post':
                warn('unexpected_a', right);
                break;
            }
            if (
                option.es6 &&
                left.arity === 'unary' &&
                (left.id === '[' || left.id === '{')
            ) {
                warn('expected_a_before_b', left, 'const', left.id);
            } else {
                mutation_check(left);
            }
            return the_token;
        };
        return the_symbol;
    }

    function constant(id, type, value) {

// Make a constant symbol.

        var the_symbol = symbol(id);
        the_symbol.nud = typeof value === 'function'
            ? value
            : function () {
                token.constant = true;
                if (value !== undefined) {
                    token.value = value;
                }
                return token;
            };
        the_symbol.type = type;
        the_symbol.value = value;
        return the_symbol;
    }

    function infix(id, bp, f) {

// Make an infix operator.

        var the_symbol = symbol(id, bp);
        the_symbol.led = function (left) {
            var the_token = token;
            the_token.arity = 'binary';
            if (f !== undefined) {
                return f(left);
            }
            the_token.expression = [left, expression(bp)];
            return the_token;
        };
        return the_symbol;
    }

    function post(id) {

// Make one of the post operators.

        var the_symbol = symbol(id, 150);
        the_symbol.led = function (left) {
            token.expression = left;
            token.arity = 'post';
            mutation_check(token.expression);
            return token;
        };
        return the_symbol;
    }

    function pre(id) {

// Make one of the pre operators.

        var the_symbol = symbol(id);
        the_symbol.nud = function () {
            var the_token = token;
            the_token.arity = 'pre';
            the_token.expression = expression(150);
            mutation_check(the_token.expression);
            return the_token;
        };
        return the_symbol;
    }

    function prefix(id, f) {

// Make a prefix operator.

        var the_symbol = symbol(id);
        the_symbol.nud = function () {
            var the_token = token;
            the_token.arity = 'unary';
            if (typeof f === 'function') {
                return f();
            }
            the_token.expression = expression(150);
            return the_token;
        };
        return the_symbol;
    }

    function stmt(id, f) {

// Make a statement.

        var the_symbol = symbol(id);
        the_symbol.fud = function () {
            token.arity = 'statement';
            return f();
        };
        return the_symbol;
    }

    function ternary(id1, id2) {

// Make a ternary operator.

        var the_symbol = symbol(id1, 30);
        the_symbol.led = function (left) {
            var the_token = token,
                second = expression(20);
            advance(id2);
            token.arity = 'ternary';
            the_token.arity = 'ternary';
            the_token.expression = [left, second, expression(10)];
            return the_token;
        };
        return the_symbol;
    }

// Begin defining the language.

    syntax = empty();

    symbol('}');
    symbol(')');
    symbol(']');
    symbol(',');
    symbol(';');
    symbol(':');
    symbol('*/');
    symbol('await');
    symbol('case');
    symbol('catch');
    symbol('class');
    symbol('default');
    symbol('else');
    symbol('enum');
    symbol('finally');
    symbol('implements');
    symbol('interface');
    symbol('package');
    symbol('private');
    symbol('protected');
    symbol('public');
    symbol('static');
    symbol('super');
    symbol('void');
    symbol('with');
    symbol('yield');

    constant('(number)', 'number');
    constant('(regexp)', 'regexp');
    constant('(string)', 'string');
    constant('arguments', 'object', function () {
        warn('unexpected_a', token);
        return token;
    });
    constant('eval', 'function', function () {
        if (!option.eval) {
            warn('unexpected_a', token);
        } else if (next_token.id !== '(') {
            warn('expected_a_before_b', next_token, '(', artifact());
        }
        return token;
    });
    constant('false', 'boolean', false);
    constant('ignore', 'undefined', function () {
        warn('unexpected_a', token);
        return token;
    });
    constant('Infinity', 'number', Infinity);
    constant('NaN', 'number', NaN);
    constant('null', 'null', null);
    constant('this', 'object', function () {
        if (!option.this) {
            warn('unexpected_a', token);
        }
        return token;
    });
    constant('true', 'boolean', true);
    constant('undefined', 'undefined');

    assignment('=');
    assignment('+=');
    assignment('-=');
    assignment('*=');
    assignment('/=');
    assignment('%=');
    assignment('&=');
    assignment('|=');
    assignment('^=');
    assignment('<<=');
    assignment('>>=');
    assignment('>>>=');

    infix('||', 40);
    infix('&&', 50);
    infix('|', 70);
    infix('^', 80);
    infix('&', 90);
    infix('==', 100);
    infix('===', 100);
    infix('!=', 100);
    infix('!==', 100);
    infix('<', 110);
    infix('>', 110);
    infix('<=', 110);
    infix('>=', 110);
    infix('in', 110);
    infix('instanceof', 110);
    infix('<<', 120);
    infix('>>', 120);
    infix('>>>', 120);
    infix('+', 130);
    infix('-', 130);
    infix('*', 140);
    infix('/', 140);
    infix('%', 140);
    infix('(', 160, function (left) {
        var the_paren = token,
            the_argument;
        if (left.id !== 'function') {
            left_check(left, the_paren);
        }
        the_paren.free = false;
        the_paren.expression = [left];
        if (left.identifier) {
            if (left.new) {
                if (
                    left.id.charAt(0) > 'Z' ||
                    left.id === 'Boolean' ||
                    left.id === 'Number' ||
                    left.id === 'String' ||
                    (left.id === 'Symbol' && option.es6)
                ) {
                    warn('unexpected_a', left, 'new');
                } else if (left.id === 'Function') {
                    if (!option.eval) {
                        warn('unexpected_a', left, 'new Function');
                    }
                } else if (left.id === 'Array') {
                    warn('expected_a_b', left, '[]', 'new Array');
                } else if (left.id === 'Object') {
                    warn(
                        'expected_a_b',
                        left,
                        'Object.create(null)',
                        'new Object'
                    );
                }
            } else {
                if (
                    left.id.charAt(0) >= 'A' &&
                    left.id.charAt(0) <= 'Z' &&
                    left.id !== 'Boolean' &&
                    left.id !== 'Number' &&
                    left.id !== 'String' &&
                    left.id !== 'Symbol'
                ) {
                    warn(
                        'expected_a_before_b',
                        left,
                        'new',
                        artifact(left)
                    );
                }
                if (functionage.arity === 'statement') {
                    functionage.name.calls[left.id] = left;
                }
            }
        }
        if (next_token.id !== ')') {
            (function next() {
                the_argument = expression(10);
                the_paren.expression.push(the_argument);
                if (next_token.id === ',') {
                    advance(',');
                    return next();
                }
            }());
        }
        advance(')', the_paren);
        if (the_paren.expression.length === 2) {
            if (the_argument.wrapped === true) {
                warn('unexpected_a', the_paren);
            }
            if (the_argument.id === '(') {
                the_argument.wrapped = true;
            }
        }
        return the_paren;
    });
    infix('.', 170, function (left) {
        var the_token = token,
            name = next_token;
        if (
            (left.id !== '(string)' || name.id !== 'indexOf') &&
            (left.id !== '[' || (
                name.id !== 'concat' && name.id !== 'forEach'
            )) &&
            (left.id !== '+' || name.id !== 'slice') &&
            (left.id !== '(regexp)' || (
                name.id !== 'exec' && name.id !== 'test'
            ))
        ) {
            left_check(left, the_token);
        }
        if (!name.identifier) {
            stop('expected_identifier_a');
        }
        advance();
        survey(name);

// The property name is not an expression.

        the_token.name = name;
        the_token.expression = left;
        return the_token;
    });
    infix('[', 170, function (left) {
        var the_token = token,
            the_subscript = expression(0);
        if (
            the_subscript.id === '(string)' &&
            rx_identifier.test(the_subscript.value)
        ) {
            warn('subscript_a', the_subscript);
            survey(the_subscript);
        } else if (the_subscript.id === '`') {
            warn('unexpected_a', the_subscript);
        }
        left_check(left, the_token);
        the_token.expression = [left, the_subscript];
        advance(']');
        return the_token;
    });
    infix('=>', 170, function (left) {
        return stop('expected_a_before_b', left, '(', artifact(left));
    });

    function do_tick() {
        var the_tick = token;
        if (!option.es6) {
            warn('es6', the_tick);
        }
        the_tick.value = [];
        the_tick.expression = [];
        if (next_token.id !== '`') {
            (function part() {
                advance('(string)');
                the_tick.value.push(token);
                if (next_token.id === '${') {
                    advance('${');
                    the_tick.expression.push(expression(0));
                    advance('}');
                    return part();
                }
            }());
        }
        advance('`');
        return the_tick;
    }

    infix('`', 160, function (left) {
        var the_tick = do_tick();
        left_check(left, the_tick);
        the_tick.expression = [left].concat(the_tick.expression);
        return the_tick;
    });

    post('++');
    post('--');
    pre('++');
    pre('--');

    prefix('+');
    prefix('-');
    prefix('~');
    prefix('!');
    prefix('!!');
    prefix('[', function () {
        var the_token = token;
        the_token.expression = [];
        if (next_token.id !== ']') {
            (function next() {
                the_token.expression.push(expression(10));
                if (next_token.id === ',') {
                    advance(',');
                    return next();
                }
            }());
        }
        advance(']');
        return the_token;
    });
    prefix('=>', function () {
        return stop('expected_a_before_b', token, '()', '=>');
    });
    prefix('new', function () {
        var the_new = token;
        next_token.new = true;
        the_new.expression = expression(150);
        if (the_new.expression.id !== '(') {
            warn('expected_a_before_b', next_token, '()', artifact(next_token));
        }
        return the_new;
    });
    prefix('typeof');
    prefix('void', function () {
        var the_void = token;
        warn('unexpected_a', the_void);
        the_void.expression = expression(0);
        return the_void;
    });

    function parameter(list, signature) {
        var ellipsis = false,
            param;
        if (next_token.id === '{') {
            if (!option.es6) {
                warn('es6');
            }
            param = next_token;
            param.names = [];
            advance('{');
            signature.push('{');
            (function subparameter() {
                var subparam = next_token;
                if (!subparam.identifier) {
                    return stop('expected_identifier_a');
                }
                survey(subparam);
                advance();
                signature.push(subparam.id);
                if (next_token.id === ':') {
                    advance(':');
                    advance();
                    token.label = subparam;
                    subparam = token;
                    if (!subparam.identifier) {
                        return stop('expected_identifier_a');
                    }
                }
                param.names.push(subparam);
                if (next_token.id === ',') {
                    advance(',');
                    signature.push(", ");
                    return subparameter();
                }
            }());
            list.push(param);
            advance('}');
            signature.push('}');
            if (next_token.id === ',') {
                advance(',');
                signature.push(", ");
                return parameter(list, signature);
            }
        } else if (next_token.id === '[') {
            if (!option.es6) {
                warn('es6');
            }
            param = next_token;
            param.names = [];
            advance('[');
            signature.push("[]");
            (function subparameter() {
                var subparam = next_token;
                if (!subparam.identifier) {
                    return stop('expected_identifier_a');
                }
                advance();
                param.names.push(subparam);
                if (next_token.id === ',') {
                    advance(',');
                    return subparameter();
                }
            }());
            list.push(param);
            advance(']');
            if (next_token.id === ',') {
                advance(',');
                signature.push(", ");
                return parameter(list, signature);
            }
        } else {
            if (next_token.id === '...') {
                if (!option.es6) {
                    warn('es6');
                }
                ellipsis = true;
                signature.push("...");
                advance('...');
            }
            if (!next_token.identifier) {
                return stop('expected_identifier_a');
            }
            param = next_token;
            list.push(param);
            advance();
            signature.push(param.id);
            if (ellipsis) {
                param.ellipsis = true;
            } else {
                if (next_token.id === '=') {
                    if (!option.es6) {
                        warn('es6');
                    }
                    advance('=');
                    param.expression = expression(0);
                }
                if (next_token.id === ',') {
                    advance(',');
                    signature.push(", ");
                    return parameter(list, signature);
                }
            }
        }
    }

    function parameter_list() {
        var list = [], signature = ['('];
        if (next_token.id !== ')' && next_token.id !== '(end)') {
            parameter(list, signature);
        }
        advance(')');
        signature.push(')');
        return [list, signature.join('')];
    }

    function do_function(the_function) {
        var name;
        if (the_function === undefined) {
            the_function = token;

// A function statement must have a name that will be in the parent's scope.

            if (the_function.arity === 'statement') {
                if (!next_token.identifier) {
                    return stop('expected_identifier_a', next_token);
                }
                name = next_token;
                enroll(name, 'variable', true);
                the_function.name = name;
                name.init = true;
                name.calls = empty();
                advance();
            } else if (name === undefined) {

// A function expression may have an optional name.

                if (next_token.identifier) {
                    name = next_token;
                    the_function.name = name;
                    advance();
                } else {
                    the_function.name = anon;
                }
            }
        } else {
            name = the_function.name;
        }
        the_function.level = functionage.level + 1;
        if (mega_mode) {
            warn('unexpected_a', the_function);
        }

// Don't make functions in loops. It is inefficient, and it can lead to scoping
// errors.

        if (functionage.loop > 0) {
            warn('function_in_loop', the_function);
        }

// Give the function properties for storing its names and for observing the
// depth of loops and switches.

        the_function.context = empty();
        the_function.loop = 0;
        the_function.switch = 0;

// Push the current function context and establish a new one.

        stack.push(functionage);
        functions.push(the_function);
        functionage = the_function;
        if (the_function.arity !== 'statement' && name) {
            enroll(name, 'function', true);
            name.dead = false;
            name.init = true;
            name.used = 1;
        }

// Parse the parameter list.

        advance('(');
        token.free = false;
        token.arity = 'function';
        var pl = parameter_list();
        functionage.parameters = pl[0];
        functionage.signature = pl[1];
        functionage.parameters.forEach(function enroll_parameter(name) {
            if (name.identifier) {
                enroll(name, 'parameter', false);
            } else {
                name.names.forEach(enroll_parameter);
            }
        });

// The function's body is a block.

        the_function.block = block('body');
        if (the_function.arity === 'statement' && next_token.line === token.line) {
            return stop('unexpected_a', next_token);
        }
        if (next_token.id === '.' || next_token.id === '[') {
            warn('unexpected_a');
        }

// Restore the previous context.

        functionage = stack.pop();
        return the_function;
    }

    prefix('function', do_function);

    function fart(pl) {
        advance('=>');
        var the_arrow = token;
        the_arrow.arity = 'binary';
        the_arrow.name = "=>";
        the_arrow.level = functionage.level + 1;
        functions.push(the_arrow);
        if (functionage.loop > 0) {
            warn('function_in_loop', the_arrow);
        }

// Give the function properties storing its names and for observing the depth
// of loops and switches.

        the_arrow.context = empty();
        the_arrow.loop = 0;
        the_arrow.switch = 0;

// Push the current function context and establish a new one.

        stack.push(functionage);
        functionage = the_arrow;
        the_arrow.parameters = pl[0];
        the_arrow.signature = pl[1];
        the_arrow.parameters.forEach(function (name) {
            enroll(name, 'parameter', true);
        });
        if (!option.es6) {
            warn('es6', the_arrow);
        }
        if (next_token.id === '{') {
            warn('expected_a_b', the_arrow, "function", "=>");
            the_arrow.block = block('body');
        } else {
            the_arrow.expression = expression(0);
        }
        functionage = stack.pop();
        return the_arrow;
    }

    prefix('(', function () {
        var the_paren = token,
            the_value,
            cadet = lookahead().id;

// We can distinguish between a parameter list for => and a wrapped expression
// with one token of lookahead.

        if (
            next_token.id === ')' ||
            next_token.id === '...' ||
            (next_token.identifier && (cadet === ',' || cadet === '='))
        ) {
            the_paren.free = false;
            return fart(parameter_list());
        }
        the_paren.free = true;
        the_value = expression(0);
        if (the_value.wrapped === true) {
            warn('unexpected_a', the_paren);
        }
        the_value.wrapped = true;
        advance(')', the_paren);
        if (next_token.id === "=>") {
            if (the_value.arity !== 'variable') {
                return stop('expected_identifier_a', the_value);
            }
            the_paren.expression = [the_value];
            return fart([the_paren.expression, "(" + the_value.id + ")"]);
        }
        return the_value;
    });
    prefix('`', do_tick);
    prefix('{', function () {
        var the_brace = token,
            seen = empty();
        the_brace.expression = [];
        if (next_token.id !== '}') {
            (function member() {
                var extra = true,
                    id,
                    name = next_token,
                    value;
                advance();
                if (
                    (name.id === 'get' || name.id === 'set') &&
                    next_token.identifier
                ) {
                    extra = name.id;
                    name = next_token;
                    advance();
                }
                id = survey(name);
                if (seen[id] === true) {
                    warn('duplicate_a', name);
                } else if (seen[id] === 'get' && extra !== 'set') {
                    warn('expected_a_before_b', name, 'set', artifact(name));
                }
                seen[id] = extra === 'get'
                    ? 'get'
                    : true;
                if (name.identifier) {
                    switch (next_token.id) {
                    case '}':
                    case ',':
                        if (!option.es6) {
                            warn('es6');
                        } else if (extra !== true) {
                            advance(':');
                        }
                        name.arity = 'variable';
                        value = name;
                        break;
                    case '(':
                        if (!option.es6) {
                            warn('es6');
                        }
                        value = do_function({
                            arity: 'unary',
                            from: name.from,
                            id: 'function',
                            line: name.line,
                            name: name,
                            thru: name.from
                        }, name);
                        break;
                    default:
                        advance(':');
                        value = expression(0);
                    }
                    value.label = name;
                    if (typeof extra === 'string') {
                        value.extra = extra;
                    }
                    the_brace.expression.push(value);
                } else {
                    advance(':');
                    value = expression(0);
                    value.label = name;
                    the_brace.expression.push(value);
                }
                if (next_token.id === ',') {
                    advance(',');
                    return member();
                }
            }());
        }
        advance('}');
        return the_brace;
    });

    stmt(';', function () {
        warn('unexpected_a', token);
        return token;
    });
    stmt('{', function () {
        warn('naked_block', token);
        return block('naked');
    });
    stmt('break', function () {
        var the_break = token,
            the_label;
        if (functionage.loop < 1 && functionage.switch < 1) {
            warn('unexpected_a', the_break);
        }
        the_break.disrupt = true;
        if (next_token.identifier && token.line === next_token.line) {
            the_label = functionage.context[next_token.id];
            if (
                the_label === undefined ||
                the_label.role !== 'label' ||
                the_label.dead
            ) {
                warn(the_label !== undefined && the_label.dead
                    ? 'out_of_scope_a'
                    : 'not_label_a');
            } else {
                the_label.used += 1;
            }
            the_break.label = next_token;
            advance();
        }
        advance(';');
        return the_break;
    });

    function do_var() {
        var the_statement = token,
            is_const = the_statement.id === 'const';
        the_statement.names = [];

// A program may use var or let, but not both, and let and const require
// option.es6.

        if (is_const) {
            if (!option.es6) {
                warn('es6', the_statement);
            }
        } else if (var_mode === undefined) {
            var_mode = the_statement.id;
            if (!option.es6 && var_mode !== 'var') {
                warn('es6', the_statement);
            }
        } else if (the_statement.id !== var_mode) {
            warn(
                'expected_a_b',
                the_statement,
                var_mode,
                the_statement.id
            );
        }

// We don't expect to see variables created in switch statements.

        if (functionage.switch > 0) {
            warn('var_switch', the_statement);
        }
        if (functionage.loop > 0 && the_statement.id === 'var') {
            warn('var_loop', the_statement);
        }
        (function next() {
            if (next_token.id === '{' && the_statement.id !== 'var') {
                var the_brace = next_token;
                the_brace.names = [];
                advance('{');
                (function pair() {
                    if (!next_token.identifier) {
                        return stop('expected_identifier_a', next_token);
                    }
                    var name = next_token;
                    survey(name);
                    advance();
                    if (next_token.id === ':') {
                        advance(':');
                        if (!next_token.identifier) {
                            return stop('expected_identifier_a', next_token);
                        }
                        next_token.label = name;
                        the_brace.names.push(next_token);
                        enroll(next_token, 'variable', is_const);
                        advance();
                    } else {
                        the_brace.names.push(name);
                        enroll(name, 'variable', is_const);
                    }
                    if (next_token.id === ',') {
                        advance(',');
                        return pair();
                    }
                }());
                advance('}');
                advance('=');
                the_brace.expression = expression(0);
                the_statement.names.push(the_brace);
            } else if (next_token.id === '[' && the_statement.id !== 'var') {
                var the_bracket = next_token;
                the_bracket.names = [];
                advance('[');
                (function element() {
                    var ellipsis;
                    if (next_token.id === '...') {
                        ellipsis = true;
                        advance('...');
                    }
                    if (!next_token.identifier) {
                        return stop('expected_identifier_a', next_token);
                    }
                    var name = next_token;
                    advance();
                    the_bracket.names.push(name);
                    enroll(name, 'variable', the_statement.id === 'const');
                    if (ellipsis) {
                        name.ellipsis = true;
                    } else if (next_token.id === ',') {
                        advance(',');
                        return element();
                    }
                }());
                advance(']');
                advance('=');
                the_bracket.expression = expression(0);
                the_statement.names.push(the_bracket);
            } else if (next_token.identifier) {
                var name = next_token;
                advance();
                if (name.id === 'ignore') {
                    warn('unexpected_a', name);
                }
                enroll(name, 'variable', is_const);
                if (next_token.id === '=' || is_const) {
                    advance('=');
                    name.expression = expression(0);
                    name.init = true;
                }
                the_statement.names.push(name);
            } else {
                return stop('expected_identifier_a', next_token);
            }
            if (next_token.id === ',') {
                advance(',');
                return next();
            }
        }());
        the_statement.open =
                the_statement.names.length > 1 &&
                the_statement.line !== the_statement.names[1].line;
        semicolon();
        return the_statement;
    }

    stmt('const', do_var);
    stmt('continue', function () {
        var the_continue = token;
        if (functionage.loop < 1) {
            warn('unexpected_a', the_continue);
        }
        not_top_level(the_continue);
        the_continue.disrupt = true;
        warn('unexpected_a', the_continue);
        advance(';');
        return the_continue;
    });
    stmt('debugger', function () {
        var the_debug = token;
        if (!option.devel) {
            warn('unexpected_a', the_debug);
        }
        semicolon();
        return the_debug;
    });
    stmt('delete', function () {
        var the_token = token,
            the_value = expression(0);
        if (
            (the_value.id !== '.' && the_value.id !== '[') ||
            the_value.arity !== 'binary'
        ) {
            stop('expected_a_b', the_value, '.', artifact(the_value));
        }
        the_token.expression = the_value;
        semicolon();
        return the_token;
    });
    stmt('do', function () {
        var the_do = token;
        not_top_level(the_do);
        functionage.loop += 1;
        the_do.block = block();
        advance('while');
        the_do.expression = condition();
        semicolon();
        if (the_do.block.disrupt === true) {
            warn('weird_loop', the_do);
        }
        functionage.loop -= 1;
        return the_do;
    });
    stmt('export', function () {
        var the_export = token;
        if (export_mode) {
            warn('es6', the_export);
        }
        if (!option.es6) {
            warn('es6', the_export);
        }
        if (typeof module_mode === 'object') {
            warn('unexpected_directive_a', module_mode, module_mode.directive);
        }
        module_mode = true;
        advance('default');
        the_export.expression = expression(0);
        semicolon();
        return the_export;
    });
    stmt('for', function () {
        var first,
            the_for = token;
        if (!option.for) {
            warn('unexpected_a', the_for);
        }
        not_top_level(the_for);
        functionage.loop += 1;
        advance('(');
        token.free = true;
        if (next_token.id === ';') {
            return stop('expected_a_b', the_for, 'while (', 'for (;');
        }
        if (
            next_token.id === 'var' ||
            next_token.id === 'let' ||
            next_token.id === 'const'
        ) {
            return stop('unexpected_a');
        }
        first = expression(0);
        if (first.id === 'in') {
            if (first.expression[0].arity !== 'variable') {
                warn('bad_assignment_a', first.expression[0]);
            }
            the_for.name = first.expression[0];
            the_for.expression = first.expression[1];
            warn('expected_a_b', the_for, 'Object.keys', 'for in');
        } else {
            the_for.initial = first;
            advance(';');
            the_for.expression = expression(0);
            advance(';');
            the_for.inc = expression(0);
            if (the_for.inc.id === '++') {
                warn('expected_a_b', the_for.inc, '+= 1', '++');
            }
        }
        advance(')');
        the_for.block = block();
        if (the_for.block.disrupt === true) {
            warn('weird_loop', the_for);
        }
        functionage.loop -= 1;
        return the_for;
    });
    stmt('function', do_function);
    stmt('if', function () {
        var the_else,
            the_if = token;
        the_if.expression = condition();
        the_if.block = block();
        if (next_token.id === 'else') {
            advance('else');
            the_else = token;
            the_if.else = next_token.id === 'if'
                ? statement()
                : block();
            if (the_if.block.disrupt === true) {
                if (the_if.else.disrupt === true) {
                    the_if.disrupt = true;
                } else {
                    warn('unexpected_a', the_else);
                }
            }
        }
        return the_if;
    });
    stmt('import', function () {
        var the_import = token;
        if (!option.es6) {
            warn('es6', the_import);
        } else if (typeof module_mode === 'object') {
            warn('unexpected_directive_a', module_mode, module_mode.directive);
        }
        module_mode = true;
        if (!next_token.identifier) {
            return stop('expected_identifier_a');
        }
        var name = next_token;
        advance();
        if (name.id === 'ignore') {
            warn('unexpected_a', name);
        }
        enroll(name, 'variable', true);
        advance('from');
        advance('(string)');
        the_import.import = token;
        the_import.name = name;
        if (!rx_identifier.test(token.value)) {
            warn('bad_module_name_a', token);
        }
        imports.push(token.value);
        semicolon();
        return the_import;
    });
    stmt('let', do_var);
    stmt('return', function () {
        var the_return = token;
        not_top_level(the_return);
        the_return.disrupt = true;
        if (next_token.id !== ';' && the_return.line === next_token.line) {
            the_return.expression = expression(10);
        }
        advance(';');
        return the_return;
    });
    stmt('switch', function () {
        var stmts,
            the_cases = [],
            the_switch = token;
        not_top_level(the_switch);
        functionage.switch += 1;
        advance('(');
        token.free = true;
        the_switch.expression = expression(0);
        the_switch.block = the_cases;
        advance(')');
        advance('{');
        (function major() {
            var the_case = next_token;
            the_case.arity = 'statement';
            the_case.expression = [];
            (function minor() {
                advance('case');
                token.switch = true;
                the_case.expression.push(expression(0));
                advance(':');
                if (next_token.id === 'case') {
                    return minor();
                }
            }());
            stmts = statements();
            the_case.block = stmts;
            the_cases.push(the_case);
            if (!stmts[stmts.length - 1].disrupt) {
                warn(
                    'expected_a_before_b',
                    next_token,
                    'break;',
                    artifact(next_token)
                );
            }
            if (next_token.id === 'case') {
                return major();
            }
        }());
        if (next_token.id === 'default') {
            advance('default');
            token.switch = true;
            advance(':');
            the_switch.else = statements();
        }
        advance('}', the_switch);
        functionage.switch -= 1;
        return the_switch;
    });
    stmt('throw', function () {
        var the_throw = token;
        the_throw.disrupt = true;
        the_throw.expression = expression(10);
        semicolon();
        return the_throw;
    });
    stmt('try', function () {
        var the_try = token,
            the_catch;
        the_try.block = block();
        if (next_token.id === 'catch') {
            var ignored = 'ignore';
            the_catch = next_token;
            the_try.catch = the_catch;
            advance('catch');
            advance('(');
            if (!next_token.identifier) {
                return stop('expected_identifier_a', next_token);
            }
            if (next_token.id !== 'ignore') {
                ignored = undefined;
                the_catch.name = next_token;
                enroll(next_token, 'exception', true);
            }
            advance();
            advance(')');
            the_catch.block = block(ignored);
        }
        if (next_token.id === 'finally') {
            advance('finally');
            the_try.else = block();
        }
        return the_try;
    });
    stmt('var', do_var);
    stmt('while', function () {
        var the_while = token;
        not_top_level(the_while);
        functionage.loop += 1;
        the_while.expression = condition();
        the_while.block = block();
        if (the_while.block.disrupt === true) {
            warn('weird_loop', the_while);
        }
        functionage.loop -= 1;
        return the_while;
    });

    ternary('?', ':');

// Ambulation of the parse tree.

    function action(when) {

// Produce a function that will register task functions that will be called as
// the tree is traversed.

        return function (arity, id, task) {
            var a_set = when[arity],
                i_set;

// The id parameter is optional. If excluded, the task will be applied to all
// ids.

            if (typeof id !== 'string') {
                task = id;
                id = '(all)';
            }

// If this arity has no registrations yet, then create a set object to hold
// them.

            if (a_set === undefined) {
                a_set = empty();
                when[arity] = a_set;
            }

// If this id has no registrations yet, then create a set array to hold them.

            i_set = a_set[id];
            if (i_set === undefined) {
                i_set = [];
                a_set[id] = i_set;
            }

// Register the task with the arity and the id.

            i_set.push(task);
        };
    }

    function amble(when) {

// Produce a function that will act on the tasks registered by an action
// function while walking the tree.

        return function (the_token) {

// Given a task set that was built by an action function, run all of the
// relevant tasks on the token.

            var a_set = when[the_token.arity],
                i_set;

// If there are tasks associated with the token's arity...

            if (a_set !== undefined) {

// If there are tasks associated with the token's id...

                i_set = a_set[the_token.id];
                if (i_set !== undefined) {
                    i_set.forEach(function (task) {
                        return task(the_token);
                    });
                }

// If there are tasks for all ids.

                i_set = a_set['(all)'];
                if (i_set !== undefined) {
                    i_set.forEach(function (task) {
                        return task(the_token);
                    });
                }
            }
        };
    }

    var posts = empty(),
        pres = empty(),
        preaction = action(pres),
        postaction = action(posts),
        preamble = amble(pres),
        postamble = amble(posts);

    function walk_expression(thing) {
        if (thing) {
            if (Array.isArray(thing)) {
                thing.forEach(walk_expression);
            } else {
                preamble(thing);
                walk_expression(thing.expression);
                if (thing.id === 'function') {
                    walk_statement(thing.block);
                }
                switch (thing.arity) {
                case 'post':
                case 'pre':
                    warn('unexpected_a', thing);
                    break;
                case 'statement':
                case 'assignment':
                    warn('unexpected_statement_a', thing);
                    break;
                }
                postamble(thing);
            }
        }
    }

    function walk_statement(thing) {
        if (thing) {
            if (Array.isArray(thing)) {
                thing.forEach(walk_statement);
            } else {
                preamble(thing);
                walk_expression(thing.expression);
                switch (thing.arity) {
                case 'statement':
                case 'assignment':
                    break;
                case 'binary':
                    if (thing.id !== '(') {
                        warn('unexpected_expression_a', thing);
                    }
                    break;
                default:
                    warn('unexpected_expression_a', thing);
                }
                walk_statement(thing.block);
                walk_statement(thing.else);
                postamble(thing);
            }
        }
    }

    function lookup(thing) {
        if (thing.arity === 'variable') {

// Look up the variable in the current context.

            var the_variable = functionage.context[thing.id];

// If it isn't local, search all the other contexts. If there are name
// collisions, take the most recent.

            if (the_variable === undefined) {
                stack.forEach(function (outer) {
                    var a_variable = outer.context[thing.id];
                    if (
                        a_variable !== undefined &&
                        a_variable.role !== 'label'
                    ) {
                        the_variable = a_variable;
                    }
                });

// If it isn't in any of those either, perhaps it is a predefined global.
// If so, add it to the global context.

                if (the_variable === undefined) {
                    if (declared_globals[thing.id] === undefined) {
                        warn('undeclared_a', thing);
                        return;
                    }
                    the_variable = {
                        dead: false,
                        function: global,
                        id: thing.id,
                        init: true,
                        role: 'variable',
                        used: 0,
                        writable: false
                    };
                    global.context[thing.id] = the_variable;
                }
                the_variable.closure = true;
                functionage.context[thing.id] = the_variable;
            } else if (the_variable.role === 'label') {
                warn('label_a', thing);
            }
            if (the_variable.dead && the_variable.function === functionage) {
                warn('out_of_scope_a', thing);
            }
            return the_variable;
        }
    }

    function preaction_function(thing) {
        if (thing.arity === 'statement' && blockage.body !== true) {
            warn('unexpected_a', thing);
        }
        stack.push(functionage);
        block_stack.push(blockage);
        functionage = thing;
        blockage = thing;
        thing.live = [];
        if (typeof thing.name === 'object') {
            thing.name.dead = false;
            thing.name.init = true;
        }
        switch (thing.extra) {
        case 'get':
            if (thing.parameters.length !== 0) {
                warn('bad_get', thing);
            }
            break;
        case 'set':
            if (thing.parameters.length !== 1) {
                warn('bad_set', thing);
            }
            break;
        }
        thing.parameters.forEach(function (name) {
            walk_expression(name.expression);
            if (name.id === '{' || name.id === '[') {
                name.names.forEach(subactivate);
            } else {
                name.dead = false;
                name.init = true;
            }
        });
    }

    function bitwise_check(thing) {
        if (!option.bitwise && bitwiseop[thing.id] === true) {
            warn('unexpected_a', thing);
        }
    }

    function pop_block() {
        blockage.live.forEach(function (name) {
            name.dead = true;
        });
        delete blockage.live;
        blockage = block_stack.pop();
    }

    function subactivate(name) {
        name.init = true;
        name.dead = false;
        blockage.live.push(name);
    }

    function activate(name) {
        if (name.expression !== undefined) {
            walk_expression(name.expression);
            if (name.id === '{' || name.id === '[') {
                name.names.forEach(subactivate);
            } else {
                name.init = true;
            }
        }
        name.dead = false;
        blockage.live.push(name);
    }

    function action_var(thing) {
        thing.names.forEach(activate);
    }

    preaction('assignment', bitwise_check);
    preaction('binary', bitwise_check);
    preaction('binary', function (thing) {
        if (relationop[thing.id] === true) {
            if (
                thing.expression[0].id === 'NaN' ||
                thing.expression[1].id === 'NaN'
            ) {
                warn('isNaN', thing);
            } else if (thing.expression[0].id === 'typeof') {
                if (thing.expression[1].id !== '(string)') {
                    if (thing.expression[1].id !== 'typeof') {
                        warn('expected_string_a', expression[1]);
                    }
                } else {
                    var value = thing.expression[1].value;
                    if (value === 'symbol') {
                        if (!option.es6) {
                            warn('es6', thing.expression[1], value);
                        }
                    } else if (value === 'null' || value === 'undefined') {
                        warn('unexpected_typeof_a', thing.expression[1], value);
                    } else if (
                        value !== 'boolean' &&
                        value !== 'function' &&
                        value !== 'number' &&
                        value !== 'object' &&
                        value !== 'string'
                    ) {
                        warn('expected_type_string_a', expression[1], value);
                    }
                }
            }
        }
    });
    preaction('binary', '==', function (thing) {
        warn('expected_a_b', thing, '===', '==');
    });
    preaction('binary', '!=', function (thing) {
        warn('expected_a_b', thing, '!==', '!=');
    });
    preaction('binary', '=>', preaction_function);
    preaction('binary', '||', function (thing) {
        thing.expression.forEach(function (thang) {
            if (thang.id === '&&' && !thang.wrapped) {
                warn('and', thang);
            }
        });
    });
    preaction('binary', '(', function (thing) {
        var left = thing.expression[0];
        if (
            left.identifier &&
            functionage.context[left.id] === undefined &&
            typeof functionage.name === 'object'
        ) {
            var parent = functionage.name.function;
            if (parent) {
                var left_variable = parent.context[left.id];
                if (
                    left_variable !== undefined &&
                    left_variable.dead &&
                    left_variable.function === parent &&
                    left_variable.calls !== undefined &&
                    left_variable.calls[functionage.name.id] !== undefined
                ) {
                    left_variable.dead = false;
                }
            }
        }
    });
    preaction('binary', 'in', function (thing) {
        warn('infix_in', thing);
    });
    preaction('statement', '{', function (thing) {
        block_stack.push(blockage);
        blockage = thing;
        thing.live = [];
    });
    preaction('statement', 'for', function (thing) {
        if (thing.name !== undefined) {
            var the_variable = lookup(thing.name);
            if (the_variable !== undefined) {
                the_variable.init = true;
                if (!the_variable.writable) {
                    warn('bad_assignment_a', thing.name);
                }
            }
        }
        walk_statement(thing.initial);
    });
    preaction('statement', 'function', preaction_function);
    preaction('unary', '~', bitwise_check);
    preaction('unary', 'function', preaction_function);
    preaction('variable', function (thing) {
        var the_variable = lookup(thing);
        if (the_variable !== undefined) {
            thing.variable = the_variable;
            the_variable.used += 1;
        }
    });

    function init_variable(name) {
        var the_variable = lookup(name);
        if (the_variable !== undefined) {
            if (the_variable.writable) {
                the_variable.init = true;
                return;
            }
        }
        warn('bad_assignment_a', name);
    }

    postaction('assignment', function (thing) {

// Assignment using = sets the init property of a variable. No other assignment
// operator can do this. A = token keeps that variable (or array of variables
// in case of destructuring) in its name property.

        if (thing.id === '=') {
            if (thing.names !== undefined) {
                if (Array.isArray(thing.names)) {
                    thing.names.forEach(init_variable);
                } else {
                    init_variable(thing.names);
                }
            }
        } else {
            var lvalue = thing.expression[0];
            if (lvalue.arity === 'variable') {
                if (!lvalue.variable || lvalue.variable.writable !== true) {
                    warn('bad_assignment_a', lvalue);
                }
            }
        }
    });

    function postaction_function(thing) {
        delete functionage.loop;
        delete functionage.switch;
        functionage = stack.pop();
        if (thing.wrapped) {
            warn('unexpected_parens', thing);
        }
        return pop_block();
    }

    function is_weird(thing) {
        return (
            thing.id === '(regexp)' ||
            thing.id === '{' ||
            thing.id === '=>' ||
            thing.id === 'function' ||
            (thing.id === '[' && thing.arity === 'unary')
        );
    }

    function are_similar(a, b) {
        if (a === b) {
            return true;
        }
        if (Array.isArray(a)) {
            return (
                Array.isArray(b) &&
                a.length === b.length &&
                a.every(function (value, index) {
                    return are_similar(value, b[index]);
                })
            );
        }
        if (Array.isArray(b)) {
            return false;
        }
        if (a.id === '(number)' && b.id === '(number)') {
            return a.value === b.value;
        }
        var a_string, b_string;
        if (a.id === '(string)') {
            a_string = a.value;
        } else if (a.id === '`' && a.constant) {
            a_string = a.value[0];
        }
        if (b.id === '(string)') {
            b_string = b.value;
        } else if (b.id === '`' && b.constant) {
            b_string = b.value[0];
        }
        if (typeof a_string === 'string') {
            return a_string === b_string;
        }
        if (is_weird(a) || is_weird(b)) {
            return false;
        }
        if (a.arity === b.arity && a.id === b.id) {
            if (a.id === '.') {
                return are_similar(a.expression, b.expression) &&
                        are_similar(a.name, b.name);
            }
            switch (a.arity) {
            case 'unary':
                return are_similar(a.expression, b.expression);
            case 'binary':
                return a.id !== '(' &&
                        are_similar(a.expression[0], b.expression[0]) &&
                        are_similar(a.expression[1], b.expression[1]);
            case 'ternary':
                return are_similar(a.expression[0], b.expression[0]) &&
                        are_similar(a.expression[1], b.expression[1]) &&
                        are_similar(a.expression[2], b.expression[2]);
            case 'function':
            case 'regexp':
                return false;
            default:
                return true;
            }
        }
        return false;
    }


    postaction('binary', function (thing) {
        if (relationop[thing.id]) {
            if (
                is_weird(thing.expression[0]) ||
                is_weird(thing.expression[1]) ||
                are_similar(thing.expression[0], thing.expression[1]) ||
                (
                    thing.expression[0].constant === true &&
                    thing.expression[1].constant === true
                )
            ) {
                warn('weird_relation_a', thing);
            }
        }
        switch (thing.id) {
        case '=>':
        case '(':
        case '.':
            break;
        default:
            if (
                thing.expression[0].constant === true &&
                thing.expression[1].constant === true
            ) {
                thing.constant = true;
            }
        }
    });
    postaction('binary', '&&', function (thing) {
        if (
            is_weird(thing.expression[0]) ||
            are_similar(thing.expression[0], thing.expression[1]) ||
            thing.expression[0].constant === true ||
            thing.expression[1].constant === true
        ) {
            warn('weird_condition_a', thing);
        }
    });
    postaction('binary', '||', function (thing) {
        if (
            is_weird(thing.expression[0]) ||
            are_similar(thing.expression[0], thing.expression[1]) ||
            thing.expression[0].constant === true
        ) {
            warn('weird_condition_a', thing);
        }
    });
    postaction('binary', '=>', postaction_function);
    postaction('binary', '(', function (thing) {
        if (!thing.wrapped && thing.expression[0].id === 'function') {
            warn('wrap_immediate', thing);
        }
    });
    postaction('binary', '[', function (thing) {
        if (is_weird(thing.expression[1])) {
            warn('weird_expression_a', thing.expression[1]);
        }
    });
    postaction('statement', '{', pop_block);
    postaction('statement', 'const', action_var);
    postaction('statement', 'export', top_level_only);
    postaction('statement', 'for', function (thing) {
        walk_statement(thing.inc);
    });
    postaction('statement', 'function', postaction_function);
    postaction('statement', 'import', function (the_thing) {
        var name = the_thing.name;
        name.init = true;
        name.dead = false;
        blockage.live.push(name);
        return top_level_only(the_thing);
    });
    postaction('statement', 'let', action_var);
    postaction('statement', 'try', function (thing) {
        if (thing.catch !== undefined) {
            var the_name = thing.catch.name;
            if (the_name !== undefined) {
                var the_variable = functionage.context[the_name.id];
                the_variable.dead = false;
                the_variable.init = true;
            }
            walk_statement(thing.catch.block);
        }
    });
    postaction('statement', 'var', action_var);
    postaction('ternary', function (thing) {
        if (
            is_weird(thing.expression[0]) ||
            thing.expression[0].constant === true ||
            are_similar(thing.expression[1], thing.expression[2])
        ) {
            warn('unexpected_a', thing);
        } else if (are_similar(thing.expression[0], thing.expression[1])) {
            warn('expected_a_b', thing, '||', '?');
        } else if (are_similar(thing.expression[0], thing.expression[2])) {
            warn('expected_a_b', thing, '&&', '?');
        } else if (
            thing.expression[1].id === 'true' &&
            thing.expression[2].id === 'false'
        ) {
            warn('expected_a_b', thing, '!!', '?');
        } else if (
            thing.expression[1].id === 'false' &&
            thing.expression[2].id === 'true'
        ) {
            warn('expected_a_b', thing, '!', '?');
        }
    });
    postaction('unary', function (thing) {
        switch (thing.id) {
        case '[':
        case '{':
        case 'function':
        case 'new':
            break;
        case '`':
            if (thing.expression.every(function (thing) {
                return thing.constant;
            })) {
                thing.constant = true;
            }
            break;
        default:
            if (thing.expression.constant === true) {
                thing.constant = true;
            }
        }
    });
    postaction('unary', 'function', postaction_function);

    function delve(the_function) {
        Object.keys(the_function.context).forEach(function (id) {
            if (id !== 'ignore') {
                var name = the_function.context[id];
                if (name.function === the_function) {
                    if (name.used === 0) {
                        warn('unused_a', name);
                    } else if (!name.init) {
                        warn('uninitialized_a', name);
                    }
                }
            }
        });
    }

    function uninitialized_and_unused() {

// Delve into the functions looking for variables that were not initialized
// or used. If the file imports or exports, then its global object is also
// delved.

        if (module_mode === true || option.node) {
            delve(global);
        }
        functions.forEach(delve);
    }

// Go through the token list, looking at usage of whitespace.

    function whitage() {
        var closer = '(end)',
            free = false,
            left = global,
            margin = 0,
            nr_comments_skipped = 0,
            open = true,
            qmark = '',
            result,
            right;

        function at_margin(fit) {
            if (right.from !== margin + fit) {
                warn(
                    'expected_a_at_b_c',
                    right,
                    artifact(right),
                    margin + fit,
                    artifact_column(right)
                );
            }
        }

        function expected_at(at) {
            warn(
                'expected_a_at_b_c',
                right,
                artifact(right),
                at,
                artifact_column(right)
            );
        }

        function no_space_only() {
            if (left.line !== right.line || left.thru !== right.from) {
                warn(
                    'unexpected_space_a_b',
                    right,
                    artifact(left),
                    artifact(right)
                );
            }
        }

        function no_space() {
            if (left.line === right.line) {
                if (left.thru !== right.from && nr_comments_skipped === 0) {
                    warn(
                        'unexpected_space_a_b',
                        right,
                        artifact(left),
                        artifact(right)
                    );
                }
            } else {
                if (open) {
                    var at = free
                        ? margin
                        : margin + 8;
                    if (right.from < at) {
                        expected_at(at);
                    }
                } else {
                    if (right.from !== margin + 8) {
                        expected_at(margin + 8);
                    }
                }
            }
        }

        function one_space_only() {
            if (left.line !== right.line || left.thru + 1 !== right.from) {
                warn(
                    'expected_space_a_b',
                    right,
                    artifact(left),
                    artifact(right)
                );
            }
        }

        function one_space() {
            if (left.line === right.line) {
                if (left.thru + 1 !== right.from && nr_comments_skipped === 0) {
                    warn(
                        'expected_space_a_b',
                        right,
                        artifact(left),
                        artifact(right)
                    );
                }
            } else {
                if (open) {
                    var at = free
                        ? margin
                        : margin + 8;
                    if (right.from < at) {
                        expected_at(at);
                    }
                } else {
                    if (right.from !== margin + 8) {
                        expected_at(margin + 8);
                    }
                }
            }
        }

        function unqmark() {

// Undo the effects of dangling nested ternary operators.

            var level = qmark.length;
            if (level > 0) {
                margin -= level * 4;
            }
            qmark = '';
        }

        stack = [];
        tokens.forEach(function (the_token) {
            right = the_token;
            if (right.id === '(comment)' || right.id === '(end)') {
                nr_comments_skipped += 1;
            } else {

// If left is an opener and right is not the closer, then push the previous
// state. If the token following the opener is on the next line, then this is
// an open form. If the tokens are on different lines, then it is a closed for.
// Open form is more readable, with each item (statement, argument, parameter,
// etc) starting on its own line. Closed form is more compact. Statement blocks
// are always in open form.

                var new_closer = opener[left.id];
                if (typeof new_closer === 'string') {
                    if (new_closer !== right.id) {
                        stack.push({
                            closer: closer,
                            free: free,
                            margin: margin,
                            open: open,
                            qmark: qmark
                        });
                        qmark = '';
                        closer = new_closer;
                        if (left.line !== right.line) {
                            free = (closer === ')' && left.free) || closer === ']';
                            open = true;
                            margin += 4;
                            if (right.role === 'label') {
                                if (right.from !== 0) {
                                    expected_at(0);
                                }
                            } else if (right.switch) {
                                unqmark();
                                at_margin(-4);
                            } else {
                                at_margin(0);
                            }
                        } else {
                            if (right.statement || right.role === 'label') {
                                warn(
                                    'expected_line_break_a_b',
                                    right,
                                    artifact(left),
                                    artifact(right)
                                );
                            }
                            free = false;
                            open = false;
                            no_space_only();
                        }
                    } else {

// If left and right are opener and closer, then the placement of right depends
// on the openness. Illegal pairs (like {]) have already been detected.

                        if (left.line === right.line) {
                            no_space();
                        } else {
                            at_margin(0);
                        }
                    }
                } else {

// If right is a closer, then pop the previous state,

                    if (right.id === closer) {
                        var previous = stack.pop();
                        margin = previous.margin;
                        if (open && right.id !== ';') {
                            at_margin(0);
                        } else {
                            no_space_only();
                        }
                        closer = previous.closer;
                        free = previous.free;
                        open = previous.open;
                        qmark = previous.qmark;
                    } else {

// Left is not an opener, and right is not a closer. The nature of left and
// right will determine the space between them.

// If left is , or ; or right is a statement then if open, right must go at the
// margin, or if closed, a space before.


                        if (right.switch) {
                            unqmark();
                            at_margin(-4);
                        } else if (right.role === 'label') {
                            if (right.from !== 0) {
                                expected_at(0);
                            }
                        } else if (left.id === ',') {
                            unqmark();
                            if (!open || (free && left.line === right.line)) {
                                one_space();
                            } else {
                                at_margin(0);
                            }

// If right is a ternary operator, line it up on the margin. Use qmark to
// deal with nested ternary operators.

                        } else if (right.arity === 'ternary') {
                            if (right.id === '?') {
                                margin += 4;
                                qmark += '?';
                            } else {
                                result = qmark.match(rx_colons);
                                qmark = result[1] + ':';
                                margin -= 4 * result[2].length;
                            }
                            at_margin(0);
                        } else if (
                            left.id === '...' ||
                            right.id === ',' ||
                            right.id === ';' ||
                            right.id === ':' ||
                            (right.arity === 'binary' && (
                                right.id === '(' ||
                                right.id === '['
                            )) ||
                            (right.arity === 'function' && left.id !== 'function')
                        ) {
                            no_space_only();
                        } else if (left.id === '.') {
                            no_space();
                        } else if (right.id === '.') {
                            if (left.line === right.line) {
                                no_space();
                            } else {
                                if (!rx_dot.test(qmark)) {
                                    qmark += '.';
                                    margin += 4;
                                }
                                at_margin(0);
                            }
                        } else if (left.id === ';') {
                            unqmark();
                            if (open) {
                                at_margin(0);
                            } else {
                                one_space();
                            }
                        } else if (
                            left.arity === 'ternary' ||
                            left.id === 'case' ||
                            left.id === 'catch' ||
                            left.id === 'else' ||
                            left.id === 'finally' ||
                            left.id === 'while' ||
                            right.id === 'catch' ||
                            right.id === 'else' ||
                            right.id === 'finally' ||
                            (right.id === 'while' && !right.statement) ||
                            (left.id === ')' && right.id === '{')
                        ) {
                            one_space_only();
                        } else if (right.statement === true) {
                            if (open) {
                                at_margin(0);
                            } else {
                                one_space();
                            }
                        } else if (
                            left.id === 'var' ||
                            left.id === 'const' ||
                            left.id === 'let'
                        ) {
                            stack.push({
                                closer: closer,
                                free: free,
                                margin: margin,
                                open: open,
                                qmark: qmark
                            });
                            closer = ';';
                            free = false;
                            open = left.open;
                            qmark = '';
                            if (open) {
                                margin = margin + 4;
                                at_margin(0);
                            } else {
                                one_space_only();
                            }
                        } else if (

// There is a space between left and right.

                            spaceop[left.id] === true ||
                            spaceop[right.id] === true ||
                            (
                                left.arity === 'binary' &&
                                (left.id === '+' || left.id === '-')
                            ) ||
                            (
                                right.arity === 'binary' &&
                                (right.id === '+' || right.id === '-')
                            ) ||
                            left.id === 'function' ||
                            left.id === ':' ||
                            (
                                (
                                    left.identifier ||
                                    left.id === '(string)' ||
                                    left.id === '(number)'
                                ) &&
                                (
                                    right.identifier ||
                                    right.id === '(string)' ||
                                    right.id === '(number)'
                                )
                            ) ||
                            (left.arity === 'statement' && right.id !== ';')
                        ) {
                            one_space();
                        } else if (left.arity === 'unary') {
                            no_space_only();
                        }
                    }
                }
                nr_comments_skipped = 0;
                delete left.calls;
                delete left.dead;
                delete left.free;
                delete left.init;
                delete left.open;
                delete left.used;
                left = right;
            }
        });
    }

// The jslint function itself.

    return function jslint(source, option_object, global_array) {
        try {
            warnings = [];
            option = option_object || empty();
            anon = "anonymous";
            block_stack = [];
            declared_globals = empty();
            directive_mode = true;
            early_stop = true;
            export_mode = true;
            fudge = option.fudge
                ? 1
                : 0;
            functions = [];
            global = {
                id: '(global)',
                body: true,
                context: empty(),
                from: 0,
                level: 0,
                line: 0,
                live: [],
                loop: 0,
                switch: 0,
                thru: 0
            };
            blockage = global;
            functionage = global;
            imports = [];
            json_mode = false;
            mega_mode = false;
            module_mode = false;
            next_token = global;
            property = empty();
            stack = [];
            tenure = undefined;
            token = global;
            token_nr = 0;
            var_mode = undefined;
            populate(declared_globals, standard, false);
            if (global_array !== undefined) {
                populate(declared_globals, global_array, false);
            }
            Object.keys(option).forEach(function (name) {
                if (option[name] === true) {
                    var allowed = allowed_option[name];
                    if (Array.isArray(allowed)) {
                        populate(declared_globals, allowed, false);
                    }
                }
            });
            tokenize(source);
            advance();
            if (tokens[0].id === '{' || tokens[0].id === '[') {
                json_mode = true;
                tree = json_value();
                advance('(end)');
            } else {

// Because browsers encourage combining of script files, the first token might
// be a semicolon to defend against a missing semicolon in the preceding file.

                if (option.browser) {
                    if (next_token.id === ';') {
                        advance(';');
                    }
                } else {

// If we are not in a browser, then the file form of strict pragma may be used.

                    if (
                        next_token.id === '(string)' &&
                        next_token.value === 'use strict'
                    ) {
                        advance('(string)');
                        advance(';');
                        global.strict = true;
                    }
                }
                tree = statements();
                advance('(end)');
                functionage = global;
                walk_statement(tree);
                uninitialized_and_unused();
                if (!option.white) {
                    whitage();
                }
            }
            early_stop = false;
        } catch (e) {
            if (e.name !== 'JSLintError') {
                warnings.push(e);
            }
        }
        return {
            functions: functions,
            global: global,
            id: "(JSLint)",
            imports: imports,
            json: json_mode,
            lines: lines,
            module: module_mode === true,
            ok: warnings.length === 0 && !early_stop,
            option: option,
            property: property,
            stop: early_stop,
            tokens: tokens,
            tree: tree,
            warnings: warnings.sort(function (a, b) {
                return a.line - b.line || a.column - b.column;
            }),
            edition: "2015-06-11"
        };
    };
}());