// -------------------------------------------------------------
// WARNING: this file is used by both the client and the server.
// Do not use any browser or node-specific API!
// -------------------------------------------------------------

/*
 Copyright (C) 2014 Ivan Nikulin <ifaaan@gmail.com>
 Copyright (C) 2012-2014 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.
 */

'use strict';

var isArray,
    json,
    renumber,
    hexadecimal,
    quotes,
    escapeless,
    parentheses,
    semicolons,
    safeConcatenation,
    directive,
    extra,
    parse;

var Syntax = {
    AssignmentExpression:     'AssignmentExpression',
    AssignmentPattern:        'AssignmentPattern',
    ArrayExpression:          'ArrayExpression',
    ArrayPattern:             'ArrayPattern',
    ArrowFunctionExpression:  'ArrowFunctionExpression',
    AwaitExpression:          'AwaitExpression',
    BlockStatement:           'BlockStatement',
    BinaryExpression:         'BinaryExpression',
    BreakStatement:           'BreakStatement',
    CallExpression:           'CallExpression',
    CatchClause:              'CatchClause',
    ClassBody:                'ClassBody',
    ClassDeclaration:         'ClassDeclaration',
    ClassExpression:          'ClassExpression',
    ComprehensionBlock:       'ComprehensionBlock',
    ComprehensionExpression:  'ComprehensionExpression',
    ConditionalExpression:    'ConditionalExpression',
    ContinueStatement:        'ContinueStatement',
    DirectiveStatement:       'DirectiveStatement',
    DoWhileStatement:         'DoWhileStatement',
    DebuggerStatement:        'DebuggerStatement',
    EmptyStatement:           'EmptyStatement',
    ExportAllDeclaration:     'ExportAllDeclaration',
    ExportBatchSpecifier:     'ExportBatchSpecifier',
    ExportDeclaration:        'ExportDeclaration',
    ExportNamedDeclaration:   'ExportNamedDeclaration',
    ExportSpecifier:          'ExportSpecifier',
    ExpressionStatement:      'ExpressionStatement',
    ForStatement:             'ForStatement',
    ForInStatement:           'ForInStatement',
    ForOfStatement:           'ForOfStatement',
    FunctionDeclaration:      'FunctionDeclaration',
    FunctionExpression:       'FunctionExpression',
    GeneratorExpression:      'GeneratorExpression',
    Identifier:               'Identifier',
    IfStatement:              'IfStatement',
    ImportExpression:         'ImportExpression',
    ImportSpecifier:          'ImportSpecifier',
    ImportDeclaration:        'ImportDeclaration',
    ChainExpression:          'ChainExpression',
    Literal:                  'Literal',
    LabeledStatement:         'LabeledStatement',
    LogicalExpression:        'LogicalExpression',
    MemberExpression:         'MemberExpression',
    MetaProperty:             'MetaProperty',
    MethodDefinition:         'MethodDefinition',
    ModuleDeclaration:        'ModuleDeclaration',
    NewExpression:            'NewExpression',
    ObjectExpression:         'ObjectExpression',
    ObjectPattern:            'ObjectPattern',
    Program:                  'Program',
    Property:                 'Property',
    RestElement:              'RestElement',
    ReturnStatement:          'ReturnStatement',
    SequenceExpression:       'SequenceExpression',
    SpreadElement:            'SpreadElement',
    Super:                    'Super',
    SwitchStatement:          'SwitchStatement',
    SwitchCase:               'SwitchCase',
    TaggedTemplateExpression: 'TaggedTemplateExpression',
    TemplateElement:          'TemplateElement',
    TemplateLiteral:          'TemplateLiteral',
    ThisExpression:           'ThisExpression',
    ThrowStatement:           'ThrowStatement',
    TryStatement:             'TryStatement',
    UnaryExpression:          'UnaryExpression',
    UpdateExpression:         'UpdateExpression',
    VariableDeclaration:      'VariableDeclaration',
    VariableDeclarator:       'VariableDeclarator',
    WhileStatement:           'WhileStatement',
    WithStatement:            'WithStatement',
    YieldExpression:          'YieldExpression'
};

exports.Syntax = Syntax;

var Precedence = {
    Sequence:         0,
    Yield:            1,
    Assignment:       1,
    Conditional:      2,
    ArrowFunction:    2,
    Coalesce:         3,
    LogicalOR:        3,
    LogicalAND:       4,
    BitwiseOR:        5,
    BitwiseXOR:       6,
    BitwiseAND:       7,
    Equality:         8,
    Relational:       9,
    BitwiseSHIFT:     10,
    Additive:         11,
    Multiplicative:   12,
    Unary:            13,
    Exponentiation:   14,
    Postfix:          14,
    Await:            14,
    Call:             15,
    New:              16,
    TaggedTemplate:   17,
    OptionalChaining: 17,
    Member:           18,
    Primary:          19
};

var 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,
    '??':         Precedence.Coalesce,
    '**':         Precedence.Exponentiation
};

function getDefaultOptions () {
    // default options
    return {
        indent:    null,
        base:      null,
        parse:     null,
        format:    {
            indent:            {
                style: '    ',
                base:  0
            },
            newline:           '\n',
            space:             ' ',
            json:              false,
            renumber:          false,
            hexadecimal:       false,
            quotes:            'single',
            escapeless:        false,
            compact:           false,
            parentheses:       true,
            semicolons:        true,
            safeConcatenation: false
        },
        directive: false,
        raw:       true,
        verbatim:  null
    };
}

//-------------------------------------------------===------------------------------------------------------
//                                            Lexical utils
//-------------------------------------------------===------------------------------------------------------

//Const
var NON_ASCII_WHITESPACES = [
    0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005,
    0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000,
    0xFEFF
];

//Regular expressions
var NON_ASCII_IDENTIFIER_CHARACTERS_REGEXP = new RegExp(
    '[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376' +
    '\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-' +
    '\u0527\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA' +
    '\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-' +
    '\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0\u08A2-\u08AC\u08E4-\u08FE\u0900-' +
    '\u0963\u0966-\u096F\u0971-\u0977\u0979-\u097F\u0981-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-' +
    '\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-' +
    '\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38' +
    '\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83' +
    '\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9' +
    '\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-' +
    '\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-' +
    '\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E' +
    '\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-' +
    '\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D-\u0C44\u0C46-' +
    '\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C82\u0C83\u0C85-\u0C8C\u0C8E-' +
    '\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE' +
    '\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D02\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44' +
    '\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-' +
    '\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E01-\u0E3A' +
    '\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-' +
    '\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9' +
    '\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84' +
    '\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\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\u135D-\u135F\u1380-' +
    '\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-' +
    '\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD' +
    '\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191C\u1920-\u192B' +
    '\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E' +
    '\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-' +
    '\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1D00-\u1DE6\u1DFC-\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\u200C\u200D\u203F' +
    '\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\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-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-' +
    '\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-' +
    '\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A' +
    '\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-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA697' +
    '\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA827\uA840-\uA873' +
    '\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-' +
    '\uA9D9\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7B\uAA80-\uAAC2\uAADB-\uAADD\uAAE0-' +
    '\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABEA\uABEC' +
    '\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-' +
    '\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D' +
    '\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE26\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74' +
    '\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-' +
    '\uFFD7\uFFDA-\uFFDC]'
);


//Methods
function isIdentifierCh (cp) {
    if (cp < 0x80) {
        return cp >= 97 && cp <= 122 ||      // a..z
               cp >= 65 && cp <= 90 ||       // A..Z
               cp >= 48 && cp <= 57 ||       // 0..9
               cp === 36 || cp === 95 ||     // $ (dollar) and _ (underscore)
               cp === 92;                    // \ (backslash)
    }

    var ch = String.fromCharCode(cp);

    return NON_ASCII_IDENTIFIER_CHARACTERS_REGEXP.test(ch);
}

