/*
 * Copyright (C) 2011 Google Inc. All rights reserved.
 *
 * 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.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * 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 THE COPYRIGHT
 * OWNER OR CONTRIBUTORS 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.
 */

function FormattedContentBuilder(content, mapping, originalOffset, formattedOffset)
{
    this._originalContent = content;
    this._originalOffset = originalOffset;
    this._lastOriginalPosition = 0;

    this._formattedContent = [];
    this._formattedContentLength = 0;
    this._formattedOffset = formattedOffset;
    this._lastFormattedPosition = 0;

    this._mapping = mapping;

    this._lineNumber = 0;
    this._nestingLevelLevel = 0;
}

FormattedContentBuilder.prototype = {
    addToken: function(token)
    {
        for (var i = 0; i < token.comments_before.length; ++i)
            this._addComment(token.comments_before[i]);

        while (this._lineNumber < token.line) {
            this._addText("\n");
            this._addIndent();
            this._needNewLine = false;
            this._lineNumber += 1;
        }

        if (this._needNewLine) {
            this._addText("\n");
            this._addIndent();
            this._needNewLine = false;
        }

        if (token.pos - this._lastOriginalPosition !== this._formattedContentLength - this._lastFormattedPosition) {
            this._mapping.original.push(this._originalOffset + token.pos);
            this._lastOriginalPosition = token.pos;
            this._mapping.formatted.push(this._formattedOffset + this._formattedContentLength);
            this._lastFormattedPosition = this._formattedContentLength;
        }

        this._addText(this._originalContent.substring(token.pos, token.endPos));
        this._lineNumber = token.endLine;
    },

    addSpace: function()
    {
        this._addText(" ");
    },

    addNewLine: function()
    {
        this._needNewLine = true;
    },

    increaseNestingLevel: function()
    {
        this._nestingLevelLevel += 1;
    },

    decreaseNestingLevel: function()
    {
        this._nestingLevelLevel -= 1;
    },

    content: function()
    {
        return this._formattedContent.join("");
    },

    mapping: function()
    {
        return { original: this._originalPositions, formatted: this._formattedPositions };
    },

    _addIndent: function()
    {
        for (var i = 0; i < this._nestingLevelLevel * 4; ++i)
            this.addSpace();
    },

    _addComment: function(comment)
    {
        if (this._lineNumber < comment.line) {
            for (var j = this._lineNumber; j < comment.line; ++j)
                this._addText("\n");
            this._lineNumber = comment.line;
            this._needNewLine = false;
            this._addIndent();
        } else
            this.addSpace();

        if (comment.type === "comment1")
            this._addText("//");
        else
            this._addText("/*");

        this._addText(comment.value);

        if (comment.type !== "comment1") {
            this._addText("*/");
            var position;
            while ((position = comment.value.indexOf("\n", position + 1)) !== -1)
                this._lineNumber += 1;
        }
    },

    _addText: function(text)
    {
        this._formattedContent.push(text);
        this._formattedContentLength += text.length;
    }
}

var tokens = [
    ["EOS"],
    ["LPAREN", "("], ["RPAREN", ")"], ["LBRACK", "["], ["RBRACK", "]"], ["LBRACE", "{"], ["RBRACE", "}"], ["COLON", ":"], ["SEMICOLON", ";"], ["PERIOD", "."], ["CONDITIONAL", "?"],
    ["INC", "++"], ["DEC", "--"],
    ["ASSIGN", "="], ["ASSIGN_BIT_OR", "|="], ["ASSIGN_BIT_XOR", "^="], ["ASSIGN_BIT_AND", "&="], ["ASSIGN_SHL", "<<="], ["ASSIGN_SAR", ">>="], ["ASSIGN_SHR", ">>>="],
    ["ASSIGN_ADD", "+="], ["ASSIGN_SUB", "-="], ["ASSIGN_MUL", "*="], ["ASSIGN_DIV", "/="], ["ASSIGN_MOD", "%="],
    ["COMMA", ","], ["OR", "||"], ["AND", "&&"], ["BIT_OR", "|"], ["BIT_XOR", "^"], ["BIT_AND", "&"], ["SHL", "<<"], ["SAR", ">>"], ["SHR", ">>>"],
    ["ADD", "+"], ["SUB", "-"], ["MUL", "*"], ["DIV", "/"], ["MOD", "%"],
    ["EQ", "=="], ["NE", "!="], ["EQ_STRICT", "==="], ["NE_STRICT", "!=="], ["LT", "<"], ["GT", ">"], ["LTE", "<="], ["GTE", ">="],
    ["INSTANCEOF", "instanceof"], ["IN", "in"], ["NOT", "!"], ["BIT_NOT", "~"], ["DELETE", "delete"], ["TYPEOF", "typeof"], ["VOID", "void"],
    ["BREAK", "break"], ["CASE", "case"], ["CATCH", "catch"], ["CONTINUE", "continue"], ["DEBUGGER", "debugger"], ["DEFAULT", "default"], ["DO", "do"], ["ELSE", "else"], ["FINALLY", "finally"],
    ["FOR", "for"], ["FUNCTION", "function"], ["IF", "if"], ["NEW", "new"], ["RETURN", "return"], ["SWITCH", "switch"], ["THIS", "this"], ["THROW", "throw"], ["TRY", "try"], ["VAR", "var"],
    ["WHILE", "while"], ["WITH", "with"], ["NULL_LITERAL", "null"], ["TRUE_LITERAL", "true"], ["FALSE_LITERAL", "false"], ["NUMBER"], ["STRING"], ["IDENTIFIER"], ["CONST", "const"]
];

