1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @constructor
33 * @param {!FormatterWorker.JavaScriptTokenizer} tokenizer
34 * @param {!FormatterWorker.JavaScriptFormattedContentBuilder} builder
35 */
36FormatterWorker.JavaScriptFormatter = function(tokenizer, builder)
37{
38    this._tokenizer = tokenizer;
39    this._builder = builder;
40    this._token = null;
41    this._nextToken = this._tokenizer.next();
42}
43
44FormatterWorker.JavaScriptFormatter.prototype = {
45    format: function()
46    {
47        this._parseSourceElements(FormatterWorker.JavaScriptTokens.EOS);
48        this._consume(FormatterWorker.JavaScriptTokens.EOS);
49    },
50
51    /**
52     * @return {string}
53     */
54    _peek: function()
55    {
56        return this._nextToken.token;
57    },
58
59    /**
60     * @return {string}
61     */
62    _next: function()
63    {
64        if (this._token && this._token.token === FormatterWorker.JavaScriptTokens.EOS)
65            throw "Unexpected EOS token";
66
67        this._builder.addToken(this._nextToken);
68        this._token = this._nextToken;
69        this._nextToken = this._tokenizer.next(this._forceRegexp);
70        this._forceRegexp = false;
71        return this._token.token;
72    },
73
74    /**
75     * @param {string} token
76     */
77    _consume: function(token)
78    {
79        var next = this._next();
80        if (next !== token)
81            throw "Unexpected token in consume: expected " + token + ", actual " + next;
82    },
83
84    /**
85     * @param {string} token
86     */
87    _expect: function(token)
88    {
89        var next = this._next();
90        if (next !== token)
91            throw "Unexpected token: expected " + token + ", actual " + next;
92    },
93
94    _expectSemicolon: function()
95    {
96        if (this._peek() === FormatterWorker.JavaScriptTokens.SEMICOLON)
97            this._consume(FormatterWorker.JavaScriptTokens.SEMICOLON);
98    },
99
100    /**
101     * @return {boolean}
102     */
103    _hasLineTerminatorBeforeNext: function()
104    {
105        return this._nextToken.nlb;
106    },
107
108    /**
109     * @param {string} endToken
110     */
111    _parseSourceElements: function(endToken)
112    {
113        while (this._peek() !== endToken) {
114            this._parseStatement();
115            this._builder.addNewLine();
116        }
117    },
118
119    _parseStatementOrBlock: function()
120    {
121        if (this._peek() === FormatterWorker.JavaScriptTokens.LBRACE) {
122            this._builder.addSpace();
123            this._parseBlock();
124            return true;
125        }
126
127        this._builder.addNewLine();
128        this._builder.increaseNestingLevel();
129        this._parseStatement();
130        this._builder.decreaseNestingLevel();
131    },
132
133    _parseStatement: function()
134    {
135        switch (this._peek()) {
136        case FormatterWorker.JavaScriptTokens.LBRACE:
137            return this._parseBlock();
138        case FormatterWorker.JavaScriptTokens.CONST:
139        case FormatterWorker.JavaScriptTokens.VAR:
140            return this._parseVariableStatement();
141        case FormatterWorker.JavaScriptTokens.SEMICOLON:
142            return this._next();
143        case FormatterWorker.JavaScriptTokens.IF:
144            return this._parseIfStatement();
145        case FormatterWorker.JavaScriptTokens.DO:
146            return this._parseDoWhileStatement();
147        case FormatterWorker.JavaScriptTokens.WHILE:
148            return this._parseWhileStatement();
149        case FormatterWorker.JavaScriptTokens.FOR:
150            return this._parseForStatement();
151        case FormatterWorker.JavaScriptTokens.CONTINUE:
152            return this._parseContinueStatement();
153        case FormatterWorker.JavaScriptTokens.BREAK:
154            return this._parseBreakStatement();
155        case FormatterWorker.JavaScriptTokens.RETURN:
156            return this._parseReturnStatement();
157        case FormatterWorker.JavaScriptTokens.WITH:
158            return this._parseWithStatement();
159        case FormatterWorker.JavaScriptTokens.SWITCH:
160            return this._parseSwitchStatement();
161        case FormatterWorker.JavaScriptTokens.THROW:
162            return this._parseThrowStatement();
163        case FormatterWorker.JavaScriptTokens.TRY:
164            return this._parseTryStatement();
165        case FormatterWorker.JavaScriptTokens.FUNCTION:
166            return this._parseFunctionDeclaration();
167        case FormatterWorker.JavaScriptTokens.DEBUGGER:
168            return this._parseDebuggerStatement();
169        default:
170            return this._parseExpressionOrLabelledStatement();
171        }
172    },
173
174    _parseFunctionDeclaration: function()
175    {
176        this._expect(FormatterWorker.JavaScriptTokens.FUNCTION);
177        this._builder.addSpace();
178        this._expect(FormatterWorker.JavaScriptTokens.IDENTIFIER);
179        this._parseFunctionLiteral()
180    },
181
182    _parseBlock: function()
183    {
184        this._expect(FormatterWorker.JavaScriptTokens.LBRACE);
185        this._builder.addNewLine();
186        this._builder.increaseNestingLevel();
187        while (this._peek() !== FormatterWorker.JavaScriptTokens.RBRACE) {
188            this._parseStatement();
189            this._builder.addNewLine();
190        }
191        this._builder.decreaseNestingLevel();
192        this._expect(FormatterWorker.JavaScriptTokens.RBRACE);
193    },
194
195    _parseVariableStatement: function()
196    {
197        this._parseVariableDeclarations();
198        this._expectSemicolon();
199    },
200
201    _parseVariableDeclarations: function()
202    {
203        if (this._peek() === FormatterWorker.JavaScriptTokens.VAR)
204            this._consume(FormatterWorker.JavaScriptTokens.VAR);
205        else
206            this._consume(FormatterWorker.JavaScriptTokens.CONST)
207        this._builder.addSpace();
208
209        var isFirstVariable = true;
210        do {
211            if (!isFirstVariable) {
212                this._consume(FormatterWorker.JavaScriptTokens.COMMA);
213                this._builder.addSpace();
214            }
215            isFirstVariable = false;
216            this._expect(FormatterWorker.JavaScriptTokens.IDENTIFIER);
217            if (this._peek() === FormatterWorker.JavaScriptTokens.ASSIGN) {
218                this._builder.addSpace();
219                this._consume(FormatterWorker.JavaScriptTokens.ASSIGN);
220                this._builder.addSpace();
221                this._parseAssignmentExpression();
222            }
223        } while (this._peek() === FormatterWorker.JavaScriptTokens.COMMA);
224    },
225
226    _parseExpressionOrLabelledStatement: function()
227    {
228        this._parseExpression();
229        if (this._peek() === FormatterWorker.JavaScriptTokens.COLON) {
230            this._expect(FormatterWorker.JavaScriptTokens.COLON);
231            this._builder.addSpace();
232            this._parseStatement();
233        }
234        this._expectSemicolon();
235    },
236
237    _parseIfStatement: function()
238    {
239        this._expect(FormatterWorker.JavaScriptTokens.IF);
240        this._builder.addSpace();
241        this._expect(FormatterWorker.JavaScriptTokens.LPAREN);
242        this._parseExpression();
243        this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
244
245        var isBlock = this._parseStatementOrBlock();
246        if (this._peek() === FormatterWorker.JavaScriptTokens.ELSE) {
247            if (isBlock)
248                this._builder.addSpace();
249            else
250                this._builder.addNewLine();
251            this._next();
252
253            if (this._peek() === FormatterWorker.JavaScriptTokens.IF) {
254                this._builder.addSpace();
255                this._parseStatement();
256            } else
257                this._parseStatementOrBlock();
258        }
259    },
260
261    _parseContinueStatement: function()
262    {
263        this._expect(FormatterWorker.JavaScriptTokens.CONTINUE);
264        var token = this._peek();
265        if (!this._hasLineTerminatorBeforeNext() && token !== FormatterWorker.JavaScriptTokens.SEMICOLON && token !== FormatterWorker.JavaScriptTokens.RBRACE && token !== FormatterWorker.JavaScriptTokens.EOS) {
266            this._builder.addSpace();
267            this._expect(FormatterWorker.JavaScriptTokens.IDENTIFIER);
268        }
269        this._expectSemicolon();
270    },
271
272    _parseBreakStatement: function()
273    {
274        this._expect(FormatterWorker.JavaScriptTokens.BREAK);
275        var token = this._peek();
276        if (!this._hasLineTerminatorBeforeNext() && token !== FormatterWorker.JavaScriptTokens.SEMICOLON && token !== FormatterWorker.JavaScriptTokens.RBRACE && token !== FormatterWorker.JavaScriptTokens.EOS) {
277            this._builder.addSpace();
278            this._expect(FormatterWorker.JavaScriptTokens.IDENTIFIER);
279        }
280        this._expectSemicolon();
281    },
282
283    _parseReturnStatement: function()
284    {
285        this._expect(FormatterWorker.JavaScriptTokens.RETURN);
286        var token = this._peek();
287        if (!this._hasLineTerminatorBeforeNext() && token !== FormatterWorker.JavaScriptTokens.SEMICOLON && token !== FormatterWorker.JavaScriptTokens.RBRACE && token !== FormatterWorker.JavaScriptTokens.EOS) {
288            this._builder.addSpace();
289            this._parseExpression();
290        }
291        this._expectSemicolon();
292    },
293
294    _parseWithStatement: function()
295    {
296        this._expect(FormatterWorker.JavaScriptTokens.WITH);
297        this._builder.addSpace();
298        this._expect(FormatterWorker.JavaScriptTokens.LPAREN);
299        this._parseExpression();
300        this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
301        this._parseStatementOrBlock();
302    },
303
304    _parseCaseClause: function()
305    {
306        if (this._peek() === FormatterWorker.JavaScriptTokens.CASE) {
307            this._expect(FormatterWorker.JavaScriptTokens.CASE);
308            this._builder.addSpace();
309            this._parseExpression();
310        } else
311            this._expect(FormatterWorker.JavaScriptTokens.DEFAULT);
312        this._expect(FormatterWorker.JavaScriptTokens.COLON);
313        this._builder.addNewLine();
314
315        this._builder.increaseNestingLevel();
316        while (this._peek() !== FormatterWorker.JavaScriptTokens.CASE && this._peek() !== FormatterWorker.JavaScriptTokens.DEFAULT && this._peek() !== FormatterWorker.JavaScriptTokens.RBRACE) {
317            this._parseStatement();
318            this._builder.addNewLine();
319        }
320        this._builder.decreaseNestingLevel();
321    },
322
323    _parseSwitchStatement: function()
324    {
325        this._expect(FormatterWorker.JavaScriptTokens.SWITCH);
326        this._builder.addSpace();
327        this._expect(FormatterWorker.JavaScriptTokens.LPAREN);
328        this._parseExpression();
329        this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
330        this._builder.addSpace();
331
332        this._expect(FormatterWorker.JavaScriptTokens.LBRACE);
333        this._builder.addNewLine();
334        this._builder.increaseNestingLevel();
335        while (this._peek() !== FormatterWorker.JavaScriptTokens.RBRACE)
336            this._parseCaseClause();
337        this._builder.decreaseNestingLevel();
338        this._expect(FormatterWorker.JavaScriptTokens.RBRACE);
339    },
340
341    _parseThrowStatement: function()
342    {
343        this._expect(FormatterWorker.JavaScriptTokens.THROW);
344        this._builder.addSpace();
345        this._parseExpression();
346        this._expectSemicolon();
347    },
348
349    _parseTryStatement: function()
350    {
351        this._expect(FormatterWorker.JavaScriptTokens.TRY);
352        this._builder.addSpace();
353        this._parseBlock();
354
355        var token = this._peek();
356        if (token === FormatterWorker.JavaScriptTokens.CATCH) {
357            this._builder.addSpace();
358            this._consume(FormatterWorker.JavaScriptTokens.CATCH);
359            this._builder.addSpace();
360            this._expect(FormatterWorker.JavaScriptTokens.LPAREN);
361            this._expect(FormatterWorker.JavaScriptTokens.IDENTIFIER);
362            this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
363            this._builder.addSpace();
364            this._parseBlock();
365            token = this._peek();
366        }
367
368        if (token === FormatterWorker.JavaScriptTokens.FINALLY) {
369            this._consume(FormatterWorker.JavaScriptTokens.FINALLY);
370            this._builder.addSpace();
371            this._parseBlock();
372        }
373    },
374
375    _parseDoWhileStatement: function()
376    {
377        this._expect(FormatterWorker.JavaScriptTokens.DO);
378        var isBlock = this._parseStatementOrBlock();
379        if (isBlock)
380            this._builder.addSpace();
381        else
382            this._builder.addNewLine();
383        this._expect(FormatterWorker.JavaScriptTokens.WHILE);
384        this._builder.addSpace();
385        this._expect(FormatterWorker.JavaScriptTokens.LPAREN);
386        this._parseExpression();
387        this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
388        this._expectSemicolon();
389    },
390
391    _parseWhileStatement: function()
392    {
393        this._expect(FormatterWorker.JavaScriptTokens.WHILE);
394        this._builder.addSpace();
395        this._expect(FormatterWorker.JavaScriptTokens.LPAREN);
396        this._parseExpression();
397        this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
398        this._parseStatementOrBlock();
399    },
400
401    _parseForStatement: function()
402    {
403        this._expect(FormatterWorker.JavaScriptTokens.FOR);
404        this._builder.addSpace();
405        this._expect(FormatterWorker.JavaScriptTokens.LPAREN);
406        if (this._peek() !== FormatterWorker.JavaScriptTokens.SEMICOLON) {
407            if (this._peek() === FormatterWorker.JavaScriptTokens.VAR || this._peek() === FormatterWorker.JavaScriptTokens.CONST) {
408                this._parseVariableDeclarations();
409                if (this._peek() === FormatterWorker.JavaScriptTokens.IN) {
410                    this._builder.addSpace();
411                    this._consume(FormatterWorker.JavaScriptTokens.IN);
412                    this._builder.addSpace();
413                    this._parseExpression();
414                }
415            } else
416                this._parseExpression();
417        }
418
419        if (this._peek() !== FormatterWorker.JavaScriptTokens.RPAREN) {
420            this._expect(FormatterWorker.JavaScriptTokens.SEMICOLON);
421            this._builder.addSpace();
422            if (this._peek() !== FormatterWorker.JavaScriptTokens.SEMICOLON)
423                this._parseExpression();
424            this._expect(FormatterWorker.JavaScriptTokens.SEMICOLON);
425            this._builder.addSpace();
426            if (this._peek() !== FormatterWorker.JavaScriptTokens.RPAREN)
427                this._parseExpression();
428        }
429        this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
430
431        this._parseStatementOrBlock();
432    },
433
434    _parseExpression: function()
435    {
436        this._parseAssignmentExpression();
437        while (this._peek() === FormatterWorker.JavaScriptTokens.COMMA) {
438            this._expect(FormatterWorker.JavaScriptTokens.COMMA);
439            this._builder.addSpace();
440            this._parseAssignmentExpression();
441        }
442    },
443
444    _parseAssignmentExpression: function()
445    {
446        this._parseConditionalExpression();
447        var token = this._peek();
448        if (FormatterWorker.JavaScriptTokens.ASSIGN <= token && token <= FormatterWorker.JavaScriptTokens.ASSIGN_MOD) {
449            this._builder.addSpace();
450            this._next();
451            this._builder.addSpace();
452            this._parseAssignmentExpression();
453        }
454    },
455
456    _parseConditionalExpression: function()
457    {
458        this._parseBinaryExpression();
459        if (this._peek() === FormatterWorker.JavaScriptTokens.CONDITIONAL) {
460            this._builder.addSpace();
461            this._consume(FormatterWorker.JavaScriptTokens.CONDITIONAL);
462            this._builder.addSpace();
463            this._parseAssignmentExpression();
464            this._builder.addSpace();
465            this._expect(FormatterWorker.JavaScriptTokens.COLON);
466            this._builder.addSpace();
467            this._parseAssignmentExpression();
468        }
469    },
470
471    _parseBinaryExpression: function()
472    {
473        this._parseUnaryExpression();
474        var token = this._peek();
475        while (FormatterWorker.JavaScriptTokens.OR <= token && token <= FormatterWorker.JavaScriptTokens.IN) {
476            this._builder.addSpace();
477            this._next();
478            this._builder.addSpace();
479            this._parseBinaryExpression();
480            token = this._peek();
481        }
482    },
483
484    _parseUnaryExpression: function()
485    {
486        var token = this._peek();
487        if ((FormatterWorker.JavaScriptTokens.NOT <= token && token <= FormatterWorker.JavaScriptTokens.VOID) || token === FormatterWorker.JavaScriptTokens.ADD || token === FormatterWorker.JavaScriptTokens.SUB || token ===  FormatterWorker.JavaScriptTokens.INC || token === FormatterWorker.JavaScriptTokens.DEC) {
488            this._next();
489            if (token === FormatterWorker.JavaScriptTokens.DELETE || token === FormatterWorker.JavaScriptTokens.TYPEOF || token === FormatterWorker.JavaScriptTokens.VOID)
490                this._builder.addSpace();
491            this._parseUnaryExpression();
492        } else
493            return this._parsePostfixExpression();
494    },
495
496    _parsePostfixExpression: function()
497    {
498        this._parseLeftHandSideExpression();
499        var token = this._peek();
500        if (!this._hasLineTerminatorBeforeNext() && (token === FormatterWorker.JavaScriptTokens.INC || token === FormatterWorker.JavaScriptTokens.DEC))
501            this._next();
502    },
503
504    _parseLeftHandSideExpression: function()
505    {
506        if (this._peek() === FormatterWorker.JavaScriptTokens.NEW)
507            this._parseNewExpression();
508        else
509            this._parseMemberExpression();
510
511        while (true) {
512            switch (this._peek()) {
513            case FormatterWorker.JavaScriptTokens.LBRACK:
514                this._consume(FormatterWorker.JavaScriptTokens.LBRACK);
515                this._parseExpression();
516                this._expect(FormatterWorker.JavaScriptTokens.RBRACK);
517                break;
518
519            case FormatterWorker.JavaScriptTokens.LPAREN:
520                this._parseArguments();
521                break;
522
523            case FormatterWorker.JavaScriptTokens.PERIOD:
524                this._consume(FormatterWorker.JavaScriptTokens.PERIOD);
525                this._expect(FormatterWorker.JavaScriptTokens.IDENTIFIER);
526                break;
527
528            default:
529                return;
530            }
531        }
532    },
533
534    _parseNewExpression: function()
535    {
536        this._expect(FormatterWorker.JavaScriptTokens.NEW);
537        this._builder.addSpace();
538        if (this._peek() === FormatterWorker.JavaScriptTokens.NEW)
539            this._parseNewExpression();
540        else
541            this._parseMemberExpression();
542    },
543
544    _parseMemberExpression: function()
545    {
546        if (this._peek() === FormatterWorker.JavaScriptTokens.FUNCTION) {
547            this._expect(FormatterWorker.JavaScriptTokens.FUNCTION);
548            if (this._peek() === FormatterWorker.JavaScriptTokens.IDENTIFIER) {
549                this._builder.addSpace();
550                this._expect(FormatterWorker.JavaScriptTokens.IDENTIFIER);
551            }
552            this._parseFunctionLiteral();
553        } else
554            this._parsePrimaryExpression();
555
556        while (true) {
557            switch (this._peek()) {
558            case FormatterWorker.JavaScriptTokens.LBRACK:
559                this._consume(FormatterWorker.JavaScriptTokens.LBRACK);
560                this._parseExpression();
561                this._expect(FormatterWorker.JavaScriptTokens.RBRACK);
562                break;
563
564            case FormatterWorker.JavaScriptTokens.PERIOD:
565                this._consume(FormatterWorker.JavaScriptTokens.PERIOD);
566                this._expect(FormatterWorker.JavaScriptTokens.IDENTIFIER);
567                break;
568
569            case FormatterWorker.JavaScriptTokens.LPAREN:
570                this._parseArguments();
571                break;
572
573            default:
574                return;
575            }
576        }
577    },
578
579    _parseDebuggerStatement: function()
580    {
581        this._expect(FormatterWorker.JavaScriptTokens.DEBUGGER);
582        this._expectSemicolon();
583    },
584
585    _parsePrimaryExpression: function()
586    {
587        switch (this._peek()) {
588        case FormatterWorker.JavaScriptTokens.THIS:
589            return this._consume(FormatterWorker.JavaScriptTokens.THIS);
590        case FormatterWorker.JavaScriptTokens.NULL_LITERAL:
591            return this._consume(FormatterWorker.JavaScriptTokens.NULL_LITERAL);
592        case FormatterWorker.JavaScriptTokens.TRUE_LITERAL:
593            return this._consume(FormatterWorker.JavaScriptTokens.TRUE_LITERAL);
594        case FormatterWorker.JavaScriptTokens.FALSE_LITERAL:
595            return this._consume(FormatterWorker.JavaScriptTokens.FALSE_LITERAL);
596        case FormatterWorker.JavaScriptTokens.IDENTIFIER:
597            return this._consume(FormatterWorker.JavaScriptTokens.IDENTIFIER);
598        case FormatterWorker.JavaScriptTokens.NUMBER:
599            return this._consume(FormatterWorker.JavaScriptTokens.NUMBER);
600        case FormatterWorker.JavaScriptTokens.STRING:
601            return this._consume(FormatterWorker.JavaScriptTokens.STRING);
602        case FormatterWorker.JavaScriptTokens.ASSIGN_DIV:
603            return this._parseRegExpLiteral();
604        case FormatterWorker.JavaScriptTokens.DIV:
605            return this._parseRegExpLiteral();
606        case FormatterWorker.JavaScriptTokens.LBRACK:
607            return this._parseArrayLiteral();
608        case FormatterWorker.JavaScriptTokens.LBRACE:
609            return this._parseObjectLiteral();
610        case FormatterWorker.JavaScriptTokens.LPAREN:
611            this._consume(FormatterWorker.JavaScriptTokens.LPAREN);
612            this._parseExpression();
613            this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
614            return;
615        default:
616            return this._next();
617        }
618    },
619
620    _parseArrayLiteral: function()
621    {
622        this._expect(FormatterWorker.JavaScriptTokens.LBRACK);
623        this._builder.increaseNestingLevel();
624        while (this._peek() !== FormatterWorker.JavaScriptTokens.RBRACK) {
625            if (this._peek() !== FormatterWorker.JavaScriptTokens.COMMA)
626                this._parseAssignmentExpression();
627            if (this._peek() !== FormatterWorker.JavaScriptTokens.RBRACK) {
628                this._expect(FormatterWorker.JavaScriptTokens.COMMA);
629                this._builder.addSpace();
630            }
631        }
632        this._builder.decreaseNestingLevel();
633        this._expect(FormatterWorker.JavaScriptTokens.RBRACK);
634    },
635
636    _parseObjectLiteralGetSet: function()
637    {
638        var token = this._peek();
639        if (token === FormatterWorker.JavaScriptTokens.IDENTIFIER || token === FormatterWorker.JavaScriptTokens.NUMBER || token === FormatterWorker.JavaScriptTokens.STRING ||
640            FormatterWorker.JavaScriptTokens.DELETE <= token && token <= FormatterWorker.JavaScriptTokens.FALSE_LITERAL ||
641            token === FormatterWorker.JavaScriptTokens.INSTANCEOF || token === FormatterWorker.JavaScriptTokens.IN || token === FormatterWorker.JavaScriptTokens.CONST) {
642            this._next();
643            this._parseFunctionLiteral();
644        }
645    },
646
647    _parseObjectLiteral: function()
648    {
649        this._expect(FormatterWorker.JavaScriptTokens.LBRACE);
650        this._builder.increaseNestingLevel();
651        while (this._peek() !== FormatterWorker.JavaScriptTokens.RBRACE) {
652            var token = this._peek();
653            switch (token) {
654            case FormatterWorker.JavaScriptTokens.IDENTIFIER:
655                this._consume(FormatterWorker.JavaScriptTokens.IDENTIFIER);
656                var name = this._token.value;
657                if ((name === "get" || name === "set") && this._peek() !== FormatterWorker.JavaScriptTokens.COLON) {
658                    this._builder.addSpace();
659                    this._parseObjectLiteralGetSet();
660                    if (this._peek() !== FormatterWorker.JavaScriptTokens.RBRACE) {
661                        this._expect(FormatterWorker.JavaScriptTokens.COMMA);
662                    }
663                    continue;
664                }
665                break;
666
667            case FormatterWorker.JavaScriptTokens.STRING:
668                this._consume(FormatterWorker.JavaScriptTokens.STRING);
669                break;
670
671            case FormatterWorker.JavaScriptTokens.NUMBER:
672                this._consume(FormatterWorker.JavaScriptTokens.NUMBER);
673                break;
674
675            default:
676                this._next();
677            }
678
679            this._expect(FormatterWorker.JavaScriptTokens.COLON);
680            this._builder.addSpace();
681            this._parseAssignmentExpression();
682            if (this._peek() !== FormatterWorker.JavaScriptTokens.RBRACE) {
683                this._expect(FormatterWorker.JavaScriptTokens.COMMA);
684            }
685        }
686        this._builder.decreaseNestingLevel();
687
688        this._expect(FormatterWorker.JavaScriptTokens.RBRACE);
689    },
690
691    _parseRegExpLiteral: function()
692    {
693        if (this._nextToken.type === "regexp")
694            this._next();
695        else {
696            this._forceRegexp = true;
697            this._next();
698        }
699    },
700
701    _parseArguments: function()
702    {
703        this._expect(FormatterWorker.JavaScriptTokens.LPAREN);
704        var done = (this._peek() === FormatterWorker.JavaScriptTokens.RPAREN);
705        while (!done) {
706            this._parseAssignmentExpression();
707            done = (this._peek() === FormatterWorker.JavaScriptTokens.RPAREN);
708            if (!done) {
709                this._expect(FormatterWorker.JavaScriptTokens.COMMA);
710                this._builder.addSpace();
711            }
712        }
713        this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
714    },
715
716    _parseFunctionLiteral: function()
717    {
718        this._expect(FormatterWorker.JavaScriptTokens.LPAREN);
719        var done = (this._peek() === FormatterWorker.JavaScriptTokens.RPAREN);
720        while (!done) {
721            this._expect(FormatterWorker.JavaScriptTokens.IDENTIFIER);
722            done = (this._peek() === FormatterWorker.JavaScriptTokens.RPAREN);
723            if (!done) {
724                this._expect(FormatterWorker.JavaScriptTokens.COMMA);
725                this._builder.addSpace();
726            }
727        }
728        this._expect(FormatterWorker.JavaScriptTokens.RPAREN);
729        this._builder.addSpace();
730
731        this._expect(FormatterWorker.JavaScriptTokens.LBRACE);
732        this._builder.addNewLine();
733        this._builder.increaseNestingLevel();
734        this._parseSourceElements(FormatterWorker.JavaScriptTokens.RBRACE);
735        this._builder.decreaseNestingLevel();
736        this._expect(FormatterWorker.JavaScriptTokens.RBRACE);
737    }
738}
739
740/**
741 * @constructor
742 * @param {string} content
743 * @param {!{original: !Array.<number>, formatted: !Array.<number>}} mapping
744 * @param {number} originalOffset
745 * @param {number} formattedOffset
746 * @param {string} indentString
747 */
748FormatterWorker.JavaScriptFormattedContentBuilder = function(content, mapping, originalOffset, formattedOffset, indentString)
749{
750    this._originalContent = content;
751    this._originalOffset = originalOffset;
752    this._lastOriginalPosition = 0;
753
754    this._formattedContent = [];
755    this._formattedContentLength = 0;
756    this._formattedOffset = formattedOffset;
757    this._lastFormattedPosition = 0;
758
759    this._mapping = mapping;
760
761    this._lineNumber = 0;
762    this._nestingLevel = 0;
763    this._indentString = indentString;
764    this._cachedIndents = {};
765}
766
767FormatterWorker.JavaScriptFormattedContentBuilder.prototype = {
768    /**
769     * @param {!{comments_before: !Array.<string>, line: number, pos: number, endLine: number, nlb: boolean}} token
770     */
771    addToken: function(token)
772    {
773        for (var i = 0; i < token.comments_before.length; ++i)
774            this._addComment(token.comments_before[i]);
775
776        while (this._lineNumber < token.line) {
777            this._addText("\n");
778            this._addIndent();
779            this._needNewLine = false;
780            this._lineNumber += 1;
781        }
782
783        if (this._needNewLine) {
784            this._addText("\n");
785            this._addIndent();
786            this._needNewLine = false;
787        }
788
789        this._addMappingIfNeeded(token.pos);
790        this._addText(this._originalContent.substring(token.pos, token.endPos));
791        this._lineNumber = token.endLine;
792    },
793
794    addSpace: function()
795    {
796        this._addText(" ");
797    },
798
799    addNewLine: function()
800    {
801        this._needNewLine = true;
802    },
803
804    increaseNestingLevel: function()
805    {
806        this._nestingLevel += 1;
807    },
808
809    decreaseNestingLevel: function()
810    {
811        this._nestingLevel -= 1;
812    },
813
814    /**
815     * @return {string}
816     */
817    content: function()
818    {
819        return this._formattedContent.join("");
820    },
821
822    _addIndent: function()
823    {
824        if (this._cachedIndents[this._nestingLevel]) {
825            this._addText(this._cachedIndents[this._nestingLevel]);
826            return;
827        }
828
829        var fullIndent = "";
830        for (var i = 0; i < this._nestingLevel; ++i)
831            fullIndent += this._indentString;
832        this._addText(fullIndent);
833
834        // Cache a maximum of 20 nesting level indents.
835        if (this._nestingLevel <= 20)
836            this._cachedIndents[this._nestingLevel] = fullIndent;
837    },
838
839    _addComment: function(comment)
840    {
841        if (this._lineNumber < comment.line) {
842            for (var j = this._lineNumber; j < comment.line; ++j)
843                this._addText("\n");
844            this._lineNumber = comment.line;
845            this._needNewLine = false;
846            this._addIndent();
847        } else
848            this.addSpace();
849
850        this._addMappingIfNeeded(comment.pos);
851        if (comment.type === "comment1")
852            this._addText("//");
853        else
854            this._addText("/*");
855
856        this._addText(comment.value);
857
858        if (comment.type !== "comment1") {
859            this._addText("*/");
860            var position;
861            while ((position = comment.value.indexOf("\n", position + 1)) !== -1)
862                this._lineNumber += 1;
863        }
864    },
865
866    /**
867     * @param {string} text
868     */
869    _addText: function(text)
870    {
871        this._formattedContent.push(text);
872        this._formattedContentLength += text.length;
873    },
874
875    /**
876     * @param {number} originalPosition
877     */
878    _addMappingIfNeeded: function(originalPosition)
879    {
880        if (originalPosition - this._lastOriginalPosition === this._formattedContentLength - this._lastFormattedPosition)
881            return;
882        this._mapping.original.push(this._originalOffset + originalPosition);
883        this._lastOriginalPosition = originalPosition;
884        this._mapping.formatted.push(this._formattedOffset + this._formattedContentLength);
885        this._lastFormattedPosition = this._formattedContentLength;
886    }
887}
888
889FormatterWorker.JavaScriptTokens = {};
890FormatterWorker.JavaScriptTokensByValue = {};
891
892FormatterWorker.JavaScriptTokens.EOS = 0;
893FormatterWorker.JavaScriptTokens.LPAREN = FormatterWorker.JavaScriptTokensByValue["("] = 1;
894FormatterWorker.JavaScriptTokens.RPAREN = FormatterWorker.JavaScriptTokensByValue[")"] = 2;
895FormatterWorker.JavaScriptTokens.LBRACK = FormatterWorker.JavaScriptTokensByValue["["] = 3;
896FormatterWorker.JavaScriptTokens.RBRACK = FormatterWorker.JavaScriptTokensByValue["]"] = 4;
897FormatterWorker.JavaScriptTokens.LBRACE = FormatterWorker.JavaScriptTokensByValue["{"] = 5;
898FormatterWorker.JavaScriptTokens.RBRACE = FormatterWorker.JavaScriptTokensByValue["}"] = 6;
899FormatterWorker.JavaScriptTokens.COLON = FormatterWorker.JavaScriptTokensByValue[":"] = 7;
900FormatterWorker.JavaScriptTokens.SEMICOLON = FormatterWorker.JavaScriptTokensByValue[";"] = 8;
901FormatterWorker.JavaScriptTokens.PERIOD = FormatterWorker.JavaScriptTokensByValue["."] = 9;
902FormatterWorker.JavaScriptTokens.CONDITIONAL = FormatterWorker.JavaScriptTokensByValue["?"] = 10;
903FormatterWorker.JavaScriptTokens.INC = FormatterWorker.JavaScriptTokensByValue["++"] = 11;
904FormatterWorker.JavaScriptTokens.DEC = FormatterWorker.JavaScriptTokensByValue["--"] = 12;
905FormatterWorker.JavaScriptTokens.ASSIGN = FormatterWorker.JavaScriptTokensByValue["="] = 13;
906FormatterWorker.JavaScriptTokens.ASSIGN_BIT_OR = FormatterWorker.JavaScriptTokensByValue["|="] = 14;
907FormatterWorker.JavaScriptTokens.ASSIGN_BIT_XOR = FormatterWorker.JavaScriptTokensByValue["^="] = 15;
908FormatterWorker.JavaScriptTokens.ASSIGN_BIT_AND = FormatterWorker.JavaScriptTokensByValue["&="] = 16;
909FormatterWorker.JavaScriptTokens.ASSIGN_SHL = FormatterWorker.JavaScriptTokensByValue["<<="] = 17;
910FormatterWorker.JavaScriptTokens.ASSIGN_SAR = FormatterWorker.JavaScriptTokensByValue[">>="] = 18;
911FormatterWorker.JavaScriptTokens.ASSIGN_SHR = FormatterWorker.JavaScriptTokensByValue[">>>="] = 19;
912FormatterWorker.JavaScriptTokens.ASSIGN_ADD = FormatterWorker.JavaScriptTokensByValue["+="] = 20;
913FormatterWorker.JavaScriptTokens.ASSIGN_SUB = FormatterWorker.JavaScriptTokensByValue["-="] = 21;
914FormatterWorker.JavaScriptTokens.ASSIGN_MUL = FormatterWorker.JavaScriptTokensByValue["*="] = 22;
915FormatterWorker.JavaScriptTokens.ASSIGN_DIV = FormatterWorker.JavaScriptTokensByValue["/="] = 23;
916FormatterWorker.JavaScriptTokens.ASSIGN_MOD = FormatterWorker.JavaScriptTokensByValue["%="] = 24;
917FormatterWorker.JavaScriptTokens.COMMA = FormatterWorker.JavaScriptTokensByValue[","] = 25;
918FormatterWorker.JavaScriptTokens.OR = FormatterWorker.JavaScriptTokensByValue["||"] = 26;
919FormatterWorker.JavaScriptTokens.AND = FormatterWorker.JavaScriptTokensByValue["&&"] = 27;
920FormatterWorker.JavaScriptTokens.BIT_OR = FormatterWorker.JavaScriptTokensByValue["|"] = 28;
921FormatterWorker.JavaScriptTokens.BIT_XOR = FormatterWorker.JavaScriptTokensByValue["^"] = 29;
922FormatterWorker.JavaScriptTokens.BIT_AND = FormatterWorker.JavaScriptTokensByValue["&"] = 30;
923FormatterWorker.JavaScriptTokens.SHL = FormatterWorker.JavaScriptTokensByValue["<<"] = 31;
924FormatterWorker.JavaScriptTokens.SAR = FormatterWorker.JavaScriptTokensByValue[">>"] = 32;
925FormatterWorker.JavaScriptTokens.SHR = FormatterWorker.JavaScriptTokensByValue[">>>"] = 33;
926FormatterWorker.JavaScriptTokens.ADD = FormatterWorker.JavaScriptTokensByValue["+"] = 34;
927FormatterWorker.JavaScriptTokens.SUB = FormatterWorker.JavaScriptTokensByValue["-"] = 35;
928FormatterWorker.JavaScriptTokens.MUL = FormatterWorker.JavaScriptTokensByValue["*"] = 36;
929FormatterWorker.JavaScriptTokens.DIV = FormatterWorker.JavaScriptTokensByValue["/"] = 37;
930FormatterWorker.JavaScriptTokens.MOD = FormatterWorker.JavaScriptTokensByValue["%"] = 38;
931FormatterWorker.JavaScriptTokens.EQ = FormatterWorker.JavaScriptTokensByValue["=="] = 39;
932FormatterWorker.JavaScriptTokens.NE = FormatterWorker.JavaScriptTokensByValue["!="] = 40;
933FormatterWorker.JavaScriptTokens.EQ_STRICT = FormatterWorker.JavaScriptTokensByValue["==="] = 41;
934FormatterWorker.JavaScriptTokens.NE_STRICT = FormatterWorker.JavaScriptTokensByValue["!=="] = 42;
935FormatterWorker.JavaScriptTokens.LT = FormatterWorker.JavaScriptTokensByValue["<"] = 43;
936FormatterWorker.JavaScriptTokens.GT = FormatterWorker.JavaScriptTokensByValue[">"] = 44;
937FormatterWorker.JavaScriptTokens.LTE = FormatterWorker.JavaScriptTokensByValue["<="] = 45;
938FormatterWorker.JavaScriptTokens.GTE = FormatterWorker.JavaScriptTokensByValue[">="] = 46;
939FormatterWorker.JavaScriptTokens.INSTANCEOF = FormatterWorker.JavaScriptTokensByValue["instanceof"] = 47;
940FormatterWorker.JavaScriptTokens.IN = FormatterWorker.JavaScriptTokensByValue["in"] = 48;
941FormatterWorker.JavaScriptTokens.NOT = FormatterWorker.JavaScriptTokensByValue["!"] = 49;
942FormatterWorker.JavaScriptTokens.BIT_NOT = FormatterWorker.JavaScriptTokensByValue["~"] = 50;
943FormatterWorker.JavaScriptTokens.DELETE = FormatterWorker.JavaScriptTokensByValue["delete"] = 51;
944FormatterWorker.JavaScriptTokens.TYPEOF = FormatterWorker.JavaScriptTokensByValue["typeof"] = 52;
945FormatterWorker.JavaScriptTokens.VOID = FormatterWorker.JavaScriptTokensByValue["void"] = 53;
946FormatterWorker.JavaScriptTokens.BREAK = FormatterWorker.JavaScriptTokensByValue["break"] = 54;
947FormatterWorker.JavaScriptTokens.CASE = FormatterWorker.JavaScriptTokensByValue["case"] = 55;
948FormatterWorker.JavaScriptTokens.CATCH = FormatterWorker.JavaScriptTokensByValue["catch"] = 56;
949FormatterWorker.JavaScriptTokens.CONTINUE = FormatterWorker.JavaScriptTokensByValue["continue"] = 57;
950FormatterWorker.JavaScriptTokens.DEBUGGER = FormatterWorker.JavaScriptTokensByValue["debugger"] = 58;
951FormatterWorker.JavaScriptTokens.DEFAULT = FormatterWorker.JavaScriptTokensByValue["default"] = 59;
952FormatterWorker.JavaScriptTokens.DO = FormatterWorker.JavaScriptTokensByValue["do"] = 60;
953FormatterWorker.JavaScriptTokens.ELSE = FormatterWorker.JavaScriptTokensByValue["else"] = 61;
954FormatterWorker.JavaScriptTokens.FINALLY = FormatterWorker.JavaScriptTokensByValue["finally"] = 62;
955FormatterWorker.JavaScriptTokens.FOR = FormatterWorker.JavaScriptTokensByValue["for"] = 63;
956FormatterWorker.JavaScriptTokens.FUNCTION = FormatterWorker.JavaScriptTokensByValue["function"] = 64;
957FormatterWorker.JavaScriptTokens.IF = FormatterWorker.JavaScriptTokensByValue["if"] = 65;
958FormatterWorker.JavaScriptTokens.NEW = FormatterWorker.JavaScriptTokensByValue["new"] = 66;
959FormatterWorker.JavaScriptTokens.RETURN = FormatterWorker.JavaScriptTokensByValue["return"] = 67;
960FormatterWorker.JavaScriptTokens.SWITCH = FormatterWorker.JavaScriptTokensByValue["switch"] = 68;
961FormatterWorker.JavaScriptTokens.THIS = FormatterWorker.JavaScriptTokensByValue["this"] = 69;
962FormatterWorker.JavaScriptTokens.THROW = FormatterWorker.JavaScriptTokensByValue["throw"] = 70;
963FormatterWorker.JavaScriptTokens.TRY = FormatterWorker.JavaScriptTokensByValue["try"] = 71;
964FormatterWorker.JavaScriptTokens.VAR = FormatterWorker.JavaScriptTokensByValue["var"] = 72;
965FormatterWorker.JavaScriptTokens.WHILE = FormatterWorker.JavaScriptTokensByValue["while"] = 73;
966FormatterWorker.JavaScriptTokens.WITH = FormatterWorker.JavaScriptTokensByValue["with"] = 74;
967FormatterWorker.JavaScriptTokens.NULL_LITERAL = FormatterWorker.JavaScriptTokensByValue["null"] = 75;
968FormatterWorker.JavaScriptTokens.TRUE_LITERAL = FormatterWorker.JavaScriptTokensByValue["true"] = 76;
969FormatterWorker.JavaScriptTokens.FALSE_LITERAL = FormatterWorker.JavaScriptTokensByValue["false"] = 77;
970FormatterWorker.JavaScriptTokens.NUMBER = 78;
971FormatterWorker.JavaScriptTokens.STRING = 79;
972FormatterWorker.JavaScriptTokens.IDENTIFIER = 80;
973FormatterWorker.JavaScriptTokens.CONST = FormatterWorker.JavaScriptTokensByValue["const"] = 81;
974
975FormatterWorker.JavaScriptTokensByType = {
976    "eof": FormatterWorker.JavaScriptTokens.EOS,
977    "name": FormatterWorker.JavaScriptTokens.IDENTIFIER,
978    "num": FormatterWorker.JavaScriptTokens.NUMBER,
979    "regexp": FormatterWorker.JavaScriptTokens.DIV,
980    "string": FormatterWorker.JavaScriptTokens.STRING
981};
982
983/**
984 * @constructor
985 * @param {string} content
986 */
987FormatterWorker.JavaScriptTokenizer = function(content)
988{
989    this._readNextToken = parse.tokenizer(content);
990    this._state = this._readNextToken.context();
991}
992
993FormatterWorker.JavaScriptTokenizer.prototype = {
994    /**
995     * @return {string}
996     */
997    content: function()
998    {
999        return this._state.text;
1000    },
1001
1002    /**
1003     * @param {boolean=} forceRegexp
1004     */
1005    next: function(forceRegexp)
1006    {
1007        var uglifyToken = this._readNextToken(forceRegexp);
1008        uglifyToken.endPos = this._state.pos;
1009        uglifyToken.endLine = this._state.line;
1010        uglifyToken.token = this._convertUglifyToken(uglifyToken);
1011        return uglifyToken;
1012    },
1013
1014    _convertUglifyToken: function(uglifyToken)
1015    {
1016        var token = FormatterWorker.JavaScriptTokensByType[uglifyToken.type];
1017        if (typeof token === "number")
1018            return token;
1019        token = FormatterWorker.JavaScriptTokensByValue[uglifyToken.value];
1020        if (typeof token === "number")
1021            return token;
1022        throw "Unknown token type " + uglifyToken.type;
1023    }
1024}
1025