function isLineTerminator (cp) {
    return cp === 0x0A || cp === 0x0D || cp === 0x2028 || cp === 0x2029;
}

function isWhitespace (cp) {
    return cp === 0x20 || cp === 0x09 || isLineTerminator(cp) || cp === 0x0B || cp === 0x0C || cp === 0xA0 ||
           (cp >= 0x1680 && NON_ASCII_WHITESPACES.indexOf(cp) >= 0);
}

function isDecimalDigit (cp) {
    return cp >= 48 && cp <= 57;
}

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]';
    };
}


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 === 1 / 0) {
        return json ? 'null' : renumber ? '1e400' : '1e+400';
    }

    result = '' + value;
    if (!renumber || result.length < 3) {
        return result;
    }

    point = result.indexOf('.');
    //NOTE: 0x30 == '0'
    if (!json && result.charCodeAt(0) === 0x30 && 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;

    //NOTE: 0x30 == '0'
    while (temp.charCodeAt(temp.length + pos - 1) === 0x30) {
        --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 && !isDecimalDigit(next)) {
                result += '0';
            }

            else if (code === 0x000B) {     // \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;
    }

    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;
    //TODO http://jsperf.com/character-counting/8
    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 (isLineTerminator(code) || code === 0x5C) { // \
            result += escapeDisallowedCharacter(code);
            continue;
        }
        else if ((json && code < 0x20) ||                                     // SP
                 !(json || escapeless || (code >= 0x20 && code <= 0x7E))) {   // SP, ~
            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 join (l, r) {
    if (!l.length)
        return r;

    if (!r.length)
        return l;

    var lCp = l.charCodeAt(l.length - 1),
        rCp = r.charCodeAt(0);

    if (isIdentifierCh(lCp) && isIdentifierCh(rCp) ||
        lCp === rCp && (lCp === 0x2B || lCp === 0x2D) ||   // + +, - -
        lCp === 0x2F && rCp === 0x69) {                    // /re/ instanceof foo
        return l + _.space + r;
    }

    else if (isWhitespace(lCp) || isWhitespace(rCp))
        return l + r;

    return l + _.optSpace + r;
}

function shiftIndent () {
    var prevIndent = _.indent;

    _.indent += _.indentUnit;
    return prevIndent;
}

function adoptionPrefix ($stmt) {
    if ($stmt.type === Syntax.BlockStatement)
        return _.optSpace;

    if ($stmt.type === Syntax.EmptyStatement)
        return '';

    return _.newline + _.indent + _.indentUnit;
}

function adoptionSuffix ($stmt) {
    if ($stmt.type === Syntax.BlockStatement)
        return _.optSpace;

    return _.newline + _.indent;
}

//Subentities generators
function generateVerbatim ($expr, settings) {
    var verbatim     = $expr[extra.verbatim],
        strVerbatim  = typeof verbatim === 'string',
        precedence   = !strVerbatim &&
                       verbatim.precedence !== void 0 ? verbatim.precedence : Precedence.Sequence,
        parenthesize = precedence < settings.precedence,
        content      = strVerbatim ? verbatim : verbatim.content,
        chunks       = content.split(/\r\n|\n/),
        chunkCount   = chunks.length;

    if (parenthesize)
        _.js += '(';

    _.js += chunks[0];

    for (var i = 1; i < chunkCount; i++)
        _.js += _.newline + _.indent + chunks[i];

    if (parenthesize)
        _.js += ')';
}

function generateFunctionParams ($node) {
    var $params                     = $node.params,
        paramCount                  = $params.length,
        lastParamIdx                = paramCount - 1,
        arrowFuncWithoutParentheses = $node.type === Syntax.ArrowFunctionExpression && paramCount === 1 &&
                                      $params[0].type === Syntax.Identifier;

    //NOTE: arg => { } case
    if (arrowFuncWithoutParentheses)
        _.js += $params[0].name;

    else {
        _.js += '(';

        for (var i = 0; i < paramCount; ++i) {
            var $param = $params[i];

            if ($params[i].type === Syntax.Identifier)
                _.js += $param.name;

            else
                ExprGen[$param.type]($param, Preset.e4);

            if (i !== lastParamIdx)
                _.js += ',' + _.optSpace;
        }

        _.js += ')';
    }
}

function generateFunctionBody ($node) {
    var $body = $node.body;

    generateFunctionParams($node);

    if ($node.type === Syntax.ArrowFunctionExpression)
        _.js += _.optSpace + '=>';

    if ($node.expression) {
        _.js += _.optSpace;

        var exprJs = exprToJs($body, Preset.e4);

        if (exprJs.charAt(0) === '{')
            exprJs = '(' + exprJs + ')';

        _.js += exprJs;
    }

    else {
        _.js += adoptionPrefix($body);
        StmtGen[$body.type]($body, Preset.s8);
    }
}


//-------------------------------------------------===------------------------------------------------------
//                                Syntactic entities generation presets
//-------------------------------------------------===------------------------------------------------------

var Preset = {
    e1: function (allowIn) {
        return {
            precedence:              Precedence.Assignment,
            allowIn:                 allowIn,
            allowCall:               true,
            allowUnparenthesizedNew: true
        };
    },

    e2: function (allowIn) {
        return {
            precedence:              Precedence.LogicalOR,
            allowIn:                 allowIn,
            allowCall:               true,
            allowUnparenthesizedNew: true
        };
    },

    e3: {
        precedence:              Precedence.Call,
        allowIn:                 true,
        allowCall:               true,
        allowUnparenthesizedNew: false
    },

    e4: {
        precedence:              Precedence.Assignment,
        allowIn:                 true,
        allowCall:               true,
        allowUnparenthesizedNew: true
    },

    e5: {
        precedence:              Precedence.Sequence,
        allowIn:                 true,
        allowCall:               true,
        allowUnparenthesizedNew: true
    },

    e6: function (allowUnparenthesizedNew) {
        return {
            precedence:              Precedence.New,
            allowIn:                 true,
            allowCall:               false,
            allowUnparenthesizedNew: allowUnparenthesizedNew
        };
    },

    e7: {
        precedence:              Precedence.Unary,
        allowIn:                 true,
        allowCall:               true,
        allowUnparenthesizedNew: true
    },

    e8: {
        precedence:              Precedence.Postfix,
        allowIn:                 true,
        allowCall:               true,
        allowUnparenthesizedNew: true
    },

    e9: {
        precedence:              void 0,
        allowIn:                 true,
        allowCall:               true,
        allowUnparenthesizedNew: true
    },

    e10: {
        precedence:              Precedence.Call,
        allowIn:                 true,
        allowCall:               true,
        allowUnparenthesizedNew: true
    },

    e11: function (allowCall) {
        return {
            precedence:              Precedence.Call,
            allowIn:                 true,
            allowCall:               allowCall,
            allowUnparenthesizedNew: false
        };
    },

    e12: {
        precedence:              Precedence.Primary,
        allowIn:                 false,
        allowCall:               false,
        allowUnparenthesizedNew: true
    },

    e13: {
        precedence:              Precedence.Primary,
        allowIn:                 true,
        allowCall:               true,
        allowUnparenthesizedNew: true
    },


    e14: {
        precedence:              Precedence.Sequence,
        allowIn:                 false,
        allowCall:               true,
        allowUnparenthesizedNew: true
    },


    e15: function (allowCall) {
        return {
            precedence:              Precedence.Sequence,
            allowIn:                 true,
            allowCall:               allowCall,
            allowUnparenthesizedNew: true
        };
    },

    e16: function (precedence, allowIn) {
        return {
            precedence:              precedence,
            allowIn:                 allowIn,
            allowCall:               true,
            allowUnparenthesizedNew: true
        };
    },

    e17: function (allowIn) {
        return {
            precedence:              Precedence.Call,
            allowIn:                 allowIn,
            allowCall:               true,
            allowUnparenthesizedNew: true
        }
    },

    e18: function (allowIn) {
        return {
            precedence:              Precedence.Assignment,
            allowIn:                 allowIn,
            allowCall:               true,
            allowUnparenthesizedNew: true
        }
    },

    e19: {
        precedence:        Precedence.Sequence,
        allowIn:           true,
        allowCall:         true,
        semicolonOptional: false
    },

    e20: {
        precedence: Precedence.Await,
        allowCall:  true
    },

    s1: function (functionBody, semicolonOptional) {
        return {
            allowIn:           true,
            functionBody:      false,
            directiveContext:  functionBody,
            semicolonOptional: semicolonOptional
        };
    },

    s2: {
        allowIn:           true,
        functionBody:      false,
        directiveContext:  false,
        semicolonOptional: true
    },

    s3: function (allowIn) {
        return {
            allowIn:           allowIn,
            functionBody:      false,
            directiveContext:  false,
            semicolonOptional: false
        };
    },

    s4: function (semicolonOptional) {
        return {
            allowIn:           true,
            functionBody:      false,
            directiveContext:  false,
            semicolonOptional: semicolonOptional
        };
    },

    s5: function (semicolonOptional) {
        return {
            allowIn:           true,
            functionBody:      false,
            directiveContext:  true,
            semicolonOptional: semicolonOptional,
        };
    },

    s6: {
        allowIn:           false,
        functionBody:      false,
        directiveContext:  false,
        semicolonOptional: false
    },

    s7: {
        allowIn:           true,
        functionBody:      false,
        directiveContext:  false,
        semicolonOptional: false
    },

    s8: {
        allowIn:           true,
        functionBody:      true,
        directiveContext:  false,
        semicolonOptional: false
    }
};


//-------------------------------------------------===-------------------------------------------------------
//                                             Expressions
//-------------------------------------------------===-------------------------------------------------------

//Regular expressions
var FLOATING_OR_OCTAL_REGEXP  = /[.eExX]|^0[0-9]+/,
    LAST_DECIMAL_DIGIT_REGEXP = /[0-9]$/;


//Common expression generators
function isLogicalExpression(node) {
    if (!node)
        return false;

    return node.type === Syntax.LogicalExpression;
}

function needParensForLogicalExpression (node, parent) {
    switch (node.operator) {
        case "||":
            if (!isLogicalExpression(parent)) return false;
            return parent.operator === "??" || parent.operator === "&&";

        case "&&":
            return isLogicalExpression(parent, {
                operator: "??"
            });

        case "??":
            return isLogicalExpression(parent) && parent.operator !== "??";
    }
}

function generateLogicalOrBinaryExpression ($expr, settings, $parent) {
    var op                 = $expr.operator,
        precedence         = BinaryPrecedence[$expr.operator],
        parenthesize       = precedence < settings.precedence,
        allowIn            = settings.allowIn || parenthesize,
        operandGenSettings = Preset.e16(precedence, allowIn),
        exprJs             = exprToJs($expr.left, operandGenSettings, $expr);

    parenthesize |= op === 'in' && !allowIn;

    var needParens = needParensForLogicalExpression($expr, $parent);

    if (parenthesize || needParens)
        _.js += '(';

    // 0x2F = '/'
    if (exprJs.charCodeAt(exprJs.length - 1) === 0x2F && isIdentifierCh(op.charCodeAt(0)))
        exprJs = exprJs + _.space + op;

    else
        exprJs = join(exprJs, op);

    operandGenSettings.precedence++;

    var rightJs = exprToJs($expr.right, operandGenSettings, $expr);

    //NOTE: If '/' concats with '/' or `<` concats with `!--`, it is interpreted as comment start
    if (op === '/' && rightJs.charAt(0) === '/' || op.slice(-1) === '<' && rightJs.slice(0, 3) === '!--')
        exprJs += _.space + rightJs;

    else
        exprJs = join(exprJs, rightJs);

    _.js += exprJs;

    if (parenthesize || needParens)
        _.js += ')';
}

function generateArrayPatternOrExpression ($expr) {
    var $elems    = $expr.elements,
        elemCount = $elems.length;

    if (elemCount) {
        var lastElemIdx = elemCount - 1,
            multiline   = elemCount > 1,
            prevIndent  = shiftIndent(),
            itemPrefix  = _.newline + _.indent;

        _.js += '[';

        for (var i = 0; i < elemCount; i++) {
            var $elem = $elems[i];

            if (multiline)
                _.js += itemPrefix;

            if ($elem)
                ExprGen[$elem.type]($elem, Preset.e4);

            if (i !== lastElemIdx || !$elem)
                _.js += ',';
        }

        _.indent = prevIndent;

        if (multiline)
            _.js += _.newline + _.indent;

        _.js += ']';
    }

    else
        _.js += '[]';
}

function generateGeneratorOrComprehensionExpression ($expr) {
    //NOTE: GeneratorExpression should be parenthesized with (...), ComprehensionExpression with [...]
    var $blocks     = $expr.blocks,
        $filter     = $expr.filter,
        isGenerator = $expr.type === Syntax.GeneratorExpression,
        exprJs      = isGenerator ? '(' : '[',
        bodyJs      = exprToJs($expr.body, Preset.e4);

    if ($blocks) {
        var prevIndent = shiftIndent(),
            blockCount = $blocks.length;

        for (var i = 0; i < blockCount; ++i) {
            var blockJs = exprToJs($blocks[i], Preset.e5);

            exprJs = i > 0 ? join(exprJs, blockJs) : (exprJs + blockJs);
        }

        _.indent = prevIndent;
    }

    if ($filter) {
        var filterJs = exprToJs($filter, Preset.e5);

        exprJs = join(exprJs, 'if' + _.optSpace);
        exprJs = join(exprJs, '(' + filterJs + ')');
    }

    exprJs = join(exprJs, bodyJs);
    exprJs += isGenerator ? ')' : ']';

    _.js += exprJs;
}


//Expression raw generator dictionary
var ExprRawGen = {
    SequenceExpression: function generateSequenceExpression ($expr, settings) {
        var $children       = $expr.expressions,
            childrenCount   = $children.length,
            lastChildIdx    = childrenCount - 1,
            parenthesize    = Precedence.Sequence < settings.precedence,
            exprGenSettings = Preset.e1(settings.allowIn || parenthesize);

        if (parenthesize)
            _.js += '(';

        for (var i = 0; i < childrenCount; i++) {
            var $child = $children[i];

            ExprGen[$child.type]($child, exprGenSettings);

            if (i !== lastChildIdx)
                _.js += ',' + _.optSpace;
        }

        if (parenthesize)
            _.js += ')';
    },

    AssignmentExpression: function generateAssignmentExpression ($expr, settings) {
        var $left        = $expr.left,
            $right       = $expr.right,
            parenthesize = Precedence.Assignment < settings.precedence,
            allowIn      = settings.allowIn || parenthesize;

        if (parenthesize)
            _.js += '(';

        ExprGen[$left.type]($left, Preset.e17(allowIn));
        _.js += _.optSpace + $expr.operator + _.optSpace;
        ExprGen[$right.type]($right, Preset.e18(allowIn));

        if (parenthesize)
            _.js += ')';
    },

    AssignmentPattern: function generateAssignmentPattern ($node) {
        var $fakeAssign = {
            left:     $node.left,
            right:    $node.right,
            operator: '='
        };

        ExprGen.AssignmentExpression($fakeAssign, Preset.e4);
    },

    ArrowFunctionExpression: function generateArrowFunctionExpression ($expr, settings) {
        var parenthesize = Precedence.ArrowFunction < settings.precedence;

        if (parenthesize)
            _.js += '(';

        if ($expr.async)
            _.js += 'async ';

        generateFunctionBody($expr);

        if (parenthesize)
            _.js += ')';
    },

    AwaitExpression: function generateAwaitExpression ($expr, settings) {
        var parenthesize = Precedence.Await < settings.precedence;

        if (parenthesize)
            _.js += '(';

        _.js += $expr.all ? 'await* ' : 'await ';

        ExprGen[$expr.argument.type]($expr.argument, Preset.e20);

        if (parenthesize)
            _.js += ')';
    },

    ConditionalExpression: function generateConditionalExpression ($expr, settings) {
        var $test             = $expr.test,
            $conseq           = $expr.consequent,
            $alt              = $expr.alternate,
            parenthesize      = Precedence.Conditional < settings.precedence,
            allowIn           = settings.allowIn || parenthesize,
            testGenSettings   = Preset.e2(allowIn),
            branchGenSettings = Preset.e1(allowIn);

        if (parenthesize)
            _.js += '(';

        ExprGen[$test.type]($test, testGenSettings);
        _.js += _.optSpace + '?' + _.optSpace;
        ExprGen[$conseq.type]($conseq, branchGenSettings);
        _.js += _.optSpace + ':' + _.optSpace;
        ExprGen[$alt.type]($alt, branchGenSettings);

        if (parenthesize)
            _.js += ')';
    },

    LogicalExpression: generateLogicalOrBinaryExpression,

    BinaryExpression: generateLogicalOrBinaryExpression,

    CallExpression: function generateCallExpression ($expr, settings) {
        var $callee      = $expr.callee,
            $args        = $expr['arguments'],
            argCount     = $args.length,
            lastArgIdx   = argCount - 1,
            parenthesize = !settings.allowCall || Precedence.Call < settings.precedence;

        if (parenthesize)
            _.js += '(';

        ExprGen[$callee.type]($callee, Preset.e3);

        if ($expr.optional)
            _.js += '?.';

        _.js += '(';

        for (var i = 0; i < argCount; ++i) {
            var $arg = $args[i];

            ExprGen[$arg.type]($arg, Preset.e4);

            if (i !== lastArgIdx)
                _.js += ',' + _.optSpace;
        }

        _.js += ')';

        if (parenthesize)
            _.js += ')';
    },

    NewExpression: function generateNewExpression ($expr, settings) {
        var $args        = $expr['arguments'],
            parenthesize = Precedence.New < settings.precedence,
            argCount     = $args.length,
            lastArgIdx   = argCount - 1,
            withCall     = !settings.allowUnparenthesizedNew || parentheses || argCount > 0,
            calleeJs     = exprToJs($expr.callee, Preset.e6(!withCall));

        if (parenthesize)
            _.js += '(';

        _.js += join('new', calleeJs);

        if (withCall) {
            _.js += '(';

            for (var i = 0; i < argCount; ++i) {
                var $arg = $args[i];

                ExprGen[$arg.type]($arg, Preset.e4);

                if (i !== lastArgIdx)
                    _.js += ',' + _.optSpace;
            }

            _.js += ')';
        }

        if (parenthesize)
            _.js += ')';
    },

    MemberExpression: function generateMemberExpression ($expr, settings) {
        var $obj         = $expr.object,
            $prop        = $expr.property,
            parenthesize = Precedence.Member < settings.precedence,
            isNumObj     = !$expr.computed && $obj.type === Syntax.Literal && typeof $obj.value === 'number';

        if (parenthesize)
            _.js += '(';

        if (isNumObj) {

            //NOTE: 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
            // then we should add a floating point.

            var numJs     = exprToJs($obj, Preset.e11(settings.allowCall)),
                withPoint = LAST_DECIMAL_DIGIT_REGEXP.test(numJs) && !FLOATING_OR_OCTAL_REGEXP.test(numJs);

            _.js += withPoint ? (numJs + '.') : numJs;
        }

        else
            ExprGen[$obj.type]($obj, Preset.e11(settings.allowCall));

        if ($expr.computed) {
            if ($expr.optional)
                _.js += '?.';

            _.js += '[';
            ExprGen[$prop.type]($prop, Preset.e15(settings.allowCall));
            _.js += ']';
        }

        else
            _.js += ($expr.optional ? '?.' : '.') + $prop.name;

        if (parenthesize)
            _.js += ')';
    },

    UnaryExpression: function generateUnaryExpression ($expr, settings) {
        var parenthesize = Precedence.Unary < settings.precedence,
            op           = $expr.operator,
            argJs        = exprToJs($expr.argument, Preset.e7);

        if (parenthesize)
            _.js += '(';

        //NOTE: delete, void, typeof
        // get `typeof []`, not `typeof[]`
        if (_.optSpace === '' || op.length > 2)
            _.js += join(op, argJs);

        else {
            _.js += op;

            //NOTE: Prevent inserting spaces between operator and argument if it is unnecessary
            // like, `!cond`
            var leftCp  = op.charCodeAt(op.length - 1),
                rightCp = argJs.charCodeAt(0);

            // 0x2B = '+', 0x2D =  '-'
            if (leftCp === rightCp && (leftCp === 0x2B || leftCp === 0x2D) ||
                isIdentifierCh(leftCp) && isIdentifierCh(rightCp)) {
                _.js += _.space;
            }

            _.js += argJs;
        }

        if (parenthesize)
            _.js += ')';
    },

    YieldExpression: function generateYieldExpression ($expr, settings) {
        var $arg         = $expr.argument,
            js           = $expr.delegate ? 'yield*' : 'yield',
            parenthesize = Precedence.Yield < settings.precedence;

        if (parenthesize)
            _.js += '(';

        if ($arg) {
            var argJs = exprToJs($arg, Preset.e4);

            js = join(js, argJs);
        }

        _.js += js;

        if (parenthesize)
            _.js += ')';
    },

    UpdateExpression: function generateUpdateExpression ($expr, settings) {
        var $arg         = $expr.argument,
            $op          = $expr.operator,
            prefix       = $expr.prefix,
            precedence   = prefix ? Precedence.Unary : Precedence.Postfix,
            parenthesize = precedence < settings.precedence;

        if (parenthesize)
            _.js += '(';

        if (prefix) {
            _.js += $op;
            ExprGen[$arg.type]($arg, Preset.e8);

        }

        else {
            ExprGen[$arg.type]($arg, Preset.e8);
            _.js += $op;
        }

        if (parenthesize)
            _.js += ')';
    },

    FunctionExpression: function generateFunctionExpression ($expr) {
        var isGenerator = !!$expr.generator;

        if ($expr.async)
            _.js += 'async ';

        _.js += isGenerator ? 'function*' : 'function';

        if ($expr.id) {
            _.js += isGenerator ? _.optSpace : _.space;
            _.js += $expr.id.name;
        }
        else
            _.js += _.optSpace;

        generateFunctionBody($expr);
    },

    ExportBatchSpecifier: function generateExportBatchSpecifier () {
        _.js += '*';
    },

    ArrayPattern: generateArrayPatternOrExpression,

    ArrayExpression: generateArrayPatternOrExpression,

    ClassExpression: function generateClassExpression ($expr) {
        var $id    = $expr.id,
            $super = $expr.superClass,
            $body  = $expr.body,
            exprJs = 'class';

        if ($id) {
            var idJs = exprToJs($id, Preset.e9);

            exprJs = join(exprJs, idJs);
        }

        if ($super) {
            var superJs = exprToJs($super, Preset.e4);

            superJs = join('extends', superJs);
            exprJs  = join(exprJs, superJs);
        }

        _.js += exprJs + _.optSpace;
        StmtGen[$body.type]($body, Preset.s2);
    },

    MetaProperty: function generateMetaProperty ($expr, settings) {
        var $meta        = $expr.meta,
            $property    = $expr.property,
            parenthesize = Precedence.Member < settings.precedence;

        if (parenthesize)
            _.js += '(';

        _.js += (typeof $meta === "string" ? $meta : $meta.name) +
            '.' + (typeof $property === "string" ? $property : $property.name);

        if (parenthesize)
            _.js += ')';
    },

    MethodDefinition: function generateMethodDefinition ($expr) {
        var exprJs = $expr['static'] ? 'static' + _.optSpace : '',
            keyJs  = exprToJs($expr.key, Preset.e5);

        if ($expr.computed)
            keyJs = '[' + keyJs + ']';

        if ($expr.kind === 'get' || $expr.kind === 'set') {
            keyJs = join($expr.kind, keyJs);
            _.js += join(exprJs, keyJs);
        }

        else {
            if ($expr.value.generator)
                _.js += exprJs + '*' + keyJs;
            else if ($expr.value.async)
                _.js += exprJs + 'async ' + keyJs;
            else
                _.js += join(exprJs, keyJs);
        }

        generateFunctionBody($expr.value);
    },

    Property: function generateProperty ($expr) {
        var $val  = $expr.value,
            $kind = $expr.kind,
            keyJs = exprToJs($expr.key, Preset.e4);

        if ($expr.computed)
            keyJs = '[' + keyJs + ']';

        if ($kind === 'get' || $kind === 'set') {
            _.js += $kind + _.space + keyJs;
            generateFunctionBody($val);
        }

        else {
            if ($expr.shorthand)
                _.js += keyJs;

            else if ($expr.method) {
                if ($val.generator)
                    keyJs = '*' + keyJs;
                else if ($val.async)
                    keyJs = 'async ' + keyJs;

                _.js += keyJs;
                generateFunctionBody($val)
            }

            else {
                _.js += keyJs + ':' + _.optSpace;
                ExprGen[$val.type]($val, Preset.e4);
            }
        }
    },

    ObjectExpression: function generateObjectExpression ($expr) {
        var $props    = $expr.properties,
            propCount = $props.length;

        if (propCount) {
            var lastPropIdx = propCount - 1,
                prevIndent  = shiftIndent();

            _.js += '{';

            for (var i = 0; i < propCount; i++) {
                var $prop    = $props[i],
                    propType = $prop.type || Syntax.Property;

                _.js += _.newline + _.indent;
                ExprGen[propType]($prop, Preset.e5);

                if (i !== lastPropIdx)
                    _.js += ',';
            }

            _.indent = prevIndent;
            _.js += _.newline + _.indent + '}';
        }

        else
            _.js += '{}';
    },

    ObjectPattern: function generateObjectPattern ($expr) {
        var $props    = $expr.properties,
            propCount = $props.length;

        if (propCount) {
            var lastPropIdx = propCount - 1,
                multiline   = false;

            if (propCount === 1)
                multiline = $props[0].value.type !== Syntax.Identifier;

            else {
                for (var i = 0; i < propCount; i++) {
                    if (!$props[i].shorthand) {
                        multiline = true;
                        break;
                    }
                }
            }

            _.js += multiline ? ('{' + _.newline) : '{';

            var prevIndent = shiftIndent(),
                propSuffix = ',' + (multiline ? _.newline : _.optSpace);

            for (var i = 0; i < propCount; i++) {
                var $prop = $props[i];

                if (multiline)
                    _.js += _.indent;

                ExprGen[$prop.type]($prop, Preset.e5);

                if (i !== lastPropIdx)
                    _.js += propSuffix;
            }

            _.indent = prevIndent;
            _.js += multiline ? (_.newline + _.indent + '}') : '}';
        }
        else
            _.js += '{}';
    },

    ThisExpression: function generateThisExpression () {
        _.js += 'this';
    },

    Identifier: function generateIdentifier ($expr, precedence, flag) {
        _.js += $expr.name;
    },

    ImportExpression: function generateImportExpression ($expr, settings) {
        var parenthesize = Precedence.Call < settings.precedence;
        var $source      = $expr.source;

        if (parenthesize)
            _.js += '(';

        _.js += 'import(';

        ExprGen[$source.type]($source, Preset.e4);

        _.js += ')';

        if (parenthesize)
            _.js += ')';
    },

    ImportSpecifier: function generateImportSpecifier ($expr) {
        _.js += $expr.imported.name;

        if ($expr.local)
            _.js += _.space + 'as' + _.space + $expr.local.name;
    },

    ExportSpecifier: function generateImportOrExportSpecifier ($expr) {
        _.js += $expr.local.name;

        if ($expr.exported)
            _.js += _.space + 'as' + _.space + $expr.exported.name;
    },

    ChainExpression: function generateChainExpression ($expr, settings) {
        var parenthesize = Precedence.OptionalChaining < settings.precedence;
        var $expression  = $expr.expression;

        settings = settings || {};

        var newSettings  = {
            precedence: Precedence.OptionalChaining,
            allowIn:    settings.allowIn ,
            allowCall:  settings.allowCall,

            allowUnparenthesizedNew: settings.allowUnparenthesizedNew
        }

        if (parenthesize) {
            newSettings.allowCall = true;
            _.js += '(';
        }

        ExprGen[$expression.type]($expression, newSettings);

        if (parenthesize)
            _.js += ')';
    },

    Literal: function generateLiteral ($expr) {
        if (extra.raw && $expr.raw !== void 0)
            _.js += $expr.raw;

        else if ($expr.value === null)
            _.js += 'null';

        else {
            var valueType = typeof $expr.value;

            if (valueType === 'string')
                _.js += escapeString($expr.value);

            else if (valueType === 'number')
                _.js += generateNumber($expr.value);

            else if (valueType === 'boolean')
                _.js += $expr.value ? 'true' : 'false';

            else
                _.js += generateRegExp($expr.value);
        }
    },

    GeneratorExpression: generateGeneratorOrComprehensionExpression,

    ComprehensionExpression: generateGeneratorOrComprehensionExpression,

    ComprehensionBlock: function generateComprehensionBlock ($expr) {
        var $left   = $expr.left,
            leftJs  = void 0,
            rightJs = exprToJs($expr.right, Preset.e5);

        if ($left.type === Syntax.VariableDeclaration)
            leftJs = $left.kind + _.space + stmtToJs($left.declarations[0], Preset.s6);

        else
            leftJs = exprToJs($left, Preset.e10);

        leftJs = join(leftJs, $expr.of ? 'of' : 'in');

        _.js += 'for' + _.optSpace + '(' + join(leftJs, rightJs) + ')';
    },

    RestElement: function generateRestElement ($node) {
        _.js += '...' + $node.argument.name;
    },

    SpreadElement: function generateSpreadElement ($expr) {
        var $arg = $expr.argument;

        _.js += '...';
        ExprGen[$arg.type]($arg, Preset.e4);
    },

    TaggedTemplateExpression: function generateTaggedTemplateExpression ($expr, settings) {
        var $tag         = $expr.tag,
            $quasi       = $expr.quasi,
            parenthesize = Precedence.TaggedTemplate < settings.precedence;

        if (parenthesize)
            _.js += '(';

        ExprGen[$tag.type]($tag, Preset.e11(settings.allowCall));
        ExprGen[$quasi.type]($quasi, Preset.e12);

        if (parenthesize)
            _.js += ')';
    },

    TemplateElement: function generateTemplateElement ($expr) {
        //NOTE: Don't use "cooked". Since tagged template can use raw template
        // representation. So if we do so, it breaks the script semantics.
        _.js += $expr.value.raw;
    },

    TemplateLiteral: function generateTemplateLiteral ($expr) {
        var $quasis      = $expr.quasis,
            $childExprs  = $expr.expressions,
            quasiCount   = $quasis.length,
            lastQuasiIdx = quasiCount - 1;

        _.js += '`';

        for (var i = 0; i < quasiCount; ++i) {
            var $quasi = $quasis[i];

            ExprGen[$quasi.type]($quasi, Preset.e13);

            if (i !== lastQuasiIdx) {
                var $childExpr = $childExprs[i];

                _.js += '${' + _.optSpace;
                ExprGen[$childExpr.type]($childExpr, Preset.e5);
                _.js += _.optSpace + '}';
            }
        }

        _.js += '`';
    },

    Super: function generateSuper () {
        _.js += 'super';
    }
};


//-------------------------------------------------===------------------------------------------------------
//                                              Statements
//-------------------------------------------------===------------------------------------------------------


//Regular expressions
var EXPR_STMT_UNALLOWED_EXPR_REGEXP = /^{|^class(?:\s|{)|^(async )?function(?:\s|\*|\()/;


//Common statement generators
function generateTryStatementHandlers (stmtJs, $finalizer, handlers) {
    var handlerCount   = handlers.length,
        lastHandlerIdx = handlerCount - 1;

    for (var i = 0; i < handlerCount; ++i) {
        var handlerJs = stmtToJs(handlers[i], Preset.s7);

        stmtJs = join(stmtJs, handlerJs);

        if ($finalizer || i !== lastHandlerIdx)
            stmtJs += adoptionSuffix(handlers[i].body);
    }

    return stmtJs;
}

function generateForStatementIterator ($op, $stmt, settings) {
    var $body                 = $stmt.body,
        $left                 = $stmt.left,
        bodySemicolonOptional = !semicolons && settings.semicolonOptional,
        prevIndent1           = shiftIndent(),
        awaitStr              = $stmt.await ? ' await' : '',
        stmtJs                = 'for' + awaitStr + _.optSpace + '(';

    if ($left.type === Syntax.VariableDeclaration) {
        var prevIndent2 = shiftIndent();

        stmtJs += $left.kind + _.space + stmtToJs($left.declarations[0], Preset.s6);
        _.indent = prevIndent2;
    }

    else
        stmtJs += exprToJs($left, Preset.e10);

    stmtJs = join(stmtJs, $op);

    var rightJs = exprToJs($stmt.right, Preset.e4);

    stmtJs = join(stmtJs, rightJs) + ')';

    _.indent = prevIndent1;

    _.js += stmtJs + adoptionPrefix($body);
    StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional));
}


//Statement generator dictionary
var StmtRawGen = {
    BlockStatement: function generateBlockStatement ($stmt, settings) {
        var $body      = $stmt.body,
            len        = $body.length,
            lastIdx    = len - 1,
            prevIndent = shiftIndent();

        _.js += '{' + _.newline;

        for (var i = 0; i < len; i++) {
            var $item = $body[i];

            _.js += _.indent;
            StmtGen[$item.type]($item, Preset.s1(settings.functionBody, i === lastIdx));
            _.js += _.newline;
        }

        _.indent = prevIndent;
        _.js += _.indent + '}';
    },

    BreakStatement: function generateBreakStatement ($stmt, settings) {
        if ($stmt.label)
            _.js += 'break ' + $stmt.label.name;

        else
            _.js += 'break';

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    ContinueStatement: function generateContinueStatement ($stmt, settings) {
        if ($stmt.label)
            _.js += 'continue ' + $stmt.label.name;

        else
            _.js += 'continue';

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    ClassBody: function generateClassBody ($stmt) {
        var $body       = $stmt.body,
            itemCount   = $body.length,
            lastItemIdx = itemCount - 1,
            prevIndent  = shiftIndent();

        _.js += '{' + _.newline;

        for (var i = 0; i < itemCount; i++) {
            var $item    = $body[i],
                itemType = $item.type || Syntax.Property;

            _.js += _.indent;
            ExprGen[itemType]($item, Preset.e5);

            if (i !== lastItemIdx)
                _.js += _.newline;
        }

        _.indent = prevIndent;
        _.js += _.newline + _.indent + '}';
    },

    ClassDeclaration: function generateClassDeclaration ($stmt) {
        var $body  = $stmt.body,
            $super = $stmt.superClass,
            js     = 'class ' + $stmt.id.name;

        if ($super) {
            var superJs = exprToJs($super, Preset.e4);

            js += _.space + join('extends', superJs);
        }

        _.js += js + _.optSpace;
        StmtGen[$body.type]($body, Preset.s2);
    },

    DirectiveStatement: function generateDirectiveStatement ($stmt, settings) {
        if (extra.raw && $stmt.raw)
            _.js += $stmt.raw;

        else
            _.js += escapeDirective($stmt.directive);

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    DoWhileStatement: function generateDoWhileStatement ($stmt, settings) {
        var $body  = $stmt.body,
            $test  = $stmt.test,
            bodyJs = adoptionPrefix($body) +
                     stmtToJs($body, Preset.s7) +
                     adoptionSuffix($body);

        //NOTE: Because `do 42 while (cond)` is Syntax Error. We need semicolon.
        var stmtJs = join('do', bodyJs);

        _.js += join(stmtJs, 'while' + _.optSpace + '(');
        ExprGen[$test.type]($test, Preset.e5);
        _.js += ')';

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    CatchClause: function generateCatchClause ($stmt) {
        var $param     = $stmt.param,
            $guard     = $stmt.guard,
            $body      = $stmt.body,
            prevIndent = shiftIndent();

        _.js += 'catch' + _.optSpace;

        if ($param) {
           _.js += '(';
           ExprGen[$param.type]($param, Preset.e5);
        }

        if ($guard) {
            _.js += ' if ';
            ExprGen[$guard.type]($guard, Preset.e5);
        }

        _.indent = prevIndent;
        if ($param) {
           _.js += ')';
        }

        _.js += adoptionPrefix($body);
        StmtGen[$body.type]($body, Preset.s7);
    },

    DebuggerStatement: function generateDebuggerStatement ($stmt, settings) {
        _.js += 'debugger';

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    EmptyStatement: function generateEmptyStatement () {
        _.js += ';';
    },

    ExportAllDeclaration: function ($stmt, settings) {
        StmtRawGen.ExportDeclaration($stmt, settings, true);
    },

    ExportDeclaration: function generateExportDeclaration ($stmt, settings, exportAll) {
        var $specs        = $stmt.specifiers,
            $decl         = $stmt.declaration,
            withSemicolon = semicolons || !settings.semicolonOptional;

        // export default AssignmentExpression[In] ;
        if ($stmt['default']) {
            var declJs = exprToJs($decl, Preset.e4);

            _.js += join('export default', declJs);

            if (withSemicolon)
                _.js += ';';
        }

        // export * FromClause ;
        // export ExportClause[NoReference] FromClause ;
        // export ExportClause ;
        else if ($specs || exportAll) {
            var stmtJs = 'export';

            if (exportAll)
                stmtJs += _.optSpace + '*';

            else if ($specs.length === 0)
                stmtJs += _.optSpace + '{' + _.optSpace + '}';

            else if ($specs[0].type === Syntax.ExportBatchSpecifier) {
                var specJs = exprToJs($specs[0], Preset.e5);

                stmtJs = join(stmtJs, specJs);
            }

            else {
                var prevIndent  = shiftIndent(),
                    specCount   = $specs.length,
                    lastSpecIdx = specCount - 1;

                stmtJs += _.optSpace + '{';

                for (var i = 0; i < specCount; ++i) {
                    stmtJs += _.newline + _.indent;
                    stmtJs += exprToJs($specs[i], Preset.e5);

                    if (i !== lastSpecIdx)
                        stmtJs += ',';
                }

                _.indent = prevIndent;
                stmtJs += _.newline + _.indent + '}';
            }

            if ($stmt.source) {
                _.js += join(stmtJs, 'from' + _.optSpace);
                ExprGen.Literal($stmt.source);
            }

            else
                _.js += stmtJs;

            if (withSemicolon)
                _.js += ';';
        }

        // export VariableStatement
        // export Declaration[Default]
        else if ($decl) {
            var declJs = stmtToJs($decl, Preset.s4(!withSemicolon));

            _.js += join('export', declJs);
        }
    },

    ExportNamedDeclaration: function ($stmt, settings) {
        StmtRawGen.ExportDeclaration($stmt, settings);
    },

    ExpressionStatement: function generateExpressionStatement ($stmt, settings) {
        var exprJs       = exprToJs($stmt.expression, Preset.e5),
            parenthesize = EXPR_STMT_UNALLOWED_EXPR_REGEXP.test(exprJs) ||
                           (directive &&
                            settings.directiveContext &&
                            $stmt.expression.type === Syntax.Literal &&
                            typeof $stmt.expression.value === 'string');

        //NOTE: '{', 'function', 'class' are not allowed in expression statement.
        // Therefore, they should be parenthesized.
        if (parenthesize)
            _.js += '(' + exprJs + ')';

        else
            _.js += exprJs;

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    ImportDeclaration: function generateImportDeclaration ($stmt, settings) {
        var $specs    = $stmt.specifiers,
            stmtJs    = 'import',
            specCount = $specs.length;

        //NOTE: If no ImportClause is present,
        // this should be `import ModuleSpecifier` so skip `from`
        // ModuleSpecifier is StringLiteral.
        if (specCount) {
            var hasBinding    = !!$specs[0]['default'],
                firstNamedIdx = hasBinding ? 1 : 0,
                lastSpecIdx   = specCount - 1;

            // ImportedBinding
            if (hasBinding)
                stmtJs = join(stmtJs, $specs[0].id.name);

            // NamedImports
            if (firstNamedIdx < specCount) {
                if (hasBinding)
                    stmtJs += ',';

                stmtJs += _.optSpace + '{';

                // import { ... } from "...";
                if (firstNamedIdx === lastSpecIdx)
                    stmtJs += _.optSpace + exprToJs($specs[firstNamedIdx], Preset.e5) + _.optSpace;

                else {
                    var prevIndent = shiftIndent();

                    // import {
                    //    ...,
                    //    ...,
                    // } from "...";
                    for (var i = firstNamedIdx; i < specCount; i++) {
                        stmtJs += _.newline + _.indent + exprToJs($specs[i], Preset.e5);

                        if (i !== lastSpecIdx)
                            stmtJs += ',';
                    }

                    _.indent = prevIndent;
                    stmtJs += _.newline + _.indent;
                }

                stmtJs += '}' + _.optSpace;
            }

            stmtJs = join(stmtJs, 'from')
        }

        _.js += stmtJs + _.optSpace;
        ExprGen.Literal($stmt.source);

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    VariableDeclarator: function generateVariableDeclarator ($stmt, settings) {
        var $id         = $stmt.id,
            $init       = $stmt.init,
            genSettings = Preset.e1(settings.allowIn);

        if ($init) {
            ExprGen[$id.type]($id, genSettings);
            _.js += _.optSpace + '=' + _.optSpace;
            ExprGen[$init.type]($init, genSettings, $stmt);
        }

        else {
            if ($id.type === Syntax.Identifier)
                _.js += $id.name;

            else
                ExprGen[$id.type]($id, genSettings);
        }
    },

    VariableDeclaration: function generateVariableDeclaration ($stmt, settings) {
        var $decls          = $stmt.declarations,
            len             = $decls.length,
            prevIndent      = len > 1 ? shiftIndent() : _.indent,
            declGenSettings = Preset.s3(settings.allowIn);

        _.js += $stmt.kind;

        for (var i = 0; i < len; i++) {
            var $decl = $decls[i];

            _.js += i === 0 ? _.space : (',' + _.optSpace);
            StmtGen[$decl.type]($decl, declGenSettings);
        }

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';

        _.indent = prevIndent;
    },

    ThrowStatement: function generateThrowStatement ($stmt, settings) {
        var argJs = exprToJs($stmt.argument, Preset.e5);

        _.js += join('throw', argJs);

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    TryStatement: function generateTryStatement ($stmt) {
        var $block     = $stmt.block,
            $finalizer = $stmt.finalizer,
            stmtJs     = 'try' +
                         adoptionPrefix($block) +
                         stmtToJs($block, Preset.s7) +
                         adoptionSuffix($block);

        var $handlers = $stmt.handlers || $stmt.guardedHandlers;

        if ($handlers)
            stmtJs = generateTryStatementHandlers(stmtJs, $finalizer, $handlers);

        if ($stmt.handler) {
            $handlers = isArray($stmt.handler) ? $stmt.handler : [$stmt.handler];
            stmtJs    = generateTryStatementHandlers(stmtJs, $finalizer, $handlers);
        }

        if ($finalizer) {
            stmtJs = join(stmtJs, 'finally' + adoptionPrefix($finalizer));
            stmtJs += stmtToJs($finalizer, Preset.s7);
        }

        _.js += stmtJs;
    },

    SwitchStatement: function generateSwitchStatement ($stmt) {
        var $cases     = $stmt.cases,
            $discr     = $stmt.discriminant,
            prevIndent = shiftIndent();

        _.js += 'switch' + _.optSpace + '(';
        ExprGen[$discr.type]($discr, Preset.e5);
        _.js += ')' + _.optSpace + '{' + _.newline;
        _.indent = prevIndent;

        if ($cases) {
            var caseCount   = $cases.length,
                lastCaseIdx = caseCount - 1;

            for (var i = 0; i < caseCount; i++) {
                var $case = $cases[i];

                _.js += _.indent;
                StmtGen[$case.type]($case, Preset.s4(i === lastCaseIdx));
                _.js += _.newline;
            }
        }

        _.js += _.indent + '}';
    },

    SwitchCase: function generateSwitchCase ($stmt, settings) {
        var $conseqs                = $stmt.consequent,
            $firstConseq            = $conseqs[0],
            $test                   = $stmt.test,
            i                       = 0,
            conseqSemicolonOptional = !semicolons && settings.semicolonOptional,
            conseqCount             = $conseqs.length,
            lastConseqIdx           = conseqCount - 1,
            prevIndent              = shiftIndent();

        if ($test) {
            var testJs = exprToJs($test, Preset.e5);

            _.js += join('case', testJs) + ':';
        }

        else
            _.js += 'default:';


        if (conseqCount && $firstConseq.type === Syntax.BlockStatement) {
            i++;
            _.js += adoptionPrefix($firstConseq);
            StmtGen[$firstConseq.type]($firstConseq, Preset.s7);
        }

        for (; i < conseqCount; i++) {
            var $conseq           = $conseqs[i],
                semicolonOptional = i === lastConseqIdx && conseqSemicolonOptional;

            _.js += _.newline + _.indent;
            StmtGen[$conseq.type]($conseq, Preset.s4(semicolonOptional));
        }

        _.indent = prevIndent;
    },

    IfStatement: function generateIfStatement ($stmt, settings) {
        var $conseq           = $stmt.consequent,
            $test             = $stmt.test,
            prevIndent        = shiftIndent(),
            semicolonOptional = !semicolons && settings.semicolonOptional;

        _.js += 'if' + _.optSpace + '(';
        ExprGen[$test.type]($test, Preset.e5);
        _.js += ')';
        _.indent = prevIndent;
        _.js += adoptionPrefix($conseq);

        if ($stmt.alternate) {
            var conseq = stmtToJs($conseq, Preset.s7) + adoptionSuffix($conseq),
                alt    = stmtToJs($stmt.alternate, Preset.s4(semicolonOptional));

            if ($stmt.alternate.type === Syntax.IfStatement)
                alt = 'else ' + alt;

            else
                alt = join('else', adoptionPrefix($stmt.alternate) + alt);

            _.js += join(conseq, alt);
        }

        else
            StmtGen[$conseq.type]($conseq, Preset.s4(semicolonOptional));
    },

    ForStatement: function generateForStatement ($stmt, settings) {
        var $init                 = $stmt.init,
            $test                 = $stmt.test,
            $body                 = $stmt.body,
            $update               = $stmt.update,
            bodySemicolonOptional = !semicolons && settings.semicolonOptional,
            prevIndent            = shiftIndent();

        _.js += 'for' + _.optSpace + '(';

        if ($init) {
            if ($init.type === Syntax.VariableDeclaration)
                StmtGen[$init.type]($init, Preset.s6);

            else {
                ExprGen[$init.type]($init, Preset.e14);
                _.js += ';';
            }
        }

        else
            _.js += ';';

        if ($test) {
            _.js += _.optSpace;
            ExprGen[$test.type]($test, Preset.e5);
        }

        _.js += ';';

        if ($update) {
            _.js += _.optSpace;
            ExprGen[$update.type]($update, Preset.e5);
        }

        _.js += ')';
        _.indent = prevIndent;
        _.js += adoptionPrefix($body);
        StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional));
    },

    ForInStatement: function generateForInStatement ($stmt, settings) {
        generateForStatementIterator('in', $stmt, settings);
    },

    ForOfStatement: function generateForOfStatement ($stmt, settings) {
        generateForStatementIterator('of', $stmt, settings);
    },

    LabeledStatement: function generateLabeledStatement ($stmt, settings) {
        var $body                 = $stmt.body,
            bodySemicolonOptional = !semicolons && settings.semicolonOptional,
            prevIndent            = _.indent;

        _.js += $stmt.label.name + ':' + adoptionPrefix($body);

        if ($body.type !== Syntax.BlockStatement)
            prevIndent = shiftIndent();

        StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional));
        _.indent       = prevIndent;
    },

    ModuleDeclaration: function generateModuleDeclaration ($stmt, settings) {
        _.js += 'module' + _.space + $stmt.id.name + _.space + 'from' + _.optSpace;

        ExprGen.Literal($stmt.source);

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    Program: function generateProgram ($stmt) {
        var $body   = $stmt.body,
            len     = $body.length,
            lastIdx = len - 1;

        if (safeConcatenation && len > 0)
            _.js += '\n';

        for (var i = 0; i < len; i++) {
            var $item = $body[i];

            _.js += _.indent;
            StmtGen[$item.type]($item, Preset.s5(!safeConcatenation && i === lastIdx));

            if (i !== lastIdx)
                _.js += _.newline;
        }
    },

    FunctionDeclaration: function generateFunctionDeclaration ($stmt) {
        var isGenerator = !!$stmt.generator;

        if ($stmt.async)
            _.js += 'async ';

        _.js += isGenerator ? ('function*' + _.optSpace) : ('function' + _.space );
        _.js += $stmt.id.name;
        generateFunctionBody($stmt);
    },

    ReturnStatement: function generateReturnStatement ($stmt, settings) {
        var $arg = $stmt.argument;

        if ($arg) {
            var argJs = exprToJs($arg, Preset.e5);

            _.js += join('return', argJs);
        }

        else
            _.js += 'return';

        if (semicolons || !settings.semicolonOptional)
            _.js += ';';
    },

    WhileStatement: function generateWhileStatement ($stmt, settings) {
        var $body                 = $stmt.body,
            $test                 = $stmt.test,
            bodySemicolonOptional = !semicolons && settings.semicolonOptional,
            prevIndent            = shiftIndent();

        _.js += 'while' + _.optSpace + '(';
        ExprGen[$test.type]($test, Preset.e5);
        _.js += ')';
        _.indent = prevIndent;

        _.js += adoptionPrefix($body);
        StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional));
    },

    WithStatement: function generateWithStatement ($stmt, settings) {
        var $body                 = $stmt.body,
            $obj                  = $stmt.object,
            bodySemicolonOptional = !semicolons && settings.semicolonOptional,
            prevIndent            = shiftIndent();

        _.js += 'with' + _.optSpace + '(';
        ExprGen[$obj.type]($obj, Preset.e5);
        _.js += ')';
        _.indent = prevIndent;
        _.js += adoptionPrefix($body);
        StmtGen[$body.type]($body, Preset.s4(bodySemicolonOptional));
    }
};

function generateStatement ($stmt, option) {
    StmtGen[$stmt.type]($stmt, option);
}

//CodeGen
//-----------------------------------------------------------------------------------
function exprToJs ($expr, settings, $parent) {
    var savedJs = _.js;
    _.js        = '';

    ExprGen[$expr.type]($expr, settings, $parent);

    var src = _.js;
    _.js    = savedJs;

    return src;
}

function stmtToJs ($stmt, settings) {
    var savedJs = _.js;
    _.js        = '';

    StmtGen[$stmt.type]($stmt, settings);

    var src = _.js;
    _.js    = savedJs;

    return src;
}

function run ($node) {
    _.js = '';

    if (StmtGen[$node.type])
        StmtGen[$node.type]($node, Preset.s7);

    else
        ExprGen[$node.type]($node, Preset.e19);

    return _.js;
}

function wrapExprGen (gen) {
    return function ($expr, settings) {
        if (extra.verbatim && $expr.hasOwnProperty(extra.verbatim))
            generateVerbatim($expr, settings);

        else
            gen($expr, settings);
    }
}

function createExprGenWithExtras () {
    var gens = {};

    for (var key in ExprRawGen) {
        if (ExprRawGen.hasOwnProperty(key))
            gens[key] = wrapExprGen(ExprRawGen[key]);
    }

    return gens;
}


//Strings
var _ = {
    js:         '',
    newline:    '\n',
    optSpace:   ' ',
    space:      ' ',
    indentUnit: '    ',
    indent:     ''
};


//Generators
var ExprGen = void 0,
    StmtGen = StmtRawGen;


exports.generate = function ($node, options) {
    var defaultOptions = getDefaultOptions(), result, pair;

    if (options != null) {
        //NOTE: 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);
        _.indentUnit = options.format.indent.style;
        if (typeof options.base === 'string') {
            _.indent = options.base;
        }
        else {
            _.indent = stringRepeat(_.indentUnit, options.format.indent.base);
        }
    }
    else {
        options      = defaultOptions;
        _.indentUnit = options.format.indent.style;
        _.indent     = stringRepeat(_.indentUnit, 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;
    _.optSpace = options.format.space;

    if (options.format.compact)
        _.newline = _.optSpace = _.indentUnit = _.indent = '';

    _.space           = _.optSpace ? _.optSpace : ' ';
    parentheses       = options.format.parentheses;
    semicolons        = options.format.semicolons;
    safeConcatenation = options.format.safeConcatenation;
    directive         = options.directive;
    parse             = json ? null : options.parse;
    extra             = options;

    if (extra.verbatim)
        ExprGen = createExprGenWithExtras();

    else
        ExprGen = ExprRawGen;

    return run($node);
};