var Tokens = {};
for (var i = 0; i < tokens.length; ++i)
    Tokens[tokens[i][0]] = i;

var TokensByValue = {};
for (var i = 0; i < tokens.length; ++i) {
    if (tokens[i][1])
        TokensByValue[tokens[i][1]] = i;
}

var TokensByType = {
    "eof": Tokens.EOS,
    "name": Tokens.IDENTIFIER,
    "num": Tokens.NUMBER,
    "regexp": Tokens.DIV,
    "string": Tokens.STRING
};

function Tokenizer(content)
{
    this._readNextToken = parse.tokenizer(content);
    this._state = this._readNextToken.context();
}

Tokenizer.prototype = {
    content: function()
    {
        return this._state.text;
    },

    next: function(forceRegexp)
    {
        var uglifyToken = this._readNextToken(forceRegexp);
        uglifyToken.endPos = this._state.pos;
        uglifyToken.endLine = this._state.line;
        uglifyToken.token = this._convertUglifyToken(uglifyToken);
        return uglifyToken;
    },

    _convertUglifyToken: function(uglifyToken)
    {
        var token = TokensByType[uglifyToken.type];
        if (typeof token === "number")
            return token;
        token = TokensByValue[uglifyToken.value];
        if (typeof token === "number")
            return token;
        throw "Unknown token type " + uglifyToken.type;
    }
}

function JavaScriptFormatter(tokenizer, builder)
{
    this._tokenizer = tokenizer;
    this._builder = builder;
    this._token = null;
    this._nextToken = this._tokenizer.next();
}

