1/***********************************************************************
2
3  A JavaScript tokenizer / parser / beautifier / compressor.
4
5  This version is suitable for Node.js.  With minimal changes (the
6  exports stuff) it should work on any JS platform.
7
8  This file contains the tokenizer/parser.  It is a port to JavaScript
9  of parse-js [1], a JavaScript parser library written in Common Lisp
10  by Marijn Haverbeke.  Thank you Marijn!
11
12  [1] http://marijn.haverbeke.nl/parse-js/
13
14  Exported functions:
15
16    - tokenizer(code) -- returns a function.  Call the returned
17      function to fetch the next token.
18
19    - parse(code) -- returns an AST of the given JavaScript code.
20
21  -------------------------------- (C) ---------------------------------
22
23                           Author: Mihai Bazon
24                         <mihai.bazon@gmail.com>
25                       http://mihai.bazon.net/blog
26
27  Distributed under the BSD license:
28
29    Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
30    Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
31
32    Redistribution and use in source and binary forms, with or without
33    modification, are permitted provided that the following conditions
34    are met:
35
36        * Redistributions of source code must retain the above
37          copyright notice, this list of conditions and the following
38          disclaimer.
39
40        * Redistributions in binary form must reproduce the above
41          copyright notice, this list of conditions and the following
42          disclaimer in the documentation and/or other materials
43          provided with the distribution.
44
45    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
46    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
48    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
49    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
50    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
51    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
52    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
53    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
54    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
55    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
56    SUCH DAMAGE.
57
58 ***********************************************************************/
59
60/* -----[ Tokenizer (constants) ]----- */
61
62var KEYWORDS = array_to_hash([
63        "break",
64        "case",
65        "catch",
66        "const",
67        "continue",
68        "default",
69        "delete",
70        "do",
71        "else",
72        "finally",
73        "for",
74        "function",
75        "if",
76        "in",
77        "instanceof",
78        "new",
79        "return",
80        "switch",
81        "throw",
82        "try",
83        "typeof",
84        "var",
85        "void",
86        "while",
87        "with"
88]);
89
90var RESERVED_WORDS = array_to_hash([
91        "abstract",
92        "boolean",
93        "byte",
94        "char",
95        "class",
96        "debugger",
97        "double",
98        "enum",
99        "export",
100        "extends",
101        "final",
102        "float",
103        "goto",
104        "implements",
105        "import",
106        "int",
107        "interface",
108        "long",
109        "native",
110        "package",
111        "private",
112        "protected",
113        "public",
114        "short",
115        "static",
116        "super",
117        "synchronized",
118        "throws",
119        "transient",
120        "volatile"
121]);
122
123var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([
124        "return",
125        "new",
126        "delete",
127        "throw",
128        "else",
129        "case"
130]);
131
132var KEYWORDS_ATOM = array_to_hash([
133        "false",
134        "null",
135        "true",
136        "undefined"
137]);
138
139var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^"));
140
141var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
142var RE_OCT_NUMBER = /^0[0-7]+$/;
143var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
144
145var OPERATORS = array_to_hash([
146        "in",
147        "instanceof",
148        "typeof",
149        "new",
150        "void",
151        "delete",
152        "++",
153        "--",
154        "+",
155        "-",
156        "!",
157        "~",
158        "&",
159        "|",
160        "^",
161        "*",
162        "/",
163        "%",
164        ">>",
165        "<<",
166        ">>>",
167        "<",
168        ">",
169        "<=",
170        ">=",
171        "==",
172        "===",
173        "!=",
174        "!==",
175        "?",
176        "=",
177        "+=",
178        "-=",
179        "/=",
180        "*=",
181        "%=",
182        ">>=",
183        "<<=",
184        ">>>=",
185        "%=",
186        "|=",
187        "^=",
188        "&=",
189        "&&",
190        "||"
191]);
192
193var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t"));
194
195var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:"));
196
197var PUNC_CHARS = array_to_hash(characters("[]{}(),;:"));
198
199var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy"));
200
201/* -----[ Tokenizer ]----- */
202
203function is_alphanumeric_char(ch) {
204        ch = ch.charCodeAt(0);
205        return (ch >= 48 && ch <= 57) ||
206                (ch >= 65 && ch <= 90) ||
207                (ch >= 97 && ch <= 122);
208};
209
210function is_identifier_char(ch) {
211        return is_alphanumeric_char(ch) || ch == "$" || ch == "_";
212};
213
214function is_digit(ch) {
215        ch = ch.charCodeAt(0);
216        return ch >= 48 && ch <= 57;
217};
218
219function parse_js_number(num) {
220        if (RE_HEX_NUMBER.test(num)) {
221                return parseInt(num.substr(2), 16);
222        } else if (RE_OCT_NUMBER.test(num)) {
223                return parseInt(num.substr(1), 8);
224        } else if (RE_DEC_NUMBER.test(num)) {
225                return parseFloat(num);
226        }
227};
228
229function JS_Parse_Error(message, line, col, pos) {
230        this.message = message;
231        this.line = line;
232        this.col = col;
233        this.pos = pos;
234        try {
235                ({})();
236        } catch(ex) {
237                this.stack = ex.stack;
238        };
239};
240
241JS_Parse_Error.prototype.toString = function() {
242        return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack;
243};
244
245function js_error(message, line, col, pos) {
246        throw new JS_Parse_Error(message, line, col, pos);
247};
248
249function is_token(token, type, val) {
250        return token.type == type && (val == null || token.value == val);
251};
252
253var EX_EOF = {};
254
255function tokenizer($TEXT) {
256
257        var S = {
258                text            : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''),
259                pos             : 0,
260                tokpos          : 0,
261                line            : 0,
262                tokline         : 0,
263                col             : 0,
264                tokcol          : 0,
265                newline_before  : false,
266                regex_allowed   : false,
267                comments_before : []
268        };
269
270        function peek() { return S.text.charAt(S.pos); };
271
272        function next(signal_eof) {
273                var ch = S.text.charAt(S.pos++);
274                if (signal_eof && !ch)
275                        throw EX_EOF;
276                if (ch == "\n") {
277                        S.newline_before = true;
278                        ++S.line;
279                        S.col = 0;
280                } else {
281                        ++S.col;
282                }
283                return ch;
284        };
285
286        function eof() {
287                return !S.peek();
288        };
289
290        function find(what, signal_eof) {
291                var pos = S.text.indexOf(what, S.pos);
292                if (signal_eof && pos == -1) throw EX_EOF;
293                return pos;
294        };
295
296        function start_token() {
297                S.tokline = S.line;
298                S.tokcol = S.col;
299                S.tokpos = S.pos;
300        };
301
302        function token(type, value, is_comment) {
303                S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) ||
304                                   (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) ||
305                                   (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value)));
306                var ret = {
307                        type  : type,
308                        value : value,
309                        line  : S.tokline,
310                        col   : S.tokcol,
311                        pos   : S.tokpos,
312                        nlb   : S.newline_before
313                };
314                if (!is_comment) {
315                        ret.comments_before = S.comments_before;
316                        S.comments_before = [];
317                }
318                S.newline_before = false;
319                return ret;
320        };
321
322        function skip_whitespace() {
323                while (HOP(WHITESPACE_CHARS, peek()))
324                        next();
325        };
326
327        function read_while(pred) {
328                var ret = "", ch = peek(), i = 0;
329                while (ch && pred(ch, i++)) {
330                        ret += next();
331                        ch = peek();
332                }
333                return ret;
334        };
335
336        function parse_error(err) {
337                js_error(err, S.tokline, S.tokcol, S.tokpos);
338        };
339
340        function read_num(prefix) {
341                var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
342                var num = read_while(function(ch, i){
343                        if (ch == "x" || ch == "X") {
344                                if (has_x) return false;
345                                return has_x = true;
346                        }
347                        if (!has_x && (ch == "E" || ch == "e")) {
348                                if (has_e) return false;
349                                return has_e = after_e = true;
350                        }
351                        if (ch == "-") {
352                                if (after_e || (i == 0 && !prefix)) return true;
353                                return false;
354                        }
355                        if (ch == "+") return after_e;
356                        after_e = false;
357                        if (ch == ".") {
358                                if (!has_dot)
359                                        return has_dot = true;
360                                return false;
361                        }
362                        return is_alphanumeric_char(ch);
363                });
364                if (prefix)
365                        num = prefix + num;
366                var valid = parse_js_number(num);
367                if (!isNaN(valid)) {
368                        return token("num", valid);
369                } else {
370                        parse_error("Invalid syntax: " + num);
371                }
372        };
373
374        function read_escaped_char() {
375                var ch = next(true);
376                switch (ch) {
377                    case "n" : return "\n";
378                    case "r" : return "\r";
379                    case "t" : return "\t";
380                    case "b" : return "\b";
381                    case "v" : return "\v";
382                    case "f" : return "\f";
383                    case "0" : return "\0";
384                    case "x" : return String.fromCharCode(hex_bytes(2));
385                    case "u" : return String.fromCharCode(hex_bytes(4));
386                    default  : return ch;
387                }
388        };
389
390        function hex_bytes(n) {
391                var num = 0;
392                for (; n > 0; --n) {
393                        var digit = parseInt(next(true), 16);
394                        if (isNaN(digit))
395                                parse_error("Invalid hex-character pattern in string");
396                        num = (num << 4) | digit;
397                }
398                return num;
399        };
400
401        function read_string() {
402                return with_eof_error("Unterminated string constant", function(){
403                        var quote = next(), ret = "";
404                        for (;;) {
405                                var ch = next(true);
406                                if (ch == "\\") ch = read_escaped_char();
407                                else if (ch == quote) break;
408                                ret += ch;
409                        }
410                        return token("string", ret);
411                });
412        };
413
414        function read_line_comment() {
415                next();
416                var i = find("\n"), ret;
417                if (i == -1) {
418                        ret = S.text.substr(S.pos);
419                        S.pos = S.text.length;
420                } else {
421                        ret = S.text.substring(S.pos, i);
422                        S.pos = i;
423                }
424                return token("comment1", ret, true);
425        };
426
427        function read_multiline_comment() {
428                next();
429                return with_eof_error("Unterminated multiline comment", function(){
430                        var i = find("*/", true),
431                            text = S.text.substring(S.pos, i),
432                            tok = token("comment2", text, true);
433                        S.pos = i + 2;
434                        S.line += text.split("\n").length - 1;
435                        S.newline_before = text.indexOf("\n") >= 0;
436                        return tok;
437                });
438        };
439
440        function read_regexp() {
441                return with_eof_error("Unterminated regular expression", function(){
442                        var prev_backslash = false, regexp = "", ch, in_class = false;
443                        while ((ch = next(true))) if (prev_backslash) {
444                                regexp += "\\" + ch;
445                                prev_backslash = false;
446                        } else if (ch == "[") {
447                                in_class = true;
448                                regexp += ch;
449                        } else if (ch == "]" && in_class) {
450                                in_class = false;
451                                regexp += ch;
452                        } else if (ch == "/" && !in_class) {
453                                break;
454                        } else if (ch == "\\") {
455                                prev_backslash = true;
456                        } else {
457                                regexp += ch;
458                        }
459                        var mods = read_while(function(ch){
460                                return HOP(REGEXP_MODIFIERS, ch);
461                        });
462                        return token("regexp", [ regexp, mods ]);
463                });
464        };
465
466        function read_operator(prefix) {
467                function grow(op) {
468                        if (!peek()) return op;
469                        var bigger = op + peek();
470                        if (HOP(OPERATORS, bigger)) {
471                                next();
472                                return grow(bigger);
473                        } else {
474                                return op;
475                        }
476                };
477                return token("operator", grow(prefix || next()));
478        };
479
480        function handle_slash() {
481                next();
482                var regex_allowed = S.regex_allowed;
483                switch (peek()) {
484                    case "/":
485                        S.comments_before.push(read_line_comment());
486                        S.regex_allowed = regex_allowed;
487                        return next_token();
488                    case "*":
489                        S.comments_before.push(read_multiline_comment());
490                        S.regex_allowed = regex_allowed;
491                        return next_token();
492                }
493                return S.regex_allowed ? read_regexp() : read_operator("/");
494        };
495
496        function handle_dot() {
497                next();
498                return is_digit(peek())
499                        ? read_num(".")
500                        : token("punc", ".");
501        };
502
503        function read_word() {
504                var word = read_while(is_identifier_char);
505                return !HOP(KEYWORDS, word)
506                        ? token("name", word)
507                        : HOP(OPERATORS, word)
508                        ? token("operator", word)
509                        : HOP(KEYWORDS_ATOM, word)
510                        ? token("atom", word)
511                        : token("keyword", word);
512        };
513
514        function with_eof_error(eof_error, cont) {
515                try {
516                        return cont();
517                } catch(ex) {
518                        if (ex === EX_EOF) parse_error(eof_error);
519                        else throw ex;
520                }
521        };
522
523        function next_token(force_regexp) {
524                if (force_regexp)
525                        return read_regexp();
526                skip_whitespace();
527                start_token();
528                var ch = peek();
529                if (!ch) return token("eof");
530                if (is_digit(ch)) return read_num();
531                if (ch == '"' || ch == "'") return read_string();
532                if (HOP(PUNC_CHARS, ch)) return token("punc", next());
533                if (ch == ".") return handle_dot();
534                if (ch == "/") return handle_slash();
535                if (HOP(OPERATOR_CHARS, ch)) return read_operator();
536                if (is_identifier_char(ch)) return read_word();
537                parse_error("Unexpected character '" + ch + "'");
538        };
539
540        next_token.context = function(nc) {
541                if (nc) S = nc;
542                return S;
543        };
544
545        return next_token;
546
547};
548
549/* -----[ Parser (constants) ]----- */
550
551var UNARY_PREFIX = array_to_hash([
552        "typeof",
553        "void",
554        "delete",
555        "--",
556        "++",
557        "!",
558        "~",
559        "-",
560        "+"
561]);
562
563var UNARY_POSTFIX = array_to_hash([ "--", "++" ]);
564
565var ASSIGNMENT = (function(a, ret, i){
566        while (i < a.length) {
567                ret[a[i]] = a[i].substr(0, a[i].length - 1);
568                i++;
569        }
570        return ret;
571})(
572        ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="],
573        { "=": true },
574        0
575);
576
577var PRECEDENCE = (function(a, ret){
578        for (var i = 0, n = 1; i < a.length; ++i, ++n) {
579                var b = a[i];
580                for (var j = 0; j < b.length; ++j) {
581                        ret[b[j]] = n;
582                }
583        }
584        return ret;
585})(
586        [
587                ["||"],
588                ["&&"],
589                ["|"],
590                ["^"],
591                ["&"],
592                ["==", "===", "!=", "!=="],
593                ["<", ">", "<=", ">=", "in", "instanceof"],
594                [">>", "<<", ">>>"],
595                ["+", "-"],
596                ["*", "/", "%"]
597        ],
598        {}
599);
600
601var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]);
602
603var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]);
604
605/* -----[ Parser ]----- */
606
607function NodeWithToken(str, start, end) {
608        this.name = str;
609        this.start = start;
610        this.end = end;
611};
612
613NodeWithToken.prototype.toString = function() { return this.name; };
614
615function parse($TEXT, strict_mode, embed_tokens) {
616
617        var S = {
618                input       : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT,
619                token       : null,
620                prev        : null,
621                peeked      : null,
622                in_function : 0,
623                in_loop     : 0,
624                labels      : []
625        };
626
627        S.token = next();
628
629        function is(type, value) {
630                return is_token(S.token, type, value);
631        };
632
633        function peek() { return S.peeked || (S.peeked = S.input()); };
634
635        function next() {
636                S.prev = S.token;
637                if (S.peeked) {
638                        S.token = S.peeked;
639                        S.peeked = null;
640                } else {
641                        S.token = S.input();
642                }
643                return S.token;
644        };
645
646        function prev() {
647                return S.prev;
648        };
649
650        function croak(msg, line, col, pos) {
651                var ctx = S.input.context();
652                js_error(msg,
653                         line != null ? line : ctx.tokline,
654                         col != null ? col : ctx.tokcol,
655                         pos != null ? pos : ctx.tokpos);
656        };
657
658        function token_error(token, msg) {
659                croak(msg, token.line, token.col);
660        };
661
662        function unexpected(token) {
663                if (token == null)
664                        token = S.token;
665                token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
666        };
667
668        function expect_token(type, val) {
669                if (is(type, val)) {
670                        return next();
671                }
672                token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type);
673        };
674
675        function expect(punc) { return expect_token("punc", punc); };
676
677        function can_insert_semicolon() {
678                return !strict_mode && (
679                        S.token.nlb || is("eof") || is("punc", "}")
680                );
681        };
682
683        function semicolon() {
684                if (is("punc", ";")) next();
685                else if (!can_insert_semicolon()) unexpected();
686        };
687
688        function as() {
689                return slice(arguments);
690        };
691
692        function parenthesised() {
693                expect("(");
694                var ex = expression();
695                expect(")");
696                return ex;
697        };
698
699        function add_tokens(str, start, end) {
700                return new NodeWithToken(str, start, end);
701        };
702
703        var statement = embed_tokens ? function() {
704                var start = S.token;
705                var stmt = $statement();
706                stmt[0] = add_tokens(stmt[0], start, prev());
707                return stmt;
708        } : $statement;
709
710        function $statement() {
711                if (is("operator", "/")) {
712                        S.peeked = null;
713                        S.token = S.input(true); // force regexp
714                }
715                switch (S.token.type) {
716                    case "num":
717                    case "string":
718                    case "regexp":
719                    case "operator":
720                    case "atom":
721                        return simple_statement();
722
723                    case "name":
724                        return is_token(peek(), "punc", ":")
725                                ? labeled_statement(prog1(S.token.value, next, next))
726                                : simple_statement();
727
728                    case "punc":
729                        switch (S.token.value) {
730                            case "{":
731                                return as("block", block_());
732                            case "[":
733                            case "(":
734                                return simple_statement();
735                            case ";":
736                                next();
737                                return as("block");
738                            default:
739                                unexpected();
740                        }
741
742                    case "keyword":
743                        switch (prog1(S.token.value, next)) {
744                            case "break":
745                                return break_cont("break");
746
747                            case "continue":
748                                return break_cont("continue");
749
750                            case "debugger":
751                                semicolon();
752                                return as("debugger");
753
754                            case "do":
755                                return (function(body){
756                                        expect_token("keyword", "while");
757                                        return as("do", prog1(parenthesised, semicolon), body);
758                                })(in_loop(statement));
759
760                            case "for":
761                                return for_();
762
763                            case "function":
764                                return function_(true);
765
766                            case "if":
767                                return if_();
768
769                            case "return":
770                                if (S.in_function == 0)
771                                        croak("'return' outside of function");
772                                return as("return",
773                                          is("punc", ";")
774                                          ? (next(), null)
775                                          : can_insert_semicolon()
776                                          ? null
777                                          : prog1(expression, semicolon));
778
779                            case "switch":
780                                return as("switch", parenthesised(), switch_block_());
781
782                            case "throw":
783                                return as("throw", prog1(expression, semicolon));
784
785                            case "try":
786                                return try_();
787
788                            case "var":
789                                return prog1(var_, semicolon);
790
791                            case "const":
792                                return prog1(const_, semicolon);
793
794                            case "while":
795                                return as("while", parenthesised(), in_loop(statement));
796
797                            case "with":
798                                return as("with", parenthesised(), statement());
799
800                            default:
801                                unexpected();
802                        }
803                }
804        };
805
806        function labeled_statement(label) {
807                S.labels.push(label);
808                var start = S.token, stat = statement();
809                if (strict_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0]))
810                        unexpected(start);
811                S.labels.pop();
812                return as("label", label, stat);
813        };
814
815        function simple_statement() {
816                return as("stat", prog1(expression, semicolon));
817        };
818
819        function break_cont(type) {
820                var name = is("name") ? S.token.value : null;
821                if (name != null) {
822                        next();
823                        if (!member(name, S.labels))
824                                croak("Label " + name + " without matching loop or statement");
825                }
826                else if (S.in_loop == 0)
827                        croak(type + " not inside a loop or switch");
828                semicolon();
829                return as(type, name);
830        };
831
832        function for_() {
833                expect("(");
834                var has_var = is("keyword", "var");
835                if (has_var)
836                        next();
837                if (is("name") && is_token(peek(), "operator", "in")) {
838                        // for (i in foo)
839                        var name = S.token.value;
840                        next(); next();
841                        var obj = expression();
842                        expect(")");
843                        return as("for-in", has_var, name, obj, in_loop(statement));
844                } else {
845                        // classic for
846                        var init = is("punc", ";") ? null : has_var ? var_() : expression();
847                        expect(";");
848                        var test = is("punc", ";") ? null : expression();
849                        expect(";");
850                        var step = is("punc", ")") ? null : expression();
851                        expect(")");
852                        return as("for", init, test, step, in_loop(statement));
853                }
854        };
855
856        function function_(in_statement) {
857                var name = is("name") ? prog1(S.token.value, next) : null;
858                if (in_statement && !name)
859                        unexpected();
860                expect("(");
861                return as(in_statement ? "defun" : "function",
862                          name,
863                          // arguments
864                          (function(first, a){
865                                  while (!is("punc", ")")) {
866                                          if (first) first = false; else expect(",");
867                                          if (!is("name")) unexpected();
868                                          a.push(S.token.value);
869                                          next();
870                                  }
871                                  next();
872                                  return a;
873                          })(true, []),
874                          // body
875                          (function(){
876                                  ++S.in_function;
877                                  var loop = S.in_loop;
878                                  S.in_loop = 0;
879                                  var a = block_();
880                                  --S.in_function;
881                                  S.in_loop = loop;
882                                  return a;
883                          })());
884        };
885
886        function if_() {
887                var cond = parenthesised(), body = statement(), belse;
888                if (is("keyword", "else")) {
889                        next();
890                        belse = statement();
891                }
892                return as("if", cond, body, belse);
893        };
894
895        function block_() {
896                expect("{");
897                var a = [];
898                while (!is("punc", "}")) {
899                        if (is("eof")) unexpected();
900                        a.push(statement());
901                }
902                next();
903                return a;
904        };
905
906        var switch_block_ = curry(in_loop, function(){
907                expect("{");
908                var a = [], cur = null;
909                while (!is("punc", "}")) {
910                        if (is("eof")) unexpected();
911                        if (is("keyword", "case")) {
912                                next();
913                                cur = [];
914                                a.push([ expression(), cur ]);
915                                expect(":");
916                        }
917                        else if (is("keyword", "default")) {
918                                next();
919                                expect(":");
920                                cur = [];
921                                a.push([ null, cur ]);
922                        }
923                        else {
924                                if (!cur) unexpected();
925                                cur.push(statement());
926                        }
927                }
928                next();
929                return a;
930        });
931
932        function try_() {
933                var body = block_(), bcatch, bfinally;
934                if (is("keyword", "catch")) {
935                        next();
936                        expect("(");
937                        if (!is("name"))
938                                croak("Name expected");
939                        var name = S.token.value;
940                        next();
941                        expect(")");
942                        bcatch = [ name, block_() ];
943                }
944                if (is("keyword", "finally")) {
945                        next();
946                        bfinally = block_();
947                }
948                if (!bcatch && !bfinally)
949                        croak("Missing catch/finally blocks");
950                return as("try", body, bcatch, bfinally);
951        };
952
953        function vardefs() {
954                var a = [];
955                for (;;) {
956                        if (!is("name"))
957                                unexpected();
958                        var name = S.token.value;
959                        next();
960                        if (is("operator", "=")) {
961                                next();
962                                a.push([ name, expression(false) ]);
963                        } else {
964                                a.push([ name ]);
965                        }
966                        if (!is("punc", ","))
967                                break;
968                        next();
969                }
970                return a;
971        };
972
973        function var_() {
974                return as("var", vardefs());
975        };
976
977        function const_() {
978                return as("const", vardefs());
979        };
980
981        function new_() {
982                var newexp = expr_atom(false), args;
983                if (is("punc", "(")) {
984                        next();
985                        args = expr_list(")");
986                } else {
987                        args = [];
988                }
989                return subscripts(as("new", newexp, args), true);
990        };
991
992        function expr_atom(allow_calls) {
993                if (is("operator", "new")) {
994                        next();
995                        return new_();
996                }
997                if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
998                        return make_unary("unary-prefix",
999                                          prog1(S.token.value, next),
1000                                          expr_atom(allow_calls));
1001                }
1002                if (is("punc")) {
1003                        switch (S.token.value) {
1004                            case "(":
1005                                next();
1006                                return subscripts(prog1(expression, curry(expect, ")")), allow_calls);
1007                            case "[":
1008                                next();
1009                                return subscripts(array_(), allow_calls);
1010                            case "{":
1011                                next();
1012                                return subscripts(object_(), allow_calls);
1013                        }
1014                        unexpected();
1015                }
1016                if (is("keyword", "function")) {
1017                        next();
1018                        return subscripts(function_(false), allow_calls);
1019                }
1020                if (HOP(ATOMIC_START_TOKEN, S.token.type)) {
1021                        var atom = S.token.type == "regexp"
1022                                ? as("regexp", S.token.value[0], S.token.value[1])
1023                                : as(S.token.type, S.token.value);
1024                        return subscripts(prog1(atom, next), allow_calls);
1025                }
1026                unexpected();
1027        };
1028
1029        function expr_list(closing, allow_trailing_comma, allow_empty) {
1030                var first = true, a = [];
1031                while (!is("punc", closing)) {
1032                        if (first) first = false; else expect(",");
1033                        if (allow_trailing_comma && is("punc", closing)) break;
1034                        if (is("punc", ",") && allow_empty) {
1035                                a.push([ "atom", "undefined" ]);
1036                        } else {
1037                                a.push(expression(false));
1038                        }
1039                }
1040                next();
1041                return a;
1042        };
1043
1044        function array_() {
1045                return as("array", expr_list("]", !strict_mode, true));
1046        };
1047
1048        function object_() {
1049                var first = true, a = [];
1050                while (!is("punc", "}")) {
1051                        if (first) first = false; else expect(",");
1052                        if (!strict_mode && is("punc", "}"))
1053                                // allow trailing comma
1054                                break;
1055                        var type = S.token.type;
1056                        var name = as_property_name();
1057                        if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) {
1058                                a.push([ as_name(), function_(false), name ]);
1059                        } else {
1060                                expect(":");
1061                                a.push([ name, expression(false) ]);
1062                        }
1063                }
1064                next();
1065                return as("object", a);
1066        };
1067
1068        function as_property_name() {
1069                switch (S.token.type) {
1070                    case "num":
1071                    case "string":
1072                        return prog1(S.token.value, next);
1073                }
1074                return as_name();
1075        };
1076
1077        function as_name() {
1078                switch (S.token.type) {
1079                    case "name":
1080                    case "operator":
1081                    case "keyword":
1082                    case "atom":
1083                        return prog1(S.token.value, next);
1084                    default:
1085                        unexpected();
1086                }
1087        };
1088
1089        function subscripts(expr, allow_calls) {
1090                if (is("punc", ".")) {
1091                        next();
1092                        return subscripts(as("dot", expr, as_name()), allow_calls);
1093                }
1094                if (is("punc", "[")) {
1095                        next();
1096                        return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls);
1097                }
1098                if (allow_calls && is("punc", "(")) {
1099                        next();
1100                        return subscripts(as("call", expr, expr_list(")")), true);
1101                }
1102                if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) {
1103                        return prog1(curry(make_unary, "unary-postfix", S.token.value, expr),
1104                                     next);
1105                }
1106                return expr;
1107        };
1108
1109        function make_unary(tag, op, expr) {
1110                if ((op == "++" || op == "--") && !is_assignable(expr))
1111                        croak("Invalid use of " + op + " operator");
1112                return as(tag, op, expr);
1113        };
1114
1115        function expr_op(left, min_prec) {
1116                var op = is("operator") ? S.token.value : null;
1117                var prec = op != null ? PRECEDENCE[op] : null;
1118                if (prec != null && prec > min_prec) {
1119                        next();
1120                        var right = expr_op(expr_atom(true), prec);
1121                        return expr_op(as("binary", op, left, right), min_prec);
1122                }
1123                return left;
1124        };
1125
1126        function expr_ops() {
1127                return expr_op(expr_atom(true), 0);
1128        };
1129
1130        function maybe_conditional() {
1131                var expr = expr_ops();
1132                if (is("operator", "?")) {
1133                        next();
1134                        var yes = expression(false);
1135                        expect(":");
1136                        return as("conditional", expr, yes, expression(false));
1137                }
1138                return expr;
1139        };
1140
1141        function is_assignable(expr) {
1142                switch (expr[0]) {
1143                    case "dot":
1144                    case "sub":
1145                        return true;
1146                    case "name":
1147                        return expr[1] != "this";
1148                }
1149        };
1150
1151        function maybe_assign() {
1152                var left = maybe_conditional(), val = S.token.value;
1153                if (is("operator") && HOP(ASSIGNMENT, val)) {
1154                        if (is_assignable(left)) {
1155                                next();
1156                                return as("assign", ASSIGNMENT[val], left, maybe_assign());
1157                        }
1158                        croak("Invalid assignment");
1159                }
1160                return left;
1161        };
1162
1163        function expression(commas) {
1164                if (arguments.length == 0)
1165                        commas = true;
1166                var expr = maybe_assign();
1167                if (commas && is("punc", ",")) {
1168                        next();
1169                        return as("seq", expr, expression());
1170                }
1171                return expr;
1172        };
1173
1174        function in_loop(cont) {
1175                try {
1176                        ++S.in_loop;
1177                        return cont();
1178                } finally {
1179                        --S.in_loop;
1180                }
1181        };
1182
1183        return as("toplevel", (function(a){
1184                while (!is("eof"))
1185                        a.push(statement());
1186                return a;
1187        })([]));
1188
1189};
1190
1191/* -----[ Utilities ]----- */
1192
1193function curry(f) {
1194        var args = slice(arguments, 1);
1195        return function() { return f.apply(this, args.concat(slice(arguments))); };
1196};
1197
1198function prog1(ret) {
1199        if (ret instanceof Function)
1200                ret = ret();
1201        for (var i = 1, n = arguments.length; --n > 0; ++i)
1202                arguments[i]();
1203        return ret;
1204};
1205
1206function array_to_hash(a) {
1207        var ret = {};
1208        for (var i = 0; i < a.length; ++i)
1209                ret[a[i]] = true;
1210        return ret;
1211};
1212
1213function slice(a, start) {
1214        return Array.prototype.slice.call(a, start == null ? 0 : start);
1215};
1216
1217function characters(str) {
1218        return str.split("");
1219};
1220
1221function member(name, array) {
1222        for (var i = array.length; --i >= 0;)
1223                if (array[i] === name)
1224                        return true;
1225        return false;
1226};
1227
1228function HOP(obj, prop) {
1229        return Object.prototype.hasOwnProperty.call(obj, prop);
1230};
1231
1232/* -----[ Exports ]----- */
1233
1234exports.tokenizer = tokenizer;
1235exports.parse = parse;
1236exports.slice = slice;
1237exports.curry = curry;
1238exports.member = member;
1239exports.array_to_hash = array_to_hash;
1240exports.PRECEDENCE = PRECEDENCE;
1241exports.KEYWORDS_ATOM = KEYWORDS_ATOM;
1242exports.RESERVED_WORDS = RESERVED_WORDS;
1243exports.KEYWORDS = KEYWORDS;
1244exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN;
1245exports.OPERATORS = OPERATORS;
1246exports.is_alphanumeric_char = is_alphanumeric_char;
1247exports.is_identifier_char = is_identifier_char;
1248