text: /*
    Copyright (C) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com>
    Copyright (C) 2012-2013 Michael Ficarra <escodegen.copyright@michael.ficarra.me>
    Copyright (C) 2012-2013 Mathias Bynens <mathias@qiwi.be>
    Copyright (C) 2013 Irakli Gozalishvili <rfobic@gmail.com>
    Copyright (C) 2012 Robert Gust-Bardon <donate@robert.gust-bardon.org>
    Copyright (C) 2012 John Freeman <jfreeman08@gmail.com>
    Copyright (C) 2011-2012 Ariya Hidayat <ariya.hidayat@gmail.com>
    Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
    Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com>
    Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
  
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:
  
      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
  
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  
  /*global exports:true, generateStatement:true, generateExpression:true, require:true, global:true*/
  (function () {
      'use strict';
  
      var Syntax,
          Precedence,
          BinaryPrecedence,
          SourceNode,
          estraverse,
          esutils,
          isArray,
          base,
          indent,
          json,
          renumber,
          hexadecimal,
          quotes,
          escapeless,
          newline,
          space,
          parentheses,
          semicolons,
          safeConcatenation,
          directive,
          extra,
          parse,
          sourceMap,
          FORMAT_MINIFY,
          FORMAT_DEFAULTS;
  
      estraverse = require('estraverse');
      esutils = require('esutils');
  
      Syntax = {
          AssignmentExpression: 'AssignmentExpression',
          ArrayExpression: 'ArrayExpression',
          ArrayPattern: 'ArrayPattern',
          ArrowFunctionExpression: 'ArrowFunctionExpression',
          BlockStatement: 'BlockStatement',
          BinaryExpression: 'BinaryExpression',
          BreakStatement: 'BreakStatement',
          CallExpression: 'CallExpression',
          CatchClause: 'CatchClause',
          ComprehensionBlock: 'ComprehensionBlock',
          ComprehensionExpression: 'ComprehensionExpression',
          ConditionalExpression: 'ConditionalExpression',
          ContinueStatement: 'ContinueStatement',
          DirectiveStatement: 'DirectiveStatement',
          DoWhileStatement: 'DoWhileStatement',
          DebuggerStatement: 'DebuggerStatement',
          EmptyStatement: 'EmptyStatement',
          ExpressionStatement: 'ExpressionStatement',
          ForStatement: 'ForStatement',
          ForInStatement: 'ForInStatement',
          FunctionDeclaration: 'FunctionDeclaration',
          FunctionExpression: 'FunctionExpression',
          Identifier: 'Identifier',
          IfStatement: 'IfStatement',
          Literal: 'Literal',
          LabeledStatement: 'LabeledStatement',
          LogicalExpression: 'LogicalExpression',
          MemberExpression: 'MemberExpression',
          NewExpression: 'NewExpression',
          ObjectExpression: 'ObjectExpression',
          ObjectPattern: 'ObjectPattern',
          Program: 'Program',
          Property: 'Property',
          ReturnStatement: 'ReturnStatement',
          SequenceExpression: 'SequenceExpression',
          SwitchStatement: 'SwitchStatement',
          SwitchCase: 'SwitchCase',
          ThisExpression: 'ThisExpression',
          ThrowStatement: 'ThrowStatement',
          TryStatement: 'TryStatement',
          UnaryExpression: 'UnaryExpression',
          UpdateExpression: 'UpdateExpression',
          VariableDeclaration: 'VariableDeclaration',
          VariableDeclarator: 'VariableDeclarator',
          WhileStatement: 'WhileStatement',
          WithStatement: 'WithStatement',
          YieldExpression: 'YieldExpression'
  
      };
  
      Precedence = {
          Sequence: 0,
          Yield: 1,
          Assignment: 1,
          Conditional: 2,
          ArrowFunction: 2,
          LogicalOR: 3,
          LogicalAND: 4,
          BitwiseOR: 5,
          BitwiseXOR: 6,
          BitwiseAND: 7,
          Equality: 8,
          Relational: 9,
          BitwiseSHIFT: 10,
          Additive: 11,
          Multiplicative: 12,
          Unary: 13,
          Postfix: 14,
          Call: 15,
          New: 16,
          Member: 17,
          Primary: 18
      };
  
      BinaryPrecedence = {
          '||': Precedence.LogicalOR,
          '&&': Precedence.LogicalAND,
          '|': Precedence.BitwiseOR,
          '^': Precedence.BitwiseXOR,
          '&': Precedence.BitwiseAND,
          '==': Precedence.Equality,
          '!=': Precedence.Equality,
          '===': Precedence.Equality,
          '!==': Precedence.Equality,
          'is': Precedence.Equality,
          'isnt': Precedence.Equality,
          '<': Precedence.Relational,
          '>': Precedence.Relational,
          '<=': Precedence.Relational,
          '>=': Precedence.Relational,
          'in': Precedence.Relational,
          'instanceof': Precedence.Relational,
          '<<': Precedence.BitwiseSHIFT,
          '>>': Precedence.BitwiseSHIFT,
          '>>>': Precedence.BitwiseSHIFT,
          '+': Precedence.Additive,
          '-': Precedence.Additive,
          '*': Precedence.Multiplicative,
          '%': Precedence.Multiplicative,
          '/': Precedence.Multiplicative
      };
  
      function getDefaultOptions() {
          // default options
          return {
              indent: null,
              base: null,
              parse: null,
              comment: false,
              format: {
                  indent: {
                      style: '    ',
                      base: 0,
                      adjustMultilineComment: false
                  },
                  newline: '\n',
                  space: ' ',
                  json: false,
                  renumber: false,
                  hexadecimal: false,
                  quotes: 'single',
                  escapeless: false,
                  compact: false,
                  parentheses: true,
                  semicolons: true,
                  safeConcatenation: false
              },
              moz: {
                  starlessGenerator: false,
                  parenthesizedComprehensionBlock: false
              },
              sourceMap: null,
              sourceMapRoot: null,
              sourceMapWithCode: false,
              directive: false,
              verbatim: null
          };
      }
  
      function stringRepeat(str, num) {
          var result = '';
  
          for (num |= 0; num > 0; num >>>= 1, str += str) {
              if (num & 1) {
                  result += str;
              }
          }
  
          return result;
      }
  
      isArray = Array.isArray;
      if (!isArray) {
          isArray = function isArray(array) {
              return Object.prototype.toString.call(array) === '[object Array]';
          };
      }
  
      // Fallback for the non SourceMap environment
      function SourceNodeMock(line, column, filename, chunk) {
          var result = [];
  
          function flatten(input) {
              var i, iz;
              if (isArray(input)) {
                  for (i = 0, iz = input.length; i < iz; ++i) {
                      flatten(input[i]);
                  }
              } else if (input instanceof SourceNodeMock) {
                  result.push(input);
              } else if (typeof input === 'string' && input) {
                  result.push(input);
              }
          }
  
          flatten(chunk);
          this.children = result;
      }
  
      SourceNodeMock.prototype.toString = function toString() {
          var res = '', i, iz, node;
          for (i = 0, iz = this.children.length; i < iz; ++i) {
              node = this.children[i];
              if (node instanceof SourceNodeMock) {
                  res += node.toString();
              } else {
                  res += node;
              }
          }
          return res;
      };
  
      SourceNodeMock.prototype.replaceRight = function replaceRight(pattern, replacement) {
          var last = this.children[this.children.length - 1];
          if (last instanceof SourceNodeMock) {
              last.replaceRight(pattern, replacement);
          } else if (typeof last === 'string') {
              this.children[this.children.length - 1] = last.replace(pattern, replacement);
          } else {
              this.children.push(''.replace(pattern, replacement));
          }
          return this;
      };
  
      SourceNodeMock.prototype.join = function join(sep) {
          var i, iz, result;
          result = [];
          iz = this.children.length;
          if (iz > 0) {
              --iz;
              for (i = 0; i < iz; ++i) {
                  result.push(this.children[i], sep);
              }
              result.push(this.children[iz]);
              this.children = result;
          }
          return this;
      };
  
      function hasLineTerminator(str) {
          return (/[\r\n]/g).test(str);
      }
  
      function endsWithLineTerminator(str) {
          var len = str.length;
          return len && esutils.code.isLineTerminator(str.charCodeAt(len - 1));
      }
  
      function updateDeeply(target, override) {
          var key, val;
  
          function isHashObject(target) {
              return typeof target === 'object' && target instanceof Object && !(target instanceof RegExp);
          }
  
          for (key in override) {
              if (override.hasOwnProperty(key)) {
                  val = override[key];
                  if (isHashObject(val)) {
                      if (isHashObject(target[key])) {
                          updateDeeply(target[key], val);
                      } else {
                          target[key] = updateDeeply({}, val);
                      }
                  } else {
                      target[key] = val;
                  }
              }
          }
          return target;
      }
  
      function generateNumber(value) {
          var result, point, temp, exponent, pos;
  
          if (value !== value) {
              throw new Error('Numeric literal whose value is NaN');
          }
          if (value < 0 || (value === 0 && 1 / value < 0)) {
              throw new Error('Numeric literal whose value is negative');
          }
  
          if (value === 1 / 0) {
              return json ? 'null' : renumber ? '1e400' : '1e+400';
          }
  
          result = '' + value;
          if (!renumber || result.length < 3) {
              return result;
          }
  
          point = result.indexOf('.');
          if (!json && result.charCodeAt(0) === 0x30  /* 0 */ && point === 1) {
              point = 0;
              result = result.slice(1);
          }
          temp = result;
          result = result.replace('e+', 'e');
          exponent = 0;
          if ((pos = temp.indexOf('e')) > 0) {
              exponent = +temp.slice(pos + 1);
              temp = temp.slice(0, pos);
          }
          if (point >= 0) {
              exponent -= temp.length - point - 1;
              temp = +(temp.slice(0, point) + temp.slice(point + 1)) + '';
          }
          pos = 0;
          while (temp.charCodeAt(temp.length + pos - 1) === 0x30  /* 0 */) {
              --pos;
          }
          if (pos !== 0) {
              exponent -= pos;
              temp = temp.slice(0, pos);
          }
          if (exponent !== 0) {
              temp += 'e' + exponent;
          }
          if ((temp.length < result.length ||
                      (hexadecimal && value > 1e12 && Math.floor(value) === value && (temp = '0x' + value.toString(16)).length < result.length)) &&
                  +temp === value) {
              result = temp;
          }
  
          return result;
      }
  
      // Generate valid RegExp expression.
      // This function is based on https://github.com/Constellation/iv Engine
  
      function escapeRegExpCharacter(ch, previousIsBackslash) {
          // not handling '\' and handling \u2028 or \u2029 to unicode escape sequence
          if ((ch & ~1) === 0x2028) {
              return (previousIsBackslash ? 'u' : '\\u') + ((ch === 0x2028) ? '2028' : '2029');
          } else if (ch === 10 || ch === 13) {  // \n, \r
              return (previousIsBackslash ? '' : '\\') + ((ch === 10) ? 'n' : 'r');
          }
          return String.fromCharCode(ch);
      }
  
      function generateRegExp(reg) {
          var match, result, flags, i, iz, ch, characterInBrack, previousIsBackslash;
  
          result = reg.toString();
  
          if (reg.source) {
              // extract flag from toString result
              match = result.match(/\/([^/]*)$/);
              if (!match) {
                  return result;
              }
  
              flags = match[1];
              result = '';
  
              characterInBrack = false;
              previousIsBackslash = false;
              for (i = 0, iz = reg.source.length; i < iz; ++i) {
                  ch = reg.source.charCodeAt(i);
  
                  if (!previousIsBackslash) {
                      if (characterInBrack) {
                          if (ch === 93) {  // ]
                              characterInBrack = false;
                          }
                      } else {
                          if (ch === 47) {  // /
                              result += '\\';
                          } else if (ch === 91) {  // [
                              characterInBrack = true;
                          }
                      }
                      result += escapeRegExpCharacter(ch, previousIsBackslash);
                      previousIsBackslash = ch === 92;  // \
                  } else {
                      // if new RegExp("\\\n') is provided, create /\n/
                      result += escapeRegExpCharacter(ch, previousIsBackslash);
                      // prevent like /\\[/]/
                      previousIsBackslash = false;
                  }
              }
  
              return '/' + result + '/' + flags;
          }
  
          return result;
      }
  
      function escapeAllowedCharacter(code, next) {
          var hex, result = '\\';
  
          switch (code) {
          case 0x08  /* \b */:
              result += 'b';
              break;
          case 0x0C  /* \f */:
              result += 'f';
              break;
          case 0x09  /* \t */:
              result += 't';
              break;
          default:
              hex = code.toString(16).toUpperCase();
              if (json || code > 0xFF) {
                  result += 'u' + '0000'.slice(hex.length) + hex;
              } else if (code === 0x0000 && !esutils.code.isDecimalDigit(next)) {
                  result += '0';
              } else if (code === 0x000B  /* \v */) { // '\v'
                  result += 'x0B';
              } else {
                  result += 'x' + '00'.slice(hex.length) + hex;
              }
              break;
          }
  
          return result;
      }
  
      function escapeDisallowedCharacter(code) {
          var result = '\\';
          switch (code) {
          case 0x5C  /* \ */:
              result += '\\';
              break;
          case 0x0A  /* \n */:
              result += 'n';
              break;
          case 0x0D  /* \r */:
              result += 'r';
              break;
          case 0x2028:
              result += 'u2028';
              break;
          case 0x2029:
              result += 'u2029';
              break;
          default:
              throw new Error('Incorrectly classified character');
          }
  
          return result;
      }
  
      function escapeDirective(str) {
          var i, iz, code, quote;
  
          quote = quotes === 'double' ? '"' : '\'';
          for (i = 0, iz = str.length; i < iz; ++i) {
              code = str.charCodeAt(i);
              if (code === 0x27  /* ' */) {
                  quote = '"';
                  break;
              } else if (code === 0x22  /* " */) {
                  quote = '\'';
                  break;
              } else if (code === 0x5C  /* \ */) {
                  ++i;
              }
          }
  
          return quote + str + quote;
      }
  
      function escapeString(str) {
          var result = '', i, len, code, singleQuotes = 0, doubleQuotes = 0, single, quote;
  
          for (i = 0, len = str.length; i < len; ++i) {
              code = str.charCodeAt(i);
              if (code === 0x27  /* ' */) {
                  ++singleQuotes;
              } else if (code === 0x22  /* " */) {
                  ++doubleQuotes;
              } else if (code === 0x2F  /* / */ && json) {
                  result += '\\';
              } else if (esutils.code.isLineTerminator(code) || code === 0x5C  /* \ */) {
                  result += escapeDisallowedCharacter(code);
                  continue;
              } else if ((json && code < 0x20  /* SP */) || !(json || escapeless || (code >= 0x20  /* SP */ && code <= 0x7E  /* ~ */))) {
                  result += escapeAllowedCharacter(code, str.charCodeAt(i + 1));
                  continue;
              }
              result += String.fromCharCode(code);
          }
  
          single = !(quotes === 'double' || (quotes === 'auto' && doubleQuotes < singleQuotes));
          quote = single ? '\'' : '"';
  
          if (!(single ? singleQuotes : doubleQuotes)) {
              return quote + result + quote;
          }
  
          str = result;
          result = quote;
  
          for (i = 0, len = str.length; i < len; ++i) {
              code = str.charCodeAt(i);
              if ((code === 0x27  /* ' */ && single) || (code === 0x22  /* " */ && !single)) {
                  result += '\\';
              }
              result += String.fromCharCode(code);
          }
  
          return result + quote;
      }
  
      function toSourceNode(generated, node) {
          if (node == null) {
              if (generated instanceof SourceNode) {
                  return generated;
              } else {
                  node = {};
              }
          }
          if (node.loc == null) {
              return new SourceNode(null, null, sourceMap, generated, node.name || null);
          }
          return new SourceNode(node.loc.start.line, node.loc.start.column, (sourceMap === true ? node.loc.source || null : sourceMap), generated, node.name || null);
      }
  
      function noEmptySpace() {
          return (space) ? space : ' ';
      }
  
      function join(left, right) {
          var leftSource = toSourceNode(left).toString(),
              rightSource = toSourceNode(right).toString(),
              leftCharCode = leftSource.charCodeAt(leftSource.length - 1),
              rightCharCode = rightSource.charCodeAt(0);
  
          if ((leftCharCode === 0x2B  /* + */ || leftCharCode === 0x2D  /* - */) && leftCharCode === rightCharCode ||
          esutils.code.isIdentifierPart(leftCharCode) && esutils.code.isIdentifierPart(rightCharCode) ||
          leftCharCode === 0x2F  /* / */ && rightCharCode === 0x69  /* i */) { // infix word operators all start with `i`
              return [left, noEmptySpace(), right];
          } else if (esutils.code.isWhiteSpace(leftCharCode) || esutils.code.isLineTerminator(leftCharCode) ||
                  esutils.code.isWhiteSpace(rightCharCode) || esutils.code.isLineTerminator(rightCharCode)) {
              return [left, right];
          }
          return [left, space, right];
      }
  
      function addIndent(stmt) {
          return [base, stmt];
      }
  
      function withIndent(fn) {
          var previousBase, result;
          previousBase = base;
          base += indent;
          result = fn.call(this, base);
          base = previousBase;
          return result;
      }
  
      function calculateSpaces(str) {
          var i;
          for (i = str.length - 1; i >= 0; --i) {
              if (esutils.code.isLineTerminator(str.charCodeAt(i))) {
                  break;
              }
          }
          return (str.length - 1) - i;
      }
  
      function adjustMultilineComment(value, specialBase) {
          var array, i, len, line, j, spaces, previousBase;
  
          array = value.split(/\r\n|[\r\n]/);
          spaces = Number.MAX_VALUE;
  
          // first line doesn't have indentation
          for (i = 1, len = array.length; i < len; ++i) {
              line = array[i];
              j = 0;
              while (j < line.length && esutils.code.isWhiteSpace(line.charCodeAt(j))) {
                  ++j;
              }
              if (spaces > j) {
                  spaces = j;
              }
          }
  
          if (typeof specialBase !== 'undefined') {
              // pattern like
              // {
              //   var t = 20;  /*
              //                 * this is comment
              //                 */
              // }
              previousBase = base;
              if (array[1][spaces] === '*') {
                  specialBase += ' ';
              }
              base = specialBase;
          } else {
              if (spaces & 1) {
                  // /*
                  //  *
                  //  */
                  // If spaces are odd number, above pattern is considered.
                  // We waste 1 space.
                  --spaces;
              }
              previousBase = base;
          }
  
          for (i = 1, len = array.length; i < len; ++i) {
              array[i] = toSourceNode(addIndent(array[i].slice(spaces))).join('');
          }
  
          base = previousBase;
  
          return array.join('\n');
      }
  
      function generateComment(comment, specialBase) {
          if (comment.type === 'Line') {
              if (endsWithLineTerminator(comment.value)) {
                  return '//' + comment.value;
              } else {
                  // Always use LineTerminator
                  return '//' + comment.value + '\n';
              }
          }
          if (extra.format.indent.adjustMultilineComment && /[\n\r]/.test(comment.value)) {
              return adjustMultilineComment('/*' + comment.value + '*/', specialBase);
          }
          return '/*' + comment.value + '*/';
      }
  
      function addCommentsToStatement(stmt, result) {
          var i, len, comment, save, tailingToStatement, specialBase, fragment;
  
          if (stmt.leadingComments && stmt.leadingComments.length > 0) {
              save = result;
  
              comment = stmt.leadingComments[0];
              result = [];
              if (safeConcatenation && stmt.type === Syntax.Program && stmt.body.length === 0) {
                  result.push('\n');
              }
              result.push(generateComment(comment));
              if (!endsWithLineTerminator(toSourceNode(result).toString())) {
                  result.push('\n');
              }
  
              for (i = 1, len = stmt.leadingComments.length; i < len; ++i) {
                  comment = stmt.leadingComments[i];
                  fragment = [generateComment(comment)];
                  if (!endsWithLineTerminator(toSourceNode(fragment).toString())) {
                      fragment.push('\n');
                  }
                  result.push(addIndent(fragment));
              }
  
              result.push(addIndent(save));
          }
  
          if (stmt.trailingComments) {
              tailingToStatement = !endsWithLineTerminator(toSourceNode(result).toString());
              specialBase = stringRepeat(' ', calculateSpaces(toSourceNode([base, result, indent]).toString()));
              for (i = 0, len = stmt.trailingComments.length; i < len; ++i) {
                  comment = stmt.trailingComments[i];
                  if (tailingToStatement) {
                      // We assume target like following script
                      //
                      // var t = 20;  /**
                      //               * This is comment of t
                      //               */
                      if (i === 0) {
                          // first case
                          result = [result, indent];
                      } else {
                          result = [result, specialBase];
                      }
                      result.push(generateComment(comment, specialBase));
                  } else {
                      result = [result, addIndent(generateComment(comment))];
                  }
                  if (i !== len - 1 && !endsWithLineTerminator(toSourceNode(result).toString())) {
                      result = [result, '\n'];
                  }
              }
          }
  
          return result;
      }
  
      function parenthesize(text, current, should) {
          if (current < should) {
              return ['(', text, ')'];
          }
          return text;
      }
  
      function maybeBlock(stmt, semicolonOptional, functionBody) {
          var result, noLeadingComment;
  
          noLeadingComment = !extra.comment || !stmt.leadingComments;
  
          if (stmt.type === Syntax.BlockStatement && noLeadingComment) {
              return [space, generateStatement(stmt, { functionBody: functionBody })];
          }
  
          if (stmt.type === Syntax.EmptyStatement && noLeadingComment) {
              return ';';
          }
  
          withIndent(function () {
              result = [newline, addIndent(generateStatement(stmt, { semicolonOptional: semicolonOptional, functionBody: functionBody }))];
          });
  
          return result;
      }
  
      function maybeBlockSuffix(stmt, result) {
          var ends = endsWithLineTerminator(toSourceNode(result).toString());
          if (stmt.type === Syntax.BlockStatement && (!extra.comment || !stmt.leadingComments) && !ends) {
              return [result, space];
          }
          if (ends) {
              return [result, base];
          }
          return [result, newline, base];
      }
  
      function generateVerbatim(expr, option) {
          var i, result;
          result = expr[extra.verbatim].split(/\r\n|\n/);
          for (i = 1; i < result.length; i++) {
              result[i] = newline + base + result[i];
          }
  
          result = parenthesize(result, Precedence.Sequence, option.precedence);
          return toSourceNode(result, expr);
      }
  
      function generateIdentifier(node) {
          return toSourceNode(node.name, node);
      }
  
      function generateFunctionBody(node) {
          var result, i, len, expr, arrow;
  
          arrow = node.type === Syntax.ArrowFunctionExpression;
  
          if (arrow && node.params.length === 1 && node.params[0].type === Syntax.Identifier) {
              // arg => { } case
              result = [generateIdentifier(node.params[0])];
          } else {
              result = ['('];
              for (i = 0, len = node.params.length; i < len; ++i) {
                  result.push(generateIdentifier(node.params[i]));
                  if (i + 1 < len) {
                      result.push(',' + space);
                  }
              }
              result.push(')');
          }
  
          if (arrow) {
              result.push(space, '=>');
          }
  
          if (node.expression) {
              result.push(space);
              expr = generateExpression(node.body, {
                  precedence: Precedence.Assignment,
                  allowIn: true,
                  allowCall: true
              });
              if (expr.toString().charAt(0) === '{') {
                  expr = ['(', expr, ')'];
              }
              result.push(expr);
          } else {
              result.push(maybeBlock(node.body, false, true));
          }
          return result;
      }
  
      function generateExpression(expr, option) {
          var result,
              precedence,
              type,
              currentPrecedence,
              i,
              len,
              raw,
              fragment,
              multiline,
              leftCharCode,
              leftSource,
              rightCharCode,
              allowIn,
              allowCall,
              allowUnparenthesizedNew,
              property;
  
          precedence = option.precedence;
          allowIn = option.allowIn;
          allowCall = option.allowCall;
          type = expr.type || option.type;
  
          if (extra.verbatim && expr.hasOwnProperty(extra.verbatim)) {
              return generateVerbatim(expr, option);
          }
  
          switch (type) {
          case Syntax.SequenceExpression:
              result = [];
              allowIn |= (Precedence.Sequence < precedence);
              for (i = 0, len = expr.expressions.length; i < len; ++i) {
                  result.push(generateExpression(expr.expressions[i], {
                      precedence: Precedence.Assignment,
                      allowIn: allowIn,
                      allowCall: true
                  }));
                  if (i + 1 < len) {
                      result.push(',' + space);
                  }
              }
              result = parenthesize(result, Precedence.Sequence, precedence);
              break;
  
          case Syntax.AssignmentExpression:
              allowIn |= (Precedence.Assignment < precedence);
              result = parenthesize(
                  [
                      generateExpression(expr.left, {
                          precedence: Precedence.Call,
                          allowIn: allowIn,
                          allowCall: true
                      }),
                      space + expr.operator + space,
                      generateExpression(expr.right, {
                          precedence: Precedence.Assignment,
                          allowIn: allowIn,
                          allowCall: true
                      })
                  ],
                  Precedence.Assignment,
                  precedence
              );
              break;
  
          case Syntax.ArrowFunctionExpression:
              allowIn |= (Precedence.ArrowFunction < precedence);
              result = parenthesize(generateFunctionBody(expr), Precedence.ArrowFunction, precedence);
              break;
  
          case Syntax.ConditionalExpression:
              allowIn |= (Precedence.Conditional < precedence);
              result = parenthesize(
                  [
                      generateExpression(expr.test, {
                          precedence: Precedence.LogicalOR,
                          allowIn: allowIn,
                          allowCall: true
                      }),
                      space + '?' + space,
                      generateExpression(expr.consequent, {
                          precedence: Precedence.Assignment,
                          allowIn: allowIn,
                          allowCall: true
                      }),
                      space + ':' + space,
                      generateExpression(expr.alternate, {
                          precedence: Precedence.Assignment,
                          allowIn: allowIn,
                          allowCall: true
                      })
                  ],
                  Precedence.Conditional,
                  precedence
              );
              break;
  
          case Syntax.LogicalExpression:
          case Syntax.BinaryExpression:
              currentPrecedence = BinaryPrecedence[expr.operator];
  
              allowIn |= (currentPrecedence < precedence);
  
              fragment = generateExpression(expr.left, {
                  precedence: currentPrecedence,
                  allowIn: allowIn,
                  allowCall: true
              });
  
              leftSource = fragment.toString();
  
              if (leftSource.charCodeAt(leftSource.length - 1) === 0x2F /* / */ && esutils.code.isIdentifierPart(expr.operator.charCodeAt(0))) {
                  result = [fragment, noEmptySpace(), expr.operator];
              } else {
                  result = join(fragment, expr.operator);
              }
  
              fragment = generateExpression(expr.right, {
                  precedence: currentPrecedence + 1,
                  allowIn: allowIn,
                  allowCall: true
              });
  
              if (expr.operator === '/' && fragment.toString().charAt(0) === '/' ||
              expr.operator.slice(-1) === '<' && fragment.toString().slice(0, 3) === '!--') {
                  // If '/' concats with '/' or `<` concats with `!--`, it is interpreted as comment start
                  result.push(noEmptySpace(), fragment);
              } else {
                  result = join(result, fragment);
              }
  
              if (expr.operator === 'in' && !allowIn) {
                  result = ['(', result, ')'];
              } else {
                  result = parenthesize(result, currentPrecedence, precedence);
              }
  
              break;
  
          case Syntax.CallExpression:
              result = [generateExpression(expr.callee, {
                  precedence: Precedence.Call,
                  allowIn: true,
                  allowCall: true,
                  allowUnparenthesizedNew: false
              })];
  
              result.push('(');
              for (i = 0, len = expr['arguments'].length; i < len; ++i) {
                  result.push(generateExpression(expr['arguments'][i], {
                      precedence: Precedence.Assignment,
                      allowIn: true,
                      allowCall: true
                  }));
                  if (i + 1 < len) {
                      result.push(',' + space);
                  }
              }
              result.push(')');
  
              if (!allowCall) {
                  result = ['(', result, ')'];
              } else {
                  result = parenthesize(result, Precedence.Call, precedence);
              }
              break;
  
          case Syntax.NewExpression:
              len = expr['arguments'].length;
              allowUnparenthesizedNew = option.allowUnparenthesizedNew === undefined || option.allowUnparenthesizedNew;
  
              result = join(
                  'new',
                  generateExpression(expr.callee, {
                      precedence: Precedence.New,
                      allowIn: true,
                      allowCall: false,
                      allowUnparenthesizedNew: allowUnparenthesizedNew && !parentheses && len === 0
                  })
              );
  
              if (!allowUnparenthesizedNew || parentheses || len > 0) {
                  result.push('(');
                  for (i = 0; i < len; ++i) {
                      result.push(generateExpression(expr['arguments'][i], {
                          precedence: Precedence.Assignment,
                          allowIn: true,
                          allowCall: true
                      }));
                      if (i + 1 < len) {
                          result.push(',' + space);
                      }
                  }
                  result.push(')');
              }
  
              result = parenthesize(result, Precedence.New, precedence);
              break;
  
          case Syntax.MemberExpression:
              result = [generateExpression(expr.object, {
                  precedence: Precedence.Call,
                  allowIn: true,
                  allowCall: allowCall,
                  allowUnparenthesizedNew: false
              })];
  
              if (expr.computed) {
                  result.push('[', generateExpression(expr.property, {
                      precedence: Precedence.Sequence,
                      allowIn: true,
                      allowCall: allowCall
                  }), ']');
              } else {
                  if (expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') {
                      fragment = toSourceNode(result).toString();
                      // When the following conditions are all true,
                      //   1. No floating point
                      //   2. Don't have exponents
                      //   3. The last character is a decimal digit
                      //   4. Not hexadecimal OR octal number literal
                      // we should add a floating point.
                      if (
                              fragment.indexOf('.') < 0 &&
                              !/[eExX]/.test(fragment) &&
                              esutils.code.isDecimalDigit(fragment.charCodeAt(fragment.length - 1)) &&
                              !(fragment.length >= 2 && fragment.charCodeAt(0) === 48)  // '0'
                              ) {
                          result.push('.');
                      }
                  }
                  result.push('.', generateIdentifier(expr.property));
              }
  
              result = parenthesize(result, Precedence.Member, precedence);
              break;
  
          case Syntax.UnaryExpression:
              fragment = generateExpression(expr.argument, {
                  precedence: Precedence.Unary,
                  allowIn: true,
                  allowCall: true
              });
  
              if (space === '') {
                  result = join(expr.operator, fragment);
              } else {
                  result = [expr.operator];
                  if (expr.operator.length > 2) {
                      // delete, void, typeof
                      // get `typeof []`, not `typeof[]`
                      result = join(result, fragment);
                  } else {
                      // Prevent inserting spaces between operator and argument if it is unnecessary
                      // like, `!cond`
                      leftSource = toSourceNode(result).toString();
                      leftCharCode = leftSource.charCodeAt(leftSource.length - 1);
                      rightCharCode = fragment.toString().charCodeAt(0);
  
                      if (((leftCharCode === 0x2B  /* + */ || leftCharCode === 0x2D  /* - */) && leftCharCode === rightCharCode) ||
                              (esutils.code.isIdentifierPart(leftCharCode) && esutils.code.isIdentifierPart(rightCharCode))) {
                          result.push(noEmptySpace(), fragment);
                      } else {
                          result.push(fragment);
                      }
                  }
              }
              result = parenthesize(result, Precedence.Unary, precedence);
              break;
  
          case Syntax.YieldExpression:
              if (expr.delegate) {
                  result = 'yield*';
              } else {
                  result = 'yield';
              }
              if (expr.argument) {
                  result = join(
                      result,
                      generateExpression(expr.argument, {
                          precedence: Precedence.Yield,
                          allowIn: true,
                          allowCall: true
                      })
                  );
              }
              result = parenthesize(result, Precedence.Yield, precedence);
              break;
  
          case Syntax.UpdateExpression:
              if (expr.prefix) {
                  result = parenthesize(
                      [
                          expr.operator,
                          generateExpression(expr.argument, {
                              precedence: Precedence.Unary,
                              allowIn: true,
                              allowCall: true
                          })
                      ],
                      Precedence.Unary,
                      precedence
                  );
              } else {
                  result = parenthesize(
                      [
                          generateExpression(expr.argument, {
                              precedence: Precedence.Postfix,
                              allowIn: true,
                              allowCall: true
                          }),
                          expr.operator
                      ],
                      Precedence.Postfix,
                      precedence
                  );
              }
              break;
  
          case Syntax.FunctionExpression:
              result = 'function';
  
              if (expr.id) {
                  result = [result, noEmptySpace(),
                            generateIdentifier(expr.id),
                            generateFunctionBody(expr)];
              } else {
                  result = [result + space, generateFunctionBody(expr)];
              }
  
              break;
  
          case Syntax.ArrayPattern:
          case Syntax.ArrayExpression:
              if (!expr.elements.length) {
                  result = '[]';
                  break;
              }
              multiline = expr.elements.length > 1;
              result = ['[', multiline ? newline : ''];
              withIndent(function (indent) {
                  for (i = 0, len = expr.elements.length; i < len; ++i) {
                      if (!expr.elements[i]) {
                          if (multiline) {
                              result.push(indent);
                          }
                          if (i + 1 === len) {
                              result.push(',');
                          }
                      } else {
                          result.push(multiline ? indent : '', generateExpression(expr.elements[i], {
                              precedence: Precedence.Assignment,
                              allowIn: true,
                              allowCall: true
                          }));
                      }
                      if (i + 1 < len) {
                          result.push(',' + (multiline ? newline : space));
                      }
                  }
              });
              if (multiline && !endsWithLineTerminator(toSourceNode(result).toString())) {
                  result.push(newline);
              }
              result.push(multiline ? base : '', ']');
              break;
  
          case Syntax.Property:
              if (expr.kind === 'get' || expr.kind === 'set') {
                  result = [
                      expr.kind, noEmptySpace(),
                      generateExpression(expr.key, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }),
                      generateFunctionBody(expr.value)
                  ];
              } else {
                  if (expr.shorthand) {
                      result = generateExpression(expr.key, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      });
                  } else if (expr.method) {
                      result = [];
                      if (expr.value.generator) {
                          result.push('*');
                      }
                      result.push(generateExpression(expr.key, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }), generateFunctionBody(expr.value));
                  } else {
                      result = [
                          generateExpression(expr.key, {
                              precedence: Precedence.Sequence,
                              allowIn: true,
                              allowCall: true
                          }),
                          ':' + space,
                          generateExpression(expr.value, {
                              precedence: Precedence.Assignment,
                              allowIn: true,
                              allowCall: true
                          })
                      ];
                  }
              }
              break;
  
          case Syntax.ObjectExpression:
              if (!expr.properties.length) {
                  result = '{}';
                  break;
              }
              multiline = expr.properties.length > 1;
  
              withIndent(function () {
                  fragment = generateExpression(expr.properties[0], {
                      precedence: Precedence.Sequence,
                      allowIn: true,
                      allowCall: true,
                      type: Syntax.Property
                  });
              });
  
              if (!multiline) {
                  // issues 4
                  // Do not transform from
                  //   dejavu.Class.declare({
                  //       method2: function () {}
                  //   });
                  // to
                  //   dejavu.Class.declare({method2: function () {
                  //       }});
                  if (!hasLineTerminator(toSourceNode(fragment).toString())) {
                      result = [ '{', space, fragment, space, '}' ];
                      break;
                  }
              }
  
              withIndent(function (indent) {
                  result = [ '{', newline, indent, fragment ];
  
                  if (multiline) {
                      result.push(',' + newline);
                      for (i = 1, len = expr.properties.length; i < len; ++i) {
                          result.push(indent, generateExpression(expr.properties[i], {
                              precedence: Precedence.Sequence,
                              allowIn: true,
                              allowCall: true,
                              type: Syntax.Property
                          }));
                          if (i + 1 < len) {
                              result.push(',' + newline);
                          }
                      }
                  }
              });
  
              if (!endsWithLineTerminator(toSourceNode(result).toString())) {
                  result.push(newline);
              }
              result.push(base, '}');
              break;
  
          case Syntax.ObjectPattern:
              if (!expr.properties.length) {
                  result = '{}';
                  break;
              }
  
              multiline = false;
              if (expr.properties.length === 1) {
                  property = expr.properties[0];
                  if (property.value.type !== Syntax.Identifier) {
                      multiline = true;
                  }
              } else {
                  for (i = 0, len = expr.properties.length; i < len; ++i) {
                      property = expr.properties[i];
                      if (!property.shorthand) {
                          multiline = true;
                          break;
                      }
                  }
              }
              result = ['{', multiline ? newline : '' ];
  
              withIndent(function (indent) {
                  for (i = 0, len = expr.properties.length; i < len; ++i) {
                      result.push(multiline ? indent : '', generateExpression(expr.properties[i], {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }));
                      if (i + 1 < len) {
                          result.push(',' + (multiline ? newline : space));
                      }
                  }
              });
  
              if (multiline && !endsWithLineTerminator(toSourceNode(result).toString())) {
                  result.push(newline);
              }
              result.push(multiline ? base : '', '}');
              break;
  
          case Syntax.ThisExpression:
              result = 'this';
              break;
  
          case Syntax.Identifier:
              result = generateIdentifier(expr);
              break;
  
          case Syntax.Literal:
              if (expr.hasOwnProperty('raw') && parse) {
                  try {
                      raw = parse(expr.raw).body[0].expression;
                      if (raw.type === Syntax.Literal) {
                          if (raw.value === expr.value) {
                              result = expr.raw;
                              break;
                          }
                      }
                  } catch (e) {
                      // not use raw property
                  }
              }
  
              if (expr.value === null) {
                  result = 'null';
                  break;
              }
  
              if (typeof expr.value === 'string') {
                  result = escapeString(expr.value);
                  break;
              }
  
              if (typeof expr.value === 'number') {
                  result = generateNumber(expr.value);
                  break;
              }
  
              if (typeof expr.value === 'boolean') {
                  result = expr.value ? 'true' : 'false';
                  break;
              }
  
              result = generateRegExp(expr.value);
              break;
  
          case Syntax.ComprehensionExpression:
              result = [
                  '[',
                  generateExpression(expr.body, {
                      precedence: Precedence.Assignment,
                      allowIn: true,
                      allowCall: true
                  })
              ];
  
              if (expr.blocks) {
                  for (i = 0, len = expr.blocks.length; i < len; ++i) {
                      fragment = generateExpression(expr.blocks[i], {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      });
                      result = join(result, fragment);
                  }
              }
  
              if (expr.filter) {
                  result = join(result, 'if' + space);
                  fragment = generateExpression(expr.filter, {
                      precedence: Precedence.Sequence,
                      allowIn: true,
                      allowCall: true
                  });
                  if (extra.moz.parenthesizedComprehensionBlock) {
                      result = join(result, [ '(', fragment, ')' ]);
                  } else {
                      result = join(result, fragment);
                  }
              }
              result.push(']');
              break;
  
          case Syntax.ComprehensionBlock:
              if (expr.left.type === Syntax.VariableDeclaration) {
                  fragment = [
                      expr.left.kind, noEmptySpace(),
                      generateStatement(expr.left.declarations[0], {
                          allowIn: false
                      })
                  ];
              } else {
                  fragment = generateExpression(expr.left, {
                      precedence: Precedence.Call,
                      allowIn: true,
                      allowCall: true
                  });
              }
  
              fragment = join(fragment, expr.of ? 'of' : 'in');
              fragment = join(fragment, generateExpression(expr.right, {
                  precedence: Precedence.Sequence,
                  allowIn: true,
                  allowCall: true
              }));
  
              if (extra.moz.parenthesizedComprehensionBlock) {
                  result = [ 'for' + space + '(', fragment, ')' ];
              } else {
                  result = join('for' + space, fragment);
              }
              break;
  
          default:
              throw new Error('Unknown expression type: ' + expr.type);
          }
  
          return toSourceNode(result, expr);
      }
  
      function generateStatement(stmt, option) {
          var i, len, result, node, allowIn, functionBody, directiveContext, fragment, semicolon;
  
          allowIn = true;
          semicolon = ';';
          functionBody = false;
          directiveContext = false;
          if (option) {
              allowIn = option.allowIn === undefined || option.allowIn;
              if (!semicolons && option.semicolonOptional === true) {
                  semicolon = '';
              }
              functionBody = option.functionBody;
              directiveContext = option.directiveContext;
          }
  
          switch (stmt.type) {
          case Syntax.BlockStatement:
              result = ['{', newline];
  
              withIndent(function () {
                  for (i = 0, len = stmt.body.length; i < len; ++i) {
                      fragment = addIndent(generateStatement(stmt.body[i], {
                          semicolonOptional: i === len - 1,
                          directiveContext: functionBody
                      }));
                      result.push(fragment);
                      if (!endsWithLineTerminator(toSourceNode(fragment).toString())) {
                          result.push(newline);
                      }
                  }
              });
  
              result.push(addIndent('}'));
              break;
  
          case Syntax.BreakStatement:
              if (stmt.label) {
                  result = 'break ' + stmt.label.name + semicolon;
              } else {
                  result = 'break' + semicolon;
              }
              break;
  
          case Syntax.ContinueStatement:
              if (stmt.label) {
                  result = 'continue ' + stmt.label.name + semicolon;
              } else {
                  result = 'continue' + semicolon;
              }
              break;
  
          case Syntax.DirectiveStatement:
              if (stmt.raw) {
                  result = stmt.raw + semicolon;
              } else {
                  result = escapeDirective(stmt.directive) + semicolon;
              }
              break;
  
          case Syntax.DoWhileStatement:
              // Because `do 42 while (cond)` is Syntax Error. We need semicolon.
              result = join('do', maybeBlock(stmt.body));
              result = maybeBlockSuffix(stmt.body, result);
              result = join(result, [
                  'while' + space + '(',
                  generateExpression(stmt.test, {
                      precedence: Precedence.Sequence,
                      allowIn: true,
                      allowCall: true
                  }),
                  ')' + semicolon
              ]);
              break;
  
          case Syntax.CatchClause:
              withIndent(function () {
                  var guard;
  
                  result = [
                      'catch' + space + '(',
                      generateExpression(stmt.param, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }),
                      ')'
                  ];
  
                  if (stmt.guard) {
                      guard = generateExpression(stmt.guard, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      });
  
                      result.splice(2, 0, ' if ', guard);
                  }
              });
              result.push(maybeBlock(stmt.body));
              break;
  
          case Syntax.DebuggerStatement:
              result = 'debugger' + semicolon;
              break;
  
          case Syntax.EmptyStatement:
              result = ';';
              break;
  
          case Syntax.ExpressionStatement:
              result = [generateExpression(stmt.expression, {
                  precedence: Precedence.Sequence,
                  allowIn: true,
                  allowCall: true
              })];
              // 12.4 '{', 'function' is not allowed in this position.
              // wrap expression with parentheses
              fragment = toSourceNode(result).toString();
              if (fragment.charAt(0) === '{' || (fragment.slice(0, 8) === 'function' && ' ('.indexOf(fragment.charAt(8)) >= 0) || (directive && directiveContext && stmt.expression.type === Syntax.Literal && typeof stmt.expression.value === 'string')) {
                  result = ['(', result, ')' + semicolon];
              } else {
                  result.push(semicolon);
              }
              break;
  
          case Syntax.VariableDeclarator:
              if (stmt.init) {
                  result = [
                      generateExpression(stmt.id, {
                          precedence: Precedence.Assignment,
                          allowIn: allowIn,
                          allowCall: true
                      }),
                      space,
                      '=',
                      space,
                      generateExpression(stmt.init, {
                          precedence: Precedence.Assignment,
                          allowIn: allowIn,
                          allowCall: true
                      })
                  ];
              } else {
                  result = generateIdentifier(stmt.id);
              }
              break;
  
          case Syntax.VariableDeclaration:
              result = [stmt.kind];
              // special path for
              // var x = function () {
              // };
              if (stmt.declarations.length === 1 && stmt.declarations[0].init &&
                      stmt.declarations[0].init.type === Syntax.FunctionExpression) {
                  result.push(noEmptySpace(), generateStatement(stmt.declarations[0], {
                      allowIn: allowIn
                  }));
              } else {
                  // VariableDeclarator is typed as Statement,
                  // but joined with comma (not LineTerminator).
                  // So if comment is attached to target node, we should specialize.
                  withIndent(function () {
                      node = stmt.declarations[0];
                      if (extra.comment && node.leadingComments) {
                          result.push('\n', addIndent(generateStatement(node, {
                              allowIn: allowIn
                          })));
                      } else {
                          result.push(noEmptySpace(), generateStatement(node, {
                              allowIn: allowIn
                          }));
                      }
  
                      for (i = 1, len = stmt.declarations.length; i < len; ++i) {
                          node = stmt.declarations[i];
                          if (extra.comment && node.leadingComments) {
                              result.push(',' + newline, addIndent(generateStatement(node, {
                                  allowIn: allowIn
                              })));
                          } else {
                              result.push(',' + space, generateStatement(node, {
                                  allowIn: allowIn
                              }));
                          }
                      }
                  });
              }
              result.push(semicolon);
              break;
  
          case Syntax.ThrowStatement:
              result = [join(
                  'throw',
                  generateExpression(stmt.argument, {
                      precedence: Precedence.Sequence,
                      allowIn: true,
                      allowCall: true
                  })
              ), semicolon];
              break;
  
          case Syntax.TryStatement:
              result = ['try', maybeBlock(stmt.block)];
              result = maybeBlockSuffix(stmt.block, result);
  
              if (stmt.handlers) {
                  // old interface
                  for (i = 0, len = stmt.handlers.length; i < len; ++i) {
                      result = join(result, generateStatement(stmt.handlers[i]));
                      if (stmt.finalizer || i + 1 !== len) {
                          result = maybeBlockSuffix(stmt.handlers[i].body, result);
                      }
                  }
              } else {
                  stmt.guardedHandlers = stmt.guardedHandlers || [];
  
                  // new interface
                  if (stmt.handler) {
                      if (isArray(stmt.handler)) {
                          for (i = 0, len = stmt.handler.length; i < len; ++i) {
                              result = join(result, generateStatement(stmt.handler[i]));
                              if (stmt.finalizer || i + 1 !== len) {
                                  result = maybeBlockSuffix(stmt.handler[i].body, result);
                              }
                          }
                      } else {
                          result = join(result, generateStatement(stmt.handler));
                          if (stmt.finalizer || stmt.guardedHandlers.length > 0) {
                              result = maybeBlockSuffix(stmt.handler.body, result);
                          }
                      }
                  }
  
                  for (i = 0, len = stmt.guardedHandlers.length; i < len; ++i) {
                      result = join(result, generateStatement(stmt.guardedHandlers[i]));
                      if (stmt.finalizer || i + 1 !== len) {
                          result = maybeBlockSuffix(stmt.guardedHandlers[i].body, result);
                      }
                  }
              }
              if (stmt.finalizer) {
                  result = join(result, ['finally', maybeBlock(stmt.finalizer)]);
              }
              break;
  
          case Syntax.SwitchStatement:
              withIndent(function () {
                  result = [
                      'switch' + space + '(',
                      generateExpression(stmt.discriminant, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }),
                      ')' + space + '{' + newline
                  ];
              });
              if (stmt.cases) {
                  for (i = 0, len = stmt.cases.length; i < len; ++i) {
                      fragment = addIndent(generateStatement(stmt.cases[i], {semicolonOptional: i === len - 1}));
                      result.push(fragment);
                      if (!endsWithLineTerminator(toSourceNode(fragment).toString())) {
                          result.push(newline);
                      }
                  }
              }
              result.push(addIndent('}'));
              break;
  
          case Syntax.SwitchCase:
              withIndent(function () {
                  if (stmt.test) {
                      result = [
                          join('case', generateExpression(stmt.test, {
                              precedence: Precedence.Sequence,
                              allowIn: true,
                              allowCall: true
                          })),
                          ':'
                      ];
                  } else {
                      result = ['default:'];
                  }
  
                  i = 0;
                  len = stmt.consequent.length;
                  if (len && stmt.consequent[0].type === Syntax.BlockStatement) {
                      fragment = maybeBlock(stmt.consequent[0]);
                      result.push(fragment);
                      i = 1;
                  }
  
                  if (i !== len && !endsWithLineTerminator(toSourceNode(result).toString())) {
                      result.push(newline);
                  }
  
                  for (; i < len; ++i) {
                      fragment = addIndent(generateStatement(stmt.consequent[i], {semicolonOptional: i === len - 1 && semicolon === ''}));
                      result.push(fragment);
                      if (i + 1 !== len && !endsWithLineTerminator(toSourceNode(fragment).toString())) {
                          result.push(newline);
                      }
                  }
              });
              break;
  
          case Syntax.IfStatement:
              withIndent(function () {
                  result = [
                      'if' + space + '(',
                      generateExpression(stmt.test, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }),
                      ')'
                  ];
              });
              if (stmt.alternate) {
                  result.push(maybeBlock(stmt.consequent));
                  result = maybeBlockSuffix(stmt.consequent, result);
                  if (stmt.alternate.type === Syntax.IfStatement) {
                      result = join(result, ['else ', generateStatement(stmt.alternate, {semicolonOptional: semicolon === ''})]);
                  } else {
                      result = join(result, join('else', maybeBlock(stmt.alternate, semicolon === '')));
                  }
              } else {
                  result.push(maybeBlock(stmt.consequent, semicolon === ''));
              }
              break;
  
          case Syntax.ForStatement:
              withIndent(function () {
                  result = ['for' + space + '('];
                  if (stmt.init) {
                      if (stmt.init.type === Syntax.VariableDeclaration) {
                          result.push(generateStatement(stmt.init, {allowIn: false}));
                      } else {
                          result.push(generateExpression(stmt.init, {
                              precedence: Precedence.Sequence,
                              allowIn: false,
                              allowCall: true
                          }), ';');
                      }
                  } else {
                      result.push(';');
                  }
  
                  if (stmt.test) {
                      result.push(space, generateExpression(stmt.test, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }), ';');
                  } else {
                      result.push(';');
                  }
  
                  if (stmt.update) {
                      result.push(space, generateExpression(stmt.update, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }), ')');
                  } else {
                      result.push(')');
                  }
              });
  
              result.push(maybeBlock(stmt.body, semicolon === ''));
              break;
  
          case Syntax.ForInStatement:
              result = ['for' + space + '('];
              withIndent(function () {
                  if (stmt.left.type === Syntax.VariableDeclaration) {
                      withIndent(function () {
                          result.push(stmt.left.kind + noEmptySpace(), generateStatement(stmt.left.declarations[0], {
                              allowIn: false
                          }));
                      });
                  } else {
                      result.push(generateExpression(stmt.left, {
                          precedence: Precedence.Call,
                          allowIn: true,
                          allowCall: true
                      }));
                  }
  
                  result = join(result, 'in');
                  result = [join(
                      result,
                      generateExpression(stmt.right, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      })
                  ), ')'];
              });
              result.push(maybeBlock(stmt.body, semicolon === ''));
              break;
  
          case Syntax.LabeledStatement:
              result = [stmt.label.name + ':', maybeBlock(stmt.body, semicolon === '')];
              break;
  
          case Syntax.Program:
              len = stmt.body.length;
              result = [safeConcatenation && len > 0 ? '\n' : ''];
              for (i = 0; i < len; ++i) {
                  fragment = addIndent(
                      generateStatement(stmt.body[i], {
                          semicolonOptional: !safeConcatenation && i === len - 1,
                          directiveContext: true
                      })
                  );
                  result.push(fragment);
                  if (i + 1 < len && !endsWithLineTerminator(toSourceNode(fragment).toString())) {
                      result.push(newline);
                  }
              }
              break;
  
          case Syntax.FunctionDeclaration:
              result = [(stmt.generator && !extra.moz.starlessGenerator ? 'function* ' : 'function '),
                        generateIdentifier(stmt.id),
                        generateFunctionBody(stmt)];
              break;
  
          case Syntax.ReturnStatement:
              if (stmt.argument) {
                  result = [join(
                      'return',
                      generateExpression(stmt.argument, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      })
                  ), semicolon];
              } else {
                  result = ['return' + semicolon];
              }
              break;
  
          case Syntax.WhileStatement:
              withIndent(function () {
                  result = [
                      'while' + space + '(',
                      generateExpression(stmt.test, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }),
                      ')'
                  ];
              });
              result.push(maybeBlock(stmt.body, semicolon === ''));
              break;
  
          case Syntax.WithStatement:
              withIndent(function () {
                  result = [
                      'with' + space + '(',
                      generateExpression(stmt.object, {
                          precedence: Precedence.Sequence,
                          allowIn: true,
                          allowCall: true
                      }),
                      ')'
                  ];
              });
              result.push(maybeBlock(stmt.body, semicolon === ''));
              break;
  
          default:
              throw new Error('Unknown statement type: ' + stmt.type);
          }
  
          // Attach comments
  
          if (extra.comment) {
              result = addCommentsToStatement(stmt, result);
          }
  
          fragment = toSourceNode(result).toString();
          if (stmt.type === Syntax.Program && !safeConcatenation && newline === '' &&  fragment.charAt(fragment.length - 1) === '\n') {
              result = toSourceNode(result).replaceRight(/\s+$/, '');
          }
  
          return toSourceNode(result, stmt);
      }
  
      function generate(node, options) {
          var defaultOptions = getDefaultOptions(), result, pair;
  
          if (options != null) {
              // Obsolete options
              //
              //   `options.indent`
              //   `options.base`
              //
              // Instead of them, we can use `option.format.indent`.
              if (typeof options.indent === 'string') {
                  defaultOptions.format.indent.style = options.indent;
              }
              if (typeof options.base === 'number') {
                  defaultOptions.format.indent.base = options.base;
              }
              options = updateDeeply(defaultOptions, options);
              indent = options.format.indent.style;
              if (typeof options.base === 'string') {
                  base = options.base;
              } else {
                  base = stringRepeat(indent, options.format.indent.base);
              }
          } else {
              options = defaultOptions;
              indent = options.format.indent.style;
              base = stringRepeat(indent, options.format.indent.base);
          }
          json = options.format.json;
          renumber = options.format.renumber;
          hexadecimal = json ? false : options.format.hexadecimal;
          quotes = json ? 'double' : options.format.quotes;
          escapeless = options.format.escapeless;
          newline = options.format.newline;
          space = options.format.space;
          if (options.format.compact) {
              newline = space = indent = base = '';
          }
          parentheses = options.format.parentheses;
          semicolons = options.format.semicolons;
          safeConcatenation = options.format.safeConcatenation;
          directive = options.directive;
          parse = json ? null : options.parse;
          sourceMap = options.sourceMap;
          extra = options;
  
          if (sourceMap) {
              if (!exports.browser) {
                  // We assume environment is node.js
                  // And prevent from including source-map by browserify
                  SourceNode = require('source-map').SourceNode;
              } else {
                  SourceNode = global.sourceMap.SourceNode;
              }
          } else {
              SourceNode = SourceNodeMock;
          }
  
          switch (node.type) {
          case Syntax.BlockStatement:
          case Syntax.BreakStatement:
          case Syntax.CatchClause:
          case Syntax.ContinueStatement:
          case Syntax.DirectiveStatement:
          case Syntax.DoWhileStatement:
          case Syntax.DebuggerStatement:
          case Syntax.EmptyStatement:
          case Syntax.ExpressionStatement:
          case Syntax.ForStatement:
          case Syntax.ForInStatement:
          case Syntax.FunctionDeclaration:
          case Syntax.IfStatement:
          case Syntax.LabeledStatement:
          case Syntax.Program:
          case Syntax.ReturnStatement:
          case Syntax.SwitchStatement:
          case Syntax.SwitchCase:
          case Syntax.ThrowStatement:
          case Syntax.TryStatement:
          case Syntax.VariableDeclaration:
          case Syntax.VariableDeclarator:
          case Syntax.WhileStatement:
          case Syntax.WithStatement:
              result = generateStatement(node);
              break;
  
          case Syntax.AssignmentExpression:
          case Syntax.ArrayExpression:
          case Syntax.ArrayPattern:
          case Syntax.BinaryExpression:
          case Syntax.CallExpression:
          case Syntax.ConditionalExpression:
          case Syntax.FunctionExpression:
          case Syntax.Identifier:
          case Syntax.Literal:
          case Syntax.LogicalExpression:
          case Syntax.MemberExpression:
          case Syntax.NewExpression:
          case Syntax.ObjectExpression:
          case Syntax.ObjectPattern:
          case Syntax.Property:
          case Syntax.SequenceExpression:
          case Syntax.ThisExpression:
          case Syntax.UnaryExpression:
          case Syntax.UpdateExpression:
          case Syntax.YieldExpression:
  
              result = generateExpression(node, {
                  precedence: Precedence.Sequence,
                  allowIn: true,
                  allowCall: true
              });
              break;
  
          default:
              throw new Error('Unknown node type: ' + node.type);
          }
  
          if (!sourceMap) {
              return result.toString();
          }
  
  
          pair = result.toStringWithSourceMap({
              file: options.file,
              sourceRoot: options.sourceMapRoot
          });
  
          if (options.sourceContent) {
              pair.map.setSourceContent(options.sourceMap,
                                        options.sourceContent);
          }
  
          if (options.sourceMapWithCode) {
              return pair;
          }
  
          return pair.map.toString();
      }
  
      FORMAT_MINIFY = {
          indent: {
              style: '',
              base: 0
          },
          renumber: true,
          hexadecimal: true,
          quotes: 'auto',
          escapeless: true,
          compact: true,
          parentheses: false,
          semicolons: false
      };
  
      FORMAT_DEFAULTS = getDefaultOptions().format;
  
      exports.version = require('./package.json').version;
      exports.generate = generate;
      exports.attachComments = estraverse.attachComments;
      exports.browser = false;
      exports.FORMAT_MINIFY = FORMAT_MINIFY;
      exports.FORMAT_DEFAULTS = FORMAT_DEFAULTS;
  }());
  /* vim: set sw=4 ts=4 et tw=80 : */
  