JavaScriptFormatter.prototype = {
    format: function()
    {
        this._parseSourceElements(Tokens.EOS);
        this._consume(Tokens.EOS);
    },

    _peek: function()
    {
        return this._nextToken.token;
    },

    _next: function()
    {
        if (this._token && this._token.token === Tokens.EOS)
            throw "Unexpected EOS token";

        this._builder.addToken(this._nextToken);
        this._token = this._nextToken;
        this._nextToken = this._tokenizer.next(this._forceRegexp);
        this._forceRegexp = false;
        return this._token.token;
    },

    _consume: function(token)
    {
        var next = this._next();
        if (next !== token)
            throw "Unexpected token in consume: expected " + token + ", actual " + next;
    },

    _expect: function(token)
    {
        var next = this._next();
        if (next !== token)
            throw "Unexpected token: expected " + token + ", actual " + next;
    },

    _expectSemicolon: function()
    {
        if (this._peek() === Tokens.SEMICOLON)
            this._consume(Tokens.SEMICOLON);
    },

    _hasLineTerminatorBeforeNext: function()
    {
        return this._nextToken.nlb;
    },

    _parseSourceElements: function(endToken)
    {
        while (this._peek() !== endToken) {
            this._parseStatement();
            this._builder.addNewLine();
        }
    },

    _parseStatementOrBlock: function()
    {
        if (this._peek() === Tokens.LBRACE) {
            this._builder.addSpace();
            this._parseBlock();
            return true;
        }

        this._builder.addNewLine();
        this._builder.increaseNestingLevel();
        this._parseStatement();
        this._builder.decreaseNestingLevel();
    },

    _parseStatement: function()
    {
        switch (this._peek()) {
        case Tokens.LBRACE:
            return this._parseBlock();
        case Tokens.CONST:
        case Tokens.VAR:
            return this._parseVariableStatement();
        case Tokens.SEMICOLON:
            return this._next();
        case Tokens.IF:
            return this._parseIfStatement();
        case Tokens.DO:
            return this._parseDoWhileStatement();
        case Tokens.WHILE:
            return this._parseWhileStatement();
        case Tokens.FOR:
            return this._parseForStatement();
        case Tokens.CONTINUE:
            return this._parseContinueStatement();
        case Tokens.BREAK:
            return this._parseBreakStatement();
        case Tokens.RETURN:
            return this._parseReturnStatement();
        case Tokens.WITH:
            return this._parseWithStatement();
        case Tokens.SWITCH:
            return this._parseSwitchStatement();
        case Tokens.THROW:
            return this._parseThrowStatement();
        case Tokens.TRY:
            return this._parseTryStatement();
        case Tokens.FUNCTION:
            return this._parseFunctionDeclaration();
        case Tokens.DEBUGGER:
            return this._parseDebuggerStatement();
        default:
            return this._parseExpressionOrLabelledStatement();
        }
    },

    _parseFunctionDeclaration: function()
    {
        this._expect(Tokens.FUNCTION);
        this._builder.addSpace();
        this._expect(Tokens.IDENTIFIER);
        this._parseFunctionLiteral()
    },

    _parseBlock: function()
    {
        this._expect(Tokens.LBRACE);
        this._builder.addNewLine();
        this._builder.increaseNestingLevel();
        while (this._peek() !== Tokens.RBRACE) {
            this._parseStatement();
            this._builder.addNewLine();
        }
        this._builder.decreaseNestingLevel();
        this._expect(Tokens.RBRACE);
    },

    _parseVariableStatement: function()
    {
        this._parseVariableDeclarations();
        this._expectSemicolon();
    },

    _parseVariableDeclarations: function()
    {
        if (this._peek() === Tokens.VAR)
            this._consume(Tokens.VAR);
        else
            this._consume(Tokens.CONST)
        this._builder.addSpace();

        var isFirstVariable = true;
        do {
            if (!isFirstVariable) {
                this._consume(Tokens.COMMA);
                this._builder.addSpace();
            }
            isFirstVariable = false;
            this._expect(Tokens.IDENTIFIER);
            if (this._peek() === Tokens.ASSIGN) {
                this._builder.addSpace();
                this._consume(Tokens.ASSIGN);
                this._builder.addSpace();
                this._parseAssignmentExpression();
            }
        } while (this._peek() === Tokens.COMMA);
    },

    _parseExpressionOrLabelledStatement: function()
    {
        this._parseExpression();
        if (this._peek() === Tokens.COLON) {
            this._expect(Tokens.COLON);
            this._builder.addSpace();
            this._parseStatement();
        }
        this._expectSemicolon();
    },

    _parseIfStatement: function()
    {
        this._expect(Tokens.IF);
        this._builder.addSpace();
        this._expect(Tokens.LPAREN);
        this._parseExpression();
        this._expect(Tokens.RPAREN);

        var isBlock = this._parseStatementOrBlock();
        if (this._peek() === Tokens.ELSE) {
            if (isBlock)
                this._builder.addSpace();
            else
                this._builder.addNewLine();
            this._next();

            if (this._peek() === Tokens.IF) {
                this._builder.addSpace();
                this._parseStatement();
            } else
                this._parseStatementOrBlock();
        }
    },

    _parseContinueStatement: function()
    {
        this._expect(Tokens.CONTINUE);
        var token = this._peek();
        if (!this._hasLineTerminatorBeforeNext() && token !== Tokens.SEMICOLON && token !== Tokens.RBRACE && token !== Tokens.EOS) {
            this._builder.addSpace();
            this._expect(Tokens.IDENTIFIER);
        }
        this._expectSemicolon();
    },

    _parseBreakStatement: function()
    {
        this._expect(Tokens.BREAK);
        var token = this._peek();
        if (!this._hasLineTerminatorBeforeNext() && token !== Tokens.SEMICOLON && token !== Tokens.RBRACE && token !== Tokens.EOS) {
            this._builder.addSpace();
            this._expect(Tokens.IDENTIFIER);
        }
        this._expectSemicolon();
    },

    _parseReturnStatement: function()
    {
        this._expect(Tokens.RETURN);
        var token = this._peek();
        if (!this._hasLineTerminatorBeforeNext() && token !== Tokens.SEMICOLON && token !== Tokens.RBRACE && token !== Tokens.EOS) {
            this._builder.addSpace();
            this._parseExpression();
        }
        this._expectSemicolon();
    },

    _parseWithStatement: function()
    {
        this._expect(Tokens.WITH);
        this._builder.addSpace();
        this._expect(Tokens.LPAREN);
        this._parseExpression();
        this._expect(Tokens.RPAREN);
        this._parseStatementOrBlock();
    },

    _parseCaseClause: function()
    {
        if (this._peek() === Tokens.CASE) {
            this._expect(Tokens.CASE);
            this._builder.addSpace();
            this._parseExpression();
        } else
            this._expect(Tokens.DEFAULT);
        this._expect(Tokens.COLON);
        this._builder.addNewLine();

        this._builder.increaseNestingLevel();
        while (this._peek() !== Tokens.CASE && this._peek() !== Tokens.DEFAULT && this._peek() !== Tokens.RBRACE) {
            this._parseStatement();
            this._builder.addNewLine();
        }
        this._builder.decreaseNestingLevel();
    },

    _parseSwitchStatement: function()
    {
        this._expect(Tokens.SWITCH);
        this._builder.addSpace();
        this._expect(Tokens.LPAREN);
        this._parseExpression();
        this._expect(Tokens.RPAREN);
        this._builder.addSpace();

        this._expect(Tokens.LBRACE);
        this._builder.addNewLine();
        this._builder.increaseNestingLevel();
        while (this._peek() !== Tokens.RBRACE)
            this._parseCaseClause();
        this._builder.decreaseNestingLevel();
        this._expect(Tokens.RBRACE);
    },

    _parseThrowStatement: function()
    {
        this._expect(Tokens.THROW);
        this._builder.addSpace();
        this._parseExpression();
        this._expectSemicolon();
    },

    _parseTryStatement: function()
    {
        this._expect(Tokens.TRY);
        this._builder.addSpace();
        this._parseBlock();

        var token = this._peek();
        if (token === Tokens.CATCH) {
            this._builder.addSpace();
            this._consume(Tokens.CATCH);
            this._builder.addSpace();
            this._expect(Tokens.LPAREN);
            this._expect(Tokens.IDENTIFIER);
            this._expect(Tokens.RPAREN);
            this._builder.addSpace();
            this._parseBlock();
            token = this._peek();
        }

        if (token === Tokens.FINALLY) {
            this._consume(Tokens.FINALLY);
            this._builder.addSpace();
            this._parseBlock();
        }
    },

    _parseDoWhileStatement: function()
    {
        this._expect(Tokens.DO);
        var isBlock = this._parseStatementOrBlock();
        if (isBlock)
            this._builder.addSpace();
        else
            this._builder.addNewLine();
        this._expect(Tokens.WHILE);
        this._builder.addSpace();
        this._expect(Tokens.LPAREN);
        this._parseExpression();
        this._expect(Tokens.RPAREN);
        this._expectSemicolon();
    },

    _parseWhileStatement: function()
    {
        this._expect(Tokens.WHILE);
        this._builder.addSpace();
        this._expect(Tokens.LPAREN);
        this._parseExpression();
        this._expect(Tokens.RPAREN);
        this._parseStatementOrBlock();
    },

    _parseForStatement: function()
    {
        this._expect(Tokens.FOR);
        this._builder.addSpace();
        this._expect(Tokens.LPAREN);
        if (this._peek() !== Tokens.SEMICOLON) {
            if (this._peek() === Tokens.VAR || this._peek() === Tokens.CONST) {
                this._parseVariableDeclarations();
                if (this._peek() === Tokens.IN) {
                    this._builder.addSpace();
                    this._consume(Tokens.IN);
                    this._builder.addSpace();
                    this._parseExpression();
                }
            } else
                this._parseExpression();
        }

        if (this._peek() !== Tokens.RPAREN) {
            this._expect(Tokens.SEMICOLON);
            this._builder.addSpace();
            if (this._peek() !== Tokens.SEMICOLON)
                this._parseExpression();
            this._expect(Tokens.SEMICOLON);
            this._builder.addSpace();
            if (this._peek() !== Tokens.RPAREN)
                this._parseExpression();
        }
        this._expect(Tokens.RPAREN);

        this._parseStatementOrBlock();
    },

    _parseExpression: function()
    {
        this._parseAssignmentExpression();
        while (this._peek() === Tokens.COMMA) {
            this._expect(Tokens.COMMA);
            this._builder.addSpace();
            this._parseAssignmentExpression();
        }
    },

    _parseAssignmentExpression: function()
    {
        this._parseConditionalExpression();
        var token = this._peek();
        if (Tokens.ASSIGN <= token && token <= Tokens.ASSIGN_MOD) {
            this._builder.addSpace();
            this._next();
            this._builder.addSpace();
            this._parseAssignmentExpression();
        }
    },

    _parseConditionalExpression: function()
    {
        this._parseBinaryExpression();
        if (this._peek() === Tokens.CONDITIONAL) {
            this._builder.addSpace();
            this._consume(Tokens.CONDITIONAL);
            this._builder.addSpace();
            this._parseAssignmentExpression();
            this._builder.addSpace();
            this._expect(Tokens.COLON);
            this._builder.addSpace();
            this._parseAssignmentExpression();
        }
    },

    _parseBinaryExpression: function()
    {
        this._parseUnaryExpression();
        var token = this._peek();
        while (Tokens.OR <= token && token <= Tokens.IN) {
            this._builder.addSpace();
            this._next();
            this._builder.addSpace();
            this._parseBinaryExpression();
            token = this._peek();
        }
    },

    _parseUnaryExpression: function()
    {
        var token = this._peek();
        if ((Tokens.NOT <= token && token <= Tokens.VOID) || token === Tokens.ADD || token === Tokens.SUB || token ===  Tokens.INC || token === Tokens.DEC) {
            this._next();
            if (token === Tokens.DELETE || token === Tokens.TYPEOF || token === Tokens.VOID)
                this._builder.addSpace();
            this._parseUnaryExpression();
        } else
            return this._parsePostfixExpression();
    },

    _parsePostfixExpression: function()
    {
        this._parseLeftHandSideExpression();
        var token = this._peek();
        if (!this._hasLineTerminatorBeforeNext() && (token === Tokens.INC || token === Tokens.DEC))
            this._next();
    },

    _parseLeftHandSideExpression: function()
    {
        if (this._peek() === Tokens.NEW)
            this._parseNewExpression();
        else
            this._parseMemberExpression();

        while (true) {
            switch (this._peek()) {
            case Tokens.LBRACK:
                this._consume(Tokens.LBRACK);
                this._parseExpression();
                this._expect(Tokens.RBRACK);
                break;

            case Tokens.LPAREN:
                this._parseArguments();
                break;

            case Tokens.PERIOD:
                this._consume(Tokens.PERIOD);
                this._expect(Tokens.IDENTIFIER);
                break;

            default:
                return;
            }
        }
    },

    _parseNewExpression: function()
    {
        this._expect(Tokens.NEW);
        this._builder.addSpace();
        if (this._peek() === Tokens.NEW)
            this._parseNewExpression();
        else
            this._parseMemberExpression();
    },

    _parseMemberExpression: function()
    {
        if (this._peek() === Tokens.FUNCTION) {
            this._expect(Tokens.FUNCTION);
            if (this._peek() === Tokens.IDENTIFIER) {
                this._builder.addSpace();
                this._expect(Tokens.IDENTIFIER);
            }
            this._parseFunctionLiteral();
        } else
            this._parsePrimaryExpression();

        while (true) {
            switch (this._peek()) {
            case Tokens.LBRACK:
                this._consume(Tokens.LBRACK);
                this._parseExpression();
                this._expect(Tokens.RBRACK);
                break;

            case Tokens.PERIOD:
                this._consume(Tokens.PERIOD);
                this._expect(Tokens.IDENTIFIER);
                break;

            case Tokens.LPAREN:
                this._parseArguments();
                break;

            default:
                return;
            }
        }
    },

    _parseDebuggerStatement: function()
    {
        this._expect(Tokens.DEBUGGER);
        this._expectSemicolon();
    },

    _parsePrimaryExpression: function()
    {
        switch (this._peek()) {
        case Tokens.THIS:
            return this._consume(Tokens.THIS);
        case Tokens.NULL_LITERAL:
            return this._consume(Tokens.NULL_LITERAL);
        case Tokens.TRUE_LITERAL:
            return this._consume(Tokens.TRUE_LITERAL);
        case Tokens.FALSE_LITERAL:
            return this._consume(Tokens.FALSE_LITERAL);
        case Tokens.IDENTIFIER:
            return this._consume(Tokens.IDENTIFIER);
        case Tokens.NUMBER:
            return this._consume(Tokens.NUMBER);
        case Tokens.STRING:
            return this._consume(Tokens.STRING);
        case Tokens.ASSIGN_DIV:
            return this._parseRegExpLiteral();
        case Tokens.DIV:
            return this._parseRegExpLiteral();
        case Tokens.LBRACK:
            return this._parseArrayLiteral();
        case Tokens.LBRACE:
            return this._parseObjectLiteral();
        case Tokens.LPAREN:
            this._consume(Tokens.LPAREN);
            this._parseExpression();
            this._expect(Tokens.RPAREN);
            return;
        default:
            return this._next();
        }
    },

    _parseArrayLiteral: function()
    {
        this._expect(Tokens.LBRACK);
        this._builder.increaseNestingLevel();
        while (this._peek() !== Tokens.RBRACK) {
            if (this._peek() !== Tokens.COMMA)
                this._parseAssignmentExpression();
            if (this._peek() !== Tokens.RBRACK) {
                this._expect(Tokens.COMMA);
                this._builder.addSpace();
            }
        }
        this._builder.decreaseNestingLevel();
        this._expect(Tokens.RBRACK);
    },

    _parseObjectLiteralGetSet: function()
    {
        var token = this._peek();
        if (token === Tokens.IDENTIFIER || token === Tokens.NUMBER || token === Tokens.STRING ||
            Tokens.DELETE <= token && token <= Tokens.FALSE_LITERAL ||
            token === Tokens.INSTANCEOF || token === Tokens.IN || token === Tokens.CONST) {
            this._next();
            this._parseFunctionLiteral();
        }
    },

    _parseObjectLiteral: function()
    {
        this._expect(Tokens.LBRACE);
        this._builder.increaseNestingLevel();
        while (this._peek() !== Tokens.RBRACE) {
            var token = this._peek();
            switch (token) {
            case Tokens.IDENTIFIER:
                this._consume(Tokens.IDENTIFIER);
                var name = this._token.value;
                if ((name === "get" || name === "set") && this._peek() !== Tokens.COLON) {
                    this._builder.addSpace();
                    this._parseObjectLiteralGetSet();
                    if (this._peek() !== Tokens.RBRACE) {
                        this._expect(Tokens.COMMA);
                    }
                    continue;
                }
                break;

            case Tokens.STRING:
                this._consume(Tokens.STRING);
                break;

            case Tokens.NUMBER:
                this._consume(Tokens.NUMBER);
                break;

            default:
                this._next();
            }

            this._expect(Tokens.COLON);
            this._builder.addSpace();
            this._parseAssignmentExpression();
            if (this._peek() !== Tokens.RBRACE) {
                this._expect(Tokens.COMMA);
            }
        }
        this._builder.decreaseNestingLevel();

        this._expect(Tokens.RBRACE);
    },

    _parseRegExpLiteral: function()
    {
        if (this._nextToken.type === "regexp")
            this._next();
        else {
            this._forceRegexp = true;
            this._next();
        }
    },

    _parseArguments: function()
    {
        this._expect(Tokens.LPAREN);
        var done = (this._peek() === Tokens.RPAREN);
        while (!done) {
            this._parseAssignmentExpression();
            done = (this._peek() === Tokens.RPAREN);
            if (!done) {
                this._expect(Tokens.COMMA);
                this._builder.addSpace();
            }
        }
        this._expect(Tokens.RPAREN);
    },

    _parseFunctionLiteral: function()
    {
        this._expect(Tokens.LPAREN);
        var done = (this._peek() === Tokens.RPAREN);
        while (!done) {
            this._expect(Tokens.IDENTIFIER);
            done = (this._peek() === Tokens.RPAREN);
            if (!done) {
                this._expect(Tokens.COMMA);
                this._builder.addSpace();
            }
        }
        this._expect(Tokens.RPAREN);
        this._builder.addSpace();

        this._expect(Tokens.LBRACE);
        this._builder.addNewLine();
        this._builder.increaseNestingLevel();
        this._parseSourceElements(Tokens.RBRACE);
        this._builder.decreaseNestingLevel();
        this._expect(Tokens.RBRACE);
    }
}
